diff --git "a/modules/pricing/pricing_app.py" "b/modules/pricing/pricing_app.py" --- "a/modules/pricing/pricing_app.py" +++ "b/modules/pricing/pricing_app.py" @@ -1,1366 +1,937 @@ -""" -وحدة التسعير المتكاملة -""" - import streamlit as st import pandas as pd import numpy as np -import random -from datetime import datetime -import time +import io +import base64 +import datetime +import json +import os +from PIL import Image +import matplotlib.pyplot as plt +import seaborn as sns class PricingApp: - """ - وحدة التسعير المتكاملة للنظام - """ - def __init__(self): """ - تهيئة وحدة التسعير - """ - # تهيئة حالة الجلسة الخاصة بالتسعير إذا لم تكن موجودة - if 'pricing_projects' not in st.session_state: - # إنشاء بيانات تجريبية للمشاريع - st.session_state.pricing_projects = self._generate_sample_projects() - - if 'pricing_templates' not in st.session_state: - # إنشاء بيانات تجريبية لقوالب التسعير - st.session_state.pricing_templates = self._generate_sample_templates() - - if 'pricing_resources' not in st.session_state: - # إنشاء بيانات تجريبية للموارد - st.session_state.pricing_resources = self._generate_sample_resources() - - def render(self): - """ - طريقة للتوافق مع الواجهة القديمة - تقوم باستدعاء طريقة run - """ - self.run() - - def run(self): - """ - تشغيل وحدة التسعير - """ - st.markdown("

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

", unsafe_allow_html=True) - - # إضافة زر إنشاء تسعير جديد - if st.button("إنشاء تسعير جديد", key="create_new_pricing_btn", type="primary"): - st.session_state.show_new_pricing_form = True - - # عرض نموذج إنشاء تسعير جديد - if 'show_new_pricing_form' not in st.session_state: - st.session_state.show_new_pricing_form = False + تهيئة وحدة التسعير المحسنة + """ + self.initialize_session_state() + self.set_page_config() + + def initialize_session_state(self): + """ + تهيئة متغيرات حالة الجلسة + """ + if 'projects' not in st.session_state: + st.session_state.projects = [ + { + "id": 1, + "name": "مشروع تطوير البنية التحتية", + "client": "وزارة الإسكان", + "estimated_value": 5000000, + "deadline": "2023-12-31", + "status": "قيد التنفيذ", + "completion": 35, + "profit_margin": 15 + }, + { + "id": 2, + "name": "مشروع إنشاء مجمع سكني", + "client": "شركة الإعمار", + "estimated_value": 8500000, + "deadline": "2024-06-30", + "status": "قيد التخطيط", + "completion": 0, + "profit_margin": 18 + } + ] - if st.session_state.show_new_pricing_form: - st.markdown("### إنشاء تسعير جديد") + if 'current_project' not in st.session_state: + st.session_state.current_project = None + + if 'bill_of_quantities' not in st.session_state: + st.session_state.bill_of_quantities = [ + { + "project_id": 1, + "items": [ + {"id": 1, "description": "حفر أساسات", "unit": "م³", "quantity": 250, "unit_price": 120, "total_price": 30000}, + {"id": 2, "description": "صب خرسانة", "unit": "م³", "quantity": 180, "unit_price": 350, "total_price": 63000}, + {"id": 3, "description": "توريد وتركيب حديد تسليح", "unit": "طن", "quantity": 25, "unit_price": 5000, "total_price": 125000} + ] + }, + { + "project_id": 2, + "items": [ + {"id": 1, "description": "أعمال حفر", "unit": "م³", "quantity": 500, "unit_price": 100, "total_price": 50000}, + {"id": 2, "description": "أعمال خرسانة", "unit": "م³", "quantity": 300, "unit_price": 400, "total_price": 120000} + ] + } + ] - col1, col2 = st.columns(2) + if 'materials_catalog' not in st.session_state: + st.session_state.materials_catalog = [ + {"id": 1, "name": "أسمنت بورتلاندي", "unit": "طن", "price": 400, "category": "مواد بناء أساسية"}, + {"id": 2, "name": "حديد تسليح 8 مم", "unit": "طن", "price": 4800, "category": "حديد تسليح"}, + {"id": 3, "name": "حديد تسليح 10 مم", "unit": "طن", "price": 4700, "category": "حديد تسليح"}, + {"id": 4, "name": "حديد تسليح 12 مم", "unit": "طن", "price": 4600, "category": "حديد تسليح"}, + {"id": 5, "name": "رمل", "unit": "م³", "price": 80, "category": "مواد بناء أساسية"}, + {"id": 6, "name": "زلط", "unit": "م³", "price": 150, "category": "مواد بناء أساسية"}, + {"id": 7, "name": "طوب أحمر", "unit": "1000 طوبة", "price": 800, "category": "مواد بناء أساسية"}, + {"id": 8, "name": "طوب أسمنتي", "unit": "1000 طوبة", "price": 1200, "category": "مواد بناء أساسية"}, + {"id": 9, "name": "خشب", "unit": "م³", "price": 3500, "category": "مواد نجارة"}, + {"id": 10, "name": "دهان بلاستيك", "unit": "برميل", "price": 500, "category": "دهانات"} + ] - with col1: - new_project_name = st.text_input("اسم المشروع", key="new_project_name") - new_project_client = st.text_input("العميل", key="new_project_client") + if 'equipment_catalog' not in st.session_state: + st.session_state.equipment_catalog = [ + {"id": 1, "name": "حفار", "daily_rate": 1500, "category": "معدات حفر"}, + {"id": 2, "name": "لودر", "daily_rate": 1200, "category": "معدات حفر"}, + {"id": 3, "name": "خلاطة خرسانة", "daily_rate": 800, "category": "معدات خرسانة"}, + {"id": 4, "name": "هزاز خرسانة", "daily_rate": 200, "category": "معدات خرسانة"}, + {"id": 5, "name": "شاحنة نقل", "daily_rate": 1000, "category": "معدات نقل"}, + {"id": 6, "name": "رافعة برجية", "daily_rate": 2500, "category": "معدات رفع"}, + {"id": 7, "name": "مضخة خرسانة", "daily_rate": 1800, "category": "معدات خرسانة"}, + {"id": 8, "name": "مولد كهرباء", "daily_rate": 600, "category": "معدات كهربائية"}, + {"id": 9, "name": "كمبروسر هواء", "daily_rate": 400, "category": "معدات متنوعة"}, + {"id": 10, "name": "معدات يدوية", "daily_rate": 200, "category": "معدات متنوعة"} + ] - with col2: - new_project_value = st.number_input("القيمة التقديرية", min_value=0, value=1000000, step=100000, key="new_project_value") - new_project_deadline = st.date_input("الموعد النهائي", key="new_project_deadline") - - if st.button("حفظ التسعير الجديد", key="save_new_pricing_btn"): - if new_project_name and new_project_client: - # إضافة المشروع الجديد إلى قائمة المشاريع - new_project = { - 'id': len(st.session_state.pricing_projects) + 1, - 'name': new_project_name, - 'client': new_project_client, - 'value': new_project_value, - 'deadline': new_project_deadline.strftime('%Y-%m-%d'), - 'status': 'قيد التسعير', - 'completion': 0 - } - - st.session_state.pricing_projects.append(new_project) - st.success(f"تم إنشاء تسعير جديد للمشروع '{new_project_name}' بنجاح") - st.session_state.show_new_pricing_form = False - st.experimental_rerun() - else: - st.warning("يرجى إدخال اسم المشروع والعميل") - - if st.button("إلغاء", key="cancel_new_pricing_btn"): - st.session_state.show_new_pricing_form = False - st.experimental_rerun() - - # إنشاء تبويبات للتسعير المختلفة - tabs = st.tabs(["لوحة التحكم", "تسعير المناقصات", "جداول الكميات", "تحليل الأسعار", "قوالب التسعير"]) - - with tabs[0]: - self._render_dashboard() - - with tabs[1]: - self._render_tender_pricing() - - with tabs[2]: - self._render_bill_of_quantities() - - with tabs[3]: - self._render_price_analysis() - - with tabs[4]: - self._render_pricing_templates() - - def _render_dashboard(self): - """ - عرض لوحة التحكم - """ - st.markdown("### لوحة تحكم التسعير") - - # عرض المؤشرات الرئيسية - col1, col2, col3, col4 = st.columns(4) - - with col1: - active_tenders = len([p for p in st.session_state.pricing_projects if p['status'] == 'قيد التسعير']) - st.info(f"### {active_tenders}\nمناقصات قيد التسعير", icon="📝") - - with col2: - completed_tenders = len([p for p in st.session_state.pricing_projects if p['status'] == 'تم التسعير']) - st.success(f"### {completed_tenders}\nمناقصات تم تسعيرها", icon="✅") - - with col3: - awarded_tenders = len([p for p in st.session_state.pricing_projects if p['status'] == 'تمت الترسية']) - st.success(f"### {awarded_tenders}\nمناقصات تمت ترسيتها", icon="🏆") - - with col4: - rejected_tenders = len([p for p in st.session_state.pricing_projects if p['status'] == 'مرفوضة']) - st.error(f"### {rejected_tenders}\nمناقصات مرفوضة", icon="❌") - - # عرض المناقصات الحالية - st.markdown("### المناقصات الحالية") - - # تصفية المناقصات النشطة - active_projects = [p for p in st.session_state.pricing_projects if p['status'] in ['قيد التسعير', 'تم التسعير']] - - if active_projects: - # تحويل البيانات إلى DataFrame - df = pd.DataFrame(active_projects) - df = df[['id', 'name', 'client', 'value', 'deadline', 'status', 'completion']] - df.columns = ['الرقم', 'اسم المناقصة', 'العميل', 'القيمة التقديرية', 'الموعد النهائي', 'الحالة', 'نسبة الإنجاز'] + if 'labor_rates' not in st.session_state: + st.session_state.labor_rates = [ + {"id": 1, "role": "عامل عادي", "daily_rate": 150, "category": "عمالة عادية"}, + {"id": 2, "role": "نجار", "daily_rate": 250, "category": "عمالة ماهرة"}, + {"id": 3, "role": "حداد", "daily_rate": 250, "category": "عمالة ماهرة"}, + {"id": 4, "role": "مبيض محارة", "daily_rate": 230, "category": "عمالة ماهرة"}, + {"id": 5, "role": "سباك", "daily_rate": 270, "category": "عمالة ماهرة"}, + {"id": 6, "role": "كهربائي", "daily_rate": 280, "category": "عمالة ماهرة"}, + {"id": 7, "role": "مشغل معدات", "daily_rate": 300, "category": "عمالة ماهرة"}, + {"id": 8, "role": "مهندس موقع", "daily_rate": 600, "category": "مهندسين"}, + {"id": 9, "role": "مهندس مدني", "daily_rate": 700, "category": "مهندسين"}, + {"id": 10, "role": "مدير مشروع", "daily_rate": 1000, "category": "إدارة"} + ] - # تنسيق القيم - df['القيمة التقديرية'] = df['القيمة التقديرية'].apply(lambda x: f"{x:,} ريال") - df['نسبة الإنجاز'] = df['نسبة الإنجاز'].apply(lambda x: f"{x}%") + if 'item_analysis' not in st.session_state: + st.session_state.item_analysis = [ + { + "project_id": 1, + "item_id": 1, + "materials": [ + {"material_id": 5, "quantity": 0.2, "cost": 16}, + ], + "equipment": [ + {"equipment_id": 1, "days": 0.05, "cost": 75}, + {"equipment_id": 2, "days": 0.05, "cost": 60} + ], + "labor": [ + {"labor_id": 1, "days": 0.1, "cost": 15}, + {"labor_id": 7, "days": 0.05, "cost": 15} + ], + "overhead": 10, + "profit": 15, + "total_cost": 120 + }, + { + "project_id": 1, + "item_id": 2, + "materials": [ + {"material_id": 1, "quantity": 0.3, "cost": 120}, + {"material_id": 5, "quantity": 0.4, "cost": 32}, + {"material_id": 6, "quantity": 0.8, "cost": 120} + ], + "equipment": [ + {"equipment_id": 3, "days": 0.05, "cost": 40}, + {"equipment_id": 4, "days": 0.05, "cost": 10}, + {"equipment_id": 7, "days": 0.02, "cost": 36} + ], + "labor": [ + {"labor_id": 1, "days": 0.2, "cost": 30}, + {"labor_id": 7, "days": 0.05, "cost": 15} + ], + "overhead": 12, + "profit": 15, + "total_cost": 350 + } + ] - # عرض الجدول - st.dataframe(df, use_container_width=True) - else: - st.info("لا توجد مناقصات نشطة حالياً", icon="ℹ️") - - # عرض إحصائيات التسعير - st.markdown("### إحصائيات التسعير") - - col1, col2 = st.columns(2) - - with col1: - st.markdown("#### نسب النجاح حسب نوع المشروع") - - # إنشاء بيانات تجريبية - success_by_type = { - "طرق وجسور": 75, - "مباني": 60, - "بنية تحتية": 80, - "مياه وصرف صحي": 65, - "كهرباء": 70 - } + if 'show_new_project_form' not in st.session_state: + st.session_state.show_new_project_form = False - # تحويل البيانات إلى DataFrame - success_df = pd.DataFrame({ - "نوع المشروع": list(success_by_type.keys()), - "نسبة النجاح": list(success_by_type.values()) - }) + if 'show_item_analysis' not in st.session_state: + st.session_state.show_item_analysis = False - # عرض الرسم البياني - st.bar_chart(success_df.set_index("نوع المشروع")) - - with col2: - st.markdown("#### متوسط هامش الربح حسب العميل") - - # إنشاء بيانات تجريبية - margin_by_client = { - "وزارة النقل": 12, - "وزارة الإسكان": 15, - "أمانة منطقة الرياض": 10, - "شركة أرامكو": 8, - "الهيئة الملكية": 14 - } + if 'current_item' not in st.session_state: + st.session_state.current_item = None - # تحويل البيانات إلى DataFrame - margin_df = pd.DataFrame({ - "العميل": list(margin_by_client.keys()), - "هامش الربح (%)": list(margin_by_client.values()) - }) + if 'show_add_item_form' not in st.session_state: + st.session_state.show_add_item_form = False - # عرض الرسم البياني - st.bar_chart(margin_df.set_index("العميل")) - - def _render_tender_pricing(self): + def set_page_config(self): """ - عرض واجهة تسعير المناقصات + إعداد تكوين الصفحة """ - st.markdown("### تسعير المناقصات") - - # اختيار المناقصة - project_names = [p['name'] for p in st.session_state.pricing_projects] - selected_project = st.selectbox("اختر المناقصة", options=project_names, key="selected_pricing_project") + st.set_page_config( + page_title="وحدة التسعير المتكاملة", + page_icon="💰", + layout="wide" + ) - # الحصول على معلومات المناقصة المحددة - project_info = next((p for p in st.session_state.pricing_projects if p['name'] == selected_project), None) - - if project_info: - # عرض معلومات المناقصة - st.markdown(f""" -
-

