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

Update modules/pricing/pricing_app.py

Browse files
Files changed (1) hide show
  1. modules/pricing/pricing_app.py +301 -97
modules/pricing/pricing_app.py CHANGED
@@ -3,11 +3,14 @@ import pandas as pd
3
  import numpy as np
4
  import matplotlib.pyplot as plt
5
  import matplotlib.font_manager as fm
 
 
6
  import io
7
  import base64
8
  from datetime import datetime
9
  import random
10
  import json
 
11
 
12
  class PricingApp:
13
  """وحدة التسعير المتكاملة"""
@@ -139,6 +142,10 @@ class PricingApp:
139
  'custom_factors': {} # معاملات مخصصة لبنود محددة
140
  }
141
 
 
 
 
 
142
  def render(self):
143
  """طريقة للتوافق مع الواجهة القديمة"""
144
  self.run()
@@ -193,6 +200,9 @@ class PricingApp:
193
 
194
  with tab5:
195
  self._render_pricing_strategies(project_info)
 
 
 
196
 
197
  def _render_new_project_form(self):
198
  """عرض نموذج إنشاء مشروع جديد"""
@@ -335,35 +345,45 @@ class PricingApp:
335
 
336
  # إنشاء DataFrame من قائمة المشاريع
337
  df = pd.DataFrame(st.session_state.projects)
338
- df = df[['id', 'name', 'client', 'estimated_value', 'deadline', 'status', 'pricing_type']]
339
- df.columns = ['الرقم', 'اسم المشروع', 'العميل', 'القيمة التقديرية', 'الموعد النهائي', 'الحالة', 'نوع التسعير']
340
-
341
- # تنسيق القيمة التقديرية
342
- df['القيمة التقديرية'] = df['القيمة التقديرية'].apply(lambda x: f"{x:,.2f} ريال")
343
-
344
- # عرض الجدول
345
- st.dataframe(df, use_container_width=True)
346
-
347
- # اختيار المشروع
348
- col1, col2 = st.columns(2)
349
- with col1:
350
- selected_project_id = st.selectbox(
351
- "اختر المشروع",
352
- options=[p['id'] for p in st.session_state.projects],
353
- format_func=lambda x: next((p['name'] for p in st.session_state.projects if p['id'] == x), ""),
354
- index=[p['id'] for p in st.session_state.projects].index(st.session_state.current_project) if st.session_state.current_project in [p['id'] for p in st.session_state.projects] else 0,
355
- key="select_project"
356
- )
357
 
358
- if selected_project_id != st.session_state.current_project:
359
- st.session_state.current_project = selected_project_id
360
- st.rerun()
361
-
362
- with col2:
363
- if st.button("تعديل المشروع", key="edit_project_btn"):
364
- st.session_state.edit_project_id = st.session_state.current_project
365
- st.session_state.show_edit_project_form = True
366
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
 
368
  def _get_current_project_info(self):
369
  """الحصول على معلومات المشروع الحالي"""
@@ -440,7 +460,7 @@ class PricingApp:
440
  # أزرار التصدير والتعديل
441
  col1, col2 = st.columns(2)
442
  with col1:
443
- if st.button("تصدير", key="export_boq_btn_1"):
444
  # إنشاء CSV للتصدير
445
  export_df = pd.DataFrame(project_items)
446
  export_df = export_df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
@@ -448,21 +468,22 @@ class PricingApp:
448
 
449
  csv = export_df.to_csv(index=False)
450
  b64 = base64.b64encode(csv.encode()).decode()
451
- href = f'<a href="data:file/csv;base64,{b64}" download="boq.csv">تحميل CSV</a>'
452
  st.markdown(href, unsafe_allow_html=True)
453
 
454
  with col2:
455
- selected_item_id = st.selectbox(
456
- "اختر بند للتعديل",
457
- options=[item['id'] for item in project_items],
458
- format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
459
- key="select_boq_item"
460
- )
461
-
462
- if st.button("تعديل البند", key="edit_boq_item_btn"):
463
- st.session_state.edit_boq_item_id = selected_item_id
464
- st.session_state.show_edit_boq_item_form = True
465
- st.rerun()
 
