diff --git "a/modules/pricing/pricing_app.py" "b/modules/pricing/pricing_app.py" new file mode 100644--- /dev/null +++ "b/modules/pricing/pricing_app.py" @@ -0,0 +1,4338 @@ +""" +تطبيق وحدة التسعير المتكاملة +""" + +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 + +# ملاحظة: نحن لا نستخدم st.set_page_config هنا لأنه يجب أن يكون في ملف app.py الرئيسي فقط + +# تحسين المظهر العام باستخدام CSS +st.markdown(""" + +""", unsafe_allow_html=True) + +# تحسين شكل الأزرار بشكل متقدم +st.markdown(""" + +""", unsafe_allow_html=True) + +# وظيفة مساعدة لإنشاء أزرار بتنسيقات مختلفة +def styled_button(label, key, type="primary", on_click=None, args=None, full_width=False, icon=None): + """ + إنشاء زر بتنسيق معين + :param label: نص الزر + :param key: مفتاح الزر الفريد + :param type: نوع التنسيق ('primary', 'secondary', 'success', 'warning', 'danger', 'info', 'glass', 'flat') + :param on_click: الدالة التي سيتم تنفيذها عند النقر + :param args: معاملات الدالة + :param full_width: هل يأخذ الزر العرض كاملاً + :param icon: أيقونة لعرضها قبل النص (emoji أو HTML) + :return: زر مُنسّق + """ + with st.container(): + btn_class = f"{type}-btn" + if icon: + btn_class += " action-btn" + label = f"{icon} {label}" + + st.markdown(f'
', unsafe_allow_html=True) + clicked = st.button(label, key=key, on_click=on_click, args=args, use_container_width=full_width) + st.markdown('
', unsafe_allow_html=True) + return clicked + +# وظيفة لإنشاء أزرار أيقونات صغيرة +def icon_button(icon, key, type="primary", on_click=None, args=None, tooltip=""): + """ + إنشاء زر أيقونة صغير + :param icon: الأيقونة (emoji أو HTML) + :param key: مفتاح الزر الفريد + :param type: نوع التنسيق + :param on_click: الدالة التي سيتم تنفيذها عند النقر + :param args: معاملات الدالة + :param tooltip: تلميح عند تمرير المؤشر فوق الزر + :return: زر أيقونة + """ + with st.container(): + st.markdown(f'
', unsafe_allow_html=True) + clicked = st.button(icon, key=key, on_click=on_click, args=args) + st.markdown('
', unsafe_allow_html=True) + return clicked + +from modules.pricing.services.standard_pricing import StandardPricing +from modules.pricing.services.unbalanced_pricing import UnbalancedPricing +from modules.pricing.services.local_content_calculator import LocalContentCalculator +from modules.pricing.services.price_prediction import PricePrediction +from modules.pricing.services.construction_cost_calculator import ConstructionCostCalculator +from modules.pricing.services.construction_templates import ConstructionTemplates +from modules.pricing.services.templates_catalog.templates_catalog import TemplatesCatalog +from utils.excel_handler import export_to_excel +from utils.pdf_handler import export_pricing_to_pdf, export_pricing_with_analysis_to_pdf +from utils.helpers import format_number, format_currency, create_directory_if_not_exists + + +class PricingApp: + """وحدة التسعير المتكاملة""" + + def __init__(self): + """تهيئة وحدة التسعير المتكاملة""" + self.pricing_methods = [ + "التسعير القياسي", + "التسعير غير المتزن", + "التسعير التنافسي", + "التسعير الموجه بالربحية" + ] + + # تهيئة خدمات التسعير + self.standard_pricing = StandardPricing() + self.unbalanced_pricing = UnbalancedPricing() + self.local_content = LocalContentCalculator() + self.price_prediction = PricePrediction() + self.construction_calculator = ConstructionCostCalculator() + self.construction_templates = ConstructionTemplates() + self.templates_catalog = TemplatesCatalog(self.construction_templates) + + 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_item_analysis_tab() + + with tabs[2]: + self._render_comprehensive_pricing_tab() + + with tabs[3]: + self._render_unbalanced_pricing_tab() + + with tabs[4]: + self._render_local_content_tab() + + with tabs[5]: + self._render_construction_calculator_tab() + + with tabs[6]: + self._render_templates_catalog_tab() + + with tabs[7]: + self._render_utilities_tab() + + def _render_templates_catalog_tab(self): + """عرض تبويب كتالوج البنود النموذجية""" + + st.markdown("### كتالوج البنود النموذجية") + + # شرح كتالوج البنود النموذجية + with st.expander("دلي�� استخدام كتالوج البنود النموذجية", expanded=False): + st.markdown(""" + **كتالوج البنود النموذجية** هو مكتبة شاملة من البنود الجاهزة لمختلف أنواع الأعمال الإنشائية (خرسانة، حديد، عزل، تشطيبات، إلخ). + + ### مميزات الكتالوج: + - تفاصيل دقيقة للمواد والعمالة والمعدات المطلوبة لكل بند. + - تحليل تكلفة تفصيلي يمكن استخدامه مباشرة في عروض الأسعار. + - ربط مباشر مع حاسبة تكاليف البناء وحاسبة الأسعار. + + ### كيفية الاستخدام: + - استخدام البنود النموذجية مباشرة في مشاريعك. + - تعديل البنود النموذجية لتناسب متطلبات المشروع. + - إضافة بنود جديدة إلى الكتالوج للاستخدام المستقبلي. + """) + + # عرض الكتالوج باستخدام مكون TemplatesCatalog + self.templates_catalog.render() + + def _render_item_analysis_tab(self): + """عرض تبويب تحليل سعر البند""" + + st.markdown("### تحليل سعر البند") + + # التحقق من وجود تسعير حالي + if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None: + st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.") + return + + # اختيار البند للتحليل + if 'current_pricing' in st.session_state and st.session_state.current_pricing is not None: + items = st.session_state.current_pricing['items'] + item_options = items['رقم البند'].tolist() + selected_item = st.selectbox("اختر البند للتحليل", item_options, key="item_analysis_selector") + + if selected_item: + item_data = items[items['رقم البند'] == selected_item].iloc[0] + + st.markdown(f"### تحليل البند: {selected_item}") + st.markdown(f"**وصف البند**: {item_data['وصف البند']}") + st.markdown(f"**الوحدة**: {item_data['الوحدة']}") + st.markdown(f"**الكمية**: {item_data['الكمية']}") + st.markdown(f"**سعر الوحدة**: {item_data['سعر الوحدة']:,.2f} ريال") + + # تحليل مكونات السعر + st.markdown("### تحليل مكونات السعر") + + # عناصر التكلفة الافتراضية + cost_components = { + 'المواد': 0.6, # 60% من التكلفة + 'العمالة': 0.25, # 25% من التكلفة + 'المعدات': 0.1, # 10% من التكلفة + 'نفقات عامة': 0.05 # 5% من التكلفة + } + + # حساب تكلفة كل عنصر + unit_price = item_data['سعر الوحدة'] + component_values = {k: v * unit_price for k, v in cost_components.items()} + + # عرض مكونات التكلفة في جدول + components_df = pd.DataFrame({ + 'العنصر': component_values.keys(), + 'نسبة من التكلفة': [f"{v*100:.1f}%" for v in cost_components.values()], + 'القيمة (ريال)': [f"{v:,.2f}" for v in component_values.values()] + }) + + st.table(components_df) + + # رسم بياني لمكونات التكلفة + fig = px.pie( + names=list(component_values.keys()), + values=list(component_values.values()), + title='توزيع مكونات التكلفة' + ) + + st.plotly_chart(fig) + + # تحليل تاريخي للأسعار + st.markdown("### تحليل تاريخي للأسعار") + + # بيانات تاريخية افتراضية + historical_data = { + 'التاريخ': ['2020-01', '2020-07', '2021-01', '2021-07', '2022-01', '2022-07', '2023-01', '2023-07'], + 'السعر': [ + unit_price * 0.7, + unit_price * 0.75, + unit_price * 0.8, + unit_price * 0.85, + unit_price * 0.9, + unit_price * 0.95, + unit_price, + unit_price * 1.05 + ] + } + + hist_df = pd.DataFrame(historical_data) + + # رسم بياني للتحليل التاريخي + fig = px.line( + hist_df, + x='التاريخ', + y='السعر', + title='تطور سعر الوحدة عبر الزمن', + markers=True + ) + + st.plotly_chart(fig) + + # المقارنة مع الأسعار المرجعية + st.markdown("### المقارنة مع الأسعار المرجعية") + + # بيانات مرجعية افتراضية + reference_data = { + 'المصدر': ['قاعدة البيانات الداخلية', 'دليل الأسعار الاسترشادي', 'متوسط أسعار السوق', 'أسعار المشاريع المماثلة'], + 'السعر المرجعي': [ + unit_price * 0.95, + unit_price * 1.05, + unit_price * 1.1, + unit_price * 0.9 + ] + } + + ref_df = pd.DataFrame(reference_data) + ref_df['الفرق عن السعر الحالي'] = ref_df['السعر المرجعي'] - unit_price + ref_df['نسبة الفرق'] = (ref_df['الفرق عن السعر الحالي'] / unit_price * 100).round(2).astype(str) + '%' + + st.table(ref_df) + + def _render_new_pricing_tab(self): + """عرض تبويب إنشاء تسعير جديد""" + + st.markdown("### إنشاء تسعير جديد") + + col1, col2 = st.columns(2) + + with col1: + tender_name = st.text_input("اسم المناقصة", key="tender_name_input") + client = st.text_input("الجهة المالكة", key="client_input") + pricing_method = st.selectbox("طريقة التسعير", self.pricing_methods, key="pricing_method_selector") + + with col2: + tender_number = st.text_input("رقم المناقصة", key="tender_number_input") + location = st.text_input("الموقع", key="location_input") + submission_date = st.date_input("تاريخ التقديم", key="submission_date_input") + + # خيارات بيانات البنود + st.markdown("### بيانات البنود") + + data_source = st.radio( + "مصدر بيانات البنود", + ["إدخال يدوي", "استيراد من Excel", "استيراد من وحدة تحليل المستندات", "استيراد من وحدة المشاريع"], + key="data_source_radio" + ) + + if data_source == "إدخال يدوي": + # ضبط CSS لتحسين ظهور الواجهة العربية + st.markdown(""" + + """, unsafe_allow_html=True) + + # تهيئة قائمة الوحدات المتاحة + unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"] + + # إنشاء بيانات افتراضية إذا لم تكن موجودة + if 'manual_items' not in st.session_state: + # إنشاء DataFrame فارغ + manual_items = pd.DataFrame(columns=[ + 'رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي' + ]) + + # إضافة بضعة صفوف افتراضية + default_items = pd.DataFrame({ + 'رقم البند': ["A1", "A2", "A3", "A4", "A5"], + 'وصف البند': [ + "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", + "توريد وتركيب حديد التسليح للأساسات", + "أعمال العزل المائي للأساسات", + "أعمال الردم والدك للأساسات", + "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة" + ], + 'الوحدة': ["م3", "طن", "م2", "م3", "م3"], + 'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0], + 'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0], + 'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0] + }) + + manual_items = pd.concat([manual_items, default_items]) + st.session_state.manual_items = manual_items + + # عرض واجهة إدخال البنود + st.markdown("### إدخال تفاصيل البنود") + + # التحقق من استخدام طريقة الإدخال البسيطة + use_simple_input = st.checkbox("استخدام طريقة الإدخال البسيطة", value=True, key="use_simple_input_checkbox") + + if use_simple_input: + # عرض البنود الحالية كجدول للعرض فقط + st.markdown("### جدول البنود الحالية") + st.dataframe(st.session_state.manual_items, use_container_width=True, hide_index=True) + + # إضافة بند جديد + st.markdown("### إضافة بند جديد") + col1, col2 = st.columns(2) + + with col1: + new_id = st.text_input("رقم البند", value=f"A{len(st.session_state.manual_items)+1}", key="new_item_id") + new_desc = st.text_area("وصف البند", value="", key="new_item_description") + + with col2: + new_unit = st.selectbox("الوحدة", options=unit_options, key="new_item_unit") + new_qty = st.number_input("الكمية", value=0.0, min_value=0.0, format="%.2f", key="new_item_qty") + new_price = st.number_input("سعر الوحدة", value=0.0, min_value=0.0, format="%.2f", key="new_item_price") + + new_total = new_qty * new_price + st.info(f"إجمالي البند الجديد: {new_total:,.2f} ريال") + + if st.button("إضافة البند", key="add_item_button"): + # التحقق من صحة البيانات + if new_id and new_desc and new_qty > 0: + # إنشاء صف جديد + new_row = pd.DataFrame({ + 'رقم البند': [new_id], + 'وصف البند': [new_desc], + 'الوحدة': [new_unit], + 'الكمية': [float(new_qty)], + 'سعر الوحدة': [float(new_price)], + 'الإجمالي': [float(new_total)] + }) + + # إضافة الصف إلى DataFrame + st.session_state.manual_items = pd.concat([st.session_state.manual_items, new_row], ignore_index=True) + st.success("تم إضافة البند بنجاح!") + st.rerun() + else: + st.error("يرجى ملء جميع الحقول المطلوبة: رقم البند، الوصف، والكمية يجب أن تكون أكبر من صفر.") + + # تعديل البنود الحالية + st.markdown("### تعديل البنود الحالية") + + # تحديد البند المراد تعديله + item_to_edit = st.selectbox( + "اختر البند للتعديل", + options=st.session_state.manual_items['رقم البند'].tolist(), + format_func=lambda x: f"{x}: {st.session_state.manual_items[st.session_state.manual_items['رقم البند'] == x]['وصف البند'].values[0][:30]}...", + key="item_to_edit_selector" + ) + + if item_to_edit: + # الحصول على مؤشر الصف للبند المحدد + idx = st.session_state.manual_items[st.session_state.manual_items['رقم البند'] == item_to_edit].index[0] + row = st.session_state.manual_items.loc[idx] + + # إنشاء نموذج تعديل + col1, col2 = st.columns(2) + + with col1: + edited_id = st.text_input("رقم البند (تعديل)", value=row['رقم البند'], key="edit_id") + edited_desc = st.text_area("وصف البند (تعديل)", value=row['وصف البند'], key="edit_desc") + + with col2: + edited_unit = st.selectbox( + "الوحدة (تعديل)", + options=unit_options, + index=unit_options.index(row['الوحدة']) if row['الوحدة'] in unit_options else 0, + key="edit_unit" + ) + edited_qty = st.number_input("الكمية (تعديل)", value=float(row['الكمية']), min_value=0.0, format="%.2f", key="edit_qty") + edited_price = st.number_input("سعر الوحدة (تعديل)", value=float(row['سعر الوحدة']), min_value=0.0, format="%.2f", key="edit_price") + + edited_total = edited_qty * edited_price + st.info(f"إجمالي البند بعد التعديل: {edited_total:,.2f} ريال") + + col1, col2 = st.columns(2) + with col1: + if st.button("حفظ التعديلات", key="save_edit_button"): + # تحديث البند + st.session_state.manual_items.at[idx, 'رقم البند'] = edited_id + st.session_state.manual_items.at[idx, 'وصف البند'] = edited_desc + st.session_state.manual_items.at[idx, 'الوحدة'] = edited_unit + st.session_state.manual_items.at[idx, 'الكمية'] = edited_qty + st.session_state.manual_items.at[idx, 'سعر الوحدة'] = edited_price + st.session_state.manual_items.at[idx, 'الإجمالي'] = edited_total + + st.success("تم تحديث البند بنجاح!") + st.rerun() + + with col2: + if st.button("حذف هذا البند", key="delete_item_button"): + st.session_state.manual_items = st.session_state.manual_items.drop(idx).reset_index(drop=True) + st.warning("تم حذف البند!") + st.rerun() + + # المجموع الكلي + total = st.session_state.manual_items['الإجمالي'].sum() + st.metric("المجموع الكلي", f"{total:,.2f} ريال") + + # جعل هذه البيانات متاحة للاستخدام في الخطوات التالية + edited_items = st.session_state.manual_items.copy() + + else: + # عرض رسالة توضح أن طريقة الإدخال البسيطة هي الأفضل + st.warning("لتجنب مشاكل عدم التوافق في أنواع البيانات، يُفضل استخدام طريقة الإدخال البسيطة.") + + # محاولة استخدام المحرر القياسي مع معالجة الأخطاء + try: + # تحويل البيانات إلى الأنواع المناسبة + for col in st.session_state.manual_items.columns: + if col in ['رقم البند', 'وصف البند', 'الوحدة']: + st.session_state.manual_items[col] = st.session_state.manual_items[col].astype(str) + + # عرض المحرر (للقراءة فقط) + st.dataframe(st.session_state.manual_items, use_container_width=True, hide_index=True) + + # إنشاء نظام تعديل منفصل + st.markdown("### تعديل أسعار الوحدات") + + for idx, row in st.session_state.manual_items.iterrows(): + col1, col2 = st.columns([3, 1]) + + with col1: + st.text(f"{row['رقم البند']}: {row['وصف البند'][:50]}") + + with col2: + price = st.number_input( + f"سعر الوحدة ({row['الوحدة']})", + value=float(row['سعر الوحدة']), + min_value=0.0, + key=f"price_{idx}" + ) + + # تحديث السعر والإجمالي + st.session_state.manual_items.at[idx, 'سعر الوحدة'] = price + st.session_state.manual_items.at[idx, 'الإجمالي'] = price * row['الكمية'] + + # المجموع الكلي + total = st.session_state.manual_items['الإجمالي'].sum() + st.metric("المجموع الكلي", f"{total:,.2f} ريال") + + # جعل هذه البيانات متاحة للاستخدام في الخطوات التالية + edited_items = st.session_state.manual_items.copy() + + except Exception as e: + st.error(f"حدث خطأ: {str(e)}") + st.info("يرجى استخدام طريقة الإدخال البسيطة لتجنب هذه المشكلة.") + + 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({ + 'رقم البند': ["A1", "A2", "A3", "A4", "A5", "A6", "A7"], + 'وصف البند': [ + "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", + "توريد وتركيب حديد التسليح للأساسات", + "أعمال العزل المائي للأساسات", + "أعمال الردم والدك للأساسات", + "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة", + "توريد وتركيب حديد التسليح للأعمدة", + "أعمال البلوك للجدران" + ], + 'الوحدة': ["م3", "طن", "م2", "م3", "م3", "طن", "م2"], + 'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0, 10.0, 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.0] + }) + + st.dataframe(import_items) + + if st.button("استيراد البيانات", key="import_excel_button"): + st.session_state.manual_items = import_items.copy() + st.session_state.manual_items_modified = True + st.success("تم استيراد البيانات بنجاح!") + st.rerun() + + elif data_source == "استيراد من وحدة تحليل المستندات": + available_documents = [ + "كراسة شروط مشروع توسعة مستشفى الملك فهد", + "جدول كميات صيانة محطات المياه", + "مخططات إنشاء مدرسة ثانوية" + ] + + selected_doc = st.selectbox("اختر المستند", available_documents, key="document_selector") + + if styled_button("استيراد البيانات من تحليل المستند", key="import_doc_analysis_button", type="info", icon="📄", full_width=True): + # محاكاة استيراد البيانات + with st.spinner("جاري استيراد البيانات..."): + time.sleep(2) + + # إنشاء بيانات افتراضية + doc_items = pd.DataFrame({ + 'رقم البند': ["A1", "A2", "A3", "A4", "A5", "A6", "A7"], + 'وصف البند': [ + "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", + "توريد وتركيب حديد التسليح للأساسات", + "أعمال العزل المائي للأساسات", + "أعمال الردم والدك للأساسات", + "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة", + "توريد وتركيب حديد التسليح للأعمدة", + "أعمال البلوك للجدران" + ], + 'الوحدة': ["م3", "طن", "م2", "م3", "م3", "طن", "م2"], + 'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0, 10.0, 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.0] + }) + + st.session_state.manual_items = doc_items.copy() + st.success("تم استيراد البيانات من تحليل المستند بنجاح!") + st.dataframe(doc_items) + + elif data_source == "استيراد من وحدة المشاريع": + # قائمة المشاريع المتاحة للاستيراد منها + available_projects = [ + "مشروع تطوير طريق ا��ملك عبدالعزيز", + "مشروع إنشاء محطة تحلية المياه بالجبيل", + "مشروع توسعة مستشفى الملك فهد", + "مشروع إنشاء مجمع سكني بالرياض" + ] + + selected_project = st.selectbox("اختر المشروع", available_projects, key="project_selector") + + if styled_button("استيراد البيانات من المشروع", key="import_project_data_button", type="primary", icon="🏗️", full_width=True): + # محاكاة استيراد البيانات من المشروع + with st.spinner("جاري استيراد بيانات المشروع..."): + time.sleep(1.5) + + # إنشاء بيانات مشروع افتراضية بناءً على المشروع المختار + if selected_project == "مشروع تطوير طريق الملك عبدالعزيز": + project_items = pd.DataFrame({ + 'رقم البند': ["R1", "R2", "R3", "R4", "R5"], + 'وصف البند': [ + "أعمال الحفر والردم للطريق", + "توريد وتنفيذ طبقة الأساس", + "طبقة الأسفلت الأولى", + "طبقة الأسفلت النهائية", + "أعمال الدهانات والعلامات" + ], + 'الوحدة': ["م3", "م2", "م2", "م2", "م.ط"], + 'الكمية': [5000.0, 8000.0, 8000.0, 8000.0, 4000.0], + 'سعر الوحدة': [45.0, 120.0, 85.0, 95.0, 25.0], + 'الإجمالي': [225000.0, 960000.0, 680000.0, 760000.0, 100000.0] + }) + elif selected_project == "مشروع إنشاء محطة تحلية المياه بالجبيل": + project_items = pd.DataFrame({ + 'رقم البند': ["W1", "W2", "W3", "W4", "W5"], + 'وصف البند': [ + "أعمال الخرسانة المسلحة للخزانات", + "توريد وتركيب معدات التحلية", + "أعمال التمديدات والأنابيب", + "تشطيبات المباني الإدارية", + "أعمال الكهرباء والتحكم" + ], + 'الوحدة': ["م3", "قطعة", "م.ط", "م2", "مقطوعية"], + 'الكمية': [1800.0, 12.0, 5000.0, 1200.0, 1.0], + 'سعر الوحدة': [1200.0, 250000.0, 850.0, 750.0, 1500000.0], + 'الإجمالي': [2160000.0, 3000000.0, 4250000.0, 900000.0, 1500000.0] + }) + elif selected_project == "مشروع توسعة مستشفى الملك فهد": + project_items = pd.DataFrame({ + 'رقم البند': ["H1", "H2", "H3", "H4", "H5", "H6"], + 'وصف البند': [ + "أعمال الهيكل الخرساني", + "أعمال البناء والجدران", + "التشطيبات الداخلية", + "الأنظمة الكهربائية والميكانيكية", + "تجهيزات طبية", + "أعمال الموقع العام" + ], + 'الوحدة': ["م3", "م2", "م2", "غرفة", "قطعة", "م2"], + 'الكمية': [2800.0, 4500.0, 7500.0, 120.0, 45.0, 3000.0], + 'سعر الوحدة': [1500.0, 650.0, 1200.0, 35000.0, 80000.0, 350.0], + 'الإجمالي': [4200000.0, 2925000.0, 9000000.0, 4200000.0, 3600000.0, 1050000.0] + }) + else: # مشروع إنشاء مجمع سكني بالرياض + project_items = pd.DataFrame({ + 'رقم البند': ["B1", "B2", "B3", "B4", "B5", "B6", "B7"], + 'وصف البند': [ + "الهياكل الخرسانية للفلل", + "الجدران والقواطع الداخلية", + "التشطيبات الخارجية", + "التشطيبات الداخلية", + "أعمال الكهرباء والسباكة", + "الملحقات والحدائق", + "البنية التحتية للموقع" + ], + 'الوحدة': ["م3", "م2", "م2", "م2", "فيلا", "م2", "مقطوعية"], + 'الكمية': [3500.0, 12000.0, 8000.0, 18000.0, 25.0, 5000.0, 1.0], + 'سعر الوحدة': [1350.0, 450.0, 380.0, 750.0, 85000.0, 280.0, 2500000.0], + 'الإجمالي': [4725000.0, 5400000.0, 3040000.0, 13500000.0, 2125000.0, 1400000.0, 2500000.0] + }) + + st.session_state.manual_items = project_items.copy() + st.success(f"تم استيراد بيانات المشروع '{selected_project}' بنجاح!") + st.dataframe(project_items) + + # زر بدء التسعير + if styled_button("بدء التسعير", key="start_pricing_button", type="success", icon="✅", full_width=True): + # تحقق من صحة البيانات + if 'manual_items' in st.session_state and not st.session_state.manual_items.empty: + # التأكد من حساب الإجمالي قبل الحفظ + st.session_state.manual_items['الإجمالي'] = st.session_state.manual_items['الكمية'] * st.session_state.manual_items['سعر الوحدة'] + + # حفظ بيانات التسعير الحالي + 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['سعر الوحدة'] + + # عرض البنود + st.dataframe(items, use_container_width=True, hide_index=True) + + + # ✅ التوصية الذكية باستخدام OpenAI + with st.expander("🔍 توليد توصية ذكية باستخدام AI"): + if styled_button("توليد توصية ذك��ة باستخدام AI", key="gen_ai_recommendation_btn", type="secondary", icon="🤖", full_width=True): + import openai + import os + + # تهيئة عميل OpenAI - استخدام واجهة الإصدار الجديد فقط (1.0.0 وما فوق) + api_key = os.environ.get("ai") + client = openai.OpenAI(api_key=api_key) + + items_df = items.copy() + prompt = f"""قم بتحليل الجدول التالي للبنود في مشروع إنشاء، وقدم توصية ذكية لتحسين التسعير وضمان التوازن المالي. الجدول يحتوي على البنود، الكميات، الأسعار، والإجماليات:\n\n{items_df.to_string(index=False)}\n\nالتوصية:\n""" + + try: + with st.spinner("جاري توليد التوصية..."): + # استخدام واجهة OpenAI الجديدة + response = client.chat.completions.create( + model="gpt-4o", # استخدام أحدث نموذج من OpenAI GPT-4o + messages=[ + {"role": "system", "content": "أنت خبير في تسعير مشاريع البناء والبنية التحتية."}, + {"role": "user", "content": prompt} + ], + temperature=0.4, + max_tokens=500 + ) + + # استخراج محتوى الرسالة من واجهة OpenAI الجديدة + recommendation = response.choices[0].message.content + + st.success("تم توليد التوصية بنجاح!") + st.markdown("#### التوصية الذكية:") + st.info(recommendation) + + except Exception as e: + st.error(f"حدث خطأ أثناء الاتصال بنموذج OpenAI: {e}") + st.info("يجب التأكد من تثبيت أحدث إصدار من مكتبة OpenAI: `pip install openai --upgrade`") + + # واجهة تعديل أسعار الوحدات + st.markdown("### تعديل أسعار الوحدات") + + # تقسيم البنود إلى مجموعتين للعرض + col1, col2 = st.columns(2) + half = len(items) // 2 + len(items) % 2 + + with col1: + for idx in range(half): + if idx < len(items): + row = items.iloc[idx] + price = st.number_input( + f"{row['رقم البند']}: {row['وصف البند'][:30]}... ({row['الوحدة']})", + value=float(row['سعر الوحدة']), + min_value=0.0, + key=f"price1_{idx}" + ) + items.at[idx, 'سعر الوحدة'] = price + items.at[idx, 'الإجمالي'] = price * items.at[idx, 'الكمية'] + + with col2: + for idx in range(half, len(items)): + row = items.iloc[idx] + price = st.number_input( + f"{row['رقم البند']}: {row['وصف البند'][:30]}... ({row['الوحدة']})", + value=float(row['سعر الوحدة']), + min_value=0.0, + key=f"price2_{idx}" + ) + items.at[idx, 'سعر الوحدة'] = price + items.at[idx, 'الإجمالي'] = price * items.at[idx, 'الكمية'] + + # حساب وعرض إجماليات التسعير + total_price = 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 = 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 styled_button("حفظ التسعير", key="save_comprehensive_pricing_button", type="primary", icon="💾"): + # تحديث بيانات التسعير الحالي + st.session_state.current_pricing['items'] = items.copy() + st.success("تم حفظ التسعير بنجاح!") + + with col2: + if styled_button("تصدير إلى Excel", key="export_to_excel_button", type="info", icon="📊"): + try: + # إنشاء دليل الصادرات إذا لم يكن موجودًا + export_dir = "exports" + create_directory_if_not_exists(export_dir) + + # تصدير البيانات إلى ملف Excel + file_path = os.path.join(export_dir, f"تسعير_{tender_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx") + export_to_excel(items, file_path, tender_name) + + st.success(f"تم تصدير التسعير إلى Excel بنجاح! مسار الملف: {file_path}") + except Exception as e: + st.error(f"حدث خطأ أثناء تصدير البيانات: {str(e)}") + + if styled_button("تصدير إلى PDF", key="export_to_pdf_button", type="info", icon="📄"): + try: + # إنشاء دليل الصادرات إذا لم يكن موجودًا + export_dir = "exports" + create_directory_if_not_exists(export_dir) + + # تصدير البيانات إلى ملف PDF + file_path = os.path.join(export_dir, f"تسعير_{tender_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf") + + # التحقق من وجود بيانات تحليل الأسعار + if 'items_price_analysis' in st.session_state and st.session_state.items_price_analysis: + # استخراج المعلومات الأساسية للمشروع + project_info = { + 'اسم_المشروع': tender_name, + 'وصف_المشروع': f"مناقصة رقم: {tender_number} - الجهة المالكة: {client} - الموقع: {location}" + } + + # تصدير البيانات مع تحليل الأسعار + export_pricing_with_analysis_to_pdf( + items, + st.session_state.items_price_analysis, + file_path, + title=f"تسعير مناقصة: {tender_name}", + project_info=project_info + ) + else: + # تصدير البيانات بدون تحليل الأسعار + export_pricing_to_pdf( + items, + file_path, + title=f"تسعير مناقصة: {tender_name}", + description=f"مناقصة رقم: {tender_number} - الجهة المالكة: {client} - الموقع: {location}" + ) + + st.success(f"تم تصدير التسعير إلى PDF بنجاح! مسار الملف: {file_path}") + except Exception as e: + st.error(f"حدث خطأ أثناء تصدير البيانات: {str(e)}") + + with col3: + if styled_button("تحليل المخاطر المالية", key="financial_risk_analysis_button", type="warning", icon="⚠️"): + 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)", + "تحميل البنود المؤكدة", + "تخفيض البنود المحتمل زيادتها", + "إستراتيجية مخصصة" + ], + key="pricing_strategy_selector" + ) + + # تطبيق الإستراتيجية المختارة + 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("قم بتعديل أسعار البنود وإستراتيجية التسعير يدوياً من خلال النموذج أدناه.") + + # إضافة واجهة لتعديل الأسعار يدوياً + if 'edit_items' not in st.session_state: + st.session_state.edit_items = items.copy() + + # عرض نموذج التعديل لكل بند + for index, row in items.iterrows(): + with st.container(): + col1, col2, col3, col4 = st.columns([3, 1, 1, 1]) + + with col1: + st.markdown(f"**البند:** {row['وصف البند']}") + + with col2: + original_price = st.session_state.current_pricing['items'].iloc[index]['سعر الوحدة'] + new_price = st.number_input( + "سعر الوحدة الجديد", + min_value=0.01, + value=float(row['سعر الوحدة']), + key=f"price_{index}" + ) + items.at[index, 'سعر الوحدة'] = new_price + + with col3: + percent_change = ((new_price - original_price) / original_price) * 100 + st.metric( + "نسبة التغيير", + f"{percent_change:.1f}%", + delta=f"{new_price - original_price:.2f}" + ) + + with col4: + strategy_options = ['متوازن', 'زيادة', 'نقص'] + current_strategy = row['إستراتيجية التسعير'] + strategy_index = strategy_options.index(current_strategy) if current_strategy in strategy_options else 0 + + new_strategy = st.selectbox( + "الإستراتيجية", + options=strategy_options, + index=strategy_index, + key=f"strategy_{index}" + ) + items.at[index, 'إستراتيجية التسعير'] = new_strategy + + st.markdown("---") + + # حساب الإجمالي بعد التعديل + items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة'] + + # تعيين ألوان للإستراتيجيات وتنسيق الجدول بشكل متقدم + def highlight_row(row): + strategy = row['إستراتيجية التسعير'] + styles = [''] * len(row) + + # تطبيق لون خلفية لكل صف حسب الإستراتيجية + if strategy == 'زيادة': + background = 'linear-gradient(90deg, rgba(168, 230, 207, 0.3), rgba(168, 230, 207, 0.1))' + text_color = '#1F7A8C' + elif strategy == 'نقص': + background = 'linear-gradient(90deg, rgba(255, 154, 162, 0.3), rgba(255, 154, 162, 0.1))' + text_color = '#9D2A45' + else: + background = 'linear-gradient(90deg, rgba(220, 237, 255, 0.3), rgba(220, 237, 255, 0.1))' + text_color = '#555555' + + # تطبيق النمط على جميع الخلايا في الصف + for i in range(len(styles)): + styles[i] = f'background: {background}; color: {text_color}; border-bottom: 1px solid #ddd;' + + # تطبيق نمط خاص على خلية الإستراتيجية + if strategy == 'زيادة': + styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #a8e6cf; color: #007263; font-weight: bold; border-radius: 5px; text-align: center;' + elif strategy == 'نقص': + styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #ff9aa2; color: #9D2A45; font-weight: bold; border-radius: 5px; text-align: center;' + else: + styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #dceeff; color: #555555; font-weight: bold; border-radius: 5px; text-align: center;' + + # تنسيق عمود السعر + price_idx = list(row.index).index('سعر الوحدة') + styles[price_idx] = styles[price_idx] + 'font-weight: bold;' + + # تنسيق عمود الإجمالي + total_idx = list(row.index).index('الإجمالي') + styles[total_idx] = styles[total_idx] + 'font-weight: bold;' + + return styles + + # عرض الجدول مع تنسيق متقدم + st.markdown("