{project_info['name']}

-

العميل: {project_info['client']}

-

القيمة التقديرية: {project_info['value']:,} ريال

-

الموعد النهائي: {project_info['deadline']}

-

الحالة: {project_info['status']}

-

نسبة الإنجاز: {project_info['completion']}%

-
- """, unsafe_allow_html=True) - - # تبويبات فرعية للتسعير - subtabs = st.tabs(["ملخص التسعير", "جدول الكميات", "تحليل التكاليف", "هامش الربح", "المخاطر"]) - - with subtabs[0]: - self._render_pricing_summary(project_info) - - with subtabs[1]: - self._render_project_boq(project_info) - - with subtabs[2]: - self._render_cost_analysis(project_info) - - with subtabs[3]: - self._render_profit_margin(project_info) - - with subtabs[4]: - self._render_risk_analysis(project_info) - - def _render_pricing_summary(self, project_info): + def render(self): """ - عرض ملخص التسعير + طريقة للتوافق مع الواجهة القديمة + تقوم باستدعاء طريقة run """ - st.markdown("#### ملخص التسعير") - - # عرض شريط التقدم - st.progress(project_info['completion'] / 100, text=f"نسبة إكمال التسعير: {project_info['completion']}%") - - # عرض ملخص التكاليف - st.markdown("##### ملخص التكاليف") - - # إنشاء بيانات تجريبية للتكاليف - direct_cost = project_info['value'] * 0.7 - indirect_cost = project_info['value'] * 0.15 - profit_margin = project_info['value'] * 0.15 - - costs = { - "التكاليف المباشرة": direct_cost, - "التكاليف غير المباشرة": indirect_cost, - "هامش الربح": profit_margin, - "إجمالي العرض": project_info['value'] - } - - # عرض جدول التكاليف - costs_df = pd.DataFrame({ - "البند": list(costs.keys()), - "القيمة (ريال)": [f"{value:,.2f}" for value in costs.values()], - "النسبة": [f"{(value / project_info['value']) * 100:.1f}%" for value in costs.values()] - }) - - st.dataframe(costs_df, use_container_width=True, hide_index=True) - - # عرض الرسم البياني للتكاليف - cost_chart_data = pd.DataFrame({ - "البند": ["التكاليف المباشرة", "التكاليف غير المباشرة", "هامش الربح"], - "القيمة": [direct_cost, indirect_cost, profit_margin] - }) - - st.bar_chart(cost_chart_data.set_index("البند")) - - # عرض ملاحظات التسعير - st.markdown("##### ملاحظات التسعير") - - notes = [ - "تم تحديث أسعار المواد وفقاً لآخر الأسعار في السوق", - "تم زيادة هامش المخاطر بنسبة 2% نظراً لموقع المشروع", - "تم تخفيض تكلفة النقل بناءً على توفر المعدات في الموقع", - "يجب مراجعة أسعار الحديد قبل تقديم العرض النهائي" - ] - - for note in notes: - st.markdown(f"- {note}") + self.run() - # زر تحديث التسعير - if st.button("تحديث التسعير", key="update_pricing_summary_btn"): - st.success("تم تحديث التسعير بنجاح", icon="✅") - - def _render_project_boq(self, project_info): + def run(self): """ - عرض جدول الكميات للمشروع + تشغيل وحدة التسعير المحسنة """ - st.markdown("#### جدول الكميات") - - # إنشاء بيانات تجريبية لجدول الكميات - boq_items = [] - categories = ["أعمال ترابية", "أعمال خرسانية", "أعمال معمارية", "أعمال كهربائية", "أعمال ميكانيكية"] - - for i, category in enumerate(categories): - # إنشاء عدة بنود لكل فئة - for j in range(3): - item_id = i * 3 + j + 1 - item = { - "الرقم": f"{i+1}.{j+1}", - "الفئة": category, - "البند": f"بند {item_id}", - "الوصف": f"وصف تفصيلي للبند {item_id} ضمن فئة {category}", - "الوحدة": random.choice(["متر", "متر مربع", "متر مكعب", "طن", "قطعة"]), - "الكمية": random.randint(10, 1000), - "سعر الوحدة": random.randint(100, 5000), - "الإجمالي": 0 - } - item["الإجمالي"] = item["الكمية"] * item["سعر الوحدة"] - boq_items.append(item) - - # تحويل البيانات إلى DataFrame - boq_df = pd.DataFrame(boq_items) - - # تنسيق القيم - boq_df["سعر الوحدة"] = boq_df["سعر الوحدة"].apply(lambda x: f"{x:,}") - boq_df["الإجمالي"] = boq_df["الإجمالي"].apply(lambda x: f"{x:,}") - - # عرض جدول الكميات مع تجميع حسب الفئة - st.dataframe(boq_df, use_container_width=True) - - # حساب الإجمالي - total = sum(item["الإجمالي"] for item in boq_items) - st.markdown(f"**الإجمالي:** {total:,} ريال") - - # أزرار التحكم - col1, col2, col3 = st.columns(3) + st.title("وحدة التسعير المتكاملة") + # إضافة زر إنشاء تسعير جديد في أعلى الصفحة + col1, col2, col3 = st.columns([1, 2, 1]) with col1: - st.button("إضافة بند", key="add_boq_item_project_btn") - - with col2: - st.button("استيراد من Excel", key="import_boq_project_btn") + if st.button("➕ إنشاء تسعير جديد", key="create_new_pricing_btn", type="primary"): + st.session_state.show_new_project_form = True + + # عرض نموذج إنشاء تسعير جديد + if st.session_state.show_new_project_form: + self._render_new_project_form() + + # عرض قائمة المشاريع + self._render_projects_list() - with col3: - st.button("تصدير إلى Excel", key="export_boq_project_btn") - - def _render_cost_analysis(self, project_info): + # عرض تفاصيل المشروع المحدد + if st.session_state.current_project: + project_info = next((p for p in st.session_state.projects if p["id"] == st.session_state.current_project), None) + if project_info: + self._render_project_details(project_info) + + # عرض تحليل الربحية + self._render_profit_margin(project_info) + + # عرض جدول الكميات + self._render_bill_of_quantities() + + # عرض تحليل سعر البند + if st.session_state.show_item_analysis and st.session_state.current_item: + self._render_item_price_analysis() + + def _render_new_project_form(self): """ - عرض تحليل التكاليف + عرض نموذج إنشاء مشروع جديد """ - st.markdown("#### تحليل التكاليف") + st.subheader("إنشاء تسعير جديد") - # تبويبات فرعية لتحليل التكاليف - cost_tabs = st.tabs(["تكاليف المواد", "تكاليف العمالة", "تكاليف المعدات", "التكاليف غير المباشرة"]) - - with cost_tabs[0]: - # تحليل تكاليف المواد - st.markdown("##### تكاليف المواد") - - # إنشاء بيانات تجريبية لتكاليف المواد - materials = [ - {"المادة": "خرسانة", "الكمية": 500, "الوحدة": "متر مكعب", "سعر الوحدة": 250, "الإجمالي": 125000}, - {"المادة": "حديد تسليح", "الكمية": 50, "الوحدة": "طن", "سعر الوحدة": 3000, "الإجمالي": 150000}, - {"المادة": "طابوق", "الكمية": 10000, "الوحدة": "قطعة", "سعر الوحدة": 5, "الإجمالي": 50000}, - {"المادة": "بلاط", "الكمية": 1000, "الوحدة": "متر مربع", "سعر الوحدة": 80, "الإجمالي": 80000}, - {"المادة": "أسمنت", "الكمية": 1000, "الوحدة": "كيس", "سعر الوحدة": 20, "الإجمالي": 20000} - ] - - # تحويل البيانات إلى DataFrame - materials_df = pd.DataFrame(materials) + with st.form(key="new_project_form"): + project_name = st.text_input("اسم المشروع", key="new_project_name") + client = st.text_input("العميل", key="new_project_client") + estimated_value = st.number_input("القيمة التقديرية", min_value=0, key="new_project_value") + deadline = st.date_input("الموعد النهائي", key="new_project_deadline") - # تنسيق القيم - materials_df["سعر الوحدة"] = materials_df["سعر الوحدة"].apply(lambda x: f"{x:,}") - materials_df["الإجمالي"] = materials_df["الإجمالي"].apply(lambda x: f"{x:,}") - - # عرض جدول المواد - st.dataframe(materials_df, use_container_width=True) + col1, col2 = st.columns(2) + with col1: + submit_button = st.form_submit_button("حفظ") + with col2: + cancel_button = st.form_submit_button("إلغاء") + + if submit_button and project_name: + # إنشاء مشروع جديد + new_project_id = max([p["id"] for p in st.session_state.projects], default=0) + 1 + new_project = { + "id": new_project_id, + "name": project_name, + "client": client, + "estimated_value": estimated_value, + "deadline": deadline.strftime("%Y-%m-%d"), + "status": "قيد التخطيط", + "completion": 0, + "profit_margin": 15 + } - # حساب الإجمالي - total_materials = sum(item["الإجمالي"] for item in materials) - st.markdown(f"**إجمالي تكاليف المواد:** {total_materials:,} ريال") + # إضافة المشروع الجديد + st.session_state.projects.append(new_project) - # عرض الرسم البياني - materials_chart = pd.DataFrame({ - "المادة": [item["المادة"] for item in materials], - "التكلفة": [item["الإجمالي"] for item in materials] + # إنشاء جدول كميات فارغ للمشروع الجديد + st.session_state.bill_of_quantities.append({ + "project_id": new_project_id, + "items": [] }) - st.bar_chart(materials_chart.set_index("المادة")) - - with cost_tabs[1]: - # تحليل تكاليف العمالة - st.markdown("##### تكاليف العمالة") - - # إنشاء بيانات تجريبية لتكاليف العمالة - labor = [ - {"الوظيفة": "مهندس موقع", "العدد": 2, "المدة (شهر)": 12, "التكلفة الشهرية": 15000, "الإجمالي": 360000}, - {"الوظيفة": "مشرف", "العدد": 4, "المدة (شهر)": 12, "التكلفة الشهرية": 8000, "الإجمالي": 384000}, - {"الوظيفة": "فني", "العدد": 10, "المدة (شهر)": 12, "التكلفة الشهرية": 5000, "الإجمالي": 600000}, - {"الوظيفة": "عامل", "العدد": 20, "المدة (شهر)": 12, "التكلفة الشهرية": 3000, "الإجمالي": 720000}, - {"الوظيفة": "سائق", "العدد": 5, "المدة (شهر)": 12, "التكلفة الشهرية": 4000, "الإجمالي": 240000} - ] + # تحديد المشروع الحالي + st.session_state.current_project = new_project_id - # تحويل البيانات إلى DataFrame - labor_df = pd.DataFrame(labor) + # إخفاء النموذج + st.session_state.show_new_project_form = False - # تنسيق القيم - labor_df["التكلفة الشهرية"] = labor_df["التكلفة الشهرية"].apply(lambda x: f"{x:,}") - labor_df["الإجمالي"] = labor_df["الإجمالي"].apply(lambda x: f"{x:,}") + # إعادة تحميل الصفحة + st.rerun() - # عرض جدول العمالة - st.dataframe(labor_df, use_container_width=True) + if cancel_button: + # إخفاء النموذج + st.session_state.show_new_project_form = False + st.rerun() - # حساب الإجمالي - total_labor = sum(item["الإجمالي"] for item in labor) - st.markdown(f"**إجمالي تكاليف العمالة:** {total_labor:,} ريال") + def _render_projects_list(self): + """ + عرض قائمة المشاريع + """ + st.subheader("قائمة المشاريع") - with cost_tabs[2]: - # تحليل تكاليف المعدات - st.markdown("##### تكاليف المعدات") - - # إنشاء بيانات تجريبية لتكاليف المعدات - equipment = [ - {"المعدة": "حفارة", "العدد": 2, "المدة (شهر)": 6, "التكلفة الشهرية": 20000, "الإجمالي": 240000}, - {"المعدة": "لودر", "العدد": 2, "المدة (شهر)": 8, "التكلفة الشهرية": 15000, "الإجمالي": 240000}, - {"المعدة": "شاحنة نقل", "العدد": 4, "المدة (شهر)": 12, "التكلفة الشهرية": 10000, "الإجمالي": 480000}, - {"المعدة": "خلاطة خرسانة", "العدد": 2, "المدة (شهر)": 10, "التكلفة الشهرية": 8000, "الإجمالي": 160000}, - {"المعدة": "رافعة", "العدد": 1, "المدة (شهر)": 4, "التكلفة الشهرية": 25000, "الإجمالي": 100000} - ] - - # تحويل البيانات إلى DataFrame - equipment_df = pd.DataFrame(equipment) - - # تنسيق القيم - equipment_df["التكلفة الشهرية"] = equipment_df["التكلفة الشهرية"].apply(lambda x: f"{x:,}") - equipment_df["الإجمالي"] = equipment_df["الإجمالي"].apply(lambda x: f"{x:,}") - - # عرض جدول المعدات - st.dataframe(equipment_df, use_container_width=True) - - # حساب الإجمالي - total_equipment = sum(item["الإجمالي"] for item in equipment) - st.markdown(f"**إجمالي تكاليف المعدات:** {total_equipment:,} ريال") + # إنشاء جدول المشاريع + projects_df = pd.DataFrame(st.session_state.projects) - with cost_tabs[3]: - # تحليل التكاليف غير المباشرة - st.markdown("##### التكاليف غير المباشرة") - - # إنشاء بيانات تجريبية للتكاليف غير المباشرة - indirect_costs = [ - {"البند": "إدارة المشروع", "النسبة": "5%", "القيمة": 250000}, - {"البند": "ضمانات بنكية", "النسبة": "2%", "القيمة": 100000}, - {"البند": "تأمين", "النسبة": "1.5%", "القيمة": 75000}, - {"البند": "مكاتب الموقع", "النسبة": "1%", "القيمة": 50000}, - {"البند": "مصاريف إدارية", "النسبة": "3%", "القيمة": 150000}, - {"البند": "مصاريف نثرية", "النسبة": "1.5%", "القيمة": 75000} - ] - - # تحويل البيانات إلى DataFrame - indirect_costs_df = pd.DataFrame(indirect_costs) + # تنسيق الجدول + if not projects_df.empty: + projects_df = projects_df[["id", "name", "client", "estimated_value", "deadline", "status", "completion"]] + projects_df.columns = ["الرقم", "اسم المشروع", "العميل", "القيمة التقديرية", "الموعد النهائي", "الحالة", "نسبة الإنجاز %"] - # تنسيق القيم - indirect_costs_df["القيمة"] = indirect_costs_df["القيمة"].apply(lambda x: f"{x:,}") + # عرض الجدول + st.dataframe(projects_df, use_container_width=True) + + # اختيار مشروع + selected_project = st.selectbox( + "اختر مشروع للتسعير", + options=st.session_state.projects, + format_func=lambda x: x["name"], + key="project_selector" + ) - # عرض جدول التكاليف غير المباشرة - st.dataframe(indirect_costs_df, use_container_width=True) + if selected_project: + st.session_state.current_project = selected_project["id"] + else: + st.info("لا توجد مشاريع. قم بإنشاء مشروع جديد.") - # حساب الإجمالي - total_indirect = sum(item["القيمة"] for item in indirect_costs) - st.markdown(f"**إجمالي التكاليف غير المباشرة:** {total_indirect:,} ريال") - - def _render_profit_margin(self, project_info): + def _render_project_details(self, project_info): """ - عرض تحليل هامش الربح + عرض تفاصيل المشروع """ - st.markdown("#### تحليل هامش الربح") - - # إنشاء بيانات تجريبية لتحليل هامش الربح - direct_cost = project_info['value'] * 0.7 - indirect_cost = project_info['value'] * 0.15 - total_cost = direct_cost + indirect_cost - profit = project_info['value'] - total_cost - profit_percentage = (profit / project_info['value']) * 100 + st.subheader(f"تفاصيل المشروع: {project_info['name']}") - # عرض ملخص هامش الربح col1, col2, col3 = st.columns(3) - with col1: - st.metric("إجمالي التكاليف", f"{total_cost:,.2f} ريال") - + st.metric("العميل", project_info["client"]) + st.metric("الحالة", project_info["status"]) with col2: - st.metric("هامش الربح", f"{profit:,.2f} ريال") - + st.metric("القيمة التقديرية", f"{project_info['estimated_value']:,} ريال") + st.metric("نسبة الإنجاز", f"{project_info['completion']}%") with col3: - st.metric("نسبة الربح", f"{profit_percentage:.2f}%") - - # عرض تحليل الحساسية - st.markdown("##### تحليل الحساسية") - st.markdown("تأثير تغير التكاليف على هامش الربح") - - # إنشاء بيانات تحليل الحساسية - sensitivity_data = [] - cost_changes = [-10, -5, 0, 5, 10, 15, 20] - - for change in cost_changes: - adjusted_cost = total_cost * (1 + change / 100) - adjusted_profit = project_info['value'] - adjusted_cost - adjusted_profit_percentage = (adjusted_profit / project_info['value']) * 100 - - sensitivity_data.append({ - "تغير التكاليف": f"{change}%", - "التكلفة المعدلة": adjusted_cost, - "الربح المعدل": adjusted_profit, - "نسبة الربح المعدلة": adjusted_profit_percentage - }) - - # تحويل البيانات إلى DataFrame - sensitivity_df = pd.DataFrame(sensitivity_data) - - # تنسيق القيم - sensitivity_df["التكلفة المعدلة"] = sensitivity_df["التكلفة المعدلة"].apply(lambda x: f"{x:,.2f}") - sensitivity_df["الربح المعدل"] = sensitivity_df["الربح المعدل"].apply(lambda x: f"{x:,.2f}") - sensitivity_df["نسبة الربح المعدلة"] = sensitivity_df["نسبة الربح المعدلة"].apply(lambda x: f"{x:.2f}%") - - # عرض جدول تحليل الحساسية - st.dataframe(sensitivity_df, use_container_width=True) - - # عرض الرسم البياني لتحليل الحساسية - # تصحيح المشكلة: استخدام القيم العشرية مباشرة بدلاً من محاولة إزالة علامة % من نص - chart_data = pd.DataFrame({ - "تغير التكاليف": cost_changes, - "نسبة الربح": [row["نسبة الربح المعدلة"] for row in sensitivity_data] - }) - - # تحويل نسبة الربح من نص إلى رقم عشري - chart_data["نسبة الربح"] = chart_data["نسبة الربح"].apply(lambda x: float(x.replace("%", "")) if isinstance(x, str) else x) - - st.line_chart(chart_data.set_index("تغير التكاليف")) - - # عرض توصيات هامش الربح - st.markdown("##### توصيات هامش الربح") - - recommendations = [ - "الحفاظ على هامش ربح لا يقل عن 10% لضمان تغطية المخاطر غير المتوقعة", - "مراجعة أسعار المواد الرئيسية قبل تقديم العرض النهائي", - "التفاوض مع الموردين للحصول على خصومات إضافية", - "تقليل التكاليف غير المباشرة من خلال مشاركة الموارد مع مشاريع أخرى" - ] - - for recommendation in recommendations: - st.markdown(f"- {recommendation}") - - def _render_risk_analysis(self, project_info): + st.metric("الموعد النهائي", project_info["deadline"]) + st.metric("هامش الربح", f"{project_info['profit_margin']}%") + + def _render_profit_margin(self, project_info): """ - عرض تحليل المخاطر + عرض تحليل الربحية """ - st.markdown("#### تحليل المخاطر") - - # إنشاء بيانات تجريبية للمخاطر - risks = [ - {"المخاطرة": "ارتفاع أسعار المواد", "الاحتمالية": 70, "التأثير": 80, "المستوى": "مرتفع", "الاستجابة": "تضمين بند تعديل الأسعار في العقد"}, - {"المخاطرة": "تأخر التوريدات", "الاحتمالية": 60, "التأثير": 70, "المستوى": "مرتفع", "الاستجابة": "طلب توريدات مبكرة وتخزين المواد الأساسية"}, - {"المخاطرة": "نقص العمالة", "الاحتمالية": 50, "التأثير": 60, "المستوى": "متوسط", "الاستجابة": "التعاقد المسبق مع مقاولي الباطن"}, - {"المخاطرة": "ظروف جوية", "الاحتمالية": 40, "التأثير": 50, "المستوى": "متوسط", "الاستجابة": "تضمين مدة إضافية في الجدول الزمني"}, - {"المخاطرة": "تغيير المواصفات", "الاحتمالية": 30, "التأثير": 80, "المستوى": "متوسط", "الاستجابة": "تضمين بند تغيير الأوامر في العقد"}, - {"المخاطرة": "مشاكل تمويلية", "الاحتمالية": 20, "التأثير": 90, "المستوى": "متوسط", "الاستجابة": "تأمين خط ائتمان احتياطي"} - ] - - # تحويل البيانات إلى DataFrame - risks_df = pd.DataFrame(risks) - - # عرض جدول المخاطر - st.dataframe(risks_df, use_container_width=True) - - # عرض مصفوفة المخاطر - st.markdown("##### مصفوفة المخاطر") - - # إنشاء بيانات مصفوفة المخاطر - risk_matrix = np.zeros((5, 5)) - - # تعيين قيم المخاطر في المصفوفة - for risk in risks: - prob_index = min(int(risk["الاحتمالية"] / 20), 4) - impact_index = min(int(risk["التأثير"] / 20), 4) - risk_matrix[prob_index, impact_index] += 1 - - # تحويل المصفوفة إلى DataFrame - prob_labels = ["0-20%", "21-40%", "41-60%", "61-80%", "81-100%"] - impact_labels = ["منخفض جداً", "منخفض", "متوسط", "مرتفع", "مرتفع جداً"] + st.subheader("تحليل الربحية") - matrix_df = pd.DataFrame(risk_matrix, index=prob_labels[::-1], columns=impact_labels) - - # عرض المصفوفة كخريطة حرارية - st.markdown("الاحتمالية (عمودي) × التأثير (أفقي)") - st.dataframe(matrix_df, use_container_width=True) - - # عرض تكلفة المخاطر - st.markdown("##### تكلفة المخاطر") - - # حساب تكلفة المخاطر - risk_cost = project_info['value'] * 0.05 - contingency = project_info['value'] * 0.03 - management_reserve = project_info['value'] * 0.02 - - col1, col2, col3 = st.columns(3) + col1, col2 = st.columns([2, 3]) with col1: - st.metric("تكلفة المخاطر المحددة", f"{risk_cost:,.2f} ريال") - - with col2: - st.metric("احتياطي الطوارئ", f"{contingency:,.2f} ريال") - - with col3: - st.metric("احتياطي الإدارة", f"{management_reserve:,.2f} ريال") - - # عرض خطة الاستجابة للمخاطر - st.markdown("##### خطة الاستجابة للمخاطر") - - for risk in risks: - if risk["المستوى"] == "مرتفع": - st.markdown(f""" -
- {risk['المخاطرة']} (مخاطرة مرتفعة): {risk['الاستجابة']} -
- """, unsafe_allow_html=True) - elif risk["المستوى"] == "متوسط": - st.markdown(f""" -
- {risk['المخاطرة']} (مخاطرة متوسطة): {risk['الاستجابة']} -
- """, unsafe_allow_html=True) - - def _render_bill_of_quantities(self): - """ - عرض واجهة جداول الكميات - """ - st.markdown("### جداول الكميات") - - # تبويبات فرعية لجداول الكميات - boq_tabs = st.tabs(["إنشاء جدول كميات", "قوالب جداول الكميات", "استيراد/تصدير"]) - - with boq_tabs[0]: - # إنشاء جدول كميات جديد - st.markdown("#### إنشاء جدول كميات جديد") + # تحليل حساسية هامش الربح + profit_margin = project_info["profit_margin"] + + # إنشاء بيانات تحليل الحساسية + sensitivity_data = [] + for adjustment in [-5, -2, 0, 2, 5, 10]: + adjusted_margin = profit_margin + adjustment + if adjusted_margin < 0: + continue + + project_value = project_info["estimated_value"] + cost = project_value / (1 + adjusted_margin/100) + profit = project_value - cost + + sensitivity_data.append({ + "نسبة الربح المعدلة": f"{adjusted_margin}%", + "التكلفة": cost, + "الربح": profit, + "قيمة المشروع": project_value + }) + + # إنشاء DataFrame + sensitivity_df = pd.DataFrame(sensitivity_data) - # نموذج إنشاء جدول كميات - col1, col2 = st.columns(2) + # عرض الجدول + st.dataframe(sensitivity_df, use_container_width=True) - with col1: - boq_name = st.text_input("اسم جدول الكميات", key="new_boq_name") - boq_project = st.selectbox("المشروع", options=[p['name'] for p in st.session_state.pricing_projects], key="new_boq_project") + with col2: + # رسم بياني لتحليل الربحية + fig, ax = plt.subplots(figsize=(10, 6)) - with col2: - boq_template = st.selectbox("القالب", options=["قالب جديد"] + [t['name'] for t in st.session_state.pricing_templates], key="new_boq_template") - boq_currency = st.selectbox("العملة", options=["ريال سعودي", "دولار أمر��كي", "يورو"], key="new_boq_currency") + # إعداد البيانات للرسم البياني + margins = [float(str(row["نسبة الربح المعدلة"]).replace("%", "")) for row in sensitivity_data] + profits = [row["الربح"] for row in sensitivity_data] + costs = [row["التكلفة"] for row in sensitivity_data] - # زر إنشاء جدول الكميات - if st.button("إنشاء جدول الكميات", key="create_boq_btn"): - if boq_name: - st.success(f"تم إنشاء جدول الكميات '{boq_name}' بنجاح", icon="✅") - - # عرض جدول الكميات الفارغ - st.markdown("#### جدول الكميات الجديد") - - # إنشاء جدول فارغ - empty_boq = pd.DataFrame({ - "الرقم": ["1", "2", "3", "4", "5"], - "البند": ["", "", "", "", ""], - "الوصف": ["", "", "", "", ""], - "الوحدة": ["", "", "", "", ""], - "الكمية": ["", "", "", "", ""], - "سعر الوحدة": ["", "", "", "", ""], - "الإجمالي": ["", "", "", "", ""] - }) - - st.dataframe(empty_boq, use_container_width=True, hide_index=True) - - # أزرار التحكم - col1, col2, col3 = st.columns(3) - - with col1: - st.button("إضافة بند", key="add_item_to_new_boq_btn") - - with col2: - st.button("إضافة فئة", key="add_category_to_new_boq_btn") - - with col3: - st.button("حفظ", key="save_new_boq_btn") - else: - st.warning("يرجى إدخال اسم لجدول الكميات", icon="⚠️") - - with boq_tabs[1]: - # قوالب جداول الكميات - st.markdown("#### قوالب جداول الكميات") - - # عرض القوالب المتاحة - templates = st.session_state.pricing_templates - - # تقسيم القوالب إلى صفوف - for i in range(0, len(templates), 3): - cols = st.columns(3) - for j in range(3): - if i + j < len(templates): - template = templates[i + j] - with cols[j]: - st.markdown(f""" -
-

