Spaces:
Sleeping
Sleeping
Update modules/pricing/pricing_app.py
Browse files- 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 |
-
|
339 |
-
|
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 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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="
|
452 |
st.markdown(href, unsafe_allow_html=True)
|
453 |
|
454 |
with col2:
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
st.
|
464 |
-
|
465 |
-
|
|
|
466 |
|
467 |
def _render_new_boq_item_form(self):
|
468 |
"""عرض نموذج إضافة بند جديد"""
|
@@ -944,7 +965,9 @@ class PricingApp:
|
|
944 |
self._setup_arabic_fonts()
|
945 |
|
946 |
# إنشاء بيانات الرسم البياني
|
947 |
-
|
|
|
|
|
948 |
|
949 |
# التحقق من وجود قيم NaN واستبدالها بأصفار
|
950 |
values = [
|
@@ -974,7 +997,9 @@ class PricingApp:
|
|
974 |
plt.setp(autotexts, size=10, weight="bold")
|
975 |
|
976 |
# إضافة العنوان
|
977 |
-
|
|
|
|
|
978 |
|
979 |
# عرض الرسم البياني
|
980 |
st.pyplot(fig)
|
@@ -1042,8 +1067,9 @@ class PricingApp:
|
|
1042 |
fig, ax = plt.subplots(figsize=(10, 6))
|
1043 |
|
1044 |
# التحقق من وجود قيم NaN واستبدالها بأصفار
|
1045 |
-
|
1046 |
-
|
|
|
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 |
-
|
|
|
|
|
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 |
-
"نسبة الربح المعدلة":
|
1165 |
})
|
1166 |
|
1167 |
# إنشاء DataFrame من بيانات تحليل الحساسية
|
@@ -1228,10 +1259,20 @@ class PricingApp:
|
|
1228 |
)
|
1229 |
|
1230 |
# إضافة التسميات
|
1231 |
-
|
1232 |
-
|
1233 |
-
|
1234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1235 |
ax.grid(True)
|
1236 |
|
1237 |
# تنسيق المحور Y
|
@@ -1332,52 +1373,53 @@ class PricingApp:
|
|
1332 |
st.dataframe(df, use_container_width=True)
|
1333 |
|
1334 |
# اختيار البند لتعديل معامله
|
1335 |
-
|
1336 |
-
|
1337 |
-
|
1338 |
-
|
1339 |
-
|
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 '
|
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 |
-
|
1370 |
-
|
1371 |
-
st.
|
1372 |
-
with col4:
|
1373 |
-
st.metric("السعر المعدل", f"{adjusted_price:,.2f} ريال", delta=f"{(custom_factor - 1) * 100:.1f}%")
|
1374 |
|
1375 |
-
|
1376 |
-
|
1377 |
-
|
1378 |
-
|
1379 |
-
|
1380 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
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 |
-
|
1471 |
-
|
1472 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'.")
|