466
 
467
  def _render_new_boq_item_form(self):
468
  """عرض نموذج إضافة بند جديد"""
@@ -944,7 +965,9 @@ class PricingApp:
944
  self._setup_arabic_fonts()
945
 
946
  # إنشاء بيانات الرسم البياني
947
- labels = ['المواد', 'المعدات', 'العمالة', 'المصاريف العمومية', 'الربح']
 
 
948
 
949
  # التحقق من وجود قيم NaN واستبدالها بأصفار
950
  values = [
@@ -974,7 +997,9 @@ class PricingApp:
974
  plt.setp(autotexts, size=10, weight="bold")
975
 
976
  # إضافة العنوان
977
- ax.set_title('توزيع تكلفة البند', fontsize=16)
 
 
978
 
979
  # عرض الرسم البياني
980
  st.pyplot(fig)
@@ -1042,8 +1067,9 @@ class PricingApp:
1042
  fig, ax = plt.subplots(figsize=(10, 6))
1043
 
1044
  # التحقق من وجود قيم NaN واستبدالها بأصفار
1045
- labels = list(cost_by_resource_type.keys())
1046
- values = [cost_by_resource_type[label] if not np.isnan(cost_by_resource_type[label]) else 0 for label in labels]
 
1047
 
1048
  # التحقق من أن جميع القيم موجبة (أو صفر) لتجنب الأخطاء في الرسم البياني
1049
  values = [max(0, val) for val in values]
@@ -1061,7 +1087,9 @@ class PricingApp:
1061
  plt.setp(autotexts, size=10, weight="bold")
1062
 
1063
  # إضافة العنوان
1064
- ax.set_title('توزيع التكلفة حسب نوع المورد', fontsize=16)
 
 
1065
 
1066
  # عرض الرسم البياني
1067
  st.pyplot(fig)
@@ -1157,11 +1185,14 @@ class PricingApp:
1157
  p_amount = (total_cost + o_cost) * (p_margin / 100)
1158
  f_price = total_cost + o_cost + p_amount
1159
 
 
 
 
1160
  sensitivity_data.append({
1161
  "نسبة الربح": p_margin,
1162
  "نسبة المصاريف العمومية": o_percentage,
1163
  "السعر النهائي": f_price,
1164
- "نسبة الربح المعدلة": p_amount / f_price * 100 if f_price > 0 else 0
1165
  })
1166
 
1167
  # إنشاء DataFrame من بيانات تحليل الحساسية
@@ -1228,10 +1259,20 @@ class PricingApp:
1228
  )
1229
 
1230
  # إضافة التسميات
1231
- ax.set_xlabel('نسبة الربح', fontsize=12)
1232
- ax.set_ylabel('السعر النهائي (ريال)', fontsize=12)
1233
- ax.set_title('تحليل حساسية الربحية', fontsize=16)
1234
- ax.legend(title='نسبة المصاريف العمومية')
 
 
 
 
 
 
 
 
 
 
1235
  ax.grid(True)
1236
 
1237
  # تنسيق المحور Y
@@ -1332,52 +1373,53 @@ class PricingApp:
1332
  st.dataframe(df, use_container_width=True)
1333
 
1334
  # اختيار البند لتعديل معامله
1335
- selected_item_id = st.selectbox(
1336
- "اختر البند لتعديل معامله",
1337
- options=[item['id'] for item in project_items],
1338
- format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
1339
- key="select_item_for_custom_factor"
1340
- )
1341
-
1342
- # الحصول على البند المحدد
1343
- selected_item = next((item for item in project_items if item['id'] == selected_item_id), None)
1344
-
1345
- if selected_item:
1346
- # الحصول على المعامل الحالي للبند
1347
- current_factor = st.session_state.unbalanced_pricing_factors.get('custom_factors', {}).get(str(selected_item_id), 1.0)
1348
-
1349
- # عرض شريط تمرير لتعديل معامل البند
1350
- custom_factor = st.slider(
1351
- f"معامل البند: {selected_item['description']}",
1352
- min_value=0.5,
1353
- max_value=2.0,
1354
- value=current_factor,
1355
- step=0.01,
1356
- key="custom_factor_slider"
1357
  )
