EGYADMIN commited on
Commit
c3f8dcf
·
verified ·
1 Parent(s): edb2108

Update modules/pricing/pricing_app.py

Browse files
Files changed (1) hide show
  1. modules/pricing/pricing_app.py +693 -654
modules/pricing/pricing_app.py CHANGED
@@ -146,6 +146,10 @@ class PricingApp:
146
  if 'saved_pricing' not in st.session_state:
147
  st.session_state.saved_pricing = []
148
 
 
 
 
 
149
  def render(self):
150
  """طريقة للتوافق مع الواجهة القديمة"""
151
  self.run()
@@ -444,14 +448,41 @@ class PricingApp:
444
  # إنشاء DataFrame من بنود المشروع
445
  df = pd.DataFrame(project_items)
446
  df = df[['id', 'code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
447
- df.columns = ['الرقم', 'الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي', 'نوع المورد']
448
 
449
- # تنسيق الأسعار
450
- df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f} ريال")
451
- df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f} ريال")
 
 
 
 
 
 
 
 
 
 
 
 
 
452
 
453
- # عرض الجدول
454
- st.dataframe(df, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
 
456
  # حساب المجموع الكلي
457
  total_price = sum(item['total_price'] for item in project_items)
@@ -741,7 +772,7 @@ class PricingApp:
741
  st.rerun()
742
 
743
  def _render_item_price_analysis(self):
744
- """عرض تحليل سعر البند"""
745
  st.subheader("تحليل سعر البند")
746
 
747
  # الحصول على بنود المشروع الحالي
@@ -767,28 +798,42 @@ class PricingApp:
767
  return
768
 
769
  # عرض معلومات البند
770
- st.write(f"**البند:** {selected_item['description']}")
771
- st.write(f"**الكود:** {selected_item['code']}")
772
- st.write(f"**الوحدة:** {selected_item['unit']}")
773
- st.write(f"**الكمية:** {selected_item['quantity']}")
774
- st.write(f"**سعر الوحدة:** {selected_item['unit_price']:,.2f} ريال")
775
- st.write(f"**السعر الإجمالي:** {selected_item['total_price']:,.2f} ريال")
 
 
 
776
 
777
  # إنشاء تحليل سعر البند
778
  item_analysis = self._generate_item_analysis(selected_item)
779
 
780
- # عرض تحليل المواد
781
  self._render_materials_analysis(item_analysis)
782
 
783
- # عرض تحليل المعدات
784
  self._render_equipment_analysis(item_analysis)
785
 
786
- # عرض تحليل العمالة
787
  self._render_labor_analysis(item_analysis)
788
 
 
 
 
789
  # عرض ملخص التكلفة
790
  self._render_cost_summary(item_analysis)
791
 
 
 
 
 
 
 
 
 
792
  def _generate_item_analysis(self, item):
793
  """إنشاء تحليل سعر البند"""
794
  # في هذه النسخة التجريبية، سنقوم بإنشاء بيانات عشوائية للتحليل
@@ -864,13 +909,25 @@ class PricingApp:
864
  }
865
  ]
866
 
 
 
 
 
 
 
 
 
 
 
 
867
  # حساب إجمالي التكاليف
868
  total_materials_cost = sum(material['total_price'] for material in materials)
869
  total_equipment_cost = sum(equipment_item['total_price'] for equipment_item in equipment)
870
  total_labor_cost = sum(labor_item['total_price'] for labor_item in labor)
 
871
 
872
  # تعديل الربح ليكون الفرق بين سعر الوحدة وإجمالي التكاليف
873
- total_cost = total_materials_cost + total_equipment_cost + total_labor_cost + overhead_cost
874
  profit = unit_price - total_cost
875
 
876
  return {
@@ -878,80 +935,405 @@ class PricingApp:
878
  'materials': materials,
879
  'equipment': equipment,
880
  'labor': labor,
 
881
  'total_materials_cost': total_materials_cost,
882
  'total_equipment_cost': total_equipment_cost,
883
  'total_labor_cost': total_labor_cost,
 
884
  'overhead_cost': overhead_cost,
885
  'profit': profit,
886
  'unit_price': unit_price
887
  }
888
 
889
  def _render_materials_analysis(self, item_analysis):
890
- """عرض تحليل المواد"""
891
  st.subheader("تحليل المواد")
892
 
893
  if not item_analysis['materials']:
894
  st.info("لا توجد مواد في تحليل هذا البند.")
 
 
 
 
 
 
 
 
 
 
 
 
895
  return
896
 
897
  # إنشاء DataFrame من قائمة المواد
898
  df = pd.DataFrame(item_analysis['materials'])
899
  df.columns = ['المادة', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
900
 
901
- # تنسيق الأسعار
902
- df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f} ريال")
903
- df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f} ريال")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
 
905
- # عرض الجدول
906
- st.dataframe(df, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
907
 
908
  # عرض إجمالي تكلفة المواد
909
  st.metric("إجمالي تكلفة المواد", f"{item_analysis['total_materials_cost']:,.2f} ريال")
910
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
  def _render_equipment_analysis(self, item_analysis):
912
- """عرض تحليل المعدات"""
913
  st.subheader("تحليل المعدات")
914
 
915
  if not item_analysis['equipment']:
916
  st.info("لا توجد معدات في تحليل هذا البند.")
 
 
 
 
 
 
 
 
 
 
 
 
917
  return
918
 
919
  # إنشاء DataFrame من قائمة المعدات
920
  df = pd.DataFrame(item_analysis['equipment'])
921
  df.columns = ['المعدة', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
922
 
923
- # تنسيق الأسعار
924
- df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f} ريال")
925
- df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f} ريال")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
 
