SA-SAJCOAI / modules /pricing /pricing_app.back2.py
EGYADMIN's picture
Rename modules/pricing/pricing_app.py to modules/pricing/pricing_app.back2.py
e055149 verified
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'<a href="data:file/csv;base64,{b64}" download="boq_{st.session_state.current_project}.csv">تحميل CSV</a>'
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()