1358
 
1359
- # تحديث معامل البند
1360
- if 'custom_factors' not in st.session_state.unbalanced_pricing_factors:
1361
- st.session_state.unbalanced_pricing_factors['custom_factors'] = {}
1362
-
1363
- st.session_state.unbalanced_pricing_factors['custom_factors'][str(selected_item_id)] = custom_factor
1364
-
1365
- # عرض السعر الأصلي والسعر المعدل
1366
- original_price = selected_item['unit_price']
1367
- adjusted_price = original_price * custom_factor
1368
 
1369
- col3, col4 = st.columns(2)
1370
- with col3:
1371
- st.metric("السعر الأصلي", f"{original_price:,.2f} ريال")
1372
- with col4:
1373
- st.metric("السعر المعدل", f"{adjusted_price:,.2f} ريال", delta=f"{(custom_factor - 1) * 100:.1f}%")
1374
 
1375
- # عرض زر تطبيق التسعير الغير متزن
1376
- if st.button("تطبيق التسعير الغير متزن", key="apply_unbalanced_pricing_btn"):
1377
- # تطبيق التسعير الغير متزن على جميع البنود
1378
- self._apply_unbalanced_pricing()
1379
- st.success("تم تطبيق التسعير الغير متزن بنجاح!")
1380
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1381
 
1382
  elif pricing_strategy == "تسعير موجه ربحية":