927
- # عرض الجدول
928
- st.dataframe(df, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
929
 
930
  # عرض إجمالي تكلفة المعدات
931
  st.metric("إجمالي تكلفة المعدات", f"{item_analysis['total_equipment_cost']:,.2f} ريال")
932
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
933
  def _render_labor_analysis(self, item_analysis):
934
- """عرض تحليل العمالة"""
935
  st.subheader("تحليل العمالة")
936
 
937
  if not item_analysis['labor']:
938
  st.info("لا توجد عمالة في تحليل هذا البند.")
 
 
 
 
 
 
 
 
 
 
 
 
939
  return
940
 
941
  # إنشاء DataFrame من قائمة العمالة
942
  df = pd.DataFrame(item_analysis['labor'])
943
  df.columns = ['العامل', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي']
944
 
945
- # تنسيق الأسعار
946
- df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f} ريال")
947
- df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f} ريال")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
948
 
949
- # عرض الجدول
950
- st.dataframe(df, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
 
952
  # عرض إجمالي تكلفة العمالة
953
  st.metric("إجمالي تكلفة العمالة", f"{item_analysis['total_labor_cost']:,.2f} ريال")
954
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
955
  def _setup_arabic_fonts(self):
956
  """إعداد الخطوط العربية للرسوم البيانية"""
957
  plt.rcParams['font.family'] = 'Arial'
@@ -966,7 +1348,7 @@ class PricingApp:
966
 
967
  # إنشاء بيانات الرسم البياني
968
  # استخدام arabic_reshaper و bidi لمعالجة النص العربي
969
- labels_ar = ['المواد', 'المعدات', 'العمالة', 'المصاريف العمومية', 'الربح']
970
  labels = [get_display(arabic_reshaper.reshape(label)) for label in labels_ar]
971
 
972
  # التحقق من وجود قيم NaN واستبدالها بأصفار
@@ -974,6 +1356,7 @@ class PricingApp:
974
  item_analysis['total_materials_cost'] if not np.isnan(item_analysis['total_materials_cost']) else 0,
975
  item_analysis['total_equipment_cost'] if not np.isnan(item_analysis['total_equipment_cost']) else 0,
976
  item_analysis['total_labor_cost'] if not np.isnan(item_analysis['total_labor_cost']) else 0,
 
977
  item_analysis['overhead_cost'] if not np.isnan(item_analysis['overhead_cost']) else 0,
978
  item_analysis['profit'] if not np.isnan(item_analysis['profit']) else 0
979
  ]
@@ -1006,11 +1389,12 @@ class PricingApp:
1006
 
1007
  # عرض جدول ملخص التكلفة