{template['name']}

-

النوع: {template['type']}

-

عدد البنود: {template['items_count']}

-

{template['description']}

-
- """, unsafe_allow_html=True) - - # إضافة قالب جديد - st.markdown("#### إضافة قالب جديد") + # رسم الأعمدة + x = np.arange(len(margins)) + width = 0.35 - col1, col2 = st.columns(2) + ax.bar(x - width/2, costs, width, label='التكلفة', color='#3498db') + ax.bar(x + width/2, profits, width, label='الربح', color='#2ecc71') - with col1: - new_template_name = st.text_input("اسم القالب", key="new_template_name_boq") - new_template_type = st.selectbox("نوع القالب", options=["طرق", "مباني", "بنية تحتية", "مياه وصرف صحي", "كهرباء", "أخرى"], key="new_template_type_boq") + # إضافة التسميات + ax.set_xlabel('نسبة الربح %') + ax.set_ylabel('القيمة (ريال)') + ax.set_title('تحليل الربحية حسب نسبة الربح') + ax.set_xticks(x) + ax.set_xticklabels([f"{m}%" for m in margins]) + ax.legend() - with col2: - new_template_desc = st.text_area("وصف القالب", key="new_template_desc_boq") - - # زر إضافة القالب - if st.button("إضافة القالب", key="add_template_boq_btn"): - if new_template_name: - st.success(f"تم إضافة القالب '{new_template_name}' بنجاح", icon="✅") - else: - st.warning("يرجى إدخال اسم للق��لب", icon="⚠️") - - with boq_tabs[2]: - # استيراد/تصدير جداول الكميات - st.markdown("#### استيراد/تصدير جداول الكميات") + # عرض الرسم البياني + st.pyplot(fig) - col1, col2 = st.columns(2) + def _render_bill_of_quantities(self): + """ + عرض جدول الكميات + """ + st.subheader("جدول الكميات") + + # الحصول على جدول الكميات للمشروع الحالي + boq = next((b for b in st.session_state.bill_of_quantities if b["project_id"] == st.session_state.current_project), None) + + if boq and boq["items"]: + # إنشاء DataFrame + boq_df = pd.DataFrame(boq["items"]) + boq_df.columns = ["الرقم", "البند", "الوحدة", "الكمية", "سعر الوحدة", "الإجمالي"] + # إضافة زر لإضافة بند جديد + col1, col2, col3 = st.columns([1, 2, 1]) with col1: - st.markdown("##### استيراد جدول كميات") - st.file_uploader("اختر ملف جدول الكميات (Excel, CSV)", type=["xlsx", "csv"], key="import_boq_file") + if st.button("➕ إضافة بند جديد", key="add_new_item_btn"): + st.session_state.show_add_item_form = True + + # عرض نموذج إضافة بند جديد + if st.session_state.show_add_item_form: + self._render_add_item_form() - import_format = st.radio( - "تنسيق الاستيراد", - options=["تنسيق النظام", "تنسيق وزارة المالية", "تنسيق مخصص"], - horizontal=True, - key="import_boq_format" - ) + # عرض الجدول + st.dataframe(boq_df, use_container_width=True) + + # حساب الإجمالي + total = sum(item["total_price"] for item in boq["items"]) + st.metric("إجمالي جدول الكميات", f"{total:,} ريال") + + # تصدير جدول الكميات + if st.button("تصدير", key="export_boq_btn_1"): + # إنشاء ملف Excel + output = io.BytesIO() + with pd.ExcelWriter(output, engine='openpyxl') as writer: + boq_df.to_excel(writer, index=False, sheet_name='جدول الكميات') + + # تنزيل الملف + b64 = base64.b64encode(output.getvalue()).decode() + href = f'تنزيل ملف Excel' + st.markdown(href, unsafe_allow_html=True) - st.button("استيراد", key="import_boq_file_btn") + # اختيار بند للتحليل + st.subheader("تحليل سعر البند") + selected_item = st.selectbox( + "اختر بند للتحليل", + options=boq["items"], + format_func=lambda x: x["description"], + key="item_selector" + ) - with col2: - st.markdown("##### تصدير جدول كميات") + if selected_item: + st.session_state.current_item = selected_item["id"] + st.session_state.show_item_analysis = True - export_boq = st.selectbox( - "اختر جدول الكميات", - options=[p['name'] for p in st.session_state.pricing_projects], - key="export_boq_select" - ) + # عرض زر لتحليل سعر البند + if st.button("تحليل سعر البند", key="analyze_item_btn"): + self._render_item_price_analysis() + else: + st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود جديدة.") + + # إضافة زر لإضافة بند جديد + if st.button("➕ إضافة بند جديد", key="add_first_item_btn"): + st.session_state.show_add_item_form = True - export_format = st.radio( - "تنسيق التصدير", - options=["Excel", "CSV", "PDF"], - horizontal=True, - key="export_boq_format" - ) + # عرض نموذج إضافة بند جديد + if st.session_state.show_add_item_form: + self._render_add_item_form() - if st.button("تصدير", key="export_boq_tab_btn"): - st.success(f"تم تصدير جدول الكميات '{export_boq}' بنجاح", icon="✅") - - # زر تنزيل الملف المصدر (وهمي) - st.download_button( - label="تنزيل جدول الكميات", - data="محتوى وهمي لجدول الكميات".encode('utf-8'), - file_name=f"{export_boq}.{export_format.lower()}", - mime="application/octet-stream", - key="download_boq_tab" - ) - - def _render_price_analysis(self): + def _render_add_item_form(self): """ - عرض واجهة تحليل الأسعار + عرض نموذج إضافة بند جديد """ - st.markdown("### تحليل الأسعار") + st.subheader("إضافة بند جديد") - # تبويبات فرعية لتحليل الأسعار - price_tabs = st.tabs(["تحليل أسعار المواد", "مقارنة الأسعار", "تحليل الاتجاهات", "تقارير"]) - - with price_tabs[0]: - # تحليل أسعار المواد - st.markdown("#### تحليل أسعار المواد") - - # اختيار المواد للتحليل - materials = ["خرسانة", "حديد تسليح", "أسمنت", "رمل", "بلاط", "طابوق", "أسلاك كهربائية", "أنابيب"] - selected_materials = st.multiselect("اختر المواد للتحليل", options=materials, default=materials[:3], key="selected_materials") - - if selected_materials: - # إنشاء بيانات تجريبية لأسعار المواد - price_data = {} - months = ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"] + with st.form(key="add_item_form"): + description = st.text_input("وصف البند", key="new_item_description") + + col1, col2, col3 = st.columns(3) + with col1: + unit = st.selectbox( + "الوحدة", + options=["م³", "م²", "م.ط", "طن", "كجم", "عدد", "مقطوعية"], + key="new_item_unit" + ) + with col2: + quantity = st.number_input("الكمية", min_value=0.0, key="new_item_quantity") + with col3: + unit_price = st.number_input("سعر الوحدة", min_value=0.0, key="new_item_unit_price") - for material in selected_materials: - # إنشاء سلسلة أسعار عشوائية مع اتجاه تصاعدي - base_price = random.randint(100, 1000) - prices = [] - for i in range(12): - # إضافة تغير عشوائي مع اتجاه تصاعدي - change = random.uniform(-0.05, 0.1) * base_price - price = base_price + change - base_price = price # تحديث السعر الأساسي للشهر التالي - prices.append(price) - - price_data[material] = prices + total_price = quantity * unit_price + st.metric("الإجمالي", f"{total_price:,} ريال") + + col1, col2 = st.columns(2) + with col1: + submit_button = st.form_submit_button("إضافة") + with col2: + cancel_button = st.form_submit_button("إلغاء") - # تحويل البيانات إلى DataFrame - price_df = pd.DataFrame(price_data, index=months) + if submit_button and description and quantity > 0 and unit_price > 0: + # الحصول على جدول الكميات للمشروع الحالي + boq = next((b for b in st.session_state.bill_of_quantities if b["project_id"] == st.session_state.current_project), None) + + if boq: + # إنشاء معرف جديد للبند + new_item_id = max([item["id"] for item in boq["items"]], default=0) + 1 - # عرض الرسم البياني - st.line_chart(price_df) + # إضافة البند الجديد + new_item = { + "id": new_item_id, + "description": description, + "unit": unit, + "quantity": quantity, + "unit_price": unit_price, + "total_price": total_price + } - # عرض جدول الأسعار - st.dataframe(price_df, use_container_width=True) + boq["items"].append(new_item) - # تحليل التغيرات - st.markdown("##### تحليل التغيرات في الأسعار") + # إخفاء النموذج + st.session_state.show_add_item_form = False - for material in selected_materials: - prices = price_data[material] - first_price = prices[0] - last_price = prices[-1] - change = ((last_price - first_price) / first_price) * 100 - - if change > 0: - st.markdown(f"- **{material}**: ارتفاع بنسبة {change:.2f}% خلال الفترة") - else: - st.markdown(f"- **{material}**: انخفاض بنسبة {abs(change):.2f}% خلال الفترة") - - with price_tabs[1]: - # مقارنة الأسعار - st.markdown("#### مقارنة الأسعار بين الموردين") - - # اختيار المادة للمقارنة - material_for_comparison = st.selectbox("اختر المادة للمقارنة", options=materials, key="material_for_comparison") - - # إنشاء بيانات تجريبية للموردين - suppliers = ["المورد أ", "المورد ب", "المورد ج", "المورد د", "المورد هـ"] - - # إنشاء أسعار عشوائية للموردين - base_price = random.randint(100, 1000) - supplier_prices = [base_price * random.uniform(0.9, 1.1) for _ in range(len(suppliers))] - - # تحويل البيانات إلى DataFrame - comparison_df = pd.DataFrame({ - "المورد": suppliers, - "السعر": supplier_prices, - "الخصم": [f"{random.randint(0, 15)}%" for _ in range(len(suppliers))], - "شروط الدفع": [random.choice(["فوري", "30 يوم", "60 يوم", "90 يوم"]) for _ in range(len(suppliers))], - "وقت التسليم": [f"{random.randint(1, 30)} يوم" for _ in range(len(suppliers))] - }) - - # تنسيق القيم - comparison_df["السعر"] = comparison_df["السعر"].apply(lambda x: f"{x:.2f}") + # إعادة تحميل الصفحة + st.rerun() + + if cancel_button: + # إخفاء النموذج + st.session_state.show_add_item_form = False + st.rerun() + + def _render_item_price_analysis(self): + """ + عرض تحليل سعر البند + """ + if not st.session_state.current_item: + return + + st.subheader("تحليل سعر البند") + + # الحصول على البند الحالي + boq = next((b for b in st.session_state.bill_of_quantities if b["project_id"] == st.session_state.current_project), None) + if not boq: + return + + item = next((i for i in boq["items"] if i["id"] == st.session_state.current_item), None) + if not item: + return + + # عرض معلومات البند + st.write(f"**البند:** {item['description']}") + st.write(f"**الوحدة:** {item['unit']}") + st.write(f"**سعر الوحدة:** {item['unit_price']} ريال") + + # البحث عن تحليل سعر البند + item_analysis = next((a for a in st.session_state.item_analysis + if a["project_id"] == st.session_state.current_project and a["item_id"] == st.session_state.current_item), None) + + # إذا لم يكن هناك تحليل، إنشاء تحليل جديد + if not item_analysis: + # إنشاء تحليل افتراضي + item_analysis = { + "project_id": st.session_state.current_project, + "item_id": st.session_state.current_item, + "materials": [], + "equipment": [], + "labor": [], + "overhead": 10, + "profit": 15, + "total_cost": item["unit_price"] + } - # عرض جدول المقارنة - st.dataframe(comparison_df, use_container_width=True) + # إضافة التحليل الجديد + st.session_state.item_analysis.append(item_analysis) - # عرض الرسم البياني للمقارنة - chart_data = pd.DataFrame({ - "المورد": suppliers, - "السعر": supplier_prices - }) + # عرض تحليل سعر البند + tab1, tab2, tab3, tab4 = st.tabs(["المواد", "المعدات", "العمال��", "ملخص التكلفة"]) + + with tab1: + self._render_materials_analysis(item_analysis) - st.bar_chart(chart_data.set_index("المورد")) + with tab2: + self._render_equipment_analysis(item_analysis) - # توصيات الشراء - st.markdown("##### توصيات الشراء") + with tab3: + self._render_labor_analysis(item_analysis) - best_supplier_index = supplier_prices.index(min(supplier_prices)) - best_supplier = suppliers[best_supplier_index] + with tab4: + self._render_cost_summary(item_analysis) - st.markdown(f""" -
-
التوصية الأفضل
-