1383
  st.info("""
@@ -1460,16 +1502,25 @@ class PricingApp:
1460
  fig, ax = plt.subplots(figsize=(10, 6))
1461
 
1462
  # إنشاء بيانات الرسم البياني
1463
- strategies = comparison_data['استراتيجية التسعير']
 
1464
  prices = [standard_price, unbalanced_price, profit_oriented_price, competitive_price, strategic_price]
1465
 
1466
  # رسم الأعمدة
1467
  bars = ax.bar(strategies, prices, color=['blue', 'green', 'red', 'purple', 'orange'])
1468
 
1469
  # إضافة التسميات
1470
- ax.set_xlabel('استراتيجية التسعير', fontsize=12)
1471
- ax.set_ylabel('السعر النهائي (ريال)', fontsize=12)
1472
- ax.set_title('مقارنة بين استراتيجيات التسعير', fontsize=16)
 
 
 
 
 
 
 
 
1473
 
1474
  # تنسيق المحور Y
1475
  ax.get_yaxis().set_major_formatter(
@@ -1557,3 +1608,156 @@ class PricingApp:
1557
  if item['project_id'] == st.session_state.current_project:
1558
  st.session_state.boq_items[i]['unit_price'] *= adjustment_factor
1559
  st.session_state.boq_items[i]['total_price'] = item['quantity'] * st.session_state.boq_items[i]['unit_price']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import numpy as np
4
  import matplotlib.pyplot as plt
5
  import matplotlib.font_manager as fm
6
+ import arabic_reshaper
7
+ from bidi.algorithm import get_display
8
  import io
9
  import base64
10
  from datetime import datetime
11
  import random
12
  import json
13
+ import os
14
 
15
  class PricingApp:
16
  """وحدة التسعير المتكاملة"""
 
142
  'custom_factors': {} # معاملات مخصصة لبنود محددة
143
  }
144
 
145
+ # تهيئة حالة حفظ التسعير
146
+ if 'saved_pricing' not in st.session_state:
147
+ st.session_state.saved_pricing = []
148
+
149
  def render(self):
150
  """طريقة للتوافق مع الواجهة القديمة"""
151
  self.run()
 
200
 
201
  with tab5:
202
  self._render_pricing_strategies(project_info)
203
+
204
+ # عرض أزرار التصدير والحفظ
205
+ self._render_export_save_buttons(project_info)
206
 
207
  def _render_new_project_form(self):
208
  """عرض نموذج إنشاء مشروع جديد"""
 
345
 
346
  # إنشاء DataFrame من قائمة المشاريع
347
  df = pd.DataFrame(st.session_state.projects)
348
+ if len(df) > 0 and 'id' in df.columns:
349
+ df = df[['id', 'name', 'client', 'estimated_value', 'deadline', 'status', 'pricing_type']]
350
+ df.columns = ['الرقم', 'اسم المشروع', 'العميل', 'القيمة التقديرية', 'الموعد النهائي', 'الحالة', 'نوع التسعير']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
+ # تنسيق القيمة التقديرية
353
+ df['القيمة التقديرية'] = df['القيمة التقديرية'].apply(lambda x: f"{x:,.2f} ريال")
354
+
355
+ # عرض الجدول
356
+ st.dataframe(df, use_container_width=True)
357
+
358
+ # اختيار المشروع
359
+ col1, col2 = st.columns(2)
360
+ with col1:
361
+ project_ids = [p['id'] for p in st.session_state.projects]
362
+ if st.session_state.current_project in project_ids:
363
+ current_index = project_ids.index(st.session_state.current_project)
364
+ else:
365
+ current_index = 0 if project_ids else None
366
+
367
+ if current_index is not None and project_ids:
368
+ selected_project_id = st.selectbox(
369
+ "اختر المشروع",
370
+ options=project_ids,
371
+ format_func=lambda x: next((p['name'] for p in st.session_state.projects if p['id'] == x), ""),
372
+ index=current_index,
373
+ key="select_project"
374
+ )
375
+
376
+ if selected_project_id != st.session_state.current_project:
377
+ st.session_state.current_project = selected_project_id
378
+ st.rerun()
379
+
380
+ with col2:
381
+ if st.button("تعديل المشروع", key="edit_project_btn"):
382
+ st.session_state.edit_project_id = st.session_state.current_project
383
+ st.session_state.show_edit_project_form = True
384
+ st.rerun()
385
+ else:
386
+ st.info("لا توجد مشاريع. قم بإنشاء مشروع جديد للبدء.")
387
 
388
  def _get_current_project_info(self):
389
  """الحصول على معلومات المشروع الحالي"""
 
460
  # أزرار التصدير والتعديل
461
  col1, col2 = st.columns(2)
462
  with col1:
463
+ if st.button("تصدير جدول الكميات", key="export_boq_btn_1"):
464
  # إنشاء CSV للتصدير
465
  export_df = pd.DataFrame(project_items)
466
  export_df = export_df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
 
468
 
469
  csv = export_df.to_csv(index=False)
470
  b64 = base64.b64encode(csv.encode()).decode()
471
+ href = f'<a href="data:file/csv;base64,{b64}" download="boq_{st.session_state.current_project}.csv">تحميل CSV</a>'
472
  st.markdown(href, unsafe_allow_html=True)
473
 
474
  with col2:
475
+ if len(project_items) > 0:
476
+ selected_item_id = st.selectbox(
477
+ "اختر بند للتعديل",
478
+ options=[item['id'] for item in project_items],
479
+ format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
480
+ key="select_boq_item"
481
+ )
482
+
483
+ if st.button("تعديل البند", key="edit_boq_item_btn"):
484
+ st.session_state.edit_boq_item_id = selected_item_id
485
+ st.session_state.show_edit_boq_item_form = True
486
+ st.rerun()
487
 
488
  def _render_new_boq_item_form(self):
489
  """عرض نموذج إضافة بند جديد"""
 
965
  self._setup_arabic_fonts()
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 واستبدالها بأصفار
973
  values = [
 
997
  plt.setp(autotexts, size=10, weight="bold")
998
 
999
  # إضافة العنوان
1000
+ title_ar = 'توزيع تكلفة البند'
1001
+ title = get_display(arabic_reshaper.reshape(title_ar))
1002
+ ax.set_title(title, fontsize=16)
1003
 
1004
  # عرض الرسم البياني
1005
  st.pyplot(fig)
 
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]
 
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)
 
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 من بيانات تحليل الحساسية
 
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
 
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("""
 
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(
 
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'.")