Update modules/pricing/pricing_app.py
Browse files- modules/pricing/pricing_app.py +131 -1434
modules/pricing/pricing_app.py
CHANGED
@@ -57,23 +57,73 @@ class PricingApp:
|
|
57 |
},
|
58 |
{
|
59 |
'id': 4,
|
60 |
-
'code': '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
'description': 'توريد وتركيب حديد تسليح',
|
62 |
'unit': 'طن',
|
63 |
-
'quantity':
|
64 |
-
'unit_price':
|
65 |
-
'total_price':
|
66 |
'category': 'أعمال حديد'
|
67 |
},
|
68 |
{
|
69 |
-
'id':
|
70 |
-
'code': '
|
71 |
'description': 'توريد وبناء طابوق',
|
72 |
'unit': 'م2',
|
73 |
-
'quantity':
|
74 |
-
'unit_price':
|
75 |
'total_price': 54000,
|
76 |
'category': 'أعمال بناء'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
}
|
78 |
]
|
79 |
|
@@ -84,16 +134,16 @@ class PricingApp:
|
|
84 |
'category': 'تكاليف مباشرة',
|
85 |
'subcategory': 'مواد',
|
86 |
'description': 'خرسانة',
|
87 |
-
'amount':
|
88 |
-
'percentage':
|
89 |
},
|
90 |
{
|
91 |
'id': 2,
|
92 |
'category': 'تكاليف مباشرة',
|
93 |
'subcategory': 'مواد',
|
94 |
'description': 'حديد تسليح',
|
95 |
-
'amount':
|
96 |
-
'percentage':
|
97 |
},
|
98 |
{
|
99 |
'id': 3,
|
@@ -192,6 +242,11 @@ class PricingApp:
|
|
192 |
}
|
193 |
]
|
194 |
|
|
|
|
|
|
|
|
|
|
|
195 |
def render(self):
|
196 |
"""عرض واجهة وحدة التسعير"""
|
197 |
|
@@ -301,1460 +356,102 @@ class PricingApp:
|
|
301 |
'المبلغ': list(direct_cost_subcategories.values())
|
302 |
})
|
303 |
|
304 |
-
# إنشاء رسم بياني
|
305 |
-
fig = px.
|
306 |
direct_cost_df,
|
307 |
-
|
308 |
-
|
309 |
title='توزيع التكاليف المباشرة',
|
310 |
-
|
311 |
-
text_auto='.2s'
|
312 |
)
|
313 |
|
314 |
st.plotly_chart(fig, use_container_width=True)
|
315 |
|
316 |
-
# عرض
|
317 |
-
st.markdown("###
|
318 |
|
319 |
-
#
|
320 |
-
|
321 |
-
'السيناريو': [item['name'] for item in st.session_state.price_scenarios],
|
322 |
-
'التكلفة الإجمالية': [item['total_cost'] for item in st.session_state.price_scenarios],
|
323 |
-
'هامش الربح (%)': [item['profit_margin'] for item in st.session_state.price_scenarios],
|
324 |
-
'السعر الإجمالي': [item['total_price'] for item in st.session_state.price_scenarios]
|
325 |
-
})
|
326 |
-
|
327 |
-
# إنشاء رسم بياني شريطي مزدوج
|
328 |
-
fig = go.Figure()
|
329 |
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
# إضافة شريط للسعر الإجمالي
|
339 |
-
fig.add_trace(go.Bar(
|
340 |
-
x=scenarios_df['السيناريو'],
|
341 |
-
y=scenarios_df['السعر الإجمالي'],
|
342 |
-
name='السعر الإجمالي',
|
343 |
-
marker_color='lightsalmon'
|
344 |
-
))
|
345 |
-
|
346 |
-
# إضافة خط لهامش الربح
|
347 |
-
fig.add_trace(go.Scatter(
|
348 |
-
x=scenarios_df['السيناريو'],
|
349 |
-
y=scenarios_df['هامش الربح (%)'] * 10000, # تكبير القيم لتظهر على الرسم البياني
|
350 |
-
name='هامش الربح (%)',
|
351 |
-
yaxis='y2',
|
352 |
-
line=dict(color='royalblue', width=4)
|
353 |
-
))
|
354 |
|
355 |
-
#
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
yaxis2=dict(
|
361 |
-
title='هامش الربح (%)',
|
362 |
-
titlefont=dict(color='royalblue'),
|
363 |
-
tickfont=dict(color='royalblue'),
|
364 |
-
overlaying='y',
|
365 |
-
side='right',
|
366 |
-
range=[0, 20]
|
367 |
-
),
|
368 |
-
barmode='group',
|
369 |
-
legend=dict(
|
370 |
-
x=0,
|
371 |
-
y=1.2,
|
372 |
-
orientation='h'
|
373 |
-
)
|
374 |
-
)
|
375 |
|
376 |
-
#
|
377 |
-
fig.
|
378 |
-
|
379 |
-
|
|
|
|
|
|
|
380 |
)
|
381 |
|
382 |
st.plotly_chart(fig, use_container_width=True)
|
383 |
-
|
384 |
-
# عرض مؤشرات الأداء الرئيسية
|
385 |
-
st.markdown("### مؤشرات الأداء الرئيسية")
|
386 |
-
|
387 |
-
col1, col2, col3 = st.columns(3)
|
388 |
-
|
389 |
-
with col1:
|
390 |
-
# حساب نسبة التكاليف المباشرة من إجمالي التكاليف
|
391 |
-
direct_cost_percentage = (total_direct_cost / total_cost) * 100
|
392 |
-
st.metric("نسبة التكاليف المباشرة", f"{direct_cost_percentage:.1f}%")
|
393 |
-
|
394 |
-
with col2:
|
395 |
-
# حساب نسبة التكاليف غير المباشرة من إجمالي التكاليف
|
396 |
-
indirect_cost_percentage = (total_indirect_cost / total_cost) * 100
|
397 |
-
st.metric("نسبة التكاليف غير المباشرة", f"{indirect_cost_percentage:.1f}%")
|
398 |
-
|
399 |
-
with col3:
|
400 |
-
# حساب نسبة هامش الربح من السعر الإجمالي
|
401 |
-
profit_margin = (total_profit / total_price) * 100
|
402 |
-
st.metric("هامش الربح", f"{profit_margin:.1f}%")
|
403 |
|
404 |
def _render_bill_of_quantities_tab(self):
|
405 |
"""عرض تبويب جدول الكميات"""
|
406 |
|
407 |
st.markdown("### جدول الكميات")
|
408 |
|
409 |
-
#
|
410 |
-
st.markdown("#### قائمة البنود")
|
411 |
-
|
412 |
-
# تحويل قائمة البنود إلى DataFrame
|
413 |
boq_df = pd.DataFrame(st.session_state.bill_of_quantities)
|
414 |
|
415 |
-
# عرض
|
416 |
-
|
417 |
-
boq_df,
|
418 |
column_config={
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
"quantity": st.column_config.NumberColumn("الكمية", min_value=0),
|
427 |
-
"unit_price": st.column_config.NumberColumn("سعر الوحدة (ريال)", min_value=0, format="%.2f"),
|
428 |
-
"total_price": st.column_config.NumberColumn("السعر الإجمالي (ريال)", min_value=0, format="%.2f", disabled=True),
|
429 |
-
"category": st.column_config.SelectboxColumn(
|
430 |
-
"الفئة",
|
431 |
-
options=["أعمال ترابية", "أعمال خرسانية", "أعمال حديد", "أعمال بناء", "أعمال تشطيب", "أعمال كهربائية", "أعمال ميكانيكية", "أعمال صحية", "أخرى"]
|
432 |
-
)
|
433 |
},
|
434 |
-
use_container_width=True,
|
435 |
hide_index=True,
|
436 |
-
|
437 |
)
|
438 |
|
439 |
-
# تحديث السعر الإجمالي لكل بند
|
440 |
-
for i, row in edited_df.iterrows():
|
441 |
-
edited_df.at[i, 'total_price'] = row['quantity'] * row['unit_price']
|
442 |
-
|
443 |
-
# تحديث قائمة البنود
|
444 |
-
if not edited_df.equals(boq_df):
|
445 |
-
st.session_state.bill_of_quantities = edited_df.to_dict('records')
|
446 |
-
st.success("تم تحديث جدول الكميات بنجاح!")
|
447 |
-
|
448 |
-
# عرض إجمالي جدول الكميات
|
449 |
-
total_boq = sum(item['total_price'] for item in st.session_state.bill_of_quantities)
|
450 |
-
st.metric("إجمالي جدول الكميات", f"{total_boq:,.2f} ريال")
|
451 |
-
|
452 |
# إضافة بند جديد
|
453 |
-
st.markdown("
|
454 |
-
|
455 |
-
with st.form(key="add_boq_item_form"):
|
456 |
-
col1, col2 = st.columns(2)
|
457 |
-
|
458 |
-
with col1:
|
459 |
-
new_code = st.text_input("الكود", key="new_boq_code")
|
460 |
-
new_description = st.text_area("الوصف", key="new_boq_description")
|
461 |
-
new_category = st.selectbox(
|
462 |
-
"الفئة",
|
463 |
-
["أعمال ترابية", "أعمال خرسانية", "أعمال حديد", "أعمال بناء", "أعمال تشطيب", "أعمال كهربائية", "أعمال ميكانيكية", "أعمال صحية", "أخرى"],
|
464 |
-
key="new_boq_category"
|
465 |
-
)
|
466 |
-
|
467 |
-
with col2:
|
468 |
-
new_unit = st.selectbox(
|
469 |
-
"الوحدة",
|
470 |
-
["م3", "م2", "طن", "كجم", "عدد", "لتر", "متر"],
|
471 |
-
key="new_boq_unit"
|
472 |
-
)
|
473 |
-
new_quantity = st.number_input("الكمية", min_value=0.0, key="new_boq_quantity")
|
474 |
-
new_unit_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, key="new_boq_unit_price")
|
475 |
-
|
476 |
-
submit_button = st.form_submit_button("إضافة بند")
|
477 |
-
|
478 |
-
if submit_button:
|
479 |
-
if new_code and new_description:
|
480 |
-
# إنشاء معرف جديد
|
481 |
-
new_id = max([item['id'] for item in st.session_state.bill_of_quantities], default=0) + 1
|
482 |
-
|
483 |
-
# حساب السعر الإجمالي
|
484 |
-
new_total_price = new_quantity * new_unit_price
|
485 |
-
|
486 |
-
# إضافة البند الجديد
|
487 |
-
st.session_state.bill_of_quantities.append({
|
488 |
-
'id': new_id,
|
489 |
-
'code': new_code,
|
490 |
-
'description': new_description,
|
491 |
-
'unit': new_unit,
|
492 |
-
'quantity': new_quantity,
|
493 |
-
'unit_price': new_unit_price,
|
494 |
-
'total_price': new_total_price,
|
495 |
-
'category': new_category
|
496 |
-
})
|
497 |
-
|
498 |
-
st.success(f"تمت إضافة البند '{new_code}' بنجاح!")
|
499 |
-
st.rerun()
|
500 |
-
else:
|
501 |
-
st.error("يرجى إدخال الكود والوصف.")
|
502 |
-
|
503 |
-
# تحليل جدول الكميات
|
504 |
-
st.markdown("#### تحليل جدول الكميات")
|
505 |
-
|
506 |
-
col1, col2 = st.columns(2)
|
507 |
-
|
508 |
-
with col1:
|
509 |
-
# توزيع البنود حسب الفئة
|
510 |
-
category_totals = {}
|
511 |
-
|
512 |
-
for item in st.session_state.bill_of_quantities:
|
513 |
-
category = item['category']
|
514 |
-
if category in category_totals:
|
515 |
-
category_totals[category] += item['total_price']
|
516 |
-
else:
|
517 |
-
category_totals[category] = item['total_price']
|
518 |
-
|
519 |
-
category_df = pd.DataFrame({
|
520 |
-
'الفئة': list(category_totals.keys()),
|
521 |
-
'المبلغ': list(category_totals.values())
|
522 |
-
})
|
523 |
-
|
524 |
-
fig = px.pie(
|
525 |
-
category_df,
|
526 |
-
values='المبلغ',
|
527 |
-
names='الفئة',
|
528 |
-
title='توزيع جدول الكميات حسب الفئة'
|
529 |
-
)
|
530 |
-
|
531 |
-
st.plotly_chart(fig, use_container_width=True)
|
532 |
-
|
533 |
-
with col2:
|
534 |
-
# ترتيب البنود حسب القيمة
|
535 |
-
top_items = sorted(st.session_state.bill_of_quantities, key=lambda x: x['total_price'], reverse=True)[:5]
|
536 |
-
|
537 |
-
top_items_df = pd.DataFrame({
|
538 |
-
'البند': [item['code'] + ' - ' + item['description'][:20] + '...' for item in top_items],
|
539 |
-
'القيمة': [item['total_price'] for item in top_items]
|
540 |
-
})
|
541 |
-
|
542 |
-
fig = px.bar(
|
543 |
-
top_items_df,
|
544 |
-
x='البند',
|
545 |
-
y='القيمة',
|
546 |
-
title='أعلى 5 بنود من حيث القيمة',
|
547 |
-
color='القيمة',
|
548 |
-
text_auto='.2s'
|
549 |
-
)
|
550 |
-
|
551 |
-
st.plotly_chart(fig, use_container_width=True)
|
552 |
-
|
553 |
-
# استيراد وتصدير جدول الكميات
|
554 |
-
st.markdown("#### استيراد وتصدير جدول الكميات")
|
555 |
-
|
556 |
-
col1, col2 = st.columns(2)
|
557 |
-
|
558 |
-
with col1:
|
559 |
-
if st.button("تصدير جدول الكميات إلى Excel", key="export_boq_button"):
|
560 |
-
# محاكاة تصدير البيانات
|
561 |
-
st.success("تم تصدير جدول الكميات إلى Excel بنجاح!")
|
562 |
-
|
563 |
-
with col2:
|
564 |
-
uploaded_file = st.file_uploader("استيراد جدول الكميات من Excel", type=["xlsx"], key="import_boq_file")
|
565 |
-
|
566 |
-
if uploaded_file is not None:
|
567 |
-
if st.button("استيراد البيانات", key="import_boq_button"):
|
568 |
-
# محاكاة استيراد البيانات
|
569 |
-
st.success("تم استيراد جدول الكميات بنجاح!")
|
570 |
-
|
571 |
-
def _render_cost_analysis_tab(self):
|
572 |
-
"""عرض تبويب تحليل التكاليف"""
|
573 |
-
|
574 |
-
st.markdown("### تحليل التكاليف")
|
575 |
-
|
576 |
-
# عرض تحليل التكاليف الحالي
|
577 |
-
st.markdown("#### قائمة التكاليف")
|
578 |
-
|
579 |
-
# تحويل قائمة التكاليف إلى DataFrame
|
580 |
-
cost_df = pd.DataFrame(st.session_state.cost_analysis)
|
581 |
-
|
582 |
-
# عرض التكاليف كجدول قابل للتعديل
|
583 |
-
edited_df = st.data_editor(
|
584 |
-
cost_df,
|
585 |
-
column_config={
|
586 |
-
"id": st.column_config.NumberColumn("الرقم", disabled=True),
|
587 |
-
"category": st.column_config.SelectboxColumn(
|
588 |
-
"الفئة",
|
589 |
-
options=["تكاليف مباشرة", "تكاليف غير مباشرة", "أرباح"]
|
590 |
-
),
|
591 |
-
"subcategory": st.column_config.TextColumn("الفئة الفرعية"),
|
592 |
-
"description": st.column_config.TextColumn("الوصف"),
|
593 |
-
"amount": st.column_config.NumberColumn("المبلغ (ريال)", min_value=0, format="%.2f"),
|
594 |
-
"percentage": st.column_config.NumberColumn("النسبة (%)", min_value=0, format="%.1f", disabled=True)
|
595 |
-
},
|
596 |
-
use_container_width=True,
|
597 |
-
hide_index=True,
|
598 |
-
num_rows="dynamic"
|
599 |
-
)
|
600 |
-
|
601 |
-
# حساب إجمالي التكاليف
|
602 |
-
total_amount = sum(item['amount'] for item in st.session_state.cost_analysis)
|
603 |
-
|
604 |
-
# تحديث النسبة المئوية لكل بند
|
605 |
-
for i, row in edited_df.iterrows():
|
606 |
-
edited_df.at[i, 'percentage'] = (row['amount'] / total_amount) * 100
|
607 |
-
|
608 |
-
# تحديث قائمة التكاليف
|
609 |
-
if not edited_df.equals(cost_df):
|
610 |
-
st.session_state.cost_analysis = edited_df.to_dict('records')
|
611 |
-
st.success("تم تحديث تحليل التكاليف بنجاح!")
|
612 |
-
|
613 |
-
# عرض إجمالي التكاليف
|
614 |
-
st.metric("إجمالي التكاليف", f"{total_amount:,.2f} ريال")
|
615 |
-
|
616 |
-
# إضافة تكلفة جديدة
|
617 |
-
st.markdown("#### إضافة تكلفة جديدة")
|
618 |
-
|
619 |
-
with st.form(key="add_cost_item_form"):
|
620 |
-
col1, col2 = st.columns(2)
|
621 |
-
|
622 |
-
with col1:
|
623 |
-
new_category = st.selectbox(
|
624 |
-
"الفئة",
|
625 |
-
["تكاليف مباشرة", "تكاليف غير مباشرة", "أرباح"],
|
626 |
-
key="new_cost_category"
|
627 |
-
)
|
628 |
-
new_subcategory = st.text_input("الفئة الفرعية", key="new_cost_subcategory")
|
629 |
-
|
630 |
-
with col2:
|
631 |
-
new_description = st.text_input("الوصف", key="new_cost_description")
|
632 |
-
new_amount = st.number_input("المبلغ (ريال)", min_value=0.0, key="new_cost_amount")
|
633 |
-
|
634 |
-
submit_button = st.form_submit_button("إضافة تكلفة")
|
635 |
-
|
636 |
-
if submit_button:
|
637 |
-
if new_description and new_subcategory:
|
638 |
-
# إنشاء معرف جديد
|
639 |
-
new_id = max([item['id'] for item in st.session_state.cost_analysis], default=0) + 1
|
640 |
-
|
641 |
-
# حساب النسبة المئوية
|
642 |
-
new_percentage = (new_amount / (total_amount + new_amount)) * 100
|
643 |
-
|
644 |
-
# إضافة التكلفة الجديدة
|
645 |
-
st.session_state.cost_analysis.append({
|
646 |
-
'id': new_id,
|
647 |
-
'category': new_category,
|
648 |
-
'subcategory': new_subcategory,
|
649 |
-
'description': new_description,
|
650 |
-
'amount': new_amount,
|
651 |
-
'percentage': new_percentage
|
652 |
-
})
|
653 |
-
|
654 |
-
# إعادة حساب النسب المئوية لجميع البنود
|
655 |
-
new_total = total_amount + new_amount
|
656 |
-
for item in st.session_state.cost_analysis:
|
657 |
-
item['percentage'] = (item['amount'] / new_total) * 100
|
658 |
-
|
659 |
-
st.success(f"تمت إضافة التكلفة '{new_description}' بنجاح!")
|
660 |
-
st.rerun()
|
661 |
-
else:
|
662 |
-
st.error("يرجى إدخال الفئة الفرعية والوصف.")
|
663 |
-
|
664 |
-
# تحليل التكاليف
|
665 |
-
st.markdown("#### تحليل التكاليف")
|
666 |
-
|
667 |
-
# تحليل التكاليف حسب الفئة
|
668 |
-
st.markdown("##### توزيع التكاليف حسب الفئة")
|
669 |
-
|
670 |
-
# تجميع البيانات حسب الفئة
|
671 |
-
category_totals = {}
|
672 |
-
|
673 |
-
for item in st.session_state.cost_analysis:
|
674 |
-
category = item['category']
|
675 |
-
if category in category_totals:
|
676 |
-
category_totals[category] += item['amount']
|
677 |
-
else:
|
678 |
-
category_totals[category] = item['amount']
|
679 |
-
|
680 |
-
category_df = pd.DataFrame({
|
681 |
-
'الفئة': list(category_totals.keys()),
|
682 |
-
'المبلغ': list(category_totals.values())
|
683 |
-
})
|
684 |
-
|
685 |
-
fig = px.pie(
|
686 |
-
category_df,
|
687 |
-
values='المبلغ',
|
688 |
-
names='الفئة',
|
689 |
-
title='توزيع التكاليف حسب الفئة',
|
690 |
-
color_discrete_sequence=px.colors.qualitative.Set3
|
691 |
-
)
|
692 |
-
|
693 |
-
st.plotly_chart(fig, use_container_width=True)
|
694 |
-
|
695 |
-
# تحليل التكاليف المباشرة
|
696 |
-
st.markdown("##### تحليل التكاليف المباشرة")
|
697 |
|
698 |
col1, col2 = st.columns(2)
|
699 |
|
700 |
with col1:
|
701 |
-
|
702 |
-
|
703 |
-
|
704 |
-
for item in st.session_state.cost_analysis:
|
705 |
-
if item['category'] == 'تكاليف مباشرة':
|
706 |
-
subcategory = item['subcategory']
|
707 |
-
if subcategory in direct_subcategory_totals:
|
708 |
-
direct_subcategory_totals[subcategory] += item['amount']
|
709 |
-
else:
|
710 |
-
direct_subcategory_totals[subcategory] = item['amount']
|
711 |
-
|
712 |
-
direct_subcategory_df = pd.DataFrame({
|
713 |
-
'الفئة الفرعية': list(direct_subcategory_totals.keys()),
|
714 |
-
'المبلغ': list(direct_subcategory_totals.values())
|
715 |
-
})
|
716 |
-
|
717 |
-
fig = px.pie(
|
718 |
-
direct_subcategory_df,
|
719 |
-
values='المبلغ',
|
720 |
-
names='الفئة الفرعية',
|
721 |
-
title='توزيع التكاليف المباشرة حسب الفئة الفرعية'
|
722 |
-
)
|
723 |
-
|
724 |
-
st.plotly_chart(fig, use_container_width=True)
|
725 |
|
726 |
with col2:
|
727 |
-
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
|
742 |
-
|
743 |
-
|
744 |
-
direct_description_df = direct_description_df.sort_values(by='المبلغ', ascending=False)
|
745 |
-
|
746 |
-
fig = px.bar(
|
747 |
-
direct_description_df,
|
748 |
-
x='الوصف',
|
749 |
-
y='المبلغ',
|
750 |
-
title='توزيع التكاليف المباشرة حسب البند',
|
751 |
-
color='المبلغ',
|
752 |
-
text_auto='.2s'
|
753 |
-
)
|
754 |
-
|
755 |
-
st.plotly_chart(fig, use_container_width=True)
|
756 |
-
|
757 |
-
# تحليل التكاليف غير المباشرة
|
758 |
-
st.markdown("##### تحليل التكاليف غير المباشرة")
|
759 |
-
|
760 |
-
col1, col2 = st.columns(2)
|
761 |
-
|
762 |
-
with col1:
|
763 |
-
# تجميع البيانات حسب الفئة الفرعية للتكاليف غير المباشرة
|
764 |
-
indirect_subcategory_totals = {}
|
765 |
-
|
766 |
-
for item in st.session_state.cost_analysis:
|
767 |
-
if item['category'] == 'تكاليف غير مباشرة':
|
768 |
-
subcategory = item['subcategory']
|
769 |
-
if subcategory in indirect_subcategory_totals:
|
770 |
-
indirect_subcategory_totals[subcategory] += item['amount']
|
771 |
-
else:
|
772 |
-
indirect_subcategory_totals[subcategory] = item['amount']
|
773 |
-
|
774 |
-
indirect_subcategory_df = pd.DataFrame({
|
775 |
-
'الفئة الفرعية': list(indirect_subcategory_totals.keys()),
|
776 |
-
'المبلغ': list(indirect_subcategory_totals.values())
|
777 |
-
})
|
778 |
-
|
779 |
-
fig = px.pie(
|
780 |
-
indirect_subcategory_df,
|
781 |
-
values='المبلغ',
|
782 |
-
names='الفئة الفرعية',
|
783 |
-
title='توزيع التكاليف غير المباشرة حسب الفئة الفرعية'
|
784 |
-
)
|
785 |
-
|
786 |
-
st.plotly_chart(fig, use_container_width=True)
|
787 |
-
|
788 |
-
with col2:
|
789 |
-
# تجميع البيانات حسب الوصف للتكاليف غير المباشرة
|
790 |
-
indirect_description_totals = {}
|
791 |
-
|
792 |
-
for item in st.session_state.cost_analysis:
|
793 |
-
if item['category'] == 'تكاليف غير مباشرة':
|
794 |
-
description = item['description']
|
795 |
-
if description in indirect_description_totals:
|
796 |
-
indirect_description_totals[description] += item['amount']
|
797 |
-
else:
|
798 |
-
indirect_description_totals[description] = item['amount']
|
799 |
-
|
800 |
-
indirect_description_df = pd.DataFrame({
|
801 |
-
'الوصف': list(indirect_description_totals.keys()),
|
802 |
-
'المبلغ': list(indirect_description_totals.values())
|
803 |
-
})
|
804 |
-
|
805 |
-
# ترتيب البيانات تنازلياً حسب المبلغ
|
806 |
-
indirect_description_df = indirect_description_df.sort_values(by='المبلغ', ascending=False)
|
807 |
-
|
808 |
-
fig = px.bar(
|
809 |
-
indirect_description_df,
|
810 |
-
x='الوصف',
|
811 |
-
y='المبلغ',
|
812 |
-
title='توزيع التكاليف غير المباشرة حسب البند',
|
813 |
-
color='المبلغ',
|
814 |
-
text_auto='.2s'
|
815 |
-
)
|
816 |
-
|
817 |
-
st.plotly_chart(fig, use_container_width=True)
|
818 |
-
|
819 |
-
# استيراد وتصدير تحليل التكاليف
|
820 |
-
st.markdown("#### استيراد وتصدير تحليل التكاليف")
|
821 |
-
|
822 |
-
col1, col2 = st.columns(2)
|
823 |
-
|
824 |
-
with col1:
|
825 |
-
if st.button("تصدير تحليل التكاليف إلى Excel", key="export_cost_button"):
|
826 |
-
# محاكاة تصدير البيانات
|
827 |
-
st.success("تم تصدير تحليل التكاليف إلى Excel بنجاح!")
|
828 |
-
|
829 |
-
with col2:
|
830 |
-
uploaded_file = st.file_uploader("استيراد تحليل التكاليف من Excel", type=["xlsx"], key="import_cost_file")
|
831 |
-
|
832 |
-
if uploaded_file is not None:
|
833 |
-
if st.button("استيراد البيانات", key="import_cost_button"):
|
834 |
-
# محاكاة استيراد البيانات
|
835 |
-
st.success("تم استيراد تحليل التكاليف بنجاح!")
|
836 |
-
|
837 |
-
def _render_pricing_scenarios_tab(self):
|
838 |
-
"""عرض تبويب سيناريوهات التسعير"""
|
839 |
-
|
840 |
-
st.markdown("### سيناريوهات التسعير")
|
841 |
-
|
842 |
-
# عرض سيناريوهات التسعير الحالية
|
843 |
-
st.markdown("#### قائمة السيناريوهات")
|
844 |
-
|
845 |
-
# تحويل قائمة السيناريوهات إلى DataFrame
|
846 |
-
scenarios_df = pd.DataFrame(st.session_state.price_scenarios)
|
847 |
-
|
848 |
-
# عرض السيناريوهات كجدول قابل للتعديل
|
849 |
-
edited_df = st.data_editor(
|
850 |
-
scenarios_df,
|
851 |
-
column_config={
|
852 |
-
"id": st.column_config.NumberColumn("الرقم", disabled=True),
|
853 |
-
"name": st.column_config.TextColumn("اسم السيناريو"),
|
854 |
-
"description": st.column_config.TextColumn("الوصف"),
|
855 |
-
"total_cost": st.column_config.NumberColumn("إجمالي التكلفة (ريال)", min_value=0, format="%.2f"),
|
856 |
-
"profit_margin": st.column_config.NumberColumn("هامش الربح (%)", min_value=0, format="%.1f"),
|
857 |
-
"total_price": st.column_config.NumberColumn("السعر الإجمالي (ريال)", min_value=0, format="%.2f"),
|
858 |
-
"is_active": st.column_config.CheckboxColumn("نشط")
|
859 |
-
},
|
860 |
-
use_container_width=True,
|
861 |
-
hide_index=True,
|
862 |
-
num_rows="dynamic"
|
863 |
-
)
|
864 |
-
|
865 |
-
# تحديث قائمة السيناريوهات
|
866 |
-
if not edited_df.equals(scenarios_df):
|
867 |
-
# التأكد من وجود سيناريو نشط واحد فقط
|
868 |
-
active_count = sum(edited_df['is_active'])
|
869 |
-
if active_count != 1:
|
870 |
-
st.error("يجب أن يكون هناك سيناريو نشط واحد فقط.")
|
871 |
-
else:
|
872 |
-
st.session_state.price_scenarios = edited_df.to_dict('records')
|
873 |
-
st.success("تم تحديث سيناريوهات التسعير بنجاح!")
|
874 |
-
|
875 |
-
# إضافة سيناريو جديد
|
876 |
-
st.markdown("#### إضافة سيناريو جديد")
|
877 |
-
|
878 |
-
with st.form(key="add_scenario_form"):
|
879 |
-
col1, col2 = st.columns(2)
|
880 |
-
|
881 |
-
with col1:
|
882 |
-
new_name = st.text_input("اسم السيناريو", key="new_scenario_name")
|
883 |
-
new_description = st.text_area("الوصف", key="new_scenario_description")
|
884 |
-
|
885 |
-
with col2:
|
886 |
-
# حساب إجمالي التكاليف الحالي
|
887 |
-
total_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] != 'أرباح')
|
888 |
-
|
889 |
-
st.number_input("إجمالي التكلفة (ريال)", min_value=0.0, value=total_cost, key="new_scenario_total_cost", disabled=True)
|
890 |
-
new_profit_margin = st.number_input("هامش الربح (%)", min_value=0.0, max_value=100.0, value=10.0, key="new_scenario_profit_margin")
|
891 |
-
|
892 |
-
# حساب السعر الإجمالي
|
893 |
-
new_profit_amount = total_cost * (new_profit_margin / 100)
|
894 |
-
new_total_price = total_cost + new_profit_amount
|
895 |
-
|
896 |
-
st.number_input("السعر الإجمالي (ريال)", min_value=0.0, value=new_total_price, key="new_scenario_total_price", disabled=True)
|
897 |
-
new_is_active = st.checkbox("نشط", key="new_scenario_is_active")
|
898 |
-
|
899 |
-
submit_button = st.form_submit_button("إضافة سيناريو")
|
900 |
-
|
901 |
-
if submit_button:
|
902 |
-
if new_name:
|
903 |
-
# التحقق من حالة التنشيط
|
904 |
-
if new_is_active:
|
905 |
-
# إلغاء تنشيط جميع السيناريوهات الأخرى
|
906 |
-
for scenario in st.session_state.price_scenarios:
|
907 |
-
scenario['is_active'] = False
|
908 |
-
|
909 |
-
# إنشاء معرف جديد
|
910 |
-
new_id = max([item['id'] for item in st.session_state.price_scenarios], default=0) + 1
|
911 |
-
|
912 |
-
# إضافة السيناريو الجديد
|
913 |
-
st.session_state.price_scenarios.append({
|
914 |
-
'id': new_id,
|
915 |
-
'name': new_name,
|
916 |
-
'description': new_description,
|
917 |
-
'total_cost': total_cost,
|
918 |
-
'profit_margin': new_profit_margin,
|
919 |
-
'total_price': new_total_price,
|
920 |
-
'is_active': new_is_active
|
921 |
-
})
|
922 |
-
|
923 |
-
st.success(f"تمت إضافة السيناريو '{new_name}' بنجاح!")
|
924 |
-
st.rerun()
|
925 |
-
else:
|
926 |
-
st.error("يرجى إدخال اسم السيناريو.")
|
927 |
-
|
928 |
-
# تحليل السيناريوهات
|
929 |
-
st.markdown("#### تحليل السيناريوهات")
|
930 |
-
|
931 |
-
# مقارنة السيناريوهات
|
932 |
-
st.markdown("##### مقارنة السيناريوهات")
|
933 |
-
|
934 |
-
# إنشاء DataFrame للرسم البياني
|
935 |
-
scenarios_comparison_df = pd.DataFrame({
|
936 |
-
'السيناريو': [item['name'] for item in st.session_state.price_scenarios],
|
937 |
-
'التكلفة الإجمالية': [item['total_cost'] for item in st.session_state.price_scenarios],
|
938 |
-
'هامش الربح (%)': [item['profit_margin'] for item in st.session_state.price_scenarios],
|
939 |
-
'السعر الإجمالي': [item['total_price'] for item in st.session_state.price_scenarios],
|
940 |
-
'الحالة': ['نشط' if item['is_active'] else 'غير نشط' for item in st.session_state.price_scenarios]
|
941 |
-
})
|
942 |
-
|
943 |
-
# إنشاء رسم بياني شريطي مزدوج
|
944 |
-
fig = go.Figure()
|
945 |
-
|
946 |
-
# إضافة شريط للتكلفة الإجمالية
|
947 |
-
fig.add_trace(go.Bar(
|
948 |
-
x=scenarios_comparison_df['السيناريو'],
|
949 |
-
y=scenarios_comparison_df['التكلفة الإجمالية'],
|
950 |
-
name='التكلفة الإجمالية',
|
951 |
-
marker_color='indianred'
|
952 |
-
))
|
953 |
-
|
954 |
-
# إضافة شريط للسعر الإجمالي
|
955 |
-
fig.add_trace(go.Bar(
|
956 |
-
x=scenarios_comparison_df['السيناريو'],
|
957 |
-
y=scenarios_comparison_df['السعر الإجمالي'],
|
958 |
-
name='السعر الإجمالي',
|
959 |
-
marker_color='lightsalmon'
|
960 |
-
))
|
961 |
-
|
962 |
-
# إضافة خط لهامش الربح
|
963 |
-
fig.add_trace(go.Scatter(
|
964 |
-
x=scenarios_comparison_df['السيناريو'],
|
965 |
-
y=scenarios_comparison_df['هامش الربح (%)'] * 10000, # تكبير القيم لتظهر على الرسم البياني
|
966 |
-
name='هامش الربح (%)',
|
967 |
-
yaxis='y2',
|
968 |
-
line=dict(color='royalblue', width=4)
|
969 |
-
))
|
970 |
-
|
971 |
-
# تعديل تخطيط الرسم البياني
|
972 |
-
fig.update_layout(
|
973 |
-
title='مقارنة سيناريوهات التسعير',
|
974 |
-
xaxis_title='السيناريو',
|
975 |
-
yaxis_title='المبلغ (ريال)',
|
976 |
-
yaxis2=dict(
|
977 |
-
title='هامش الربح (%)',
|
978 |
-
titlefont=dict(color='royalblue'),
|
979 |
-
tickfont=dict(color='royalblue'),
|
980 |
-
overlaying='y',
|
981 |
-
side='right',
|
982 |
-
range=[0, 20]
|
983 |
-
),
|
984 |
-
barmode='group',
|
985 |
-
legend=dict(
|
986 |
-
x=0,
|
987 |
-
y=1.2,
|
988 |
-
orientation='h'
|
989 |
)
|
990 |
-
)
|
991 |
-
|
992 |
-
# تعديل النص على الأشرطة
|
993 |
-
fig.update_traces(
|
994 |
-
texttemplate='%{y:,.0f}',
|
995 |
-
textposition='outside'
|
996 |
-
)
|
997 |
-
|
998 |
-
st.plotly_chart(fig, use_container_width=True)
|
999 |
-
|
1000 |
-
# تحليل تأثير هامش الربح
|
1001 |
-
st.markdown("##### تحليل تأثير هامش الربح")
|
1002 |
-
|
1003 |
-
# إنشاء نطاق من هوامش الربح
|
1004 |
-
profit_margins = list(range(0, 21, 2)) # من 0% إلى 20% بزيادة 2%
|
1005 |
-
|
1006 |
-
# حساب السعر الإجمالي لكل هامش ربح
|
1007 |
-
total_cost = st.session_state.price_scenarios[0]['total_cost'] # استخدام التكلفة الإجمالية من السيناريو الأول
|
1008 |
-
total_prices = [total_cost * (1 + margin / 100) for margin in profit_margins]
|
1009 |
-
|
1010 |
-
# إنشاء DataFrame للرسم البياني
|
1011 |
-
profit_analysis_df = pd.DataFrame({
|
1012 |
-
'هامش الربح (%)': profit_margins,
|
1013 |
-
'السعر الإجمالي': total_prices
|
1014 |
-
})
|
1015 |
-
|
1016 |
-
# إنشاء رسم بياني خطي
|
1017 |
-
fig = px.line(
|
1018 |
-
profit_analysis_df,
|
1019 |
-
x='هامش الربح (%)',
|
1020 |
-
y='السعر الإجمالي',
|
1021 |
-
title='تأثير هامش الربح على السعر الإجمالي',
|
1022 |
-
markers=True
|
1023 |
-
)
|
1024 |
-
|
1025 |
-
# تعديل النص على النقاط
|
1026 |
-
fig.update_traces(
|
1027 |
-
texttemplate='%{y:,.0f}',
|
1028 |
-
textposition='top center'
|
1029 |
-
)
|
1030 |
-
|
1031 |
-
st.plotly_chart(fig, use_container_width=True)
|
1032 |
-
|
1033 |
-
# تحليل نقطة التعادل
|
1034 |
-
st.markdown("##### تحليل نقطة التعادل")
|
1035 |
-
|
1036 |
-
# افتراض تكاليف ثابتة ومتغيرة
|
1037 |
-
fixed_costs = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف غير مباشرة')
|
1038 |
-
variable_costs = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف مباشرة')
|
1039 |
-
|
1040 |
-
# افتراض سعر البيع من السيناريو النشط
|
1041 |
-
active_scenario = next((item for item in st.session_state.price_scenarios if item['is_active']), None)
|
1042 |
-
if active_scenario:
|
1043 |
-
selling_price = active_scenario['total_price']
|
1044 |
-
else:
|
1045 |
-
selling_price = st.session_state.price_scenarios[0]['total_price']
|
1046 |
-
|
1047 |
-
# حساب نقطة التعادل
|
1048 |
-
if selling_price > variable_costs:
|
1049 |
-
breakeven_point = fixed_costs / (selling_price - variable_costs)
|
1050 |
-
st.metric("نقطة التعادل", f"{breakeven_point:.2f} وحدة")
|
1051 |
-
|
1052 |
-
# إنشاء رسم بياني لنقطة التعادل
|
1053 |
-
units = list(range(0, int(breakeven_point * 2) + 1, max(1, int(breakeven_point / 10))))
|
1054 |
-
|
1055 |
-
total_costs = [fixed_costs + variable_costs * unit for unit in units]
|
1056 |
-
total_revenues = [selling_price * unit for unit in units]
|
1057 |
-
profits = [revenue - cost for revenue, cost in zip(total_revenues, total_costs)]
|
1058 |
-
|
1059 |
-
breakeven_df = pd.DataFrame({
|
1060 |
-
'الوحدات': units,
|
1061 |
-
'إجمالي التكاليف': total_costs,
|
1062 |
-
'إجمالي الإيرادات': total_revenues,
|
1063 |
-
'الربح': profits
|
1064 |
-
})
|
1065 |
-
|
1066 |
-
fig = go.Figure()
|
1067 |
-
|
1068 |
-
fig.add_trace(go.Scatter(
|
1069 |
-
x=breakeven_df['الوحدات'],
|
1070 |
-
y=breakeven_df['إجمالي التكاليف'],
|
1071 |
-
name='إجمالي التكاليف',
|
1072 |
-
line=dict(color='red', width=2)
|
1073 |
-
))
|
1074 |
-
|
1075 |
-
fig.add_trace(go.Scatter(
|
1076 |
-
x=breakeven_df['الوحدات'],
|
1077 |
-
y=breakeven_df['إجمالي الإيرادات'],
|
1078 |
-
name='إجمالي الإيرادات',
|
1079 |
-
line=dict(color='green', width=2)
|
1080 |
-
))
|
1081 |
-
|
1082 |
-
fig.add_trace(go.Scatter(
|
1083 |
-
x=breakeven_df['الوحدات'],
|
1084 |
-
y=breakeven_df['الربح'],
|
1085 |
-
name='الربح',
|
1086 |
-
line=dict(color='blue', width=2)
|
1087 |
-
))
|
1088 |
-
|
1089 |
-
# إضافة خط عمودي عند نقطة التعادل
|
1090 |
-
fig.add_vline(
|
1091 |
-
x=breakeven_point,
|
1092 |
-
line_dash="dash",
|
1093 |
-
line_color="black",
|
1094 |
-
annotation_text=f"نقطة التعادل: {breakeven_point:.2f}",
|
1095 |
-
annotation_position="top right"
|
1096 |
-
)
|
1097 |
-
|
1098 |
-
# إضافة خط أفقي عند الصفر
|
1099 |
-
fig.add_hline(
|
1100 |
-
y=0,
|
1101 |
-
line_dash="dash",
|
1102 |
-
line_color="gray"
|
1103 |
-
)
|
1104 |
-
|
1105 |
-
fig.update_layout(
|
1106 |
-
title='تحليل نقطة التعادل',
|
1107 |
-
xaxis_title='الوحدات',
|
1108 |
-
yaxis_title='المبلغ (ريال)'
|
1109 |
-
)
|
1110 |
-
|
1111 |
-
st.plotly_chart(fig, use_container_width=True)
|
1112 |
-
else:
|
1113 |
-
st.warning("لا يمكن حساب نقطة التعادل لأن سعر البيع أقل من التكاليف المتغيرة.")
|
1114 |
-
|
1115 |
-
def _render_competitive_analysis_tab(self):
|
1116 |
-
"""عرض تبويب المقارنة التنافسية"""
|
1117 |
-
|
1118 |
-
st.markdown("### المقارنة التنافسية")
|
1119 |
-
|
1120 |
-
# بيانات افتراضية للمنافسين
|
1121 |
-
competitors_data = [
|
1122 |
-
{
|
1123 |
-
'name': 'شركتنا',
|
1124 |
-
'price': 670000,
|
1125 |
-
'quality': 4.5,
|
1126 |
-
'delivery_time': 180,
|
1127 |
-
'experience': 8,
|
1128 |
-
'local_content': 85
|
1129 |
-
},
|
1130 |
-
{
|
1131 |
-
'name': 'المنافس أ',
|
1132 |
-
'price': 700000,
|
1133 |
-
'quality': 4.2,
|
1134 |
-
'delivery_time': 200,
|
1135 |
-
'experience': 10,
|
1136 |
-
'local_content': 75
|
1137 |
-
},
|
1138 |
-
{
|
1139 |
-
'name': 'المنافس ب',
|
1140 |
-
'price': 650000,
|
1141 |
-
'quality': 3.8,
|
1142 |
-
'delivery_time': 160,
|
1143 |
-
'experience': 5,
|
1144 |
-
'local_content': 90
|
1145 |
-
},
|
1146 |
-
{
|
1147 |
-
'name': 'المنافس ج',
|
1148 |
-
'price': 680000,
|
1149 |
-
'quality': 4.0,
|
1150 |
-
'delivery_time': 190,
|
1151 |
-
'experience': 12,
|
1152 |
-
'local_content': 80
|
1153 |
-
}
|
1154 |
-
]
|
1155 |
-
|
1156 |
-
# عرض بيانات المنافسين
|
1157 |
-
st.markdown("#### بيانات المنافسين")
|
1158 |
-
|
1159 |
-
competitors_df = pd.DataFrame(competitors_data)
|
1160 |
-
st.dataframe(competitors_df, use_container_width=True, hide_index=True)
|
1161 |
-
|
1162 |
-
# مقارنة الأسعار
|
1163 |
-
st.markdown("#### مقارنة الأسعار")
|
1164 |
-
|
1165 |
-
fig = px.bar(
|
1166 |
-
competitors_df,
|
1167 |
-
x='name',
|
1168 |
-
y='price',
|
1169 |
-
title='مقارنة الأسعار بين المنافسين',
|
1170 |
-
color='price',
|
1171 |
-
text_auto='.2s'
|
1172 |
-
)
|
1173 |
-
|
1174 |
-
fig.update_layout(
|
1175 |
-
xaxis_title='المنافس',
|
1176 |
-
yaxis_title='السعر (ريال)'
|
1177 |
-
)
|
1178 |
-
|
1179 |
-
st.plotly_chart(fig, use_container_width=True)
|
1180 |
-
|
1181 |
-
# مقارنة متعددة الأبعاد
|
1182 |
-
st.markdown("#### مقارنة متعددة الأبعاد")
|
1183 |
-
|
1184 |
-
# تحويل البيانات إلى تنسيق مناسب للرسم البياني الراداري
|
1185 |
-
categories = ['price', 'quality', 'delivery_time', 'experience', 'local_content']
|
1186 |
-
|
1187 |
-
# تطبيع البيانات (لجعل القيم بين 0 و 1)
|
1188 |
-
normalized_data = {}
|
1189 |
-
|
1190 |
-
for category in categories:
|
1191 |
-
if category == 'price' or category == 'delivery_time':
|
1192 |
-
# للسعر ووقت التسليم، القيمة الأقل أفضل
|
1193 |
-
min_val = min(item[category] for item in competitors_data)
|
1194 |
-
max_val = max(item[category] for item in competitors_data)
|
1195 |
-
normalized_data[category] = [(max_val - item[category]) / (max_val - min_val) if max_val != min_val else 0.5 for item in competitors_data]
|
1196 |
-
else:
|
1197 |
-
# للجودة والخبرة والمحتوى المحلي، القيمة الأعلى أفضل
|
1198 |
-
min_val = min(item[category] for item in competitors_data)
|
1199 |
-
max_val = max(item[category] for item in competitors_data)
|
1200 |
-
normalized_data[category] = [(item[category] - min_val) / (max_val - min_val) if max_val != min_val else 0.5 for item in competitors_data]
|
1201 |
-
|
1202 |
-
# إنشاء الرسم البياني الراداري
|
1203 |
-
fig = go.Figure()
|
1204 |
-
|
1205 |
-
for i, competitor in enumerate(competitors_data):
|
1206 |
-
fig.add_trace(go.Scatterpolar(
|
1207 |
-
r=[normalized_data[category][i] for category in categories],
|
1208 |
-
theta=['السعر', 'الجودة', 'وقت التسليم', 'الخبرة', 'المحتوى المحلي'],
|
1209 |
-
fill='toself',
|
1210 |
-
name=competitor['name']
|
1211 |
-
))
|
1212 |
-
|
1213 |
-
fig.update_layout(
|
1214 |
-
polar=dict(
|
1215 |
-
radialaxis=dict(
|
1216 |
-
visible=True,
|
1217 |
-
range=[0, 1]
|
1218 |
-
)
|
1219 |
-
),
|
1220 |
-
title='مقارنة متعددة الأبعاد بين المنافسين',
|
1221 |
-
showlegend=True
|
1222 |
-
)
|
1223 |
-
|
1224 |
-
st.plotly_chart(fig, use_container_width=True)
|
1225 |
-
|
1226 |
-
# تحليل نقاط القوة والضعف
|
1227 |
-
st.markdown("#### تحليل نقاط القوة والضعف")
|
1228 |
-
|
1229 |
-
# تحديد نقاط القوة والضعف لشركتنا
|
1230 |
-
our_company = competitors_data[0]
|
1231 |
-
|
1232 |
-
strengths = []
|
1233 |
-
weaknesses = []
|
1234 |
-
|
1235 |
-
# مقارنة السعر
|
1236 |
-
other_prices = [comp['price'] for comp in competitors_data[1:]]
|
1237 |
-
if our_company['price'] <= min(other_prices):
|
1238 |
-
strengths.append("السعر تنافسي جداً")
|
1239 |
-
elif our_company['price'] <= sum(other_prices) / len(other_prices):
|
1240 |
-
strengths.append("السعر تنافسي")
|
1241 |
-
else:
|
1242 |
-
weaknesses.append("السعر أعلى من المتوسط")
|
1243 |
-
|
1244 |
-
# مقارنة الجودة
|
1245 |
-
other_qualities = [comp['quality'] for comp in competitors_data[1:]]
|
1246 |
-
if our_company['quality'] >= max(other_qualities):
|
1247 |
-
strengths.append("الجودة ممتازة")
|
1248 |
-
elif our_company['quality'] >= sum(other_qualities) / len(other_qualities):
|
1249 |
-
strengths.append("الجودة جيدة")
|
1250 |
-
else:
|
1251 |
-
weaknesses.append("الجودة أقل من المتوسط")
|
1252 |
-
|
1253 |
-
# مقارنة وقت التسليم
|
1254 |
-
other_delivery_times = [comp['delivery_time'] for comp in competitors_data[1:]]
|
1255 |
-
if our_company['delivery_time'] <= min(other_delivery_times):
|
1256 |
-
strengths.append("وقت التسليم سريع جداً")
|
1257 |
-
elif our_company['delivery_time'] <= sum(other_delivery_times) / len(other_delivery_times):
|
1258 |
-
strengths.append("وقت التسليم جيد")
|
1259 |
-
else:
|
1260 |
-
weaknesses.append("وقت التسليم أطول من المتوسط")
|
1261 |
-
|
1262 |
-
# مقارنة الخبرة
|
1263 |
-
other_experiences = [comp['experience'] for comp in competitors_data[1:]]
|
1264 |
-
if our_company['experience'] >= max(other_experiences):
|
1265 |
-
strengths.append("خبرة واسعة جداً")
|
1266 |
-
elif our_company['experience'] >= sum(other_experiences) / len(other_experiences):
|
1267 |
-
strengths.append("خبرة جيدة")
|
1268 |
-
else:
|
1269 |
-
weaknesses.append("خبرة أقل من المتوسط")
|
1270 |
-
|
1271 |
-
# مقارنة المحتوى المحلي
|
1272 |
-
other_local_contents = [comp['local_content'] for comp in competitors_data[1:]]
|
1273 |
-
if our_company['local_content'] >= max(other_local_contents):
|
1274 |
-
strengths.append("محتوى محلي ممتاز")
|
1275 |
-
elif our_company['local_content'] >= sum(other_local_contents) / len(other_local_contents):
|
1276 |
-
strengths.append("محتوى محلي جيد")
|
1277 |
-
else:
|
1278 |
-
weaknesses.append("محتوى محلي أقل من المتوسط")
|
1279 |
-
|
1280 |
-
# عرض نقاط القوة والضعف
|
1281 |
-
col1, col2 = st.columns(2)
|
1282 |
-
|
1283 |
-
with col1:
|
1284 |
-
st.markdown("##### نقاط القوة")
|
1285 |
-
for strength in strengths:
|
1286 |
-
st.markdown(f"- {strength}")
|
1287 |
-
|
1288 |
-
with col2:
|
1289 |
-
st.markdown("##### نقاط الضعف")
|
1290 |
-
for weakness in weaknesses:
|
1291 |
-
st.markdown(f"- {weakness}")
|
1292 |
-
|
1293 |
-
# توصيات للتسعير
|
1294 |
-
st.markdown("#### توصيات للتسعير")
|
1295 |
-
|
1296 |
-
# تحديد التوصيات بناءً على المقارنة
|
1297 |
-
recommendations = []
|
1298 |
-
|
1299 |
-
# توصية بناءً على السعر
|
1300 |
-
avg_price = sum(comp['price'] for comp in competitors_data) / len(competitors_data)
|
1301 |
-
if our_company['price'] > avg_price:
|
1302 |
-
recommendations.append("النظر في تخفيض السعر للمنافسة بشكل أفضل.")
|
1303 |
-
|
1304 |
-
# توصية بناءً على الجودة
|
1305 |
-
if our_company['quality'] > sum(comp['quality'] for comp in competitors_data[1:]) / len(competitors_data[1:]):
|
1306 |
-
recommendations.append("التأكيد على جودة الخدمات في العروض التسويقية.")
|
1307 |
-
|
1308 |
-
# توصية بناءً على وقت التسليم
|
1309 |
-
if our_company['delivery_time'] < sum(comp['delivery_time'] for comp in competitors_data[1:]) / len(competitors_data[1:]):
|
1310 |
-
recommendations.append("التأكيد على سرعة التسليم كميزة تنافسية.")
|
1311 |
-
|
1312 |
-
# توصية بناءً على الخبرة
|
1313 |
-
if our_company['experience'] < max(comp['experience'] for comp in competitors_data[1:]):
|
1314 |
-
recommendations.append("تعزيز فريق العمل بخبرات إضافية.")
|
1315 |
-
|
1316 |
-
# توصية بناءً على المحتوى المحلي
|
1317 |
-
if our_company['local_content'] > sum(comp['local_content'] for comp in competitors_data[1:]) / len(competitors_data[1:]):
|
1318 |
-
recommendations.append("التأكيد على نسبة المحتوى المحلي العالية في العروض.")
|
1319 |
-
|
1320 |
-
# توصية عامة
|
1321 |
-
recommendations.append("مراجعة هيكل التكاليف بشكل دوري للحفاظ على القدرة التنافسية.")
|
1322 |
-
|
1323 |
-
# عرض التوصيات
|
1324 |
-
for recommendation in recommendations:
|
1325 |
-
st.markdown(f"- {recommendation}")
|
1326 |
-
|
1327 |
-
def _render_reports_tab(self):
|
1328 |
-
"""عرض تبويب التقارير"""
|
1329 |
-
|
1330 |
-
st.markdown("### التقارير")
|
1331 |
-
|
1332 |
-
# قائمة التقارير المتاحة
|
1333 |
-
reports = [
|
1334 |
-
"تقرير جدول الكميات",
|
1335 |
-
"تقرير تحليل التكاليف",
|
1336 |
-
"تقرير سيناريوهات التسعير",
|
1337 |
-
"تقرير المقارنة التنافسية",
|
1338 |
-
"تقرير ملخص التسعير"
|
1339 |
-
]
|
1340 |
-
|
1341 |
-
# اختيار التقرير
|
1342 |
-
selected_report = st.selectbox("اختر التقرير", reports)
|
1343 |
-
|
1344 |
-
# خيارات التصدير
|
1345 |
-
export_format = st.radio("صيغة التصدير", ["PDF", "Excel", "Word"])
|
1346 |
-
|
1347 |
-
# زر إنشاء التقرير
|
1348 |
-
if st.button("إنشاء التقرير"):
|
1349 |
-
st.success(f"تم إنشاء {selected_report} بصيغة {export_format} بنجاح!")
|
1350 |
-
|
1351 |
-
# عرض نموذج للتقرير
|
1352 |
-
st.markdown("#### نموذج التقرير")
|
1353 |
-
|
1354 |
-
if selected_report == "تقرير جدول الكميات":
|
1355 |
-
self._render_boq_report()
|
1356 |
-
elif selected_report == "تقرير تحليل التكاليف":
|
1357 |
-
self._render_cost_analysis_report()
|
1358 |
-
elif selected_report == "تقرير سيناريوهات التسعير":
|
1359 |
-
self._render_pricing_scenarios_report()
|
1360 |
-
elif selected_report == "تقرير المقارنة التنافسية":
|
1361 |
-
self._render_competitive_analysis_report()
|
1362 |
-
elif selected_report == "تقرير ملخص التسعير":
|
1363 |
-
self._render_pricing_summary_report()
|
1364 |
-
|
1365 |
-
def _render_boq_report(self):
|
1366 |
-
"""عرض نموذج تقرير جدول الكميات"""
|
1367 |
-
|
1368 |
-
st.markdown("### تقرير جدول الكميات")
|
1369 |
-
st.markdown("**تاريخ التقرير:** " + time.strftime("%Y-%m-%d"))
|
1370 |
-
st.markdown("**اسم المشروع:** مشروع إنشاء مبنى إداري")
|
1371 |
-
st.markdown("**رقم المناقصة:** T-2024-001")
|
1372 |
-
|
1373 |
-
st.markdown("#### جدول الكميات")
|
1374 |
-
|
1375 |
-
# عرض جدول الكميات
|
1376 |
-
boq_df = pd.DataFrame(st.session_state.bill_of_quantities)
|
1377 |
-
st.dataframe(boq_df, use_container_width=True, hide_index=True)
|
1378 |
-
|
1379 |
-
# عرض إجمالي جدول الكميات
|
1380 |
-
total_boq = sum(item['total_price'] for item in st.session_state.bill_of_quantities)
|
1381 |
-
st.metric("إجمالي جدول الكميات", f"{total_boq:,.2f} ريال")
|
1382 |
-
|
1383 |
-
# عرض توزيع البنود حسب الفئة
|
1384 |
-
st.markdown("#### توزيع البنود حسب الفئة")
|
1385 |
-
|
1386 |
-
# تجميع البيانات حسب الفئة
|
1387 |
-
category_totals = {}
|
1388 |
-
|
1389 |
-
for item in st.session_state.bill_of_quantities:
|
1390 |
-
category = item['category']
|
1391 |
-
if category in category_totals:
|
1392 |
-
category_totals[category] += item['total_price']
|
1393 |
-
else:
|
1394 |
-
category_totals[category] = item['total_price']
|
1395 |
-
|
1396 |
-
category_df = pd.DataFrame({
|
1397 |
-
'الفئة': list(category_totals.keys()),
|
1398 |
-
'المبلغ': list(category_totals.values())
|
1399 |
-
})
|
1400 |
-
|
1401 |
-
fig = px.pie(
|
1402 |
-
category_df,
|
1403 |
-
values='المبلغ',
|
1404 |
-
names='الفئة',
|
1405 |
-
title='توزيع جدول الكميات حسب الفئة'
|
1406 |
-
)
|
1407 |
-
|
1408 |
-
st.plotly_chart(fig, use_container_width=True)
|
1409 |
-
|
1410 |
-
def _render_cost_analysis_report(self):
|
1411 |
-
"""عرض نموذج تقرير تحليل التكاليف"""
|
1412 |
-
|
1413 |
-
st.markdown("### تقرير تحليل التكاليف")
|
1414 |
-
st.markdown("**تاريخ التقرير:** " + time.strftime("%Y-%m-%d"))
|
1415 |
-
st.markdown("**اسم المشروع:** مشروع إنشاء مبنى إداري")
|
1416 |
-
st.markdown("**رقم المناقصة:** T-2024-001")
|
1417 |
-
|
1418 |
-
st.markdown("#### تحليل التكاليف")
|
1419 |
-
|
1420 |
-
# عرض تحليل التكاليف
|
1421 |
-
cost_df = pd.DataFrame(st.session_state.cost_analysis)
|
1422 |
-
st.dataframe(cost_df, use_container_width=True, hide_index=True)
|
1423 |
-
|
1424 |
-
# عرض إجمالي التكاليف
|
1425 |
-
total_cost = sum(item['amount'] for item in st.session_state.cost_analysis)
|
1426 |
-
st.metric("إجمالي التكاليف", f"{total_cost:,.2f} ريال")
|
1427 |
-
|
1428 |
-
# عرض توزيع التكاليف حسب الفئة
|
1429 |
-
st.markdown("#### توزيع التكاليف حسب الفئة")
|
1430 |
-
|
1431 |
-
# تجميع البيانات حسب الفئة
|
1432 |
-
category_totals = {}
|
1433 |
-
|
1434 |
-
for item in st.session_state.cost_analysis:
|
1435 |
-
category = item['category']
|
1436 |
-
if category in category_totals:
|
1437 |
-
category_totals[category] += item['amount']
|
1438 |
-
else:
|
1439 |
-
category_totals[category] = item['amount']
|
1440 |
-
|
1441 |
-
category_df = pd.DataFrame({
|
1442 |
-
'الفئة': list(category_totals.keys()),
|
1443 |
-
'المبلغ': list(category_totals.values())
|
1444 |
-
})
|
1445 |
-
|
1446 |
-
fig = px.pie(
|
1447 |
-
category_df,
|
1448 |
-
values='المبلغ',
|
1449 |
-
names='الفئة',
|
1450 |
-
title='توزيع التكاليف حسب الفئة'
|
1451 |
-
)
|
1452 |
-
|
1453 |
-
st.plotly_chart(fig, use_container_width=True)
|
1454 |
-
|
1455 |
-
# عرض توزيع التكاليف المباشرة
|
1456 |
-
st.markdown("#### توزيع التكاليف المباشرة")
|
1457 |
-
|
1458 |
-
# تجميع البيانات حسب الفئة الفرعية للتكاليف المباشرة
|
1459 |
-
direct_subcategory_totals = {}
|
1460 |
-
|
1461 |
-
for item in st.session_state.cost_analysis:
|
1462 |
-
if item['category'] == 'تكاليف مباشرة':
|
1463 |
-
subcategory = item['subcategory']
|
1464 |
-
if subcategory in direct_subcategory_totals:
|
1465 |
-
direct_subcategory_totals[subcategory] += item['amount']
|
1466 |
-
else:
|
1467 |
-
direct_subcategory_totals[subcategory] = item['amount']
|
1468 |
-
|
1469 |
-
direct_subcategory_df = pd.DataFrame({
|
1470 |
-
'الفئة الفرعية': list(direct_subcategory_totals.keys()),
|
1471 |
-
'المبلغ': list(direct_subcategory_totals.values())
|
1472 |
-
})
|
1473 |
-
|
1474 |
-
fig = px.bar(
|
1475 |
-
direct_subcategory_df,
|
1476 |
-
x='الفئة الفرعية',
|
1477 |
-
y='المبلغ',
|
1478 |
-
title='توزيع التكاليف المباشرة',
|
1479 |
-
color='الفئة الفرعية',
|
1480 |
-
text_auto='.2s'
|
1481 |
-
)
|
1482 |
-
|
1483 |
-
st.plotly_chart(fig, use_container_width=True)
|
1484 |
-
|
1485 |
-
def _render_pricing_scenarios_report(self):
|
1486 |
-
"""عرض نموذج تقرير سيناريوهات التسعير"""
|
1487 |
-
|
1488 |
-
st.markdown("### تقرير سيناريوهات التسعير")
|
1489 |
-
st.markdown("**تاريخ التقرير:** " + time.strftime("%Y-%m-%d"))
|
1490 |
-
st.markdown("**اسم المشروع:** مشروع إنشاء مبنى إداري")
|
1491 |
-
st.markdown("**رقم المناقصة:** T-2024-001")
|
1492 |
-
|
1493 |
-
st.markdown("#### سيناريوهات التسعير")
|
1494 |
-
|
1495 |
-
# عرض سيناريوهات التسعير
|
1496 |
-
scenarios_df = pd.DataFrame(st.session_state.price_scenarios)
|
1497 |
-
st.dataframe(scenarios_df, use_container_width=True, hide_index=True)
|
1498 |
-
|
1499 |
-
# عرض السيناريو النشط
|
1500 |
-
active_scenario = next((item for item in st.session_state.price_scenarios if item['is_active']), None)
|
1501 |
-
if active_scenario:
|
1502 |
-
st.markdown(f"**السيناريو النشط:** {active_scenario['name']}")
|
1503 |
-
st.markdown(f"**السعر الإجمالي:** {active_scenario['total_price']:,.2f} ريال")
|
1504 |
-
st.markdown(f"**هامش الربح:** {active_scenario['profit_margin']:.1f}%")
|
1505 |
-
|
1506 |
-
# عرض مقارنة السيناريوهات
|
1507 |
-
st.markdown("#### مقارنة السيناريوهات")
|
1508 |
-
|
1509 |
-
# إنشاء DataFrame للرسم البياني
|
1510 |
-
scenarios_comparison_df = pd.DataFrame({
|
1511 |
-
'السيناريو': [item['name'] for item in st.session_state.price_scenarios],
|
1512 |
-
'التكلفة الإجمالية': [item['total_cost'] for item in st.session_state.price_scenarios],
|
1513 |
-
'هامش الربح (%)': [item['profit_margin'] for item in st.session_state.price_scenarios],
|
1514 |
-
'السعر الإجمالي': [item['total_price'] for item in st.session_state.price_scenarios],
|
1515 |
-
'الحالة': ['نشط' if item['is_active'] else 'غير نشط' for item in st.session_state.price_scenarios]
|
1516 |
-
})
|
1517 |
-
|
1518 |
-
# إنشاء رسم بياني شريطي مزدوج
|
1519 |
-
fig = go.Figure()
|
1520 |
-
|
1521 |
-
# إضافة شريط للتكلفة الإجمالية
|
1522 |
-
fig.add_trace(go.Bar(
|
1523 |
-
x=scenarios_comparison_df['السيناريو'],
|
1524 |
-
y=scenarios_comparison_df['التكلفة الإجمالية'],
|
1525 |
-
name='التكلفة الإجمالية',
|
1526 |
-
marker_color='indianred'
|
1527 |
-
))
|
1528 |
-
|
1529 |
-
# إضافة شريط للسعر الإجمالي
|
1530 |
-
fig.add_trace(go.Bar(
|
1531 |
-
x=scenarios_comparison_df['السيناريو'],
|
1532 |
-
y=scenarios_comparison_df['السعر الإجمالي'],
|
1533 |
-
name='السعر الإجمالي',
|
1534 |
-
marker_color='lightsalmon'
|
1535 |
-
))
|
1536 |
-
|
1537 |
-
# إضافة خط لهامش الربح
|
1538 |
-
fig.add_trace(go.Scatter(
|
1539 |
-
x=scenarios_comparison_df['السيناريو'],
|
1540 |
-
y=scenarios_comparison_df['هامش الربح (%)'] * 10000, # تكبير القيم لتظهر على الرسم البياني
|
1541 |
-
name='هامش الربح (%)',
|
1542 |
-
yaxis='y2',
|
1543 |
-
line=dict(color='royalblue', width=4)
|
1544 |
-
))
|
1545 |
-
|
1546 |
-
# تعديل تخطيط الرسم البياني
|
1547 |
-
fig.update_layout(
|
1548 |
-
title='مقارنة سيناريوهات التسعير',
|
1549 |
-
xaxis_title='السيناريو',
|
1550 |
-
yaxis_title='المبلغ (ريال)',
|
1551 |
-
yaxis2=dict(
|
1552 |
-
title='هامش الربح (%)',
|
1553 |
-
titlefont=dict(color='royalblue'),
|
1554 |
-
tickfont=dict(color='royalblue'),
|
1555 |
-
overlaying='y',
|
1556 |
-
side='right',
|
1557 |
-
range=[0, 20]
|
1558 |
-
),
|
1559 |
-
barmode='group',
|
1560 |
-
legend=dict(
|
1561 |
-
x=0,
|
1562 |
-
y=1.2,
|
1563 |
-
orientation='h'
|
1564 |
-
)
|
1565 |
-
)
|
1566 |
-
|
1567 |
-
# تعديل النص على الأشرطة
|
1568 |
-
fig.update_traces(
|
1569 |
-
texttemplate='%{y:,.0f}',
|
1570 |
-
textposition='outside'
|
1571 |
-
)
|
1572 |
-
|
1573 |
-
st.plotly_chart(fig, use_container_width=True)
|
1574 |
-
|
1575 |
-
def _render_competitive_analysis_report(self):
|
1576 |
-
"""عرض نموذج تقرير المقارنة التنافسية"""
|
1577 |
-
|
1578 |
-
st.markdown("### تقرير المقارنة التنافسية")
|
1579 |
-
st.markdown("**تاريخ التقرير:** " + time.strftime("%Y-%m-%d"))
|
1580 |
-
st.markdown("**اسم المشروع:** مشروع إنشاء مبنى إداري")
|
1581 |
-
st.markdown("**رقم المناقصة:** T-2024-001")
|
1582 |
-
|
1583 |
-
# بيانات افتراضية للمنافسين
|
1584 |
-
competitors_data = [
|
1585 |
-
{
|
1586 |
-
'name': 'شركتنا',
|
1587 |
-
'price': 670000,
|
1588 |
-
'quality': 4.5,
|
1589 |
-
'delivery_time': 180,
|
1590 |
-
'experience': 8,
|
1591 |
-
'local_content': 85
|
1592 |
-
},
|
1593 |
-
{
|
1594 |
-
'name': 'المنافس أ',
|
1595 |
-
'price': 700000,
|
1596 |
-
'quality': 4.2,
|
1597 |
-
'delivery_time': 200,
|
1598 |
-
'experience': 10,
|
1599 |
-
'local_content': 75
|
1600 |
-
},
|
1601 |
-
{
|
1602 |
-
'name': 'المنافس ب',
|
1603 |
-
'price': 650000,
|
1604 |
-
'quality': 3.8,
|
1605 |
-
'delivery_time': 160,
|
1606 |
-
'experience': 5,
|
1607 |
-
'local_content': 90
|
1608 |
-
},
|
1609 |
-
{
|
1610 |
-
'name': 'المنافس ج',
|
1611 |
-
'price': 680000,
|
1612 |
-
'quality': 4.0,
|
1613 |
-
'delivery_time': 190,
|
1614 |
-
'experience': 12,
|
1615 |
-
'local_content': 80
|
1616 |
-
}
|
1617 |
-
]
|
1618 |
-
|
1619 |
-
# عرض بيانات المنافسين
|
1620 |
-
st.markdown("#### بيانات المنافسين")
|
1621 |
-
|
1622 |
-
competitors_df = pd.DataFrame(competitors_data)
|
1623 |
-
st.dataframe(competitors_df, use_container_width=True, hide_index=True)
|
1624 |
-
|
1625 |
-
# مقارنة الأسعار
|
1626 |
-
st.markdown("#### مقارنة الأسعار")
|
1627 |
-
|
1628 |
-
fig = px.bar(
|
1629 |
-
competitors_df,
|
1630 |
-
x='name',
|
1631 |
-
y='price',
|
1632 |
-
title='مقارنة الأسعار بين المنافسين',
|
1633 |
-
color='price',
|
1634 |
-
text_auto='.2s'
|
1635 |
-
)
|
1636 |
-
|
1637 |
-
fig.update_layout(
|
1638 |
-
xaxis_title='المنافس',
|
1639 |
-
yaxis_title='السعر (ريال)'
|
1640 |
-
)
|
1641 |
-
|
1642 |
-
st.plotly_chart(fig, use_container_width=True)
|
1643 |
-
|
1644 |
-
# مقارنة متعددة الأبعاد
|
1645 |
-
st.markdown("#### مقارنة متعددة الأبعاد")
|
1646 |
-
|
1647 |
-
# تحويل البيانات إلى تنسيق مناسب للرسم البياني الراداري
|
1648 |
-
categories = ['price', 'quality', 'delivery_time', 'experience', 'local_content']
|
1649 |
-
|
1650 |
-
# تطبيع البيانات (لجعل القيم بين 0 و 1)
|
1651 |
-
normalized_data = {}
|
1652 |
-
|
1653 |
-
for category in categories:
|
1654 |
-
if category == 'price' or category == 'delivery_time':
|
1655 |
-
# للسعر ووقت التسليم، القيمة الأقل أفضل
|
1656 |
-
min_val = min(item[category] for item in competitors_data)
|
1657 |
-
max_val = max(item[category] for item in competitors_data)
|
1658 |
-
normalized_data[category] = [(max_val - item[category]) / (max_val - min_val) if max_val != min_val else 0.5 for item in competitors_data]
|
1659 |
-
else:
|
1660 |
-
# للجودة والخبرة والمحتوى المحلي، القيمة الأعلى أفضل
|
1661 |
-
min_val = min(item[category] for item in competitors_data)
|
1662 |
-
max_val = max(item[category] for item in competitors_data)
|
1663 |
-
normalized_data[category] = [(item[category] - min_val) / (max_val - min_val) if max_val != min_val else 0.5 for item in competitors_data]
|
1664 |
-
|
1665 |
-
# إنشاء الرسم البياني الراداري
|
1666 |
-
fig = go.Figure()
|
1667 |
-
|
1668 |
-
for i, competitor in enumerate(competitors_data):
|
1669 |
-
fig.add_trace(go.Scatterpolar(
|
1670 |
-
r=[normalized_data[category][i] for category in categories],
|
1671 |
-
theta=['السعر', 'الجودة', 'وقت التسليم', 'الخبرة', 'المحتوى المحلي'],
|
1672 |
-
fill='toself',
|
1673 |
-
name=competitor['name']
|
1674 |
-
))
|
1675 |
-
|
1676 |
-
fig.update_layout(
|
1677 |
-
polar=dict(
|
1678 |
-
radialaxis=dict(
|
1679 |
-
visible=True,
|
1680 |
-
range=[0, 1]
|
1681 |
-
)
|
1682 |
-
),
|
1683 |
-
title='مقارنة متعددة الأبعاد بين المنافسين',
|
1684 |
-
showlegend=True
|
1685 |
-
)
|
1686 |
-
|
1687 |
-
st.plotly_chart(fig, use_container_width=True)
|
1688 |
-
|
1689 |
-
def _render_pricing_summary_report(self):
|
1690 |
-
"""عرض نموذج تقرير ملخص التسعير"""
|
1691 |
-
|
1692 |
-
st.markdown("### تقرير ملخص التسعير")
|
1693 |
-
st.markdown("**تاريخ التقرير:** " + time.strftime("%Y-%m-%d"))
|
1694 |
-
st.markdown("**اسم المشروع:** مشروع إنشاء مبنى إداري")
|
1695 |
-
st.markdown("**رقم المناقصة:** T-2024-001")
|
1696 |
-
|
1697 |
-
# عرض ملخص التسعير
|
1698 |
-
st.markdown("#### ملخص التسعير")
|
1699 |
-
|
1700 |
-
# حساب إجمالي التكاليف
|
1701 |
-
total_direct_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف مباشرة')
|
1702 |
-
total_indirect_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف غير مباشرة')
|
1703 |
-
total_profit = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'أرباح')
|
1704 |
-
total_cost = total_direct_cost + total_indirect_cost
|
1705 |
-
total_price = total_cost + total_profit
|
1706 |
-
|
1707 |
-
# إنشاء جدول ملخص
|
1708 |
-
summary_data = {
|
1709 |
-
'البند': ['التكاليف المباشرة', 'التكاليف غير المباشرة', 'إجمالي التكاليف', 'هامش الربح', 'السعر الإجمالي'],
|
1710 |
-
'المبلغ (ريال)': [total_direct_cost, total_indirect_cost, total_cost, total_profit, total_price],
|
1711 |
-
'النسبة (%)': [
|
1712 |
-
(total_direct_cost / total_price) * 100,
|
1713 |
-
(total_indirect_cost / total_price) * 100,
|
1714 |
-
(total_cost / total_price) * 100,
|
1715 |
-
(total_profit / total_price) * 100,
|
1716 |
-
100.0
|
1717 |
-
]
|
1718 |
-
}
|
1719 |
-
|
1720 |
-
summary_df = pd.DataFrame(summary_data)
|
1721 |
-
st.dataframe(summary_df, use_container_width=True, hide_index=True)
|
1722 |
-
|
1723 |
-
# عرض توزيع التكاليف
|
1724 |
-
st.markdown("#### توزيع التكاليف")
|
1725 |
-
|
1726 |
-
# إنشاء DataFrame للرسم البياني
|
1727 |
-
cost_distribution_df = pd.DataFrame({
|
1728 |
-
'البند': ['التكاليف المباشرة', 'التكاليف غير المباشرة', 'هامش الربح'],
|
1729 |
-
'المبلغ': [total_direct_cost, total_indirect_cost, total_profit]
|
1730 |
-
})
|
1731 |
-
|
1732 |
-
fig = px.pie(
|
1733 |
-
cost_distribution_df,
|
1734 |
-
values='المبلغ',
|
1735 |
-
names='البند',
|
1736 |
-
title='توزيع التكاليف والأرباح',
|
1737 |
-
color_discrete_sequence=px.colors.qualitative.Set3
|
1738 |
-
)
|
1739 |
-
|
1740 |
-
st.plotly_chart(fig, use_container_width=True)
|
1741 |
-
|
1742 |
-
# عرض ملخص السيناريو النشط
|
1743 |
-
st.markdown("#### السيناريو النشط")
|
1744 |
-
|
1745 |
-
active_scenario = next((item for item in st.session_state.price_scenarios if item['is_active']), None)
|
1746 |
-
if active_scenario:
|
1747 |
-
st.markdown(f"**اسم السيناريو:** {active_scenario['name']}")
|
1748 |
-
st.markdown(f"**الوصف:** {active_scenario['description']}")
|
1749 |
-
st.markdown(f"**إجمالي التكلفة:** {active_scenario['total_cost']:,.2f} ريال")
|
1750 |
-
st.markdown(f"**هامش الربح:** {active_scenario['profit_margin']:.1f}%")
|
1751 |
-
st.markdown(f"**السعر الإجمالي:** {active_scenario['total_price']:,.2f} ريال")
|
1752 |
-
|
1753 |
-
# عرض توصيات التسعير
|
1754 |
-
st.markdown("#### توصيات التسعير")
|
1755 |
|
1756 |
-
st.
|
1757 |
-
|
1758 |
-
|
1759 |
-
st.markdown("- مراقبة أسعار المنافسين وتعديل الاستراتيجية التسعيرية عند الحاجة.")
|
1760 |
-
st.markdown("- تحليل نقاط القوة والضعف بشكل مستمر لتحسين العروض المستقبلية.")
|
|
|
57 |
},
|
58 |
{
|
59 |
'id': 4,
|
60 |
+
'code': 'A-004',
|
61 |
+
'description': 'توريد وصب خرسانة مسلحة للأعمدة',
|
62 |
+
'unit': 'م3',
|
63 |
+
'quantity': 120,
|
64 |
+
'unit_price': 500,
|
65 |
+
'total_price': 60000,
|
66 |
+
'category': 'أعمال خرسانية'
|
67 |
+
},
|
68 |
+
{
|
69 |
+
'id': 5,
|
70 |
+
'code': 'A-005',
|
71 |
'description': 'توريد وتركيب حديد تسليح',
|
72 |
'unit': 'طن',
|
73 |
+
'quantity': 45,
|
74 |
+
'unit_price': 3000,
|
75 |
+
'total_price': 135000,
|
76 |
'category': 'أعمال حديد'
|
77 |
},
|
78 |
{
|
79 |
+
'id': 6,
|
80 |
+
'code': 'A-006',
|
81 |
'description': 'توريد وبناء طابوق',
|
82 |
'unit': 'م2',
|
83 |
+
'quantity': 1200,
|
84 |
+
'unit_price': 45,
|
85 |
'total_price': 54000,
|
86 |
'category': 'أعمال بناء'
|
87 |
+
},
|
88 |
+
{
|
89 |
+
'id': 7,
|
90 |
+
'code': 'A-007',
|
91 |
+
'description': 'أعمال اللياسة والتشطيبات',
|
92 |
+
'unit': 'م2',
|
93 |
+
'quantity': 2400,
|
94 |
+
'unit_price': 35,
|
95 |
+
'total_price': 84000,
|
96 |
+
'category': 'أعمال تشطيبات'
|
97 |
+
},
|
98 |
+
{
|
99 |
+
'id': 8,
|
100 |
+
'code': 'A-008',
|
101 |
+
'description': 'أعمال الدهانات',
|
102 |
+
'unit': 'م2',
|
103 |
+
'quantity': 2400,
|
104 |
+
'unit_price': 25,
|
105 |
+
'total_price': 60000,
|
106 |
+
'category': 'أعمال تشطيبات'
|
107 |
+
},
|
108 |
+
{
|
109 |
+
'id': 9,
|
110 |
+
'code': 'A-009',
|
111 |
+
'description': 'توريد وتركيب أبواب خشبية',
|
112 |
+
'unit': 'عدد',
|
113 |
+
'quantity': 24,
|
114 |
+
'unit_price': 750,
|
115 |
+
'total_price': 18000,
|
116 |
+
'category': 'أعمال نجارة'
|
117 |
+
},
|
118 |
+
{
|
119 |
+
'id': 10,
|
120 |
+
'code': 'A-010',
|
121 |
+
'description': 'توريد وتركيب نوافذ ألمنيوم',
|
122 |
+
'unit': 'م2',
|
123 |
+
'quantity': 120,
|
124 |
+
'unit_price': 350,
|
125 |
+
'total_price': 42000,
|
126 |
+
'category': 'أعمال ألمنيوم'
|
127 |
}
|
128 |
]
|
129 |
|
|
|
134 |
'category': 'تكاليف مباشرة',
|
135 |
'subcategory': 'مواد',
|
136 |
'description': 'خرسانة',
|
137 |
+
'amount': 120000,
|
138 |
+
'percentage': 17.9
|
139 |
},
|
140 |
{
|
141 |
'id': 2,
|
142 |
'category': 'تكاليف مباشرة',
|
143 |
'subcategory': 'مواد',
|
144 |
'description': 'حديد تسليح',
|
145 |
+
'amount': 135000,
|
146 |
+
'percentage': 20.1
|
147 |
},
|
148 |
{
|
149 |
'id': 3,
|
|
|
242 |
}
|
243 |
]
|
244 |
|
245 |
+
def run(self):
|
246 |
+
"""تشغيل وحدة التسعير"""
|
247 |
+
# استدعاء دالة العرض
|
248 |
+
self.render()
|
249 |
+
|
250 |
def render(self):
|
251 |
"""عرض واجهة وحدة التسعير"""
|
252 |
|
|
|
356 |
'المبلغ': list(direct_cost_subcategories.values())
|
357 |
})
|
358 |
|
359 |
+
# إنشاء رسم بياني دائري
|
360 |
+
fig = px.pie(
|
361 |
direct_cost_df,
|
362 |
+
values='المبلغ',
|
363 |
+
names='الفئة الفرعية',
|
364 |
title='توزيع التكاليف المباشرة',
|
365 |
+
color_discrete_sequence=px.colors.qualitative.Pastel
|
|
|
366 |
)
|
367 |
|
368 |
st.plotly_chart(fig, use_container_width=True)
|
369 |
|
370 |
+
# عرض توزيع التكاليف غير المباشرة
|
371 |
+
st.markdown("### توزيع التكاليف غير المباشرة")
|
372 |
|
373 |
+
# تجميع البيانات حسب الفئة الفرعية للتكاليف غير المباشرة
|
374 |
+
indirect_cost_subcategories = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
|
376 |
+
for item in st.session_state.cost_analysis:
|
377 |
+
if item['category'] == 'تكاليف غير مباشرة':
|
378 |
+
subcategory = item['subcategory']
|
379 |
+
if subcategory in indirect_cost_subcategories:
|
380 |
+
indirect_cost_subcategories[subcategory] += item['amount']
|
381 |
+
else:
|
382 |
+
indirect_cost_subcategories[subcategory] = item['amount']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
383 |
|
384 |
+
# إنشاء DataFrame للرسم البياني
|
385 |
+
indirect_cost_df = pd.DataFrame({
|
386 |
+
'الفئة الفرعية': list(indirect_cost_subcategories.keys()),
|
387 |
+
'المبلغ': list(indirect_cost_subcategories.values())
|
388 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
389 |
|
390 |
+
# إنشاء رسم بياني دائري
|
391 |
+
fig = px.pie(
|
392 |
+
indirect_cost_df,
|
393 |
+
values='المبلغ',
|
394 |
+
names='الفئة الفرعية',
|
395 |
+
title='توزيع التكاليف غير المباشرة',
|
396 |
+
color_discrete_sequence=px.colors.qualitative.Pastel1
|
397 |
)
|
398 |
|
399 |
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
|
401 |
def _render_bill_of_quantities_tab(self):
|
402 |
"""عرض تبويب جدول الكميات"""
|
403 |
|
404 |
st.markdown("### جدول الكميات")
|
405 |
|
406 |
+
# إنشاء DataFrame من بيانات جدول الكميات
|
|
|
|
|
|
|
407 |
boq_df = pd.DataFrame(st.session_state.bill_of_quantities)
|
408 |
|
409 |
+
# عرض جدول الكميات
|
410 |
+
st.dataframe(
|
411 |
+
boq_df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'category']],
|
412 |
column_config={
|
413 |
+
'code': 'الكود',
|
414 |
+
'description': 'الوصف',
|
415 |
+
'unit': 'الوحدة',
|
416 |
+
'quantity': 'الكمية',
|
417 |
+
'unit_price': st.column_config.NumberColumn('سعر الوحدة', format='%d ريال'),
|
418 |
+
'total_price': st.column_config.NumberColumn('السعر الإجمالي', format='%d ريال'),
|
419 |
+
'category': 'الفئة'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
420 |
},
|
|
|
421 |
hide_index=True,
|
422 |
+
use_container_width=True
|
423 |
)
|
424 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
425 |
# إضافة بند جديد
|
426 |
+
st.markdown("### إضافة بند جديد")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
427 |
|
428 |
col1, col2 = st.columns(2)
|
429 |
|
430 |
with col1:
|
431 |
+
new_code = st.text_input("الكود", key="new_boq_code")
|
432 |
+
new_description = st.text_input("الوصف", key="new_boq_description")
|
433 |
+
new_unit = st.selectbox("الوحدة", ["م3", "م2", "طن", "عدد", "متر طولي"], key="new_boq_unit")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
434 |
|
435 |
with col2:
|
436 |
+
new_quantity = st.number_input("الكمية", min_value=0.0, step=1.0, key="new_boq_quantity")
|
437 |
+
new_unit_price = st.number_input("سعر الوحدة", min_value=0.0, step=10.0, key="new_boq_unit_price")
|
438 |
+
new_category = st.selectbox(
|
439 |
+
"الفئة",
|
440 |
+
[
|
441 |
+
"أعمال ترابية",
|
442 |
+
"أعمال خرسانية",
|
443 |
+
"أعمال حديد",
|
444 |
+
"أعمال بناء",
|
445 |
+
"أعمال تشطيبات",
|
446 |
+
"أعمال نجارة",
|
447 |
+
"أعمال ألمنيوم",
|
448 |
+
"أعمال كهربائية",
|
449 |
+
"أعمال ميكانيكية",
|
450 |
+
"أعمال صحية"
|
451 |
+
],
|
452 |
+
key="new_boq_category"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
453 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
|
455 |
+
if st.button("إضافة البند", key="add_boq_item"):
|
456 |
+
if new_code and ne
|
457 |
+
(Content truncated due to size limit. Use line ranges to read in chunks)
|
|
|
|