diff --git "a/modules/pricing/pricing_app.py" "b/modules/pricing/pricing_app.py" deleted file mode 100644--- "a/modules/pricing/pricing_app.py" +++ /dev/null @@ -1,4358 +0,0 @@ -""" -تطبيق وحدة التسعير المتكاملة -""" - -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) - :return: زر مُنسّق - """ - # استخدام مكونات Streamlit فقط بدون HTML - with st.container(): - # إنشاء مساحة تعرض الزر فقط - col1 = st.columns([1]) - - # إضافة الأيقونة للنص إذا تم تزويدها - display_label = f"{icon} {label}" if icon else label - - # إنشاء الزر مباشرة باستخدام Streamlit - clicked = col1[0].button( - display_label, - key=key, - on_click=on_click, - args=args, - use_container_width=full_width - ) - - return clicked - -# وظيفة لإنشاء أزرار أيقونات صغيرة -def icon_button(icon, key, type="primary", on_click=None, args=None, tooltip=""): - """ - إنشاء زر أيقونة صغير - :param icon: الأيقونة (emoji) - :param key: مفتاح الزر الفريد - :param type: نوع التنسيق - :param on_click: الدالة التي سيتم تنفيذها عند النقر - :param args: معاملات الدالة - :param tooltip: تلميح عند تمرير المؤشر فوق الزر - :return: زر أيقونة - """ - # استخدام مكونات Streamlit فقط - with st.container(): - # إضافة تلميح باستخدام Streamlit - if tooltip: - st.caption(tooltip) - - # إنشاء زر الأيقونة - clicked = st.button( - icon, - key=key, - on_click=on_click, - args=args, - help=tooltip # استخدام خاصية help لعرض التلميح - ) - - 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): - """عرض واجهة وحدة التسعير""" - - # استخدام مكونات Streamlit مباشرة بدلاً من HTML - st.title("وحدة التسعير المتكاملة") - - 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.subheader("بنود التسعير غير المتزن") - - # تطبيق التنسيق على الجدول - 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.subheader("مقارنة التسعير المتوازن وغير المتوازن") - - 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.subheader("تحليل بصري للتسعير غير المتوازن") - - # إعداد البيانات للرسم البياني - 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.divider() - st.subheader("حفظ وتصدير البيانات") - - 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