بناءً على تحليل الأسعار وشروط التوريد، يوصى بالشراء من {best_supplier} حيث يقدم أفضل سعر مع شروط دفع وتسليم مناسبة.

-
- """, unsafe_allow_html=True) + def _render_materials_analysis(self, item_analysis): + """ + عرض تحليل المواد + """ + st.subheader("تحليل المواد") - with price_tabs[2]: - # تحليل الاتجاهات - st.markdown("#### تحليل اتجاهات الأسعار") - - # اختيار الفترة الزمنية - time_period = st.radio( - "الفترة الزمنية", - options=["شهري", "ربع سنوي", "سنوي"], - horizontal=True, - key="price_trend_period" - ) + # عرض المواد الحالية + if item_analysis["materials"]: + materials_data = [] + for material_item in item_analysis["materials"]: + material = next((m for m in st.session_state.materials_catalog if m["id"] == material_item["material_id"]), None) + if material: + materials_data.append({ + "المادة": material["name"], + "الوحدة": material["unit"], + "الكمية": material_item["quantity"], + "سعر الوحدة": material["price"], + "التكلفة": material_item["cost"] + }) + + # إنشاء DataFrame + materials_df = pd.DataFrame(materials_data) - # إنشاء بيانات تجريبية للاتجاهات - if time_period == "شهري": - periods = months - elif time_period == "ربع سنوي": - periods = ["الربع الأول", "الربع الثاني", "الربع الثالث", "الربع الرابع"] - else: - periods = [f"20{year}" for year in range(18, 26)] - - # إنشاء مؤشر أسعار تجريبي - base_index = 100 - price_indices = [] - - for i in range(len(periods)): - # إضافة تغير عشوائي مع اتجاه تصاعدي - change = random.uniform(-2, 5) - index_value = base_index + change - base_index = index_value - price_indices.append(index_value) - - # تحويل البيانات إلى DataFrame - trend_df = pd.DataFrame({ - "الفترة": periods, - "مؤشر الأسعار": price_indices - }) + # عرض الجدول + st.dataframe(materials_df, use_container_width=True) - # عرض الرسم البياني للاتجاهات - st.line_chart(trend_df.set_index("الفترة")) - - # تحليل الاتجاه - first_index = price_indices[0] - last_index = price_indices[-1] - total_change = ((last_index - first_index) / first_index) * 100 - - st.markdown(f"##### تحليل الاتجاه العام") - - if total_change > 10: - trend_message = "ارتفاع حاد في الأسعار" - recommendation = "يوصى بالشراء المبكر وتخزين المواد الأساسية لتجنب الزيادات المستقبلية" - elif total_change > 5: - trend_message = "ارتفاع معتدل في الأسعار" - recommendation = "يوصى بمراقبة الأسعار عن كثب والشراء عند انخفاض الأسعار مؤقتاً" - elif total_change > 0: - trend_message = "ارتفاع طفيف في الأسعار" - recommendation = "الوضع مستقر نسبياً، يمكن الشراء وفق الجدول الزمني للمشروع" - elif total_change > -5: - trend_message = "انخفاض طفيف في الأسعار" - recommendation = "يمكن تأجيل بعض المشتريات للاستفادة من انخفاض الأسعار المتوقع" - else: - trend_message = "انخفاض ملحوظ في الأسعار" - recommendation = "يوصى بتأجيل المشتريات غير العاجلة للاستفادة من انخفاض الأسعار" - - st.markdown(f""" -
-
الاتجاه العام: {trend_message}
-