بنود التسعير غير المتزن

", unsafe_allow_html=True) + + # تطبيق التنسيق على الجدول + styled_items = items.style.apply(highlight_row, axis=1) + + # تنسيق تنسيق الأرقام + styled_items = styled_items.format({ + 'الكمية': '{:,.2f}', + 'سعر الوحدة': '{:,.2f}', + 'الإجمالي': '{:,.2f}' + }) + + st.dataframe(styled_items, use_container_width=True, height=None) + + # المقارنة بين التسعير المتوازن وغير المتوازن + st.markdown("

مقارنة التسعير المتوازن وغير المتوازن

", unsafe_allow_html=True) + + original_items = st.session_state.current_pricing['items'].copy() + original_total = original_items['الإجمالي'].sum() + unbalanced_total = items['الإجمالي'].sum() + + # عرض بطاقات المقارنة بتصميم متقدم + st.markdown(""" + + """, unsafe_allow_html=True) + + col1, col2, col3 = st.columns(3) + + with col1: + st.markdown(""" +
+
إجمالي التسعير المتوازن
+
{:,.2f} ريال
+
التسعير الأصلي
+
+ """.format(original_total), unsafe_allow_html=True) + + with col2: + st.markdown(""" +
+
إجمالي التسعير غير المتوازن
+
{:,.2f} ريال
+
بعد إعادة توزيع الأسعار
+
+ """.format( + unbalanced_total, + "positive-delta" if unbalanced_total > original_total else "negative-delta" if unbalanced_total < original_total else "neutral-delta" + ), unsafe_allow_html=True) + + with col3: + diff = unbalanced_total - original_total + delta_percent = diff/original_total*100 if original_total > 0 else 0 + + st.markdown(""" +
+
الفرق بين التسعيرين
+
{:,.2f} ريال
+
نسبة الفرق: {:+.1f}%
+
+ """.format( + diff, + "positive-delta" if diff > 0 else "negative-delta" if diff < 0 else "neutral-delta", + delta_percent + ), unsafe_allow_html=True) + + # المعايرة للحفاظ على إجمالي التسعير + if abs(diff) > 1: # إذا كان هناك فرق كبير + if styled_button("معايرة الأسعار للحفاظ على إجمالي التسعير", key="calibrate_prices_button", type="primary", icon="⚖️", full_width=True): + # تعديل الأسعار للحفاظ على إجمالي التكلفة + 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("

تحليل بصري للتسعير غير المتوازن

", unsafe_allow_html=True) + + # إعداد البيانات للرسم البياني + chart_data = pd.DataFrame({ + 'وصف البند': original_items['وصف البند'], + 'التسعير المتوازن': original_items['الإجمالي'], + 'التسعير غير المتوازن': items['الإجمالي'] + }) + + # إضافة عمود للنسبة المئوية للتغيير + chart_data['نسبة التغيير'] = (chart_data['التسعير غير المتوازن'] - chart_data['التسعير المتوازن']) / chart_data['التسعير المتوازن'] * 100 + + # تحديد لون الأعمدة بناءً على نسبة التغيير + bar_colors = [] + for change in chart_data['نسبة التغيير']: + if change > 5: # زيادة كبيرة + bar_colors.append('#1F7A8C') # أزرق مخضر + elif change > 0: # زيادة صغيرة + bar_colors.append('#81B29A') # أخضر فاتح + elif change > -5: # نقص صغير + bar_colors.append('#F2CC8F') # أصفر + else: # نقص كبير + bar_colors.append('#E07A5F') # أحمر + + # التبويب بين مخططات مختلفة للمقارنة + chart_tabs = st.tabs(["مخطط شريطي", "مخطط مقارنة", "مخطط نسبة التغيير"]) + + with chart_tabs[0]: # رسم بياني شريطي + # رسم بياني شريطي للمقارنة + fig = go.Figure() + + fig.add_trace(go.Bar( + x=chart_data['وصف البند'], + y=chart_data['التسعير المتوازن'], + name='التسعير المتوازن', + marker_color='rgba(55, 83, 109, 0.7)' + )) + + fig.add_trace(go.Bar( + x=chart_data['وصف البند'], + y=chart_data['التسعير غير المتوازن'], + name='التسعير غير المتوازن', + marker_color=bar_colors + )) + + fig.update_layout( + title='مقارنة بين التسعير المتوازن وغير المتوازن', + xaxis_tickfont_size=14, + yaxis=dict( + title='الإجمالي (ريال)', + titlefont_size=16, + tickfont_size=14, + ), + legend=dict( + x=0.01, + y=0.99, + bgcolor='rgba(255, 255, 255, 0.8)', + bordercolor='rgba(0, 0, 0, 0.1)', + borderwidth=1 + ), + barmode='group', + bargap=0.15, + bargroupgap=0.1, + plot_bgcolor='rgba(240, 249, 255, 0.5)', + margin=dict(t=50, b=50, l=20, r=20) + ) + + st.plotly_chart(fig, use_container_width=True) + + with chart_tabs[1]: # رسم مقارنة + # رسم مقارنة بين التسعيرين + fig = go.Figure() + + # إضافة خط للتسعير المتوازن + fig.add_trace(go.Scatter( + x=chart_data['وصف البند'], + y=chart_data['التسعير المتوازن'], + name='التسعير المتوازن', + mode='lines+markers', + line=dict(color='rgb(55, 83, 109)', width=3), + marker=dict(size=10, color='rgb(55, 83, 109)') + )) + + # إضافة نقاط للتسعير غير المتوازن + fig.add_trace(go.Scatter( + x=chart_data['وصف البند'], + y=chart_data['التسعير غير المتوازن'], + name='التسعير غير المتوازن', + mode='lines+markers', + line=dict(color='rgb(26, 118, 255)', width=3), + marker=dict( + size=12, + color=bar_colors, + line=dict(width=2, color='white') + ) + )) + + # تحديثات التخطيط + fig.update_layout( + title='مقارنة مرئية بين استراتيجيات التسعير', + xaxis_tickfont_size=14, + yaxis=dict( + title='القيمة الإجمالية (ريال)', + titlefont_size=16, + tickfont_size=14, + gridcolor='rgba(200, 200, 200, 0.2)' + ), + legend=dict( + x=0.01, + y=0.99, + bgcolor='rgba(255, 255, 255, 0.8)', + bordercolor='rgba(0, 0, 0, 0.1)', + borderwidth=1 + ), + plot_bgcolor='rgba(240, 249, 255, 0.5)', + margin=dict(t=50, b=50, l=20, r=20) + ) + + st.plotly_chart(fig, use_container_width=True) + + with chart_tabs[2]: # مخطط نسبة التغيير + # مخطط للنسبة المئوية للتغيير + fig = go.Figure() + + # إضافة أعمدة لنسبة التغيير مع ألوان مختلفة حسب القيمة + fig.add_trace(go.Bar( + x=chart_data['وصف البند'], + y=chart_data['نسبة التغيير'], + name='نسبة التغيير', + marker_color=bar_colors, + text=[f"{val:.1f}%" for val in chart_data['نسبة التغيير']], + textposition='auto' + )) + + # إضافة خط أفقي عند الصفر + fig.add_shape( + type="line", + x0=-0.5, + y0=0, + x1=len(chart_data['وصف البند'])-0.5, + y1=0, + line=dict( + color="black", + width=2, + dash="dash", + ) + ) + + # تحديثات التخطيط + fig.update_layout( + title='نسبة التغيير في أسعار البنود (%)', + xaxis_tickfont_size=14, + yaxis=dict( + title='نسبة التغيير (%)', + titlefont_size=16, + tickfont_size=14, + gridcolor='rgba(200, 200, 200, 0.2)', + zeroline=True, + zerolinecolor='black', + zerolinewidth=2 + ), + plot_bgcolor='rgba(240, 249, 255, 0.5)', + margin=dict(t=50, b=50, l=20, r=20) + ) + + st.plotly_chart(fig, use_container_width=True) + + # إضافة جدول مع نسب التغيير + st.markdown("#### جدول مفصل بنسب التغيير") + + # إعداد بيانات الجدول + table_data = chart_data[['وصف البند', 'التسعير المتوازن', 'التسعير غير المتوازن', 'نسبة التغيير']] + + # تنسيق الجدول + def highlight_change(row): + change = row['نسبة التغيير'] + if change > 5: + return ['', '', '', 'background-color: rgba(31, 122, 140, 0.3); color: #1F7A8C; font-weight: bold;'] + elif change > 0: + return ['', '', '', 'background-color: rgba(129, 178, 154, 0.3); color: #2A9D8F; font-weight: bold;'] + elif change > -5: + return ['', '', '', 'background-color: rgba(242, 204, 143, 0.3); color: #BC6C25; font-weight: bold;'] + else: + return ['', '', '', 'background-color: rgba(224, 122, 95, 0.3); color: #AE2012; font-weight: bold;'] + + # تطبيق التنسيق + styled_table = table_data.style.apply(highlight_change, axis=1).format({ + 'التسعير المتوازن': '{:,.2f} ريال', + 'التسعير غير المتوازن': '{:,.2f} ريال', + 'نسبة التغيير': '{:+.1f}%' + }) + + st.dataframe(styled_table, use_container_width=True) + + # قسم لإضافة أو حذف البنود + st.markdown("### إدارة بنود التسعير") + + # إضافة بند جديد + with st.expander("إضافة بند جديد"): + col1, col2, col3, col4 = st.columns([3, 1, 1, 1]) + + with col1: + new_item_desc = st.text_input("وصف البند", placeholder="أدخل وصف البند الجديد") + + with col2: + new_item_unit = st.selectbox( + "الوحدة", + options=["م3", "م2", "متر طولي", "طن", "قطعة", "كجم", "لتر"], + index=0, + key="construction_item_unit" + ) + + with col3: + new_item_qty = st.number_input("الكمية", min_value=0.1, value=1.0, format="%.2f") + + with col4: + new_item_price = st.number_input("سعر الوحدة", min_value=0.1, value=100.0, format="%.2f") + + if st.button("إضافة البند"): + if new_item_desc: + # إنشاء رقم البند الجديد + new_id = f"UB{len(items)+1}" + + # إنشاء صف جديد + new_row = pd.DataFrame({ + 'رقم البند': [new_id], + 'وصف البند': [new_item_desc], + 'الوحدة': [new_item_unit], + 'الكمية': [float(new_item_qty)], + 'سعر الوحدة': [float(new_item_price)], + 'الإجمالي': [float(new_item_qty * new_item_price)], + 'إستراتيجية التسعير': ['متوازن'] + }) + + # إضافة الصف إلى DataFrame + items = pd.concat([items, new_row], ignore_index=True) + + # تحديث الإجمالي + items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة'] + + st.success(f"تم إضافة البند \"{new_item_desc}\" بنجاح!") + st.rerun() + else: + st.warning("يرجى إدخال وصف للبند") + + # حذف بند + with st.expander("حذف بند"): + if len(items) > 0: + # قائمة بالبنود الحالية + item_options = [f"{row['رقم البند']} - {row['وصف البند']}" for idx, row in items.iterrows()] + selected_item_to_delete = st.selectbox( + "اختر البند المراد حذفه", + options=item_options, + key="item_to_delete_selector" + ) + + # استخراج رقم البند + item_id_to_delete = selected_item_to_delete.split(" - ")[0] + + if st.button("حذف البند", key="delete_item_button_2"): + # البحث عن البند وحذفه + items = items[items['رقم البند'] != item_id_to_delete] + st.success(f"تم حذف البند {item_id_to_delete} بنجاح!") + st.rerun() + else: + st.info("لا توجد بنود لحذفها") + + # أزرار الحفظ والتصدير مع تصميم محسن + st.markdown("
", unsafe_allow_html=True) + st.markdown("

حفظ وتصدير البيانات

", unsafe_allow_html=True) + + st.markdown(""" + + """, unsafe_allow_html=True) + + col1, col2 = st.columns(2) + + with col1: + # بطاقة حفظ التسعير + st.markdown(""" +
+
💾
+
حفظ التسعير غير المتوازن
+
قم بحفظ التسعير الحالي في المشروع لاستخدامه لاحقاً في التقارير وفي إجمالي التسعير.
+
+ """, unsafe_allow_html=True) + + # زر حفظ التسعير غير المتوازن + if st.button("حفظ التسعير غير المتوازن", type="primary", use_container_width=True, key="save_unbalanced_pricing_button"): + st.session_state.current_pricing['items'] = items.copy() + st.session_state.current_pricing['method'] = "التسعير غير المتزن" + st.success("تم حفظ التسعير غير المتوازن بنجاح!") + st.balloons() # إضافة تأثير احتفالي عند الحفظ + + with col2: + # بطاقة تصدير التسعير + st.markdown(""" +
+
📊
+
تصدير البيانات
+
قم بتصدير جدول التسعير الحالي بصيغة CSV لاستخدامه في برامج أخرى مثل Excel.
+
+ """, unsafe_allow_html=True) + + # زر تصدير التسعير + export_button = st.button("تجهيز ملف للتصدير", use_container_width=True, key="prepare_export_file_button") + if export_button: + # تحويل البيانات إلى CSV + csv = items.to_csv(index=False) + st.success("تم تجهيز ملف التصدير بنجاح! يمكنك تنزيله الآن.") + # تقديم البيانات للتنزيل + st.download_button( + label="تنزيل ملف CSV", + data=csv, + file_name="unbalanced_pricing.csv", + mime="text/csv", + use_container_width=True + ) + + def _render_construction_calculator_tab(self): + """عرض تبويب حاسبة تكاليف البناء المتكاملة""" + + # استدعاء حاسبة تكاليف البناء المتكاملة الجديدة + from .construction_calculator import render_construction_calculator + + st.markdown("### حاسبة تكاليف البناء المتكاملة") + + # شرح حاسبة تكاليف البناء + with st.expander("دليل استخدام حاسبة تكاليف البناء", expanded=False): + st.markdown(""" + **حاسبة تكاليف البناء المتكاملة** هي أداة تساعد في حساب تكاليف البناء بشكل تفصيلي، مع مراعاة جميع عناصر التكلفة: + + ### مكونات التكلفة: + - المواد الخام + - العمالة + - المعدات + - المصاريف الإدارية + - هامش الربح + + ### كيفية الاستخدام: + 1. اختر إما حساب تكلفة بند واحد أو حساب تكلفة مشروع. + 2. أدخل بيانات المواد والعمالة والمعدات. + 3. حدد نسب المصاريف الإدارية وهامش الربح. + 4. اضبط عوامل التعديل حسب ظروف المشروع. + 5. احصل على تحليل تفصيلي للتكاليف والسعر النهائي. + + ### مميزات الحاسبة: + - قاعدة بيانات مدمجة للأسعار المرجعية للمواد والعمالة. + - تحليل نسب مساهمة كل عنصر في التكلفة. + - إمكانية تعديل عوامل التكلفة حسب الموقع والظروف. + - تصدير النتائج بتنسيقات متعددة. + - الربط مع وحدة التسعير لنقل النتائج مباشرة إلى جدول البنود. + - قاعدة بيانات للبنود النموذجية في أعمال المقاولات. + """) + + render_construction_calculator() + + # شرح حاسبة تكاليف البناء + with st.expander("دليل استخدام حاسبة تكاليف البناء", expanded=False): + st.markdown(""" + **حاسبة تكاليف البناء المتكاملة** هي أداة تساعد في حساب تكاليف البناء بشكل تفصيلي، مع مراعاة جميع عناصر التكلفة: + + ### مكونات التكلفة: + + 1. **المواد الخام**: جميع المواد المستخدمة في البناء مثل الخرسانة، الحديد، الطوب، الأسمنت، وغيرها. + 2. **العمالة**: تكاليف جميع العمالة المطلوبة بمختلف تخصصاتها. + 3. **المعدات**: تكاليف استخدام أو استئجار المعدات اللازمة للمشروع. + 4. **المصاريف الإدارية**: النفقات العامة والإدارية للمشروع (نسبة من التكلفة المباشرة). + 5. **هامش الربح**: نسبة الربح المضافة على التكلفة. + + ### طريقة الاستخدام: + + 1. اختر إما حساب تكلفة بند واحد أو حساب تكلفة مشروع كامل. + 2. أدخل بيانات المواد والعمالة والمعدات المستخدمة. + 3. حدد نسب المصاريف الإدارية وهامش الربح. + 4. اضبط عوامل التعديل حسب ظروف المشروع. + 5. احصل على تحليل تفصيلي للتكاليف والسعر النهائي. + + ### ميزات الحاسبة: + + - قاعدة بيانات مدمجة للأسعار المرجعية للمواد والعمالة والمعدات. + - تحليل نسب مساهمة كل عنصر في التكلفة الإجمالية. + - إمكانية تعديل عوامل التكلفة حسب الموقع والظروف السوقية. + - تصدير النتائج بتنسيقات مختلفة. + - الربط مع وحدة التسعير لنقل النتائج مباشرة إلى جدول التسعير. + - قاعدة بيانات للبنود النموذجية في أعمال المقاولات (مناهل، مواسير، إسفلت، إلخ). + """) + + # تبويبات حاسبة التكاليف + calc_tabs = st.tabs(["حساب تكلفة بند", "حساب تكلفة مشروع", "قوائم الأسعار المرجعية", "كتالوج أعمال المقاولات"]) + + with calc_tabs[0]: # حساب تكلفة بند + st.markdown("#### حساب تكلفة بند بناء") + + # تهيئة بيانات البند الافتراضية إذا لم تكن موجودة + if 'construction_item' not in st.session_state: + st.session_state.construction_item = { + 'وصف_البند': "توريد وصب خرسانة مسلحة للأساسات", + 'الكمية': 25.0, + 'الوحدة': "م3", + 'المواد': [ + {'الاسم': 'خرسانة جاهزة', 'الكمية': 25.0, 'الوحدة': 'م3', 'سعر_الوحدة': 750.0}, + {'الاسم': 'حديد تسليح', 'الكمية': 3.5, 'الوحدة': 'طن', 'سعر_الوحدة': 5500.0} + ], + 'العمالة': [ + {'النوع': 'عامل خرسانة', 'العدد': 6, 'المدة': 1.0, 'سعر_اليوم': 150.0}, + {'النوع': 'حداد مسلح', 'العدد': 4, 'المدة': 3.0, 'سعر_اليوم': 250.0}, + {'النوع': 'نجار مسلح', 'العدد': 4, 'المدة': 3.0, 'سعر_اليوم': 250.0} + ], + 'المعدات': [ + {'النوع': 'مضخة خرسانة', 'العدد': 1.0, 'المدة': 0.5, 'سعر_اليوم': 5000.0}, + {'النوع': 'هزاز خرسانة', 'العدد': 2.0, 'المدة': 1.0, 'سعر_اليوم': 150.0} + ], + 'المصاريف_الإدارية': 0.05, # 5% + 'هامش_الربح': 0.10, # 10% + 'عوامل_التعديل': { + 'location_factor': 1.0, + 'time_factor': 1.0, + 'risk_factor': 1.0, + 'market_factor': 1.0 + } + } + + # نموذج إدخال بيانات البند + col1, col2 = st.columns(2) + + with col1: + st.session_state.construction_item['وصف_البند'] = st.text_area( + "وصف البند", + value=st.session_state.construction_item['وصف_البند'], + key="construction_item_description" + ) + + with col2: + st.session_state.construction_item['الكمية'] = st.number_input( + "الكمية", + value=st.session_state.construction_item['الكمية'], + min_value=0.1, + format="%.2f" + ) + + unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"] + st.session_state.construction_item['الوحدة'] = st.selectbox( + "الوحدة", + options=unit_options, + index=unit_options.index(st.session_state.construction_item['الوحدة']) if st.session_state.construction_item['الوحدة'] in unit_options else 0, + key="construction_item_unit_2" + ) + + # إدخال تفاصيل المواد + st.markdown("#### تفاصيل المواد") + + material_controls = [] + for i, material in enumerate(st.session_state.construction_item['المواد']): + col1, col2, col3, col4, col5 = st.columns([3, 2, 1, 2, 1]) + + with col1: + material_name = st.text_input( + "اسم المادة", + value=material['الاسم'], + key=f"material_name_{i}" + ) + + with col2: + material_qty = st.number_input( + "الكمية", + value=material['الكمية'], + min_value=0.0, + format="%.2f", + key=f"material_qty_{i}" + ) + + with col3: + material_unit = st.selectbox( + "الوحدة", + options=unit_options, + index=unit_options.index(material['الوحدة']) if material['الوحدة'] in unit_options else 0, + key=f"material_unit_{i}" + ) + + with col4: + material_price = st.number_input( + "سعر الوحدة", + value=material['سعر_الوحدة'], + min_value=0.0, + format="%.2f", + key=f"material_price_{i}" + ) + + with col5: + delete_button = st.button("حذف", key=f"delete_material_{i}") + + material_controls.append({ + 'الاسم': material_name, + 'الكمية': material_qty, + 'الوحدة': material_unit, + 'سعر_الوحدة': material_price, + 'delete': delete_button + }) + + # إضافة مادة جديدة + if st.button("إضافة مادة جديدة"): + st.session_state.construction_item['المواد'].append({ + 'الاسم': '', + 'الكمية': 0.0, + 'الوحدة': 'م3', + 'سعر_الوحدة': 0.0 + }) + st.rerun() + + # تحديث أو حذف المواد + new_materials = [] + for i, control in enumerate(material_controls): + if not control['delete']: + new_materials.append({ + 'الاسم': control['الاسم'], + 'الكمية': control['الكمية'], + 'الوحدة': control['الوحدة'], + 'سعر_الوحدة': control['سعر_الوحدة'] + }) + + if len(new_materials) != len(st.session_state.construction_item['المواد']): + st.session_state.construction_item['المواد'] = new_materials + st.rerun() + else: + for i, material in enumerate(new_materials): + st.session_state.construction_item['المواد'][i] = material + + # إدخال تفاصيل العمالة + st.markdown("#### تفاصيل العمالة") + + labor_controls = [] + for i, labor in enumerate(st.session_state.construction_item['العمالة']): + col1, col2, col3, col4, col5 = st.columns([3, 1, 1, 2, 1]) + + with col1: + labor_type = st.text_input( + "نوع العامل", + value=labor['النوع'], + key=f"labor_type_{i}" + ) + + with col2: + labor_count = st.number_input( + "العدد", + value=float(labor['العدد']), + min_value=1.0, + step=1.0, + key=f"labor_count_{i}" + ) + + with col3: + labor_days = st.number_input( + "المدة (أيام)", + value=labor['المدة'], + min_value=0.1, + format="%.1f", + key=f"labor_days_{i}" + ) + + with col4: + labor_daily_rate = st.number_input( + "سعر اليوم", + value=labor['سعر_اليوم'], + min_value=0.0, + format="%.2f", + key=f"labor_daily_rate_{i}" + ) + + with col5: + delete_button = st.button("حذف", key=f"delete_labor_{i}") + + labor_controls.append({ + 'النوع': labor_type, + 'العدد': labor_count, + 'المدة': labor_days, + 'سعر_اليوم': labor_daily_rate, + 'delete': delete_button + }) + + # إضافة عامل جديد + if st.button("إضافة عامل جديد"): + st.session_state.construction_item['العمالة'].append({ + 'النوع': '', + 'العدد': 1.0, + 'المدة': 1.0, + 'سعر_اليوم': 0.0 + }) + st.rerun() + + # تحديث أو حذف العمالة + new_labor = [] + for i, control in enumerate(labor_controls): + if not control['delete']: + new_labor.append({ + 'النوع': control['النوع'], + 'العدد': control['العدد'], + 'المدة': control['المدة'], + 'سعر_اليوم': control['سعر_اليوم'] + }) + + if len(new_labor) != len(st.session_state.construction_item['العمالة']): + st.session_state.construction_item['العمالة'] = new_labor + st.rerun() + else: + for i, labor in enumerate(new_labor): + st.session_state.construction_item['العمالة'][i] = labor + + # إدخال تفاصيل المعدات + st.markdown("#### تفاصيل المعدات") + + equipment_controls = [] + for i, equipment in enumerate(st.session_state.construction_item['المعدات']): + col1, col2, col3, col4, col5 = st.columns([3, 1, 1, 2, 1]) + + with col1: + equip_type = st.text_input( + "نوع المعدة", + value=equipment['النوع'], + key=f"equip_type_{i}" + ) + + with col2: + equip_count = st.number_input( + "العدد", + value=float(equipment['العدد']), + min_value=1.0, + step=1.0, + key=f"equip_count_{i}" + ) + + with col3: + equip_days = st.number_input( + "المدة (أيام)", + value=equipment['المدة'], + min_value=0.1, + format="%.1f", + key=f"equip_days_{i}" + ) + + with col4: + equip_daily_rate = st.number_input( + "سعر اليوم", + value=equipment['سعر_اليوم'], + min_value=0.0, + format="%.2f", + key=f"equip_daily_rate_{i}" + ) + + with col5: + delete_button = st.button("حذف", key=f"delete_equipment_{i}") + + equipment_controls.append({ + 'النوع': equip_type, + 'العدد': equip_count, + 'المدة': equip_days, + 'سعر_اليوم': equip_daily_rate, + 'delete': delete_button + }) + + # إضافة معدة جديدة + if st.button("إضافة معدة جديدة"): + st.session_state.construction_item['المعدات'].append({ + 'النوع': '', + 'العدد': 1.0, + 'المدة': 1.0, + 'سعر_اليوم': 0.0 + }) + st.rerun() + + # تحديث أو حذف المعدات + new_equipment = [] + for i, control in enumerate(equipment_controls): + if not control['delete']: + new_equipment.append({ + 'النوع': control['النوع'], + 'العدد': control['العدد'], + 'المدة': control['المدة'], + 'سعر_اليوم': control['سعر_اليوم'] + }) + + if len(new_equipment) != len(st.session_state.construction_item['المعدات']): + st.session_state.construction_item['المعدات'] = new_equipment + st.rerun() + else: + for i, equipment in enumerate(new_equipment): + st.session_state.construction_item['المعدات'][i] = equipment + + # النسب والعوامل + st.markdown("#### المصاريف الإدارية وهامش الربح") + + col1, col2 = st.columns(2) + + with col1: + st.session_state.construction_item['المصاريف_الإدارية'] = st.slider( + "نسبة المصاريف الإدارية (%)", + min_value=0.0, + max_value=20.0, + value=st.session_state.construction_item['المصاريف_الإدارية'] * 100, + step=0.5 + ) / 100 + + with col2: + st.session_state.construction_item['هامش_الربح'] = st.slider( + "نسبة هامش الربح (%)", + min_value=0.0, + max_value=30.0, + value=st.session_state.construction_item['هامش_الربح'] * 100, + step=0.5 + ) / 100 + + # عوامل التعديل + st.markdown("#### عوامل تعديل التكلفة") + + col1, col2 = st.columns(2) + + with col1: + st.session_state.construction_item['عوامل_التعديل']['location_factor'] = st.slider( + "معامل الموقع", + min_value=0.5, + max_value=2.0, + value=st.session_state.construction_item['عوامل_التعديل']['location_factor'], + step=0.05, + help="يؤثر في التكلفة حسب صعوبة أو سهولة الوصول للموقع وتوفر الخدمات" + ) + + st.session_state.construction_item['عوامل_التعديل']['time_factor'] = st.slider( + "معامل الوقت", + min_value=0.8, + max_value=1.5, + value=st.session_state.construction_item['عوامل_التعديل']['time_factor'], + step=0.05, + help="يؤثر في التكلفة حسب الجدول الزمني للمشروع وضرورة الإسراع في التنفيذ" + ) + + with col2: + st.session_state.construction_item['عوامل_التعديل']['risk_factor'] = st.slider( + "معامل المخاطر", + min_value=1.0, + max_value=1.5, + value=st.session_state.construction_item['عوامل_التعديل']['risk_factor'], + step=0.05, + help="يؤثر في التكلفة حسب المخاطر المتوقعة في المشروع" + ) + + st.session_state.construction_item['عوامل_التعديل']['market_factor'] = st.slider( + "معامل السوق", + min_value=0.8, + max_value=1.3, + value=st.session_state.construction_item['عوامل_التعديل']['market_factor'], + step=0.05, + help="يؤثر في التكلفة حسب حالة السوق الحالية وتغيرات الأسعار" + ) + + # صف أزرار العمليات + col1, col2 = st.columns(2) + + with col1: + # زر حساب التكلفة + if st.button("حساب تكلفة البند", type="primary"): + with st.spinner("جاري حساب التكلفة..."): + # حساب التكلفة باستخدام الحاسبة + item_cost = self.construction_calculator.calculate_item_cost(st.session_state.construction_item) + st.session_state.item_cost_result = item_cost + + with col2: + # زر استيراد بند من كتالوج أعمال المقاولات + if st.button("استيراد بند من الكتالوج"): + # تهيئة session state للاختيار من الكتالوج + if 'show_catalog_selection' not in st.session_state: + st.session_state.show_catalog_selection = True + else: + st.session_state.show_catalog_selection = True + st.rerun() + + # عرض واجهة اختيار البند من الكتالوج + if 'show_catalog_selection' in st.session_state and st.session_state.show_catalog_selection: + st.markdown("#### اختيار بند من كتالوج أعمال المقاولات") + + # الحصول على فئات البنود + categories = self.construction_templates.get_all_templates()['categories'] + category_options = [f"{cat_data['name']}" for cat_id, cat_data in categories.items()] + category_ids = list(categories.keys()) + + selected_category_index = st.selectbox( + "اختر فئة البند", + options=range(len(category_options)), + format_func=lambda i: category_options[i], + key="construction_category_selector" + ) + + selected_category_id = category_ids[selected_category_index] + + # الحصول على البنود في الفئة المحددة + templates = self.construction_templates.get_templates_by_category(selected_category_id) + + if templates: + template_options = [f"{template['name']}" for template in templates] + + selected_template_index = st.selectbox( + "اختر البند", + options=range(len(template_options)), + format_func=lambda i: template_options[i], + key="construction_template_selector" + ) + + selected_template = templates[selected_template_index] + + # عرض تفاصيل البند المحدد + st.markdown(f"**وصف البند**: {selected_template['description']}") + st.markdown(f"**الوحدة**: {selected_template['unit']}") + + if st.button("استخدام هذا البند", type="primary"): + # تحويل البند إلى صيغة الحاسبة + template_id = selected_template['id'] + construction_item = self.construction_templates.convert_template_to_item(template_id) + + # تحديث بيانات البند في session state + st.session_state.construction_item = construction_item + st.session_state.show_catalog_selection = False + st.rerun() + else: + st.info("لا توجد بنود في هذه الفئة") + + if st.button("إلغاء"): + st.session_state.show_catalog_selection = False + st.rerun() + + # عرض نتائج الحساب + if 'item_cost_result' in st.session_state: + st.markdown("### نتائج حساب تكلفة البند") + + result = st.session_state.item_cost_result + + # ملخص البند + st.markdown(f"**البند:** {result['وصف_البند']}") + st.markdown(f"**الكمية:** {result['الكمية']} {result['الوحدة']}") + + # المكونات الرئيسية للتكلفة + col1, col2, col3, col4 = st.columns(4) + + with col1: + st.metric( + "تكلفة المواد", + f"{result['تكاليف_مباشرة']['المواد']['الإجمالي']:,.2f} ريال" + ) + + with col2: + st.metric( + "تكلفة العمالة", + f"{result['تكاليف_مباشرة']['العمالة']['الإجمالي']:,.2f} ريال" + ) + + with col3: + st.metric( + "تكلفة المعدات", + f"{result['تكاليف_مباشرة']['المعدات']['الإجمالي']:,.2f} ريال" + ) + + with col4: + st.metric( + "التكلفة المباشرة", + f"{result['تكاليف_مباشرة']['إجمالي_تكاليف_مباشرة']:,.2f} ريال" + ) + + # تفاصيل المصاريف والربح والسعر النهائي + col1, col2, col3 = st.columns(3) + + with col1: + st.metric( + f"المصاريف الإدارية ({result['مصاريف_إدارية']['نسبة']}%)", + f"{result['مصاريف_إدارية']['قيمة']:,.2f} ريال" + ) + + with col2: + st.metric( + f"هامش الربح ({result['هامش_ربح']['نسبة']}%)", + f"{result['هامش_ربح']['قيمة']:,.2f} ريال" + ) + + with col3: + st.metric( + "التكلفة الإجمالية", + f"{result['التكلفة_الإجمالية']:,.2f} ريال" + ) + + # التكلفة بعد تطبيق عوامل التعديل + adjustment_factor = result['عوامل_التعديل']['المعامل_الإجمالي'] + st.metric( + f"السعر النهائي المعدل (معامل التعديل: {adjustment_factor:.2f})", + f"{result['السعر_المعدل']['إجمالي']:,.2f} ريال", + delta=f"{(adjustment_factor - 1) * 100:.1f}%" + ) + + # سعر الوحدة وزر نقل السعر إلى جدول التسعير + col1, col2 = st.columns([2, 1]) + + with col1: + st.metric( + f"سعر الوحدة ({result['الوحدة']})", + f"{result['السعر_المعدل']['سعر_الوحدة']:,.2f} ريال" + ) + + with col2: + if st.button("نقل السعر إلى جدول التسعير", key="transfer_to_pricing"): + if 'manual_items' not in st.session_state: + st.session_state.manual_items = pd.DataFrame(columns=[ + 'رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي' + ]) + + # إنشاء رقم البند الجديد + new_id = f"B{len(st.session_state.manual_items)+1}" + + # إنشاء صف جديد + new_row = pd.DataFrame({ + 'رقم البند': [new_id], + 'وصف البند': [result['وصف_البند']], + 'الوحدة': [result['الوحدة']], + 'الكمية': [float(result['الكمية'])], + 'سعر الوحدة': [float(result['السعر_المعدل']['سعر_الوحدة'])], + 'الإجمالي': [float(result['السعر_المعدل']['إجمالي'])] + }) + + # إضافة الصف إلى DataFrame + st.session_state.manual_items = pd.concat([st.session_state.manual_items, new_row], ignore_index=True) + + # إنشاء حالة التسعير الحالي إذا لم تكن موجودة + if 'current_pricing' not in st.session_state: + st.session_state.current_pricing = { + 'name': 'تسعير جديد', + 'method': 'تسعير مستورد من حاسبة تكاليف البناء', + 'items': st.session_state.manual_items + } + else: + # تحديث البنود في التسعير الحالي + st.session_state.current_pricing['items'] = st.session_state.manual_items + + st.success(f"تم نقل البند \"{result['وصف_البند']}\" إلى جدول التسعير بنجاح!") + + # تفاصيل التكلفة - المواد + st.markdown("#### تفاصيل تكلفة المواد") + if len(result['تكاليف_مباشرة']['المواد']['التفاصيل']) > 0: + materials_df = pd.DataFrame(result['تكاليف_مباشرة']['المواد']['التفاصيل']) + st.dataframe(materials_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد مواد مضافة") + + # تفاصيل ال��كلفة - العمالة + st.markdown("#### تفاصيل تكلفة العمالة") + if len(result['تكاليف_مباشرة']['العمالة']['التفاصيل']) > 0: + labor_df = pd.DataFrame(result['تكاليف_مباشرة']['العمالة']['التفاصيل']) + st.dataframe(labor_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد عمالة مضافة") + + # تفاصيل التكلفة - المعدات + st.markdown("#### تفاصيل تكلفة المعدات") + if len(result['تكاليف_مباشرة']['المعدات']['التفاصيل']) > 0: + equipment_df = pd.DataFrame(result['تكاليف_مباشرة']['المعدات']['التفاصيل']) + st.dataframe(equipment_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد معدات مضافة") + + # رسم بياني لتوزيع التكلفة + st.markdown("#### توزيع مكونات التكلفة") + + cost_components = { + 'المواد': result['تكاليف_مباشرة']['المواد']['الإجمالي'], + 'العمالة': result['تكاليف_مباشرة']['العمالة']['الإجمالي'], + 'المعدات': result['تكاليف_مباشرة']['المعدات']['الإجمالي'], + 'المصاريف الإدارية': result['مصاريف_إدارية']['قيمة'], + 'هامش الربح': result['هامش_ربح']['قيمة'] + } + + colors = ['#2E86C1', '#28B463', '#EB984E', '#8E44AD', '#C0392B'] + + fig = px.pie( + values=list(cost_components.values()), + names=list(cost_components.keys()), + title='توزيع مكونات التكلفة', + color_discrete_sequence=colors + ) + + st.plotly_chart(fig, use_container_width=True) + + with calc_tabs[1]: # حساب تكلفة مشروع + st.markdown("#### حساب تكلفة مشروع كامل") + + # تهيئة بيانات المشروع الافتراضية إذا لم تكن موجودة + if 'construction_project' not in st.session_state: + st.session_state.construction_project = self.construction_calculator.generate_sample_project_data() + + # نموذج إدخال بيانات المشروع + col1, col2 = st.columns(2) + + with col1: + st.session_state.construction_project['اسم_المشروع'] = st.text_input( + "اسم المشروع", + value=st.session_state.construction_project['اسم_المشروع'] + ) + + with col2: + st.session_state.construction_project['وصف_المشروع'] = st.text_area( + "وصف المشروع", + value=st.session_state.construction_project['وصف_المشروع'], + height=100, + key="construction_project_description" + ) + + # النسب والعوامل الإجمالية للمشروع + st.markdown("#### النسب والعوامل الإجمالية للمشروع") + + col1, col2 = st.columns(2) + + with col1: + st.session_state.construction_project['المصاريف_الإدارية'] = st.slider( + "نسبة المصاريف الإدارية الإجمالية (%)", + min_value=0.0, + max_value=20.0, + value=st.session_state.construction_project['المصاريف_الإدارية'] * 100, + step=0.5, + key="project_admin_expenses" + ) / 100 + + with col2: + st.session_state.construction_project['هامش_الربح'] = st.slider( + "نسبة هامش الربح الإجمالي (%)", + min_value=0.0, + max_value=30.0, + value=st.session_state.construction_project['هامش_الربح'] * 100, + step=0.5, + key="project_profit_margin" + ) / 100 + + # عوامل التعديل للمشروع + st.markdown("#### عوامل تعديل التكلفة للمشروع") + + col1, col2 = st.columns(2) + + with col1: + st.session_state.construction_project['عوامل_التع��يل']['location_factor'] = st.slider( + "معامل الموقع", + min_value=0.5, + max_value=2.0, + value=st.session_state.construction_project['عوامل_التعديل']['location_factor'], + step=0.05, + help="يؤثر في التكلفة حسب صعوبة أو سهولة الوصول للموقع وتوفر الخدمات", + key="project_location_factor" + ) + + st.session_state.construction_project['عوامل_التعديل']['time_factor'] = st.slider( + "معامل الوقت", + min_value=0.8, + max_value=1.5, + value=st.session_state.construction_project['عوامل_التعديل']['time_factor'], + step=0.05, + help="يؤثر في التكلفة حسب الجدول الزمني للمشروع وضرورة الإسراع في التنفيذ", + key="project_time_factor" + ) + + with col2: + st.session_state.construction_project['عوامل_التعديل']['risk_factor'] = st.slider( + "معامل المخاطر", + min_value=1.0, + max_value=1.5, + value=st.session_state.construction_project['عوامل_التعديل']['risk_factor'], + step=0.05, + help="يؤثر في التكلفة حسب المخاطر المتوقعة في المشروع", + key="project_risk_factor" + ) + + st.session_state.construction_project['عوامل_التعديل']['market_factor'] = st.slider( + "معامل السوق", + min_value=0.8, + max_value=1.3, + value=st.session_state.construction_project['عوامل_التعديل']['market_factor'], + step=0.05, + help="يؤثر في التكلفة حسب حالة السوق الحالية وتغيرات الأسعار", + key="project_market_factor" + ) + + # زر حساب تكلفة المشروع + if st.button("حساب تكلفة المشروع", type="primary"): + with st.spinner("جاري حساب تكلفة المشروع..."): + # حساب التكلفة باستخدام الحاسبة + project_cost = self.construction_calculator.calculate_project_cost(st.session_state.construction_project) + st.session_state.project_cost_result = project_cost + + # عرض نتائج حساب المشروع + if 'project_cost_result' in st.session_state: + st.markdown("### نتائج حساب تكلفة المشروع") + + result = st.session_state.project_cost_result + + # ملخص المشروع + st.markdown(f"**المشروع:** {result['اسم_المشروع']}") + st.markdown(f"**الوصف:** {result['وصف_المشروع']}") + st.markdown(f"**عدد البنود:** {result['عدد_البنود']} بند") + + # المكونات الرئيسية للتكلفة + col1, col2, col3 = st.columns(3) + + with col1: + st.metric( + "إجمالي تكلفة المواد", + f"{result['تكاليف_مباشرة']['المواد']['الإجمالي']:,.2f} ريال", + delta=f"{result['تكاليف_مباشرة']['المواد']['النسبة_المئوية']:.1f}% من التكلفة المباشرة" + ) + + with col2: + st.metric( + "إجمالي تكلفة العمالة", + f"{result['تكاليف_مباشرة']['العمالة']['الإجمالي']:,.2f} ريال", + delta=f"{result['تكاليف_مباشرة']['العمالة']['النسبة_المئوية']:.1f}% من التكلفة المباشرة" + ) + + with col3: + st.metric( + "إجمالي تكلفة المعدات", + f"{result['تكاليف_مباشرة']['المعدات']['الإجمالي']:,.2f} ريال", + delta=f"{result['تكاليف_مباشرة']['المعدات']['النسبة_المئوية']:.1f}% من التكلفة المباشرة" + ) + + # إجمالي التكاليف المباشرة + st.metric( + "إجمالي التكاليف المباشرة", + f"{result['تكاليف_مباشرة']['إجمالي_تكاليف_مباشرة']:,.2f} ريال" + ) + + # تفاصيل المصاريف والربح والسعر النهائي + col1, col2, col3 = st.columns(3) + + with col1: + st.metric( + f"المصاريف الإدارية ({result['مصاريف_إدارية']['نسبة']}%)", + f"{result['مصاريف_إدارية']['قيمة']:,.2f} ريال" + ) + + with col2: + st.metric( + f"هامش الربح ({result['هامش_ربح']['نسبة']}%)", + f"{result['هامش_ربح']['قيمة']:,.2f} ريال" + ) + + with col3: + st.metric( + "التكلفة الإجمالية", + f"{result['التكلفة_الإجمالية']:,.2f} ريال" + ) + + # التكلفة بعد تطبيق عوامل التعديل + adjustment_factor = result['عوامل_التعديل']['المعامل_الإجمالي'] + st.metric( + f"السعر النهائي المعدل (معامل التعديل: {adjustment_factor:.2f})", + f"{result['التكلفة_النهائية_المعدلة']:,.2f} ريال", + delta=f"{(adjustment_factor - 1) * 100:.1f}%" + ) + + # رسم بياني لتوزيع التكلفة + st.markdown("#### توزيع مكونات التكلفة") + + cost_components = { + 'المواد': result['تكاليف_مباشرة']['المواد']['الإجمالي'], + 'العمالة': result['تكاليف_مباشرة']['العمالة']['الإجمالي'], + 'المعدات': result['تكاليف_مباشرة']['المعدات']['الإجمالي'], + 'المصاريف الإدارية': result['مصاريف_إدارية']['قيمة'], + 'هامش الربح': result['هامش_ربح']['قيمة'] + } + + colors = ['#2E86C1', '#28B463', '#EB984E', '#8E44AD', '#C0392B'] + + fig = px.pie( + values=list(cost_components.values()), + names=list(cost_components.keys()), + title='توزيع مكونات التكلفة', + color_discrete_sequence=colors + ) + + st.plotly_chart(fig, use_container_width=True) + + # جدول تفاصيل البنود + st.markdown("#### تفاصيل بنود المشروع") + + items_data = [] + for i, item in enumerate(result['تفاصيل_البنود']): + items_data.append({ + 'رقم': i + 1, + 'الوصف': item['وصف_البند'], + 'الكمية': item['الكمية'], + 'الوحدة': item['الوحدة'], + 'سعر الوحدة': item['سعر_الوحدة'], + 'الإجمالي': item['التكلفة_الإجمالية'], + 'بعد التعديل': item['السعر_المعدل']['إجمالي'] + }) + + if len(items_data) > 0: + items_df = pd.DataFrame(items_data) + + # تنسيق الجدول لعرض الأرقام بشكل أفضل + def highlight_row(row): + """تنسيق الجدول مع تمييز الصفوف بالتناوب""" + color = '#F0F8FF' if row.name % 2 == 0 else 'white' + return ['background-color: %s' % color] * len(row) + + styled_df = items_df.style.apply(highlight_row, axis=1) + styled_df = styled_df.format({ + 'الكمية': '{:,.2f}', + 'سعر الوحدة': '{:,.2f}', + 'الإجمالي': '{:,.2f}', + 'بعد التعديل': '{:,.2f}' + }) + + st.dataframe(styled_df, use_container_width=True) + else: + st.info("لا توجد بنود في المشروع") + + with calc_tabs[2]: # قوائم الأسعار المرجعية + st.markdown("#### قوائم الأسعار المرجعية") + + ref_tabs = st.tabs(["قائمة المواد", "قائمة العم��لة", "قائمة المعدات"]) + + with ref_tabs[0]: # قائمة المواد + st.markdown("#### قائمة أسعار المواد المرجعية") + + # الحصول على قائمة المواد + materials = self.construction_calculator.get_all_rates(item_type='مادة') + + if materials and 'المواد' in materials: + # تحويل قاموس المواد إلى DataFrame + materials_list = [] + for name, data in materials['المواد'].items(): + materials_list.append({ + 'اسم المادة': name, + 'الوحدة': data.get('وحدة', ''), + 'سعر الوحدة': data.get('سعر_الوحدة', 0.0), + 'الوصف': data.get('وصف', ''), + 'الفئة': data.get('فئة', '') + }) + + if materials_list: + materials_df = pd.DataFrame(materials_list) + + # تصفية حسب الفئة + categories = ['الكل'] + sorted(materials_df['الفئة'].unique().tolist()) + selected_category = st.selectbox("تصفية حسب الفئة", categories, key="materials_cat_filter_section1") + + if selected_category != 'الكل': + filtered_df = materials_df[materials_df['الفئة'] == selected_category] + else: + filtered_df = materials_df + + # تنسيق الجدول + def highlight_materials_row(row): + """تنسيق الجدول مع تمييز الصفوف بالتناوب""" + color = '#F0F8FF' if row.name % 2 == 0 else 'white' + return ['background-color: %s' % color] * len(row) + + styled_df = filtered_df.style.apply(highlight_materials_row, axis=1) + styled_df = styled_df.format({ + 'سعر الوحدة': '{:,.2f}' + }) + + st.dataframe(styled_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد مواد في قاعدة البيانات") + else: + st.info("لا توجد قائمة مواد متاحة") + + with ref_tabs[1]: # قائمة العمالة + st.markdown("#### قائمة أسعار العمالة المرجعية") + + # الحصول على قائمة العمالة + labor = self.construction_calculator.get_all_rates(item_type='عمالة') + + if labor and 'العمالة' in labor: + # تحويل قاموس العمالة إلى DataFrame + labor_list = [] + for name, data in labor['العمالة'].items(): + labor_list.append({ + 'نوع العامل': name, + 'وحدة الأجر': data.get('وحدة', ''), + 'سعر الوحدة': data.get('سعر_الوحدة', 0.0), + 'الوصف': data.get('وصف', ''), + 'الفئة': data.get('فئة', '') + }) + + if labor_list: + labor_df = pd.DataFrame(labor_list) + + # تصفية حسب الفئة + categories = ['الكل'] + sorted(labor_df['الفئة'].unique().tolist()) + selected_category = st.selectbox("تصفية حسب الفئة", categories, key="labor_cat_filter_section1") + + if selected_category != 'الكل': + filtered_df = labor_df[labor_df['الفئة'] == selected_category] + else: + filtered_df = labor_df + + # تنسيق الجدول + def highlight_labor_row(row): + """تنسيق الجدول مع تمييز الصفوف بالتناوب""" + color = '#F0F8FF' if row.name % 2 == 0 else 'white' + return ['background-color: %s' % color] * len(row) + + styled_df = filtered_df.style.apply(highlight_labor_row, axis=1) + styled_df = styled_df.format({ + 'سعر الوحدة': '{:,.2f}' + }) + + st.dataframe(styled_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد عمالة في قاعدة البيانات") + else: + st.info("لا توجد قائمة عمالة متاحة") + + with ref_tabs[2]: # قائمة المعدات + st.markdown("#### قائمة أسعار المعدات المرجعية") + + # الحصول على قائمة المعدات + equipment = self.construction_calculator.get_all_rates(item_type='معدة') + + if equipment and 'المعدات' in equipment: + # تحويل قاموس المعدات إلى DataFrame + equipment_list = [] + for name, data in equipment['المعدات'].items(): + equipment_list.append({ + 'نوع المعدة': name, + 'وحدة الإيجار': data.get('وحدة', ''), + 'سعر الوحدة': data.get('سعر_الوحدة', 0.0), + 'الوصف': data.get('وصف', ''), + 'الفئة': data.get('فئة', '') + }) + + if equipment_list: + equipment_df = pd.DataFrame(equipment_list) + + # تصفية حسب الفئة + categories = ['الكل'] + sorted(equipment_df['الفئة'].unique().tolist()) + selected_category = st.selectbox("تصفية حسب الفئة", categories, key="equipment_cat_filter_section1") + + if selected_category != 'الكل': + filtered_df = equipment_df[equipment_df['الفئة'] == selected_category] + else: + filtered_df = equipment_df + + # تنسيق الجدول + def highlight_equipment_row(row): + """تنسيق الجدول مع تمييز الصفوف بالتناوب""" + color = '#F0F8FF' if row.name % 2 == 0 else 'white' + return ['background-color: %s' % color] * len(row) + + styled_df = filtered_df.style.apply(highlight_equipment_row, axis=1) + styled_df = styled_df.format({ + 'سعر الوحدة': '{:,.2f}' + }) + + st.dataframe(styled_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد معدات في قاعدة البيانات") + else: + st.info("لا توجد قائمة معدات متاحة") + + with calc_tabs[3]: # كتالوج أعمال المقاولات + st.markdown("#### كتالوج أعمال المقاولات") + + # شرح كتالوج أعمال المقاولات + with st.expander("معلومات عن كتالوج أعمال المقاولات", expanded=False): + st.markdown(""" + **كتالوج أعمال المقاولات** هو قاعدة بيانات شاملة للبنود النموذجية المستخدمة في مشاريع المقاولات ويوفر: + + - بنود جاهزة لمختلف أنواع الأعمال الإنشائية (خرسانة مناهل مواسير طرق إلخ). + - تفاصيل دقيقة للمواد والعمالة والمعدات المطلوبة لكل بند. + - تحليل تكلفة تفصيلي يمكن استخدامه مباشرة في عروض الأسعار والمناقصات. + - ربط مباشر مع حاسبة تكاليف البناء وحاسبة التسعير. + + **استخدامات الكتالوج:** + + 1. استخدام البنود النموذجية مباشرة في التسعير. + 2. تعديل البنود النموذجية لتناسب متطلبات المشروع. + 3. إضافة بنود جديدة إلى الكتالوج للاستخدام المستقبلي. + """) + + # الفئات وعرض محتوى الكتالوج + category_col, template_col = st.columns([1, 2]) + + with category_col: + st.markdown("### فئات البنود") + + # الحصول على فئ��ت البنود + templates = self.construction_templates.get_all_templates() + categories = templates['categories'] + + for cat_id, cat_data in categories.items(): + st.markdown(f"#### {cat_data['name']}") + st.markdown(f"{cat_data['description']}") + + if st.button(f"عرض بنود {cat_data['name']}", key=f"cat_btn_{cat_id}"): + st.session_state.selected_category = cat_id + st.rerun() + + with template_col: + st.markdown("### البنود النموذجية") + + selected_category = st.session_state.get("selected_category", list(categories.keys())[0]) + + # عرض البنود في الفئة المحددة + cat_templates = self.construction_templates.get_templates_by_category(selected_category) + + if cat_templates: + st.markdown(f"#### بنود فئة {categories[selected_category]['name']}") + + for template in cat_templates: + with st.expander(template['name'], expanded=False): + st.markdown(f"**الوصف**: {template['description']}") + st.markdown(f"**الوحدة**: {template['unit']}") + + # عرض مكونات البند + st.markdown("##### مكونات البند") + + # المواد + materials = template['components']['materials'] + if materials: + materials_df = pd.DataFrame(materials) + st.markdown("**المواد:**") + st.dataframe(materials_df, hide_index=True) + + # العمالة + labor = template['components']['labor'] + if labor: + labor_df = pd.DataFrame(labor) + st.markdown("**العمالة:**") + st.dataframe(labor_df, hide_index=True) + + # المعدات + equipment = template['components']['equipment'] + if equipment: + equipment_df = pd.DataFrame(equipment) + st.markdown("**المعدات:**") + st.dataframe(equipment_df, hide_index=True) + + # أزرار العمليات + col1, col2 = st.columns(2) + + with col1: + if st.button("استخدام هذا البند في حاسبة التكاليف", key=f"use_template_{template['id']}"): + # تحويل البند إلى صيغة الحاسبة + construction_item = self.construction_templates.convert_template_to_item(template['id']) + + # تحديث بيانات البند في session state + st.session_state.construction_item = construction_item + st.session_state.active_tab = 0 # الانتقال إلى تبويب حساب تكلفة البند + st.rerun() + + with col2: + if st.button("إضافة مباشرة إلى جدول التسعير", key=f"add_to_pricing_{template['id']}"): + # تحويل البند إلى صيغة الحاسبة + construction_item = self.construction_templates.convert_template_to_item(template['id']) + + # حساب التكلفة + item_cost = self.construction_calculator.calculate_item_cost(construction_item) + + # إضافة البند إلى جدول التسعير + if 'manual_items' not in st.session_state: + st.session_state.manual_items = pd.DataFrame(columns=[ + 'رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي' + ]) + + # إنشاء رقم البن�� الجديد + new_id = f"C{len(st.session_state.manual_items)+1}" + + # إنشاء صف جديد + new_row = pd.DataFrame({ + 'رقم البند': [new_id], + 'وصف البند': [item_cost['وصف_البند']], + 'الوحدة': [item_cost['الوحدة']], + 'الكمية': [float(item_cost['الكمية'])], + 'سعر الوحدة': [float(item_cost['السعر_المعدل']['سعر_الوحدة'])], + 'الإجمالي': [float(item_cost['السعر_المعدل']['إجمالي'])] + }) + + # إضافة الصف إلى DataFrame + st.session_state.manual_items = pd.concat([st.session_state.manual_items, new_row], ignore_index=True) + + # إنشاء حالة التسعير الحالي إذا لم تكن موجودة + if 'current_pricing' not in st.session_state: + st.session_state.current_pricing = { + 'name': 'تسعير جديد', + 'method': 'تسعير مستورد من كتالوج المقاولات', + 'items': st.session_state.manual_items + } + else: + # تحديث البنود في التسعير الحالي + st.session_state.current_pricing['items'] = st.session_state.manual_items + + st.success(f"تم إضافة البند \"{item_cost['وصف_البند']}\" إلى جدول التسعير بنجاح!") + + # إضافة قسم لإضافة بند جديد إلى الكتالوج + st.markdown("### إضافة بند جديد إلى الكتالوج") + + if st.button("إضافة البند الحالي إلى الكتالوج"): + if 'item_cost_result' in st.session_state: + # عرض نموذج لإضافة البند إلى الكتالوج + st.session_state.show_add_to_catalog = True + st.rerun() + else: + st.warning("يجب حساب تكلفة البند أولاً في تبويب 'حساب تكلفة بند' قبل إضافته إلى الكتالوج.") + + # عرض نموذج إضافة البند إلى الكتالوج + if 'show_add_to_catalog' in st.session_state and st.session_state.show_add_to_catalog: + st.markdown("#### إضافة البند الحالي إلى كتالوج المقاولات") + + # اختيار الفئة + category_options = [f"{cat_data['name']}" for cat_id, cat_data in categories.items()] + category_ids = list(categories.keys()) + + selected_category_index = st.selectbox( + "اختر فئة البند", + options=range(len(category_options)), + format_func=lambda i: category_options[i], + key="new_template_category" + ) + + selected_category_id = category_ids[selected_category_index] + + # معلومات البند + item_result = st.session_state.item_cost_result + + template_name = st.text_input("اسم البند في الكتالوج", value=item_result['وصف_البند'][:50]) + template_description = st.text_area("وصف البند", value=item_result['وصف_البند'], key="template_item_description") + + # الكلمات المفتاحية + tags_input = st.text_input("الكلمات المفتاحية (مفصولة بفواصل)", value="بناء, مقاولات") + tags = [tag.strip() for tag in tags_input.split(",")] + + if st.button("إضافة إلى الكتالوج", type="primary"): + # تحويل البند إلى صيغة قالب + template_data = { + "category": selected_category_id, + "name": template_name, + "description": template_description, + "unit": item_result['الوحدة'], + "components": { + "materials": item_result['تكاليف_مباشرة']['المواد']['التفاصيل'], + "labor": item_result['تكاليف_مباشرة']['العمالة']['التفاصيل'], + "equipment": item_result['تكاليف_مباشرة']['المعدات']['التفاصيل'] + }, + "admin_expenses": item_result['مصاريف_إدارية']['نسبة'] / 100, + "profit_margin": item_result['هامش_ربح']['نسبة'] / 100, + "tags": tags + } + + # إضافة القالب إلى الكتالوج + template_id = self.construction_templates.add_template(template_data) + + st.success(f"تم إضافة البند \"{template_name}\" إلى كتالوج المقاولات بنجاح!") + st.session_state.show_add_to_catalog = False + st.rerun() + + if st.button("إلغاء", key="cancel_add_to_catalog"): + st.session_state.show_add_to_catalog = False + st.rerun() + + with ref_tabs[0]: # قائمة المواد + st.markdown("#### قائمة أسعار المواد المرجعية") + + # الحصول على قائمة المواد + materials = self.construction_calculator.get_all_rates(item_type='مادة') + + if materials and 'المواد' in materials: + # تحويل قاموس المواد إلى DataFrame + materials_list = [] + for name, data in materials['المواد'].items(): + materials_list.append({ + 'اسم المادة': name, + 'الوحدة': data.get('وحدة', ''), + 'سعر الوحدة': data.get('سعر_الوحدة', 0.0), + 'الوصف': data.get('وصف', ''), + 'الفئة': data.get('فئة', '') + }) + + if materials_list: + materials_df = pd.DataFrame(materials_list) + + # تصفية حسب الفئة + categories = ['الكل'] + sorted(materials_df['الفئة'].unique().tolist()) + selected_category = st.selectbox("تصفية حسب الفئة", categories, key="materials_cat_filter_section2") + + if selected_category != 'الكل': + filtered_df = materials_df[materials_df['الفئة'] == selected_category] + else: + filtered_df = materials_df + + # تنسيق الجدول + def highlight_materials_row(row): + """تنسيق الجدول مع تمييز الصفوف بالتناوب""" + color = '#F0F8FF' if row.name % 2 == 0 else 'white' + return ['background-color: %s' % color] * len(row) + + styled_df = filtered_df.style.apply(highlight_materials_row, axis=1) + styled_df = styled_df.format({ + 'سعر الوحدة': '{:,.2f}' + }) + + st.dataframe(styled_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد مواد في قاعدة البيانات") + else: + st.info("لا توجد قائمة مواد متاحة") + + with ref_tabs[1]: # قائمة العمالة + st.markdown("#### قائمة أسعار العمالة المرجعية") + + # الحصول على قائمة العمالة + labor = self.construction_calculator.get_all_rates(item_type='عمالة') + + if labor and 'العمالة' in labor: + # تحويل قاموس العمالة إلى DataFrame + labor_list = [] + for name, data in labor['العمالة'].items(): + labor_list.append({ + 'نوع العامل': name, + 'وحدة الأجر': data.get('وحدة', ''), + 'سعر الوحدة': data.get('سعر_الوحدة', 0.0), + 'الوصف': data.get('وصف', ''), + 'الفئة': data.get('فئة', '') + }) + + if labor_list: + labor_df = pd.DataFrame(labor_list) + + # تصفية حسب الفئة + categories = ['الكل'] + sorted(labor_df['الفئة'].unique().tolist()) + selected_category = st.selectbox("تصفية حسب الفئة", categories, key="labor_cat_filter_section2") + + if selected_category != 'الكل': + filtered_df = labor_df[labor_df['الفئة'] == selected_category] + else: + filtered_df = labor_df + + # تنسيق الجدول + def highlight_labor_row(row): + """تنسيق الجدول مع تمييز الصفوف بالتناوب""" + color = '#F0F8FF' if row.name % 2 == 0 else 'white' + return ['background-color: %s' % color] * len(row) + + styled_df = filtered_df.style.apply(highlight_labor_row, axis=1) + styled_df = styled_df.format({ + 'سعر الوحدة': '{:,.2f}' + }) + + st.dataframe(styled_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد عمالة في قاعدة البيانات") + else: + st.info("لا توجد قائمة عمالة متاحة") + + with ref_tabs[2]: # قائمة المعدات + st.markdown("#### قائمة أسعار المعدات المرجعية") + + # الحصول على قائمة المعدات + equipment = self.construction_calculator.get_all_rates(item_type='معدة') + + if equipment and 'المعدات' in equipment: + # تحويل قاموس المعدات إلى DataFrame + equipment_list = [] + for name, data in equipment['المعدات'].items(): + equipment_list.append({ + 'نوع المعدة': name, + 'وحدة الأجر': data.get('وحدة', ''), + 'سعر الوحدة': data.get('سعر_الوحدة', 0.0), + 'الوصف': data.get('وصف', ''), + 'الفئة': data.get('فئة', '') + }) + + if equipment_list: + equipment_df = pd.DataFrame(equipment_list) + + # تصفية حسب الفئة + categories = ['الكل'] + sorted(equipment_df['الفئة'].unique().tolist()) + selected_category = st.selectbox("تصفية حسب الفئة", categories, key="equipment_cat_filter_section2") + + if selected_category != 'الكل': + filtered_df = equipment_df[equipment_df['الفئة'] == selected_category] + else: + filtered_df = equipment_df + + # تنسيق الجدول + def highlight_equipment_row(row): + """تنسيق الجدول مع تمييز الصفوف بالتناوب""" + color = '#F0F8FF' if row.name % 2 == 0 else 'white' + return ['background-color: %s' % color] * len(row) + + styled_df = filtered_df.style.apply(highlight_equipment_row, axis=1) + styled_df = styled_df.format({ + 'سعر الوحدة': '{:,.2f}' + }) + + st.dataframe(styled_df, use_container_width=True, hide_index=True) + else: + st.info("لا توجد معدات في قاعدة البيانات") + else: + st.info("لا توجد قائمة معدات متاحة") + + def _render_local_content_tab(self): + """عرض تبويب المحتوى المحلي""" + + st.markdown("

