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,1751 +1,1801 @@
import streamlit as st
import pandas as pd
+import numpy as np
import matplotlib.pyplot as plt
+import matplotlib.font_manager as fm
+import arabic_reshaper
+from bidi.algorithm import get_display
+import io
import base64
from datetime import datetime
-import os
+import random
import json
-import csv
-import io
-
-# استيراد الوحدات المخصصة
-from data_storage import DataStorage
-from export_utils import (
- export_to_excel, export_to_pdf, export_local_content_report,
- export_risk_report, get_download_link
-)
-
-# محاولة استيراد المكتبات الإضافية
-try:
- import arabic_reshaper
- from bidi.algorithm import get_display
- ARABIC_SUPPORT = True
-except ImportError:
- ARABIC_SUPPORT = False
- st.warning("لم يتم العثور على مكتبات دعم اللغة العربية. بعض الميزات قد لا تعمل بشكل صحيح.")
+import os
class PricingApp:
+ """وحدة التسعير المتكاملة"""
+
def __init__(self):
- """تهيئة التطبيق"""
- # تهيئة مدير التخزين
- self.storage = DataStorage()
+ """تهيئة وحدة التسعير"""
+ # تهيئة البيانات في حالة الجلسة إذا لم تكن موجودة
+ if 'projects' not in st.session_state:
+ st.session_state.projects = [
+ {
+ 'id': 1,
+ 'name': 'مشروع تطوير الطرق الداخلية',
+ 'client': 'وزارة النقل',
+ 'estimated_value': 5000000,
+ 'deadline': '2024-06-30',
+ 'status': 'قيد التسعير',
+ 'created_at': '2024-01-15',
+ 'pricing_type': 'قياسي'
+ },
+ {
+ 'id': 2,
+ 'name': 'مشروع إنشاء مبنى إداري',
+ 'client': 'شركة التطوير العقاري',
+ 'estimated_value': 12000000,
+ 'deadline': '2024-08-15',
+ 'status': 'قيد التسعير',
+ 'created_at': '2024-02-01',
+ 'pricing_type': 'غير متزن'
+ }
+ ]
- # تهيئة حالة الجلسة إذا لم تكن موجودة
- if 'initialized' not in st.session_state:
- self._initialize_session_state()
+ if 'current_project' not in st.session_state:
+ st.session_state.current_project = 1
+
+ if 'next_project_id' not in st.session_state:
+ st.session_state.next_project_id = len(st.session_state.projects) + 1
+
+ if 'show_new_project_form' not in st.session_state:
+ st.session_state.show_new_project_form = False
+
+ if 'show_edit_project_form' not in st.session_state:
+ st.session_state.show_edit_project_form = False
+
+ if 'edit_project_id' not in st.session_state:
+ st.session_state.edit_project_id = None
+
+ if 'boq_items' not in st.session_state:
+ st.session_state.boq_items = [
+ {
+ 'id': 1,
+ 'project_id': 1,
+ 'code': 'A-001',
+ 'description': 'أعمال الحفر والردم',
+ 'unit': 'م3',
+ 'quantity': 1500,
+ 'unit_price': 45,
+ 'total_price': 67500,
+ 'resource_type': 'مواد'
+ },
+ {
+ 'id': 2,
+ 'project_id': 1,
+ 'code': 'A-002',
+ 'description': 'توريد وتركيب طبقة أساس',
+ 'unit': 'م2',
+ 'quantity': 3000,
+ 'unit_price': 85,
+ 'total_price': 255000,
+ 'resource_type': 'مواد'
+ },
+ {
+ 'id': 3,
+ 'project_id': 1,
+ 'code': 'A-003',
+ 'description': 'توريد وتركيب خرسانة جاهزة',
+ 'unit': 'م3',
+ 'quantity': 750,
+ 'unit_price': 320,
+ 'total_price': 240000,
+ 'resource_type': 'مواد'
+ },
+ {
+ 'id': 4,
+ 'project_id': 2,
+ 'code': 'B-001',
+ 'description': 'أعمال الأساسات',
+ 'unit': 'م3',
+ 'quantity': 500,
+ 'unit_price': 450,
+ 'total_price': 225000,
+ 'resource_type': 'مواد'
+ },
+ {
+ 'id': 5,
+ 'project_id': 2,
+ 'code': 'B-002',
+ 'description': 'أعمال الهيكل الخرساني',
+ 'unit': 'م3',
+ 'quantity': 1200,
+ 'unit_price': 550,
+ 'total_price': 660000,
+ 'resource_type': 'مواد'
+ }
+ ]
+
+ if 'next_boq_item_id' not in st.session_state:
+ st.session_state.next_boq_item_id = len(st.session_state.boq_items) + 1
+
+ if 'show_new_boq_item_form' not in st.session_state:
+ st.session_state.show_new_boq_item_form = False
+
+ if 'show_edit_boq_item_form' not in st.session_state:
+ st.session_state.show_edit_boq_item_form = False
+
+ if 'edit_boq_item_id' not in st.session_state:
+ st.session_state.edit_boq_item_id = None
+
+ if 'show_resource_selector' not in st.session_state:
+ st.session_state.show_resource_selector = False
+
+ if 'selected_resource_type' not in st.session_state:
+ st.session_state.selected_resource_type = "المواد"
+
+ # تهيئة معامل تعديل التسعير الغير متزن
+ if 'unbalanced_pricing_factors' not in st.session_state:
+ st.session_state.unbalanced_pricing_factors = {
+ 'early_items_factor': 1.15, # زيادة أسعار البنود المبكرة بنسبة 15%
+ 'late_items_factor': 0.90, # تخفيض أسعار البنود المتأخرة بنسبة 10%
+ 'custom_factors': {} # معاملات مخصصة لبنود محددة
+ }
+
+ # تهيئة حالة حفظ التسعير
+ if 'saved_pricing' not in st.session_state:
+ st.session_state.saved_pricing = []
+
+ # تهيئة حالة تحليل سعر البند
+ if 'item_analysis_edited' not in st.session_state:
+ st.session_state.item_analysis_edited = False
+
+ def render(self):
+ """طريقة للتوافق مع الواجهة القديمة"""
+ self.run()
- # تعيين نمط CSS
- self._set_css()
-
- def _initialize_session_state(self):
- """تهيئة حالة الجلسة وتحميل البيانات من التخزين"""
- # تحميل البيانات من ملفات JSON
- projects = self.storage.load_projects()
- boq_items = self.storage.load_boq_items()
- pricing_history = self.storage.load_pricing_history()
- risks = self.storage.load_risks()
-
- # تعيين حالة الجلسة
- st.session_state.projects = projects
- st.session_state.boq_items = boq_items
- st.session_state.saved_pricing = pricing_history
- st.session_state.risks = risks
- st.session_state.current_project = None
- st.session_state.pricing_step = "project_info"
- st.session_state.initialized = True
-
- def _set_css(self):
- """تعيين نمط CSS للتطبيق"""
- st.markdown("""
-
- """, unsafe_allow_html=True)
-
def run(self):
- """تشغيل التطبيق"""
- st.title("نظام تسعير المشاريع")
-
- # عرض شريط التنقل الجانبي
- self._render_sidebar()
-
- # تحديد الخطوة الحالية
- current_step = st.session_state.pricing_step
-
- # تحديث قائمة الخطوات
- steps = {
- "project_info": self._render_project_info_step,
- "boq": self._render_boq_step,
- "cost_analysis": self._render_cost_analysis_step,
- "pricing_strategies": self._render_pricing_strategies_step,
- "review": self._render_review_step,
- "local_content": self._render_local_content_step,
- "risk_assessment": self._render_risk_assessment_step
- }
+ """تشغيل وحدة التسعير"""
+ st.title("وحدة التسعير المتكاملة")
- # عرض الخطوة الحالية
- if current_step in steps:
- steps[current_step]()
- else:
- st.error(f"خطوة غير معروفة: {current_step}")
-
- # إضافة طريقة render كبديل لطريقة run للتوافق مع الكود القديم
- def render(self):
- """طريقة بديلة لتشغيل التطبيق للتوافق مع الكود القديم"""
- self.run()
-
- def _render_sidebar(self):
- """عرض شريط التنقل الجانبي"""
- with st.sidebar:
- st.header("المشاريع")
-
- # عرض قائمة المشاريع
- if st.session_state.projects:
- project_names = [p['name'] for p in st.session_state.projects]
- project_ids = [p['id'] for p in st.session_state.projects]
-
- selected_index = 0
- if st.session_state.current_project:
- if st.session_state.current_project in project_ids:
- selected_index = project_ids.index(st.session_state.current_project)
-
- selected_project = st.selectbox(
- "اختر مشروعًا",
- project_names,
- index=selected_index
- )
+ # عرض زر إنشاء تسعير جديد
+ col1, col2, col3 = st.columns([1, 2, 1])
+ with col2:
+ if st.button("➕ إنشاء تسعير جديد", key="create_new_pricing_btn", type="primary"):
+ st.session_state.show_new_project_form = True
- selected_project_id = project_ids[project_names.index(selected_project)]
+ # عرض نموذج إنشاء تسعير جديد
+ if st.session_state.show_new_project_form:
+ self._render_new_project_form()
+
+ # عرض نموذج تعديل المشروع
+ if st.session_state.show_edit_project_form and st.session_state.edit_project_id is not None:
+ self._render_edit_project_form()
+
+ # عرض قائمة المشاريع
+ self._render_projects_list()
+
+ # عرض تفاصيل المشروع الحالي
+ if st.session_state.current_project:
+ project_info = self._get_current_project_info()
+ if project_info:
+ self._render_project_info(project_info)
- if st.button("فتح المشروع"):
- st.session_state.current_project = selected_project_id
- st.session_state.pricing_step = "project_info"
- st.rerun()
+ # عرض علامات التبويب
+ tab1, tab2, tab3, tab4, tab5 = st.tabs([
+ "جدول الكميات",
+ "تحليل سعر البند",
+ "تحليل التكلفة",
+ "تحليل الربحية",
+ "استراتيجيات التسعير"
+ ])
- if st.button("حذف المشروع"):
- self.storage.delete_project(selected_project_id)
+ with tab1:
+ self._render_bill_of_quantities()
- # تحديث حالة الجلسة
- st.session_state.projects = self.storage.load_projects()
- st.session_state.boq_items = self.storage.load_boq_items()
+ with tab2:
+ self._render_item_price_analysis()
- if st.session_state.current_project == selected_project_id:
- st.session_state.current_project = None
+ with tab3:
+ self._render_cost_analysis(project_info)
- st.success(f"تم حذف المشروع: {selected_project}")
- st.rerun()
-
- if st.button("إنشاء مشروع جديد"):
- st.session_state.current_project = None
- st.session_state.pricing_step = "project_info"
- st.rerun()
-
- # عرض معلومات المشروع الحالي
- if st.session_state.current_project:
- st.divider()
- st.subheader("المشروع الحالي")
-
- project_info = self._get_current_project_info()
- if project_info:
- st.write(f"**الاسم:** {project_info['name']}")
- st.write(f"**العميل:** {project_info['client']}")
- st.write(f"**القيمة التقديرية:** {project_info['estimated_value']:,.2f} ريال")
-
- # عرض خطوات التسعير
- st.divider()
- st.subheader("خطوات التسعير")
+ with tab4:
+ self._render_profit_margin(project_info)
- steps = [
- ("project_info", "معلومات المشروع"),
- ("boq", "جدول الكميات"),
- ("cost_analysis", "تحليل التكلفة"),
- ("pricing_strategies", "استراتيجيات التسعير"),
- ("review", "المراجعة النهائية"),
- ("local_content", "المحتوى المحلي"),
- ("risk_assessment", "تقييم المخاطر")
- ]
+ with tab5:
+ self._render_pricing_strategies(project_info)
- for step_id, step_name in steps:
- if st.button(step_name, key=f"nav_{step_id}"):
- st.session_state.pricing_step = step_id
- st.rerun()
-
- # عرض معلومات إضافية
- st.divider()
- st.caption("نظام تسعير المشاريع - الإصدار 2.0")
-
- def _get_current_project_info(self):
- """الحصول على معلومات المشروع الحالي"""
- if not st.session_state.current_project:
- return None
-
- for project in st.session_state.projects:
- if project['id'] == st.session_state.current_project:
- return project
-
- return None
-
- def _render_project_info_step(self):
- """عرض خطوة معلومات المشروع"""
- st.markdown('
', unsafe_allow_html=True)
- st.markdown('
الخطوة 1: معلومات المشروع
', unsafe_allow_html=True)
-
- # التحقق مما إذا كان هناك مشروع حالي
- project_info = self._get_current_project_info()
-
- # إنشاء نموذج إدخال معلومات المشروع
- with st.form(key="project_info_form"):
- # حقول النموذج
- project_name = st.text_input(
- "اسم المشروع",
- value=project_info['name'] if project_info else ""
- )
-
- client = st.text_input(
- "العميل",
- value=project_info['client'] if project_info else ""
+ # عرض أزرار التصدير والحفظ
+ self._render_export_save_buttons(project_info)
+
+ def _render_new_project_form(self):
+ """عرض نموذج إنشاء مشروع جديد"""
+ st.subheader("إنشاء تسعير جديد")
+
+ with st.form(key="new_project_form"):
+ name = st.text_input("اسم المشروع", key="new_project_name")
+ client = st.text_input("العميل", key="new_project_client")
+ estimated_value = st.number_input("القيمة التقديرية", min_value=0.0, format="%f", key="new_project_value")
+ deadline = st.date_input("الموعد النهائي", key="new_project_deadline")
+ pricing_type = st.selectbox(
+ "نوع التسعير",
+ ["قياسي", "غير متزن", "موجه ربحية", "تنافسي", "استراتيجي"],
+ key="new_project_pricing_type"
)
- location = st.text_input(
- "الموقع",
- value=project_info.get('location', "") if project_info else ""
- )
+ col1, col2 = st.columns(2)
+ with col1:
+ submit_button = st.form_submit_button("حفظ")
+ with col2:
+ cancel_button = st.form_submit_button("إلغاء")
+
+ if submit_button:
+ if name and client and estimated_value > 0:
+ new_project = {
+ 'id': st.session_state.next_project_id,
+ 'name': name,
+ 'client': client,
+ 'estimated_value': estimated_value,
+ 'deadline': deadline.strftime("%Y-%m-%d"),
+ 'status': 'قيد التسعير',
+ 'created_at': datetime.now().strftime("%Y-%m-%d"),
+ 'pricing_type': pricing_type
+ }
+
+ st.session_state.projects.append(new_project)
+ st.session_state.current_project = new_project['id']
+ st.session_state.next_project_id += 1
+ st.session_state.show_new_project_form = False
+ st.rerun()
+
+ if cancel_button:
+ st.session_state.show_new_project_form = False
+ st.rerun()
- tender_number = st.text_input(
- "رقم المناقصة",
- value=project_info.get('tender_number', "") if project_info else ""
- )
+ def _render_edit_project_form(self):
+ """عرض نموذج تعديل المشروع"""
+ project = None
+ for p in st.session_state.projects:
+ if p['id'] == st.session_state.edit_project_id:
+ project = p
+ break
+
+ if not project:
+ st.session_state.show_edit_project_form = False
+ st.rerun()
+ return
+ st.subheader(f"تعديل المشروع: {project['name']}")
+
+ with st.form(key="edit_project_form"):
+ name = st.text_input("اسم المشروع", value=project['name'], key="edit_project_name")
+ client = st.text_input("العميل", value=project['client'], key="edit_project_client")
estimated_value = st.number_input(
- "القيمة التقديرية (ريال)",
- min_value=0.0,
- value=float(project_info['estimated_value']) if project_info else 0.0,
- step=1000.0
+ "القيمة التقديرية",
+ min_value=0.0,
+ value=float(project['estimated_value']),
+ format="%f",
+ key="edit_project_value"
)
-
deadline = st.date_input(
- "الموعد النهائي",
- value=datetime.strptime(project_info['deadline'], "%Y-%m-%d").date() if project_info and 'deadline' in project_info else datetime.now().date()
+ "الموعد النهائي",
+ value=datetime.strptime(project['deadline'], "%Y-%m-%d").date(),
+ key="edit_project_deadline"
)
-
- contract_duration = st.text_input(
- "مدة العقد",
- value=project_info.get('contract_duration', "") if project_info else ""
+ status = st.selectbox(
+ "الحالة",
+ ["قيد التسعير", "تم التسعير", "تم التقديم", "فائز", "خاسر"],
+ index=["قيد التسعير", "تم التسعير", "تم التقديم", "فائز", "خاسر"].index(project['status']),
+ key="edit_project_status"
)
-
pricing_type = st.selectbox(
"نوع التسعير",
- ["قياسي", "تنافسي", "مخصص"],
- index=["قياسي", "تنافسي", "مخصص"].index(project_info['pricing_type']) if project_info and 'pricing_type' in project_info else 0
+ ["قياسي", "غير متزن", "موجه ربحية", "تنافسي", "استراتيجي"],
+ index=["قياسي", "غير متزن", "موجه ربحية", "تنافسي", "استراتيجي"].index(project.get('pricing_type', 'قياسي')),
+ key="edit_project_pricing_type"
)
- # زر الإرسال
- submit_button = st.form_submit_button("حفظ معلومات المشروع")
-
- if submit_button:
- # التحقق من صحة البيانات
- if not project_name or not client or estimated_value <= 0:
- st.error("يرجى ملء جميع الحقول المطلوبة")
- else:
- # إنشاء كائن المشروع
- project_data = {
- 'name': project_name,
- 'client': client,
- 'location': location,
- 'tender_number': tender_number,
- 'estimated_value': estimated_value,
- 'deadline': deadline.strftime("%Y-%m-%d"),
- 'contract_duration': contract_duration,
- 'pricing_type': pricing_type
- }
-
- if project_info:
- # تحديث المشروع الحالي
- self.storage.update_project(project_info['id'], project_data)
-
- # تحديث حالة الجلسة
- for i, p in enumerate(st.session_state.projects):
- if p['id'] == project_info['id']:
- st.session_state.projects[i].update(project_data)
- break
-
- st.success(f"تم تحديث معلومات المشروع: {project_name}")
- else:
- # إنشاء معرف فريد للمشروع
- project_id = f"project_{len(st.session_state.projects) + 1}"
- project_data['id'] = project_id
-
- # إضافة المشروع إلى قائمة المشاريع
- added_project = self.storage.add_project(project_data)
- st.session_state.projects.append(added_project)
- st.session_state.current_project = project_id
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ submit_button = st.form_submit_button("حفظ")
+ with col2:
+ cancel_button = st.form_submit_button("إلغاء")
+ with col3:
+ delete_button = st.form_submit_button("حذف المشروع", type="primary")
+
+ if submit_button:
+ if name and client and estimated_value > 0:
+ for i, p in enumerate(st.session_state.projects):
+ if p['id'] == st.session_state.edit_project_id:
+ st.session_state.projects[i]['name'] = name
+ st.session_state.projects[i]['client'] = client
+ st.session_state.projects[i]['estimated_value'] = estimated_value
+ st.session_state.projects[i]['deadline'] = deadline.strftime("%Y-%m-%d")
+ st.session_state.projects[i]['status'] = status
+ st.session_state.projects[i]['pricing_type'] = pricing_type
+ break
- st.success(f"تم إنشاء مشروع جديد: {project_name}")
-
- # زر الانتقال إلى الخطوة التالية
- if project_info:
- if st.button("متابعة إلى جدول الكميات ⬅️", type="primary"):
- st.session_state.pricing_step = "boq"
+ st.session_state.show_edit_project_form = False
st.rerun()
+
+ if cancel_button:
+ st.session_state.show_edit_project_form = False
+ st.rerun()
+
+ if delete_button:
+ for i, p in enumerate(st.session_state.projects):
+ if p['id'] == st.session_state.edit_project_id:
+ st.session_state.projects.pop(i)
+ break
+
+ # حذف بنود جدول الكميات المرتبطة بالمشروع
+ st.session_state.boq_items = [item for item in st.session_state.boq_items if item['project_id'] != st.session_state.edit_project_id]
+
+ st.session_state.show_edit_project_form = False
+ if st.session_state.projects:
+ st.session_state.current_project = st.session_state.projects[0]['id']
+ else:
+ st.session_state.current_project = None
+
+ st.rerun()
+
+ def _render_projects_list(self):
+ """عرض قائمة المشاريع"""
+ st.subheader("قائمة المشاريع")
- st.markdown('
', unsafe_allow_html=True)
-
- def _render_boq_step(self):
- """عرض خطوة جدول الكميات"""
- st.markdown('', unsafe_allow_html=True)
- st.markdown('
الخطوة 2: جدول الكميات
', unsafe_allow_html=True)
-
- project_info = self._get_current_project_info()
- if not project_info:
- st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
- if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
- st.session_state.pricing_step = "project_info"
- st.rerun()
+ if not st.session_state.projects:
+ st.info("لا توجد مشاريع. قم بإنشاء مشروع جديد للبدء.")
return
-
- # الحصول على بنود المشروع الحالي
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
-
- # عرض جدول الكميات
- if project_items:
- st.subheader("جدول الكميات")
- # إنشاء DataFrame للعرض
- df = pd.DataFrame([
- {
- 'الكود': item['code'],
- 'الوصف': item['description'],
- 'الوحدة': item['unit'],
- 'الكمية': item['quantity'],
- 'سعر الوحدة': item['unit_price'],
- 'السعر الإجمالي': item['total_price'],
- 'نوع المورد': item.get('resource_type', '-')
- }
- for item in project_items
- ])
+ # إنشاء DataFrame من قائمة المشاريع
+ df = pd.DataFrame(st.session_state.projects)
+ if len(df) > 0 and 'id' in df.columns:
+ df = df[['id', 'name', 'client', 'estimated_value', 'deadline', 'status', 'pricing_type']]
+ df.columns = ['الرقم', 'اسم المشروع', 'العميل', 'القيمة التقديرية', 'الموعد النهائي', 'الحالة', 'نوع التسعير']
- # عرض الجدول
- st.dataframe(df)
+ # تنسيق القيمة التقديرية
+ df['القيمة التقديرية'] = df['القيمة التقديرية'].apply(lambda x: f"{x:,.2f} ريال")
- # عرض إجمالي التكلفة
- total_cost = sum(item['total_price'] for item in project_items)
- st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
- else:
- st.info("لا توجد بنود في جدول الكميات. يرجى إضافة بنود باستخدام النموذج أدناه.")
-
- # نموذج إضافة بند جديد
- with st.expander("إضافة بند جديد", expanded=not project_items):
- with st.form(key="add_item_form"):
- # حقول النموذج
- code = st.text_input("الكود")
- description = st.text_area("الوصف")
- unit = st.text_input("الوحدة")
- quantity = st.number_input("الكمية", min_value=0.0, step=0.1)
- unit_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1)
- resource_type = st.selectbox(
- "نوع المورد",
- ["مواد", "عمالة", "معدات", "خدمات", "أخرى"]
- )
-
- # حساب السعر الإجمالي
- total_price = quantity * unit_price
- st.metric("السعر الإجمالي", f"{total_price:,.2f} ريال")
-
- # زر الإرسال
- submit_button = st.form_submit_button("إضافة البند")
-
- if submit_button:
- # التحقق من صحة البيانات
- if not code or not description or not unit or quantity <= 0 or unit_price <= 0:
- st.error("يرجى ملء جميع الحقول المطلوبة")
- else:
- # إنشاء كائن البند
- item_id = f"item_{len(st.session_state.boq_items) + 1}"
- item_data = {
- 'id': item_id,
- 'project_id': st.session_state.current_project,
- 'code': code,
- 'description': description,
- 'unit': unit,
- 'quantity': quantity,
- 'unit_price': unit_price,
- 'total_price': total_price,
- 'resource_type': resource_type
- }
-
- # إضافة البند إلى قائمة البنود
- added_item = self.storage.add_boq_item(item_data)
- st.session_state.boq_items.append(added_item)
-
- st.success(f"تم إضافة البند: {code}")
- st.rerun()
-
- # نموذج استيراد بنود من ملف CSV
- with st.expander("استيراد من CSV"):
- uploaded_file = st.file_uploader("اختر ملف CSV", type="csv")
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
- if uploaded_file is not None:
- try:
- # قراءة الملف
- csv_data = uploaded_file.read().decode('utf-8')
- csv_reader = csv.reader(io.StringIO(csv_data))
-
- # تخطي الصف الأول (العناوين)
- headers = next(csv_reader)
+ # اختيار المشروع
+ col1, col2 = st.columns(2)
+ with col1:
+ project_ids = [p['id'] for p in st.session_state.projects]
+ if st.session_state.current_project in project_ids:
+ current_index = project_ids.index(st.session_state.current_project)
+ else:
+ current_index = 0 if project_ids else None
- # قراءة البنود
- imported_items = []
- for row in csv_reader:
- if len(row) >= 6:
- code = row[0]
- description = row[1]
- unit = row[2]
- quantity = float(row[3])
- unit_price = float(row[4])
- resource_type = row[5] if len(row) > 5 else "مواد"
-
- total_price = quantity * unit_price
-
- item_id = f"item_{len(st.session_state.boq_items) + len(imported_items) + 1}"
- item_data = {
- 'id': item_id,
- 'project_id': st.session_state.current_project,
- 'code': code,
- 'description': description,
- 'unit': unit,
- 'quantity': quantity,
- 'unit_price': unit_price,
- 'total_price': total_price,
- 'resource_type': resource_type
- }
-
- imported_items.append(item_data)
+ if current_index is not None and project_ids:
+ selected_project_id = st.selectbox(
+ "اختر المشروع",
+ options=project_ids,
+ format_func=lambda x: next((p['name'] for p in st.session_state.projects if p['id'] == x), ""),
+ index=current_index,
+ key="select_project"
+ )
- if imported_items:
- if st.button("استيراد البنود", key="import_items_btn"):
- # إضافة البنود إلى قائمة البنود
- for item_data in imported_items:
- added_item = self.storage.add_boq_item(item_data)
- st.session_state.boq_items.append(added_item)
-
- st.success(f"تم استيراد {len(imported_items)} بند")
- st.rerun()
- else:
- st.error("لم يتم العثور على بنود صالحة في الملف")
+ if selected_project_id != st.session_state.current_project:
+ st.session_state.current_project = selected_project_id
+ st.rerun()
+
+ with col2:
+ if st.button("تعديل المشروع", key="edit_project_btn"):
+ st.session_state.edit_project_id = st.session_state.current_project
+ st.session_state.show_edit_project_form = True
+ st.rerun()
+ else:
+ st.info("لا توجد مشاريع. قم بإنشاء مشروع جديد للبدء.")
- except Exception as e:
- st.error(f"حدث خطأ أثناء استيراد الملف: {str(e)}")
+ def _get_current_project_info(self):
+ """الحصول على معلومات المشروع الحالي"""
+ for project in st.session_state.projects:
+ if project['id'] == st.session_state.current_project:
+ return project
+ return None
- # أزرار التنقل بين الخطوات
- col1, col2 = st.columns(2)
- with col1:
- if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info_btn"):
- st.session_state.pricing_step = "project_info"
- st.rerun()
+ def _render_project_info(self, project):
+ """عرض معلومات المشروع"""
+ st.header(f"تسعير: {project['name']}")
+ col1, col2, col3, col4 = st.columns(4)
+ with col1:
+ st.metric("العميل", project['client'])
with col2:
- if project_items:
- if st.button("متابعة إلى تحليل التكلفة ⬅️", key="continue_to_cost_analysis", type="primary"):
- st.session_state.pricing_step = "cost_analysis"
- st.rerun()
- else:
- st.warning("يجب إضافة بند واحد على الأقل للمتابعة")
-
- st.markdown('
', unsafe_allow_html=True)
-
- def _render_cost_analysis_step(self):
- """عرض خطوة تحليل التكلفة"""
- st.markdown('', unsafe_allow_html=True)
- st.markdown('
الخطوة 3: تحليل التكلفة
', unsafe_allow_html=True)
-
- project_info = self._get_current_project_info()
- if not project_info:
- st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
- if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
- st.session_state.pricing_step = "project_info"
- st.rerun()
- return
+ st.metric("القيمة التقديرية", f"{project['estimated_value']:,.2f} ريال")
+ with col3:
+ st.metric("الموعد النهائي", project['deadline'])
+ with col4:
+ st.metric("نوع التسعير", project['pricing_type'])
+
+ def _render_bill_of_quantities(self):
+ """عرض جدول الكميات"""
+ st.subheader("جدول الكميات")
+ # زر إضافة بند جديد
+ col1, col2, col3 = st.columns([1, 1, 2])
+ with col1:
+ if st.button("➕ إضافة بند جديد", key="add_boq_item_btn"):
+ st.session_state.show_new_boq_item_form = True
+ st.session_state.show_resource_selector = False
+
+ with col2:
+ if st.button("📋 سحب من الموارد", key="add_from_resources_btn"):
+ st.session_state.show_resource_selector = True
+ st.session_state.show_new_boq_item_form = False
+
+ # عرض نموذج إضافة بند جديد
+ if st.session_state.show_new_boq_item_form:
+ self._render_new_boq_item_form()
+
+ # عرض نموذج تعديل البند
+ if st.session_state.show_edit_boq_item_form and st.session_state.edit_boq_item_id is not None:
+ self._render_edit_boq_item_form()
+
+ # عرض محدد الموارد
+ if st.session_state.show_resource_selector:
+ self._render_resource_selector()
+
# الحصول على بنود المشروع الحالي
project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
if not project_items:
- st.warning("لا توجد بنود في جدول الكميات. يرجى العودة إلى الخطوة السابقة لإضافة البنود.")
-
- col1, col2 = st.columns(2)
- with col1:
- if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq_btn"):
- st.session_state.pricing_step = "boq"
- st.rerun()
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
return
+
+ # إنشاء DataFrame من بنود المشروع
+ df = pd.DataFrame(project_items)
+ df = df[['id', 'code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
- # تحليل التكلفة
- st.subheader("تحليل التكلفة حسب نوع المورد")
-
- # حساب التكلفة حسب نوع المورد
- resource_types = {}
- for item in project_items:
- resource_type = item.get('resource_type', 'أخرى')
- if resource_type in resource_types:
- resource_types[resource_type] += item['total_price']
- else:
- resource_types[resource_type] = item['total_price']
-
- # عرض التكلفة حسب نوع المورد
- resource_df = pd.DataFrame({
- 'نوع المورد': list(resource_types.keys()),
- 'التكلفة': list(resource_types.values())
- })
-
- # إضافة نسبة التكلفة
- total_cost = sum(resource_types.values())
- resource_df['النسبة'] = resource_df['التكلفة'] / total_cost * 100
-
- # عرض الجدول
- st.dataframe(resource_df)
-
- # عرض الرسم البياني
- fig, ax = plt.subplots(figsize=(10, 6))
- ax.pie(
- resource_df['التكلفة'],
- labels=resource_df['نوع المورد'],
- autopct='%1.1f%%',
- startangle=90
+ # تحويل الجدول إلى جدول قابل للتعديل
+ edited_df = st.data_editor(
+ df,
+ column_config={
+ "id": st.column_config.Column("الرقم", disabled=True),
+ "code": st.column_config.Column("الكود"),
+ "description": st.column_config.Column("الوصف"),
+ "unit": st.column_config.Column("الوحدة"),
+ "quantity": st.column_config.NumberColumn("الكمية", min_value=0.0, format="%.2f", step=0.1),
+ "unit_price": st.column_config.NumberColumn("سعر الوحدة", min_value=0.0, format="%.2f ريال", step=0.1),
+ "total_price": st.column_config.NumberColumn("السعر الإجمالي", format="%.2f ريال", disabled=True),
+ "resource_type": st.column_config.SelectboxColumn("نوع المورد", options=["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"])
+ },
+ use_container_width=True,
+ key="edit_boq_items"
)
- ax.axis('equal')
- st.pyplot(fig)
- # عرض إجمالي التكلفة
- st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
+ # تحديث البيانات في حالة التعديل
+ if edited_df is not None and not edited_df.equals(df):
+ for i, row in edited_df.iterrows():
+ item_id = row['id']
+ for j, item in enumerate(st.session_state.boq_items):
+ if item['id'] == item_id:
+ st.session_state.boq_items[j]['code'] = row['code']
+ st.session_state.boq_items[j]['description'] = row['description']
+ st.session_state.boq_items[j]['unit'] = row['unit']
+ st.session_state.boq_items[j]['quantity'] = row['quantity']
+ st.session_state.boq_items[j]['unit_price'] = row['unit_price']
+ st.session_state.boq_items[j]['total_price'] = row['quantity'] * row['unit_price']
+ st.session_state.boq_items[j]['resource_type'] = row['resource_type']
+ break
+
+ st.success("تم تحديث جدول الكميات بنجاح")
+ st.rerun()
- # أزرار التنقل بين الخطوات
+ # حساب المجموع الكلي
+ total_price = sum(item['total_price'] for item in project_items)
+ st.metric("إجمالي جدول الكميات", f"{total_price:,.2f} ريال")
+
+ # أزرار التصدير والتعديل
col1, col2 = st.columns(2)
with col1:
- if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq"):
- st.session_state.pricing_step = "boq"
- st.rerun()
-
+ if st.button("تصدير جدول الكميات", key="export_boq_btn_1"):
+ # إنشاء CSV للتصدير
+ export_df = pd.DataFrame(project_items)
+ export_df = export_df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
+ export_df.columns = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي', 'نوع المورد']
+
+ csv = export_df.to_csv(index=False)
+ b64 = base64.b64encode(csv.encode()).decode()
+ href = f'
تحميل CSV'
+ st.markdown(href, unsafe_allow_html=True)
+
with col2:
- if st.button("متابعة إلى استراتيجيات التسعير ⬅️", key="continue_to_pricing_strategies", type="primary"):
- st.session_state.pricing_step = "pricing_strategies"
- st.rerun()
+ if len(project_items) > 0:
+ selected_item_id = st.selectbox(
+ "اختر بند للتعديل",
+ options=[item['id'] for item in project_items],
+ format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
+ key="select_boq_item"
+ )
+
+ if st.button("تعديل البند", key="edit_boq_item_btn"):
+ st.session_state.edit_boq_item_id = selected_item_id
+ st.session_state.show_edit_boq_item_form = True
+ st.rerun()
+
+ def _render_new_boq_item_form(self):
+ """عرض نموذج إضافة بند جديد"""
+ st.subheader("إضافة بند جديد")
- st.markdown('
', unsafe_allow_html=True)
-
- def _render_pricing_strategies_step(self):
- """عرض خطوة استراتيجيات التسعير"""
- st.markdown('', unsafe_allow_html=True)
- st.markdown('
الخطوة 4: استراتيجيات التسعير
', unsafe_allow_html=True)
-
- project_info = self._get_current_project_info()
- if not project_info:
- st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
- if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
- st.session_state.pricing_step = "project_info"
+ with st.form(key="new_boq_item_form"):
+ code = st.text_input("الكود", key="new_boq_item_code")
+ description = st.text_input("الوصف", key="new_boq_item_description")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ unit = st.text_input("الوحدة", key="new_boq_item_unit")
+ with col2:
+ resource_type = st.selectbox(
+ "نوع المورد",
+ ["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"],
+ key="new_boq_item_resource_type"
+ )
+
+ col3, col4 = st.columns(2)
+ with col3:
+ quantity = st.number_input("الكمية", min_value=0.0, format="%f", key="new_boq_item_quantity")
+ with col4:
+ unit_price = st.number_input("سعر الوحدة", min_value=0.0, format="%f", key="new_boq_item_unit_price")
+
+ total_price = quantity * unit_price
+ st.metric("السعر الإجمالي", f"{total_price:,.2f} ريال")
+
+ col5, col6 = st.columns(2)
+ with col5:
+ submit_button = st.form_submit_button("حفظ")
+ with col6:
+ cancel_button = st.form_submit_button("إلغاء")
+
+ if submit_button:
+ if code and description and unit and quantity > 0 and unit_price > 0:
+ new_item = {
+ 'id': st.session_state.next_boq_item_id,
+ 'project_id': st.session_state.current_project,
+ 'code': code,
+ 'description': description,
+ 'unit': unit,
+ 'quantity': quantity,
+ 'unit_price': unit_price,
+ 'total_price': total_price,
+ 'resource_type': resource_type
+ }
+
+ st.session_state.boq_items.append(new_item)
+ st.session_state.next_boq_item_id += 1
+ st.session_state.show_new_boq_item_form = False
st.rerun()
+
+ if cancel_button:
+ st.session_state.show_new_boq_item_form = False
+ st.rerun()
+
+ def _render_edit_boq_item_form(self):
+ """عرض نموذج تعديل البند"""
+ item = None
+ for i in st.session_state.boq_items:
+ if i['id'] == st.session_state.edit_boq_item_id:
+ item = i
+ break
+
+ if not item:
+ st.session_state.show_edit_boq_item_form = False
+ st.rerun()
return
+
+ st.subheader(f"تعديل البند: {item['description']}")
- # الحصول على بنود المشروع الحالي
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
-
- if not project_items:
- st.warning("لا توجد بنود في جدول الكميات. يرجى العودة إلى الخطوة السابقة لإضافة البنود.")
+ with st.form(key="edit_boq_item_form"):
+ code = st.text_input("الكود", value=item['code'], key="edit_boq_item_code")
+ description = st.text_input("الوصف", value=item['description'], key="edit_boq_item_description")
col1, col2 = st.columns(2)
with col1:
- if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq_btn"):
- st.session_state.pricing_step = "boq"
- st.rerun()
- return
-
- # حساب إجمالي التكلفة
- total_cost = sum(item['total_price'] for item in project_items)
-
- # عرض استراتيجيات التسعير
- st.subheader("استراتيجيات التسعير")
-
- # اختيار استراتيجية التسعير
- pricing_strategy = st.selectbox(
- "اختر استراتيجية التسعير",
- ["قياسي", "تنافسي", "مخصص"],
- index=["قياسي", "تنافسي", "مخصص"].index(project_info['pricing_type']) if 'pricing_type' in project_info else 0
+ unit = st.text_input("الوحدة", value=item['unit'], key="edit_boq_item_unit")
+ with col2:
+ resource_type = st.selectbox(
+ "نوع المورد",
+ ["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"],
+ index=["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"].index(item['resource_type']) if 'resource_type' in item else 0,
+ key="edit_boq_item_resource_type"
+ )
+
+ col3, col4 = st.columns(2)
+ with col3:
+ quantity = st.number_input("الكمية", min_value=0.0, value=float(item['quantity']), format="%f", key="edit_boq_item_quantity")
+ with col4:
+ unit_price = st.number_input("سعر الوحدة", min_value=0.0, value=float(item['unit_price']), format="%f", key="edit_boq_item_unit_price")
+
+ total_price = quantity * unit_price
+ st.metric("السعر الإجمالي", f"{total_price:,.2f} ريال")
+
+ col5, col6, col7 = st.columns(3)
+ with col5:
+ submit_button = st.form_submit_button("حفظ")
+ with col6:
+ cancel_button = st.form_submit_button("إلغاء")
+ with col7:
+ delete_button = st.form_submit_button("حذف البند", type="primary")
+
+ if submit_button:
+ if code and description and unit and quantity > 0 and unit_price > 0:
+ for i, itm in enumerate(st.session_state.boq_items):
+ if itm['id'] == st.session_state.edit_boq_item_id:
+ st.session_state.boq_items[i]['code'] = code
+ st.session_state.boq_items[i]['description'] = description
+ st.session_state.boq_items[i]['unit'] = unit
+ st.session_state.boq_items[i]['quantity'] = quantity
+ st.session_state.boq_items[i]['unit_price'] = unit_price
+ st.session_state.boq_items[i]['total_price'] = total_price
+ st.session_state.boq_items[i]['resource_type'] = resource_type
+ break
+
+ st.session_state.show_edit_boq_item_form = False
+ st.rerun()
+
+ if cancel_button:
+ st.session_state.show_edit_boq_item_form = False
+ st.rerun()
+
+ if delete_button:
+ for i, itm in enumerate(st.session_state.boq_items):
+ if itm['id'] == st.session_state.edit_boq_item_id:
+ st.session_state.boq_items.pop(i)
+ break
+
+ st.session_state.show_edit_boq_item_form = False
+ st.rerun()
+
+ def _render_resource_selector(self):
+ """عرض محدد الموارد"""
+ st.subheader("سحب من الموارد المسجلة")
+
+ # اختيار نوع المورد
+ resource_type = st.selectbox(
+ "نوع المورد",
+ ["المواد", "العمالة", "المعدات", "المقاولين من الباطن"],
+ index=["المواد", "العمالة", "المعدات", "المقاولين من الباطن"].index(st.session_state.selected_resource_type),
+ key="resource_type_selector"
)
- # تعيين نسبة الربح حسب استراتيجية التسعير
- if pricing_strategy == "قياسي":
- profit_percentage = st.slider("نسبة الربح", min_value=10, max_value=30, value=20, step=1)
- st.info("استراتيجية التسعير القياسية تستخدم نسبة ربح ثابتة على إجمالي التكلفة.")
- elif pricing_strategy == "تنافسي":
- profit_percentage = st.slider("نسبة الربح", min_value=5, max_value=20, value=10, step=1)
- st.info("استراتيجية التسعير التنافسية تستخدم نسبة ربح منخفضة لزيادة القدرة التنافسية.")
- else: # مخصص
- profit_percentage = st.slider("نسبة الربح", min_value=0, max_value=50, value=15, step=1)
- st.info("استراتيجية التسعير المخصصة تتيح تحديد نسبة الربح بحرية.")
-
- # حساب السعر النهائي
- profit_amount = total_cost * (profit_percentage / 100)
- final_price = total_cost + profit_amount
-
- # عرض ملخص التسعير
- st.subheader("ملخص التسعير")
-
- col1, col2, col3 = st.columns(3)
-
- with col1:
- st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
-
- with col2:
- st.metric("مبلغ الربح", f"{profit_amount:,.2f} ريال")
-
- with col3:
- st.metric("السعر النهائي", f"{final_price:,.2f} ريال")
-
- # زر تطبيق استراتيجية التسعير
- if st.button("تطبيق استراتيجية التسعير", key="apply_pricing_strategy"):
- # تحديث نوع التسعير في المشروع
- self.storage.update_project(project_info['id'], {'pricing_type': pricing_strategy})
+ if resource_type != st.session_state.selected_resource_type:
+ st.session_state.selected_resource_type = resource_type
+ st.rerun()
- # تحديث حالة الجلسة
- for i, p in enumerate(st.session_state.projects):
- if p['id'] == project_info['id']:
- st.session_state.projects[i]['pricing_type'] = pricing_strategy
- break
+ # الحصول على الموارد المناسبة
+ resources = []
+ if resource_type == "المواد" and 'materials' in st.session_state:
+ resources = st.session_state.materials
+ elif resource_type == "العمالة" and 'labor' in st.session_state:
+ resources = st.session_state.labor
+ elif resource_type == "المعدات" and 'equipment' in st.session_state:
+ resources = st.session_state.equipment
+ elif resource_type == "المقاولين من الباطن" and 'subcontractors' in st.session_state:
+ resources = st.session_state.subcontractors
- # حفظ التسعير في سجل التسعير
- pricing_data = {
- 'project_id': project_info['id'],
- 'pricing_strategy': pricing_strategy,
- 'profit_percentage': profit_percentage,
- 'total_cost': total_cost,
- 'profit_amount': profit_amount,
- 'final_price': final_price
- }
+ if not resources:
+ st.info(f"لا توجد موارد مسجلة من نوع {resource_type}. يرجى إضافة موارد في وحدة الموارد أولاً.")
+
+ col1, col2 = st.columns(2)
+ with col2:
+ if st.button("إلغاء", key="cancel_resource_selector"):
+ st.session_state.show_resource_selector = False
+ st.rerun()
+ return
- self.storage.save_pricing(pricing_data)
+ # إنشاء DataFrame من الموارد
+ df = pd.DataFrame(resources)
+ if 'id' in df.columns and 'name' in df.columns and 'unit' in df.columns and 'price' in df.columns:
+ df = df[['id', 'name', 'category', 'unit', 'price']]
+ df.columns = ['الرقم', 'الاسم', 'الفئة', 'الوحدة', 'السعر']
- st.success("تم تطبيق استراتيجية التسعير بنجاح")
-
- # أزرار التنقل بين الخطوات
- col1, col2 = st.columns(2)
- with col1:
- if st.button("العودة إلى تحليل التكلفة ➡️", key="back_to_cost_analysis"):
- st.session_state.pricing_step = "cost_analysis"
+ # تنسيق السعر
+ df['السعر'] = df['السعر'].apply(lambda x: f"{x:,.2f} ريال")
+
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
+
+ with st.form(key="add_from_resource_form"):
+ col1, col2 = st.columns(2)
+ with col1:
+ selected_resource_id = st.selectbox(
+ "اختر المورد",
+ options=[r['id'] for r in resources],
+ format_func=lambda x: next((r['name'] for r in resources if r['id'] == x), ""),
+ key="select_resource"
+ )
+
+ with col2:
+ quantity = st.number_input("الكمية", min_value=0.1, value=1.0, format="%f", key="resource_quantity")
+
+ # الحصول على المورد المحدد
+ selected_resource = next((r for r in resources if r['id'] == selected_resource_id), None)
+ if selected_resource:
+ total_price = quantity * selected_resource['price']
+ st.metric("السعر الإجمالي", f"{total_price:,.2f} ريال")
+
+ col3, col4 = st.columns(2)
+ with col3:
+ submit_button = st.form_submit_button("إضافة إلى جدول الكميات")
+ with col4:
+ cancel_button = st.form_submit_button("إلغاء")
+
+ if submit_button and selected_resource and quantity > 0:
+ # تحويل نوع المورد إلى الصيغة المناسبة
+ resource_type_map = {
+ "المواد": "مواد",
+ "العمالة": "عمالة",
+ "المعدات": "معدات",
+ "المقاولين من الباطن": "مقاولين من الباطن"
+ }
+
+ # إنشاء كود فريد
+ resource_code_prefix = {
+ "المواد": "M",
+ "العمالة": "L",
+ "المعدات": "E",
+ "المقاولين من الباطن": "S"
+ }
+
+ code_prefix = resource_code_prefix.get(resource_type, "X")
+ code = f"{code_prefix}-{selected_resource['id']:03d}"
+
+ # إضافة البند إلى جدول الكميات
+ new_item = {
+ 'id': st.session_state.next_boq_item_id,
+ 'project_id': st.session_state.current_project,
+ 'code': code,
+ 'description': selected_resource['name'],
+ 'unit': selected_resource['unit'],
+ 'quantity': quantity,
+ 'unit_price': selected_resource['price'],
+ 'total_price': quantity * selected_resource['price'],
+ 'resource_type': resource_type_map.get(resource_type, "أخرى"),
+ 'resource_id': selected_resource['id']
+ }
+
+ st.session_state.boq_items.append(new_item)
+ st.session_state.next_boq_item_id += 1
+ st.session_state.show_resource_selector = False
st.rerun()
-
- with col2:
- if st.button("متابعة إلى المراجعة ��لنهائية ⬅️", key="continue_to_review", type="primary"):
- st.session_state.pricing_step = "review"
+
+ if cancel_button:
+ st.session_state.show_resource_selector = False
st.rerun()
-
- st.markdown('
', unsafe_allow_html=True)
-
- def _render_review_step(self):
- """عرض خطوة المراجعة النهائية"""
- st.markdown('', unsafe_allow_html=True)
- st.markdown('
الخطوة 5: المراجعة النهائية
', unsafe_allow_html=True)
-
- project_info = self._get_current_project_info()
- if not project_info:
- st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
- if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
- st.session_state.pricing_step = "project_info"
+ else:
+ st.error("تنسيق بيانات الموارد غير صحيح. يرجى التأكد من وجود الحقول المطلوبة.")
+
+ if st.button("إلغاء", key="cancel_resource_selector_error"):
+ st.session_state.show_resource_selector = False
st.rerun()
- return
+
+ def _render_item_price_analysis(self):
+ """عرض تحليل سعر البند مع إمكانية التعديل والحفظ"""
+ st.subheader("تحليل سعر البند")
# الحصول على بنود المشروع الحالي
project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
if not project_items:
- st.warning("لا توجد بنود في جدول الكميات. يرجى العودة إلى الخطوة السابقة لإضافة البنود.")
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
+ return
- col1, col2 = st.columns(2)
- with col1:
- if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq_btn"):
- st.session_state.pricing_step = "boq"
- st.rerun()
+ # اختيار البند للتحليل
+ selected_item_id = st.selectbox(
+ "اختر البند للتحليل",
+ options=[item['id'] for item in project_items],
+ format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
+ key="select_item_for_analysis"
+ )
+
+ # الحصول على البند المحدد
+ selected_item = next((item for item in project_items if item['id'] == selected_item_id), None)
+
+ if not selected_item:
+ st.error("لم يتم العثور على البند المحدد.")
return
+
+ # عرض معلومات البند
+ col1, col2 = st.columns(2)
+ with col1:
+ st.write(f"**البند:** {selected_item['description']}")
+ st.write(f"**الكود:** {selected_item['code']}")
+ st.write(f"**الوحدة:** {selected_item['unit']}")
+ with col2:
+ st.write(f"**الكمية:** {selected_item['quantity']}")
+ st.write(f"**سعر الوحدة:** {selected_item['unit_price']:,.2f} ريال")
+ st.write(f"**السعر الإجمالي:** {selected_item['total_price']:,.2f} ريال")
- # الحصول على آخر تسعير للمشروع
- pricing_history = self.storage.get_project_pricing_history(st.session_state.current_project)
- latest_pricing = pricing_history[-1] if pricing_history else None
+ # إنشاء تحليل سعر البند
+ item_analysis = self._generate_item_analysis(selected_item)
- # عرض معلومات المشروع
- st.subheader("معلومات المشروع")
+ # عرض تحليل المواد مع إمكانية التعديل
+ self._render_materials_analysis(item_analysis)
- col1, col2 = st.columns(2)
+ # عرض تحليل المعدات مع إمكانية التعديل
+ self._render_equipment_analysis(item_analysis)
- with col1:
- st.write(f"**اسم المشروع:** {project_info['name']}")
- st.write(f"**العميل:** {project_info['client']}")
- st.write(f"**الموقع:** {project_info.get('location', '-')}")
- st.write(f"**رقم المناقصة:** {project_info.get('tender_number', '-')}")
+ # عرض تحليل العمالة مع إمكانية التعديل
+ self._render_labor_analysis(item_analysis)
- with col2:
- st.write(f"**القيمة التقديرية:** {project_info['estimated_value']:,.2f} ريال")
- st.write(f"**الموعد النهائي:** {project_info['deadline']}")
- st.write(f"**مدة العقد:** {project_info.get('contract_duration', '-')}")
- st.write(f"**نوع التسعير:** {project_info['pricing_type']}")
+ # عرض تحليل المقاولين من الباطن مع إمكانية التعديل
+ self._render_subcontractors_analysis(item_analysis)
- # عرض ملخص التسعير
- st.subheader("ملخص التسعير")
+ # عرض ملخص التكلفة
+ self._render_cost_summary(item_analysis)
- # حساب إجمالي التكلفة
- total_cost = sum(item['total_price'] for item in project_items)
+ # زر حفظ التغييرات في جدول الكميات الرئيسي
+ if st.button("حفظ جميع التغييرات في جدول الكميات", key="save_all_changes", type="primary"):
+ # تحديث البند في جدول الكميات
+ self._update_boq_item_from_analysis(selected_item_id, item_analysis)
+ st.success("تم حفظ جميع التغييرات بنجاح في جدول الكميات الرئيسي")
+ st.session_state.item_analysis_edited = False
+ st.rerun()
- if latest_pricing:
- profit_percentage = latest_pricing['profit_percentage']
- profit_amount = latest_pricing['profit_amount']
- final_price = latest_pricing['final_price']
- else:
- # استخدام قيم افتراضية إذا لم يكن هناك تسعير سابق
- profit_percentage = 15
- profit_amount = total_cost * (profit_percentage / 100)
- final_price = total_cost + profit_amount
+ def _generate_item_analysis(self, item):
+ """إنشاء تحليل سعر البند"""
+ # في هذه النسخة التجريبية، سنقوم بإنشاء بيانات عشوائية للتحليل
+ # في النسخة النهائية، يجب استخدام بيانات حقيقية من قاعدة البيانات
- col1, col2, col3 = st.columns(3)
+ unit_price = item['unit_price']
- with col1:
- st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
+ # تقسيم سعر الوحدة إلى مكوناته
+ materials_cost = unit_price * random.uniform(0.4, 0.6)
+ equipment_cost = unit_price * random.uniform(0.1, 0.2)
+ labor_cost = unit_price * random.uniform(0.1, 0.2)
+ overhead_cost = unit_price * random.uniform(0.05, 0.1)
+ profit = unit_price - (materials_cost + equipment_cost + labor_cost + overhead_cost)
- with col2:
- st.metric("مبلغ الربح", f"{profit_amount:,.2f} ريال", f"{profit_percentage}%")
+ # إنشاء قائمة المواد
+ materials = [
+ {
+ 'name': 'خرسانة جاهزة' if 'خرسانة' in item['description'].lower() else 'حديد تسليح' if 'حديد' in item['description'].lower() else 'رمل',
+ 'unit': 'م3' if 'خرسانة' in item['description'].lower() else 'طن' if 'حديد' in item['description'].lower() else 'م3',
+ 'quantity': random.uniform(0.5, 1.5),
+ 'unit_price': materials_cost * 0.6,
+ 'total_price': materials_cost * 0.6 * random.uniform(0.5, 1.5)
+ },
+ {
+ 'name': 'إسمنت',
+ 'unit': 'طن',
+ 'quantity': random.uniform(0.2, 0.5),
+ 'unit_price': materials_cost * 0.3,
+ 'total_price': materials_cost * 0.3 * random.uniform(0.2, 0.5)
+ },
+ {
+ 'name': 'مواد أخرى',
+ 'unit': 'مجموعة',
+ 'quantity': 1,
+ 'unit_price': materials_cost * 0.1,
+ 'total_price': materials_cost * 0.1
+ }
+ ]
- with col3:
- st.metric("السعر النهائي", f"{final_price:,.2f} ريال")
+ # إنشاء قائمة المعدات
+ equipment = [
+ {
+ 'name': 'خلاطة خرسانة' if 'خرسانة' in item['description'].lower() else 'حفارة' if 'حفر' in item['description'].lower() else 'رافعة',
+ 'unit': 'يوم',
+ 'quantity': random.uniform(1, 3),
+ 'unit_price': equipment_cost * 0.7,
+ 'total_price': equipment_cost * 0.7 * random.uniform(1, 3)
+ },
+ {
+ 'name': 'معدات أخرى',
+ 'unit': 'يوم',
+ 'quantity': random.uniform(1, 2),
+ 'unit_price': equipment_cost * 0.3,
+ 'total_price': equipment_cost * 0.3 * random.uniform(1, 2)
+ }
+ ]
- # عرض جدول الكميات
- st.subheader("جدول الكميات")
+ # إنشاء قائمة العمالة
+ labor = [
+ {
+ 'name': 'عامل فني',
+ 'unit': 'يوم',
+ 'quantity': random.uniform(2, 5),
+ 'unit_price': labor_cost * 0.6,
+ 'total_price': labor_cost * 0.6 * random.uniform(2, 5)
+ },
+ {
+ 'name': 'عامل عادي',
+ 'unit': 'يوم',
+ 'quantity': random.uniform(3, 8),
+ 'unit_price': labor_cost * 0.4,
+ 'total_price': labor_cost * 0.4 * random.uniform(3, 8)
+ }
+ ]
- # إنشاء DataFrame للعرض
- df = pd.DataFrame([
+ # إنشاء قائمة المقاولين من الباطن
+ subcontractors = [
{
- 'الكود': item['code'],
- 'الوصف': item['description'],
- 'الوحدة': item['unit'],
- 'الكمية': item['quantity'],
- 'سعر الوحدة': item['unit_price'],
- 'السعر الإجمالي': item['total_price'],
- 'نوع المورد': item.get('resource_type', '-')
+ 'name': 'مقاول أعمال خرسانية' if 'خرسانة' in item['description'].lower() else 'مقاول أعمال حفر' if 'حفر' in item['description'].lower() else 'مقاول عام',
+ 'unit': 'عقد',
+ 'quantity': 1,
+ 'unit_price': unit_price * 0.15,
+ 'total_price': unit_price * 0.15
}
- for item in project_items
- ])
+ ]
+
+ # حساب إجمالي التكاليف
+ total_materials_cost = sum(material['total_price'] for material in materials)
+ total_equipment_cost = sum(equipment_item['total_price'] for equipment_item in equipment)
+ total_labor_cost = sum(labor_item['total_price'] for labor_item in labor)
+ total_subcontractors_cost = sum(subcontractor['total_price'] for subcontractor in subcontractors)
+
+ # تعديل الربح ليكون الفرق بين سعر الوحدة وإجمالي التكاليف
+ total_cost = total_materials_cost + total_equipment_cost + total_labor_cost + total_subcontractors_cost + overhead_cost
+ profit = unit_price - total_cost
+
+ return {
+ 'item': item,
+ 'materials': materials,
+ 'equipment': equipment,
+ 'labor': labor,
+ 'subcontractors': subcontractors,
+ 'total_materials_cost': total_materials_cost,
+ 'total_equipment_cost': total_equipment_cost,
+ 'total_labor_cost': total_labor_cost,
+ 'total_subcontractors_cost': total_subcontractors_cost,
+ 'overhead_cost': overhead_cost,
+ 'profit': profit,
+ 'unit_price': unit_price
+ }
- # عرض الجدول
- st.dataframe(df)
-
- # خيارات التصدير
- st.subheader("تصدير البيانات")
-
- export_options = st.multiselect(
- "اختر خيارات التصدير",
- [
- "جدول الكميات (CSV)",
- "جدول الكميات (Excel)",
- "تقرير كامل (PDF)",
- "تقرير المحتوى المحلي (Excel)",
- "تقرير المخاطر (Excel)"
- ],
- default=["جدول الكميات (CSV)"],
- key="export_options"
- )
+ def _render_materials_analysis(self, item_analysis):
+ """عرض تحليل المواد مع إمكانية التعديل"""
+ st.subheader("تحليل المواد")
- if st.button("تصدير البيانات", key="export_data_btn", type="primary"):
- if "جدول الكميات (CSV)" in export_options:
- # إنشاء ملف CSV للتصدير
- csv_buffer = io.StringIO()
- csv_writer = csv.writer(csv_buffer)
-
- # كتابة العناوين
- csv_writer.writerow(['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي', 'نوع المورد'])
-
- # كتابة البيانات
- for item in project_items:
- csv_writer.writerow([
- item['code'],
- item['description'],
- item['unit'],
- item['quantity'],
- item['unit_price'],
- item['total_price'],
- item.get('resource_type', '-')
- ])
-
- # تحويل البيانات إلى base64
- b64 = base64.b64encode(csv_buffer.getvalue().encode('utf-8')).decode()
- href = f'
تحميل CSV'
- st.markdown(href, unsafe_allow_html=True)
+ if not item_analysis['materials']:
+ st.info("لا توجد مواد في تحليل هذا البند.")
- if "جدول الكميات (Excel)" in export_options:
- # إنشاء ملف Excel للتصدير
- excel_file = export_to_excel(
- project_items,
- project_info,
- f"/tmp/boq_{st.session_state.current_project}.xlsx"
- )
-
- # عرض رابط التنزيل
- href = get_download_link(
- excel_file,
- "تحميل Excel",
- "excel"
- )
- st.markdown(href, unsafe_allow_html=True)
+ # إضافة زر لإضافة مواد جديدة
+ if st.button("إضافة مواد", key="add_first_material"):
+ item_analysis['materials'] = [{
+ 'name': 'مادة جديدة',
+ 'unit': 'وحدة',
+ 'quantity': 1.0,
+ 'unit_price': 0.0,
+ 'total_price': 0.0
+ }]
+ st.session_state.item_analysis_edited = True
+ st.rerun()
+ return
- if "تقرير كامل (PDF)" in export_options:
- # إنشاء ملف PDF للتصدير
- pdf_file = export_to_pdf(
- project_items,
- project_info,
- f"/tmp/report_{st.session_state.current_project}.pdf"
- )
-
- # عرض رابط التنزيل
- href = get_download_link(
- pdf_file,
- "تحميل PDF",
- "pdf"
- )
- st.markdown(href, unsafe_allow_html=True)
+ # إنشاء DataFrame من قائمة المواد
+ df = pd.DataFrame(item_analysis['materials'])
+ df.columns = ['المادة', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
+
+ # تحويل الجدول إلى جدول قابل للتعديل
+ edited_df = st.data_editor(
+ df,
+ use_container_width=True,
+ key="edit_materials_table",
+ column_config={
+ "المادة": st.column_config.Column("المادة"),
+ "الوحدة": st.column_config.Column("الوحدة"),
+ "الكمية": st.column_config.NumberColumn(
+ "الكمية",
+ min_value=0.0,
+ format="%.2f",
+ step=0.1,
+ ),
+ "سعر الوحدة": st.column_config.NumberColumn(
+ "سعر الوحدة",
+ min_value=0.0,
+ format="%.2f ريال",
+ step=0.1,
+ ),
+ "السعر الإجمالي": st.column_config.NumberColumn(
+ "السعر الإجمالي",
+ format="%.2f ريال",
+ disabled=True,
+ ),
+ },
+ num_rows="dynamic"
+ )
+
+ # تحديث البيانات في item_analysis بناءً على التعديلات
+ if edited_df is not None and not edited_df.equals(df):
+ # حذف جميع المواد الحالية
+ item_analysis['materials'] = []
- if "تقرير المحتوى المحلي (Excel)" in export_options:
- # إنشاء تقرير المحتوى المحلي
- local_content_file = export_local_content_report(
- project_items,
- project_info,
- f"/tmp/local_content_{st.session_state.current_project}.xlsx"
- )
+ # إضافة المواد المعدلة
+ for i, row in edited_df.iterrows():
+ # حساب السعر الإجمالي
+ total_price = row['الكمية'] * row['سعر الوحدة']
- # عرض رابط التنزيل
- href = get_download_link(
- local_content_file,
- "تحميل تقرير المحتوى المحلي",
- "excel"
- )
- st.markdown(href, unsafe_allow_html=True)
+ # إضافة المادة
+ item_analysis['materials'].append({
+ 'name': row['المادة'],
+ 'unit': row['الوحدة'],
+ 'quantity': row['الكمية'],
+ 'unit_price': row['سعر الوحدة'],
+ 'total_price': total_price
+ })
- if "تقرير المخاطر (Excel)" in export_options:
- # الحصول على مخاطر المشروع
- project_risks = [risk for risk in st.session_state.risks if risk['project_id'] == st.session_state.current_project]
-
- if project_risks:
- # إنشاء تقرير المخاطر
- risk_report_file = export_risk_report(
- project_risks,
- project_info,
- total_cost,
- f"/tmp/risk_report_{st.session_state.current_project}.xlsx"
- )
-
- # عرض رابط التنزيل
- href = get_download_link(
- risk_report_file,
- "تحميل تقرير المخاطر",
- "excel"
- )
- st.markdown(href, unsafe_allow_html=True)
- else:
- st.warning("لا توجد مخاطر مسجلة للمشروع. لا يمكن إنشاء تقرير المخاطر.")
+ # إعادة حساب إجمالي تكلفة المواد
+ item_analysis['total_materials_cost'] = sum(material['total_price'] for material in item_analysis['materials'])
+
+ # تعيين علامة التعديل
+ st.session_state.item_analysis_edited = True
+ st.rerun()
- # أزرار التنقل بين الخطوات
- col1, col2, col3 = st.columns(3)
+ # عرض إجمالي تكلفة المواد
+ st.metric("إجمالي تكلفة المواد", f"{item_analysis['total_materials_cost']:,.2f} ريال")
+
+ # أزرار التحكم
+ col1, col2 = st.columns(2)
with col1:
- if st.button("العودة إلى استراتيجيات التسعير ➡️", key="back_to_pricing_strategies"):
- st.session_state.pricing_step = "pricing_strategies"
+ if st.button("إضافة مادة جديدة", key="add_material"):
+ item_analysis['materials'].append({
+ 'name': 'مادة جديدة',
+ 'unit': 'وحدة',
+ 'quantity': 1.0,
+ 'unit_price': 0.0,
+ 'total_price': 0.0
+ })
+ st.session_state.item_analysis_edited = True
st.rerun()
- with col2:
- if st.button("متابعة إلى المحتوى المحلي ⬅️", key="continue_to_local_content", type="primary"):
- st.session_state.pricing_step = "local_content"
- st.rerun()
+ def _render_equipment_analysis(self, item_analysis):
+ """عرض تحليل المعدات مع إمكانية التعديل"""
+ st.subheader("تحليل المعدات")
- with col3:
- if st.button("إنهاء وعودة للصفحة الرئيسية", key="go_to_home"):
- st.session_state.pricing_step = "project_info"
+ if not item_analysis['equipment']:
+ st.info("لا توجد معدات في تحليل هذا البند.")
+
+ # إضافة زر لإضافة معدات جديدة
+ if st.button("إضافة معدات", key="add_first_equipment"):
+ item_analysis['equipment'] = [{
+ 'name': 'معدة جديدة',
+ 'unit': 'يوم',
+ 'quantity': 1.0,
+ 'unit_price': 0.0,
+ 'total_price': 0.0
+ }]
+ st.session_state.item_analysis_edited = True
st.rerun()
+ return
+
+ # إنشاء DataFrame من قائمة المعدات
+ df = pd.DataFrame(item_analysis['equipment'])
+ df.columns = ['المعدة', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
- st.markdown('
', unsafe_allow_html=True)
-
- def _render_local_content_step(self):
- """عرض خطوة احتساب المحتوى المحلي"""
- st.markdown('', unsafe_allow_html=True)
- st.markdown('
الخطوة 6: المحتوى المحلي
', unsafe_allow_html=True)
-
- project_info = self._get_current_project_info()
- if not project_info:
- st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
- if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
- st.session_state.pricing_step = "project_info"
+ # تحويل الجدول إلى جدول قابل للتعديل
+ edited_df = st.data_editor(
+ df,
+ use_container_width=True,
+ key="edit_equipment_table",
+ column_config={
+ "المعدة": st.column_config.Column("المعدة"),
+ "الوحدة": st.column_config.Column("الوحدة"),
+ "الكمية": st.column_config.NumberColumn(
+ "الكمية",
+ min_value=0.0,
+ format="%.2f",
+ step=0.1,
+ ),
+ "سعر الوحدة": st.column_config.NumberColumn(
+ "سعر الوحدة",
+ min_value=0.0,
+ format="%.2f ريال",
+ step=0.1,
+ ),
+ "السعر الإجمالي": st.column_config.NumberColumn(
+ "السعر الإجمالي",
+ format="%.2f ريال",
+ disabled=True,
+ ),
+ },
+ num_rows="dynamic"
+ )
+
+ # تحديث البيانات في item_analysis بناءً على التعديلات
+ if edited_df is not None and not edited_df.equals(df):
+ # حذف جميع المعدات الحالية
+ item_analysis['equipment'] = []
+
+ # إضافة المعدات المعدلة
+ for i, row in edited_df.iterrows():
+ # حساب السعر الإجمالي
+ total_price = row['الكمية'] * row['سعر الوحدة']
+
+ # إضافة المعدة
+ item_analysis['equipment'].append({
+ 'name': row['المعدة'],
+ 'unit': row['الوحدة'],
+ 'quantity': row['الكمية'],
+ 'unit_price': row['سعر الوحدة'],
+ 'total_price': total_price
+ })
+
+ # إعادة حساب إجمالي تكلفة المعدات
+ item_analysis['total_equipment_cost'] = sum(equipment_item['total_price'] for equipment_item in item_analysis['equipment'])
+
+ # تعيين علامة التعديل
+ st.session_state.item_analysis_edited = True
+ st.rerun()
+
+ # عرض إجمالي تكلفة المعدات
+ st.metric("إجمالي تكلفة المعدات", f"{item_analysis['total_equipment_cost']:,.2f} ريال")
+
+ # أزرار التحكم
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("إضافة معدة جديدة", key="add_equipment"):
+ item_analysis['equipment'].append({
+ 'name': 'معدة جديدة',
+ 'unit': 'يوم',
+ 'quantity': 1.0,
+ 'unit_price': 0.0,
+ 'total_price': 0.0
+ })
+ st.session_state.item_analysis_edited = True
st.rerun()
- return
- # الحصول على بنود المشروع الحالي
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
+ def _render_labor_analysis(self, item_analysis):
+ """عرض تحليل العمالة مع إمكانية التعديل"""
+ st.subheader("تحليل العمالة")
- if not project_items:
- st.warning("لا توجد بنود في جدول الكميات. يرجى العودة إلى الخطوة السابقة لإضافة البنود.")
+ if not item_analysis['labor']:
+ st.info("لا توجد عمالة في تحليل هذا البند.")
- col1, col2 = st.columns(2)
- with col1:
- if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq_btn"):
- st.session_state.pricing_step = "boq"
- st.rerun()
+ # إضافة زر لإضافة عمالة جديدة
+ if st.button("إضافة عمالة", key="add_first_labor"):
+ item_analysis['labor'] = [{
+ 'name': 'عامل جديد',
+ 'unit': 'يوم',
+ 'quantity': 1.0,
+ 'unit_price': 0.0,
+ 'total_price': 0.0
+ }]
+ st.session_state.item_analysis_edited = True
+ st.rerun()
return
+
+ # إنشاء DataFrame من قائمة العمالة
+ df = pd.DataFrame(item_analysis['labor'])
+ df.columns = ['العامل', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
- # تعيين النسبة المستهدفة للمحتوى المحلي
- local_content_target = st.slider(
- "النسبة المستهدفة للمحتوى المحلي (%)",
- min_value=0,
- max_value=100,
- value=project_info.get('local_content_target', 40),
- step=5
+ # تحويل الجدول إلى جدول قابل للتعديل
+ edited_df = st.data_editor(
+ df,
+ use_container_width=True,
+ key="edit_labor_table",
+ column_config={
+ "العامل": st.column_config.Column("العامل"),
+ "الوحدة": st.column_config.Column("الوحدة"),
+ "الكمية": st.column_config.NumberColumn(
+ "الكمية",
+ min_value=0.0,
+ format="%.2f",
+ step=0.1,
+ ),
+ "سعر الوحدة": st.column_config.NumberColumn(
+ "سعر الوحدة",
+ min_value=0.0,
+ format="%.2f ريال",
+ step=0.1,
+ ),
+ "السعر الإجمالي": st.column_config.NumberColumn(
+ "السعر الإجمالي",
+ format="%.2f ريال",
+ disabled=True,
+ ),
+ },
+ num_rows="dynamic"
)
- # تحديث النسبة المستهدفة في المشروع
- if local_content_target != project_info.get('local_content_target', 40):
- self.storage.update_project(project_info['id'], {'local_content_target': local_content_target})
+ # تحديث البيانات في item_analysis بناءً على التعديلات
+ if edited_df is not None and not edited_df.equals(df):
+ # حذف جميع العمالة الحالية
+ item_analysis['labor'] = []
- # تحديث حالة الجلسة
- for i, p in enumerate(st.session_state.projects):
- if p['id'] == project_info['id']:
- st.session_state.projects[i]['local_content_target'] = local_content_target
- break
+ # إضافة العمالة المعدلة
+ for i, row in edited_df.iterrows():
+ # حساب السعر الإجمالي
+ total_price = row['الكمية'] * row['سعر الوحدة']
+
+ # إضافة العامل
+ item_analysis['labor'].append({
+ 'name': row['العامل'],
+ 'unit': row['الوحدة'],
+ 'quantity': row['الكمية'],
+ 'unit_price': row['سعر الوحدة'],
+ 'total_price': total_price
+ })
+
+ # إعادة حساب إجمالي تكلفة العمالة
+ item_analysis['total_labor_cost'] = sum(labor_item['total_price'] for labor_item in item_analysis['labor'])
+
+ # تعيين علامة التعديل
+ st.session_state.item_analysis_edited = True
+ st.rerun()
+
+ # عرض إجمالي تكلفة العمالة
+ st.metric("إجمالي تكلفة العمالة", f"{item_analysis['total_labor_cost']:,.2f} ريال")
+
+ # أزرار التحكم
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("إضافة عامل جديد", key="add_labor"):
+ item_analysis['labor'].append({
+ 'name': 'عامل جديد',
+ 'unit': 'يوم',
+ 'quantity': 1.0,
+ 'unit_price': 0.0,
+ 'total_price': 0.0
+ })
+ st.session_state.item_analysis_edited = True
+ st.rerun()
+
+ def _render_subcontractors_analysis(self, item_analysis):
+ """عرض تحليل المقاولين من الباطن مع إمكانية التعديل"""
+ st.subheader("تحليل المقاولين من الباطن")
- # عرض جدول البنود مع معلومات المحتوى المحلي
- st.subheader("تحديث معلومات المحتوى المحلي")
+ # التحقق من وجود مفتاح المقاولين من الباطن في التحليل
+ if 'subcontractors' not in item_analysis:
+ item_analysis['subcontractors'] = []
+ item_analysis['total_subcontractors_cost'] = 0
- # إنشاء DataFrame للعرض
- df = pd.DataFrame([
- {
- 'معرف': item['id'],
- 'الكود': item['code'],
- 'الوصف': item['description'],
- 'نوع المورد': item.get('resource_type', '-'),
- 'مورد محلي': item.get('is_local_supplier', False),
- 'نسبة المحتوى المحلي (%)': item.get('local_content_percentage', 0),
- 'السعر الإجمالي': item['total_price']
- }
- for item in project_items
- ])
+ if not item_analysis['subcontractors']:
+ st.info("لا يوجد مقاولين من الباطن في تحليل هذا البند.")
+
+ # إضافة زر لإضافة مقاول جديد
+ if st.button("إضافة مقاول من الباطن", key="add_first_subcontractor"):
+ item_analysis['subcontractors'] = [{
+ 'name': 'مقاول جديد',
+ 'unit': 'عقد',
+ 'quantity': 1.0,
+ 'unit_price': 0.0,
+ 'total_price': 0.0
+ }]
+ st.session_state.item_analysis_edited = True
+ st.rerun()
+ return
+
+ # إنشاء DataFrame من قائمة المقاولين
+ df = pd.DataFrame(item_analysis['subcontractors'])
+ df.columns = ['المقاول', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
- # عرض الجدول
+ # تحويل الجدول إلى جدول قابل للتعديل
edited_df = st.data_editor(
df,
+ use_container_width=True,
+ key="edit_subcontractors_table",
column_config={
- 'معرف': st.column_config.TextColumn(disabled=True),
- 'الكود': st.column_config.TextColumn(disabled=True),
- 'الوصف': st.column_config.TextColumn(disabled=True),
- 'نوع المورد': st.column_config.TextColumn(disabled=True),
- 'مورد محلي': st.column_config.CheckboxColumn(
- "مورد محلي",
- help="حدد إذا كان المورد محلي"
+ "المقاول": st.column_config.Column("المقاول"),
+ "الوحدة": st.column_config.Column("الوحدة"),
+ "الكمية": st.column_config.NumberColumn(
+ "الكمية",
+ min_value=0.0,
+ format="%.2f",
+ step=0.1,
),
- 'نسبة المحتوى المحلي (%)': st.column_config.NumberColumn(
- "نسبة المحتوى المحلي (%)",
- min_value=0,
- max_value=100,
- step=5,
- format="%d %%"
+ "سعر الوحدة": st.column_config.NumberColumn(
+ "سعر الوحدة",
+ min_value=0.0,
+ format="%.2f ريال",
+ step=0.1,
),
- 'السعر الإجمالي': st.column_config.NumberColumn(
+ "السعر الإجمالي": st.column_config.NumberColumn(
"السعر الإجمالي",
- format="%,.2f ريال",
- disabled=True
- )
+ format="%.2f ريال",
+ disabled=True,
+ ),
},
- hide_index=True,
- use_container_width=True,
- key="local_content_editor"
+ num_rows="dynamic"
)
- # تحديث معلومات المحتوى المحلي
- if st.button("حفظ معلومات المحتوى المحلي", key="save_local_content"):
+ # تحديث البيانات في item_analysis بناءً على التعديلات
+ if edited_df is not None and not edited_df.equals(df):
+ # حذف جميع المقاولين الحاليين
+ item_analysis['subcontractors'] = []
+
+ # إضافة المقاولين المعدلين
for i, row in edited_df.iterrows():
- item_id = row['معرف']
- is_local_supplier = row['مورد محلي']
- local_content_percentage = row['نسبة المحتوى المحلي (%)']
+ # حساب السعر الإجمالي
+ total_price = row['الكمية'] * row['سعر الوحدة']
- # تحديث البند
- self.storage.update_boq_item(item_id, {
- 'is_local_supplier': is_local_supplier,
- 'local_content_percentage': local_content_percentage
+ # إضافة المقاول
+ item_analysis['subcontractors'].append({
+ 'name': row['المقاول'],
+ 'unit': row['الوحدة'],
+ 'quantity': row['الكمية'],
+ 'unit_price': row['سعر الوحدة'],
+ 'total_price': total_price
})
-
- # تحديث حالة الجلسة
- for j, item in enumerate(st.session_state.boq_items):
- if item['id'] == item_id:
- st.session_state.boq_items[j]['is_local_supplier'] = is_local_supplier
- st.session_state.boq_items[j]['local_content_percentage'] = local_content_percentage
- break
-
- st.success("تم حفظ معلومات المحتوى المحلي بنجاح")
-
- # حساب المحتوى المحلي
- if st.button("حساب المحتوى المحلي", key="calculate_local_content"):
- # تحديث بنود المشروع بعد التعديل
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
-
- # حساب ملخص المحتوى المحلي
- local_content_summary = self._calculate_local_content_summary(project_info['id'])
- # تحديث ملخص المحتوى المحلي في المشروع
- self.storage.update_project(project_info['id'], {'local_content_summary': local_content_summary})
-
- # تحديث حالة الجلسة
- for i, p in enumerate(st.session_state.projects):
- if p['id'] == project_info['id']:
- st.session_state.projects[i]['local_content_summary'] = local_content_summary
- break
+ # إعادة حساب إجمالي تكلفة المقاولين
+ item_analysis['total_subcontractors_cost'] = sum(subcontractor['total_price'] for subcontractor in item_analysis['subcontractors'])
- st.success("تم حساب المحتوى المحلي بنجاح")
+ # تعيين علامة التعديل
+ st.session_state.item_analysis_edited = True
st.rerun()
- # عرض ملخص المحتوى المحلي
- st.subheader("ملخص المحتوى المحلي")
-
- # الحصول على ملخص المحتوى المحلي
- local_content_summary = project_info.get('local_content_summary', {
- 'total_percentage': 0,
- 'by_category': {
- 'materials': 0,
- 'labor': 0,
- 'services': 0,
- 'equipment': 0
- }
- })
-
- # عرض النسبة الإجمالية للمحتوى المحلي
- total_percentage = local_content_summary.get('total_percentage', 0)
+ # عرض إجمالي تكلفة المقاولين
+ st.metric("إجمالي تكلفة المقاولين من الباطن", f"{item_analysis['total_subcontractors_cost']:,.2f} ريال")
- # تحديد لون المؤشر حسب النسبة المستهدفة
- if total_percentage >= local_content_target:
- delta_color = "normal" # أخضر
- else:
- delta_color = "inverse" # أحمر
+ # أزرار التحكم
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("إضافة مقاول جديد", key="add_subcontractor"):
+ item_analysis['subcontractors'].append({
+ 'name': 'مقاول جديد',
+ 'unit': 'عقد',
+ 'quantity': 1.0,
+ 'unit_price': 0.0,
+ 'total_price': 0.0
+ })
+ st.session_state.item_analysis_edited = True
+ st.rerun()
+
+ def _setup_arabic_fonts(self):
+ """إعداد الخطوط العربية للرسوم البيانية"""
+ plt.rcParams['font.family'] = 'Arial'
+ plt.rcParams['axes.unicode_minus'] = False
+
+ def _render_cost_summary(self, item_analysis):
+ """عرض ملخص التكلفة"""
+ st.subheader("ملخص التكلفة")
+
+ # إعداد الخطوط العربية
+ self._setup_arabic_fonts()
+
+ # إنشاء بيانات الرسم البياني
+ # استخدام arabic_reshaper و bidi لمعالجة النص العربي
+ labels_ar = ['المواد', 'المعدات', 'العمالة', 'المقاولين من الباطن', 'المصاريف العمومية', 'الربح']
+ labels = [get_display(arabic_reshaper.reshape(label)) for label in labels_ar]
+
+ # التحقق من وجود قيم NaN واستبدالها بأصفار
+ values = [
+ item_analysis['total_materials_cost'] if not np.isnan(item_analysis['total_materials_cost']) else 0,
+ item_analysis['total_equipment_cost'] if not np.isnan(item_analysis['total_equipment_cost']) else 0,
+ item_analysis['total_labor_cost'] if not np.isnan(item_analysis['total_labor_cost']) else 0,
+ item_analysis['total_subcontractors_cost'] if not np.isnan(item_analysis.get('total_subcontractors_cost', 0)) else 0,
+ item_analysis['overhead_cost'] if not np.isnan(item_analysis['overhead_cost']) else 0,
+ item_analysis['profit'] if not np.isnan(item_analysis['profit']) else 0
+ ]
+
+ # التحقق من أن جميع القيم موجبة (أو صفر) لتجنب الأخطاء في الرسم البياني
+ values = [max(0, val) for val in values]
+
+ # إنشاء الرسم البياني
+ fig, ax = plt.subplots(figsize=(10, 6))
- st.metric(
- "النسبة الإجمالية للمحتوى المحلي",
- f"{total_percentage:.1f}%",
- f"{total_percentage - local_content_target:.1f}% من المستهدف",
- delta_color=delta_color
+ # رسم المخطط الدائري
+ wedges, texts, autotexts = ax.pie(
+ values,
+ labels=labels,
+ autopct='%1.1f%%',
+ startangle=90,
+ shadow=True,
)
- # عرض النسب حسب الفئة
- st.subheader("المحتوى المحلي حسب الفئة")
+ # تعديل حجم النص
+ plt.setp(autotexts, size=10, weight="bold")
- by_category = local_content_summary.get('by_category', {})
+ # إضافة العنوان
+ title_ar = 'توزيع تكلفة البند'
+ title = get_display(arabic_reshaper.reshape(title_ar))
+ ax.set_title(title, fontsize=16)
- col1, col2, col3, col4 = st.columns(4)
+ # عرض الرسم البياني
+ st.pyplot(fig)
- with col1:
- st.metric("المواد", f"{by_category.get('materials', 0):.1f}%")
+ # عرض جدول ملخص التكلفة
+ cost_summary = {
+ 'البند': ['المواد', 'المعدات', 'العمالة', 'المقاولين من الباطن', 'المصاريف العمومية', 'الربح', 'الإجمالي'],
+ 'التكلفة': [
+ item_analysis['total_materials_cost'],
+ item_analysis['total_equipment_cost'],
+ item_analysis['total_labor_cost'],
+ item_analysis.get('total_subcontractors_cost', 0),
+ item_analysis['overhead_cost'],
+ item_analysis['profit'],
+ item_analysis['unit_price']
+ ],
+ 'النسبة': [
+ item_analysis['total_materials_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
+ item_analysis['total_equipment_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
+ item_analysis['total_labor_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
+ item_analysis.get('total_subcontractors_cost', 0) / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
+ item_analysis['overhead_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
+ item_analysis['profit'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
+ 100
+ ]
+ }
- with col2:
- st.metric("العمالة", f"{by_category.get('labor', 0):.1f}%")
+ df = pd.DataFrame(cost_summary)
+ df.columns = ['البند', 'التكلفة (ريال)', 'النسبة (%)']
- with col3:
- st.metric("الخدمات", f"{by_category.get('services', 0):.1f}%")
+ # تنسيق الأرقام
+ df['التكلفة (ريال)'] = df['التكلفة (ريال)'].apply(lambda x: f"{x:,.2f}")
+ df['النسبة (%)'] = df['النسبة (%)'].apply(lambda x: f"{x:.2f}%")
- with col4:
- st.metric("المعدات", f"{by_category.get('equipment', 0):.1f}%")
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
- # عرض الرسم البياني
- if by_category:
- fig, ax = plt.subplots(figsize=(10, 6))
- categories = list(by_category.keys())
- percentages = list(by_category.values())
-
- ax.bar(categories, percentages)
- ax.set_ylabel('النسبة المئوية (%)')
- ax.set_title('المحتوى المحلي حسب الفئة')
- ax.axhline(y=local_content_target, color='r', linestyle='-', label=f'المستهدف ({local_content_target}%)')
- ax.legend()
-
- st.pyplot(fig)
-
- # تصدير تقرير المحتوى المحلي
- if st.button("تصدير تقرير المحتوى المحلي", key="export_local_content_report", type="primary"):
- # إنشاء تقرير المحتوى المحلي
- local_content_file = export_local_content_report(
- project_items,
- project_info,
- f"/tmp/local_content_{st.session_state.current_project}.xlsx"
- )
-
- # عرض رابط التنزيل
- href = get_download_link(
- local_content_file,
- "تحميل تقرير المحتوى المحلي",
- "excel"
- )
- st.markdown(href, unsafe_allow_html=True)
+ # إضافة حقل لتعديل المصاريف العمومية
+ st.subheader("تعديل المصاريف العمومية والربح")
- # أزرار التنقل بين الخطوات
col1, col2 = st.columns(2)
with col1:
- if st.button("العودة إلى المراجعة النهائية ➡️", key="back_to_review"):
- st.session_state.pricing_step = "review"
+ new_overhead = st.number_input(
+ "المصاريف العمومية (ريال)",
+ min_value=0.0,
+ value=float(item_analysis['overhead_cost']),
+ step=10.0,
+ format="%.2f",
+ key="edit_overhead_cost"
+ )
+
+ if new_overhead != item_analysis['overhead_cost']:
+ item_analysis['overhead_cost'] = new_overhead
+
+ # إعادة حساب الربح
+ total_cost = (
+ item_analysis['total_materials_cost'] +
+ item_analysis['total_equipment_cost'] +
+ item_analysis['total_labor_cost'] +
+ item_analysis.get('total_subcontractors_cost', 0) +
+ item_analysis['overhead_cost']
+ )
+
+ item_analysis['profit'] = item_analysis['unit_price'] - total_cost
+ st.session_state.item_analysis_edited = True
st.rerun()
-
+
with col2:
- if st.button("متابعة إلى تقييم المخاطر ⬅️", key="continue_to_risk_assessment", type="primary"):
- st.session_state.pricing_step = "risk_assessment"
+ new_profit = st.number_input(
+ "الربح (ريال)",
+ min_value=float(-1000000.0),
+ value=float(item_analysis['profit']),
+ step=10.0,
+ format="%.2f",
+ key="edit_profit"
+ )
+
+ if new_profit != item_analysis['profit']:
+ item_analysis['profit'] = new_profit
+
+ # إعادة حساب سعر الوحدة
+ total_cost = (
+ item_analysis['total_materials_cost'] +
+ item_analysis['total_equipment_cost'] +
+ item_analysis['total_labor_cost'] +
+ item_analysis.get('total_subcontractors_cost', 0) +
+ item_analysis['overhead_cost']
+ )
+
+ item_analysis['unit_price'] = total_cost + item_analysis['profit']
+ st.session_state.item_analysis_edited = True
st.rerun()
- st.markdown('
', unsafe_allow_html=True)
+ def _update_boq_item_from_analysis(self, item_id, item_analysis):
+ """تحديث البند في جدول الكميات بناءً على التحليل"""
+ # حساب السعر الإجمالي الجديد
+ total_cost = (
+ item_analysis['total_materials_cost'] +
+ item_analysis['total_equipment_cost'] +
+ item_analysis['total_labor_cost'] +
+ item_analysis.get('total_subcontractors_cost', 0) +
+ item_analysis['overhead_cost'] +
+ item_analysis['profit']
+ )
+
+ # تحديث سعر الوحدة والسعر الإجمالي في البند
+ for i, item in enumerate(st.session_state.boq_items):
+ if item['id'] == item_id:
+ # حفظ الكمية الأصلية
+ quantity = item['quantity']
+
+ # تحديث سعر الوحدة
+ unit_price = total_cost
+
+ # تحديث البند
+ st.session_state.boq_items[i]['unit_price'] = unit_price
+ st.session_state.boq_items[i]['total_price'] = unit_price * quantity
+ break
- def _calculate_local_content_summary(self, project_id):
- """حساب ملخص المحتوى المحلي للمشروع"""
- # الحصول على بنود المشروع
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == project_id]
+ def _render_cost_analysis(self, project_info):
+ """عرض تحليل التكلفة"""
+ st.subheader("تحليل التكلفة")
- if not project_items:
- return {
- 'total_percentage': 0,
- 'by_category': {
- 'materials': 0,
- 'labor': 0,
- 'services': 0,
- 'equipment': 0
- }
- }
+ # الحصول على بنود المشروع الحالي
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
+ if not project_items:
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
+ return
+
# حساب إجمالي التكلفة
total_cost = sum(item['total_price'] for item in project_items)
- if total_cost == 0:
- return {
- 'total_percentage': 0,
- 'by_category': {
- 'materials': 0,
- 'labor': 0,
- 'services': 0,
- 'equipment': 0
- }
- }
+ # عرض إجمالي التكلفة
+ st.metric("إجمالي تكلفة المشروع", f"{total_cost:,.2f} ريال")
- # حساب المحتوى المحلي الإجمالي
- local_content_value = sum(item['total_price'] * item.get('local_content_percentage', 0) / 100 for item in project_items)
- total_percentage = local_content_value / total_cost * 100
+ # إنشاء DataFrame من بنود المشروع
+ df = pd.DataFrame(project_items)
+ df = df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price']]
+ df.columns = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
- # حساب المحتوى المحلي حسب الفئة
- categories = {}
- for category in ['materials', 'labor', 'services', 'equipment']:
- category_items = [item for item in project_items if item.get('resource_type', '-').lower() == category]
-
- if category_items:
- category_cost = sum(item['total_price'] for item in category_items)
- category_local_content = sum(item['total_price'] * item.get('local_content_percentage', 0) / 100 for item in category_items)
- category_percentage = category_local_content / category_cost * 100 if category_cost > 0 else 0
- categories[category] = category_percentage
- else:
- categories[category] = 0
+ # تنسيق الأسعار
+ df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f} ريال")
+ df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f} ريال")
- # إنشاء ملخص المحتوى المحلي
- summary = {
- 'total_percentage': total_percentage,
- 'by_category': categories
- }
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
- return summary
-
- def _render_risk_assessment_step(self):
- """عرض خطوة تقييم المخاطر"""
- st.markdown('', unsafe_allow_html=True)
- st.markdown('
الخطوة 7: تقييم المخاطر
', unsafe_allow_html=True)
-
- project_info = self._get_current_project_info()
- if not project_info:
- st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
- if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
- st.session_state.pricing_step = "project_info"
- st.rerun()
- return
+ # إنشاء رسم بياني للتكلفة
+ self._setup_arabic_fonts()
+
+ # إنشاء بيانات الرسم البياني
+ fig, ax = plt.subplots(figsize=(10, 6))
+
+ # تحويل الأسعار إلى أرقام
+ prices = [item['total_price'] for item in project_items]
+ labels = [item['description'] for item in project_items]
+
+ # تحويل النص العربي
+ labels = [get_display(arabic_reshaper.reshape(label)) for label in labels]
+
+ # رسم المخطط الشريطي
+ bars = ax.bar(labels, prices)
+
+ # تدوير التسميات
+ plt.xticks(rotation=45, ha='right')
+
+ # إضافة العنوان
+ title_ar = 'توزيع تكلفة المشروع حسب البنود'
+ title = get_display(arabic_reshaper.reshape(title_ar))
+ ax.set_title(title, fontsize=16)
+
+ # إضافة التسميات
+ ax.set_ylabel(get_display(arabic_reshaper.reshape('التكلفة (ريال)')))
+
+ # تنسيق الرسم البياني
+ plt.tight_layout()
+
+ # عرض الرسم البياني
+ st.pyplot(fig)
+
+ def _render_profit_margin(self, project_info):
+ """عرض تحليل الربحية"""
+ st.subheader("تحليل الربحية")
# الحصول على بنود المشروع الحالي
project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
if not project_items:
- st.warning("لا توجد بنود في جدول الكميات. يرجى العودة إلى الخطوة السابقة لإضافة البنود.")
-
- col1, col2 = st.columns(2)
- with col1:
- if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq_btn"):
- st.session_state.pricing_step = "boq"
- st.rerun()
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
return
-
+
# حساب إجمالي التكلفة
total_cost = sum(item['total_price'] for item in project_items)
- # الحصول على مخاطر المشروع الحالي
- project_risks = [risk for risk in st.session_state.risks if risk['project_id'] == st.session_state.current_project]
-
- # استخدام علامات تبويب لتنظيم العرض
- tabs = st.tabs(["ملخص المخاطر", "قائمة المخاطر", "إضافة مخاطرة جديدة", "تقرير المخاطر"])
+ # حساب هامش الربح
+ profit_margin = 0.15 # افتراضي 15%
+ profit = total_cost * profit_margin
+ total_with_profit = total_cost + profit
- with tabs[0]:
- # عرض ملخص المخاطر
- st.subheader("ملخص المخاطر")
-
- # حساب ملخص المخاطر
- risk_summary = self._calculate_risk_summary(project_info['id'], total_cost)
-
- # عرض الإحصائيات الرئيسية
- col1, col2, col3 = st.columns(3)
-
- with col1:
- st.metric("إجمالي عدد المخاطر", risk_summary['total_risks'])
+ # عرض معلومات الربحية
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
+ with col2:
+ st.metric("هامش الربح", f"{profit_margin:.1%}")
+ with col3:
+ st.metric("إجمالي مع الربح", f"{total_with_profit:,.2f} ريال")
- with col2:
- st.metric("احتياطي المخاطر المقترح", f"{risk_summary['risk_contingency']:,.2f} ريال")
+ # إضافة شريط تمرير لتعديل هامش الربح
+ new_profit_margin = st.slider(
+ "تعديل هامش الربح",
+ min_value=0.0,
+ max_value=0.5,
+ value=profit_margin,
+ step=0.01,
+ format="%.2f",
+ key="profit_margin_slider"
+ )
+
+ if new_profit_margin != profit_margin:
+ profit_margin = new_profit_margin
+ profit = total_cost * profit_margin
+ total_with_profit = total_cost + profit
- with col3:
- st.metric("نسبة احتياطي المخاطر", f"{risk_summary['risk_contingency_percentage']:.1f}%")
+ st.metric("إجمالي مع الربح الجديد", f"{total_with_profit:,.2f} ريال")
- # عرض توزيع المخاطر حسب الشدة
- st.subheader("توزيع المخاطر حسب الشدة")
+ # إنشاء رسم بياني للربحية
+ self._setup_arabic_fonts()
+
+ # إنشاء بيانات الرسم البياني
+ fig, ax = plt.subplots(figsize=(10, 6))
+
+ # إنشاء المخطط الدائري
+ labels_ar = ['التكلفة', 'الربح']
+ labels = [get_display(arabic_reshaper.reshape(label)) for label in labels_ar]
+
+ values = [total_cost, profit]
+
+ # رسم المخطط الدائري
+ wedges, texts, autotexts = ax.pie(
+ values,
+ labels=labels,
+ autopct='%1.1f%%',
+ startangle=90,
+ shadow=True,
+ colors=['#ff9999', '#66b3ff']
+ )
+
+ # تعديل حجم النص
+ plt.setp(autotexts, size=10, weight="bold")
+
+ # إضافة العنوان
+ title_ar = 'توزيع التكلفة والربح'
+ title = get_display(arabic_reshaper.reshape(title_ar))
+ ax.set_title(title, fontsize=16)
+
+ # عرض الرسم البياني
+ st.pyplot(fig)
+
+ def _render_pricing_strategies(self, project_info):
+ """عرض استراتيجيات التسعير"""
+ st.subheader("استراتيجيات التسعير")
+
+ # الحصول على بنود المشروع الحالي
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
+
+ if not project_items:
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
+ return
- col1, col2, col3 = st.columns(3)
+ # عرض استراتيجيات التسعير المختلفة
+ st.write("### استراتيجيات التسعير المتاحة")
+
+ # استراتيجية التسعير القياسية
+ st.write("#### التسعير القياسي")
+ st.write("في هذه الاستراتيجية، يتم تسعير جميع البنود بناءً على التكلفة الفعلية مع إضافة هامش ربح موحد.")
+
+ # استراتيجية التسعير الغير متزن
+ st.write("#### التسعير الغير متزن")
+ st.write("في هذه الاستراتيجية، يتم زيادة أسعار البنود المبكرة وتخفيض أسعار البنود المتأخرة للحصول على تدفق نقدي أفضل.")
+
+ # تعديل معاملات التسعير الغير متزن
+ if project_info['pricing_type'] == 'غير متزن':
+ st.write("### تعديل معاملات التسعير الغير متزن")
+ col1, col2 = st.columns(2)
with col1:
- st.metric("مخاطر عالية", risk_summary['high_risks'], delta_color="inverse")
-
- with col2:
- st.metric("مخاطر متوسطة", risk_summary['medium_risks'], delta_color="off")
-
- with col3:
- st.metric("مخاطر منخفضة", risk_summary['low_risks'], delta_color="normal")
-
- # عرض رسم بياني للمخاطر
- if project_risks:
- st.subheader("توزيع المخاطر حسب الفئة")
-
- # تجميع المخاطر حسب الفئة
- category_counts = {}
- for risk in project_risks:
- category = risk['category']
- if category in category_counts:
- category_counts[category] += 1
- else:
- category_counts[category] = 1
-
- # إنشاء DataFrame للرسم البياني
- chart_data = pd.DataFrame({
- "الفئة": list(category_counts.keys()),
- "عدد المخاطر": list(category_counts.values())
- })
-
- # عرض الرسم البياني
- st.bar_chart(chart_data, x="الفئة", y="عدد المخاطر")
-
- # عرض مصفوفة المخاطر
- st.subheader("مصفوفة المخاطر")
-
- # إنشاء مصفوفة المخاطر
- risk_matrix = self._create_risk_matrix(project_risks)
-
- # عرض المصفوفة
- st.dataframe(
- risk_matrix,
- column_config={
- "": st.column_config.TextColumn(""),
- "1": st.column_config.TextColumn("1 - ضئيل"),
- "2": st.column_config.TextColumn("2 - منخفض"),
- "3": st.column_config.TextColumn("3 - متوسط"),
- "4": st.column_config.TextColumn("4 - عالي"),
- "5": st.column_config.TextColumn("5 - حرج")
- },
- hide_index=True
- )
- else:
- st.info("لا توجد مخاطر مسجلة للمشروع. يرجى إضافة مخاطر لعرض التحليل.")
-
- with tabs[1]:
- # عرض قائمة المخاطر
- st.subheader("قائمة المخاطر")
-
- if project_risks:
- # عرض جدول المخاطر
- risk_df = pd.DataFrame([
- {
- 'معرف': risk['id'],
- 'اسم المخاطرة': risk['name'],
- 'الفئة': risk['category'],
- 'الاحتمالية': risk['probability'],
- 'التأثير': risk['impact'],
- 'درجة المخاطرة': risk['risk_score'],
- 'التأثير المالي': risk['cost_impact'],
- 'التأثير على الجدول': risk['schedule_impact'],
- 'الحالة': risk['status']
- }
- for risk in project_risks
- ])
-
- # تنسيق الجدول
- st.dataframe(
- risk_df,
- column_config={
- 'معرف': st.column_config.NumberColumn(disabled=True),
- 'اسم المخاطرة': st.column_config.TextColumn(),
- 'الفئة': st.column_config.TextColumn(),
- 'الاحتمالية': st.column_config.NumberColumn(format="%d"),
- 'التأثير': st.column_config.NumberColumn(format="%d"),
- 'درجة المخاطرة': st.column_config.NumberColumn(format="%d"),
- 'التأثير المالي': st.column_config.NumberColumn(format="%,.2f ريال"),
- 'التأثير على الجدول': st.column_config.NumberColumn(format="%d يوم"),
- 'الحالة': st.column_config.TextColumn()
- },
- hide_index=True,
- use_container_width=True
- )
-
- # عرض تفاصيل المخاطر
- st.subheader("تفاصيل المخاطر")
-
- # اختيار مخاطرة لعرض تفاصيلها
- selected_risk_id = st.selectbox(
- "اختر مخاطرة لعرض التفاصيل",
- options=[risk['id'] for risk in project_risks],
- format_func=lambda x: next((risk['name'] for risk in project_risks if risk['id'] == x), ""),
- key="selected_risk_id"
+ early_factor = st.slider(
+ "معامل البنود المبكرة",
+ min_value=1.0,
+ max_value=1.5,
+ value=st.session_state.unbalanced_pricing_factors['early_items_factor'],
+ step=0.05,
+ format="%.2f",
+ key="early_factor_slider"
)
- # عرض تفاصيل المخاطرة المختارة
- if selected_risk_id:
- selected_risk = next((risk for risk in project_risks if risk['id'] == selected_risk_id), None)
+ if early_factor != st.session_state.unbalanced_pricing_factors['early_items_factor']:
+ st.session_state.unbalanced_pricing_factors['early_items_factor'] = early_factor
- if selected_risk:
- col1, col2 = st.columns(2)
-
- with col1:
- st.markdown(f"**اسم المخاطرة:** {selected_risk['name']}")
- st.markdown(f"**الفئة:** {selected_risk['category']}")
- st.markdown(f"**الاحتمالية:** {selected_risk['probability']}")
- st.markdown(f"**التأثير:** {selected_risk['impact']}")
- st.markdown(f"**درجة المخاطرة:** {selected_risk['risk_score']}")
-
- with col2:
- st.markdown(f"**التأثير المالي:** {selected_risk['cost_impact']:,.2f} ريال")
- st.markdown(f"**التأثير على الجدول الزمني:** {selected_risk['schedule_impact']} يوم")
- st.markdown(f"**الحالة:** {selected_risk['status']}")
- st.markdown(f"**تاريخ التحديث:** {selected_risk.get('updated_at', '-')}")
-
- st.markdown(f"**الوصف:** {selected_risk.get('description', '-')}")
- st.markdown(f"**خطة التخفيف:** {selected_risk.get('mitigation_plan', '-')}")
- st.markdown(f"**خطة الطوارئ:** {selected_risk.get('contingency_plan', '-')}")
-
- # أزرار تعديل وحذف المخاطرة
- col1, col2 = st.columns(2)
-
- with col1:
- if st.button("تعديل المخاطرة", key="edit_risk_btn"):
- st.session_state.edit_risk_id = selected_risk_id
- st.rerun()
-
- with col2:
- if st.button("حذف المخاطرة", key="delete_risk_btn"):
- # حذف المخاطرة
- self.storage.delete_risk(selected_risk_id)
-
- # تحديث حالة الجلسة
- st.session_state.risks = [r for r in st.session_state.risks if r['id'] != selected_risk_id]
-
- # إعادة حساب ملخص المخاطر
- self._calculate_risk_summary(project_info['id'], total_cost)
-
- st.success(f"تم حذف المخاطرة '{selected_risk['name']}' بنجاح")
- st.rerun()
+ with col2:
+ late_factor = st.slider(
+ "معامل البنود المتأخرة",
+ min_value=0.5,
+ max_value=1.0,
+ value=st.session_state.unbalanced_pricing_factors['late_items_factor'],
+ step=0.05,
+ format="%.2f",
+ key="late_factor_slider"
+ )
- # نموذج تعديل المخاطرة
- if 'edit_risk_id' in st.session_state:
- edit_risk_id = st.session_state.edit_risk_id
- edit_risk = next((risk for risk in project_risks if risk['id'] == edit_risk_id), None)
+ if late_factor != st.session_state.unbalanced_pricing_factors['late_items_factor']:
+ st.session_state.unbalanced_pricing_factors['late_items_factor'] = late_factor
- if edit_risk:
- st.subheader(f"تعديل المخاطرة: {edit_risk['name']}")
-
- with st.form(key="edit_risk_form"):
- # حقول النموذج
- risk_name = st.text_input("اسم المخاطرة", value=edit_risk['name'])
- risk_description = st.text_area("وصف المخاطرة", value=edit_risk.get('description', ''))
-
- col1, col2 = st.columns(2)
-
- with col1:
- risk_category = st.selectbox(
- "فئة المخاطرة",
- options=["مالية", "فنية", "تعاقدية", "تنظيمية", "بيئية", "أمنية", "أخرى"],
- index=["مالية", "فنية", "تعاقدية", "تنظيمية", "بيئية", "أمنية", "أخرى"].index(edit_risk['category']) if edit_risk['category'] in ["مالية", "فنية", "تعاقدية", "تنظيمية", "بيئية", "أمنية", "أخرى"] else 0
- )
-
- risk_probability = st.slider(
- "احتمالية حدوث المخاطرة",
- min_value=1,
- max_value=5,
- value=edit_risk['probability'],
- help="1: ضئيلة جداً، 5: شبه مؤكدة"
- )
-
- risk_impact = st.slider(
- "تأثير المخاطرة",
- min_value=1,
- max_value=5,
- value=edit_risk['impact'],
- help="1: ضئيل جداً، 5: كارثي"
- )
-
- with col2:
- risk_cost_impact = st.number_input(
- "التأثير المالي (ريال)",
- min_value=0.0,
- value=float(edit_risk['cost_impact']),
- step=1000.0
- )
-
- risk_schedule_impact = st.number_input(
- "التأثير على الجدول الزمني (أيام)",
- min_value=0,
- value=edit_risk['schedule_impact'],
- step=1
- )
-
- risk_status = st.selectbox(
- "حالة المخاطرة",
- options=["نشطة", "مغلقة", "تم التخفيف", "حدثت"],
- index=["نشطة", "مغلقة", "تم التخفيف", "حدثت"].index(edit_risk['status']) if edit_risk['status'] in ["نشطة", "مغلقة", "تم التخفيف", "حدثت"] else 0
- )
-
- risk_mitigation_plan = st.text_area("خطة التخفيف", value=edit_risk.get('mitigation_plan', ''))
- risk_contingency_plan = st.text_area("خطة الطوارئ", value=edit_risk.get('contingency_plan', ''))
-
- # حساب درجة المخاطرة
- risk_score = risk_probability * risk_impact
- st.info(f"درجة المخاطرة: {risk_score}")
-
- # أزرار الإجراءات
- col1, col2 = st.columns(2)
-
- with col1:
- cancel_button = st.form_submit_button("إلغاء")
-
- with col2:
- submit_button = st.form_submit_button("حفظ التغييرات")
-
- if cancel_button:
- # إلغاء التعديل
- del st.session_state.edit_risk_id
- st.rerun()
-
- if submit_button:
- # التحقق من صحة البيانات
- if not risk_name:
- st.error("يجب إدخال اسم المخاطرة")
- else:
- # تحديث المخاطرة
- updated_risk = {
- 'name': risk_name,
- 'description': risk_description,
- 'category': risk_category,
- 'probability': risk_probability,
- 'impact': risk_impact,
- 'risk_score': risk_score,
- 'cost_impact': risk_cost_impact,
- 'schedule_impact': risk_schedule_impact,
- 'status': risk_status,
- 'mitigation_plan': risk_mitigation_plan,
- 'contingency_plan': risk_contingency_plan
- }
-
- # تحديث المخاطرة في التخزين
- self.storage.update_risk(edit_risk_id, updated_risk)
-
- # تحديث حالة الجلسة
- for i, r in enumerate(st.session_state.risks):
- if r['id'] == edit_risk_id:
- st.session_state.risks[i].update(updated_risk)
- break
-
- # إعادة حساب ملخص المخاطر
- self._calculate_risk_summary(project_info['id'], total_cost)
-
- # إزالة معرف التعديل
- del st.session_state.edit_risk_id
-
- st.success(f"تم تحديث المخاطرة '{risk_name}' بنجاح")
- st.rerun()
- else:
- st.info("لا توجد مخاطر مسجلة للمشروع. يرجى إضافة مخاطر باستخدام النموذج أدناه.")
-
- with tabs[2]:
- # نموذج إضافة مخاطرة جديدة
- st.subheader("إضافة مخاطرة جديدة")
+ # عرض جدول الأسعار المعدلة
+ st.write("### الأسعار المعدلة باستخدام التسعير الغير متزن")
- with st.form(key="add_risk_form"):
- # حقول النموذج
- risk_name = st.text_input("اسم المخاطرة")
- risk_description = st.text_area("وصف المخاطرة")
-
- col1, col2 = st.columns(2)
-
- with col1:
- risk_category = st.selectbox(
- "فئة المخاطرة",
- options=["مالية", "فنية", "تعاقدية", "تنظيمية", "بيئية", "أمنية", "أخرى"]
- )
-
- risk_probability = st.slider(
- "احتمالية حدوث المخاطرة",
- min_value=1,
- max_value=5,
- value=3,
- help="1: ضئيلة جداً، 5: شبه مؤكدة"
- )
-
- risk_impact = st.slider(
- "تأثير المخاطرة",
- min_value=1,
- max_value=5,
- value=3,
- help="1: ضئيل جداً، 5: كارثي"
- )
+ # إنشاء نسخة من بنود المشروع
+ unbalanced_items = []
+
+ # تقسيم البنود إلى مبكرة ومتأخرة
+ num_items = len(project_items)
+ early_items_count = num_items // 3
+ late_items_count = num_items // 3
+
+ for i, item in enumerate(project_items):
+ unbalanced_item = item.copy()
- with col2:
- risk_cost_impact = st.number_input(
- "التأثير المالي (ريال)",
- min_value=0.0,
- value=0.0,
- step=1000.0
- )
-
- risk_schedule_impact = st.number_input(
- "التأثير على الجدول الزمني (أيام)",
- min_value=0,
- value=0,
- step=1
- )
+ if i < early_items_count:
+ # بند مبكر
+ factor = st.session_state.unbalanced_pricing_factors['early_items_factor']
+ unbalanced_item['unbalanced_unit_price'] = item['unit_price'] * factor
+ unbalanced_item['unbalanced_total_price'] = unbalanced_item['unbalanced_unit_price'] * item['quantity']
+ elif i >= num_items - late_items_count:
+ # بند متأخر
+ factor = st.session_state.unbalanced_pricing_factors['late_items_factor']
+ unbalanced_item['unbalanced_unit_price'] = item['unit_price'] * factor
+ unbalanced_item['unbalanced_total_price'] = unbalanced_item['unbalanced_unit_price'] * item['quantity']
+ else:
+ # بند وسطي
+ unbalanced_item['unbalanced_unit_price'] = item['unit_price']
+ unbalanced_item['unbalanced_total_price'] = item['total_price']
- risk_status = st.selectbox(
- "حالة المخاطرة",
- options=["نشطة", "مغلقة", "تم التخفيف", "حدثت"],
- index=0
- )
-
- risk_mitigation_plan = st.text_area("خطة التخفيف")
- risk_contingency_plan = st.text_area("خطة الطوارئ")
-
- # حساب درجة المخاطرة
- risk_score = risk_probability * risk_impact
- st.info(f"درجة المخاطرة: {risk_score}")
+ unbalanced_items.append(unbalanced_item)
- # زر الإرسال
- submit_button = st.form_submit_button("إضافة المخاطرة")
-
- if submit_button:
- # التحقق من صحة البيانات
- if not risk_name:
- st.error("يجب إدخال اسم المخاطرة")
- else:
- # إنشاء معرف فريد
- risk_id = f"risk_{len(st.session_state.risks) + 1}"
-
- # إنشاء كائن المخاطرة
- new_risk = {
- 'id': risk_id,
- 'project_id': project_info['id'],
- 'name': risk_name,
- 'description': risk_description,
- 'category': risk_category,
- 'probability': risk_probability,
- 'impact': risk_impact,
- 'risk_score': risk_score,
- 'cost_impact': risk_cost_impact,
- 'schedule_impact': risk_schedule_impact,
- 'status': risk_status,
- 'mitigation_plan': risk_mitigation_plan,
- 'contingency_plan': risk_contingency_plan
- }
-
- # إضافة المخاطرة إلى التخزين
- added_risk = self.storage.add_risk(new_risk)
-
- # تحديث حالة الجلسة
- st.session_state.risks.append(added_risk)
-
- # إعادة حساب ملخص المخاطر
- self._calculate_risk_summary(project_info['id'], total_cost)
-
- st.success(f"تم إضافة المخاطرة '{risk_name}' بنجاح")
- st.rerun()
-
- with tabs[3]:
- # تقرير المخاطر
- st.subheader("تقرير المخاطر")
+ # إنشاء DataFrame من البنود المعدلة
+ df = pd.DataFrame(unbalanced_items)
+ df = df[['code', 'description', 'unit', 'quantity', 'unit_price', 'unbalanced_unit_price', 'total_price', 'unbalanced_total_price']]
+ df.columns = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة الأصلي', 'سعر الوحدة المعدل', 'السعر الإجمالي الأصلي', 'السعر الإجمالي المعدل']
+
+ # تنسيق الأسعار
+ df['سعر الوحدة الأصلي'] = df['سعر الوحدة الأصلي'].apply(lambda x: f"{x:,.2f} ريال")
+ df['سعر الوحدة المعدل'] = df['سعر الوحدة المعدل'].apply(lambda x: f"{x:,.2f} ريال")
+ df['السعر الإجمالي الأصلي'] = df['السعر الإجمالي الأصلي'].apply(lambda x: f"{x:,.2f} ريال")
+ df['السعر الإجمالي المعدل'] = df['السعر الإجمالي المعدل'].apply(lambda x: f"{x:,.2f} ريال")
+
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
+
+ # حساب إجمالي الأسعار
+ total_original = sum(item['total_price'] for item in project_items)
+ total_unbalanced = sum(item['unbalanced_total_price'] for item in unbalanced_items)
- if project_risks:
- # عرض ملخص التقرير
- risk_summary = project_info.get('risk_summary', self._calculate_risk_summary(project_info['id'], total_cost))
+ col1, col2 = st.columns(2)
+ with col1:
+ st.metric("إجمالي السعر الأصلي", f"{total_original:,.2f} ريال")
+ with col2:
+ st.metric("إجمالي السعر المعدل", f"{total_unbalanced:,.2f} ريال")
- st.markdown(f"""
- ### ملخص تقرير المخاطر لمشروع: {project_info['name']}
+ # زر تطبيق التسعير الغير متزن
+ if st.button("تطبيق التسعير الغير متزن", key="apply_unbalanced_pricing"):
+ for i, item in enumerate(unbalanced_items):
+ for j, boq_item in enumerate(st.session_state.boq_items):
+ if boq_item['id'] == item['id']:
+ st.session_state.boq_items[j]['unit_price'] = item['unbalanced_unit_price']
+ st.session_state.boq_items[j]['total_price'] = item['unbalanced_total_price']
+ break
+
+ st.success("تم تطبيق التسعير الغير متزن بنجاح")
+ st.rerun()
+
+ def _render_export_save_buttons(self, project_info):
+ """عرض أزرار التصدير والحفظ"""
+ st.subheader("تصدير وحفظ التسعير")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("تصدير التسعير كملف Excel", key="export_pricing_excel"):
+ st.info("سيتم تنفيذ هذه الميزة في الإصدار القادم")
- - **إجمالي عدد المخاطر:** {risk_summary['total_risks']}
- - **المخاطر العالية:** {risk_summary['high_risks']}
- - **المخاطر المتوسطة:** {risk_summary['medium_risks']}
- - **المخاطر المنخفضة:** {risk_summary['low_risks']}
- - **إجمالي التأثير المالي:** {risk_summary['total_cost_impact']:,.2f} ريال
- - **إجمالي التأثير على الجدول الزمني:** {risk_summary['total_schedule_impact']} يوم
- - **احتياطي المخاطر المقترح:** {risk_summary['risk_contingency']:,.2f} ريال ({risk_summary['risk_contingency_percentage']:.1f}% من التكلفة الإجمالية)
- """)
+ with col2:
+ if st.button("حفظ التسعير", key="save_pricing"):
+ # حفظ التسعير الحالي
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
- # عرض أعلى المخاطر
- st.subheader("أعلى المخاطر")
+ saved_pricing = {
+ 'project_id': project_info['id'],
+ 'project_name': project_info['name'],
+ 'saved_at': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+ 'items': project_items
+ }
- # ترتيب المخاطر حسب درجة المخاطرة
- sorted_risks = sorted(project_risks, key=lambda x: x['risk_score'], reverse=True)
+ st.session_state.saved_pricing.append(saved_pricing)
- for i, risk in enumerate(sorted_risks[:5]): # عرض أعلى 5 مخاطر
- risk_color = "🔴" if risk['risk_score'] >= 15 else "🟠" if risk['risk_score'] >= 8 else "🟢"
- st.markdown(f"""
- {risk_color} **{risk['name']}** (درجة المخاطرة: {risk['risk_score']})
- - **الفئة:** {risk['category']}
- - **الاحتمالية:** {risk['probability']}/5, **التأثير:** {risk['impact']}/5
- - **التأثير المالي:** {risk['cost_impact']:,.2f} ريال
- - **خطة التخفيف:** {risk.get('mitigation_plan', '-')}
- """)
+ # تحديث حالة المشروع
+ for i, p in enumerate(st.session_state.projects):
+ if p['id'] == project_info['id']:
+ st.session_state.projects[i]['status'] = 'تم التسعير'
+ break
+
+ st.success("تم حفظ التسعير بنجاح")
- # زر تصدير التقرير
- if st.button("تصدير تقرير المخاطر", key="export_risk_report_btn", type="primary"):
- # إنشاء تقرير المخاطر
- risk_report_file = export_risk_report(
- project_risks,
- project_info,
- total_cost,
- f"/tmp/risk_report_{st.session_state.current_project}.xlsx"
- )
-
- # عرض رابط التنزيل
- href = get_download_link(
- risk_report_file,
- "تحميل تقرير المخاطر",
- "excel"
- )
- st.markdown(href, unsafe_allow_html=True)
- else:
- st.info("لا توجد مخاطر مسجلة للمشروع. يرجى إضافة مخاطر لإنشاء التقرير.")
-
- # أزرار التنقل بين الخطوات
- col1, col2 = st.columns(2)
- with col1:
- if st.button("العودة إلى المحتوى المحلي ➡️", key="back_to_local_content"):
- st.session_state.pricing_step = "local_content"
- st.rerun()
-
- with col2:
- if st.button("إنهاء وعودة للصفحة الرئيسية", key="go_to_home", type="primary"):
- st.session_state.pricing_step = "project_info"
- st.rerun()
-
- st.markdown('
', unsafe_allow_html=True)
-
- def _calculate_risk_summary(self, project_id, total_cost):
- """حساب ملخص المخاطر للمشروع"""
- # الحصول على مخاطر المشروع
- project_risks = [risk for risk in st.session_state.risks if risk['project_id'] == project_id]
-
- # حساب عدد المخاطر حسب الشدة
- high_risks = sum(1 for risk in project_risks if risk['risk_score'] >= 15)
- medium_risks = sum(1 for risk in project_risks if 8 <= risk['risk_score'] < 15)
- low_risks = sum(1 for risk in project_risks if risk['risk_score'] < 8)
-
- # حساب إجمالي التأثير المالي والجدول الزمني
- total_cost_impact = sum(risk['cost_impact'] for risk in project_risks)
- total_schedule_impact = sum(risk['schedule_impact'] for risk in project_risks)
-
- # حساب احتياطي المخاطر المقترح
- # استخدام صيغة بسيطة: مجموع (احتمالية * تأثير مالي) لكل المخاطر
- risk_contingency = sum(risk['probability'] / 5 * risk['cost_impact'] for risk in project_risks)
-
- # حساب نسبة احتياطي المخاطر من التكلفة الإجمالية
- risk_contingency_percentage = (risk_contingency / total_cost * 100) if total_cost > 0 else 0
-
- # إنشاء ملخص
- summary = {
- 'total_risks': len(project_risks),
- 'high_risks': high_risks,
- 'medium_risks': medium_risks,
- 'low_risks': low_risks,
- 'total_cost_impact': total_cost_impact,
- 'total_schedule_impact': total_schedule_impact,
- 'risk_contingency': risk_contingency,
- 'risk_contingency_percentage': risk_contingency_percentage
- }
-
- # تحديث ملخص المخاطر في المشروع
- self.storage.update_project(project_id, {'risk_summary': summary})
-
- # تحديث حالة الجلسة
- for i, p in enumerate(st.session_state.projects):
- if p['id'] == project_id:
- st.session_state.projects[i]['risk_summary'] = summary
- break
-
- return summary
-
- def _create_risk_matrix(self, risks):
- """إنشاء مصفوفة المخاطر"""
- # إنشاء مصفوفة ف��رغة
- matrix = {
- "": ["5 - شبه مؤكدة", "4 - محتملة", "3 - ممكنة", "2 - مستبعدة", "1 - نادرة"],
- "1": ["", "", "", "", ""],
- "2": ["", "", "", "", ""],
- "3": ["", "", "", "", ""],
- "4": ["", "", "", "", ""],
- "5": ["", "", "", "", ""]
- }
-
- # تجميع المخاطر حسب الاحتمالية والتأثير
- risk_counts = {}
- for risk in risks:
- probability = risk['probability']
- impact = risk['impact']
- key = f"{probability}_{impact}"
-
- if key in risk_counts:
- risk_counts[key] += 1
- else:
- risk_counts[key] = 1
-
- # ملء المصفوفة
- for key, count in risk_counts.items():
- probability, impact = map(int, key.split('_'))
- row_index = 5 - probability # عكس الترتيب (5 في الأعلى)
- col_key = str(impact)
-
- # تحديد لون الخلية بناءً على درجة المخاطرة
- risk_score = probability * impact
- if risk_score >= 15:
- cell_value = f"🔴 {count}" # أحمر للمخاطر العالية
- elif risk_score >= 8:
- cell_value = f"🟠 {count}" # برتقالي للمخاطر المتوسطة
- else:
- cell_value = f"🟢 {count}" # أخضر للمخاطر المنخفضة
-
- matrix[col_key][row_index] = cell_value
-
- # تحويل إلى DataFrame
- return pd.DataFrame(matrix)
-
# تشغيل التطبيق
if __name__ == "__main__":
app = PricingApp()