التغير الإجمالي: {total_change:.2f}% خلال الفترة

-

التوصية: {recommendation}

-
- """, unsafe_allow_html=True) - - with price_tabs[3]: - # تقارير تحليل الأسعار - st.markdown("#### تقارير تحليل الأسعار") - - # قائمة التقارير المتاحة - reports = [ - "تقرير مقارنة الأسعار الشهري", - "تقرير تحليل اتجاهات الأسعار", - "تقرير أسعار المواد الرئيسية", - "تقرير مقارنة أسعار الموردين", - "تقرير تحليل تكاليف المشاريع" - ] + # حساب إجمالي تكلفة المواد + total_materials_cost = sum(material_item["cost"] for material_item in item_analysis["materials"]) + st.metric("إجمالي تكلفة المواد", f"{total_materials_cost:,} ريال") + else: + st.info("لا توجد مواد مضافة لهذا البند.") + + # إضافة مادة جديدة + st.subheader("إضافة مادة جديدة") + + with st.form(key="add_material_form"): + # اختيار المادة + material = st.selectbox( + "المادة", + options=st.session_state.materials_catalog, + format_func=lambda x: f"{x['name']} ({x['unit']})", + key="new_material" + ) - selected_report = st.selectbox("اختر التقرير", options=reports, key="selected_price_report") + # الكمية + quantity = st.number_input("الكمية", min_value=0.0, key="new_material_quantity") - # خيارات التقرير + # حساب التكلفة + if material and quantity > 0: + cost = material["price"] * quantity + st.metric("التكلفة", f"{cost:,} ريال") + else: + cost = 0 + + # أزرار الإضافة والإلغاء col1, col2 = st.columns(2) - with col1: - report_format = st.radio( - "تنسيق التقرير", - options=["PDF", "Excel", "Word"], - horizontal=True, - key="price_report_format" - ) - + submit_button = st.form_submit_button("إضافة") with col2: - report_period = st.selectbox( - "الفترة", - options=["الشهر الحالي", "الربع الحالي", "النصف الأول 2025", "النصف الثاني 2025", "السنة كاملة"], - key="price_report_period" - ) - - # زر إنشاء التقرير - if st.button("إنشاء التقرير", key="generate_price_report_btn"): - st.success(f"تم إنشاء التقرير '{selected_report}' بنجاح", icon="✅") + cancel_button = st.form_submit_button("إلغاء") - # زر تنزيل التقرير (وهمي) - st.download_button( - label="تنزيل التقرير", - data="محتوى وهمي للتقرير".encode('utf-8'), - file_name=f"{selected_report}.{report_format.lower()}", - mime="application/octet-stream", - key="download_price_report" - ) - - def _render_pricing_templates(self): + if submit_button and material and quantity > 0: + # إضافة المادة الجديدة + new_material = { + "material_id": material["id"], + "quantity": quantity, + "cost": cost + } + + # إضافة المادة إلى التحليل + item_analysis["materials"].append(new_material) + + # تحديث إجمالي التكلفة + self._update_total_cost(item_analysis) + + # إعادة تحميل الصفحة + st.rerun() + + def _render_equipment_analysis(self, item_analysis): """ - عرض واجهة قوالب التسعير + عرض تحليل المعدات """ - st.markdown("### قوالب التسعير") + st.subheader("تحليل المعدات") - # تبويبات فرعية لقوالب التسعير - template_tabs = st.tabs(["القوالب المتاحة", "إنشاء قالب جديد", "إدارة القوالب"]) - - with template_tabs[0]: - # عرض القوالب المتاحة - st.markdown("#### القوالب المتاحة") - - # فلترة القوالب - template_type_filter = st.selectbox( - "تصفية حسب النوع", - options=["الكل", "طرق", "مباني", "بنية تحتية", "مياه وصرف صحي", "كهرباء", "أخرى"], - key="template_type_filter" + # عرض المعدات الحالية + if item_analysis["equipment"]: + equipment_data = [] + for equipment_item in item_analysis["equipment"]: + equipment = next((e for e in st.session_state.equipment_catalog if e["id"] == equipment_item["equipment_id"]), None) + if equipment: + equipment_data.append({ + "المعدة": equipment["name"], + "عدد الأيام": equipment_item["days"], + "التكلفة اليومية": equipment["daily_rate"], + "التكلفة": equipment_item["cost"] + }) + + # إنشاء DataFrame + equipment_df = pd.DataFrame(equipment_data) + + # عرض الجدول + st.dataframe(equipment_df, use_container_width=True) + + # حساب إجمالي تكلفة المعدات + total_equipment_cost = sum(equipment_item["cost"] for equipment_item in item_analysis["equipment"]) + st.metric("إجمالي تكلفة المعدات", f"{total_equipment_cost:,} ريال") + else: + st.info("لا توجد معدات مضافة لهذا البند.") + + # إضافة معدة جديدة + st.subheader("إضافة معدة جديدة") + + with st.form(key="add_equipment_form"): + # اختيار المعدة + equipment = st.selectbox( + "المعدة", + options=st.session_state.equipment_catalog, + format_func=lambda x: f"{x['name']} ({x['daily_rate']} ريال/يوم)", + key="new_equipment" ) - # تطبيق التصفية - filtered_templates = st.session_state.pricing_templates - - if template_type_filter != "الكل": - filtered_templates = [t for t in filtered_templates if t['type'] == template_type_filter] - - # عرض القوالب - if filtered_templates: - # تقسيم القوالب إلى صفوف - for i in range(0, len(filtered_templates), 3): - cols = st.columns(3) - for j in range(3): - if i + j < len(filtered_templates): - template = filtered_templates[i + j] - with cols[j]: - st.markdown(f""" -
-