1008
  cost_summary = {
1009
- 'البند': ['المواد', 'المعدات', 'العمالة', 'المصاريف العمومية', 'الربح', 'الإجمالي'],
1010
  'التكلفة': [
1011
  item_analysis['total_materials_cost'],
1012
  item_analysis['total_equipment_cost'],
1013
  item_analysis['total_labor_cost'],
 
1014
  item_analysis['overhead_cost'],
1015
  item_analysis['profit'],
1016
  item_analysis['unit_price']
@@ -1019,6 +1403,7 @@ class PricingApp:
1019
  item_analysis['total_materials_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1020
  item_analysis['total_equipment_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1021
  item_analysis['total_labor_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
 
1022
  item_analysis['overhead_cost'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1023
  item_analysis['profit'] / item_analysis['unit_price'] * 100 if item_analysis['unit_price'] > 0 else 0,
1024
  100
@@ -1035,6 +1420,88 @@ class PricingApp:
1035
  # عرض الجدول
1036
  st.dataframe(df, use_container_width=True)
1037
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1038
  def _render_cost_analysis(self, project_info):
1039
  """عرض تحليل التكلفة"""
1040
  st.subheader("تحليل التكلفة")
@@ -1046,70 +1513,56 @@ class PricingApp:
1046
  st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
1047
  return
1048
 
1049
- # حساب إجمالي تكلفة المشروع
1050
  total_cost = sum(item['total_price'] for item in project_items)
1051
 
1052
- # عرض إجمالي تكلفة المشروع
1053
  st.metric("إجمالي تكلفة المشروع", f"{total_cost:,.2f} ريال")
1054
 
1055
- # إنشاء تحليل التكلفة حسب نوع المورد
1056
- cost_by_resource_type = {}
1057
- for item in project_items:
1058
- resource_type = item.get('resource_type', 'أخرى')
1059
- if resource_type not in cost_by_resource_type:
1060
- cost_by_resource_type[resource_type] = 0
1061
- cost_by_resource_type[resource_type] += item['total_price']
1062
-
1063
- # إعداد الخطوط العربية
 
 
 
 
1064
  self._setup_arabic_fonts()
1065
 
1066
- # إنشاء الرسم البياني
1067
  fig, ax = plt.subplots(figsize=(10, 6))
1068
 
1069
- # التحقق من وجود قيم NaN واستبدالها بأصفار
1070
- labels_ar = list(cost_by_resource_type.keys())
1071
- labels = [get_display(arabic_reshaper.reshape(label)) for label in labels_ar]
1072
- values = [cost_by_resource_type[label_ar] if not np.isnan(cost_by_resource_type[label_ar]) else 0 for label_ar in labels_ar]
1073
 
1074
- # التحقق من أن جميع القيم موجبة (أو صفر) لتجنب الأخطاء في الرسم البياني
1075
- values = [max(0, val) for val in values]
1076
 
1077
- # رسم المخطط الدائري
1078
- wedges, texts, autotexts = ax.pie(
1079
- values,
1080
- labels=labels,
1081
- autopct='%1.1f%%',
1082
- startangle=90,
1083
- shadow=True,
1084
- )
1085
 
1086
- # تعديل حجم النص
1087
- plt.setp(autotexts, size=10, weight="bold")
1088
 
1089
  # إضافة العنوان
1090
- title_ar = 'توزيع التكلفة حسب نوع المورد'
1091
  title = get_display(arabic_reshaper.reshape(title_ar))
1092
  ax.set_title(title, fontsize=16)
1093
 
1094
- # عرض الرسم البياني
1095
- st.pyplot(fig)
1096
-
1097
- # عرض جدول تحليل التكلفة
1098
- cost_analysis = {
1099
- 'نوع المورد': list(cost_by_resource_type.keys()),
1100
- 'التكلفة': list(cost_by_resource_type.values()),
1101
- 'النسبة': [cost / total_cost * 100 if total_cost > 0 else 0 for cost in cost_by_resource_type.values()]
1102
- }
1103
-
1104
- df = pd.DataFrame(cost_analysis)
1105
- df.columns = ['نوع المورد', 'التكلفة (ريال)', 'النسبة (%)']
1106
 
1107
- # تنسيق الأرقام
1108
- df['التكلفة (ريال)'] = df['التكلفة (ريال)'].apply(lambda x: f"{x:,.2f}")
1109
- df['النسبة (%)'] = df['النسبة (%)'].apply(lambda x: f"{x:.2f}%")
1110
 
1111
- # عرض الجدول
1112
- st.dataframe(df, use_container_width=True)
1113
 
1114
  def _render_profit_margin(self, project_info):
1115
  """عرض تحليل الربحية"""
@@ -1122,163 +1575,70 @@ class PricingApp:
1122
  st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
1123
  return
1124
 
1125
- # حساب إجمالي تكلفة المشروع
1126
  total_cost = sum(item['total_price'] for item in project_items)
1127
 
1128
- # عرض شريط تمرير لتعديل نسبة الربح
1129
- profit_margin = st.slider(
1130
- "نسبة الربح (%)",
1131
- min_value=0.0,
1132
- max_value=50.0,
1133
- value=15.0,
1134
- step=0.5,
1135
- key="profit_margin_slider"
1136
- )
1137
-
1138
- # عرض شريط تمرير لتعديل نسبة المصاريف العمومية
1139
- overhead_percentage = st.slider(
1140
- "نسبة المصاريف العمومية (%)",
1141
- min_value=0.0,
1142
- max_value=30.0,
1143
- value=10.0,
1144
- step=0.5,
1145
- key="overhead_percentage_slider"
1146
- )
1147
-
1148
- # حساب قيمة المصاريف العمومية
1149
- overhead_cost = total_cost * (overhead_percentage / 100)
1150
-
1151
- # حساب قيمة الربح
1152
- profit_amount = (total_cost + overhead_cost) * (profit_margin / 100)
1153
 
1154
- # حساب السعر النهائي
1155
- final_price = total_cost + overhead_cost + profit_amount
1156
-
1157
- # عرض النتائج
1158
  col1, col2, col3 = st.columns(3)
1159
  with col1:
1160
- st.metric("إجمالي التكلفة المباشرة", f"{total_cost:,.2f} ريال")
1161
  with col2:
1162
- st.metric("المصاريف العمومية", f"{overhead_cost:,.2f} ريال")
1163
  with col3:
1164
- st.metric("قيمة الربح", f"{profit_amount:,.2f} ريال")
1165
 
1166
- st.metric("السعر النهائي للمشروع", f"{final_price:,.2f} ريال")
1167
-
1168
- # إنشاء تحليل حساسية الربحية
1169
- st.subheader("تحليل حساسية الربحية")
1170
-
1171
- # إنشاء مصفوفة نسب الربح والمصاريف العمومية
1172
- profit_margins = [profit_margin - 5, profit_margin, profit_margin + 5]
1173
- overhead_percentages = [overhead_percentage - 2, overhead_percentage, overhead_percentage + 2]
1174
-
1175
- # التأكد من أن جميع القيم موجبة
1176
- profit_margins = [max(0, margin) for margin in profit_margins]
1177
- overhead_percentages = [max(0, percentage) for percentage in overhead_percentages]
1178
-
1179
- # إنشاء بيانات تحليل الحساسية
1180
- sensitivity_data = []
1181
-
1182
- for p_margin in profit_margins:
1183
- for o_percentage in overhead_percentages:
1184
- o_cost = total_cost * (o_percentage / 100)
1185
- p_amount = (total_cost + o_cost) * (p_margin / 100)
1186
- f_price = total_cost + o_cost + p_amount
1187
-
1188
- # حساب نسبة الربح المعدلة
1189
- adjusted_profit_percentage = p_amount / f_price * 100 if f_price > 0 else 0
1190
-
1191
- sensitivity_data.append({
1192
- "نسبة الربح": p_margin,
1193
- "نسبة المصاريف العمومية": o_percentage,
1194
- "السعر النهائي": f_price,
1195
- "نسبة الربح المعدلة": adjusted_profit_percentage
1196
- })
1197
-
1198
- # إنشاء DataFrame من بيانات تحليل الحساسية
1199
- df = pd.DataFrame(sensitivity_data)
1200
-
1201
- # إنشاء جدول تحليل الحساسية
1202
- pivot_table = pd.pivot_table(
1203
- df,
1204
- values="السعر النهائي",
1205
- index="نسبة الربح",
1206
- columns="نسبة المصاريف العمومية",
1207
- aggfunc="first"
1208
- )
1209
-
1210
- # تنسيق الأرقام
1211
- pivot_table = pivot_table.applymap(lambda x: f"{x:,.2f} ريال")
1212
-
1213
- # عرض جدول تحليل الحساسية
1214
- st.write("**جدول تحليل الحساسية (السعر النهائي)**")
1215
- st.dataframe(pivot_table, use_container_width=True)
1216
-
1217
- # إنشاء جدول نسبة الربح المعدلة
1218
- profit_pivot = pd.pivot_table(
1219
- df,
1220
- values="نسبة الربح المعدلة",
1221
- index="نسبة الربح",
1222
- columns="نسبة المصاريف العمومية",
1223
- aggfunc="first"
1224
  )
1225
 
1226
- # تنسيق الأرقام
1227
- profit_pivot = profit_pivot.applymap(lambda x: f"{x:.2f}%" if isinstance(x, (int, float)) else str(x))
1228
-
1229
- # عرض جدول نسبة الربح المعدلة
1230
- st.write("**جدول تحليل الحساسية (نسبة الربح المعدلة)**")
1231
- st.dataframe(profit_pivot, use_container_width=True)
1232
-
1233
- # إنشاء رسم بياني لتحليل الحساسية
1234
  self._setup_arabic_fonts()
1235
 
 
1236
  fig, ax = plt.subplots(figsize=(10, 6))
1237
 
1238
- # إنشاء بيانات الرسم البياني
1239
- x = [str(margin) + "%" for margin in df["نسبة الربح"]]
1240
- y = df["السعر النهائي"]
1241
- hue = [str(percentage) + "%" for percentage in df["نسبة المصاريف العمومية"]]
1242
-
1243
- # إنشاء قاموس للألوان
1244
- colors = {
1245
- str(overhead_percentages[0]) + "%": "blue",
1246
- str(overhead_percentages[1]) + "%": "green",
1247
- str(overhead_percentages[2]) + "%": "red"
1248
- }
1249
 
1250
- # رسم الخطوط
1251
- for percentage in set(hue):
1252
- mask = [h == percentage for h in hue]
1253
- ax.plot(
1254
- [x[i] for i in range(len(x)) if mask[i]],
1255
- [y[i] for i in range(len(y)) if mask[i]],
1256
- marker='o',
1257
- label=percentage,
1258
- color=colors.get(percentage, "gray")
1259
- )
1260
-
1261
- # إضافة التسميات
1262
- x_label_ar = 'نسبة الربح'
1263
- y_label_ar = 'السعر النهائي (ريال)'
1264
- title_ar = 'تحليل حساسية الربحية'
1265
- legend_title_ar = 'نسبة المصاريف العمومية'
1266
 
1267
- x_label = get_display(arabic_reshaper.reshape(x_label_ar))
1268
- y_label = get_display(arabic_reshaper.reshape(y_label_ar))
1269
- title = get_display(arabic_reshaper.reshape(title_ar))
1270
- legend_title = get_display(arabic_reshaper.reshape(legend_title_ar))
 
 
 
 
 
1271
 
1272
- ax.set_xlabel(x_label, fontsize=12)
1273
- ax.set_ylabel(y_label, fontsize=12)
1274
- ax.set_title(title, fontsize=16)
1275
- ax.legend(title=legend_title)
1276
- ax.grid(True)
1277
 
1278
- # تنسيق المحور Y
1279
- ax.get_yaxis().set_major_formatter(
1280
- plt.FuncFormatter(lambda x, loc: "{:,}".format(int(x)))
1281
- )
1282
 
1283
  # عرض الرسم البياني
1284
  st.pyplot(fig)
@@ -1294,470 +1654,149 @@ class PricingApp:
1294
  st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
1295
  return
1296
 
1297
- # اختيار استراتيجية التسعير
1298
- pricing_strategy = st.selectbox(
1299
- "اختر استراتيجية التسعير",
1300
- ["تسعير قياسي", "تسعير غير متزن", "تسعير موجه ربحية", "تسعير تنافسي", "تسعير استراتيجي"],
1301
- key="pricing_strategy_selector"
1302
- )
1303
 
1304
- # عرض وصف استراتيجية التسعير
1305
- if pricing_strategy == "تسعير قياسي":
1306
- st.info("""
1307
- **التسعير القياسي**: يعتمد على تحليل التكلفة الفعلية لكل بند مع إضافة نسبة ربح ثابتة على جميع البنود.
1308
-
1309
- **المميزات**:
1310
- - سهل التطبيق والفهم
1311
- - عادل ومتوازن لجميع البنود
1312
- - يعكس التكلفة الحقيقية للمشروع
1313
-
1314
- **العيوب**:
1315
- - لا يأخذ في الاعتبار التدفق النقدي
1316
- - قد لا يكون تنافسياً في بعض الحالات
1317
- """)
1318
- elif pricing_strategy == "تسعير غير متزن":
1319
- st.info("""
1320
- **التسعير غير المتزن**: يتضمن زيادة أسعار البنود المبكرة في المشروع وتخفيض أسعار البنود المتأخرة.
1321
-
1322
- **المميزات**:
1323
- - يحسن التدفق النقدي في بداية المشروع
1324
- - يقلل من مخاطر التمويل
1325
- - يمكن أن يزيد من الربحية الإجمالية
1326
-
1327
- **العيوب**:
1328
- - قد يكون مرفوضاً من قبل بعض العملاء
1329
- - يتطلب تحليلاً دقيقاً لجدول المشروع
1330
- - قد يؤدي إلى مخاطر إذا لم يكتمل المشروع
1331
- """)
1332
-
1333
- # عرض أدوات التحكم في التسعير الغير متزن
1334
- st.subheader("إعدادات التسعير الغير متزن")
1335
 
1336
  col1, col2 = st.columns(2)
1337
  with col1:
1338
- early_items_factor = st.slider(
1339
- "معامل زيادة البنود المبكرة",
1340
  min_value=1.0,
1341
- max_value=2.0,
1342
- value=st.session_state.unbalanced_pricing_factors.get('early_items_factor', 1.15),
1343
- step=0.01,
1344
- key="early_items_factor_slider"
 
1345
  )
 
 
 
 
1346
  with col2:
1347
- late_items_factor = st.slider(
1348
- "معامل تخفيض البنود المتأخرة",
1349
  min_value=0.5,
1350
  max_value=1.0,
1351
- value=st.session_state.unbalanced_pricing_factors.get('late_items_factor', 0.90),
1352
- step=0.01,
1353
- key="late_items_factor_slider"
 
1354
  )
1355
 
1356
- # تحديث معاملات التسعير الغير متزن
1357
- st.session_state.unbalanced_pricing_factors['early_items_factor'] = early_items_factor
1358
- st.session_state.unbalanced_pricing_factors['late_items_factor'] = late_items_factor
 
 
1359
 
1360
- # عرض معاملات مخصصة للبنود
1361
- st.subheader("معاملات مخصصة للبنود")
1362
 
1363
- # إنشاء DataFrame من بنود المشروع
1364
- df = pd.DataFrame(project_items)
1365
- df = df[['id', 'code', 'description', 'unit_price', 'total_price']]
1366
- df.columns = ['الرقم', 'الكود', 'الوصف', 'سعر الوحدة', 'السعر الإجمالي']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1367
 
1368
  # تنسيق الأسعار
1369
- df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f} ريال")
1370
- df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f} ريال")
 
 
1371
 
1372
  # عرض الجدول
1373
  st.dataframe(df, use_container_width=True)
1374
 
1375
- # اختيار البند لتعديل معامله
1376
- if len(project_items) > 0:
1377
- selected_item_id = st.selectbox(
1378
- "اختر البند لتعديل معامله",
1379
- options=[item['id'] for item in project_items],
1380
- format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
1381
- key="select_item_for_custom_factor"
1382
- )
1383
-
1384
- # الحصول على البند المحدد
1385
- selected_item = next((item for item in project_items if item['id'] == selected_item_id), None)
1386
-
1387
- if selected_item:
1388
- # الحصول على المعامل الحالي للبند
1389
- current_factor = st.session_state.unbalanced_pricing_factors.get('custom_factors', {}).get(str(selected_item_id), 1.0)
1390
-
1391
- # عرض شريط تمرير لتعديل معامل البند
1392
- custom_factor = st.slider(
1393
- f"معامل البند: {selected_item['description']}",
1394
- min_value=0.5,
1395
- max_value=2.0,
1396
- value=current_factor,
1397
- step=0.01,
1398
- key="custom_factor_slider"
1399
- )
1400
-
1401
- # تحديث معامل البند
1402
- if 'custom_factors' not in st.session_state.unbalanced_pricing_factors:
1403
- st.session_state.unbalanced_pricing_factors['custom_factors'] = {}
1404
-
1405
- st.session_state.unbalanced_pricing_factors['custom_factors'][str(selected_item_id)] = custom_factor
1406
-
1407
- # عرض السعر الأصلي والسعر المعدل
1408
- original_price = selected_item['unit_price']
1409
- adjusted_price = original_price * custom_factor
1410
-
1411
- col3, col4 = st.columns(2)
1412
- with col3:
1413
- st.metric("السعر الأصلي", f"{original_price:,.2f} ريال")
1414
- with col4:
1415
- st.metric("السعر المعدل", f"{adjusted_price:,.2f} ريال", delta=f"{(custom_factor - 1) * 100:.1f}%")
1416
-
1417
- # عرض زر تطبيق التسعير الغير متزن
1418
- if st.button("تطبيق ��لتسعير الغير متزن", key="apply_unbalanced_pricing_btn"):
1419
- # تطبيق التسعير الغير متزن على جميع البنود
1420
- self._apply_unbalanced_pricing()
1421
- st.success("تم تطبيق التسعير الغير متزن بنجاح!")
1422
- st.rerun()
1423
-
1424
- elif pricing_strategy == "تسعير موجه ربحية":
1425
- st.info("""
1426
- **التسعير الموجه ربحية**: يركز على تحقيق أقصى ربح ممكن من خلال زيادة أسعار البنود ذات التكلفة المنخفضة والمخاطر القليلة.
1427
-
1428
- **المميزات**:
1429
- - يزيد من الربحية الإجمالية للمشروع
1430
- - يستفيد من البنود ذات هامش الربح العالي
1431
- - يمكن أن يعوض الخسائر في البنود الأخرى
1432
-
1433
- **العيوب**:
1434
- - قد يؤدي إلى أسعار غير تنافسية في بعض البنود
1435
- - يتطلب تحليلاً دقيقاً للتكاليف والمخاطر
1436
- - قد يكون مرفوضاً من قبل بعض العملاء
1437
- """)
1438
- elif pricing_strategy == "تسعير تنافسي":
1439
- st.info("""
1440
- **التسعير التنافسي**: يهدف إلى تقديم أقل سعر ممكن للفوز بالمناقصة مع الحفاظ على هامش ربح مقبول.
1441
-
1442
- **المميزات**:
1443
- - يزيد من فرص الفوز بالمناقصة
1444
- - يمكن أن يفتح أسواقاً جديدة
1445
- - يمكن أن يبني علاقات طويلة المدى مع العملاء
1446
-
1447
- **العيوب**:
1448
- - يقلل من هامش الربح
1449
- - قد يؤدي إلى خسائر إذا لم يتم تقدير التكاليف بدقة
1450
- - قد يؤدي إلى منافسة سعرية غير مستدامة
1451
- """)
1452
- elif pricing_strategy == "تسعير استراتيجي":
1453
- st.info("""
1454
- **التسعير الاستراتيجي**: يأخذ في الاعتبار العلاقة طويلة المدى مع العميل والمشاريع المستقبلية المحتملة.
1455
-
1456
- **المميزات**:
1457
- - يبني علاقات طويلة المدى مع العملاء
1458
- - يمكن أن يؤدي إلى مشاريع مستقبلية أكثر ربحية
1459
- - يمكن أن يفتح أسواقاً جديدة
1460
-
1461
- **العيوب**:
1462
- - قد يقلل من الربحية في المشروع الحالي
1463
- - يتطلب تحليلاً دقيقاً للسوق والمنافسين
1464
- - قد لا يكون مناسباً لجميع المشاريع
1465
- """)
1466
-
1467
- # عرض مقارنة بين استراتيجيات التسعير
1468
- st.subheader("مقارنة بين استراتيجيات التسعير")
1469
-
1470
- # حساب إجمالي تكلفة المشروع
1471
- total_cost = sum(item['total_price'] for item in project_items)
1472
-
1473
- # حساب السعر النهائي لكل استراتيجية
1474
- standard_price = total_cost * 1.25 # تسعير قياسي: تكلفة + 25%
1475
- unbalanced_price = total_cost * 1.25 # تسعير غير متزن: نفس السعر الإجمالي ولكن بتوزيع مختلف
1476
- profit_oriented_price = total_cost * 1.35 # تسعير موجه ربحية: تكلفة + 35%
1477
- competitive_price = total_cost * 1.15 # تسعير تنافسي: تكلفة + 15%
1478
- strategic_price = total_cost * 1.20 # تسعير استراتيجي: تكلفة + 20%
1479
-
1480
- # إنشاء بيانات المقارنة
1481
- comparison_data = {
1482
- 'استراتيجية التسعير': ['تسعير قياسي', 'تسعير غير متزن', 'تسعير موجه ربحية', 'تسعير تنافسي', 'تسعير استراتيجي'],
1483
- 'السعر النهائي': [standard_price, unbalanced_price, profit_oriented_price, competitive_price, strategic_price],
1484
- 'نسبة الزيادة عن التكلفة': [25, 25, 35, 15, 20],
1485
- 'التدفق النقدي': ['متوسط', 'جيد', 'متوسط', 'ضعيف', 'متوسط'],
1486
- 'فرص الفوز': ['متوسطة', 'متوسطة', 'منخفضة', 'عالية', 'متوسطة']
1487
- }
1488
-
1489
- # إنشاء DataFrame من بيانات المقارنة
1490
- df = pd.DataFrame(comparison_data)
1491
-
1492
- # تنسيق الأسعار
1493
- df['السعر النهائي'] = df['السعر النهائي'].apply(lambda x: f"{x:,.2f} ريال")
1494
- df['نسبة الزيادة عن التكلفة'] = df['نسبة الزيادة عن التكلفة'].apply(lambda x: f"{x}%")
1495
-
1496
- # عرض الجدول
1497
- st.dataframe(df, use_container_width=True)
1498
-
1499
- # إنشاء رسم بياني للمقارنة
1500
- self._setup_arabic_fonts()
1501
-
1502
- fig, ax = plt.subplots(figsize=(10, 6))
1503
-
1504
- # إنشاء بيانات الرسم البياني
1505
- strategies_ar = comparison_data['استراتيجية التسعير']
1506
- strategies = [get_display(arabic_reshaper.reshape(strategy)) for strategy in strategies_ar]
1507
- prices = [standard_price, unbalanced_price, profit_oriented_price, competitive_price, strategic_price]
1508
-
1509
- # رسم الأعمدة
1510
- bars = ax.bar(strategies, prices, color=['blue', 'green', 'red', 'purple', 'orange'])
1511
-
1512
- # إضافة التسميات
1513
- x_label_ar = 'استراتيجية التسعير'
1514
- y_label_ar = 'السعر النهائي (ريال)'
1515
- title_ar = 'مقارنة بين استراتيجيات التسعير'
1516
-
1517
- x_label = get_display(arabic_reshaper.reshape(x_label_ar))
1518
- y_label = get_display(arabic_reshaper.reshape(y_label_ar))
1519
- title = get_display(arabic_reshaper.reshape(title_ar))
1520
-
1521
- ax.set_xlabel(x_label, fontsize=12)
1522
- ax.set_ylabel(y_label, fontsize=12)
1523
- ax.set_title(title, fontsize=16)
1524
-
1525
- # تنسيق المحور Y
1526
- ax.get_yaxis().set_major_formatter(
1527
- plt.FuncFormatter(lambda x, loc: "{:,}".format(int(x)))
1528
- )
1529
-
1530
- # إضافة قيم الأعمدة
1531
- for bar in bars:
1532
- height = bar.get_height()
1533
- ax.text(
1534
- bar.get_x() + bar.get_width() / 2.,
1535
- height * 1.01,
1536
- f"{height:,.0f}",
1537
- ha='center',
1538
- va='bottom',
1539
- rotation=0,
1540
- fontsize=8
1541
- )
1542
-
1543
- # تدوير تسميات المحور X
1544
- plt.xticks(rotation=45, ha='right')
1545
-
1546
- # ضبط التخطيط
1547
- plt.tight_layout()
1548
-
1549
- # عرض الرسم البياني
1550
- st.pyplot(fig)
1551
-
1552
- def _apply_unbalanced_pricing(self):
1553
- """تطبيق التسعير الغير متزن على بنود المشروع"""
1554
- # الحصول على بنود المشروع الحالي
1555
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
1556
-
1557
- if not project_items:
1558
- return
1559
 
1560
- # الحصول على معاملات التسعير الغير متزن
1561
- early_items_factor = st.session_state.unbalanced_pricing_factors.get('early_items_factor', 1.15)
1562
- late_items_factor = st.session_state.unbalanced_pricing_factors.get('late_items_factor', 0.90)
1563
- custom_factors = st.session_state.unbalanced_pricing_factors.get('custom_factors', {})
1564
-
1565
- # حساب إجمالي تكلفة المشروع قبل التعديل
1566
- total_price_before = sum(item['total_price'] for item in project_items)
1567
-
1568
- # تقسيم البنود إلى مبكرة ومتأخرة
1569
- num_items = len(project_items)
1570
- early_items = project_items[:num_items // 3]
1571
- late_items = project_items[num_items * 2 // 3:]
1572
- middle_items = project_items[num_items // 3:num_items * 2 // 3]
1573
-
1574
- # تطبيق المعاملات على البنود
1575
- for i, item in enumerate(st.session_state.boq_items):
1576
- if item['project_id'] != st.session_state.current_project:
1577
- continue
1578
-
1579
- # التحقق من وجود معامل مخصص للبند
1580
- if str(item['id']) in custom_factors:
1581
- factor = custom_factors[str(item['id'])]
1582
- # تطبيق معامل البنود المبكرة
1583
- elif item in early_items:
1584
- factor = early_items_factor
1585
- # تطبيق معامل البنود المتأخرة
1586
- elif item in late_items:
1587
- factor = late_items_factor
1588
- # البنود الوسطى تبقى كما هي
1589
- else:
1590
- factor = 1.0
1591
 
1592
- # تعديل سعر الوحدة والسعر الإجمالي
1593
- for j, original_item in enumerate(st.session_state.boq_items):
1594
- if original_item['id'] == item['id']:
1595
- original_unit_price = original_item['unit_price'] / factor if factor != 0 else original_item['unit_price']
1596
- st.session_state.boq_items[j]['unit_price'] = original_unit_price * factor
1597
- st.session_state.boq_items[j]['total_price'] = original_item['quantity'] * st.session_state.boq_items[j]['unit_price']
1598
- break
1599
-
1600
- # حساب إجمالي تكلفة المشروع بعد التعديل
1601
- total_price_after = sum(item['total_price'] for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project)
 
1602
 
1603
- # تعديل الأسعار للحفاظ على نفس إجمالي التكلفة
1604
- if total_price_after > 0:
1605
- adjustment_factor = total_price_before / total_price_after
1606
-
1607
- for i, item in enumerate(st.session_state.boq_items):
1608
- if item['project_id'] == st.session_state.current_project:
1609
- st.session_state.boq_items[i]['unit_price'] *= adjustment_factor
1610
- st.session_state.boq_items[i]['total_price'] = item['quantity'] * st.session_state.boq_items[i]['unit_price']
1611
-
1612
  def _render_export_save_buttons(self, project_info):
1613
  """عرض أزرار التصدير والحفظ"""
1614
  st.subheader("تصدير وحفظ التسعير")
1615
 
1616
  col1, col2 = st.columns(2)
1617
-
1618
  with col1:
1619
- if st.button("💾 حفظ التسعير الحالي", key="save_pricing_btn", type="primary"):
1620
- self._save_current_pricing(project_info)
1621
- st.success("تم حفظ التسعير بنجاح!")
1622
 
1623
  with col2:
1624
- if st.button("📤 تصدير التسعير الكامل", key="export_full_pricing_btn"):
1625
- self._export_full_pricing(project_info)
 
1626
 
1627
- # عرض التسعيرات المحفوظة
1628
- if st.session_state.saved_pricing:
1629
- st.subheader("التسعيرات المحفوظة")
1630
-
1631
- # إنشاء DataFrame من التسعيرات المحفوظة
1632
- saved_pricing_data = []
1633
- for pricing in st.session_state.saved_pricing:
1634
- saved_pricing_data.append({
1635
- 'المشروع': pricing['project_name'],
1636
- 'تاريخ الحفظ': pricing['save_date'],
1637
- 'إجمالي التكلفة': f"{pricing['total_cost']:,.2f} ريال",
1638
- 'نوع التسعير': pricing['pricing_type'],
1639
- 'الملاحظات': pricing['notes'] if 'notes' in pricing else ""
1640
- })
1641
 
1642
- df = pd.DataFrame(saved_pricing_data)
1643
- st.dataframe(df, use_container_width=True)
1644
-
1645
- # اختيار تسعير محفوظ للتحميل
1646
- if len(st.session_state.saved_pricing) > 0:
1647
- selected_pricing_index = st.selectbox(
1648
- "اختر تسعير محفوظ للتحميل",
1649
- options=list(range(len(st.session_state.saved_pricing))),
1650
- format_func=lambda x: f"{st.session_state.saved_pricing[x]['project_name']} - {st.session_state.saved_pricing[x]['save_date']}",
1651
- key="select_saved_pricing"
1652
- )
1653
 
1654
- if st.button("تحميل التسعير المحفوظ", key="load_saved_pricing_btn"):
1655
- self._load_saved_pricing(selected_pricing_index)
1656
- st.success("تم تحميل التسعير المحفوظ بنجاح!")
1657
- st.rerun()
1658
-
1659
- def _save_current_pricing(self, project_info):
1660
- """حفظ التسعير الحالي"""
1661
- # الحصول على بنود المشروع الحالي
1662
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
1663
-
1664
- if not project_items:
1665
- st.warning("لا توجد بنود في جدول الكميات. لا يمكن حفظ التسعير.")
1666
- return
1667
-
1668
- # حساب إجمالي تكلفة المشروع
1669
- total_cost = sum(item['total_price'] for item in project_items)
1670
-
1671
- # إنشاء كائن التسعير المحفوظ
1672
- saved_pricing = {
1673
- 'project_id': project_info['id'],
1674
- 'project_name': project_info['name'],
1675
- 'save_date': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
1676
- 'pricing_type': project_info.get('pricing_type', 'قياسي'),
1677
- 'total_cost': total_cost,
1678
- 'items': project_items,
1679
- 'unbalanced_pricing_factors': st.session_state.unbalanced_pricing_factors.copy() if 'unbalanced_pricing_factors' in st.session_state else {}
1680
- }
1681
-
1682
- # إضافة التسعير المحفوظ إلى قائمة التسعيرات المحفوظة
1683
- if 'saved_pricing' not in st.session_state:
1684
- st.session_state.saved_pricing = []
1685
-
1686
- st.session_state.saved_pricing.append(saved_pricing)
1687
-
1688
- def _load_saved_pricing(self, index):
1689
- """تحميل تسعير محفوظ"""
1690
- if index < 0 or index >= len(st.session_state.saved_pricing):
1691
- st.error("مؤشر التسعير المحفوظ غير صالح.")
1692
- return
1693
-
1694
- saved_pricing = st.session_state.saved_pricing[index]
1695
-
1696
- # تحديث المشروع الحالي
1697
- st.session_state.current_project = saved_pricing['project_id']
1698
-
1699
- # تحديث بنود المشروع
1700
- for item in saved_pricing['items']:
1701
- # البحث عن البند في القائمة الحالية
1702
- existing_item = next((i for i in st.session_state.boq_items if i['id'] == item['id']), None)
1703
-
1704
- if existing_item:
1705
- # تحديث البند الموجود
1706
- for i, boq_item in enumerate(st.session_state.boq_items):
1707
- if boq_item['id'] == item['id']:
1708
- st.session_state.boq_items[i] = item
1709
  break
1710
- else:
1711
- # إضافة البند الجديد
1712
- st.session_state.boq_items.append(item)
1713
 
1714
- # تحديث معاملات التسعير الغير متزن
1715
- if 'unbalanced_pricing_factors' in saved_pricing:
1716
- st.session_state.unbalanced_pricing_factors = saved_pricing['unbalanced_pricing_factors']
1717
-
1718
- def _export_full_pricing(self, project_info):
1719
- """تصدير التسعير الكامل"""
1720
- # الحصول على بنود المشروع الحالي
1721
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
1722
-
1723
- if not project_items:
1724
- st.warning("لا توجد بنود في جدول الكميات. لا يمكن تصدير التسعير.")
1725
- return
1726
-
1727
- # حساب إجمالي تكلفة المشروع
1728
- total_cost = sum(item['total_price'] for item in project_items)
1729
-
1730
- # إنشاء DataFrame من بنود المشروع
1731
- df = pd.DataFrame(project_items)
1732
- df = df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
1733
- df.columns = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي', 'نوع المورد']
1734
-
1735
- # تنسيق الأسعار
1736
- df['سعر الوحدة'] = df['سعر الوحدة'].apply(lambda x: f"{x:,.2f}")
1737
- df['السعر الإجمالي'] = df['السعر الإجمالي'].apply(lambda x: f"{x:,.2f}")
1738
-
1739
- # إضافة صف المجموع
1740
- df.loc[len(df)] = ['', 'الإجمالي', '', '', '', f"{total_cost:,.2f}", '']
1741
-
1742
- # إنشاء CSV للتصدير
1743
- csv = df.to_csv(index=False)
1744
- b64 = base64.b64encode(csv.encode()).decode()
1745
-
1746
- # إنشاء رابط التحميل
1747
- filename = f"pricing_{project_info['id']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
1748
- href = f'<a href="data:file/csv;base64,{b64}" download="{filename}">تحميل ملف التسعير الكامل (CSV)</a>'
1749
- st.markdown(href, unsafe_allow_html=True)
1750
-
1751
- # إنشاء Excel للتصدير
1752
- excel_buffer = io.BytesIO()
1753
- df.to_excel(excel_buffer, index=False, engine='openpyxl')
1754
- excel_buffer.seek(0)
1755
- b64_excel = base64.b64encode(excel_buffer.read()).decode()
1756
-
1757
- # إنشاء رابط التحميل
1758
- filename_excel = f"pricing_{project_info['id']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
1759
- href_excel = f'<a href="data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,{b64_excel}" download="{filename_excel}">تحميل ملف التسعير الكامل (Excel)</a>'
1760
- st.markdown(href_excel, unsafe_allow_html=True)
1761
-
1762
- # إنشاء تقرير PDF
1763
- st.info("لتصدير التسعير بتنسيق PDF، يرجى استخدام خيار 'طباعة الصفحة' في المتصفح واختيار 'حفظ كـ PDF'.")
 
146
  if 'saved_pricing' not in st.session_state:
147
  st.session_state.saved_pricing = []
148
 
149
+ # تهيئة حالة تحليل سعر البند
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()
 
448
  # إنشاء DataFrame من بنود المشروع
449
  df = pd.DataFrame(project_items)
450
  df = df[['id', 'code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
 
451
 
452
+ # تحويل الجدول إلى جدول قابل للتعديل
453
+ edited_df = st.data_editor(
454
+ df,
455
+ column_config={
456
+ "id": st.column_config.Column("الرقم", disabled=True),
457
+ "code": st.column_config.Column("الكود"),
458
+ "description": st.column_config.Column("الوصف"),
459
+ "unit": st.column_config.Column("الوحدة"),
460
+ "quantity": st.column_config.NumberColumn("الكمية", min_value=0.0, format="%.2f", step=0.1),
461
+ "unit_price": st.column_config.NumberColumn("سعر الوحدة", min_value=0.0, format="%.2f ريال", step=0.1),
462
+ "total_price": st.column_config.NumberColumn("السعر الإجمالي", format="%.2f ريال", disabled=True),
463
+ "resource_type": st.column_config.SelectboxColumn("نوع المورد", options=["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"])
464
+ },
465
+ use_container_width=True,
466
+ key="edit_boq_items"
467
+ )
468
 
469
+ # تحديث البيانات في حالة التعديل
470
+ if edited_df is not None and not edited_df.equals(df):
471
+ for i, row in edited_df.iterrows():
472
+ item_id = row['id']
473
+ for j, item in enumerate(st.session_state.boq_items):
474
+ if item['id'] == item_id:
475
+ st.session_state.boq_items[j]['code'] = row['code']
476
+ st.session_state.boq_items[j]['description'] = row['description']
477
+ st.session_state.boq_items[j]['unit'] = row['unit']
478
+ st.session_state.boq_items[j]['quantity'] = row['quantity']
479
+ st.session_state.boq_items[j]['unit_price'] = row['unit_price']
480
+ st.session_state.boq_items[j]['total_price'] = row['quantity'] * row['unit_price']
481
+ st.session_state.boq_items[j]['resource_type'] = row['resource_type']
482
+ break
483
+
484
+ st.success("تم تحديث جدول الكميات بنجاح")
485
+ st.rerun()
486
 
487
  # حساب المجموع الكلي
488
  total_price = sum(item['total_price'] for item in project_items)
 
772
  st.rerun()
773
 
774
  def _render_item_price_analysis(self):
775
+ """عرض تحليل سعر البند مع إمكانية التعديل والحفظ"""
776
  st.subheader("تحليل سعر البند")
777
 
778
  # الحصول على بنود المشروع الحالي
 
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
  # في هذه النسخة التجريبية، سنقوم بإنشاء بيانات عشوائية للتحليل
 
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 {
 
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'
 
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 واستبدالها بأصفار
 
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
  ]
 
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']
 
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
 
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("تحليل التكلفة")
 
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
  """عرض تحليل الربحية"""
 
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)
 
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()