EGYADMIN commited on
Commit
1edb3ab
·
verified ·
1 Parent(s): 3e44ec8

Update modules/pricing/pricing_app.py

Browse files
Files changed (1) hide show
  1. modules/pricing/pricing_app.py +837 -1031
modules/pricing/pricing_app.py CHANGED
@@ -11,12 +11,13 @@ from datetime import datetime
11
  import random
12
  import json
13
  import os
 
14
 
15
- class PricingApp:
16
- """وحدة التسعير المتكاملة"""
17
 
18
  def __init__(self):
19
- """تهيئة وحدة التسعير"""
20
  # تهيئة البيانات في حالة الجلسة إذا لم تكن موجودة
21
  if 'projects' not in st.session_state:
22
  st.session_state.projects = [
@@ -150,12 +151,41 @@ class PricingApp:
150
  if 'item_analysis_edited' not in st.session_state:
151
  st.session_state.item_analysis_edited = False
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  def render(self):
154
  """طريقة للتوافق مع الواجهة القديمة"""
155
  self.run()
156
 
157
  def run(self):
158
- """تشغيل وحدة التسعير"""
159
  st.title("وحدة التسعير المتكاملة")
160
 
161
  # عرض زر إنشاء تسعير جديد
@@ -181,33 +211,510 @@ class PricingApp:
181
  if project_info:
182
  self._render_project_info(project_info)
183
 
184
- # عرض علامات التبويب
185
- tab1, tab2, tab3, tab4, tab5 = st.tabs([
186
- "جدول الكميات",
187
- "تحليل سعر البند",
188
- "تحليل التكلفة",
189
- "تحليل الربحية",
190
- "استراتيجيات التسعير"
191
- ])
192
-
193
- with tab1:
194
- self._render_bill_of_quantities()
195
-
196
- with tab2:
197
- self._render_item_price_analysis()
198
-
199
- with tab3:
200
- self._render_cost_analysis(project_info)
201
-
202
- with tab4:
203
- self._render_profit_margin(project_info)
204
-
205
- with tab5:
206
- self._render_pricing_strategies(project_info)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
- # عرض أزرار التصدير والحفظ
209
- self._render_export_save_buttons(project_info)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  def _render_new_project_form(self):
212
  """عرض نموذج إنشاء مشروع جديد"""
213
  st.subheader("إنشاء تسعير جديد")
@@ -246,6 +753,7 @@ class PricingApp:
246
  st.session_state.current_project = new_project['id']
247
  st.session_state.next_project_id += 1
248
  st.session_state.show_new_project_form = False
 
249
  st.rerun()
250
 
251
  if cancel_button:
@@ -771,1032 +1279,330 @@ class PricingApp:
771
  st.session_state.show_resource_selector = False
772
  st.rerun()
773
 
774
- def _render_item_price_analysis(self):
775
- """عرض تحليل سعر البند مع إمكانية التعديل والحفظ"""
776
- st.subheader("تحليل سعر البند")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
777
 
778
- # الحصول على بنود المشروع الحالي
779
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
780
 
781
- if not project_items:
782
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
783
- return
784
-
785
- # اختيار البند للتحليل
786
- selected_item_id = st.selectbox(
787
- "اختر البند للتحليل",
788
- options=[item['id'] for item in project_items],
789
- format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
790
- key="select_item_for_analysis"
791
- )
792
 
793
- # الحصول على البند المحدد
794
- selected_item = next((item for item in project_items if item['id'] == selected_item_id), None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
 
796
- if not selected_item:
797
- st.error("لم يتم العثور على البند المحدد.")
798
- return
799
-
800
- # عرض معلومات البند
801
- col1, col2 = st.columns(2)
802
- with col1:
803
- st.write(f"**البند:** {selected_item['description']}")
804
- st.write(f"**الكود:** {selected_item['code']}")
805
- st.write(f"**الوحدة:** {selected_item['unit']}")
806
- with col2:
807
- st.write(f"**الكمية:** {selected_item['quantity']}")
808
- st.write(f"**سعر الوحدة:** {selected_item['unit_price']:,.2f} ريال")
809
- st.write(f"**السعر الإجمالي:** {selected_item['total_price']:,.2f} ريال")
810
 
811
- # إنشاء تحليل سعر البند
812
- item_analysis = self._generate_item_analysis(selected_item)
813
 
814
- # عرض تحليل المواد مع إمكانية التعديل
815
- self._render_materials_analysis(item_analysis)
 
816
 
817
- # عرض تحليل المعدات مع إمكانية التعديل
818
- self._render_equipment_analysis(item_analysis)
819
 
820
- # عرض تحليل العمالة مع إمكانية التعديل
821
- self._render_labor_analysis(item_analysis)
822
 
823
- # عرض تحليل المقاولين من الباطن مع إمكانية التعديل
824
- self._render_subcontractors_analysis(item_analysis)
825
 
826
- # عرض ملخص التكلفة
827
- self._render_cost_summary(item_analysis)
828
 
829
- # زر حفظ التغييرات في جدول الكميات الرئيسي
830
- if st.button("حفظ جميع التغييرات في جدول الكميات", key="save_all_changes", type="primary"):
831
- # تحديث البند في جدول الكميات
832
- self._update_boq_item_from_analysis(selected_item_id, item_analysis)
833
- st.success("تم حفظ جميع التغييرات بنجاح في جدول الكميات الرئيسي")
834
- st.session_state.item_analysis_edited = False
835
- st.rerun()
 
836
 
837
- def _generate_item_analysis(self, item):
838
- """إنشاء تحليل سعر البند"""
839
- # في هذه النسخة التجريبية، سنقوم بإنشاء بيانات عشوائية للتحليل
840
- # في النسخة النهائية، يجب استخدام بيانات حقيقية من قاعدة البيانات
841
-
842
- unit_price = item['unit_price']
843
-
844
- # تقسيم سعر الوحدة إلى مكوناته
845
- materials_cost = unit_price * random.uniform(0.4, 0.6)
846
- equipment_cost = unit_price * random.uniform(0.1, 0.2)
847
- labor_cost = unit_price * random.uniform(0.1, 0.2)
848
- overhead_cost = unit_price * random.uniform(0.05, 0.1)
849
- profit = unit_price - (materials_cost + equipment_cost + labor_cost + overhead_cost)
850
-
851
- # إنشاء قائمة المواد
852
- materials = [
853
- {
854
- 'name': 'خرسانة جاهزة' if 'خرسانة' in item['description'].lower() else 'حديد تسليح' if 'حديد' in item['description'].lower() else 'رمل',
855
- 'unit': 'م3' if 'خرسانة' in item['description'].lower() else 'طن' if 'حديد' in item['description'].lower() else 'م3',
856
- 'quantity': random.uniform(0.5, 1.5),
857
- 'unit_price': materials_cost * 0.6,
858
- 'total_price': materials_cost * 0.6 * random.uniform(0.5, 1.5)
859
- },
860
- {
861
- 'name': 'إسمنت',
862
- 'unit': 'طن',
863
- 'quantity': random.uniform(0.2, 0.5),
864
- 'unit_price': materials_cost * 0.3,
865
- 'total_price': materials_cost * 0.3 * random.uniform(0.2, 0.5)
866
- },
867
- {
868
- 'name': 'مواد أخرى',
869
- 'unit': 'مجموعة',
870
- 'quantity': 1,
871
- 'unit_price': materials_cost * 0.1,
872
- 'total_price': materials_cost * 0.1
873
- }
874
- ]
875
 
876
- # إنشاء قائمة الم��دات
877
- equipment = [
878
- {
879
- 'name': 'خلاطة خرسانة' if 'خرسانة' in item['description'].lower() else 'حفارة' if 'حفر' in item['description'].lower() else 'رافعة',
880
- 'unit': 'يوم',
881
- 'quantity': random.uniform(1, 3),
882
- 'unit_price': equipment_cost * 0.7,
883
- 'total_price': equipment_cost * 0.7 * random.uniform(1, 3)
884
- },
885
- {
886
- 'name': 'معدات أخرى',
887
- 'unit': 'يوم',
888
- 'quantity': random.uniform(1, 2),
889
- 'unit_price': equipment_cost * 0.3,
890
- 'total_price': equipment_cost * 0.3 * random.uniform(1, 2)
891
- }
892
- ]
893
 
894
- # إنشاء قائمة العمالة
895
- labor = [
896
- {
897
- 'name': 'عامل فني',
898
- 'unit': 'يوم',
899
- 'quantity': random.uniform(2, 5),
900
- 'unit_price': labor_cost * 0.6,
901
- 'total_price': labor_cost * 0.6 * random.uniform(2, 5)
902
- },
903
- {
904
- 'name': 'عامل عادي',
905
- 'unit': 'يوم',
906
- 'quantity': random.uniform(3, 8),
907
- 'unit_price': labor_cost * 0.4,
908
- 'total_price': labor_cost * 0.4 * random.uniform(3, 8)
909
- }
910
- ]
911
 
912
- # إنشاء قائمة المقاولين من الباطن
913
- subcontractors = [
914
- {
915
- 'name': 'مقاول أعمال خرسانية' if 'خرسانة' in item['description'].lower() else 'مقاول أعمال حفر' if 'حفر' in item['description'].lower() else 'مقاول عام',
916
- 'unit': 'عقد',
917
- 'quantity': 1,
918
- 'unit_price': unit_price * 0.15,
919
- 'total_price': unit_price * 0.15
 
 
 
 
 
 
 
 
 
920
  }
921
- ]
922
 
923
- # حساب إجمالي التكاليف
924
- total_materials_cost = sum(material['total_price'] for material in materials)
925
- total_equipment_cost = sum(equipment_item['total_price'] for equipment_item in equipment)
926
- total_labor_cost = sum(labor_item['total_price'] for labor_item in labor)
927
- total_subcontractors_cost = sum(subcontractor['total_price'] for subcontractor in subcontractors)
928
-
929
- # تعديل الربح ليكون الفرق بين سعر الوحدة وإجمالي التكاليف
930
- total_cost = total_materials_cost + total_equipment_cost + total_labor_cost + total_subcontractors_cost + overhead_cost
931
- profit = unit_price - total_cost
932
-
933
- return {
934
- 'item': item,
935
- 'materials': materials,
936
- 'equipment': equipment,
937
- 'labor': labor,
938
- 'subcontractors': subcontractors,
939
- 'total_materials_cost': total_materials_cost,
940
- 'total_equipment_cost': total_equipment_cost,
941
- 'total_labor_cost': total_labor_cost,
942
- 'total_subcontractors_cost': total_subcontractors_cost,
943
- 'overhead_cost': overhead_cost,
944
- 'profit': profit,
945
- 'unit_price': unit_price
946
- }
947
 
948
- def _render_materials_analysis(self, item_analysis):
949
- """عرض تحليل المواد مع إمكانية التعديل"""
950
- st.subheader("تحليل المواد")
951
-
952
- if not item_analysis['materials']:
953
- st.info("لا توجد مواد في تحليل هذا البند.")
954
-
955
- # إضافة زر لإضافة مواد جديدة
956
- if st.button("إضافة مواد", key="add_first_material"):
957
- item_analysis['materials'] = [{
958
- 'name': 'مادة جديدة',
959
- 'unit': 'وحدة',
960
- 'quantity': 1.0,
961
- 'unit_price': 0.0,
962
- 'total_price': 0.0
963
- }]
964
- st.session_state.item_analysis_edited = True
965
  st.rerun()
966
- return
967
-
968
- # إنشاء DataFrame من قائمة المواد
969
- df = pd.DataFrame(item_analysis['materials'])
970
- df.columns = ['المادة', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
971
 
972
- # تحويل الجدول إلى جدول قابل للتعديل
973
- edited_df = st.data_editor(
974
- df,
975
- use_container_width=True,
976
- key="edit_materials_table",
977
- column_config={
978
- "المادة": st.column_config.Column("المادة"),
979
- "الوحدة": st.column_config.Column("الوحدة"),
980
- "الكمية": st.column_config.NumberColumn(
981
- "الكمية",
982
- min_value=0.0,
983
- format="%.2f",
984
- step=0.1,
985
- ),
986
- "سعر الوحدة": st.column_config.NumberColumn(
987
- "سعر الوحدة",
988
- min_value=0.0,
989
- format="%.2f ريال",
990
- step=0.1,
991
- ),
992
- "السعر الإجمالي": st.column_config.NumberColumn(
993
- "السعر الإجمالي",
994
- format="%.2f ريال",
995
- disabled=True,
996
- ),
997
- },
998
- num_rows="dynamic"
999
- )
1000
-
1001
- # تحديث البيانات في item_analysis بناءً على التعديلات
1002
- if edited_df is not None and not edited_df.equals(df):
1003
- # حذف جميع المواد الحالية
1004
- item_analysis['materials'] = []
1005
-
1006
- # إضافة المواد المعدلة
1007
- for i, row in edited_df.iterrows():
1008
- # حساب السعر الإجمالي
1009
- total_price = row['الكمية'] * row['سعر الوحدة']
1010
-
1011
- # إضافة المادة
1012
- item_analysis['materials'].append({
1013
- 'name': row['المادة'],
1014
- 'unit': row['الوحدة'],
1015
- 'quantity': row['الكمية'],
1016
- 'unit_price': row['سعر الوحدة'],
1017
- 'total_price': total_price
1018
- })
1019
-
1020
- # إعادة حساب إجمالي تكلفة المواد
1021
- item_analysis['total_materials_cost'] = sum(material['total_price'] for material in item_analysis['materials'])
1022
-
1023
- # تعيين علامة التعديل
1024
- st.session_state.item_analysis_edited = True
1025
- st.rerun()
1026
-
1027
- # عرض إجمالي تكلفة المواد
1028
- st.metric("إجمالي تكلفة المواد", f"{item_analysis['total_materials_cost']:,.2f} ريال")
1029
-
1030
- # أزرار التحكم
1031
- col1, col2 = st.columns(2)
1032
- with col1:
1033
- if st.button("إضافة مادة جديدة", key="add_material"):
1034
- item_analysis['materials'].append({
1035
- 'name': 'مادة جديدة',
1036
- 'unit': 'وحدة',
1037
- 'quantity': 1.0,
1038
- 'unit_price': 0.0,
1039
- 'total_price': 0.0
1040
- })
1041
- st.session_state.item_analysis_edited = True
1042
- st.rerun()
1043
-
1044
- def _render_equipment_analysis(self, item_analysis):
1045
- """عرض تحليل المعدات مع إمكانية التعديل"""
1046
- st.subheader("تحليل المعدات")
1047
-
1048
- if not item_analysis['equipment']:
1049
- st.info("لا توجد معدات في تحليل هذا البند.")
1050
-
1051
- # إضافة زر لإضافة معدات جديدة
1052
- if st.button("إضافة معدات", key="add_first_equipment"):
1053
- item_analysis['equipment'] = [{
1054
- 'name': 'معدة جديدة',
1055
- 'unit': 'يوم',
1056
- 'quantity': 1.0,
1057
- 'unit_price': 0.0,
1058
- 'total_price': 0.0
1059
- }]
1060
- st.session_state.item_analysis_edited = True
1061
- st.rerun()
1062
- return
1063
-
1064
- # إنشاء DataFrame من قائمة المعدات
1065
- df = pd.DataFrame(item_analysis['equipment'])
1066
- df.columns = ['المعدة', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
1067
-
1068
- # تحويل الجدول إلى جدول قابل للتعديل
1069
- edited_df = st.data_editor(
1070
- df,
1071
- use_container_width=True,
1072
- key="edit_equipment_table",
1073
- column_config={
1074
- "المعدة": st.column_config.Column("المعدة"),
1075
- "الوحدة": st.column_config.Column("الوحدة"),
1076
- "الكمية": st.column_config.NumberColumn(
1077
- "الكمية",
1078
- min_value=0.0,
1079
- format="%.2f",
1080
- step=0.1,
1081
- ),
1082
- "سعر الوحدة": st.column_config.NumberColumn(
1083
- "سعر الوحدة",
1084
- min_value=0.0,
1085
- format="%.2f ريال",
1086
- step=0.1,
1087
- ),
1088
- "السعر الإجمالي": st.column_config.NumberColumn(
1089
- "السعر الإجمالي",
1090
- format="%.2f ريال",
1091
- disabled=True,
1092
- ),
1093
- },
1094
- num_rows="dynamic"
1095
- )
1096
-
1097
- # تحديث البيانات في item_analysis بناءً على التعديلات
1098
- if edited_df is not None and not edited_df.equals(df):
1099
- # حذف جميع المعدات الحالية
1100
- item_analysis['equipment'] = []
1101
-
1102
- # إضافة المعدات المعدلة
1103
- for i, row in edited_df.iterrows():
1104
- # حساب السعر الإجمالي
1105
- total_price = row['الكمية'] * row['سعر الوحدة']
1106
-
1107
- # إضافة المعدة
1108
- item_analysis['equipment'].append({
1109
- 'name': row['المعدة'],
1110
- 'unit': row['الوحدة'],
1111
- 'quantity': row['الكمية'],
1112
- 'unit_price': row['سعر الوحدة'],
1113
- 'total_price': total_price
1114
- })
1115
-
1116
- # إعادة حساب إجمالي تكلفة المعدات
1117
- item_analysis['total_equipment_cost'] = sum(equipment_item['total_price'] for equipment_item in item_analysis['equipment'])
1118
-
1119
- # تعيين علامة التعديل
1120
- st.session_state.item_analysis_edited = True
1121
- st.rerun()
1122
-
1123
- # عرض إجمالي تكلفة المعدات
1124
- st.metric("إجمالي تكلفة المعدات", f"{item_analysis['total_equipment_cost']:,.2f} ريال")
1125
-
1126
- # أزرار التحكم
1127
- col1, col2 = st.columns(2)
1128
- with col1:
1129
- if st.button("إضافة معدة جديدة", key="add_equipment"):
1130
- item_analysis['equipment'].append({
1131
- 'name': 'معدة جديدة',
1132
- 'unit': 'يوم',
1133
- 'quantity': 1.0,
1134
- 'unit_price': 0.0,
1135
- 'total_price': 0.0
1136
- })
1137
- st.session_state.item_analysis_edited = True
1138
- st.rerun()
1139
-
1140
- def _render_labor_analysis(self, item_analysis):
1141
- """عرض تحليل العمالة مع إمكانية التعديل"""
1142
- st.subheader("تحليل العمالة")
1143
-
1144
- if not item_analysis['labor']:
1145
- st.info("لا توجد عمالة في تحليل هذا البند.")
1146
-
1147
- # إضافة زر لإضافة عمالة جديدة
1148
- if st.button("إضافة عمالة", key="add_first_labor"):
1149
- item_analysis['labor'] = [{
1150
- 'name': 'عامل جديد',
1151
- 'unit': 'يوم',
1152
- 'quantity': 1.0,
1153
- 'unit_price': 0.0,
1154
- 'total_price': 0.0
1155
- }]
1156
- st.session_state.item_analysis_edited = True
1157
- st.rerun()
1158
- return
1159
-
1160
- # إنشاء DataFrame من قائمة العمالة
1161
- df = pd.DataFrame(item_analysis['labor'])
1162
- df.columns = ['العامل', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
1163
-
1164
- # تحويل الجدول إلى جدول قابل للتعديل
1165
- edited_df = st.data_editor(
1166
- df,
1167
- use_container_width=True,
1168
- key="edit_labor_table",
1169
- column_config={
1170
- "العامل": st.column_config.Column("العا��ل"),
1171
- "الوحدة": st.column_config.Column("الوحدة"),
1172
- "الكمية": st.column_config.NumberColumn(
1173
- "الكمية",
1174
- min_value=0.0,
1175
- format="%.2f",
1176
- step=0.1,
1177
- ),
1178
- "سعر الوحدة": st.column_config.NumberColumn(
1179
- "سعر الوحدة",
1180
- min_value=0.0,
1181
- format="%.2f ريال",
1182
- step=0.1,
1183
- ),
1184
- "السعر الإجمالي": st.column_config.NumberColumn(
1185
- "السعر الإجمالي",
1186
- format="%.2f ريال",
1187
- disabled=True,
1188
- ),
1189
- },
1190
- num_rows="dynamic"
1191
- )
1192
-
1193
- # تحديث البيانات في item_analysis بناءً على التعديلات
1194
- if edited_df is not None and not edited_df.equals(df):
1195
- # حذف جميع العمالة الحالية
1196
- item_analysis['labor'] = []
1197
-
1198
- # إضافة العمالة المعدلة
1199
- for i, row in edited_df.iterrows():
1200
- # حساب السعر الإجمالي
1201
- total_price = row['الكمية'] * row['سعر الوحدة']
1202
-
1203
- # إضافة العامل
1204
- item_analysis['labor'].append({
1205
- 'name': row['العامل'],
1206
- 'unit': row['الوحدة'],
1207
- 'quantity': row['الكمية'],
1208
- 'unit_price': row['سعر الوحدة'],
1209
- 'total_price': total_price
1210
- })
1211
-
1212
- # إعادة حساب إجمالي تكلفة العمالة
1213
- item_analysis['total_labor_cost'] = sum(labor_item['total_price'] for labor_item in item_analysis['labor'])
1214
-
1215
- # تعيين علامة التعديل
1216
- st.session_state.item_analysis_edited = True
1217
- st.rerun()
1218
-
1219
- # عرض إجمالي تكلفة العمالة
1220
- st.metric("إجمالي تكلفة العمالة", f"{item_analysis['total_labor_cost']:,.2f} ريال")
1221
-
1222
- # أزرار التحكم
1223
- col1, col2 = st.columns(2)
1224
- with col1:
1225
- if st.button("إضافة عامل جديد", key="add_labor"):
1226
- item_analysis['labor'].append({
1227
- 'name': 'عامل جديد',
1228
- 'unit': 'يوم',
1229
- 'quantity': 1.0,
1230
- 'unit_price': 0.0,
1231
- 'total_price': 0.0
1232
- })
1233
- st.session_state.item_analysis_edited = True
1234
- st.rerun()
1235
-
1236
- def _render_subcontractors_analysis(self, item_analysis):
1237
- """عرض تحليل المقاولين من الباطن مع إمكانية التعديل"""
1238
- st.subheader("تحليل المقاولين من الباطن")
1239
-
1240
- # التحقق من وجود مفتاح المقاولين من الباطن في التحليل
1241
- if 'subcontractors' not in item_analysis:
1242
- item_analysis['subcontractors'] = []
1243
- item_analysis['total_subcontractors_cost'] = 0
1244
-
1245
- if not item_analysis['subcontractors']:
1246
- st.info("لا يوجد مقاولين من الباطن في تحليل هذا البند.")
1247
-
1248
- # إضافة زر لإضافة مقاول جديد
1249
- if st.button("إضافة مقاول من الباطن", key="add_first_subcontractor"):
1250
- item_analysis['subcontractors'] = [{
1251
- 'name': 'مقاول جديد',
1252
- 'unit': 'عقد',
1253
- 'quantity': 1.0,
1254
- 'unit_price': 0.0,
1255
- 'total_price': 0.0
1256
- }]
1257
- st.session_state.item_analysis_edited = True
1258
- st.rerun()
1259
- return
1260
-
1261
- # إنشاء DataFrame من قائمة المقاولين
1262
- df = pd.DataFrame(item_analysis['subcontractors'])
1263
- df.columns = ['المقاول', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
1264
-
1265
- # تحويل الجدول إلى جدول قابل للتعديل
1266
- edited_df = st.data_editor(
1267
- df,
1268
- use_container_width=True,
1269
- key="edit_subcontractors_table",
1270
- column_config={
1271
- "المقاول": st.column_config.Column("المقاول"),
1272
- "الوحدة": st.column_config.Column("الوحدة"),
1273
- "الكمية": st.column_config.NumberColumn(
1274
- "الكمية",
1275
- min_value=0.0,
1276
- format="%.2f",
1277
- step=0.1,
1278
- ),
1279
- "سعر الوحدة": st.column_config.NumberColumn(
1280
- "سعر الوحدة",
1281
- min_value=0.0,
1282
- format="%.2f ريال",
1283
- step=0.1,
1284
- ),
1285
- "السعر الإجمالي": st.column_config.NumberColumn(
1286
- "السعر الإجمالي",
1287
- format="%.2f ريال",
1288
- disabled=True,
1289
- ),
1290
- },
1291
- num_rows="dynamic"
1292
- )
1293
-
1294
- # تحديث البيانات في item_analysis بناءً على التعديلات
1295
- if edited_df is not None and not edited_df.equals(df):
1296
- # حذف جميع المقاولين الحاليين
1297
- item_analysis['subcontractors'] = []
1298
-
1299
- # إضافة المقاولين المعدلين
1300
- for i, row in edited_df.iterrows():
1301
- # حساب السعر الإجمالي
1302
- total_price = row['الكمية'] * row['سعر الوحدة']
1303
-
1304
- # إضافة المقاول
1305
- item_analysis['subcontractors'].append({
1306
- 'name': row['المقاول'],
1307
- 'unit': row['الوحدة'],
1308
- 'quantity': row['الكمية'],
1309
- 'unit_price': row['سعر الوحدة'],
1310
- 'total_price': total_price
1311
- })
1312
-
1313
- # إعادة حساب إجمالي تكلفة المقاولين
1314
- item_analysis['total_subcontractors_cost'] = sum(subcontractor['total_price'] for subcontractor in item_analysis['subcontractors'])
1315
-
1316
- # تعيين علامة التعديل
1317
- st.session_state.item_analysis_edited = True
1318
- st.rerun()
1319
-
1320
- # عرض إجمالي تكلفة المقاولين
1321
- st.metric("إجمالي تكلفة المقاولين من الباطن", f"{item_analysis['total_subcontractors_cost']:,.2f} ريال")
1322
-
1323
- # أزرار التحكم
1324
- col1, col2 = st.columns(2)
1325
- with col1:
1326
- if st.button("إضافة مقاول جديد", key="add_subcontractor"):
1327
- item_analysis['subcontractors'].append({
1328
- 'name': 'مقاول جديد',
1329
- 'unit': 'عقد',
1330
- 'quantity': 1.0,
1331
- 'unit_price': 0.0,
1332
- 'total_price': 0.0
1333
- })
1334
- st.session_state.item_analysis_edited = True
1335
- st.rerun()
1336
-
1337
- def _setup_arabic_fonts(self):
1338
- """إعداد الخطوط العربية للرسوم البيانية"""
1339
- plt.rcParams['font.family'] = 'Arial'
1340
- plt.rcParams['axes.unicode_minus'] = False
1341
-
1342
- def _render_cost_summary(self, item_analysis):
1343
- """عرض ملخص التكلفة"""
1344
- st.subheader("ملخص التكلفة")
1345
-
1346
- # إعداد الخطوط العربية
1347
- self._setup_arabic_fonts()
1348
-
1349
- # إنشاء بيانات الرسم البياني
1350
- # استخدام arabic_reshaper و bidi لمعالجة النص العربي
1351
- labels_ar = ['المواد', 'المعدات', 'العمالة', 'المقاولين من الباطن', 'المصاريف العمومية', 'الربح']
1352
- labels = [get_display(arabic_reshaper.reshape(label)) for label in labels_ar]
1353
-
1354
- # التحقق من وجود قيم NaN واستبدالها بأصفار
1355
- values = [
1356
- item_analysis['total_materials_cost'] if not np.isnan(item_analysis['total_materials_cost']) else 0,
1357
- item_analysis['total_equipment_cost'] if not np.isnan(item_analysis['total_equipment_cost']) else 0,
1358
- item_analysis['total_labor_cost'] if not np.isnan(item_analysis['total_labor_cost']) else 0,
1359
- item_analysis['total_subcontractors_cost'] if not np.isnan(item_analysis.get('total_subcontractors_cost', 0)) else 0,
1360
- item_analysis['overhead_cost'] if not np.isnan(item_analysis['overhead_cost']) else 0,
1361
- item_analysis['profit'] if not np.isnan(item_analysis['profit']) else 0
1362
- ]
1363
-
1364
- # التحقق من أن جميع القيم موجبة (أو صفر) لتجنب الأخطاء في الرسم البياني
1365
- values = [max(0, val) for val in values]
1366
-
1367
- # إنشاء الرسم البياني
1368
- fig, ax = plt.subplots(figsize=(10, 6))
1369
-
1370
- # رسم المخطط الدائري
1371
- wedges, texts, autotexts = ax.pie(
1372
- values,
1373
- labels=labels,
1374
- autopct='%1.1f%%',
1375
- startangle=90,
1376
- shadow=True,
1377
- )
1378
-
1379
- # تعديل حجم النص
1380
- plt.setp(autotexts, size=10, weight="bold")
1381
-
1382
- # إضافة العنوان
1383
- title_ar = 'توزيع تكلفة البند'
1384
- title = get_display(arabic_reshaper.reshape(title_ar))
1385
- ax.set_title(title, fontsize=16)
1386
-
1387
- # عرض الرسم البياني
1388
- st.pyplot(fig)
1389
-
1390
- # عرض جدول ملخص التكلفة
1391
- cost_summary = {
1392
- 'البند': ['المواد', 'المعدات', 'العمالة', 'المقاولين من الباطن', 'المصاريف العمومية', 'الربح', 'الإجمالي'],
1393
- 'التكلفة': [
1394
- item_analysis['total_materials_cost'],
1395
- item_analysis['total_equipment_cost'],
1396
- item_analysis['total_labor_cost'],
1397
- item_analysis.get('total_subcontractors_cost', 0),
1398
- item_analysis['overhead_cost'],
1399
- item_analysis['profit'],
1400
- item_analysis['unit_price']
1401
- ],
1402
- 'النسبة': [
1403
- item_analysis['total_materials_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1404
- item_analysis['total_equipment_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1405
- item_analysis['total_labor_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1406
- item_analysis.get('total_subcontractors_cost', 0) / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1407
- item_analysis['overhead_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1408
- item_analysis['profit'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1409
- 100
1410
- ]
1411
- }
1412
-
1413
- df = pd.DataFrame(cost_summary)
1414
- df.columns = ['البند', 'التكلفة (ريال)', 'النسبة (%)']
1415
-
1416
- # تنسيق الأرقام
1417
- df['التكلفة (ريال)'] = df['التكلفة (ريال)'].apply(lambda x: f"{x:,.2f}")
1418
- df['النسبة (%)'] = df['النسبة (%)'].apply(lambda x: f"{x:.2f}%")
1419
-
1420
- # عرض الجدول
1421
- st.dataframe(df, use_container_width=True)
1422
-
1423
- # إضافة حقل لتعديل المصاريف العمومية
1424
- st.subheader("تعديل المصاريف العمومية والربح")
1425
-
1426
- col1, col2 = st.columns(2)
1427
- with col1:
1428
- new_overhead = st.number_input(
1429
- "المصاريف العمومية (ريال)",
1430
- min_value=0.0,
1431
- value=float(item_analysis['overhead_cost']),
1432
- step=10.0,
1433
- format="%.2f",
1434
- key="edit_overhead_cost"
1435
- )
1436
-
1437
- if new_overhead != item_analysis['overhead_cost']:
1438
- item_analysis['overhead_cost'] = new_overhead
1439
-
1440
- # إعادة حساب الربح
1441
- total_cost = (
1442
- item_analysis['total_materials_cost'] +
1443
- item_analysis['total_equipment_cost'] +
1444
- item_analysis['total_labor_cost'] +
1445
- item_analysis.get('total_subcontractors_cost', 0) +
1446
- item_analysis['overhead_cost']
1447
- )
1448
-
1449
- item_analysis['profit'] = item_analysis['unit_price'] - total_cost
1450
- st.session_state.item_analysis_edited = True
1451
- st.rerun()
1452
-
1453
  with col2:
1454
- new_profit = st.number_input(
1455
- "الربح (ريال)",
1456
- min_value=float(-1000000.0),
1457
- value=float(item_analysis['profit']),
1458
- step=10.0,
1459
- format="%.2f",
1460
- key="edit_profit"
1461
- )
1462
-
1463
- if new_profit != item_analysis['profit']:
1464
- item_analysis['profit'] = new_profit
1465
-
1466
- # إعادة حساب سعر الوحدة
1467
- total_cost = (
1468
- item_analysis['total_materials_cost'] +
1469
- item_analysis['total_equipment_cost'] +
1470
- item_analysis['total_labor_cost'] +
1471
- item_analysis.get('total_subcontractors_cost', 0) +
1472
- item_analysis['overhead_cost']
1473
- )
1474
-
1475
- item_analysis['unit_price'] = total_cost + item_analysis['profit']
1476
- st.session_state.item_analysis_edited = True
1477
- st.rerun()
1478
-
1479
- def _update_boq_item_from_analysis(self, item_id, item_analysis):
1480
- """تحديث البند في جدول الكميات بناءً على التحليل"""
1481
- # حساب السعر الإجمالي الجديد
1482
- total_cost = (
1483
- item_analysis['total_materials_cost'] +
1484
- item_analysis['total_equipment_cost'] +
1485
- item_analysis['total_labor_cost'] +
1486
- item_analysis.get('total_subcontractors_cost', 0) +
1487
- item_analysis['overhead_cost'] +
1488
- item_analysis['profit']
1489
- )
1490
-
1491
- # تحديث سعر الوحدة والسعر الإجمالي في الب��د
1492
- for i, item in enumerate(st.session_state.boq_items):
1493
- if item['id'] == item_id:
1494
- # حفظ الكمية الأصلية
1495
- quantity = item['quantity']
1496
-
1497
- # تحديث سعر الوحدة
1498
- unit_price = total_cost
1499
-
1500
- # تحديث البند
1501
- st.session_state.boq_items[i]['unit_price'] = unit_price
1502
- st.session_state.boq_items[i]['total_price'] = unit_price * quantity
1503
- break
1504
-
1505
- def _render_cost_analysis(self, project_info):
1506
- """عرض تحليل التكلفة"""
1507
- st.subheader("تحليل التكلفة")
1508
-
1509
- # الحصول على بنود المشروع الحالي
1510
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
1511
-
1512
- if not project_items:
1513
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
1514
- return
1515
-
1516
- # حساب إجمالي التكلفة
1517
- total_cost = sum(item['total_price'] for item in project_items)
1518
-
1519
- # عرض إجمالي التكلفة
1520
- st.metric("إجمالي تكلفة المشروع", f"{total_cost:,.2f} ريال")
1521
-
1522
- # إنشاء DataFrame من بنود المشروع
1523
- df = pd.DataFrame(project_items)
1524
- df = df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price']]
1525
- df.columns = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
1526
-
1527
- # تنسيق الأسعار
1528
- df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f} ريال")
1529
- df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f} ريال")
1530
-
1531
- # عرض الجدول
1532
- st.dataframe(df, use_container_width=True)
1533
-
1534
- # إنشاء رسم بياني للتكلفة
1535
- self._setup_arabic_fonts()
1536
-
1537
- # إنشاء بيانات الرسم البياني
1538
- fig, ax = plt.subplots(figsize=(10, 6))
1539
-
1540
- # تحويل الأسعار إلى أرقام
1541
- prices = [item['total_price'] for item in project_items]
1542
- labels = [item['description'] for item in project_items]
1543
-
1544
- # تحويل النص العربي
1545
- labels = [get_display(arabic_reshaper.reshape(label)) for label in labels]
1546
-
1547
- # رسم المخطط الشريطي
1548
- bars = ax.bar(labels, prices)
1549
-
1550
- # تدوير التسميات
1551
- plt.xticks(rotation=45, ha='right')
1552
-
1553
- # إضافة العنوان
1554
- title_ar = 'توزيع تكلفة المشروع حسب البنود'
1555
- title = get_display(arabic_reshaper.reshape(title_ar))
1556
- ax.set_title(title, fontsize=16)
1557
-
1558
- # إضافة التسميات
1559
- ax.set_ylabel(get_display(arabic_reshaper.reshape('التكلفة (ريال)')))
1560
-
1561
- # تنسيق الرسم البياني
1562
- plt.tight_layout()
1563
 
1564
- # عرض الرسم البياني
1565
- st.pyplot(fig)
1566
-
1567
- def _render_profit_margin(self, project_info):
1568
- """عرض تحليل الربحية"""
1569
- st.subheader("تحليل الربحية")
1570
-
1571
- # الحصول على بنود المشروع الحالي
1572
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
1573
-
1574
- if not project_items:
1575
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
1576
- return
1577
-
1578
- # حساب إجمالي التكلفة
1579
- total_cost = sum(item['total_price'] for item in project_items)
1580
-
1581
- # حساب هامش الربح
1582
- profit_margin = 0.15 # افتراضي 15%
1583
- profit = total_cost * profit_margin
1584
- total_with_profit = total_cost + profit
1585
-
1586
- # عرض معلومات الربحية
1587
- col1, col2, col3 = st.columns(3)
1588
- with col1:
1589
- st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
1590
- with col2:
1591
- st.metric("هامش الربح", f"{profit_margin:.1%}")
1592
  with col3:
1593
- st.metric("إجمالي مع الربح", f"{total_with_profit:,.2f} ريال")
1594
-
1595
- # إضافة شريط تمرير لتعديل هامش الربح
1596
- new_profit_margin = st.slider(
1597
- "تعديل هامش الربح",
1598
- min_value=0.0,
1599
- max_value=0.5,
1600
- value=profit_margin,
1601
- step=0.01,
1602
- format="%.2f",
1603
- key="profit_margin_slider"
1604
- )
1605
-
1606
- if new_profit_margin != profit_margin:
1607
- profit_margin = new_profit_margin
1608
- profit = total_cost * profit_margin
1609
- total_with_profit = total_cost + profit
1610
-
1611
- st.metric("إجمالي مع الربح الجديد", f"{total_with_profit:,.2f} ريال")
1612
-
1613
- # إنشاء رسم بياني للربحية
1614
- self._setup_arabic_fonts()
1615
-
1616
- # إنشاء بيانات الرسم البياني
1617
- fig, ax = plt.subplots(figsize=(10, 6))
1618
-
1619
- # إنشاء المخطط الدائري
1620
- labels_ar = ['التكلفة', 'الربح']
1621
- labels = [get_display(arabic_reshaper.reshape(label)) for label in labels_ar]
1622
-
1623
- values = [total_cost, profit]
1624
-
1625
- # رسم المخطط الدائري
1626
- wedges, texts, autotexts = ax.pie(
1627
- values,
1628
- labels=labels,
1629
- autopct='%1.1f%%',
1630
- startangle=90,
1631
- shadow=True,
1632
- colors=['#ff9999', '#66b3ff']
1633
- )
1634
-
1635
- # تعديل حجم النص
1636
- plt.setp(autotexts, size=10, weight="bold")
1637
-
1638
- # إضافة العنوان
1639
- title_ar = 'توزيع التكلفة والربح'
1640
- title = get_display(arabic_reshaper.reshape(title_ar))
1641
- ax.set_title(title, fontsize=16)
1642
-
1643
- # عرض الرسم البياني
1644
- st.pyplot(fig)
1645
-
1646
- def _render_pricing_strategies(self, project_info):
1647
- """عرض استراتيجيات التسعير"""
1648
- st.subheader("استراتيجيات التسعير")
1649
-
1650
- # الحصول على بنود المشروع الحالي
1651
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
1652
-
1653
- if not project_items:
1654
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
1655
- return
1656
-
1657
- # عرض استراتيجيات التسعير المختلفة
1658
- st.write("### استراتيجيات التسعير المتاحة")
1659
-
1660
- # استراتيجية التسعير القياسية
1661
- st.write("#### التسعير القياسي")
1662
- st.write("في هذه الاستراتيجية، يتم تسعير جميع البنود بناءً على التكلفة الفعلية مع إضافة هامش ربح موحد.")
1663
-
1664
- # استراتيجية التسعير الغير متزن
1665
- st.write("#### التسعير الغير متزن")
1666
- st.write("في هذه الاستراتيجية، يتم زيادة أسعار البنود المبكرة وتخفيض أسعار البنود المتأخرة للحصول على تدفق نقدي أفضل.")
1667
-
1668
- # تعديل معاملات التسعير الغير متزن
1669
- if project_info['pricing_type'] == 'غير متزن':
1670
- st.write("### تعديل معاملات التسعير الغير متزن")
1671
-
1672
- col1, col2 = st.columns(2)
1673
- with col1:
1674
- early_factor = st.slider(
1675
- "معامل البنود المبكرة",
1676
- min_value=1.0,
1677
- max_value=1.5,
1678
- value=st.session_state.unbalanced_pricing_factors['early_items_factor'],
1679
- step=0.05,
1680
- format="%.2f",
1681
- key="early_factor_slider"
1682
- )
1683
-
1684
- if early_factor != st.session_state.unbalanced_pricing_factors['early_items_factor']:
1685
- st.session_state.unbalanced_pricing_factors['early_items_factor'] = early_factor
1686
-
1687
- with col2:
1688
- late_factor = st.slider(
1689
- "معامل البنود المتأخرة",
1690
- min_value=0.5,
1691
- max_value=1.0,
1692
- value=st.session_state.unbalanced_pricing_factors['late_items_factor'],
1693
- step=0.05,
1694
- format="%.2f",
1695
- key="late_factor_slider"
1696
- )
1697
-
1698
- if late_factor != st.session_state.unbalanced_pricing_factors['late_items_factor']:
1699
- st.session_state.unbalanced_pricing_factors['late_items_factor'] = late_factor
1700
-
1701
- # عرض جدول الأسعار المعدلة
1702
- st.write("### الأسعار المعدلة باستخدام التسعير الغير متزن")
1703
-
1704
- # إنشاء نسخة من بنود المشروع
1705
- unbalanced_items = []
1706
-
1707
- # تقسيم البنود إلى مبكرة ومتأخرة
1708
- num_items = len(project_items)
1709
- early_items_count = num_items // 3
1710
- late_items_count = num_items // 3
1711
-
1712
- for i, item in enumerate(project_items):
1713
- unbalanced_item = item.copy()
1714
-
1715
- if i < early_items_count:
1716
- # بند مبكر
1717
- factor = st.session_state.unbalanced_pricing_factors['early_items_factor']
1718
- unbalanced_item['unbalanced_unit_price'] = item['unit_price'] * factor
1719
- unbalanced_item['unbalanced_total_price'] = unbalanced_item['unbalanced_unit_price'] * item['quantity']
1720
- elif i >= num_items - late_items_count:
1721
- # بند متأخر
1722
- factor = st.session_state.unbalanced_pricing_factors['late_items_factor']
1723
- unbalanced_item['unbalanced_unit_price'] = item['unit_price'] * factor
1724
- unbalanced_item['unbalanced_total_price'] = unbalanced_item['unbalanced_unit_price'] * item['quantity']
1725
- else:
1726
- # بند وسطي
1727
- unbalanced_item['unbalanced_unit_price'] = item['unit_price']
1728
- unbalanced_item['unbalanced_total_price'] = item['total_price']
1729
-
1730
- unbalanced_items.append(unbalanced_item)
1731
-
1732
- # إنشاء DataFrame من البنود المعدلة
1733
- df = pd.DataFrame(unbalanced_items)
1734
- df = df[['code', 'description', 'unit', 'quantity', 'unit_price', 'unbalanced_unit_price', 'total_price', 'unbalanced_total_price']]
1735
- df.columns = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة الأصلي', 'سعر الوحدة المعدل', 'السعر الإجمالي الأصلي', 'السعر الإجمالي المعدل']
1736
-
1737
- # تنسيق الأسعار
1738
- df['سعر الوحدة الأصلي'] = df['سعر الوحدة الأصلي'].apply(lambda x: f"{x:,.2f} ريال")
1739
- df['سعر الوحدة المعدل'] = df['سعر الوحدة المعدل'].apply(lambda x: f"{x:,.2f} ريال")
1740
- df['السعر الإجمالي الأصلي'] = df['السعر الإجمالي الأصلي'].apply(lambda x: f"{x:,.2f} ريال")
1741
- df['السعر الإجمالي المعدل'] = df['السعر الإجمالي المعدل'].apply(lambda x: f"{x:,.2f} ريال")
1742
-
1743
- # عرض الجدول
1744
- st.dataframe(df, use_container_width=True)
1745
-
1746
- # حساب إجمالي الأسعار
1747
- total_original = sum(item['total_price'] for item in project_items)
1748
- total_unbalanced = sum(item['unbalanced_total_price'] for item in unbalanced_items)
1749
-
1750
- col1, col2 = st.columns(2)
1751
- with col1:
1752
- st.metric("إجمالي السعر الأصلي", f"{total_original:,.2f} ريال")
1753
- with col2:
1754
- st.metric("إجمالي السعر المعدل", f"{total_unbalanced:,.2f} ريال")
1755
 
1756
- # زر تطبيق التسعير الغير متزن
1757
- if st.button("تطبيق التسعير الغير متزن", key="apply_unbalanced_pricing"):
1758
- for i, item in enumerate(unbalanced_items):
1759
- for j, boq_item in enumerate(st.session_state.boq_items):
1760
- if boq_item['id'] == item['id']:
1761
- st.session_state.boq_items[j]['unit_price'] = item['unbalanced_unit_price']
1762
- st.session_state.boq_items[j]['total_price'] = item['unbalanced_total_price']
1763
- break
1764
-
1765
- st.success("تم تطبيق التسعير الغير متزن بنجاح")
1766
  st.rerun()
1767
-
1768
- def _render_export_save_buttons(self, project_info):
1769
- """عرض أزرار التصدير والحفظ"""
1770
- st.subheader("تصدير وحفظ التسعير")
1771
-
1772
- col1, col2 = st.columns(2)
1773
- with col1:
1774
- if st.button("تصدير التسعير كملف Excel", key="export_pricing_excel"):
1775
- st.info("سيتم تنفيذ هذه الميزة في الإصدار القادم")
1776
-
1777
- with col2:
1778
- if st.button("حفظ التسعير", key="save_pricing"):
1779
- # حفظ التسعير الحالي
1780
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
1781
-
1782
- saved_pricing = {
1783
- 'project_id': project_info['id'],
1784
- 'project_name': project_info['name'],
1785
- 'saved_at': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
1786
- 'items': project_items
1787
- }
1788
-
1789
- st.session_state.saved_pricing.append(saved_pricing)
1790
-
1791
- # تحديث حالة المشروع
1792
- for i, p in enumerate(st.session_state.projects):
1793
- if p['id'] == project_info['id']:
1794
- st.session_state.projects[i]['status'] = 'تم التسعير'
1795
- break
1796
-
1797
- st.success("تم حفظ التسعير بنجاح")
1798
-
1799
  # تشغيل التطبيق
1800
- if __name__ == "__main__":
1801
- app = PricingApp()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1802
  app.run()
 
 
 
 
11
  import random
12
  import json
13
  import os
14
+ import time
15
 
16
+ class IntegratedPricingApp:
17
+ """وحدة التسعير المتكاملة مع تحليل الأسعار"""
18
 
19
  def __init__(self):
20
+ """تهيئة وحدة التسعير المتكاملة"""
21
  # تهيئة البيانات في حالة الجلسة إذا لم تكن موجودة
22
  if 'projects' not in st.session_state:
23
  st.session_state.projects = [
 
151
  if 'item_analysis_edited' not in st.session_state:
152
  st.session_state.item_analysis_edited = False
153
 
154
+ # تهيئة قائمة الوحدات المتاحة لتحليل الأسعار
155
+ self.unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"]
156
+
157
+ # تهيئة فئات التكاليف لتحليل الأسعار
158
+ self.cost_categories = [
159
+ "مواد",
160
+ "عمالة",
161
+ "معدات",
162
+ "مقاولي الباطن",
163
+ "مصاريف عامة",
164
+ "أرباح"
165
+ ]
166
+
167
+ # تهيئة قائمة تحليل أسعار البنود
168
+ if 'items_price_analysis' not in st.session_state:
169
+ st.session_state.items_price_analysis = {}
170
+
171
+ # تهيئة بيانات المحتوى المحلي
172
+ if 'local_content' not in st.session_state:
173
+ st.session_state.local_content = {}
174
+
175
+ # تهيئة بيانات المخاطر
176
+ if 'risk_analysis' not in st.session_state:
177
+ st.session_state.risk_analysis = {}
178
+
179
+ # تهيئة مرحلة العمل الحالية
180
+ if 'current_stage' not in st.session_state:
181
+ st.session_state.current_stage = 1
182
+
183
  def render(self):
184
  """طريقة للتوافق مع الواجهة القديمة"""
185
  self.run()
186
 
187
  def run(self):
188
+ """تشغيل وحدة التسعير المتكاملة"""
189
  st.title("وحدة التسعير المتكاملة")
190
 
191
  # عرض زر إنشاء تسعير جديد
 
211
  if project_info:
212
  self._render_project_info(project_info)
213
 
214
+ # عرض مراحل العمل
215
+ self._render_workflow_stages(project_info)
216
+
217
+ def _render_workflow_stages(self, project_info):
218
+ """عرض مراحل العمل"""
219
+ # تعريف المراحل
220
+ stages = [
221
+ "إنشاء تسعير جديد",
222
+ "تحليل الأسعار",
223
+ "المحتوى المحلي",
224
+ "تحليل المخاطر",
225
+ "جدول الكميات النهائي والتصدير"
226
+ ]
227
+
228
+ # عرض شريط التقدم
229
+ st.markdown("### مراحل العمل")
230
+ progress_cols = st.columns(len(stages))
231
+
232
+ for i, stage in enumerate(stages):
233
+ with progress_cols[i]:
234
+ if i + 1 < st.session_state.current_stage:
235
+ st.markdown(f"<div style='text-align: center; background-color: #0068c9; color: white; padding: 10px; border-radius: 5px;'>{stage} ✓</div>", unsafe_allow_html=True)
236
+ elif i + 1 == st.session_state.current_stage:
237
+ st.markdown(f"<div style='text-align: center; background-color: #ff4b4b; color: white; padding: 10px; border-radius: 5px;'>{stage} ⟳</div>", unsafe_allow_html=True)
238
+ else:
239
+ st.markdown(f"<div style='text-align: center; background-color: #f0f2f6; color: gray; padding: 10px; border-radius: 5px;'>{stage}</div>", unsafe_allow_html=True)
240
+
241
+ # عرض المحتوى حسب المرحلة الحالية
242
+ if st.session_state.current_stage == 1:
243
+ # المرحلة الأولى: إنشاء تسعير جديد
244
+ self._render_stage_1_new_pricing(project_info)
245
+ elif st.session_state.current_stage == 2:
246
+ # المرحلة الثانية: تحليل الأسعار
247
+ self._render_stage_2_price_analysis(project_info)
248
+ elif st.session_state.current_stage == 3:
249
+ # المرحلة الثالثة: المحتوى المحلي
250
+ self._render_stage_3_local_content(project_info)
251
+ elif st.session_state.current_stage == 4:
252
+ # المرحلة الرابعة: تحليل المخاطر
253
+ self._render_stage_4_risk_analysis(project_info)
254
+ elif st.session_state.current_stage == 5:
255
+ # المرحلة الخامسة: جدول الكميات النهائي والتصدير
256
+ self._render_stage_5_final_boq(project_info)
257
+
258
+ def _render_stage_1_new_pricing(self, project_info):
259
+ """عرض المرحلة الأولى: إنشاء تسعير جديد"""
260
+ st.markdown("## المرحلة الأولى: إنشاء تسعير جديد")
261
+
262
+ # عرض جدول الكميات الأولي
263
+ self._render_bill_of_quantities()
264
+
265
+ # زر الانتقال للمرحلة التالية
266
+ col1, col2, col3 = st.columns([2, 1, 2])
267
+ with col2:
268
+ if st.button("الانتقال للمرحلة التالية ⟶", key="next_stage_1", type="primary"):
269
+ # التحقق من وجود بنود في جدول الكميات
270
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
271
+ if not project_items:
272
+ st.error("يجب إضافة بند واحد على الأقل في جدول الكميات قبل الانتقال للمرحلة التالية")
273
+ else:
274
+ st.session_state.current_stage = 2
275
+ st.rerun()
276
 
277
+ def _render_stage_2_price_analysis(self, project_info):
278
+ """عرض المرحلة الثانية: تحليل الأسعار"""
279
+ st.markdown("## المرحلة الثانية: تحليل الأسعار")
280
+
281
+ # الحصول على بنود المشروع الحالي
282
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
283
+
284
+ if not project_items:
285
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
286
+ return
287
+
288
+ # اختيار البند للتحليل
289
+ selected_item_id = st.selectbox(
290
+ "اختر البند للتحليل",
291
+ options=[item['id'] for item in project_items],
292
+ format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
293
+ key="select_item_for_analysis_stage2"
294
+ )
295
+
296
+ # الحصول على البند المحدد
297
+ selected_item = next((item for item in project_items if item['id'] == selected_item_id), None)
298
+
299
+ if not selected_item:
300
+ st.error("لم يتم العثور على البند المحدد.")
301
+ return
302
+
303
+ # عرض معلومات البند
304
+ col1, col2 = st.columns(2)
305
+ with col1:
306
+ st.write(f"**البند:** {selected_item['description']}")
307
+ st.write(f"**الكود:** {selected_item['code']}")
308
+ st.write(f"**الوحدة:** {selected_item['unit']}")
309
+ with col2:
310
+ st.write(f"**الكمية:** {selected_item['quantity']}")
311
+ st.write(f"**سعر الوحدة:** {selected_item['unit_price']:,.2f} ريال")
312
+ st.write(f"**السعر الإجمالي:** {selected_item['total_price']:,.2f} ريال")
313
+
314
+ # إنشاء تحليل سعر البند إذا لم يكن موجوداً
315
+ if selected_item_id not in st.session_state.items_price_analysis:
316
+ self._create_default_price_analysis(selected_item_id, selected_item)
317
+
318
+ # عرض تحليل السعر
319
+ self._render_price_analysis_editor(selected_item_id, selected_item)
320
+
321
+ # أزرار التنقل بين المراحل
322
+ col1, col2, col3 = st.columns(3)
323
+ with col1:
324
+ if st.button("⟵ العودة للمرحلة السابقة", key="prev_stage_2"):
325
+ st.session_state.current_stage = 1
326
+ st.rerun()
327
+ with col3:
328
+ if st.button("الانتقال للمرحلة التالية ⟶", key="next_stage_2", type="primary"):
329
+ st.session_state.current_stage = 3
330
+ st.rerun()
331
+
332
+ def _render_stage_3_local_content(self, project_info):
333
+ """عرض المرحلة الثالثة: المحتوى المحلي"""
334
+ st.markdown("## المرحلة الثالثة: المحتوى المحلي")
335
+
336
+ # الحصول على بنود المشروع الحالي
337
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
338
+
339
+ if not project_items:
340
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
341
+ return
342
+
343
+ # تهيئة بيانات المحتوى المحلي للمشروع الحالي إذا لم تكن موجودة
344
+ project_id = st.session_state.current_project
345
+ if project_id not in st.session_state.local_content:
346
+ st.session_state.local_content[project_id] = {}
347
+ for item in project_items:
348
+ st.session_state.local_content[project_id][item['id']] = {
349
+ 'local_materials_percentage': 50,
350
+ 'local_labor_percentage': 90,
351
+ 'local_equipment_percentage': 30,
352
+ 'local_subcontractors_percentage': 80,
353
+ 'notes': ''
354
+ }
355
+
356
+ # عرض جدول المحتوى المحلي
357
+ st.markdown("### تحديد نسب المحتوى المحلي لكل بند")
358
+
359
+ # إنشاء DataFrame للعرض والتعديل
360
+ local_content_data = []
361
+ for item in project_items:
362
+ item_id = item['id']
363
+ if item_id in st.session_state.local_content[project_id]:
364
+ local_content_data.append({
365
+ 'رقم البند': item_id,
366
+ 'وصف البند': item['description'],
367
+ 'نسبة المواد المحلية (%)': st.session_state.local_content[project_id][item_id]['local_materials_percentage'],
368
+ 'نسبة العمالة المحلية (%)': st.session_state.local_content[project_id][item_id]['local_labor_percentage'],
369
+ 'نسبة المعدات المحلية (%)': st.session_state.local_content[project_id][item_id]['local_equipment_percentage'],
370
+ 'نسبة المقاولين المحليين (%)': st.session_state.local_content[project_id][item_id]['local_subcontractors_percentage'],
371
+ 'ملاحظات': st.session_state.local_content[project_id][item_id]['notes']
372
+ })
373
+
374
+ # إنشاء DataFrame
375
+ df = pd.DataFrame(local_content_data)
376
+
377
+ # عرض الجدول للتعديل
378
+ edited_df = st.data_editor(
379
+ df,
380
+ use_container_width=True,
381
+ column_config={
382
+ 'رقم البند': st.column_config.Column('رقم البند', disabled=True),
383
+ 'وصف البند': st.column_config.Column('وصف البند', disabled=True),
384
+ 'نسبة المواد المحلية (%)': st.column_config.NumberColumn(
385
+ 'نسبة المواد المحلية (%)',
386
+ min_value=0,
387
+ max_value=100,
388
+ step=1,
389
+ format="%d"
390
+ ),
391
+ 'نسبة العمالة المحلية (%)': st.column_config.NumberColumn(
392
+ 'نسبة العمالة المحلية (%)',
393
+ min_value=0,
394
+ max_value=100,
395
+ step=1,
396
+ format="%d"
397
+ ),
398
+ 'نسبة المعدات المحلية (%)': st.column_config.NumberColumn(
399
+ 'نسبة المعدات المحلية (%)',
400
+ min_value=0,
401
+ max_value=100,
402
+ step=1,
403
+ format="%d"
404
+ ),
405
+ 'نسبة المقاولين المحليين (%)': st.column_config.NumberColumn(
406
+ 'نسبة المقاولين المحليين (%)',
407
+ min_value=0,
408
+ max_value=100,
409
+ step=1,
410
+ format="%d"
411
+ ),
412
+ 'ملاحظات': st.column_config.TextColumn('ملاحظات')
413
+ }
414
+ )
415
+
416
+ # تحديث البيانات في حالة التعديل
417
+ if edited_df is not None and not edited_df.equals(df):
418
+ for _, row in edited_df.iterrows():
419
+ item_id = row['رقم البند']
420
+ st.session_state.local_content[project_id][item_id] = {
421
+ 'local_materials_percentage': row['نسبة المواد المحلية (%)'],
422
+ 'local_labor_percentage': row['نسبة العمالة المحلية (%)'],
423
+ 'local_equipment_percentage': row['نسبة المعدات المحلية (%)'],
424
+ 'local_subcontractors_percentage': row['نسبة المقاولين المحليين (%)'],
425
+ 'notes': row['ملاحظات']
426
+ }
427
+
428
+ # حساب وعرض إجمالي المحتوى المحلي للمشروع
429
+ total_materials = 0
430
+ total_labor = 0
431
+ total_equipment = 0
432
+ total_subcontractors = 0
433
+
434
+ for item in project_items:
435
+ item_id = item['id']
436
+ if item_id in st.session_state.local_content[project_id]:
437
+ total_materials += st.session_state.local_content[project_id][item_id]['local_materials_percentage']
438
+ total_labor += st.session_state.local_content[project_id][item_id]['local_labor_percentage']
439
+ total_equipment += st.session_state.local_content[project_id][item_id]['local_equipment_percentage']
440
+ total_subcontractors += st.session_state.local_content[project_id][item_id]['local_subcontractors_percentage']
441
+
442
+ # حساب المتوسط
443
+ num_items = len(project_items)
444
+ if num_items > 0:
445
+ avg_materials = total_materials / num_items
446
+ avg_labor = total_labor / num_items
447
+ avg_equipment = total_equipment / num_items
448
+ avg_subcontractors = total_subcontractors / num_items
449
+
450
+ # حساب المتوسط الإجمالي
451
+ overall_avg = (avg_materials + avg_labor + avg_equipment + avg_subcontractors) / 4
452
+
453
+ # عرض الإحصائيات
454
+ st.markdown("### إحصائيات المحتوى المحلي للمشروع")
455
+ col1, col2, col3, col4 = st.columns(4)
456
+
457
+ with col1:
458
+ st.metric("متوسط المواد المحلية", f"{avg_materials:.1f}%")
459
+ with col2:
460
+ st.metric("متوسط العمالة المحلية", f"{avg_labor:.1f}%")
461
+ with col3:
462
+ st.metric("متوسط المعدات المحلية", f"{avg_equipment:.1f}%")
463
+ with col4:
464
+ st.metric("متوسط المقاولين المحليين", f"{avg_subcontractors:.1f}%")
465
+
466
+ # عرض المتوسط الإجمالي
467
+ st.metric("متوسط المحتوى المحلي الإجمالي للمشروع", f"{overall_avg:.1f}%")
468
+
469
+ # أزرار التنقل بين المراحل
470
+ col1, col2, col3 = st.columns(3)
471
+ with col1:
472
+ if st.button("⟵ العودة للمرحلة السابقة", key="prev_stage_3"):
473
+ st.session_state.current_stage = 2
474
+ st.rerun()
475
+ with col3:
476
+ if st.button("الانتقال للمرحلة التالية ⟶", key="next_stage_3", type="primary"):
477
+ st.session_state.current_stage = 4
478
+ st.rerun()
479
+
480
+ def _render_stage_4_risk_analysis(self, project_info):
481
+ """عرض المرحلة الرابعة: تحليل المخاطر"""
482
+ st.markdown("## المرحلة الرابعة: تحليل المخاطر")
483
+
484
+ # تهيئة بيانات المخاطر للمشروع الحالي إذا لم تكن موجودة
485
+ project_id = st.session_state.current_project
486
+ if project_id not in st.session_state.risk_analysis:
487
+ st.session_state.risk_analysis[project_id] = {
488
+ 'risks': [
489
+ {
490
+ 'id': 1,
491
+ 'description': 'تأخر توريد المواد',
492
+ 'probability': 'متوسطة',
493
+ 'impact': 'عالي',
494
+ 'mitigation': 'التعاقد مع موردين بديلين',
495
+ 'contingency_percentage': 5
496
+ },
497
+ {
498
+ 'id': 2,
499
+ 'description': 'تغير أسعار المواد',
500
+ 'probability': 'عالية',
501
+ 'impact': 'عالي',
502
+ 'mitigation': 'إضافة بند تعديل الأسعار في العقد',
503
+ 'contingency_percentage': 8
504
+ },
505
+ {
506
+ 'id': 3,
507
+ 'description': 'ظروف جوية غير مواتية',
508
+ 'probability': 'منخفضة',
509
+ 'impact': 'متوسط',
510
+ 'mitigation': 'جدولة الأعمال الخارجية في المواسم المناسبة',
511
+ 'contingency_percentage': 3
512
+ }
513
+ ],
514
+ 'overall_contingency': 10
515
+ }
516
+
517
+ # عرض جدول المخاطر
518
+ st.markdown("### تحليل المخاطر وخطط التخفيف")
519
+
520
+ # إنشاء DataFrame للعرض والتعديل
521
+ risk_data = []
522
+ for risk in st.session_state.risk_analysis[project_id]['risks']:
523
+ risk_data.append({
524
+ 'الرقم': risk['id'],
525
+ 'وصف المخاطرة': risk['description'],
526
+ 'احتمالية الحدوث': risk['probability'],
527
+ 'التأثير': risk['impact'],
528
+ 'إجراءات التخفيف': risk['mitigation'],
529
+ 'نسبة الطوارئ (%)': risk['contingency_percentage']
530
+ })
531
+
532
+ # إنشاء DataFrame
533
+ df = pd.DataFrame(risk_data)
534
+
535
+ # عرض الجدول للتعديل
536
+ edited_df = st.data_editor(
537
+ df,
538
+ use_container_width=True,
539
+ num_rows="dynamic",
540
+ column_config={
541
+ 'الرقم': st.column_config.Column('الرقم', disabled=True),
542
+ 'وصف المخاطرة': st.column_config.TextColumn('وصف المخاطرة'),
543
+ 'احتمالية الحدوث': st.column_config.SelectboxColumn(
544
+ 'احتمالية الحدوث',
545
+ options=['منخفضة', 'متوسطة', 'عالية']
546
+ ),
547
+ 'التأثير': st.column_config.SelectboxColumn(
548
+ 'التأثير',
549
+ options=['منخفض', 'متوسط', 'عالي']
550
+ ),
551
+ 'إجراءات التخفيف': st.column_config.TextColumn('إجراءات التخفيف'),
552
+ 'نسبة الطوارئ (%)': st.column_config.NumberColumn(
553
+ 'نسبة الطوارئ (%)',
554
+ min_value=0,
555
+ max_value=100,
556
+ step=0.5,
557
+ format="%.1f"
558
+ )
559
+ }
560
+ )
561
+
562
+ # تحديث البيانات في حالة التعديل
563
+ if edited_df is not None and not edited_df.equals(df):
564
+ # حذف المخاطر الحالية
565
+ st.session_state.risk_analysis[project_id]['risks'] = []
566
+
567
+ # إضافة المخاطر المعدلة
568
+ next_risk_id = 1
569
+ for _, row in edited_df.iterrows():
570
+ st.session_state.risk_analysis[project_id]['risks'].append({
571
+ 'id': next_risk_id,
572
+ 'description': row['وصف المخاطرة'],
573
+ 'probability': row['احتمالية الحدوث'],
574
+ 'impact': row['التأثير'],
575
+ 'mitigation': row['إجراءات التخفيف'],
576
+ 'contingency_percentage': row['نسبة الطوارئ (%)']
577
+ })
578
+ next_risk_id += 1
579
+
580
+ # تعديل نسبة الطوارئ الإجمالية
581
+ st.markdown("### نسبة الطوارئ الإجمالية للمشروع")
582
+ overall_contingency = st.slider(
583
+ "نسبة الطوارئ الإجمالية (%)",
584
+ min_value=0.0,
585
+ max_value=30.0,
586
+ value=float(st.session_state.risk_analysis[project_id]['overall_contingency']),
587
+ step=0.5,
588
+ format="%.1f"
589
+ )
590
+
591
+ # تحديث نسبة الطوارئ الإجمالية
592
+ if overall_contingency != st.session_state.risk_analysis[project_id]['overall_contingency']:
593
+ st.session_state.risk_analysis[project_id]['overall_contingency'] = overall_contingency
594
+
595
+ # حساب وعرض تأثير المخاطر على التكلفة
596
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
597
+ total_price = sum(item['total_price'] for item in project_items)
598
+ contingency_amount = total_price * (overall_contingency / 100)
599
+
600
+ col1, col2, col3 = st.columns(3)
601
+ with col1:
602
+ st.metric("إجمالي تكلفة المشروع", f"{total_price:,.2f} ريال")
603
+ with col2:
604
+ st.metric("مبلغ الطوارئ", f"{contingency_amount:,.2f} ريال")
605
+ with col3:
606
+ st.metric("التكلفة الإجمالية مع الطوارئ", f"{(total_price + contingency_amount):,.2f} ريال")
607
 
608
+ # أزرار التنقل بين المراحل
609
+ col1, col2, col3 = st.columns(3)
610
+ with col1:
611
+ if st.button("⟵ العودة للمرحلة السابقة", key="prev_stage_4"):
612
+ st.session_state.current_stage = 3
613
+ st.rerun()
614
+ with col3:
615
+ if st.button("الانتقال للمرحلة التالية ⟶", key="next_stage_4", type="primary"):
616
+ st.session_state.current_stage = 5
617
+ st.rerun()
618
+
619
+ def _render_stage_5_final_boq(self, project_info):
620
+ """عرض المرحلة الخامسة: جدول الكميات النهائي والتصدير"""
621
+ st.markdown("## المرحلة الخامسة: جدول الكميات النهائي والتصدير")
622
+
623
+ # الحصول على بنود المشروع الحالي
624
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
625
+
626
+ if not project_items:
627
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
628
+ return
629
+
630
+ # حساب إجمالي تكلفة المشروع
631
+ total_price = sum(item['total_price'] for item in project_items)
632
+
633
+ # حساب مبلغ الطوارئ
634
+ project_id = st.session_state.current_project
635
+ if project_id in st.session_state.risk_analysis:
636
+ contingency_percentage = st.session_state.risk_analysis[project_id]['overall_contingency']
637
+ else:
638
+ contingency_percentage = 10 # قيمة افتراضية
639
+
640
+ contingency_amount = total_price * (contingency_percentage / 100)
641
+
642
+ # إنشاء DataFrame للعرض النهائي
643
+ final_boq_data = []
644
+ for item in project_items:
645
+ final_boq_data.append({
646
+ 'الكود': item['code'],
647
+ 'وصف البند': item['description'],
648
+ 'الوحدة': item['unit'],
649
+ 'الكمية': item['quantity'],
650
+ 'سعر الوحدة': item['unit_price'],
651
+ 'السعر الإجمالي': item['total_price']
652
+ })
653
+
654
+ # إنشاء DataFrame
655
+ df = pd.DataFrame(final_boq_data)
656
+
657
+ # عرض جدول الكميات النهائي
658
+ st.markdown("### جدول الكميات النهائي")
659
+ st.dataframe(
660
+ df,
661
+ use_container_width=True,
662
+ column_config={
663
+ 'الكود': st.column_config.Column('الكود'),
664
+ 'وصف البند': st.column_config.Column('وصف البند'),
665
+ 'الوحدة': st.column_config.Column('الوحدة'),
666
+ 'الكمية': st.column_config.NumberColumn('الكمية', format="%.2f"),
667
+ 'سعر الوحدة': st.column_config.NumberColumn('سعر الوحدة', format="%.2f ريال"),
668
+ 'السعر الإجمالي': st.column_config.NumberColumn('السعر الإجمالي', format="%.2f ريال")
669
+ }
670
+ )
671
+
672
+ # عرض ملخص التكلفة
673
+ st.markdown("### ملخص التكلفة")
674
+ col1, col2, col3 = st.columns(3)
675
+ with col1:
676
+ st.metric("إجمالي تكلفة المشروع", f"{total_price:,.2f} ريال")
677
+ with col2:
678
+ st.metric("مبلغ الطوارئ", f"{contingency_amount:,.2f} ريال")
679
+ with col3:
680
+ st.metric("التكلفة الإجمالية مع الطوارئ", f"{(total_price + contingency_amount):,.2f} ريال")
681
+
682
+ # أزرار التصدير
683
+ st.markdown("### تصدير البيانات")
684
+ col1, col2, col3 = st.columns(3)
685
+
686
+ with col1:
687
+ if st.button("تصدير جدول الكميات (CSV)", key="export_csv_btn", use_container_width=True):
688
+ # إنشاء CSV للتصدير
689
+ csv = df.to_csv(index=False)
690
+ b64 = base64.b64encode(csv.encode()).decode()
691
+ href = f'<a href="data:file/csv;base64,{b64}" download="boq_{st.session_state.current_project}.csv">تحميل CSV</a>'
692
+ st.markdown(href, unsafe_allow_html=True)
693
+
694
+ with col2:
695
+ if st.button("تصدير تقرير المشروع (PDF)", key="export_pdf_btn", use_container_width=True):
696
+ st.info("جاري إعداد تقرير PDF...")
697
+ # هنا يمكن إضافة كود لإنشاء ملف PDF
698
+
699
+ with col3:
700
+ if st.button("حفظ التسعير النهائي", key="save_final_pricing_btn", use_container_width=True, type="primary"):
701
+ # تحديث حالة المشروع
702
+ for i, p in enumerate(st.session_state.projects):
703
+ if p['id'] == st.session_state.current_project:
704
+ st.session_state.projects[i]['status'] = 'تم التسعير'
705
+ break
706
+
707
+ st.success("تم حفظ التسعير النهائي بنجاح!")
708
+ time.sleep(1)
709
+ st.rerun()
710
+
711
+ # أزرار التنقل بين المراحل
712
+ col1, col2 = st.columns(2)
713
+ with col1:
714
+ if st.button("⟵ العودة للمرحلة السابقة", key="prev_stage_5"):
715
+ st.session_state.current_stage = 4
716
+ st.rerun()
717
+
718
  def _render_new_project_form(self):
719
  """عرض نموذج إنشاء مشروع جديد"""
720
  st.subheader("إنشاء تسعير جديد")
 
753
  st.session_state.current_project = new_project['id']
754
  st.session_state.next_project_id += 1
755
  st.session_state.show_new_project_form = False
756
+ st.session_state.current_stage = 1 # إعادة تعيين المرحلة الحالية
757
  st.rerun()
758
 
759
  if cancel_button:
 
1279
  st.session_state.show_resource_selector = False
1280
  st.rerun()
1281
 
1282
+ def _create_default_price_analysis(self, item_id, item):
1283
+ """إنشاء تحليل سعر افتراضي للبند"""
1284
+ # إنشاء قائمة مكونات تحليل السعر
1285
+ components = pd.DataFrame(columns=[
1286
+ 'نوع التكلفة', 'الوصف', 'الكمية', 'الوحدة', 'سعر الوحدة', 'الإجمالي'
1287
+ ])
1288
+
1289
+ # إضافة مكونات افتراضية بناءً على نوع البند
1290
+ is_concrete = 'خرسان' in item['description']
1291
+ is_steel = 'حديد' in item['description'] or 'تسليح' in item['description']
1292
+ is_bricks = 'بلوك' in item['description'] or 'طوب' in item['description']
1293
+ is_paint = 'دهان' in item['description'] or 'طلاء' in item['description']
1294
+ is_insulation = 'عزل' in item['description']
1295
+
1296
+ # إضافة المكونات بناءً على نوع البند
1297
+ if is_concrete:
1298
+ # مكونات الخرسانة
1299
+ default_components = pd.DataFrame({
1300
+ 'نوع التكلفة': ['مواد', 'مواد', 'مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
1301
+ 'الوصف': ['أسمنت', 'رمل', 'حصى', 'عمال وفنيين', 'خلاطات ومعدات صب', 'مصاريف عامة', 'أرباح'],
1302
+ 'الكمية': [350, 0.4, 0.8, 8, 1, 1, 1],
1303
+ 'الوحدة': ['كجم', 'م3', 'م3', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
1304
+ 'سعر الوحدة': [0.5, 100, 120, 50, 500, 100, 150],
1305
+ 'الإجمالي': [175, 40, 96, 400, 500, 100, 150]
1306
+ })
1307
+ components = pd.concat([components, default_components], ignore_index=True)
1308
+
1309
+ elif is_steel:
1310
+ # مكونات الحديد
1311
+ default_components = pd.DataFrame({
1312
+ 'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
1313
+ 'الوصف': ['حديد التسليح', 'عمال وفنيين', 'معدات ثني وتجهيز الحديد', 'مصاريف عامة', 'أرباح'],
1314
+ 'الكمية': [1000, 10, 1, 1, 1],
1315
+ 'الوحدة': ['كجم', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
1316
+ 'سعر الوحدة': [4.5, 50, 300, 200, 300],
1317
+ 'الإجمالي': [4500, 500, 300, 200, 300]
1318
+ })
1319
+ components = pd.concat([components, default_components], ignore_index=True)
1320
+
1321
+ elif is_bricks:
1322
+ # مكونات البلوك
1323
+ default_components = pd.DataFrame({
1324
+ 'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
1325
+ 'الوصف': ['بلوك خرساني', 'مونة', 'عمالة بناء', 'مصاريف عامة', 'أرباح'],
1326
+ 'الكمية': [12.5, 0.02, 1, 1, 1],
1327
+ 'الوحدة': ['قطعة', 'م3', 'م2', 'وحدة', 'وحدة'],
1328
+ 'سعر الوحدة': [8, 500, 80, 15, 20],
1329
+ 'الإجمالي': [100, 10, 80, 15, 20]
1330
+ })
1331
+ components = pd.concat([components, default_components], ignore_index=True)
1332
+
1333
+ elif is_paint:
1334
+ # مكونات الدهانات
1335
+ default_components = pd.DataFrame({
1336
+ 'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
1337
+ 'الوصف': ['دهان', 'مواد تجهيز', 'عمالة دهان', 'مصاريف عامة', 'أرباح'],
1338
+ 'الكمية': [0.4, 0.1, 1, 1, 1],
1339
+ 'الوحدة': ['لتر', 'وحدة', 'م2', 'وحدة', 'وحدة'],
1340
+ 'سعر الوحدة': [80, 20, 35, 5, 10],
1341
+ 'الإجمالي': [32, 2, 35, 5, 10]
1342
+ })
1343
+ components = pd.concat([components, default_components], ignore_index=True)
1344
+
1345
+ elif is_insulation:
1346
+ # مكونات العزل
1347
+ default_components = pd.DataFrame({
1348
+ 'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
1349
+ 'الوصف': ['مواد عازلة', 'مواد لاصقة', 'عمالة تركيب', 'مصاريف عامة', 'أرباح'],
1350
+ 'الكمية': [1.1, 0.2, 1, 1, 1],
1351
+ 'الوحدة': ['م2', 'كجم', 'م2', 'وحدة', 'وحدة'],
1352
+ 'سعر الوحدة': [60, 30, 25, 10, 15],
1353
+ 'الإجمالي': [66, 6, 25, 10, 15]
1354
+ })
1355
+ components = pd.concat([components, default_components], ignore_index=True)
1356
+
1357
+ else:
1358
+ # مكونات عامة افتراضية
1359
+ default_components = pd.DataFrame({
1360
+ 'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
1361
+ 'الوصف': ['مواد أساسية', 'عمالة', 'معدات ومعد مساعدة', 'مصاريف عامة', 'أرباح'],
1362
+ 'الكمية': [1, 1, 1, 1, 1],
1363
+ 'الوحدة': [item['unit'], 'وحدة', 'وحدة', 'وحدة', 'وحدة'],
1364
+ 'سعر الوحدة': [
1365
+ item['unit_price'] * 0.6,
1366
+ item['unit_price'] * 0.2,
1367
+ item['unit_price'] * 0.1,
1368
+ item['unit_price'] * 0.05,
1369
+ item['unit_price'] * 0.05
1370
+ ],
1371
+ 'الإجمالي': [
1372
+ item['unit_price'] * 0.6,
1373
+ item['unit_price'] * 0.2,
1374
+ item['unit_price'] * 0.1,
1375
+ item['unit_price'] * 0.05,
1376
+ item['unit_price'] * 0.05
1377
+ ]
1378
+ })
1379
+ components = pd.concat([components, default_components], ignore_index=True)
1380
+
1381
+ # حفظ تحليل السعر للبند
1382
+ st.session_state.items_price_analysis[item_id] = components
1383
+
1384
+ def _render_price_analysis_editor(self, item_id, item):
1385
+ """عرض محرر تحليل السعر للبند"""
1386
+ st.markdown("### تحليل السعر")
1387
 
1388
+ # الحصول على مكونات تحليل السعر
1389
+ components = st.session_state.items_price_analysis[item_id]
1390
 
1391
+ # عرض تحليل السعر في محرر بيانات
1392
+ st.markdown("#### مكونات السعر")
 
 
 
 
 
 
 
 
 
1393
 
1394
+ edited_components = st.data_editor(
1395
+ components,
1396
+ use_container_width=True,
1397
+ hide_index=True,
1398
+ num_rows="dynamic",
1399
+ column_config={
1400
+ 'نوع التكلفة': st.column_config.SelectboxColumn(
1401
+ 'نوع التكلفة',
1402
+ help='فئة التكلفة',
1403
+ options=self.cost_categories
1404
+ ),
1405
+ 'الوحدة': st.column_config.SelectboxColumn(
1406
+ 'الوحدة',
1407
+ help='وحدة القياس',
1408
+ options=self.unit_options + ["وحدة", "ساعة", "يوم"]
1409
+ ),
1410
+ 'الكمية': st.column_config.NumberColumn(
1411
+ 'الكمية',
1412
+ help='الكمية',
1413
+ min_value=0.0,
1414
+ format="%.2f"
1415
+ ),
1416
+ 'سعر الوحدة': st.column_config.NumberColumn(
1417
+ 'سعر الوحدة',
1418
+ help='سعر الوحدة',
1419
+ min_value=0.0,
1420
+ format="%.2f"
1421
+ ),
1422
+ 'الإجمالي': st.column_config.NumberColumn(
1423
+ 'الإجمالي',
1424
+ help='الإجمالي',
1425
+ min_value=0.0,
1426
+ format="%.2f"
1427
+ )
1428
+ }
1429
+ )
1430
 
1431
+ # إعادة حساب الإجمالي لكل مكون
1432
+ edited_components['الإجمالي'] = edited_components['الكمية'] * edited_components['سعر الوحدة']
 
 
 
 
 
 
 
 
 
 
 
 
1433
 
1434
+ # حفظ التعديلات
1435
+ st.session_state.items_price_analysis[item_id] = edited_components
1436
 
1437
+ # حساب إجمالي تحليل السعر
1438
+ total_analysis_price = edited_components['الإجمالي'].sum()
1439
+ unit_price_from_analysis = total_analysis_price / item['quantity'] if item['quantity'] > 0 else 0
1440
 
1441
+ # عرض ملخص تحليل السعر
1442
+ st.markdown("#### ملخص تحليل السعر")
1443
 
1444
+ col1, col2, col3 = st.columns(3)
 
1445
 
1446
+ with col1:
1447
+ st.metric("إجمالي تكلفة البند من التحليل", f"{total_analysis_price:,.2f} ريال")
1448
 
1449
+ with col2:
1450
+ st.metric("سعر الوحدة من التحليل", f"{unit_price_from_analysis:,.2f} ريال")
1451
 
1452
+ with col3:
1453
+ # المقارنة مع السعر الأصلي
1454
+ diff = unit_price_from_analysis - item['unit_price']
1455
+ st.metric(
1456
+ "الفرق عن السعر الأصلي",
1457
+ f"{diff:,.2f} ريال",
1458
+ delta=f"{(diff/item['unit_price']*100) if item['unit_price'] > 0 else 0:.1f}%"
1459
+ )
1460
 
1461
+ # تحليل توزيع التكاليف حسب الفئة
1462
+ cost_by_category = edited_components.groupby('نوع التكلفة')['الإجمالي'].sum().reset_index()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1463
 
1464
+ # عرض مخطط توزيع التكاليف
1465
+ st.markdown("#### توزيع التكاليف حسب الفئة")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1466
 
1467
+ # عرض توزيع التكاليف في جدول
1468
+ distribution_df = pd.DataFrame({
1469
+ 'نوع التكلفة': cost_by_category['نوع التكلفة'],
1470
+ 'القيمة': cost_by_category['الإجمالي'],
1471
+ 'النسبة المئوية': (cost_by_category['الإجمالي'] / total_analysis_price * 100).round(2)
1472
+ })
 
 
 
 
 
 
 
 
 
 
 
1473
 
1474
+ st.dataframe(
1475
+ distribution_df,
1476
+ use_container_width=True,
1477
+ hide_index=True,
1478
+ column_config={
1479
+ 'القيمة': st.column_config.NumberColumn(
1480
+ 'القيمة',
1481
+ help='القيمة',
1482
+ format="%.2f"
1483
+ ),
1484
+ 'النسبة المئوية': st.column_config.ProgressColumn(
1485
+ 'النسبة المئوية',
1486
+ help='النسبة المئوية',
1487
+ format="%.2f%%",
1488
+ min_value=0,
1489
+ max_value=100
1490
+ )
1491
  }
1492
+ )
1493
 
1494
+ # أزرار الإجراءات
1495
+ col1, col2, col3 = st.columns(3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1496
 
1497
+ with col1:
1498
+ if st.button("تحديث سعر البند", use_container_width=True):
1499
+ # تحديث سعر البند بناءً على تحليل السعر
1500
+ for i, itm in enumerate(st.session_state.boq_items):
1501
+ if itm['id'] == item_id:
1502
+ st.session_state.boq_items[i]['unit_price'] = unit_price_from_analysis
1503
+ st.session_state.boq_items[i]['total_price'] = unit_price_from_analysis * itm['quantity']
1504
+ break
1505
+
1506
+ st.success(f"تم تحديث سعر البند بناءً على تحليل السعر: {unit_price_from_analysis:,.2f} ريال")
1507
+ time.sleep(0.5)
 
 
 
 
 
 
1508
  st.rerun()
 
 
 
 
 
1509
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1510
  with col2:
1511
+ if st.button("تصدير تحليل السعر", use_container_width=True):
1512
+ st.success("تم إرسال تحليل السعر للتصدير بنجاح!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1513
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1514
  with col3:
1515
+ if st.button("مسح تحليل السعر", use_container_width=True):
1516
+ # حذف تحليل السعر للبند
1517
+ if item_id in st.session_state.items_price_analysis:
1518
+ del st.session_state.items_price_analysis[item_id]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1519
 
1520
+ st.warning("تم مسح تحليل السعر للبند")
1521
+ time.sleep(0.5)
 
 
 
 
 
 
 
 
1522
  st.rerun()
1523
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1524
  # تشغيل التطبيق
1525
+ def main():
1526
+ # ضبط CSS لتحسين ظهور الواجهة العربية
1527
+ st.set_page_config(
1528
+ page_title="وحدة التسعير المتكاملة",
1529
+ page_icon="💰",
1530
+ layout="wide",
1531
+ initial_sidebar_state="collapsed"
1532
+ )
1533
+
1534
+ # إضافة CSS للواجهة العربية
1535
+ st.markdown("""
1536
+ <style>
1537
+ body {
1538
+ direction: rtl;
1539
+ }
1540
+ .stTextInput > div > div > input {
1541
+ text-align: right;
1542
+ direction: rtl;
1543
+ }
1544
+ .stNumberInput > div > div > input {
1545
+ text-align: right;
1546
+ direction: rtl;
1547
+ }
1548
+ .stTextArea > div > div > textarea {
1549
+ text-align: right;
1550
+ direction: rtl;
1551
+ }
1552
+ .stSelectbox > div > div > div {
1553
+ text-align: right;
1554
+ direction: rtl;
1555
+ }
1556
+ .stMultiselect > div > div > div {
1557
+ text-align: right;
1558
+ direction: rtl;
1559
+ }
1560
+ .stHeader, .stSubheader, p, .stMarkdown, h1, h2, h3, h4, h5, h6 {
1561
+ text-align: right;
1562
+ direction: rtl;
1563
+ }
1564
+ button {
1565
+ direction: rtl;
1566
+ }
1567
+ .stDataFrame {
1568
+ direction: rtl;
1569
+ }
1570
+ .stDataFrame > div {
1571
+ direction: rtl;
1572
+ }
1573
+ .stDataFrame th {
1574
+ text-align: right;
1575
+ }
1576
+ .stDataFrame td {
1577
+ text-align: right;
1578
+ }
1579
+ .stTabs [data-baseweb="tab-list"] {
1580
+ direction: rtl;
1581
+ }
1582
+ .stTabs [data-baseweb="tab"] {
1583
+ direction: rtl;
1584
+ }
1585
+ .stTabs [data-baseweb="tab-panel"] {
1586
+ direction: rtl;
1587
+ }
1588
+ .stMetric {
1589
+ direction: rtl;
1590
+ text-align: right;
1591
+ }
1592
+ .stMetric > div {
1593
+ direction: rtl;
1594
+ text-align: right;
1595
+ }
1596
+ .stMetric label {
1597
+ direction: rtl;
1598
+ text-align: right;
1599
+ }
1600
+ </style>
1601
+ """, unsafe_allow_html=True)
1602
+
1603
+ # إنشاء وتشغيل التطبيق
1604
+ app = IntegratedPricingApp()
1605
  app.run()
1606
+
1607
+ if __name__ == "__main__":
1608
+ main()