{template['name']}

-

النوع: {template['type']}

-

عدد البنود: {template['items_count']}

-

{template['description']}

- -
- """, unsafe_allow_html=True) - else: - st.info("لا توجد قوالب تطابق معايير التصفية", icon="ℹ️") - - with template_tabs[1]: - # إنشاء قالب جديد - st.markdown("#### إنشاء قالب جديد") + # عدد الأيام + days = st.number_input("عدد الأيام", min_value=0.0, key="new_equipment_days") - # نموذج إنشاء قالب + # حساب التكلفة + if equipment and days > 0: + cost = equipment["daily_rate"] * days + st.metric("التكلفة", f"{cost:,} ريال") + else: + cost = 0 + + # أزرار الإضافة والإلغاء col1, col2 = st.columns(2) - with col1: - new_template_name = st.text_input("اسم القالب", key="create_template_name") - new_template_type = st.selectbox("نوع القالب", options=["طرق", "مباني", "بنية تحتية", "مياه وصرف صحي", "كهرباء", "أخرى"], key="create_template_type") - + submit_button = st.form_submit_button("إضافة") with col2: - new_template_desc = st.text_area("وصف القالب", key="create_template_desc") - new_template_base = st.selectbox("القالب الأساسي", options=["قالب فارغ"] + [t['name'] for t in st.session_state.pricing_templates], key="create_template_base") + cancel_button = st.form_submit_button("إلغاء") + + if submit_button and equipment and days > 0: + # إضافة المعدة الجديدة + new_equipment = { + "equipment_id": equipment["id"], + "days": days, + "cost": cost + } - # زر إنشاء القالب - if st.button("إنشاء القالب", key="create_template_btn"): - if new_template_name: - st.success(f"تم إنشاء القالب '{new_template_name}' بنجاح", icon="✅") - - # عرض محرر القالب - st.markdown("#### محرر القالب") - - # إنشاء جدول فارغ - empty_template = pd.DataFrame({ - "الرقم": ["1", "2", "3", "4", "5"], - "البند": ["", "", "", "", ""], - "الوصف": ["", "", "", "", ""], - "الوحدة": ["", "", "", "", ""], - "ملاحظات": ["", "", "", "", ""] + # إضافة المعدة إلى التحليل + item_analysis["equipment"].append(new_equipment) + + # تحديث إجمالي التكلفة + self._update_total_cost(item_analysis) + + # إعادة تحميل الصفحة + st.rerun() + + def _render_labor_analysis(self, item_analysis): + """ + عرض تحليل العمالة + """ + st.subheader("تحليل العمالة") + + # عرض العمالة الحالية + if item_analysis["labor"]: + labor_data = [] + for labor_item in item_analysis["labor"]: + labor = next((l for l in st.session_state.labor_rates if l["id"] == labor_item["labor_id"]), None) + if labor: + labor_data.append({ + "العامل": labor["role"], + "عدد الأيام": labor_item["days"], + "التكلفة اليومية": labor["daily_rate"], + "التكلفة": labor_item["cost"] }) - st.dataframe(empty_template, use_container_width=True, hide_index=True) - - # أزرار التحكم - col1, col2, col3 = st.columns(3) - - with col1: - st.button("إضافة بند", key="add_item_to_template_btn") - - with col2: - st.button("إضافة فئة", key="add_category_to_template_btn") - - with col3: - st.button("حفظ القالب", key="save_template_btn") - else: - st.warning("يرجى إدخال اسم للقالب", icon="⚠️") - - with template_tabs[2]: - # إدارة القوالب - st.markdown("#### إدارة القوالب") - - # عرض قائمة القوالب - templates_df = pd.DataFrame({ - "اسم القالب": [t['name'] for t in st.session_state.pricing_templates], - "النوع": [t['type'] for t in st.session_state.pricing_templates], - "عدد البنود": [t['items_count'] for t in st.session_state.pricing_templates], - "تاريخ الإنشاء": [t['created_at'] for t in st.session_state.pricing_templates], - "آخر تحديث": [t['updated_at'] for t in st.session_state.pricing_templates] - }) + # إنشاء DataFrame + labor_df = pd.DataFrame(labor_data) - st.dataframe(templates_df, use_container_width=True) + # عرض الجدول + st.dataframe(labor_df, use_container_width=True) - # خيارات إدارة القوالب - st.markdown("##### خيارات الإدارة") + # حساب إجمالي تكلفة العمالة + total_labor_cost = sum(labor_item["cost"] for labor_item in item_analysis["labor"]) + st.metric("إجمالي تكلفة العمالة", f"{total_labor_cost:,} ريال") + else: + st.info("لا توجد عمالة مضافة لهذا البند.") + + # إضافة عامل جديد + st.subheader("إضافة عامل جديد") + + with st.form(key="add_labor_form"): + # اختيار العامل + labor = st.selectbox( + "العامل", + options=st.session_state.labor_rates, + format_func=lambda x: f"{x['role']} ({x['daily_rate']} ريال/يوم)", + key="new_labor" + ) - col1, col2, col3 = st.columns(3) + # عدد الأيام + days = st.number_input("عدد الأيام", min_value=0.0, key="new_labor_days") + # حساب التكلفة + if labor and days > 0: + cost = labor["daily_rate"] * days + st.metric("التكلفة", f"{cost:,} ريال") + else: + cost = 0 + + # أزرار الإضافة والإلغاء + col1, col2 = st.columns(2) with col1: - st.button("تعديل القالب المحدد", key="edit_template_btn") - + submit_button = st.form_submit_button("إضافة") with col2: - st.button("نسخ القالب المحدد", key="duplicate_template_btn") - - with col3: - st.button("حذف القالب المحدد", key="delete_template_btn") + cancel_button = st.form_submit_button("إلغاء") + + if submit_button and labor and days > 0: + # إضافة العامل الجديد + new_labor = { + "labor_id": labor["id"], + "days": days, + "cost": cost + } - # استيراد وتصدير القوالب - st.markdown("##### استيراد وتصدير القوالب") + # إضافة العامل إلى التحليل + item_analysis["labor"].append(new_labor) - col1, col2 = st.columns(2) + # تحديث إجمالي التكلفة + self._update_total_cost(item_analysis) - with col1: - st.file_uploader("استيراد قالب", type=["xlsx", "json"], key="import_template_file") - st.button("استيراد", key="import_template_btn") + # إعادة تحميل الصفحة + st.rerun() - with col2: - export_template = st.selectbox( - "تصدير قالب", - options=[t['name'] for t in st.session_state.pricing_templates], - key="export_template_select" - ) - - export_template_format = st.radio( - "تنسيق التصدير", - options=["Excel", "JSON"], - horizontal=True, - key="export_template_format" - ) - - if st.button("تصدير", key="export_template_btn"): - st.success(f"تم تصدير القالب '{export_template}' بنجاح", icon="✅") - - # زر تنزيل القالب المصدر (وهمي) - st.download_button( - label="تنزيل القالب", - data="محتوى وهمي للقالب".encode('utf-8'), - file_name=f"{export_template}.{export_template_format.lower()}", - mime="application/octet-stream", - key="download_template" - ) - - def _generate_sample_projects(self): + def _render_cost_summary(self, item_analysis): """ - إنشاء بيانات تجريبية للمشاريع + عرض ملخص التكلفة """ - projects = [ - { - 'id': 1, - 'name': 'مشروع تطوير الطرق في منطقة الرياض', - 'client': 'وزارة النقل', - 'value': 25000000, - 'deadline': '2025-05-15', - 'status': 'قيد التسعير', - 'completion': 75 - }, - { - 'id': 2, - 'name': 'مشروع إنشاء مبنى إداري', - 'client': 'وزارة الإسكان', - 'value': 15000000, - 'deadline': '2025-04-30', - 'status': 'قيد التسعير', - 'completion': 90 - }, - { - 'id': 3, - 'name': 'مشروع تطوير شبكة الصرف الصحي', - 'client': 'أمانة منطقة الرياض', - 'value': 18000000, - 'deadline': '2025-05-10', - 'status': 'تم التسعير', - 'completion': 100 - }, - { - 'id': 4, - 'name': 'مشروع إنشاء جسر', - 'client': 'وزارة النقل', - 'value': 30000000, - 'deadline': '2025-06-20', - 'status': 'قيد التسعير', - 'completion': 40 - }, - { - 'id': 5, - 'name': 'مشروع تطوير شبكة المياه', - 'client': 'وزارة المياه', - 'value': 12000000, - 'deadline': '2025-04-25', - 'status': 'تم التسعير', - 'completion': 100 - }, - { - 'id': 6, - 'name': 'مشروع إنشاء مدرسة', - 'client': 'وزارة التعليم', - 'value': 8000000, - 'deadline': '2025-05-05', - 'status': 'تمت الترسية', - 'completion': 100 - }, - { - 'id': 7, - 'name': 'مشروع تطوير شبكة الكهرباء', - 'client': 'شركة الكهرباء السعودية', - 'value': 20000000, - 'deadline': '2025-06-10', - 'status': 'مرفوضة', - 'completion': 100 - }, - { - 'id': 8, - 'name': 'مشروع إنشاء مستشفى', - 'client': 'وزارة الصحة', - 'value': 40000000, - 'deadline': '2025-07-15', - 'status': 'قيد التسعير', - 'completion': 30 - }, - { - 'id': 9, - 'name': 'مشروع تطوير حديقة عامة', - 'client': 'أمانة منطقة الرياض', - 'value': 5000000, - 'deadline': '2025-04-20', - 'status': 'تمت الترسية', - 'completion': 100 - }, - { - 'id': 10, - 'name': 'مشروع إنشاء مركز تجاري', - 'client': 'شركة تطوير العقارية', - 'value': 35000000, - 'deadline': '2025-08-10', - 'status': 'قيد التسعير', - 'completion': 20 - } - ] + st.subheader("ملخص التكلفة") - return projects - - def _generate_sample_templates(self): - """ - إنشاء بيانات تجريبية لقوالب التسعير - """ - templates = [ - { - 'id': 1, - 'name': 'قالب مشاريع الطرق', - 'type': 'طرق', - 'items_count': 120, - 'description': 'قالب شامل لمشاريع الطرق والجسور يتضمن جميع البنود القياسية', - 'created_at': '2024-10-15', - 'updated_at': '2025-01-20' - }, - { - 'id': 2, - 'name': 'قالب المباني الإدارية', - 'type': 'مباني', - 'items_count': 150, - 'description': 'قالب متكامل للمباني الإدارية والتجارية مع تفاصيل التشطيبات', - 'created_at': '2024-11-05', - 'updated_at': '2025-02-10' - }, - { - 'id': 3, - 'name': 'قالب شبكات المياه', - 'type': 'مياه وصرف صحي', - 'items_count': 85, - 'description': 'قالب لمشاريع شبكات المياه والصرف الصحي مع المواصفات القياسية', - 'created_at': '2024-09-20', - 'updated_at': '2025-01-15' - }, - { - 'id': 4, - 'name': 'قالب الأعمال الكهربائية', - 'type': 'كهرباء', - 'items_count': 95, - 'description': 'قالب للأعمال الكهربائية في المشاريع المختلفة', - 'created_at': '2024-12-10', - 'updated_at': '2025-03-05' - }, - { - 'id': 5, - 'name': 'قالب البنية التحتية', - 'type': 'بنية تحتية', - 'items_count': 110, - 'description': 'قالب شامل لمشاريع البنية التحتية والمرافق العامة', - 'created_at': '2024-10-25', - 'updated_at': '2025-02-15' - }, - { - 'id': 6, - 'name': 'قالب المدارس', - 'type': 'مباني', - 'items_count': 130, - 'description': 'قالب متخصص لمشاريع المدارس والمنشآت التعليمية', - 'created_at': '2024-11-15', - 'updated_at': '2025-01-25' - } + # حساب التكاليف + materials_cost = sum(material["cost"] for material in item_analysis["materials"]) + equipment_cost = sum(equipment["cost"] for equipment in item_analysis["equipment"]) + labor_cost = sum(labor["cost"] for labor in item_analysis["labor"]) + direct_cost = materials_cost + equipment_cost + labor_cost + + overhead_cost = direct_cost * (item_analysis["overhead"] / 100) + subtotal = direct_cost + overhead_cost + profit_cost = subtotal * (item_analysis["profit"] / 100) + total_cost = subtotal + profit_cost + + # عرض ملخص التكلفة + cost_data = [ + {"البند": "تكلفة المواد", "القيمة": materials_cost, "النسبة": materials_cost / total_cost * 100 if total_cost > 0 else 0}, + {"البند": "تكلفة المعدات", "القيمة": equipment_cost, "النسبة": equipment_cost / total_cost * 100 if total_cost > 0 else 0}, + {"البند": "تكلفة العمالة", "القيمة": labor_cost, "النسبة": labor_cost / total_cost * 100 if total_cost > 0 else 0}, + {"البند": "التكلفة المباشرة", "القيمة": direct_cost, "النسبة": direct_cost / total_cost * 100 if total_cost > 0 else 0}, + {"البند": f"المصاريف العمومية ({item_analysis['overhead']}%)", "القيمة": overhead_cost, "النسبة": overhead_cost / total_cost * 100 if total_cost > 0 else 0}, + {"البند": "الإجمالي الفرعي", "القيمة": subtotal, "النسبة": subtotal / total_cost * 100 if total_cost > 0 else 0}, + {"البند": f"الربح ({item_analysis['profit']}%)", "القيمة": profit_cost, "النسبة": profit_cost / total_cost * 100 if total_cost > 0 else 0}, + {"البند": "إجمالي التكلفة", "القيمة": total_cost, "النسبة": 100} ] - return templates - - def _generate_sample_resources(self): + # إنشاء DataFrame + cost_df = pd.DataFrame(cost_data) + + # تنسيق الأعمدة + cost_df["القيمة"] = cost_df["القيمة"].apply(lambda x: f"{x:,.2f} ريال") + cost_df["النسبة"] = cost_df["النسبة"].apply(lambda x: f"{x:.2f}%") + + # عرض الجدول + st.dataframe(cost_df, use_container_width=True) + + # رسم بياني للتكلفة + fig, ax = plt.subplots(figsize=(10, 6)) + + # إعداد البيانات للرسم البياني + labels = ["المواد", "المعدات", "العمالة", "المصاريف العمومية", "الربح"] + sizes = [materials_cost, equipment_cost, labor_cost, overhead_cost, profit_cost] + colors = ['#3498db', '#2ecc71', '#e74c3c', '#f39c12', '#9b59b6'] + + # رسم الدائرة + wedges, texts, autotexts = ax.pie( + sizes, + labels=labels, + colors=colors, + autopct='%1.1f%%', + startangle=90, + wedgeprops={'edgecolor': 'w', 'linewidth': 1} + ) + + # تنسيق النص + for text in texts: + text.set_fontsize(12) + for autotext in autotexts: + autotext.set_fontsize(10) + autotext.set_color('white') + + # إضافة العنوان + ax.set_title('توزيع التكلفة') + + # عرض الرسم البياني + st.pyplot(fig) + + # تعديل نسب المصاريف العمومية والربح + col1, col2 = st.columns(2) + with col1: + overhead = st.slider( + "نسبة المصاريف العمومية (%)", + min_value=0, + max_value=30, + value=item_analysis["overhead"], + key="overhead_slider" + ) + with col2: + profit = st.slider( + "نسبة الربح (%)", + min_value=0, + max_value=50, + value=item_analysis["profit"], + key="profit_slider" + ) + + # تحديث النسب إذا تغيرت + if overhead != item_analysis["overhead"] or profit != item_analysis["profit"]: + item_analysis["overhead"] = overhead + item_analysis["profit"] = profit + + # تحديث إجمالي التكلفة + self._update_total_cost(item_analysis) + + # إعادة تحميل الصفحة + st.rerun() + + # تحديث سعر البند + if st.button("تحديث سعر البند", key="update_item_price_btn"): + # الحصول على البند الحالي + boq = next((b for b in st.session_state.bill_of_quantities if b["project_id"] == st.session_state.current_project), None) + if boq: + item = next((i for i in boq["items"] if i["id"] == st.session_state.current_item), None) + if item: + # تحديث سعر الوحدة + item["unit_price"] = total_cost + item["total_price"] = total_cost * item["quantity"] + + # إعادة تحميل الصفحة + st.rerun() + + def _update_total_cost(self, item_analysis): """ - إنشاء بيانات تجريبية للموارد + تحديث إجمالي التكلفة """ - resources = { - 'materials': [ - {'id': 1, 'name': 'خرسانة جاهزة', 'unit': 'متر مكعب', 'price': 250, 'last_update': '2025-03-15'}, - {'id': 2, 'name': 'حديد تسليح', 'unit': 'طن', 'price': 3000, 'last_update': '2025-03-10'}, - {'id': 3, 'name': 'طابوق', 'unit': 'قطعة', 'price': 5, 'last_update': '2025-03-05'}, - {'id': 4, 'name': 'بلاط', 'unit': 'متر مربع', 'price': 80, 'last_update': '2025-03-12'}, - {'id': 5, 'name': 'أسمنت', 'unit': 'كيس', 'price': 20, 'last_update': '2025-03-08'} - ], - 'labor': [ - {'id': 1, 'name': 'مهندس موقع', 'unit': 'شهر', 'price': 15000, 'last_update': '2025-02-20'}, - {'id': 2, 'name': 'مشرف', 'unit': 'شهر', 'price': 8000, 'last_update': '2025-02-20'}, - {'id': 3, 'name': 'فني', 'unit': 'شهر', 'price': 5000, 'last_update': '2025-02-20'}, - {'id': 4, 'name': 'عامل', 'unit': 'شهر', 'price': 3000, 'last_update': '2025-02-20'}, - {'id': 5, 'name': 'سائق', 'unit': 'شهر', 'price': 4000, 'last_update': '2025-02-20'} - ], - 'equipment': [ - {'id': 1, 'name': 'حفارة', 'unit': 'شهر', 'price': 20000, 'last_update': '2025-02-15'}, - {'id': 2, 'name': 'لودر', 'unit': 'شهر', 'price': 15000, 'last_update': '2025-02-15'}, - {'id': 3, 'name': 'شاحنة نقل', 'unit': 'شهر', 'price': 10000, 'last_update': '2025-02-15'}, - {'id': 4, 'name': 'خلاطة خرسانة', 'unit': 'شهر', 'price': 8000, 'last_update': '2025-02-15'}, - {'id': 5, 'name': 'رافعة', 'unit': 'شهر', 'price': 25000, 'last_update': '2025-02-15'} - ] - } + # حساب التكاليف + materials_cost = sum(material["cost"] for material in item_analysis["materials"]) + equipment_cost = sum(equipment["cost"] for equipment in item_analysis["equipment"]) + labor_cost = sum(labor["cost"] for labor in item_analysis["labor"]) + direct_cost = materials_cost + equipment_cost + labor_cost + + overhead_cost = direct_cost * (item_analysis["overhead"] / 100) + subtotal = direct_cost + overhead_cost + profit_cost = subtotal * (item_analysis["profit"] / 100) + total_cost = subtotal + profit_cost - return resources + # تحديث إجمالي التكلفة + item_analysis["total_cost"] = total_cost