تحليل المحتوى المحلي

", unsafe_allow_html=True) + + # التحقق من وجود تسعير حالي + 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. **المنتجات**: المنتجات والمواد المصنعة محلياً. + 2. **الخدمات**: الخدمات المقدمة من شركات محلية. + 3. **القوى العاملة**: العمالة والكوادر الفنية والإدارية المحلية. + + ### أهمية المحتوى المحلي: + + - تعزيز الاقتصاد المحلي وخلق فرص عمل. + - تحقيق أهداف رؤية 2030 في زيادة المحتوى المحلي. + - التأهل للمشاريع الحكومية التي تتطلب نسبة محتوى محلي محددة. + - الحصول على حوافز وأفضلية في المناقصات الحكومية. + + ### متطلبات المحتوى المحلي: + + - نسبة المحتوى المحلي للقوى العاملة: 80% + - نسبة المحتوى المحلي للمنتجات: 70% + - نسبة المحتوى المحلي للخدمات: 60% + """) + + # عرض لوحة إدخال بيانات المحتوى المحلي + st.markdown("### بيانات المحتوى المحلي") + + # التبويبات لأنواع المحتوى المحلي + lc_tabs = st.tabs(["المنتجات", "الخدمات", "القوى العاملة", "التحليل"]) + + with lc_tabs[0]: # المنتجات + st.markdown("#### بيانات المنتجات") + + # إنشاء بيانات افتراضية للمنتجات إذا لم تكن موجودة + if 'local_content_products' not in st.session_state: + st.session_state.local_content_products = pd.DataFrame({ + 'المنتج': [ + "خرسانة مسلحة", + "حديد تسليح", + "بلوك خرساني", + "عزل مائي", + "دهانات" + ], + 'الكمية': [250, 25, 400, 500, 600], + 'سعر_الوحدة': [1200, 6000, 200, 100, 50], + 'التكلفة_الإجمالية': [300000, 150000, 80000, 50000, 30000], + 'نسبة_المحتوى_المحلي': [0.95, 0.70, 0.98, 0.60, 0.80] + }) + + # حساب التكلفة الإجمالية + st.session_state.local_content_products['التكلفة_الإجمالية'] = st.session_state.local_content_products['الكمية'] * st.session_state.local_content_products['سعر_الوحدة'] + + # نموذج إضافة منتج جديد + st.markdown("#### إضافة منتج جديد") + col1, col2, col3, col4 = st.columns(4) + + with col1: + new_product_name = st.text_input("اسم المنتج", key="new_product_name", value="") + with col2: + new_product_quantity = st.number_input("الكمية", key="new_product_quantity", min_value=0, value=0) + with col3: + new_product_price = st.number_input("سعر الوحدة", key="new_product_price", min_value=0, value=0) + with col4: + new_product_local_content = st.slider("نسبة المحتوى المحلي", key="new_product_local_content", min_value=0.0, max_value=1.0, value=0.8, step=0.01, format="%.2f") + + if st.button("إضافة المنتج"): + if new_product_name: + # حساب التكلفة الإجمالية + total_cost = new_product_quantity * new_product_price + + # إضافة منتج جديد للجدول + new_product = pd.DataFrame({ + 'المنتج': [new_product_name], + 'الكمية': [new_product_quantity], + 'سعر_الوحدة': [new_product_price], + 'التكلفة_الإجمالية': [total_cost], + 'نسبة_المحتوى_المحلي': [new_product_local_content] + }) + + # إضافة المنتج الجديد للجدول الحالي + st.session_state.local_content_products = pd.concat([st.session_state.local_content_products, new_product], ignore_index=True) + st.success(f"تم إضافة المنتج {new_product_name} بنجاح!") + else: + st.warning("يرجى إدخال اسم المنتج") + + # عرض جدول البنود مع إمكانية التعديل + st.markdown("#### جدول المنتجات") + edited_products = st.data_editor( + st.session_state.local_content_products, + use_container_width=True, + hide_index=True, + key="products_editor" + ) + st.session_state.local_content_products = edited_products + + # زر لحذف المنتجات المحددة + if st.button("حذف المنتجات المحددة"): + st.session_state.local_content_products = pd.DataFrame({ + 'المنتج': [], + 'الكمية': [], + 'سعر_الوحدة': [], + 'التكلفة_الإجمالية': [], + 'نسبة_المحتوى_المحلي': [] + }) + st.success("تم حذف جميع المنتجات!") + st.rerun() + + # عرض ملخص المنتجات + total_products_cost = edited_products['التكلفة_الإجمالية'].sum() + avg_local_content = (edited_products['التكلفة_الإجمالية'] * edited_products['نسبة_المحتوى_المحلي']).sum() / total_products_cost if total_products_cost > 0 else 0 + + st.markdown(f""" + **إجمالي تكلفة المنتجات**: {total_products_cost:,.2f} ريال + + **متوسط نسبة المحتوى المحلي للمنتجات**: {avg_local_content*100:.2f}% + + **المستهدف**: 70% + + **الحالة**: {"✅ ملتزم" if avg_local_content >= 0.7 else "❌ غير ملتزم"} + """) + + with lc_tabs[1]: # الخدمات + st.markdown("#### بيانات الخدمات") + + # إنشاء بيانات افتراضية للخدمات إذا لم تكن موجودة + if 'local_content_services' not in st.session_state: + st.session_state.local_content_services = pd.DataFrame({ + 'الخدمة': [ + "تصميم معماري", + "إشراف هندسي", + "خدمات نقل", + "خدمات أمن وسلامة", + "صيانة ونظافة" + ], + 'التكلفة': [100000, 120000, 50000, 30000, 20000], + 'نسبة_المحتوى_المحلي': [0.90, 0.85, 0.90, 0.95, 0.95] + }) + + # نموذج إضافة خدمة جديدة + st.markdown("#### إضافة خدمة جديدة") + col1, col2, col3 = st.columns(3) + + with col1: + new_service_name = st.text_input("اسم الخدمة", key="new_service_name", value="") + with col2: + new_service_cost = st.number_input("التكلفة", key="new_service_cost", min_value=0, value=0) + with col3: + new_service_local_content = st.slider("نسبة المحتوى المحلي", key="new_service_local_content", min_value=0.0, max_value=1.0, value=0.8, step=0.01, format="%.2f") + + if st.button("إضافة الخدمة"): + if new_service_name: + # إضافة خدمة جديدة للجدول + new_service = pd.DataFrame({ + 'الخدمة': [new_service_name], + 'التكلفة': [new_service_cost], + 'نسبة_المحتوى_المحلي': [new_service_local_content] + }) + + # إضافة الخدمة الجديدة للجدول الحالي + st.session_state.local_content_services = pd.concat([st.session_state.local_content_services, new_service], ignore_index=True) + st.success(f"تم إضافة الخدمة {new_service_name} بنجاح!") + else: + st.warning("يرجى إدخال اسم الخدمة") + + # عرض جدول الخدمات مع إمكانية التعديل + st.markdown("#### جدول الخدمات") + edited_services = st.data_editor( + st.session_state.local_content_services, + use_container_width=True, + hide_index=True, + key="services_editor" + ) + st.session_state.local_content_services = edited_services + + # زر لحذف الخدمات المحددة + if st.button("حذف الخدمات المحددة"): + st.session_state.local_content_services = pd.DataFrame({ + 'الخدمة': [], + 'التكلفة': [], + 'نسبة_المحتوى_المحلي': [] + }) + st.success("تم حذف جميع الخدمات!") + st.rerun() + + # عرض ملخص الخدمات + total_services_cost = edited_services['التكلفة'].sum() + avg_local_content = (edited_services['التكلفة'] * edited_services['نسبة_المحتوى_المحلي']).sum() / total_services_cost if total_services_cost > 0 else 0 + + st.markdown(f""" + **إجمالي تكلفة الخدمات**: {total_services_cost:,.2f} ريال + + **متوسط نسبة المحتوى المحلي للخدمات**: {avg_local_content*100:.2f}% + + **المستهدف**: 60% + + **الحالة**: {"✅ ملتزم" if avg_local_content >= 0.6 else "❌ غير ملتزم"} + """) + + with lc_tabs[2]: # القوى العاملة + st.markdown("#### بيانات القوى العاملة") + + # إنشاء بيانات افتراضية للقوى العاملة إذا لم تكن موجودة + if 'local_content_labor' not in st.session_state: + st.session_state.local_content_labor = pd.DataFrame({ + 'فئة_العمالة': [ + "مهندسون", + "فنيون", + "عمال بناء", + "إداريون", + "مشرفون" + ], + 'العدد': [5, 10, 30, 3, 4], + 'الراتب_الشهري': [15000, 8000, 3000, 10000, 12000], + 'المدة_بالأشهر': [12, 12, 12, 12, 12], + 'نسبة_المحتوى_المحلي': [0.75, 0.65, 0.60, 0.90, 0.80] + }) + + # حساب التكلفة الإجمالية + st.session_state.local_content_labor['التكلفة_الإجمالية'] = st.session_state.local_content_labor['العدد'] * st.session_state.local_content_labor['الراتب_الشهري'] * st.session_state.local_content_labor['المدة_بالأشهر'] + + # نموذج إضافة فئة عمالة جديدة + st.markdown("#### إضافة فئة عمالة جديدة") + col1, col2 = st.columns(2) + + with col1: + new_labor_category = st.text_input("فئة العمالة", key="new_labor_category", value="") + new_labor_count = st.number_input("العدد", key="new_labor_count", min_value=0, value=0) + + with col2: + new_labor_salary = st.number_input("الراتب الشهري", key="new_labor_salary", min_value=0, value=0) + new_labor_months = st.number_input("المدة بالأشهر", key="new_labor_months", min_value=1, value=12) + + new_labor_local_content = st.slider("نسبة المحتوى المحلي", key="new_labor_local_content", min_value=0.0, max_value=1.0, value=0.8, step=0.01, format="%.2f") + + if st.button("إضافة فئة العمالة"): + if new_labor_category: + # حساب التكلفة الإجمالية + total_cost = new_labor_count * new_labor_salary * new_labor_months + + # إضافة فئة عمالة جديدة للجدول + new_labor = pd.DataFrame({ + 'فئة_العمالة': [new_labor_category], + 'العدد': [new_labor_count], + 'الراتب_الشهري': [new_labor_salary], + 'المدة_بالأشهر': [new_labor_months], + 'نسبة_المحتوى_المحلي': [new_labor_local_content], + 'التكلفة_الإجمالية': [total_cost] + }) + + # إضافة فئة العمالة الجديدة للجدول الحالي + st.session_state.local_content_labor = pd.concat([st.session_state.local_content_labor, new_labor], ignore_index=True) + st.success(f"تم إضافة فئة العمالة {new_labor_category} بنجاح!") + else: + st.warning("يرجى إدخال اسم فئة العمالة") + + # عرض جدول القوى العاملة مع إمكانية التعديل + st.markdown("#### جدول القوى العاملة") + edited_labor = st.data_editor( + st.session_state.local_content_labor, + use_container_width=True, + hide_index=True, + key="labor_editor" + ) + + # إعادة حساب التكلفة الإجمالية بعد التعديل + edited_labor['التكلفة_الإجمالية'] = edited_labor['العدد'] * edited_labor['الراتب_الشهري'] * edited_labor['المدة_بالأشهر'] + st.session_state.local_content_labor = edited_labor + + # زر لحذف فئات العمالة المحددة + if st.button("حذف فئات العمالة المحددة"): + st.session_state.local_content_labor = pd.DataFrame({ + 'فئة_العمالة': [], + 'العدد': [], + 'الراتب_الشهري': [], + 'المدة_بالأشهر': [], + 'نسبة_المحتوى_المحلي': [], + 'التكلفة_الإجمالية': [] + }) + st.success("تم حذف جميع فئات العمالة!") + st.rerun() + + # عرض ملخص القوى العاملة + total_labor_cost = edited_labor['التكلفة_الإجمالية'].sum() + avg_local_content = (edited_labor['التكلفة_الإجمالية'] * edited_labor['نسبة_المحتوى_المحلي']).sum() / total_labor_cost if total_labor_cost > 0 else 0 + + st.markdown(f""" + **إجمالي تكلفة القوى العاملة**: {total_labor_cost:,.2f} ريال + + **متوسط نسبة المحتوى المحلي للقوى العاملة**: {avg_local_content*100:.2f}% + + **المستهدف**: 80% + + **الحالة**: {"✅ ملتزم" if avg_local_content >= 0.8 else "❌ غير ملتزم"} + """) + + with lc_tabs[3]: # التحليل + st.markdown("#### تحليل المحتوى المحلي") + + # حساب المحتوى المحلي الإجمالي + try: + # تجميع بيانات تحليل المحتوى المحلي + products_cost = st.session_state.local_content_products['التكلفة_الإجمالية'].sum() + products_local_content = (st.session_state.local_content_products['التكلفة_الإجمالية'] * st.session_state.local_content_products['نسبة_المحتوى_المحلي']).sum() / products_cost if products_cost > 0 else 0 + + services_cost = st.session_state.local_content_services['التكلفة'].sum() + services_local_content = (st.session_state.local_content_services['التكلفة'] * st.session_state.local_content_services['نسبة_المحتوى_المحلي']).sum() / services_cost if services_cost > 0 else 0 + + labor_cost = st.session_state.local_content_labor['التكلفة_الإجمالية'].sum() + labor_local_content = (st.session_state.local_content_labor['التكلفة_الإجمالية'] * st.session_state.local_content_labor['نسبة_المحتوى_المحلي']).sum() / labor_cost if labor_cost > 0 else 0 + + # حساب الوزن النسبي لكل مكون + total_cost = products_cost + services_cost + labor_cost + products_weight = products_cost / total_cost if total_cost > 0 else 0 + services_weight = services_cost / total_cost if total_cost > 0 else 0 + labor_weight = labor_cost / total_cost if total_cost > 0 else 0 + + # حساب المحتوى المحلي الإجمالي + total_local_content = (products_local_content * products_weight) + (services_local_content * services_weight) + (labor_local_content * labor_weight) + + # عرض ملخص المحتوى المحلي + st.markdown("### ملخص المحتوى المحلي") + + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("إجمالي التكاليف", f"{total_cost:,.2f} ريال") + + with col2: + st.metric("نسبة المحتوى المحلي الإجمالية", f"{total_local_content*100:.2f}%") + + with col3: + target_local_content = 0.7 # 70% + st.metric("الحالة", "ملتزم" if total_local_content >= target_local_content else "غير ملتزم", delta=f"{(total_local_content - target_local_content)*100:.2f}%") + + # عرض رسم بياني للمقارنة + st.markdown("### تحليل بصري للمحتوى المحلي") + + # رسم بياني شريطي لنسب المحتوى المحلي + categories = ['المنتجات', 'الخدمات', 'القوى العاملة', 'الإجمالي'] + actual_values = [products_local_content * 100, services_local_content * 100, labor_local_content * 100, total_local_content * 100] + target_values = [70, 60, 80, 70] # المستهدفات + + # تهيئة البيانات للرسم البياني + chart_data = pd.DataFrame({ + 'الفئة': categories, + 'النسبة الفعلية': actual_values, + 'النسبة المستهدفة': target_values + }) + + # رسم بياني شريطي للمقارنة + fig = go.Figure() + + fig.add_trace(go.Bar( + x=chart_data['الفئة'], + y=chart_data['النسبة الفعلية'], + name='النسبة الفعلية', + marker_color='rgb(26, 118, 255)' + )) + + fig.add_trace(go.Bar( + x=chart_data['الفئة'], + y=chart_data['النسبة المستهدفة'], + name='النسبة المستهدفة', + marker_color='rgb(55, 83, 109)' + )) + + 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) + + # عرض توصيات لتحسين نسبة المحتوى المحلي + st.markdown("### توصيات لتحسين نسبة المحتوى المحلي") + + recommendations = [] + + if products_local_content < 0.7: + recommendations.append("- زيادة نسبة المحتوى المحلي للمنتجات من خلال:") + recommendations.append(" - البحث عن موردين محليين للمنتجات ذات النسبة المنخفضة") + recommendations.append(" - استبدال المنتجات المستوردة ببدائل محلية") + recommendations.append(" - التعاون مع المصانع المحلية لتوطين صناعة المنتجات") + + if services_local_content < 0.6: + recommendations.append("- زيادة نسبة المحتوى المحلي للخدمات من خلال:") + recommendations.append(" - التعاقد مع شركات خدمات محلية") + recommendations.append(" - تحويل الخدمات المستعان بها من الخارج إلى شركات محلية") + recommendations.append(" - تأهيل الشركات المحلية لتقديم الخدمات المطلوبة") + + if labor_local_content < 0.8: + recommendations.append("- زيادة نسبة المحتوى المحلي للقوى العاملة من خلال:") + recommendations.append(" - زيادة توظيف الكوادر المحلية") + recommendations.append(" - تدريب وتأهيل العمالة المحلية") + recommendations.append(" - استبدال العمالة الأجنبية بكوادر محلية تدريجياً") + + if total_local_content < 0.7: + recommendations.append("- زيادة نسبة المحتوى المحلي الإجمالية من خلال:") + recommendations.append(" - إعادة توزيع الميزانية لصالح المكونات ذات النسبة العالية من المحتوى المحلي") + recommendations.append(" - وضع خطة مرحلية لزيادة المحتوى المحلي") + recommendations.append(" - التعاون مع اللجنة المحلية لزيادة المحتوى المحلي") + + if recommendations: + for rec in recommendations: + st.markdown(rec) + else: + st.success("تهانينا! نسبة المحتوى المحلي متوافقة مع المتطلبات.") + + # حساب تأثير المحتوى المحلي على التسعير + st.markdown("### تأثير المحتوى المحلي على التسعير") + + # تحديد عامل تعديل السعر بناءً على نسبة المحتوى المحلي + price_adjustment_factor = 1.0 + + if total_local_content >= 0.9: + price_adjustment_factor = 0.92 # خصم 8% للمحتوى المحلي العالي جداً + price_discount = "8%" + elif total_local_content >= 0.8: + price_adjustment_factor = 0.94 # خصم 6% للمحتوى المحلي العالي + price_discount = "6%" + elif total_local_content >= 0.7: + price_adjustment_factor = 0.96 # خصم 4% للمحتوى المحلي المتوسط + price_discount = "4%" + elif total_local_content >= 0.6: + price_adjustment_factor = 0.98 # خصم 2% للمحتوى المحلي المنخفض + price_discount = "2%" + else: + price_adjustment_factor = 1.0 # لا خصم + price_discount = "0%" + + # عرض تأثير المحتوى المحلي على التسعير + original_total = st.session_state.current_pricing['items']['الإجمالي'].sum() + adjusted_total = original_total * price_adjustment_factor + discount_amount = original_total - adjusted_total + + col1, col2, col3 = st.columns(3) + + with col1: + st.metric("إجمالي التسعير الأصلي", f"{original_total:,.2f} ريال") + + with col2: + st.metric("نسبة الخصم بسبب المحتوى المحلي", price_discount) + + with col3: + st.metric("إجمالي التسعير بعد الخصم", f"{adjusted_total:,.2f} ريال", delta=f"-{discount_amount:,.2f} ريال") + + # أزرار العمليات + col1, col2 = st.columns(2) + + with col1: + if st.button("حفظ تحليل المحتوى المحلي"): + # حفظ بيانات المحتوى المحلي في التسعير الحالي + st.session_state.current_pricing['local_content'] = { + 'products': st.session_state.local_content_products.copy(), + 'services': st.session_state.local_content_services.copy(), + 'labor': st.session_state.local_content_labor.copy(), + 'total_local_content': total_local_content, + 'price_adjustment_factor': price_adjustment_factor + } + + st.success("تم حفظ تحليل المحتوى المحلي بنجاح!") + + with col2: + if st.button("تصدير تقرير المحتوى المحلي"): + st.success("تم تصدير تقرير المحتوى المحلي بنجاح!") + + except Exception as e: + st.error(f"حدث خطأ أثناء تحليل المحتوى المحلي: {str(e)}") + st.warning("تأكد من إدخال بيانات المحتوى المحلي بشكل صحيح في التبويبات السابقة.") + + def _render_utilities_tab(self): + """عرض تبويب الأدوات المساعدة""" + import json + import copy + from datetime import datetime + + st.markdown("## الأدوات المساعدة") + + utilities_tab1, utilities_tab2, utilities_tab3, utilities_tab4, utilities_tab5 = st.tabs([ + "الرسوم البيانية المتقدمة", + "استيراد/تصدير الإعدادات", + "النسخ الاحتياطي والاستعادة", + "مقارنة نماذج التسعير", + "إنشاء التقارير" + ]) + + with utilities_tab1: + st.markdown("### الرسوم البيانية المتقدمة لتحليل ا��تكلفة") + + if 'item_cost_result' in st.session_state: + result = st.session_state.item_cost_result + + # تبويب الرسوم البيانية + chart_tab1, chart_tab2, chart_tab3 = st.tabs(["توزيع التكلفة", "مقارنة المكونات", "تأثير العوامل"]) + + with chart_tab1: + # رسم بياني دائري لتوزيع التكلفة + fig = go.Figure(data=[go.Pie( + labels=["المواد", "العمالة", "المعدات", "المصاريف الإدارية", "هامش الربح"], + values=[ + result['تكاليف_مباشرة']['المواد']['الإجمالي'], + result['تكاليف_مباشرة']['العمالة']['الإجمالي'], + result['تكاليف_مباشرة']['المعدات']['الإجمالي'], + result['مصاريف_إدارية']['قيمة'], + result['هامش_ربح']['قيمة'] + ], + hole=.3, + marker_colors=['#36a2eb', '#ff6384', '#4bc0c0', '#ffcd56', '#9966ff'] + )]) + fig.update_layout(title_text="توزيع مكونات التكلفة") + st.plotly_chart(fig, use_container_width=True) + + with chart_tab2: + # رسم بياني شريطي للمكونات الرئيسية + categories = ["المواد", "العمالة", "المعدات"] + values = [ + result['تكاليف_مباشرة']['المواد']['الإجمالي'], + result['تكاليف_مباشرة']['العمالة']['الإجمالي'], + result['تكاليف_مباشرة']['المعدات']['الإجمالي'] + ] + + fig = go.Figure(data=[go.Bar(x=categories, y=values, marker_color=['#36a2eb', '#ff6384', '#4bc0c0'])]) + fig.update_layout(title_text="مقارنة بين المكونات الرئيسية للتكلفة") + st.plotly_chart(fig, use_container_width=True) + + with chart_tab3: + # مخطط شريطي يوضح تأثير عوامل التعديل على التكلفة + original_cost = result['التكلفة_الإجمالية'] + final_cost = result['السعر_المعدل']['إجمالي'] + + # التحقق من وجود العوامل، وإضافتها بقيم افتراضية إذا كانت غير موجودة + if 'عوامل_التعديل' not in result: + result['عوامل_التعديل'] = {} + + # إضافة المفاتيح الناقصة بقيم افتراضية + default_factors = { + 'location_factor': 1.0, + 'time_factor': 1.0, + 'risk_factor': 1.0, + 'market_factor': 1.0 + } + + for key, default_value in default_factors.items(): + if key not in result['عوامل_التعديل']: + result['عوامل_التعديل'][key] = default_value + + factors = { + "معامل الموقع": result['عوامل_التعديل']['location_factor'], + "معامل الوقت": result['عوامل_التعديل']['time_factor'], + "معامل المخاطر": result['عوامل_التعديل']['risk_factor'], + "معامل السوق": result['عوامل_التعديل']['market_factor'] + } + + # حساب القيمة المضافة من كل عامل + factor_effects = {} + for factor_name, factor_value in factors.items(): + factor_effects[factor_name] = original_cost * (factor_value - 1) + + fig = go.Figure() + fig.add_trace(go.Waterfall( + name="تأثير العوامل", + orientation="v", + measure=["absolute"] + ["relative"] * len(factor_effects) + ["total"], + x=["التكلفة الأصلية"] + list(factor_effects.keys()) + ["التكلفة النهائية"], + y=[original_cost] + list(factor_effects.values()) + [0], + text=[f"{original_cost:,.2f}"] + [f"{val:,.2f}" for val in factor_effects.values()] + [f"{final_cost:,.2f}"], + connector={"line": {"color": "rgb(63, 63, 63)"}}, + )) + + fig.update_layout(title_text="تأثير عوامل التعديل على التكلفة النهائية") + st.plotly_chart(fig, use_container_width=True) + else: + st.warning("لم يتم العثور على بيانات تحليل التكلفة. يرجى إجراء تحليل تكلفة في تبويب 'تحليل سعر البند' أولاً.") + + with utilities_tab2: + st.markdown("### استيراد/تصدير إعدادات التسعير") + + export_col, import_col = st.columns(2) + + with export_col: + st.markdown("#### تصدير الإعدادات الحالية") + if st.button("تصدير إعدادات التسعير", key="export_pricing_settings"): + pricing_settings = { + "construction_item": st.session_state.construction_item if 'construction_item' in st.session_state else None, + "current_pricing": st.session_state.current_pricing if 'current_pricing' in st.session_state else None + } + settings_json = json.dumps(pricing_settings, ensure_ascii=False, indent=2) + st.download_button( + label="تنزيل إعدادات التسعير", + data=settings_json, + file_name="pricing_settings.json", + mime="application/json", + key="download_settings_button" + ) + + with import_col: + st.markdown("#### استيراد إعدادات سابقة") + uploaded_file = st.file_uploader("استيراد إعدادات تسعير سابقة", type=["json"], key="upload_pricing_settings") + if uploaded_file is not None: + try: + settings_data = json.loads(uploaded_file.getvalue().decode('utf-8')) + # تحديث بيانات التسعير في الجلسة + if 'construction_item' in settings_data and settings_data['construction_item']: + st.session_state.construction_item = settings_data['construction_item'] + if 'current_pricing' in settings_data and settings_data['current_pricing']: + st.session_state.current_pricing = settings_data['current_pricing'] + st.success("تم استيراد الإعدادات بنجاح!") + st.rerun() + except Exception as e: + st.error(f"حدث خطأ أثناء استيراد الإعدادات: {str(e)}") + + with utilities_tab3: + st.markdown("### النسخ الاحتياطي والاستعادة") + backup_tab1, backup_tab2 = st.tabs(["إنشاء نسخة احتياطية", "استعادة من نسخة احتياطية"]) + + with backup_tab1: + st.markdown("#### إنشاء نسخة احتياطية كاملة") + st.markdown("تقوم هذه العملية بإنشاء نسخة احتياطية كاملة لجميع بيانات التسعير الحالية.") + + if st.button("إنشاء نسخة احتياطية كاملة", key="create_full_backup"): + backup_data = { + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "construction_item": st.session_state.construction_item if 'construction_item' in st.session_state else None, + "current_pricing": st.session_state.current_pricing if 'current_pricing' in st.session_state else None, + "pricing_models": st.session_state.pricing_models if 'pricing_models' in st.session_state else [], + "manual_items": st.session_state.manual_items.to_dict('records') if 'manual_items' in st.session_state else [] + } + + backup_json = json.dumps(backup_data, ensure_ascii=False, indent=2) + + filename = f"wahbi_pricing_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + st.download_button( + label="تنزيل النسخة الاحتياطية", + data=backup_json, + file_name=filename, + mime="application/json", + key="download_backup_button" + ) + st.success("تم إنشاء النسخة الاحتياطية بنجاح!") + + with backup_tab2: + st.markdown("#### استعادة من نسخة احتياطية") + st.markdown("يمكنك استعادة بيانات التسعير من نسخة احتياطية سابقة.") + + backup_file = st.file_uploader("استعادة من نسخة احتياطية", type=["json"], key="restore_backup_file") + if backup_file is not None: + if st.button("استعادة البيانات", key="restore_from_backup"): + try: + backup_data = json.loads(backup_file.getvalue().decode('utf-8')) + + # استعادة البيانات إلى الحالة الحالية + if 'construction_item' in backup_data and backup_data['construction_item']: + st.session_state.construction_item = backup_data['construction_item'] + + if 'current_pricing' in backup_data and backup_data['current_pricing']: + st.session_state.current_pricing = backup_data['current_pricing'] + + if 'pricing_models' in backup_data: + st.session_state.pricing_models = backup_data['pricing_models'] + + if 'manual_items' in backup_data and backup_data['manual_items']: + st.session_state.manual_items = pd.DataFrame(backup_data['manual_items']) + + st.success(f"تم استعادة البيانات من النسخة الاحتياطية بنجاح! (تاريخ النسخة: {backup_data.get('timestamp', 'غير معروف')})") + st.rerun() + except Exception as e: + st.error(f"حدث خطأ أثناء استعادة البيانات: {str(e)}") + + with utilities_tab4: + st.markdown("### مقارنة نماذج التسعير") + st.markdown("هذه الأداة تتيح لك مقارنة نماذج التسعير المختلفة واختيار الأفضل منها.") + + # تهيئة قائمة نماذج التسعير إذا لم تكن موجودة + if 'pricing_models' not in st.session_state: + st.session_state.pricing_models = [] + + # إضافة النموذج الحالي للمقارنة + if st.button("إضافة النموذج الحالي للمقارنة", key="add_current_model"): + if 'current_pricing' in st.session_state and st.session_state.current_pricing is not None: + model_name = st.session_state.current_pricing.get('name', 'نموذج بدون اسم') + model_data = copy.deepcopy(st.session_state.current_pricing) + # تحقق من عدم وجود نموذج بنفس الاسم + exists = False + for model in st.session_state.pricing_models: + if model.get('name') == model_name: + exists = True + break + + if not exists: + st.session_state.pricing_models.append(model_data) + st.success(f"تم إضافة نموذج '{model_name}' للمقارنة!") + else: + st.warning("يوجد نموذج بنفس الاسم في المقارنة بالفعل!") + else: + st.error("لا يوجد نموذج تسعير حالي للإضافة. يرجى إنشاء تسعير جديد أولاً.") + + # عرض جدول المقارنة إذا كان هناك نماذج مضافة + if len(st.session_state.pricing_models) > 0: + st.markdown("### جدول مقارنة نماذج التسعير") + + comparison_data = [] + for model in st.session_state.pricing_models: + # حساب إجمالي التكلفة + items_df = pd.DataFrame(model.get('items', {})) + total_price = 0 + if not items_df.empty and 'الإجمالي' in items_df.columns: + total_price = items_df['الإجمالي'].sum() + + comparison_data.append({ + "اسم النموذج": model.get('name', 'غير معروف'), + "طريقة التسعير": model.get('method', 'غير معروفة'), + "إجمالي التكلفة": f"{total_price:,.2f} ريال", + "عدد البنود": len(items_df) if not items_df.empty else 0, + }) + + comparison_df = pd.DataFrame(comparison_data) + st.dataframe(comparison_df, use_container_width=True, hide_index=True) + + # عرض رسم بياني للمقارنة + if len(comparison_data) > 1: + st.markdown("### رسم بياني للمقارنة") + + # استخراج البيانات للرسم البياني + models = [data["اسم النموذج"] for data in comparison_data] + values = [float(data["إجمالي التكلفة"].replace("ريال", "").replace(",", "")) for data in comparison_data] + + # رسم بياني شريطي + fig = go.Figure(data=[ + go.Bar(x=models, y=values, marker_color='rgb(26, 118, 255)') + ]) + + fig.update_layout( + title="مقارنة التكلفة الإجمالية بين نماذج التسعير", + xaxis_title="نموذج التسعير", + yaxis_title="التكلفة الإجمالية (ريال)" + ) + + st.plotly_chart(fig, use_container_width=True) + + # أزرار إدارة النماذج + col1, col2 = st.columns(2) + with col1: + if st.button("حذف جميع النماذج", key="clear_comparison"): + st.session_state.pricing_models = [] + st.success("تم مسح جميع نماذج المقارنة!") + st.rerun() + + with col2: + # تحديد نموذج للحذف + model_to_delete = st.selectbox( + "اختر نموذج للحذف من المقارنة", + options=[model.get('name', f"نموذج {i+1}") for i, model in enumerate(st.session_state.pricing_models)], + key="model_to_delete" + ) + + if st.button("حذف النموذج المحدد", key="delete_selected_model"): + for i, model in enumerate(st.session_state.pricing_models): + if model.get('name') == model_to_delete: + st.session_state.pricing_models.pop(i) + st.warning(f"تم حذف النموذج '{model_to_delete}'") + st.rerun() + break + else: + st.info("لا توجد نماذج للمقارنة حالياً. يرجى إضافة النموذج الحالي للمقارنة أولاً.") + + with utilities_tab5: + st.markdown("### إنشاء تقارير التسعير") + st.markdown("يمكنك استخدام هذه الأداة لإنشاء تقارير مفصلة عن التسعير.") + + report_type = st.selectbox( + "اختر نوع التقرير", + options=["تقرير ملخص", "تقرير تفصيلي", "تقرير المقارنة"], + key="report_type_selector" + ) + + if st.button("إنشاء التقرير", key="generate_report_button"): + if report_type == "تقرير ملخص" and 'current_pricing' in st.session_state and st.session_state.current_pricing is not None: + # إنشاء تقرير ملخص + if isinstance(st.session_state.current_pricing.get('items'), pd.DataFrame): + df = st.session_state.current_pricing['items'].copy() + + # حساب الإجماليات + total_price = df['الإجمالي'].sum() if 'الإجمالي' in df.columns else 0 + + # تقدير تكاليف المكونات + materials_cost = total_price * 0.6 # تقدير تقريبي للمواد + labor_cost = total_price * 0.25 # تقدير تقريبي للعمالة + equipment_cost = total_price * 0.15 # تقدير تقريبي للمعدات + admin_cost = total_price * 0.05 # تقدير تقريبي للمصاريف الإدارية + profit_margin = total_price * 0.1 # تقدير تقريبي لهامش الربح + final_total = total_price * 1.15 # إجمالي بعد إضافة المصاريف الإدارية وهامش الربح + + # إنشاء جدول الملخص + summary = pd.DataFrame({ + "بند التكلفة": ["إجمالي المواد", "إجمالي الأجور", "إجمالي المعدات", "المصاريف الإدارية", "هامش الربح", "الإجمالي النهائي"], + "القيمة": [ + materials_cost, + labor_cost, + equipment_cost, + admin_cost, + profit_margin, + final_total + ] + }) + + # تنسيق التقرير + styled_df = summary.style.format({ + "القيمة": "{:,.2f} ريال" + }) + + # تحويل الجدول إلى HTML + html = f""" + + + + تقرير ملخص التسعير + + + +
+

