""" تطبيق وحدة التسعير المتكاملة """ import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import plotly.express as px import plotly.graph_objects as go from datetime import datetime import random import os import time import io from modules.pricing.services.standard_pricing import StandardPricing from modules.pricing.services.unbalanced_pricing import UnbalancedPricing from modules.pricing.services.local_content import LocalContentCalculator from modules.pricing.services.price_prediction import PricePrediction from utils.excel_handler import export_to_excel from utils.helpers import format_number, format_currency class PricingApp: """وحدة التسعير المتكاملة""" def __init__(self): self.pricing_methods = [ "التسعير القياسي", "التسعير غير المتزن", "التسعير التنافسي", "التسعير الموجه بالربحية" ] # تهيئة خدمات التسعير self.standard_pricing = StandardPricing() self.unbalanced_pricing = UnbalancedPricing() self.local_content = LocalContentCalculator() self.price_prediction = PricePrediction() def render(self): """عرض واجهة وحدة التسعير""" st.markdown("

وحدة التسعير المتكاملة

", unsafe_allow_html=True) tabs = st.tabs([ "إنشاء تسعير جديد", "نموذج التسعير الشامل", "التسعير غير المتزن", "المحتوى المحلي" ]) with tabs[0]: self._render_new_pricing_tab() with tabs[1]: self._render_comprehensive_pricing_tab() with tabs[2]: self._render_unbalanced_pricing_tab() with tabs[3]: self._render_local_content_tab() def _render_new_pricing_tab(self): """عرض تبويب إنشاء تسعير جديد""" st.markdown("### إنشاء تسعير جديد") col1, col2 = st.columns(2) with col1: tender_name = st.text_input("اسم المناقصة") client = st.text_input("الجهة المالكة") pricing_method = st.selectbox("طريقة التسعير", self.pricing_methods) with col2: tender_number = st.text_input("رقم المناقصة") location = st.text_input("الموقع") submission_date = st.date_input("تاريخ التقديم") # خيارات بيانات البنود st.markdown("### بيانات البنود") data_source = st.radio( "مصدر بيانات البنود", ["إدخال يدوي", "استيراد من Excel", "استيراد من وحدة تحليل المستندات"] ) if data_source == "إدخال يدوي": # إنشاء بيانات افتراضية if 'manual_items' not in st.session_state: st.session_state.manual_items = pd.DataFrame({ 'رقم البند': [f"A{i}" for i in range(1, 6)], 'وصف البند': [ "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", "توريد وتركيب حديد التسليح للأساسات", "أعمال العزل المائي للأساسات", "أعمال الردم والدك للأساسات", "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة" ], 'الوحدة': ["م3", "طن", "م2", "م3", "م3"], 'الكمية': [250, 25, 500, 300, 120], 'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0], 'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0] }) # عرض جدول البنود مع إمكانية التعديل edited_items = st.data_editor( st.session_state.manual_items, use_container_width=True, hide_index=True, num_rows="dynamic" ) st.session_state.manual_items = edited_items elif data_source == "استيراد من Excel": uploaded_file = st.file_uploader("رفع ملف Excel", type=["xlsx", "xls"]) if uploaded_file is not None: st.success("تم رفع الملف بنجاح") # محاكاة قراءة الملف st.markdown("### معاينة البيانات المستوردة") # إنشاء بيانات افتراضية import_items = pd.DataFrame({ 'رقم البند': [f"A{i}" for i in range(1, 8)], 'وصف البند': [ "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", "توريد وتركيب حديد التسليح للأساسات", "أعمال العزل المائي للأساسات", "أعمال الردم والدك للأساسات", "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة", "توريد وتركيب حديد التسليح للأعمدة", "أعمال البلوك للجدران" ], 'الوحدة': ["م3", "طن", "م2", "م3", "م3", "طن", "م2"], 'الكمية': [250, 25, 500, 300, 120, 10, 400], 'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }) st.dataframe(import_items) if st.button("استيراد البيانات"): st.session_state.manual_items = import_items.copy() st.session_state.manual_items_modified = True st.success("تم استيراد البيانات بنجاح!") else: # استيراد من وحدة تحليل المستندات available_documents = [ "كراسة شروط مشروع توسعة مستشفى الملك فهد", "جدول كميات صيانة محطات المياه", "مخططات إنشاء مدرسة ثانوية" ] selected_doc = st.selectbox("اختر المستند", available_documents) if st.button("استيراد البيانات من تحليل المستند"): # محاكاة استيراد البيانات with st.spinner("جاري استيراد البيانات..."): time.sleep(2) # إنشاء بيانات افتراضية doc_items = pd.DataFrame({ 'رقم البند': [f"A{i}" for i in range(1, 8)], 'وصف البند': [ "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", "توريد وتركيب حديد التسليح للأساسات", "أعمال العزل المائي للأساسات", "أعمال الردم والدك للأساسات", "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة", "توريد وتركيب حديد التسليح للأعمدة", "أعمال البلوك للجدران" ], 'الوحدة': ["م3", "طن", "م2", "م3", "م3", "طن", "م2"], 'الكمية': [250, 25, 500, 300, 120, 10, 400], 'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }) st.session_state.manual_items = doc_items.copy() st.success("تم استيراد البيانات من تحليل المستند بنجاح!") st.dataframe(doc_items) # زر بدء التسعير if st.button("بدء التسعير"): # تحقق من صحة البيانات if 'manual_items' in st.session_state and not st.session_state.manual_items.empty: # حفظ بيانات التسعير الحالي st.session_state.current_pricing = { 'name': tender_name, 'number': tender_number, 'client': client, 'location': location, 'method': pricing_method, 'submission_date': submission_date, 'items': st.session_state.manual_items.copy(), 'status': 'جديد', 'created_at': datetime.now() } # الانتقال إلى تبويب نموذج التسعير الشامل st.success("تم إنشاء التسعير بنجاح! يمكنك الانتقال إلى نموذج التسعير الشامل.") else: st.error("يرجى إدخال بيانات البنود أولاً.") def _render_comprehensive_pricing_tab(self): """عرض تبويب نموذج التسعير الشامل""" st.markdown("### نموذج التسعير الشامل") # التحقق من وجود تسعير حالي if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None: st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.") return # عرض معلومات التسعير الحالي pricing = st.session_state.current_pricing col1, col2, col3 = st.columns(3) with col1: st.metric("اسم المناقصة", pricing['name']) st.metric("الجهة المالكة", pricing['client']) with col2: st.metric("رقم المناقصة", pricing['number']) st.metric("تاريخ التقديم", pricing['submission_date'].strftime("%Y-%m-%d")) with col3: st.metric("طريقة التسعير", pricing['method']) st.metric("الموقع", pricing['location']) # عرض البنود والتسعير st.markdown("### بنود التسعير") items = pricing['items'].copy() # إضافة أسعار الوحدة للمحاكاة if 'سعر الوحدة' in items.columns and (items['سعر الوحدة'] == 0).all(): items['سعر الوحدة'] = [ round(random.uniform(1000, 3000), 2), # الخرسانة round(random.uniform(5000, 7000), 2), # الحديد round(random.uniform(100, 200), 2), # العزل round(random.uniform(50, 100), 2), # الردم round(random.uniform(1200, 3500), 2), # الخرسانة للأعمدة ] if len(items) > 5: for i in range(5, len(items)): items.at[i, 'سعر الوحدة'] = round(random.uniform(500, 5000), 2) # حساب الإجمالي items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة'] # عرض الجدول مع إمكانية التعديل edited_items = st.data_editor( items, use_container_width=True, hide_index=True, disabled=('رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'الإجمالي') ) # حساب الإجمالي بعد التعديل edited_items['الإجمالي'] = edited_items['الكمية'] * edited_items['سعر الوحدة'] st.session_state.current_pricing['items'] = edited_items # حساب وعرض إجماليات التسعير total_price = edited_items['الإجمالي'].sum() st.markdown("### إجماليات التسعير") col1, col2, col3 = st.columns(3) with col1: st.metric("إجمالي التكاليف المباشرة", f"{total_price:,.2f} ريال") with col2: overhead_percentage = st.slider("نسبة المصاريف العامة والأرباح (%)", 5, 30, 15) overhead_value = total_price * overhead_percentage / 100 st.metric("المصاريف العامة والأرباح", f"{overhead_value:,.2f} ريال") with col3: grand_total = total_price + overhead_value st.metric("الإجمالي النهائي", f"{grand_total:,.2f} ريال") # رسم بياني لتوزيع التكاليف st.markdown("### تحليل التكاليف") # حساب النسب المئوية لكل بند pie_data = edited_items.copy() pie_data['نسبة من إجمالي التكاليف'] = pie_data['الإجمالي'] / total_price * 100 fig = px.pie( pie_data, values='نسبة من إجمالي التكاليف', names='وصف البند', title='توزيع التكاليف حسب البنود', hole=0.4 ) st.plotly_chart(fig, use_container_width=True) # أزرار العمليات col1, col2, col3 = st.columns(3) with col1: if st.button("حفظ التسعير"): st.success("تم حفظ التسعير بنجاح!") with col2: if st.button("تصدير إلى Excel"): st.success("تم تصدير التسعير إلى Excel بنجاح!") with col3: if st.button("تحليل المخاطر المالية"): st.success("تم إرسال الطلب إلى وحدة تحليل المخاطر!") def _render_unbalanced_pricing_tab(self): """عرض تبويب التسعير غير المتزن""" st.markdown("### التسعير غير المتزن") # التحقق من وجود تسعير حالي if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None: st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.") return # شرح التسعير غير المتزن with st.expander("ما هو التسعير غير المتزن؟", expanded=False): st.markdown(""" **التسعير غير المتزن** هو استراتيجية تسعير تقوم على توزيع التكاليف بين بنود المناقصة بشكل غير متساوٍ، مع الحفاظ على إجمالي قيمة العطاء. ### استراتيجيات التسعير غير المتزن: 1. **التحميل الأمامي (Front Loading)**: زيادة أسعار البنود المبكرة في المشروع للحصول على تدفق نقدي أفضل في بداية المشروع. 2. **التحميل الخلفي (Back Loading)**: زيادة أسعار البنود المتأخرة في المشروع. 3. **تحميل البنود المؤكدة**: زيادة أسعار البنود التي من المؤكد تنفيذها بالكميات المحددة. 4. **تخفيض أسعار البنود المحتملة**: تخفيض أسعار البنود التي قد تزيد كمياتها أثناء التنفيذ. ### مزايا التسعير غير المتزن: - تحسين التدفق النقدي للمشروع. - تعظيم الربحية في حالة التغييرات والأوامر التغييرية. - زيادة فرص الفوز بالمناقصة. ### مخاطر التسعير غير المتزن: - قد يتم رفض العطاء إذا كان عدم التوازن واضحاً. - قد تتأثر السمعة سلباً إذا تم استخدامه بشكل مفرط. - قد يؤدي إلى خسائر إذا لم يتم تنفيذ البنود ذات الأسعار العالية. """) # عرض بنود التسعير الحالي items = st.session_state.current_pricing['items'].copy() # إضافة عمود إستراتيجية التسعير if 'إستراتيجية التسعير' not in items.columns: items['إستراتيجية التسعير'] = 'متوازن' st.markdown("### إستراتيجية التسعير غير المتزن") # اختيار الإستراتيجية strategy = st.selectbox( "اختر إستراتيجية التسعير", [ "تحميل أمامي (Front Loading)", "تحميل البنود المؤكدة", "تخفيض البنود المحتمل زيادتها", "إستراتيجية مخصصة" ] ) # تطبيق الإستراتيجية المختارة if strategy == "تحميل أمامي (Front Loading)": # محاكاة تحميل أمامي items_count = len(items) early_items = items.iloc[:items_count//3].index middle_items = items.iloc[items_count//3:2*items_count//3].index late_items = items.iloc[2*items_count//3:].index # تطبيق الزيادة والنقصان for idx in early_items: items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.3 # زيادة 30% items.at[idx, 'إستراتيجية التسعير'] = 'زيادة' for idx in middle_items: items.at[idx, 'إستراتيجية التسعير'] = 'متوازن' for idx in late_items: items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.7 # نقص 30% items.at[idx, 'إستراتيجية التسعير'] = 'نقص' elif strategy == "تحميل البنود المؤكدة": # محاكاة - اعتبار بعض البنود مؤكدة confirmed_items = [0, 2, 4] # الأصفار-مستندة variable_items = [idx for idx in range(len(items)) if idx not in confirmed_items] # تطبيق الزيادة والنقصان for idx in confirmed_items: if idx < len(items): items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.25 # زيادة 25% items.at[idx, 'إستراتيجية التسعير'] = 'زيادة' for idx in variable_items: if idx < len(items): items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.85 # نقص 15% items.at[idx, 'إستراتيجية التسعير'] = 'نقص' elif strategy == "تخفيض البنود المحتمل زيادتها": # محاكاة - اعتبار بعض البنود محتمل زيادتها variable_items = [1, 3] # الأصفار-مستندة other_items = [idx for idx in range(len(items)) if idx not in variable_items] # تطبيق الزيادة والنقصان for idx in variable_items: if idx < len(items): items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.7 # نقص 30% items.at[idx, 'إستراتيجية التسعير'] = 'نقص' for idx in other_items: if idx < len(items): items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.15 # زيادة 15% items.at[idx, 'إستراتيجية التسعير'] = 'زيادة' else: # إستراتيجية مخصصة st.markdown("### تعديل أسعار البنود يدوياً") st.markdown("قم بتعديل أسعار البنود وإستراتيجية التسعير يدوياً.") # حساب الإجمالي بعد التعديل items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة'] # تعيين ألوان للإستراتيجيات def highlight_strategy(val): if val == 'زيادة': return 'background-color: #a8e6cf' elif val == 'نقص': return 'background-color: #ff9aa2' return '' # عرض الجدول مع تنسيق st.markdown("### بنود التسعير غير المتزن") styled_items = items.style.applymap(highlight_strategy, subset=['إستراتيجية التسعير']) st.dataframe(styled_items, use_container_width=True) # المقارنة بين التسعير المتوازن وغير المتوازن st.markdown("### مقارنة التسعير المتوازن وغير المتوازن") original_items = st.session_state.current_pricing['items'].copy() original_total = original_items['الإجمالي'].sum() unbalanced_total = items['الإجمالي'].sum() col1, col2, col3 = st.columns(3) with col1: st.metric("إجمالي التسعير المتوازن", f"{original_total:,.2f} ريال") with col2: st.metric("إجمالي التسعير غير المتوازن", f"{unbalanced_total:,.2f} ريال") with col3: diff = unbalanced_total - original_total st.metric("الفرق", f"{diff:,.2f} ريال", delta=f"{diff/original_total*100:.1f}%") # المعايرة للحفاظ على إجمالي التسعير if abs(diff) > 1: # إذا كان هناك فرق كبير if st.button("معايرة الأسعار للحفاظ على إجمالي التسعير"): # تعديل الأسعار للحفاظ على إجمالي التكلفة adjustment_factor = original_total / unbalanced_total items['سعر الوحدة'] = items['سعر الوحدة'] * adjustment_factor items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة'] st.success(f"تم تعديل الأسعار للحفاظ على إجمالي التسعير الأصلي ({original_total:,.2f} ريال)") st.dataframe(items, use_container_width=True) # رسم بياني للمقارنة st.markdown("### تحليل بصري للتسعير غير المتوازن") # إعداد البيانات للرسم البياني chart_data = pd.DataFrame({ 'وصف البند': original_items['وصف البند'], 'التسعير المتوازن': original_items['الإجمالي'], 'التسعير غير المتوازن': items['الإجمالي'] }) # رسم بياني شريطي للمقارنة fig = go.Figure() fig.add_trace(go.Bar( x=chart_data['وصف البند'], y=chart_data['التسعير المتوازن'], name='التسعير المتوازن', marker_color='rgb(55, 83, 109)' )) fig.add_trace(go.Bar( x=chart_data['وصف البند'], y=chart_data['التسعير غير المتوازن'], name='التسعير غير المتوازن', marker_color='rgb(26, 118, 255)' )) fig.update_layout( title='مقارنة بين التسعير المتوازن وغير المتوازن', xaxis_tickfont_size=14, yaxis=dict( title='الإجمالي (ريال)', titlefont_size=16, tickfont_size=14, ), legend=dict( x=0, y=1.0, bgcolor='rgba(255, 255, 255, 0)', bordercolor='rgba(255, 255, 255, 0)' ), barmode='group', bargap=0.15, bargroupgap=0.1 ) st.plotly_chart(fig, use_container_width=True) # زر حفظ التسعير غير المتوازن if st.button("