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,1056 +1,1802 @@
import streamlit as st
import pandas as pd
import numpy as np
-import random
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 random
import json
import os
-from datetime import datetime
class PricingApp:
+ """وحدة التسعير المتكاملة"""
+
def __init__(self):
- """تهيئة التطبيق"""
- # إضافة CSS للتنسيق
- self._add_custom_css()
-
- # تهيئة حالة الجلسة
+ """تهيئة وحدة التسعير"""
+ # تهيئة البيانات في حالة الجلسة إذا لم تكن موجودة
if 'projects' not in st.session_state:
- st.session_state.projects = self._load_sample_projects()
-
- if 'current_project_id' not in st.session_state:
- st.session_state.current_project_id = None
-
- if 'current_tab' not in st.session_state:
- st.session_state.current_tab = "boq"
+ 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 '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 _add_custom_css(self):
- """إضافة CSS مخصص للتطبيق"""
- st.markdown("""
-
- """, unsafe_allow_html=True)
-
- def _update_total_price(self):
- """تحديث السعر الإجمالي تلقائياً عند تغيير الكمية أو سعر الوحدة"""
- # سيتم استدعاء هذه الدالة عند تغيير قيمة الكمية أو سعر الوحدة
- # يتم تحديث البيانات في الجدول وإعادة حساب المجموع الكلي
- st.session_state.item_analysis_edited = True
-
+
+ def render(self):
+ """طريقة للتوافق مع الواجهة القديمة"""
+ self.run()
+
def run(self):
- """تشغيل التطبيق"""
+ """تشغيل وحدة التسعير"""
st.title("وحدة التسعير المتكاملة")
- # عرض الشريط العلوي
- self._render_top_bar()
+ # عرض زر إنشاء تسعير جديد
+ 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
+
+ # عرض نموذج إنشاء تسعير جديد
+ 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_tab == "boq":
- self._render_bill_of_quantities()
- elif st.session_state.current_tab == "item_analysis":
- self._render_item_price_analysis()
-
- def _render_top_bar(self):
- """عرض الشريط العلوي"""
- cols = st.columns([1, 1, 1, 1, 1])
+ # عرض تفاصيل المشروع الحالي
+ if st.session_state.current_project:
+ project_info = self._get_current_project_info()
+ if project_info:
+ self._render_project_info(project_info)
+
+ # عرض علامات التبويب
+ tab1, tab2, tab3, tab4, tab5 = st.tabs([
+ "جدول الكميات",
+ "تحليل سعر البند",
+ "تحليل التكلفة",
+ "تحليل الربحية",
+ "استراتيجيات التسعير"
+ ])
+
+ with tab1:
+ self._render_bill_of_quantities()
+
+ with tab2:
+ self._render_item_price_analysis()
+
+ with tab3:
+ self._render_cost_analysis(project_info)
+
+ with tab4:
+ self._render_profit_margin(project_info)
+
+ with tab5:
+ self._render_pricing_strategies(project_info)
+
+ # عرض أزرار التصدير والحفظ
+ self._render_export_save_buttons(project_info)
+
+ def _render_new_project_form(self):
+ """عرض نموذج إنشاء مشروع جديد"""
+ st.subheader("إنشاء تسعير جديد")
- with cols[0]:
- if st.button("جدول الكميات", use_container_width=True):
- st.session_state.current_tab = "boq"
+ 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"
+ )
+
+ 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()
+
+ 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 cols[1]:
- if st.button("تحليل الأسعار", use_container_width=True):
- if st.session_state.current_project_id is not None:
- st.session_state.current_tab = "item_analysis"
- st.rerun()
- else:
- st.warning("الرجاء اختيار مشروع أولاً")
+ 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['estimated_value']),
+ format="%f",
+ key="edit_project_value"
+ )
+ deadline = st.date_input(
+ "الموعد النهائي",
+ value=datetime.strptime(project['deadline'], "%Y-%m-%d").date(),
+ key="edit_project_deadline"
+ )
+ status = st.selectbox(
+ "الحالة",
+ ["قيد التسعير", "تم التسعير", "تم التقديم", "فائز", "خاسر"],
+ index=["قيد التسعير", "تم التسعير", "تم التقديم", "فائز", "خاسر"].index(project['status']),
+ key="edit_project_status"
+ )
+ pricing_type = st.selectbox(
+ "نوع التسعير",
+ ["قياسي", "غير متزن", "موجه ربحية", "تنافسي", "استراتيجي"],
+ index=["قياسي", "غير متزن", "موجه ربحية", "تنافسي", "استراتيجي"].index(project.get('pricing_type', 'قياسي')),
+ key="edit_project_pricing_type"
+ )
+
+ 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.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("قائمة المشاريع")
- with cols[4]:
- if st.button("إنشاء مشروع جديد", use_container_width=True):
- self._create_new_project()
-
- def _create_new_project(self):
- """إنشاء مشروع جديد"""
- project_id = f"proj_{len(st.session_state.projects) + 1}"
- new_project = {
- "id": project_id,
- "name": f"مشروع رقم {len(st.session_state.projects) + 1}",
- "client": "عميل قياسي",
- "date": datetime.now().strftime("%Y-%m-%d"),
- "budget": 1000000.00,
- "items": []
- }
- st.session_state.projects[project_id] = new_project
- st.session_state.current_project_id = project_id
- st.session_state.current_tab = "boq"
- st.rerun()
-
- def _render_bill_of_quantities(self):
- """عرض جدول الكميات"""
- st.header("جدول الكميات")
-
- # اختيار المشروع
- project_options = {proj["name"]: proj_id for proj_id, proj in st.session_state.projects.items()}
- selected_project_name = st.selectbox(
- "اختر المشروع",
- options=list(project_options.keys()),
- index=0 if st.session_state.current_project_id is None else list(project_options.keys()).index(st.session_state.projects[st.session_state.current_project_id]["name"])
- )
+ if not st.session_state.projects:
+ st.info("لا توجد مشاريع. قم بإنشاء مشروع جديد للبدء.")
+ return
+
+ # إنشاء 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 = ['الرقم', 'اسم المشروع', 'العميل', 'القيمة التقديرية', 'الموعد النهائي', 'الحالة', 'نوع التسعير']
+
+ # تنسيق القيمة التقديرية
+ df['القيمة التقديرية'] = df['القيمة التقديرية'].apply(lambda x: f"{x:,.2f} ريال")
+
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
+
+ # اختيار المشروع
+ 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
+
+ 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 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("لا توجد مشاريع. قم بإنشاء مشروع جديد للبدء.")
+
+ 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
- st.session_state.current_project_id = project_options[selected_project_name]
- current_project = st.session_state.projects[st.session_state.current_project_id]
+ def _render_project_info(self, project):
+ """عرض معلومات المشروع"""
+ st.header(f"تسعير: {project['name']}")
- # عرض معلومات المشروع
- col1, col2, col3 = st.columns(3)
+ col1, col2, col3, col4 = st.columns(4)
with col1:
- st.write(f"**العميل:** {current_project['client']}")
+ st.metric("العميل", project['client'])
with col2:
- st.write(f"**التاريخ:** {current_project['date']}")
+ st.metric("القيمة التقديرية", f"{project['estimated_value']:,.2f} ريال")
with col3:
- st.write(f"**الميزانية:** {current_project['budget']:,.2f} ريال")
+ st.metric("الموعد النهائي", project['deadline'])
+ with col4:
+ st.metric("نوع التسعير", project['pricing_type'])
+
+ def _render_bill_of_quantities(self):
+ """عرض جدول الكميات"""
+ st.subheader("جدول الكميات")
- # إضافة بند جديد
- with st.expander("إضافة بند جديد"):
- with st.form("add_item_form"):
- col1, col2 = st.columns(2)
- with col1:
- new_item_name = st.text_input("اسم البند")
- new_item_unit = st.text_input("الوحدة")
- with col2:
- new_item_quantity = st.number_input("الكمية", min_value=0.0, step=0.1)
- new_item_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1)
+ # زر إضافة بند جديد
+ 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
- submitted = st.form_submit_button("إضافة البند")
- if submitted and new_item_name and new_item_unit:
- new_item = {
- "id": f"item_{len(current_project['items']) + 1}",
- "name": new_item_name,
- "unit": new_item_unit,
- "quantity": float(new_item_quantity),
- "unit_price": float(new_item_price),
- "total_price": float(new_item_quantity) * float(new_item_price),
- "materials": [],
- "labor": [],
- "equipment": [],
- "subcontractors": []
- }
- current_project["items"].append(new_item)
- st.success("تمت إضافة البند بنجاح")
- st.rerun()
+ 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 current_project["items"]:
- items_df = pd.DataFrame([
- {
- "م": i + 1,
- "اسم البند": item["name"],
- "الوحدة": item["unit"],
- "الكمية": item["quantity"],
- "سعر الوحدة (ريال)": item["unit_price"],
- "السعر الإجمالي (ريال)": item["total_price"],
- "تحليل": f'تحليل'
- }
- for i, item in enumerate(current_project["items"])
- ])
-
- # إضافة صف المجموع
- total_price = sum(item["total_price"] for item in current_project["items"])
- total_row = pd.DataFrame([{
- "م": "",
- "اسم البند": "المجموع",
- "الوحدة": "",
- "الكمية": "",
- "سعر الوحدة (ريال)": "",
- "السعر الإجمالي (ريال)": total_price,
- "تحليل": ""
- }])
-
- items_df = pd.concat([items_df, total_row], ignore_index=True)
-
- st.write(items_df.to_html(escape=False, index=False), unsafe_allow_html=True)
-
- # معالجة النقر على رابط التحليل
- for i in range(len(current_project["items"])):
- if st.button(f"تحليل البند {i+1}", key=f"analyze_btn_{i}", use_container_width=True):
- st.session_state.current_item_index = i
- st.session_state.current_tab = "item_analysis"
- st.rerun()
- else:
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود جديدة.")
-
- def _render_item_price_analysis(self):
- """عرض تحليل سعر البند"""
- if st.session_state.current_project_id is None or "current_item_index" not in st.session_state:
- st.warning("الرجاء اختيار مشروع وبند أولاً")
+ if not project_items:
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
return
+
+ # إنشاء DataFrame من بنود المشروع
+ df = pd.DataFrame(project_items)
+ df = df[['id', 'code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
- current_project = st.session_state.projects[st.session_state.current_project_id]
- current_item = current_project["items"][st.session_state.current_item_index]
+ # تحويل الجدول إلى جدول قابل للتعديل
+ 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"
+ )
- st.header(f"تحليل سعر البند: {current_item['name']}")
+ # تحديث البيانات في حالة التعديل
+ 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()
- # معلومات البند
- col1, col2, col3, col4 = st.columns(4)
+ # حساب المجموع الكلي
+ total_price = sum(item['total_price'] for item in project_items)
+ st.metric("إجمالي جدول الكميات", f"{total_price:,.2f} ريال")
+
+ # أزرار التصدير والتعديل
+ col1, col2 = st.columns(2)
with col1:
- st.write(f"**الوحدة:** {current_item['unit']}")
- with col2:
- st.write(f"**الكمية:** {current_item['quantity']}")
- with col3:
- st.write(f"**سعر الوحدة:** {current_item['unit_price']} ريال")
- with col4:
- st.write(f"**السعر الإجمالي:** {current_item['total_price']} ريال")
-
- # زر حفظ التغييرات
- if st.session_state.item_analysis_edited:
- if st.button("حفظ التغييرات", type="primary", use_container_width=True):
- # تحديث السعر الإجمالي للبند
- materials_total = sum(material.get("total_price", 0) for material in current_item["materials"])
- labor_total = sum(labor.get("total_price", 0) for labor in current_item["labor"])
- equipment_total = sum(equipment.get("total_price", 0) for equipment in current_item["equipment"])
- subcontractors_total = sum(sub.get("total_price", 0) for sub in current_item["subcontractors"])
+ 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 = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي', 'نوع المورد']
- direct_cost = materials_total + labor_total + equipment_total + subcontractors_total
- current_item["unit_price"] = direct_cost / current_item["quantity"] if current_item["quantity"] > 0 else 0
- current_item["total_price"] = current_item["unit_price"] * current_item["quantity"]
+ csv = export_df.to_csv(index=False)
+ b64 = base64.b64encode(csv.encode()).decode()
+ href = f'تحميل CSV'
+ st.markdown(href, unsafe_allow_html=True)
- st.session_state.item_analysis_edited = False
- st.success("تم حفظ التغييرات بنجاح")
- st.rerun()
-
- # تحليل المواد
- st.subheader("تحليل المواد")
-
- # إضافة مادة جديدة
- with st.expander("إضافة مادة جديدة"):
- with st.form("add_material_form"):
- col1, col2 = st.columns(2)
- with col1:
- new_material_name = st.text_input("اسم المادة")
- new_material_unit = st.text_input("الوحدة")
- with col2:
- new_material_quantity = st.number_input("الكمية", min_value=0.0, step=0.1)
- new_material_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1)
+ with col2:
+ 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"
+ )
- submitted = st.form_submit_button("إضافة المادة")
- if submitted and new_material_name and new_material_unit:
- new_material = {
- "id": f"material_{len(current_item['materials']) + 1}",
- "name": new_material_name,
- "unit": new_material_unit,
- "quantity": float(new_material_quantity),
- "unit_price": float(new_material_price),
- "total_price": float(new_material_quantity) * float(new_material_price)
- }
- current_item["materials"].append(new_material)
- st.session_state.item_analysis_edited = True
- st.success("تمت إضافة المادة بنجاح")
+ 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("إضافة بند جديد")
- # عرض جدول المواد
- if current_item["materials"]:
- # تحويل البيانات إلى DataFrame
- materials_data = []
- for i, material in enumerate(current_item["materials"]):
- materials_data.append({
- "م": i + 1,
- "اسم المادة": material["name"],
- "الوحدة": material["unit"],
- "الكمية": material["quantity"],
- "سعر الوحدة (ريال)": material["unit_price"],
- "السعر الإجمالي (ريال)": material["total_price"],
- "الإجراءات": f' '
- })
+ 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")
- materials_df = pd.DataFrame(materials_data)
-
- # إضافة صف المجموع
- materials_total = sum(material["total_price"] for material in current_item["materials"])
- materials_df = materials_df.append({
- "م": "",
- "اسم المادة": "المجموع",
- "الوحدة": "",
- "الكمية": "",
- "سعر الوحدة (ريال)": "",
- "السعر الإجمالي (ريال)": materials_total,
- "الإجراءات": ""
- }, ignore_index=True)
-
- # عرض الجدول القابل للتعديل
- edited_materials_df = st.data_editor(
- materials_df,
- column_config={
- "م": st.column_config.NumberColumn("م", width="small"),
- "اسم المادة": st.column_config.TextColumn("اسم المادة"),
- "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
- "الكمية": st.column_config.NumberColumn("الكمية", format="%.2f", width="small"),
- "سعر الوحدة (ريال)": st.column_config.NumberColumn("سعر الوحدة (ريال)", format="%.2f"),
- "السعر الإجمالي (ريال)": st.column_config.NumberColumn("السعر الإجمالي (ريال)", format="%.2f"),
- "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
- },
- hide_index=True,
- key="materials_table",
- on_change=self._update_total_price,
- disabled=["م", "اسم المادة", "الوحدة", "السعر الإجمالي (��يال)", "الإجراءات"]
- )
+ 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} ريال")
- # تحديث البيانات بعد التعديل
- for i, material in enumerate(current_item["materials"]):
- if i < len(edited_materials_df) - 1: # تجاهل صف المجموع
- material["quantity"] = edited_materials_df.iloc[i]["الكمية"]
- material["unit_price"] = edited_materials_df.iloc[i]["سعر الوحدة (ريال)"]
- material["total_price"] = material["quantity"] * material["unit_price"]
+ 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()
- # معالجة أزرار التعديل والحذف
- for i in range(len(current_item["materials"])):
- col1, col2 = st.columns([1, 1])
- with col1:
- if st.button(f"تعديل المادة {i+1}", key=f"edit_material_btn_{i}"):
- st.session_state[f"edit_material_{i}"] = True
+ 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
- with col2:
- if st.button(f"حذف المادة {i+1}", key=f"delete_material_btn_{i}"):
- st.session_state[f"delete_material_{i}"] = True
+ if not item:
+ st.session_state.show_edit_boq_item_form = False
+ st.rerun()
+ return
+
+ st.subheader(f"تعديل البند: {item['description']}")
+
+ 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:
+ 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 st.session_state.get(f"edit_material_{i}", False):
- with st.form(f"edit_material_form_{i}"):
- material = current_item["materials"][i]
- col1, col2 = st.columns(2)
- with col1:
- material_name = st.text_input("اسم المادة", value=material["name"])
- material_unit = st.text_input("الوحدة", value=material["unit"])
- with col2:
- material_quantity = st.number_input("الكمية", min_value=0.0, step=0.1, value=material["quantity"])
- material_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1, value=material["unit_price"])
+ 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
- col1, col2 = st.columns(2)
- with col1:
- if st.form_submit_button("حفظ التعديلات"):
- material["name"] = material_name
- material["unit"] = material_unit
- material["quantity"] = float(material_quantity)
- material["unit_price"] = float(material_price)
- material["total_price"] = material["quantity"] * material["unit_price"]
- st.session_state.item_analysis_edited = True
- st.session_state[f"edit_material_{i}"] = False
- st.rerun()
- with col2:
- if st.form_submit_button("إلغاء"):
- st.session_state[f"edit_material_{i}"] = False
- st.rerun()
+ st.session_state.show_edit_boq_item_form = False
+ st.rerun()
- # تأكيد الحذف
- if st.session_state.get(f"delete_material_{i}", False):
- st.warning(f"هل أنت متأكد من حذف المادة: {current_item['materials'][i]['name']}؟")
- col1, col2 = st.columns(2)
- with col1:
- if st.button("نعم، حذف", key=f"confirm_delete_material_{i}"):
- current_item["materials"].pop(i)
- st.session_state.item_analysis_edited = True
- st.session_state[f"delete_material_{i}"] = False
- st.rerun()
- with col2:
- if st.button("إلغاء", key=f"cancel_delete_material_{i}"):
- st.session_state[f"delete_material_{i}"] = False
- st.rerun()
-
- # عرض المجموع الكلي للمواد
- st.markdown(f"
إجمالي تكلفة المواد: {materials_total:,.2f} ريال
", unsafe_allow_html=True)
- else:
- st.info("لا توجد مواد مضافة لهذا البند")
+ 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("سحب من الموارد المسجلة")
- # تحليل العمالة
- st.subheader("تحليل العمالة")
+ # اختيار نوع المورد
+ resource_type = st.selectbox(
+ "نوع المورد",
+ ["المواد", "العمالة", "المعدات", "المقاولين من الباطن"],
+ index=["المواد", "العمالة", "المعدات", "المقاولين من الباطن"].index(st.session_state.selected_resource_type),
+ key="resource_type_selector"
+ )
- # إضافة عمالة جديدة
- with st.expander("إضافة عمالة جديدة"):
- with st.form("add_labor_form"):
- col1, col2 = st.columns(2)
- with col1:
- new_labor_name = st.text_input("نوع العمالة")
- new_labor_unit = st.text_input("الوحدة", value="يوم")
- with col2:
- new_labor_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5)
- new_labor_price = st.number_input("الأجر اليومي (ريال)", min_value=0.0, step=10.0)
-
- submitted = st.form_submit_button("إضافة العمالة")
- if submitted and new_labor_name:
- new_labor = {
- "id": f"labor_{len(current_item['labor']) + 1}",
- "name": new_labor_name,
- "unit": new_labor_unit,
- "quantity": float(new_labor_quantity),
- "unit_price": float(new_labor_price),
- "total_price": float(new_labor_quantity) * float(new_labor_price)
- }
- current_item["labor"].append(new_labor)
- st.session_state.item_analysis_edited = True
- st.success("تمت إضافة العمالة بنجاح")
+ if resource_type != st.session_state.selected_resource_type:
+ st.session_state.selected_resource_type = resource_type
+ st.rerun()
+
+ # الحصول على الموارد المناسبة
+ 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
+
+ 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()
-
- # عرض جدول العمالة
- if current_item["labor"]:
- # تحويل البيانات إلى DataFrame
- labor_data = []
- for i, labor in enumerate(current_item["labor"]):
- labor_data.append({
- "م": i + 1,
- "نوع العمالة": labor["name"],
- "الوحدة": labor["unit"],
- "عدد الأيام": labor["quantity"],
- "الأجر اليومي (ريال)": labor["unit_price"],
- "الإجمالي (ريال)": labor["total_price"],
- "الإجراءات": f' '
- })
+ return
- labor_df = pd.DataFrame(labor_data)
-
- # إضافة صف المجموع
- labor_total = sum(labor["total_price"] for labor in current_item["labor"])
- labor_df = labor_df.append({
- "م": "",
- "نوع العمالة": "المجموع",
- "الوحدة": "",
- "عدد الأيام": "",
- "الأجر اليومي (ريال)": "",
- "الإجمالي (ريال)": labor_total,
- "الإجراءات": ""
- }, ignore_index=True)
-
- # عرض الجدول القابل للتعديل
- edited_labor_df = st.data_editor(
- labor_df,
- column_config={
- "م": st.column_config.NumberColumn("م", width="small"),
- "نوع العمالة": st.column_config.TextColumn("نوع العمالة"),
- "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
- "عدد الأيام": st.column_config.NumberColumn("عدد الأيام", format="%.1f", width="small"),
- "الأجر اليومي (ريال)": st.column_config.NumberColumn("الأجر اليومي (ريال)", format="%.2f"),
- "الإجمالي (ريال)": st.column_config.NumberColumn("الإجمالي (ريال)", format="%.2f"),
- "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
- },
- hide_index=True,
- key="labor_table",
- on_change=self._update_total_price,
- disabled=["م", "نوع العمالة", "الوحدة", "الإجمالي (ريال)", "الإجراءات"]
- )
+ # إنشاء 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 = ['الرقم', 'الاسم', 'الفئة', 'الوحدة', 'السعر']
- # تحديث البيانات بعد التعديل
- for i, labor in enumerate(current_item["labor"]):
- if i < len(edited_labor_df) - 1: # تجاهل صف المجموع
- labor["quantity"] = edited_labor_df.iloc[i]["عدد الأيام"]
- labor["unit_price"] = edited_labor_df.iloc[i]["الأجر اليومي (ريال)"]
- labor["total_price"] = labor["quantity"] * labor["unit_price"]
+ # تنسيق السعر
+ df['السعر'] = df['السعر'].apply(lambda x: f"{x:,.2f} ريال")
- # معالجة أزرار التعديل والحذف
- for i in range(len(current_item["labor"])):
- col1, col2 = st.columns([1, 1])
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
+
+ with st.form(key="add_from_resource_form"):
+ col1, col2 = st.columns(2)
with col1:
- if st.button(f"تعديل العمالة {i+1}", key=f"edit_labor_btn_{i}"):
- st.session_state[f"edit_labor_{i}"] = True
-
+ 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:
- if st.button(f"حذف العمالة {i+1}", key=f"delete_labor_btn_{i}"):
- st.session_state[f"delete_labor_{i}"] = True
+ 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 = {
+ "المواد": "مواد",
+ "العمالة": "عمالة",
+ "المعدات": "معدات",
+ "المقاولين من الباطن": "مقاولين من الباطن"
+ }
- # نموذج التعديل
- if st.session_state.get(f"edit_labor_{i}", False):
- with st.form(f"edit_labor_form_{i}"):
- labor = current_item["labor"][i]
- col1, col2 = st.columns(2)
- with col1:
- labor_name = st.text_input("نوع العمالة", value=labor["name"])
- labor_unit = st.text_input("الوحدة", value=labor["unit"])
- with col2:
- labor_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, value=labor["quantity"])
- labor_price = st.number_input("الأجر اليومي (ريال)", min_value=0.0, step=10.0, value=labor["unit_price"])
-
- col1, col2 = st.columns(2)
- with col1:
- if st.form_submit_button("حفظ التعديلات"):
- labor["name"] = labor_name
- labor["unit"] = labor_unit
- labor["quantity"] = float(labor_quantity)
- labor["unit_price"] = float(labor_price)
- labor["total_price"] = labor["quantity"] * labor["unit_price"]
- st.session_state.item_analysis_edited = True
- st.session_state[f"edit_labor_{i}"] = False
- st.rerun()
- with col2:
- if st.form_submit_button("إلغاء"):
- st.session_state[f"edit_labor_{i}"] = False
- st.rerun()
+ # إنشاء كود فريد
+ 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()
- # تأكيد الحذف
- if st.session_state.get(f"delete_labor_{i}", False):
- st.warning(f"هل أنت متأكد من حذف العمالة: {current_item['labor'][i]['name']}؟")
- col1, col2 = st.columns(2)
- with col1:
- if st.button("نعم، حذف", key=f"confirm_delete_labor_{i}"):
- current_item["labor"].pop(i)
- st.session_state.item_analysis_edited = True
- st.session_state[f"delete_labor_{i}"] = False
- st.rerun()
- with col2:
- if st.button("إلغاء", key=f"cancel_delete_labor_{i}"):
- st.session_state[f"delete_labor_{i}"] = False
- st.rerun()
-
- # عرض المجموع الكلي للعمالة
- st.markdown(f"إجمالي تكلفة العمالة: {labor_total:,.2f} ريال
", unsafe_allow_html=True)
+ if cancel_button:
+ st.session_state.show_resource_selector = False
+ st.rerun()
else:
- st.info("لا توجد عمالة مضافة لهذا البند")
+ st.error("تنسيق بيانات الموارد غير صحيح. يرجى التأكد من وجود الحقول المطلوبة.")
+
+ if st.button("إلغاء", key="cancel_resource_selector_error"):
+ st.session_state.show_resource_selector = False
+ st.rerun()
+
+ def _render_item_price_analysis(self):
+ """عرض تحليل سعر البند مع إمكانية التعديل والحفظ"""
+ st.subheader("تحليل سعر البند")
- # تحليل المعدات
- st.subheader("تحليل المعدات")
+ # الحصول على بنود المشروع الحالي
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
- # إضافة معدات جديدة
- with st.expander("إضافة معدات جديدة"):
- with st.form("add_equipment_form"):
- col1, col2 = st.columns(2)
- with col1:
- new_equipment_name = st.text_input("نوع المعدات")
- new_equipment_unit = st.text_input("الوحدة", value="يوم")
- with col2:
- new_equipment_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, key="new_equipment_quantity")
- new_equipment_price = st.number_input("التكلفة اليومية (ريال)", min_value=0.0, step=10.0, key="new_equipment_price")
+ if not project_items:
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
+ return
+
+ # اختيار البند للتحليل
+ 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} ريال")
+
+ # إنشاء تحليل سعر البند
+ item_analysis = self._generate_item_analysis(selected_item)
+
+ # عرض تحليل المواد مع إمكانية التعديل
+ self._render_materials_analysis(item_analysis)
+
+ # عرض تحليل المعدات مع إمكانية التعديل
+ self._render_equipment_analysis(item_analysis)
+
+ # عرض تحليل العمالة مع إمكانية التعديل
+ self._render_labor_analysis(item_analysis)
+
+ # عرض تحليل المقاولين من الباطن مع إمكانية التعديل
+ self._render_subcontractors_analysis(item_analysis)
+
+ # عرض ملخص التكلفة
+ self._render_cost_summary(item_analysis)
+
+ # زر حفظ التغييرات في جدول الكميات الرئيسي
+ 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()
+
+ def _generate_item_analysis(self, item):
+ """إنشاء تحليل سعر البند"""
+ # في هذه النسخة التجريبية، سنقوم بإنشاء بيانات عشوائية للتحليل
+ # في النسخة النهائية، يجب استخدام بيانات حقيقية من قاعدة البيانات
+
+ unit_price = item['unit_price']
+
+ # تقسيم سعر الوحدة إلى مكوناته
+ 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)
+
+ # إنشاء قائمة المواد
+ 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
+ }
+ ]
+
+ # إنشاء قائمة المعدات
+ 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)
+ }
+ ]
+
+ # إنشاء قائمة العمالة
+ 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)
+ }
+ ]
+
+ # إنشاء قائمة المقاولين من الباطن
+ subcontractors = [
+ {
+ '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
+ }
+ ]
+
+ # حساب إجمالي التكاليف
+ 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
+ }
+
+ def _render_materials_analysis(self, item_analysis):
+ """عرض تحليل المواد مع إمكانية التعديل"""
+ st.subheader("تحليل المواد")
+
+ if not item_analysis['materials']:
+ st.info("لا توجد مواد في تحليل هذا البند.")
+
+ # إضافة زر لإضافة مواد جديدة
+ 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
+
+ # إنشاء 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'] = []
+
+ # إضافة المواد المعدلة
+ for i, row in edited_df.iterrows():
+ # حساب السعر الإجمالي
+ total_price = row['الكمية'] * row['سعر الوحدة']
- submitted = st.form_submit_button("إضافة المعدات")
- if submitted and new_equipment_name:
- new_equipment = {
- "id": f"equipment_{len(current_item['equipment']) + 1}",
- "name": new_equipment_name,
- "unit": new_equipment_unit,
- "quantity": float(new_equipment_quantity),
- "unit_price": float(new_equipment_price),
- "total_price": float(new_equipment_quantity) * float(new_equipment_price)
- }
- current_item["equipment"].append(new_equipment)
- st.session_state.item_analysis_edited = True
- st.success("تمت إضافة المعدات بنجاح")
- st.rerun()
+ # إضافة المادة
+ item_analysis['materials'].append({
+ 'name': row['المادة'],
+ 'unit': row['الوحدة'],
+ 'quantity': row['الكمية'],
+ 'unit_price': row['سعر الوحدة'],
+ 'total_price': total_price
+ })
+
+ # إعادة حساب إجمالي تكلفة المواد
+ item_analysis['total_materials_cost'] = sum(material['total_price'] for material in item_analysis['materials'])
+
+ # تعيين علامة التعديل
+ st.session_state.item_analysis_edited = True
+ st.rerun()
- # عرض جدول المعدات
- if current_item["equipment"]:
- # تحويل البيانات إلى DataFrame
- equipment_data = []
- for i, equipment in enumerate(current_item["equipment"]):
- equipment_data.append({
- "م": i + 1,
- "نوع المعدات": equipment["name"],
- "الوحدة": equipment["unit"],
- "عدد الأيام": equipment["quantity"],
- "التكلفة اليومية (ريال)": equipment["unit_price"],
- "الإجمالي (ريال)": equipment["total_price"],
- "الإجراءات": f' '
+ # عرض إجمالي تكلفة المواد
+ st.metric("إجمالي تكلفة المواد", f"{item_analysis['total_materials_cost']:,.2f} ريال")
+
+ # أزرار التحكم
+ col1, col2 = st.columns(2)
+ with col1:
+ 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()
+
+ def _render_equipment_analysis(self, item_analysis):
+ """عرض تحليل المعدات مع إمكانية التعديل"""
+ st.subheader("تحليل المعدات")
+
+ if not item_analysis['equipment']:
+ st.info("لا توجد معدات في تحليل هذا البند.")
- equipment_df = pd.DataFrame(equipment_data)
-
- # إضافة صف المجموع
- equipment_total = sum(equipment["total_price"] for equipment in current_item["equipment"])
- equipment_df = equipment_df.append({
- "م": "",
- "نوع المعدات": "المجموع",
- "الوحدة": "",
- "عدد الأيام": "",
- "التكلفة اليومية (ريال)": "",
- "الإجمالي (ريال)": equipment_total,
- "الإجراءات": ""
- }, ignore_index=True)
-
- # عرض الجدول القابل للتعديل
- edited_equipment_df = st.data_editor(
- equipment_df,
- column_config={
- "م": st.column_config.NumberColumn("م", width="small"),
- "نوع المعدات": st.column_config.TextColumn("نوع المعدات"),
- "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
- "عدد الأيام": st.column_config.NumberColumn("عدد الأيام", format="%.1f", width="small"),
- "التكلفة اليومية (ريال)": st.column_config.NumberColumn("التكلفة اليومية (ريال)", format="%.2f"),
- "الإجمالي (ريال)": st.column_config.NumberColumn("الإجمالي (ريال)", format="%.2f"),
- "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
- },
- hide_index=True,
- key="equipment_table",
- on_change=self._update_total_price,
- disabled=["م", "نوع المعدات", "الوحدة", "الإجمالي (ريال)", "الإجراءات"]
- )
+ # إضافة زر لإضافة معدات جديدة
+ 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
- # تحديث البيانات بعد التعديل
- for i, equipment in enumerate(current_item["equipment"]):
- if i < len(edited_equipment_df) - 1: # تجاهل صف المجموع
- equipment["quantity"] = edited_equipment_df.iloc[i]["عدد الأيام"]
- equipment["unit_price"] = edited_equipment_df.iloc[i]["التكلفة اليومية (ريال)"]
- equipment["total_price"] = equipment["quantity"] * equipment["unit_price"]
+ # إنشاء DataFrame من قائمة المعدات
+ df = pd.DataFrame(item_analysis['equipment'])
+ df.columns = ['المعدة', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
+
+ # تحويل الجدول إلى جدول قابل للتعديل
+ 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 in range(len(current_item["equipment"])):
- col1, col2 = st.columns([1, 1])
- with col1:
- if st.button(f"تعديل المعدات {i+1}", key=f"edit_equipment_btn_{i}"):
- st.session_state[f"edit_equipment_{i}"] = True
-
- with col2:
- if st.button(f"حذف المعدات {i+1}", key=f"delete_equipment_btn_{i}"):
- st.session_state[f"delete_equipment_{i}"] = True
+ # إضافة المعدات المعدلة
+ for i, row in edited_df.iterrows():
+ # حساب السعر الإجمالي
+ total_price = row['الكمية'] * row['سعر الوحدة']
- # نموذج التعديل
- if st.session_state.get(f"edit_equipment_{i}", False):
- with st.form(f"edit_equipment_form_{i}"):
- equipment = current_item["equipment"][i]
- col1, col2 = st.columns(2)
- with col1:
- equipment_name = st.text_input("نوع المعدات", value=equipment["name"])
- equipment_unit = st.text_input("الوحدة", value=equipment["unit"])
- with col2:
- equipment_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, value=equipment["quantity"])
- equipment_price = st.number_input("التكلفة اليومية (ريال)", min_value=0.0, step=10.0, value=equipment["unit_price"])
-
- col1, col2 = st.columns(2)
- with col1:
- if st.form_submit_button("حفظ التعديلات"):
- equipment["name"] = equipment_name
- equipment["unit"] = equipment_unit
- equipment["quantity"] = float(equipment_quantity)
- equipment["unit_price"] = float(equipment_price)
- equipment["total_price"] = equipment["quantity"] * equipment["unit_price"]
- st.session_state.item_analysis_edited = True
- st.session_state[f"edit_equipment_{i}"] = False
- st.rerun()
- with col2:
- if st.form_submit_button("إلغاء"):
- st.session_state[f"edit_equipment_{i}"] = False
- st.rerun()
+ # إضافة المعدة
+ 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()
+
+ def _render_labor_analysis(self, item_analysis):
+ """عرض تحليل العمالة مع إمكانية التعديل"""
+ st.subheader("تحليل العمالة")
+
+ if not item_analysis['labor']:
+ st.info("لا توجد عمالة في تحليل هذا البند.")
+
+ # إضافة زر لإضافة عمالة جديدة
+ 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 = ['العامل', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
+
+ # تحويل الجدول إلى جدول قابل للتعديل
+ 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"
+ )
+
+ # تحديث البيانات في item_analysis بناءً على التعديلات
+ if edited_df is not None and not edited_df.equals(df):
+ # حذف جميع العمالة الحالية
+ item_analysis['labor'] = []
+
+ # إضافة العمالة المعدلة
+ for i, row in edited_df.iterrows():
+ # حساب السعر الإجمالي
+ total_price = row['الكمية'] * row['سعر الوحدة']
- # تأكيد الحذف
- if st.session_state.get(f"delete_equipment_{i}", False):
- st.warning(f"هل أنت متأكد من حذف المعدات: {current_item['equipment'][i]['name']}؟")
- col1, col2 = st.columns(2)
- with col1:
- if st.button("نعم، حذف", key=f"confirm_delete_equipment_{i}"):
- current_item["equipment"].pop(i)
- st.session_state.item_analysis_edited = True
- st.session_state[f"delete_equipment_{i}"] = False
- st.rerun()
- with col2:
- if st.button("إلغاء", key=f"cancel_delete_equipment_{i}"):
- st.session_state[f"delete_equipment_{i}"] = False
- st.rerun()
-
- # عرض المجموع الكلي للمعدات
- st.markdown(f"إجمالي تكلفة المعدات: {equipment_total:,.2f} ريال
", unsafe_allow_html=True)
- else:
- st.info("لا توجد معدات مضافة لهذا البند")
+ # إضافة العامل
+ 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("تحليل المقاولين من الباطن")
- # إضافة مقاول جديد
- with st.expander("إضافة مقاول من الباطن"):
- with st.form("add_subcontractor_form"):
- col1, col2 = st.columns(2)
- with col1:
- new_sub_name = st.text_input("اسم المقاول")
- new_sub_work = st.text_input("نوع العمل")
- with col2:
- new_sub_price = st.number_input("التكلفة الإجمالية (ريال)", min_value=0.0, step=100.0)
-
- submitted = st.form_submit_button("إضافة مقاول")
- if submitted and new_sub_name and new_sub_work:
- new_sub = {
- "id": f"sub_{len(current_item['subcontractors']) + 1}",
- "name": new_sub_name,
- "work": new_sub_work,
- "quantity": 1,
- "unit_price": float(new_sub_price),
- "total_price": float(new_sub_price)
- }
- current_item["subcontractors"].append(new_sub)
- st.session_state.item_analysis_edited = True
- st.success("تمت إضافة المقاول بنجاح")
- st.rerun()
+ # التحقق من وجود مفتاح المقاولين من الباطن في التحليل
+ if 'subcontractors' not in item_analysis:
+ item_analysis['subcontractors'] = []
+ item_analysis['total_subcontractors_cost'] = 0
+
+ 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.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 current_item["subcontractors"]:
- # تحويل البيانات إلى DataFrame
- subs_data = []
- for i, sub in enumerate(current_item["subcontractors"]):
- subs_data.append({
- "م": i + 1,
- "اسم المقاول": sub["name"],
- "نوع العمل": sub["work"],
- "التكلفة الإجمالية (ريال)": sub["total_price"],
- "الإجراءات": f' '
+ # تحديث البيانات في item_analysis بناءً على التعديلات
+ if edited_df is not None and not edited_df.equals(df):
+ # حذف جميع المقاولين الحاليين
+ item_analysis['subcontractors'] = []
+
+ # إضافة المقاولين المعدلين
+ for i, row in edited_df.iterrows():
+ # حساب السعر الإجمالي
+ total_price = row['الكمية'] * row['سعر الوحدة']
+
+ # إضافة المقاول
+ item_analysis['subcontractors'].append({
+ 'name': row['المقاول'],
+ 'unit': row['الوحدة'],
+ 'quantity': row['الكمية'],
+ 'unit_price': row['سعر الوحدة'],
+ 'total_price': total_price
})
- subs_df = pd.DataFrame(subs_data)
-
- # إضافة صف المجموع
- subs_total = sum(sub["total_price"] for sub in current_item["subcontractors"])
- subs_df = subs_df.append({
- "م": "",
- "اسم المقاول": "المجموع",
- "نوع العمل": "",
- "التكلفة الإجمالية (ريال)": subs_total,
- "الإجراءات": ""
- }, ignore_index=True)
-
- # عرض الجدول القابل للتعديل
- edited_subs_df = st.data_editor(
- subs_df,
- column_config={
- "م": st.column_config.NumberColumn("م", width="small"),
- "اسم المقاول": st.column_config.TextColumn("اسم المقاول"),
- "نوع العمل": st.column_config.TextColumn("نوع العمل"),
- "التكلفة الإجمالية (ريال)": st.column_config.NumberColumn("التكلفة الإجمالية (ريال)", format="%.2f"),
- "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
- },
- hide_index=True,
- key="subs_table",
- on_change=self._update_total_price,
- disabled=["م", "اسم المقاول", "نوع العمل", "الإجراءات"]
+ # إعادة حساب إجمالي تكلفة المقاولين
+ item_analysis['total_subcontractors_cost'] = sum(subcontractor['total_price'] for subcontractor in item_analysis['subcontractors'])
+
+ # تعيين علامة التعديل
+ st.session_state.item_analysis_edited = True
+ st.rerun()
+
+ # عرض إجمالي تكلفة المقاولين
+ st.metric("إجمالي تكلفة المقاولين من الباطن", f"{item_analysis['total_subcontractors_cost']:,.2f} ريال")
+
+ # أزرار التحكم
+ 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))
+
+ # رسم المخطط الدائري
+ wedges, texts, autotexts = ax.pie(
+ values,
+ labels=labels,
+ autopct='%1.1f%%',
+ startangle=90,
+ shadow=True,
+ )
+
+ # تعديل حجم النص
+ 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)
+
+ # عرض جدول ملخص التكلفة
+ 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
+ ]
+ }
+
+ df = pd.DataFrame(cost_summary)
+ df.columns = ['البند', 'التكلفة (ريال)', 'النسبة (%)']
+
+ # تنسيق الأرقام
+ df['التكلفة (ريال)'] = df['التكلفة (ريال)'].apply(lambda x: f"{x:,.2f}")
+ df['النسبة (%)'] = df['النسبة (%)'].apply(lambda x: f"{x:.2f}%")
+
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
+
+ # إضافة حقل لتعديل المصاريف العمومية
+ st.subheader("تعديل المصاريف العمومية والربح")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ new_overhead = st.number_input(
+ "المصاريف العمومية (ريال)",
+ min_value=0.0,
+ value=float(item_analysis['overhead_cost']),
+ step=10.0,
+ format="%.2f",
+ key="edit_overhead_cost"
)
- # تحديث البيانات بعد التعديل
- for i, sub in enumerate(current_item["subcontractors"]):
- if i < len(edited_subs_df) - 1: # تجاهل صف المجموع
- sub["total_price"] = edited_subs_df.iloc[i]["التكلفة الإجمالية (ريال)"]
- sub["unit_price"] = sub["total_price"] # للمقاولين، سعر الوحدة = السعر الإجمالي
+ 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:
+ new_profit = st.number_input(
+ "الربح (ريال)",
+ min_value=float(-1000000.0),
+ value=float(item_analysis['profit']),
+ step=10.0,
+ format="%.2f",
+ key="edit_profit"
+ )
- # معالجة أزرار التعديل والحذف
- for i in range(len(current_item["subcontractors"])):
- col1, col2 = st.columns([1, 1])
- with col1:
- if st.button(f"تعديل المقاول {i+1}", key=f"edit_sub_btn_{i}"):
- st.session_state[f"edit_sub_{i}"] = True
+ if new_profit != item_analysis['profit']:
+ item_analysis['profit'] = new_profit
- with col2:
- if st.button(f"حذف المقاول {i+1}", key=f"delete_sub_btn_{i}"):
- st.session_state[f"delete_sub_{i}"] = True
+ # إعادة حساب سعر الوحدة
+ 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']
+ )
- # نموذج التعديل
- if st.session_state.get(f"edit_sub_{i}", False):
- with st.form(f"edit_sub_form_{i}"):
- sub = current_item["subcontractors"][i]
- col1, col2 = st.columns(2)
- with col1:
- sub_name = st.text_input("اسم المقاول", value=sub["name"])
- sub_work = st.text_input("نوع العمل", value=sub["work"])
- with col2:
- sub_price = st.number_input("التكلفة الإجمالية (ريال)", min_value=0.0, step=100.0, value=sub["total_price"])
-
- col1, col2 = st.columns(2)
- with col1:
- if st.form_submit_button("حفظ التعديلات"):
- sub["name"] = sub_name
- sub["work"] = sub_work
- sub["total_price"] = float(sub_price)
- sub["unit_price"] = sub["total_price"]
- st.session_state.item_analysis_edited = True
- st.session_state[f"edit_sub_{i}"] = False
- st.rerun()
- with col2:
- if st.form_submit_button("إلغاء"):
- st.session_state[f"edit_sub_{i}"] = False
- st.rerun()
+ item_analysis['unit_price'] = total_cost + item_analysis['profit']
+ st.session_state.item_analysis_edited = True
+ st.rerun()
+
+ 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']
- # تأكيد الحذف
- if st.session_state.get(f"delete_sub_{i}", False):
- st.warning(f"هل أنت متأكد من حذف المقاول: {current_item['subcontractors'][i]['name']}؟")
- col1, col2 = st.columns(2)
- with col1:
- if st.button("نعم، حذف", key=f"confirm_delete_sub_{i}"):
- current_item["subcontractors"].pop(i)
- st.session_state.item_analysis_edited = True
- st.session_state[f"delete_sub_{i}"] = False
- st.rerun()
- with col2:
- if st.button("إلغاء", key=f"cancel_delete_sub_{i}"):
- st.session_state[f"delete_sub_{i}"] = False
- st.rerun()
-
- # عرض المجموع الكلي للمقاولين
- st.markdown(f"إجمالي تكلفة المقاولين من الباطن: {subs_total:,.2f} ريال
", unsafe_allow_html=True)
- else:
- st.info("لا يوجد مقاولون من الباطن مضافون لهذا البند")
+ # تحديث سعر الوحدة
+ 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 _render_cost_analysis(self, project_info):
+ """عرض تحليل التكلفة"""
+ st.subheader("تحليل التكلفة")
- # ملخص التكلفة
- 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
+
+ # حساب إجمالي التكلفة
+ total_cost = sum(item['total_price'] for item in project_items)
- # حساب التكاليف
- materials_total = sum(material.get("total_price", 0) for material in current_item["materials"])
- labor_total = sum(labor.get("total_price", 0) for labor in current_item["labor"])
- equipment_total = sum(equipment.get("total_price", 0) for equipment in current_item["equipment"])
- subcontractors_total = sum(sub.get("total_price", 0) for sub in current_item["subcontractors"])
+ # عرض إجمالي التكلفة
+ st.metric("إجمالي تكلفة المشروع", f"{total_cost:,.2f} ريال")
- direct_cost = materials_total + labor_total + equipment_total + subcontractors_total
- overhead_profit = max(0, current_item["total_price"] - direct_cost) # تجنب القيم السالبة
+ # إنشاء DataFrame من بنود المشروع
+ df = pd.DataFrame(project_items)
+ df = df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price']]
+ df.columns = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
- # عرض ملخص التكلفة
- st.markdown("", unsafe_allow_html=True)
+ # تنسيق الأسعار
+ df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f} ريال")
+ df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f} ريال")
- col1, col2 = st.columns(2)
- with col1:
- st.markdown("
تكلفة المواد:
", unsafe_allow_html=True)
- st.markdown("
تكلفة العمالة:
", unsafe_allow_html=True)
- st.markdown("
تكلفة المعدات:
", unsafe_allow_html=True)
- st.markdown("
تكلفة المقاولين من الباطن:
", unsafe_allow_html=True)
- st.markdown("
إجمالي التكلفة المباشرة:
", unsafe_allow_html=True)
- st.markdown("
المصاريف العمومية والربح:
", unsafe_allow_html=True)
- st.markdown("
إجمالي سعر البند:
", unsafe_allow_html=True)
+ # عرض الجدول
+ st.dataframe(df, use_container_width=True)
+
+ # إنشاء رسم بياني للتكلفة
+ 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.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
+ return
+
+ # حساب إجمالي التكلفة
+ total_cost = sum(item['total_price'] for item in project_items)
+
+ # حساب هامش الربح
+ profit_margin = 0.15 # افتراضي 15%
+ profit = total_cost * profit_margin
+ total_with_profit = total_cost + profit
+ # عرض معلومات الربحية
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
with col2:
- st.markdown(f"
{materials_total:,.2f} ريال
", unsafe_allow_html=True)
- st.markdown(f"
{labor_total:,.2f} ريال
", unsafe_allow_html=True)
- st.markdown(f"
{equipment_total:,.2f} ريال
", unsafe_allow_html=True)
- st.markdown(f"
{subcontractors_total:,.2f} ريال
", unsafe_allow_html=True)
- st.markdown(f"
{direct_cost:,.2f} ريال
", unsafe_allow_html=True)
- st.markdown(f"
{overhead_profit:,.2f} ريال
", unsafe_allow_html=True)
- st.markdown(f"
{current_item['total_price']:,.2f} ريال
", unsafe_allow_html=True)
-
- st.markdown("
", unsafe_allow_html=True)
-
- # رسم بياني للتكاليف
- if direct_cost > 0:
- st.subheader("توزيع التكاليف")
-
- # إعداد البيانات للرسم البياني
- labels = ['المواد', 'العمالة', 'المعدات', 'المقاولين', 'الربح']
- sizes = [materials_total, labor_total, equipment_total, subcontractors_total, overhead_profit]
- colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#c2c2f0']
-
- # رسم الرسم البياني
- fig, ax = plt.subplots(figsize=(10, 6))
- wedges, texts, autotexts = ax.pie(
- sizes,
- labels=[get_display(arabic_reshaper.reshape(label)) for label in labels],
- autopct='%1.1f%%',
- startangle=90,
- colors=colors
- )
+ st.metric("هامش الربح", f"{profit_margin:.1%}")
+ with col3:
+ st.metric("إجمالي مع الربح", f"{total_with_profit:,.2f} ريال")
- # تنسيق النص
- for text in texts:
- text.set_fontsize(12)
- for autotext in autotexts:
- autotext.set_fontsize(10)
- autotext.set_color('white')
+ # إضافة شريط تمرير لتعديل هامش الربح
+ 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
- ax.axis('equal')
- plt.title(get_display(arabic_reshaper.reshape('توزيع تكاليف البند')), fontsize=16)
+ st.metric("إجمالي مع الربح الجديد", f"{total_with_profit:,.2f} ريال")
- st.pyplot(fig)
-
- def _load_sample_projects(self):
- """تحميل مشاريع نموذجية"""
- return {
- "proj_1": {
- "id": "proj_1",
- "name": "مشروع إنشاء مبنى سكني",
- "client": "شركة الإعمار",
- "date": "2025-06-24",
- "budget": 901000000.00,
- "items": [
- {
- "id": "item_1",
- "name": "أعمال الحفر",
- "unit": "م3",
- "quantity": 1500.0,
- "unit_price": 35.0,
- "total_price": 52500.0,
- "materials": [
- {
- "id": "material_1",
- "name": "رمل",
- "unit": "م3",
- "quantity": 50.0,
- "unit_price": 120.0,
- "total_price": 6000.0
- }
- ],
- "labor": [
- {
- "id": "labor_1",
- "name": "عامل حفر",
- "unit": "يوم",
- "quantity": 30.0,
- "unit_price": 150.0,
- "total_price": 4500.0
- }
- ],
- "equipment": [
- {
- "id": "equipment_1",
- "name": "حفارة",
- "unit": "يوم",
- "quantity": 15.0,
- "unit_price": 1200.0,
- "total_price": 18000.0
- }
- ],
- "subcontractors": []
- },
- {
- "id": "item_2",
- "name": "أعمال الخرسانة",
- "unit": "م3",
- "quantity": 800.0,
- "unit_price": 450.0,
- "total_price": 360000.0,
- "materials": [
- {
- "id": "material_1",
- "name": "أسمنت",
- "unit": "طن",
- "quantity": 120.0,
- "unit_price": 600.0,
- "total_price": 72000.0
- },
- {
- "id": "material_2",
- "name": "رمل",
- "unit": "م3",
- "quantity": 400.0,
- "unit_price": 120.0,
- "total_price": 48000.0
- },
- {
- "id": "material_3",
- "name": "حصى",
- "unit": "م3",
- "quantity": 600.0,
- "unit_price": 150.0,
- "total_price": 90000.0
- }
- ],
- "labor": [
- {
- "id": "labor_1",
- "name": "عامل خرسانة",
- "unit": "يوم",
- "quantity": 200.0,
- "unit_price": 200.0,
- "total_price": 40000.0
- },
- {
- "id": "labor_2",
- "name": "فني خرسانة",
- "unit": "يوم",
- "quantity": 50.0,
- "unit_price": 350.0,
- "total_price": 17500.0
- }
- ],
- "equipment": [
- {
- "id": "equipment_1",
- "name": "خلاطة خرسانة",
- "unit": "يوم",
- "quantity": 40.0,
- "unit_price": 800.0,
- "total_price": 32000.0
- },
- {
- "id": "equipment_2",
- "name": "مضخة خرسانة",
- "unit": "يوم",
- "quantity": 20.0,
- "unit_price": 1500.0,
- "total_price": 30000.0
- }
- ],
- "subcontractors": []
- },
- {
- "id": "item_3",
- "name": "أعمال البناء",
- "unit": "م2",
- "quantity": 2000.0,
- "unit_price": 220.0,
- "total_price": 440000.0,
- "materials": [],
- "labor": [],
- "equipment": [],
- "subcontractors": [
- {
- "id": "sub_1",
- "name": "مقاول بناء",
- "work": "بناء جدران",
- "quantity": 1,
- "unit_price": 250000.0,
- "total_price": 250000.0
- }
- ]
- }
- ]
- }
- }
-
-# تشغيل التطبيق عند تنفيذ الملف مباشرة
+ # إنشاء رسم بياني للربحية
+ 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
+
+ # عرض استراتيجيات التسعير المختلفة
+ st.write("### استراتيجيات التسعير المتاحة")
+
+ # استراتيجية التسعير القياسية
+ st.write("#### التسعير القياسي")
+ st.write("في هذه الاستراتيجية، يتم تسعير جميع البنود بناءً على التكلفة الفعلية مع إضافة هامش ربح موحد.")
+
+ # استراتيجية التسعير الغير متزن
+ st.write("#### التسعير الغير متزن")
+ st.write("في هذه الاستراتيجية، يتم زيادة أسعار البنود المبكرة وتخفيض أسعار البنود المتأخرة للحصول على تدفق نقدي أفضل.")
+
+ # تعديل معاملات التسعير الغير متزن
+ if project_info['pricing_type'] == 'غير متزن':
+ st.write("### تعديل معاملات التسعير الغير متزن")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ 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 early_factor != st.session_state.unbalanced_pricing_factors['early_items_factor']:
+ st.session_state.unbalanced_pricing_factors['early_items_factor'] = early_factor
+
+ 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 late_factor != st.session_state.unbalanced_pricing_factors['late_items_factor']:
+ st.session_state.unbalanced_pricing_factors['late_items_factor'] = late_factor
+
+ # عرض جدول الأسعار المعدلة
+ st.write("### الأسعار المعدلة باستخدام التسعير الغير متزن")
+
+ # إنشاء نسخة من بنود المشروع
+ 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()
+
+ 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']
+
+ unbalanced_items.append(unbalanced_item)
+
+ # إنشاء 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)
+
+ col1, col2 = st.columns(2)
+ with col1:
+ st.metric("إجمالي السعر الأصلي", f"{total_original:,.2f} ريال")
+ with col2:
+ st.metric("إجمالي السعر المعدل", f"{total_unbalanced:,.2f} ريال")
+
+ # زر تطبيق التسعير الغير متزن
+ 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("سيتم تنفيذ هذه الميزة في الإصدار القادم")
+
+ 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]
+
+ 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
+ }
+
+ st.session_state.saved_pricing.append(saved_pricing)
+
+ # تحديث حالة المشروع
+ 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 __name__ == "__main__":
app = PricingApp()
app.run()