تقرير ملخص التسعير

+

{st.session_state.current_pricing.get('name', 'تسعير بدون اسم')}

+

تاريخ التقرير: {datetime.now().strftime('%Y-%m-%d %H:%M')}

+
+ +

ملخص التكاليف

+ {styled_df.to_html()} + +

البيانات الأساسية

+ + + + + + """ + + # تقديم زر التنزيل + st.download_button( + label="تنزيل التقرير الملخص", + data=html, + file_name="pricing_summary_report.html", + mime="text/html", + key="download_summary_report" + ) + + st.success("تم إنشاء التقرير الملخص بنجاح!") + else: + st.error("تعذر قراءة بيانات التسعير الحالي. يرجى التأكد من وجود تسعير صالح.") + + elif report_type == "تقرير تفصيلي" and 'current_pricing' in st.session_state and st.session_state.current_pricing is not None: + # سيتم تنفيذ التقرير التفصيلي + st.info("جاري إعداد التقرير التفصيلي...") + # هنا يمكن تنفيذ كود إنشاء التقرير التفصيلي + st.success("تم إنشاء التقرير التفصيلي. سيتم تطوير هذه الميزة قريباً.") + + elif report_type == "تقرير المقارنة" and 'pricing_models' in st.session_state and len(st.session_state.pricing_models) > 0: + # سيتم تنفيذ تقرير المقارنة + st.info("جاري إعداد تقرير المقارنة...") + # هنا يمكن تنفيذ كود إنشاء تقرير المقارنة + st.success("تم إنشاء تقرير المقارنة. سيتم تطوير هذه الميزة قريباً.") + + else: + st.error("لا توجد بيانات كافية لإنشاء التقرير المطلوب. يرجى التأكد من وجود تسعير أو نماذج مقارنة.") \ No newline at end of file