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 random import json import os class PricingApp: """وحدة التسعير المتكاملة""" def __init__(self): """تهيئة وحدة التسعير""" # تهيئة البيانات في حالة الجلسة إذا لم تكن موجودة 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 '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() def run(self): """تشغيل وحدة التسعير""" st.title("وحدة التسعير المتكاملة") # عرض زر إنشاء تسعير جديد 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_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 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 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("قائمة المشاريع") 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 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: 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.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.") return # إنشاء DataFrame من بنود المشروع df = pd.DataFrame(project_items) df = df[['id', 'code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']] # تحويل الجدول إلى جدول قابل للتعديل 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" ) # تحديث البيانات في حالة التعديل 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="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 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("إضافة بند جديد") 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']}") 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 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 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() return # إنشاء 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 = ['الرقم', 'الاسم', 'الفئة', 'الوحدة', 'السعر'] # تنسيق السعر 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() if cancel_button: st.session_state.show_resource_selector = False st.rerun() else: 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("تحليل سعر البند") # الحصول على بنود المشروع الحالي 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 # اختيار البند للتحليل 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['سعر الوحدة'] # إضافة المادة 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() # عرض إجمالي تكلفة المواد 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("لا توجد معدات في تحليل هذا البند.") # إضافة زر لإضافة معدات جديدة 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 = ['المعدة', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي'] # تحويل الجدول إلى جدول قابل للتعديل 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() 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['سعر الوحدة'] # إضافة العامل 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("تحليل المقاولين من الباطن") # التحقق من وجود مفتاح المقاولين من الباطن في التحليل 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" ) # تحديث البيانات في 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 }) # إعادة حساب إجمالي تكلفة المقاولين 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" ) 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" ) 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() 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 _render_cost_analysis(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) # عرض إجمالي التكلفة st.metric("إجمالي تكلفة المشروع", f"{total_cost:,.2f} ريال") # إنشاء DataFrame من بنود المشروع df = pd.DataFrame(project_items) df = df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price']] 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) # إنشاء رسم بياني للتكلفة 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.metric("هامش الربح", f"{profit_margin:.1%}") with col3: st.metric("إجمالي مع الربح", f"{total_with_profit:,.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 st.metric("إجمالي مع الربح الجديد", f"{total_with_profit:,.2f} ريال") # إنشاء رسم بياني للربحية 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()