EGYADMIN commited on
Commit
9ec1864
·
verified ·
1 Parent(s): eecd5c0

Update modules/pricing/pricing_app.py

Browse files
Files changed (1) hide show
  1. modules/pricing/pricing_app.py +730 -871
modules/pricing/pricing_app.py CHANGED
@@ -27,9 +27,10 @@ class PricingApp:
27
 
28
  if 'item_analysis_edited' not in st.session_state:
29
  st.session_state.item_analysis_edited = False
30
-
31
- if 'new_item_created' not in st.session_state:
32
- st.session_state.new_item_created = False
 
33
 
34
  def _add_custom_css(self):
35
  """إضافة CSS مخصص للتطبيق"""
@@ -92,62 +93,6 @@ class PricingApp:
92
  padding: 10px;
93
  margin: 10px 0;
94
  }
95
- .main-total {
96
- font-size: 22px;
97
- font-weight: bold;
98
- color: #1a73e8;
99
- background-color: #f0f7ff;
100
- padding: 15px;
101
- border-radius: 8px;
102
- margin-top: 25px;
103
- text-align: center;
104
- }
105
- .card {
106
- border: 1px solid #ddd;
107
- border-radius: 8px;
108
- padding: 15px;
109
- margin-bottom: 15px;
110
- background-color: #fff;
111
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
112
- }
113
- .card-header {
114
- border-bottom: 1px solid #eee;
115
- padding-bottom: 10px;
116
- margin-bottom: 10px;
117
- font-weight: bold;
118
- font-size: 16px;
119
- color: #333;
120
- }
121
- .summary-container {
122
- display: flex;
123
- justify-content: space-between;
124
- margin-top: 15px;
125
- }
126
- .summary-item {
127
- text-align: center;
128
- padding: 10px;
129
- border-radius: 5px;
130
- background-color: #f5f5f5;
131
- width: 23%;
132
- }
133
- .summary-value {
134
- font-size: 20px;
135
- font-weight: bold;
136
- color: #1a73e8;
137
- }
138
- .summary-label {
139
- font-size: 14px;
140
- color: #666;
141
- }
142
- .primary-button {
143
- background-color: #1a73e8;
144
- color: white;
145
- border: none;
146
- padding: 10px 15px;
147
- border-radius: 5px;
148
- font-weight: bold;
149
- cursor: pointer;
150
- }
151
  </style>
152
  """, unsafe_allow_html=True)
153
 
@@ -162,11 +107,6 @@ class PricingApp:
162
  # استدعاء دالة run لتنفيذ وظائف التطبيق
163
  self.run()
164
 
165
- def _render_bill_of_quantities_tab(self):
166
- """عرض تبويب جدول الكميات"""
167
- # استدعاء دالة _render_bill_of_quantities
168
- self._render_bill_of_quantities()
169
-
170
  def run(self):
171
  """تشغيل التطبيق"""
172
  st.title("وحدة التسعير المتكاملة")
@@ -174,11 +114,6 @@ class PricingApp:
174
  # عرض الشريط العلوي
175
  self._render_top_bar()
176
 
177
- # إذا تم إنشاء بند جديد، انتقل مباشرة إلى تحليل السعر
178
- if st.session_state.new_item_created:
179
- st.session_state.current_tab = "item_analysis"
180
- st.session_state.new_item_created = False
181
-
182
  # عرض المحتوى حسب التبويب المحدد
183
  if st.session_state.current_tab == "boq":
184
  self._render_bill_of_quantities()
@@ -208,17 +143,27 @@ class PricingApp:
208
 
209
  def _create_new_project(self):
210
  """إنشاء مشروع جديد"""
211
- project_id = f"proj_{len(st.session_state.projects) + 1}"
212
  new_project = {
213
- "id": project_id,
214
  "name": f"مشروع رقم {len(st.session_state.projects) + 1}",
215
  "client": "عميل قياسي",
216
  "date": datetime.now().strftime("%Y-%m-%d"),
217
  "budget": 1000000.00,
218
  "items": []
219
  }
220
- st.session_state.projects[project_id] = new_project
221
- st.session_state.current_project_id = project_id
 
 
 
 
 
 
 
 
 
 
222
  st.session_state.current_tab = "boq"
223
  st.rerun()
224
 
@@ -227,15 +172,26 @@ class PricingApp:
227
  st.header("جدول الكميات")
228
 
229
  # اختيار المشروع
230
- project_options = {proj["name"]: proj_id for proj_id, proj in st.session_state.projects.items()}
 
 
 
 
 
 
 
 
 
 
 
231
  selected_project_name = st.selectbox(
232
  "اختر المشروع",
233
  options=list(project_options.keys()),
234
- index=0 if st.session_state.current_project_id is None else list(project_options.keys()).index(st.session_state.projects[st.session_state.current_project_id]["name"])
235
  )
236
 
237
  st.session_state.current_project_id = project_options[selected_project_name]
238
- current_project = st.session_state.projects[st.session_state.current_project_id]
239
 
240
  # عرض معلومات المشروع
241
  col1, col2, col3 = st.columns(3)
@@ -272,123 +228,737 @@ class PricingApp:
272
  "subcontractors": []
273
  }
274
  current_project["items"].append(new_item)
275
- st.session_state.current_item_index = len(current_project["items"]) - 1
276
- st.session_state.new_item_created = True # فعّل الانتقال إلى تحليل السعر
277
- st.success("تم إضافة البند بنجاح")
278
  st.rerun()
279
 
280
  # عرض جدول الكميات
281
  if current_project["items"]:
282
- st.markdown("### قائمة البنود")
 
 
 
 
 
 
 
 
 
 
 
283
 
284
- # عرض كل بند كبطاقة منفصلة
285
- for i, item in enumerate(current_project["items"]):
286
- with st.container():
287
- st.markdown(f"""
288
- <div class="card">
289
- <div class="card-header">{i+1}. {item['name']}</div>
290
- <div>
291
- <strong>الوحدة:</strong> {item['unit']} |
292
- <strong>الكمية:</strong> {item['quantity']} |
293
- <strong>سعر الوحدة:</strong> {item['unit_price']:,.2f} ريال |
294
- <strong>السعر الإجمالي:</strong> {item['total_price']:,.2f} ريال
295
- </div>
296
- </div>
297
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
- col1, col2, col3 = st.columns([1, 1, 1])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  with col1:
301
- if st.button(f"تحليل البند", key=f"analyze_btn_{i}", use_container_width=True):
302
- st.session_state.current_item_index = i
303
- st.session_state.current_tab = "item_analysis"
304
- st.rerun()
305
  with col2:
306
- if st.button(f"تعديل البند", key=f"edit_btn_{i}", use_container_width=True):
307
- st.session_state[f"edit_item_{i}"] = True
308
- with col3:
309
- if st.button(f"حذف البند", key=f"delete_btn_{i}", use_container_width=True):
310
- st.session_state[f"delete_item_{i}"] = True
311
 
312
  # نموذج التعديل
313
- if st.session_state.get(f"edit_item_{i}", False):
314
- with st.form(f"edit_item_form_{i}"):
 
315
  col1, col2 = st.columns(2)
316
  with col1:
317
- item_name = st.text_input("اسم البند", value=item["name"])
318
- item_unit = st.text_input("الوحدة", value=item["unit"])
319
  with col2:
320
- item_quantity = st.number_input("الكمية", min_value=0.0, step=0.1, value=item["quantity"])
321
- item_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1, value=item["unit_price"])
322
 
323
  col1, col2 = st.columns(2)
324
  with col1:
325
  if st.form_submit_button("حفظ التعديلات"):
326
- item["name"] = item_name
327
- item["unit"] = item_unit
328
- item["quantity"] = float(item_quantity)
329
- item["unit_price"] = float(item_price)
330
- item["total_price"] = item["quantity"] * item["unit_price"]
331
- st.session_state[f"edit_item_{i}"] = False
 
332
  st.rerun()
333
  with col2:
334
  if st.form_submit_button("إلغاء"):
335
- st.session_state[f"edit_item_{i}"] = False
336
  st.rerun()
337
 
338
  # تأكيد الحذف
339
- if st.session_state.get(f"delete_item_{i}", False):
340
- st.warning(f"هل أنت متأكد من حذف البند: {item['name']}؟")
341
  col1, col2 = st.columns(2)
342
  with col1:
343
- if st.button("نعم، حذف", key=f"confirm_delete_item_{i}"):
344
- current_project["items"].pop(i)
345
- st.session_state[f"delete_item_{i}"] = False
 
346
  st.rerun()
347
  with col2:
348
- if st.button("إلغاء", key=f"cancel_delete_item_{i}"):
349
- st.session_state[f"delete_item_{i}"] = False
350
  st.rerun()
351
 
352
- # عرض المجموع الكلي
353
- total_price = sum(item["total_price"] for item in current_project["items"])
354
- st.markdown(f"""
355
- <div class="main-total">
356
- المجموع الكلي: {total_price:,.2f} ريال
357
- </div>
358
- """, unsafe_allow_html=True)
359
  else:
360
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود جديدة.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
- # إضافة زر لتحديث سعر البند تلقائياً بناءً على التحليل
363
- if st.session_state.get("current_item_index") is not None and len(current_project["items"]) > 0:
364
- current_item = current_project["items"][st.session_state.current_item_index]
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
- # حساب التكلفة المباشرة
367
- materials_total = sum(material.get("total_price", 0) for material in current_item["materials"])
368
- labor_total = sum(labor.get("total_price", 0) for labor in current_item["labor"])
369
- equipment_total = sum(equipment.get("total_price", 0) for equipment in current_item["equipment"])
370
- subcontractors_total = sum(sub.get("total_price", 0) for sub in current_item["subcontractors"])
371
- direct_cost = materials_total + labor_total + equipment_total + subcontractors_total
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
 
373
- if st.button("احتساب السعر من التكلفة المباشرة + ربح", type="primary", use_container_width=True):
374
- # إضافة نسبة ربح افتراضية (مثلاً 15٪)
375
- profit_percentage = 15
376
- # حساب السعر الجديد مع إضافة الربح
377
- if current_item["quantity"] > 0:
378
- profit_markup = direct_cost * profit_percentage / 100
379
- new_total_price = direct_cost + profit_markup
380
- new_unit_price = new_total_price / current_item["quantity"]
381
-
382
- # تحديث سعر البند
383
- current_item["unit_price"] = new_unit_price
384
- current_item["total_price"] = new_total_price
385
-
386
- # تحديث حالة التعديل
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  st.session_state.item_analysis_edited = True
388
- st.success(f"تم احتساب السعر بنجاح مع إضافة ربح {profit_percentage}%")
389
  st.rerun()
390
- else:
391
- st.warning("لا يمكن احتساب السعر لأن الكمية صفر.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
 
393
  def _load_sample_projects(self):
394
  """تحميل مشاريع نموذجية"""
@@ -397,8 +967,8 @@ class PricingApp:
397
  "id": "proj_1",
398
  "name": "مشروع إنشاء مبنى سكني",
399
  "client": "شركة الإعمار",
400
- "date": "2025-03-24",
401
- "budget": 1000000.00,
402
  "items": [
403
  {
404
  "id": "item_1",
@@ -534,717 +1104,6 @@ class PricingApp:
534
  ]
535
  }
536
  }
537
-
538
- def _render_item_price_analysis(self):
539
- """عرض تحليل سعر البند"""
540
- if st.session_state.current_project_id is None or "current_item_index" not in st.session_state:
541
- st.warning("الرجاء اختيار مشروع وبند أولاً")
542
- return
543
-
544
- current_project = st.session_state.projects[st.session_state.current_project_id]
545
- current_item = current_project["items"][st.session_state.current_item_index]
546
-
547
- st.header(f"تحليل سعر البند: {current_item['name']}")
548
-
549
- # معلومات البند
550
- col1, col2, col3, col4 = st.columns(4)
551
- with col1:
552
- st.write(f"**الوحدة:** {current_item['unit']}")
553
- with col2:
554
- st.write(f"**الكمية:** {current_item['quantity']}")
555
- with col3:
556
- st.write(f"**سعر الوحدة:** {current_item['unit_price']} ريال")
557
- with col4:
558
- st.write(f"**السعر الإجمالي:** {current_item['total_price']} ريال")
559
-
560
- # القيم الإجمالية
561
- materials_total = sum(material.get("total_price", 0) for material in current_item["materials"])
562
- labor_total = sum(labor.get("total_price", 0) for labor in current_item["labor"])
563
- equipment_total = sum(equipment.get("total_price", 0) for equipment in current_item["equipment"])
564
- subcontractors_total = sum(sub.get("total_price", 0) for sub in current_item["subcontractors"])
565
- direct_cost = materials_total + labor_total + equipment_total + subcontractors_total
566
- overhead_profit = max(0, current_item["total_price"] - direct_cost)
567
-
568
- # عرض ملخص البند
569
- st.markdown("""
570
- <div class="summary-container">
571
- <div class="summary-item">
572
- <div class="summary-value" id="materials-total"></div>
573
- <div class="summary-label">تكلفة المواد</div>
574
- </div>
575
- <div class="summary-item">
576
- <div class="summary-value" id="labor-total"></div>
577
- <div class="summary-label">تكلفة العمالة</div>
578
- </div>
579
- <div class="summary-item">
580
- <div class="summary-value" id="equipment-total"></div>
581
- <div class="summary-label">تكلفة المعدات</div>
582
- </div>
583
- <div class="summary-item">
584
- <div class="summary-value" id="subs-total"></div>
585
- <div class="summary-label">المقاولين من الباطن</div>
586
- </div>
587
- </div>
588
- """, unsafe_allow_html=True)
589
-
590
- # تحديث القيم باستخدام JavaScript
591
- st.markdown(f"""
592
- <script>
593
- document.getElementById('materials-total').innerText = '{materials_total:,.2f} ريال';
594
- document.getElementById('labor-total').innerText = '{labor_total:,.2f} ريال';
595
- document.getElementById('equipment-total').innerText = '{equipment_total:,.2f} ريال';
596
- document.getElementById('subs-total').innerText = '{subcontractors_total:,.2f} ريال';
597
- </script>
598
- """, unsafe_allow_html=True)
599
-
600
- # زر حفظ التغييرات
601
- if st.session_state.item_analysis_edited:
602
- if st.button("حفظ التغييرات وتحديث السعر", type="primary", use_container_width=True):
603
- # تحديث السعر الإجمالي للبند
604
- current_item["unit_price"] = direct_cost / current_item["quantity"] if current_item["quantity"] > 0 else 0
605
- current_item["total_price"] = current_item["unit_price"] * current_item["quantity"]
606
-
607
- st.session_state.item_analysis_edited = False
608
- st.success("تم حفظ التغييرات وتحديث السعر بنجاح")
609
- st.rerun()
610
-
611
- # تبويبات لعناصر التكلفة
612
- tab1, tab2, tab3, tab4 = st.tabs(["المواد", "العمالة", "المعدات", "المقاولين من الباطن"])
613
-
614
- # تحليل المواد
615
- with tab1:
616
- st.subheader("تحليل المواد")
617
-
618
- # إضافة مادة جديدة
619
- with st.expander("إضافة مادة جديدة", expanded=True):
620
- with st.form("add_material_form"):
621
- col1, col2 = st.columns(2)
622
- with col1:
623
- new_material_name = st.text_input("اسم المادة")
624
- new_material_unit = st.text_input("الوحدة")
625
- with col2:
626
- new_material_quantity = st.number_input("الكمية", min_value=0.0, step=0.1)
627
- new_material_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1)
628
-
629
- submitted = st.form_submit_button("إضافة المادة")
630
- if submitted and new_material_name and new_material_unit:
631
- new_material = {
632
- "id": f"material_{len(current_item['materials']) + 1}",
633
- "name": new_material_name,
634
- "unit": new_material_unit,
635
- "quantity": float(new_material_quantity),
636
- "unit_price": float(new_material_price),
637
- "total_price": float(new_material_quantity) * float(new_material_price)
638
- }
639
- current_item["materials"].append(new_material)
640
- st.session_state.item_analysis_edited = True
641
- st.success("تمت إضافة المادة بنجاح")
642
- st.rerun()
643
-
644
- # عرض جدول المواد
645
- if current_item["materials"]:
646
- # تحويل البيانات إلى DataFrame
647
- materials_data = []
648
- for i, material in enumerate(current_item["materials"]):
649
- materials_data.append({
650
- "م": i + 1,
651
- "اسم المادة": material["name"],
652
- "الوحدة": material["unit"],
653
- "الكمية": material["quantity"],
654
- "سعر الوحدة (ريال)": material["unit_price"],
655
- "السعر الإجمالي (ريال)": material["total_price"]
656
- })
657
-
658
- materials_df = pd.DataFrame(materials_data)
659
-
660
- # إضافة صف المجموع
661
- total_row = pd.DataFrame([{
662
- "م": "",
663
- "اسم المادة": "المجموع",
664
- "الوحدة": "",
665
- "الكمية": "",
666
- "سعر الوحدة (ريال)": "",
667
- "السعر الإجمالي (ريال)": materials_total
668
- }])
669
-
670
- # استخدام pd.concat بدلاً من append
671
- materials_df = pd.concat([materials_df, total_row], ignore_index=True)
672
-
673
- # عرض الجدول القابل للتعديل
674
- edited_materials_df = st.data_editor(
675
- materials_df,
676
- column_config={
677
- "م": st.column_config.NumberColumn("م", width="small"),
678
- "اسم المادة": st.column_config.TextColumn("اسم المادة"),
679
- "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
680
- "الكمية": st.column_config.NumberColumn("الكمية", format="%.2f", width="small"),
681
- "سعر الوحدة (ريال)": st.column_config.NumberColumn("سعر الوحدة (ريال)", format="%.2f"),
682
- "السعر الإجمالي (ريال)": st.column_config.NumberColumn("السعر الإجمالي (ريال)", format="%.2f"),
683
- },
684
- hide_index=True,
685
- key="materials_table",
686
- on_change=self._update_total_price,
687
- disabled=["م", "اسم المادة", "الوحدة", "السعر الإجمالي (ريال)"]
688
- )
689
-
690
- # تحديث البيانات بعد التعديل
691
- for i, material in enumerate(current_item["materials"]):
692
- if i < len(edited_materials_df) - 1: # تجاهل صف المجموع
693
- material["quantity"] = edited_materials_df.iloc[i]["الكمية"]
694
- material["unit_price"] = edited_materials_df.iloc[i]["سعر الوحدة (ريال)"]
695
- material["total_price"] = material["quantity"] * material["unit_price"]
696
-
697
- # معالجة أزرار التعديل والحذف
698
- for i in range(len(current_item["materials"])):
699
- col1, col2 = st.columns([1, 1])
700
- with col1:
701
- if st.button(f"تعديل المادة {i+1}", key=f"edit_material_btn_{i}"):
702
- st.session_state[f"edit_material_{i}"] = True
703
-
704
- with col2:
705
- if st.button(f"حذف المادة {i+1}", key=f"delete_material_btn_{i}"):
706
- st.session_state[f"delete_material_{i}"] = True
707
-
708
- # نموذج التعديل
709
- if st.session_state.get(f"edit_material_{i}", False):
710
- with st.form(f"edit_material_form_{i}"):
711
- material = current_item["materials"][i]
712
- col1, col2 = st.columns(2)
713
- with col1:
714
- material_name = st.text_input("اسم المادة", value=material["name"])
715
- material_unit = st.text_input("الوحدة", value=material["unit"])
716
- with col2:
717
- material_quantity = st.number_input("الكمية", min_value=0.0, step=0.1, value=material["quantity"])
718
- material_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1, value=material["unit_price"])
719
-
720
- col1, col2 = st.columns(2)
721
- with col1:
722
- if st.form_submit_button("حفظ التعديلات"):
723
- material["name"] = material_name
724
- material["unit"] = material_unit
725
- material["quantity"] = float(material_quantity)
726
- material["unit_price"] = float(material_price)
727
- material["total_price"] = material["quantity"] * material["unit_price"]
728
- st.session_state.item_analysis_edited = True
729
- st.session_state[f"edit_material_{i}"] = False
730
- st.rerun()
731
- with col2:
732
- if st.form_submit_button("إلغاء"):
733
- st.session_state[f"edit_material_{i}"] = False
734
- st.rerun()
735
-
736
- # تأكيد الحذف
737
- if st.session_state.get(f"delete_material_{i}", False):
738
- st.warning(f"هل أنت متأكد من حذف المادة: {current_item['materials'][i]['name']}؟")
739
- col1, col2 = st.columns(2)
740
- with col1:
741
- if st.button("نعم، حذف", key=f"confirm_delete_material_{i}"):
742
- current_item["materials"].pop(i)
743
- st.session_state.item_analysis_edited = True
744
- st.session_state[f"delete_material_{i}"] = False
745
- st.rerun()
746
- with col2:
747
- if st.button("إلغاء", key=f"cancel_delete_material_{i}"):
748
- st.session_state[f"delete_material_{i}"] = False
749
- st.rerun()
750
-
751
- # عرض المجموع الكلي للمواد
752
- st.markdown(f"<div class='highlight-total'>إجمالي تكلفة المواد: <span class='total-value'>{materials_total:,.2f}</span> ريال</div>", unsafe_allow_html=True)
753
- else:
754
- st.info("لا توجد مواد مضافة لهذا البند")
755
-
756
- # تحليل العمالة
757
- with tab2:
758
- st.subheader("تحليل العمالة")
759
-
760
- # إضافة عمالة جديدة
761
- with st.expander("إضافة عمالة جديدة", expanded=True):
762
- with st.form("add_labor_form"):
763
- col1, col2 = st.columns(2)
764
- with col1:
765
- new_labor_name = st.text_input("نوع العمالة")
766
- new_labor_unit = st.text_input("الوحدة", value="يوم")
767
- with col2:
768
- new_labor_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5)
769
- new_labor_price = st.number_input("الأجر اليومي (ريال)", min_value=0.0, step=10.0)
770
-
771
- submitted = st.form_submit_button("إضافة العمالة")
772
- if submitted and new_labor_name:
773
- new_labor = {
774
- "id": f"labor_{len(current_item['labor']) + 1}",
775
- "name": new_labor_name,
776
- "unit": new_labor_unit,
777
- "quantity": float(new_labor_quantity),
778
- "unit_price": float(new_labor_price),
779
- "total_price": float(new_labor_quantity) * float(new_labor_price)
780
- }
781
- current_item["labor"].append(new_labor)
782
- st.session_state.item_analysis_edited = True
783
- st.success("تمت إضافة العمالة بنجاح")
784
- st.rerun()
785
-
786
- # عرض جدول العمالة
787
- if current_item["labor"]:
788
- # تحويل البيانات إلى DataFrame
789
- labor_data = []
790
- for i, labor in enumerate(current_item["labor"]):
791
- labor_data.append({
792
- "م": i + 1,
793
- "نوع العمالة": labor["name"],
794
- "الوحدة": labor["unit"],
795
- "عدد الأيام": labor["quantity"],
796
- "الأجر اليومي (ريال)": labor["unit_price"],
797
- "الإجمالي (ريال)": labor["total_price"]
798
- })
799
-
800
- labor_df = pd.DataFrame(labor_data)
801
-
802
- # إضافة صف المجموع
803
- total_row = pd.DataFrame([{
804
- "م": "",
805
- "نوع العمالة": "المجموع",
806
- "الوحدة": "",
807
- "عدد الأيام": "",
808
- "الأجر اليومي (ريال)": "",
809
- "الإجمالي (ريال)": labor_total
810
- }])
811
-
812
- # استخدام pd.concat بدلاً من append
813
- labor_df = pd.concat([labor_df, total_row], ignore_index=True)
814
-
815
- # عرض الجدول القابل للتعديل
816
- edited_labor_df = st.data_editor(
817
- labor_df,
818
- column_config={
819
- "م": st.column_config.NumberColumn("م", width="small"),
820
- "نوع العمالة": st.column_config.TextColumn("نوع العمالة"),
821
- "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
822
- "عدد الأيام": st.column_config.NumberColumn("عدد الأيام", format="%.1f", width="small"),
823
- "الأجر اليومي (ريال)": st.column_config.NumberColumn("الأجر اليومي (ريال)", format="%.2f"),
824
- "الإجمالي (ريال)": st.column_config.NumberColumn("الإجمالي (ريال)", format="%.2f")
825
- },
826
- hide_index=True,
827
- key="labor_table",
828
- on_change=self._update_total_price,
829
- disabled=["م", "نوع العمالة", "الوحدة", "الإجمالي (ريال)"]
830
- )
831
-
832
- # تحديث البيانات بعد التعديل
833
- for i, labor in enumerate(current_item["labor"]):
834
- if i < len(edited_labor_df) - 1: # تجاهل صف المجموع
835
- labor["quantity"] = edited_labor_df.iloc[i]["عدد الأيام"]
836
- labor["unit_price"] = edited_labor_df.iloc[i]["الأجر اليومي (ريال)"]
837
- labor["total_price"] = labor["quantity"] * labor["unit_price"]
838
-
839
- # معالجة أزرار التعديل والحذف
840
- for i in range(len(current_item["labor"])):
841
- col1, col2 = st.columns([1, 1])
842
- with col1:
843
- if st.button(f"تعديل العمالة {i+1}", key=f"edit_labor_btn_{i}"):
844
- st.session_state[f"edit_labor_{i}"] = True
845
-
846
- with col2:
847
- if st.button(f"حذف العمالة {i+1}", key=f"delete_labor_btn_{i}"):
848
- st.session_state[f"delete_labor_{i}"] = True
849
-
850
- # نموذج التعديل
851
- if st.session_state.get(f"edit_labor_{i}", False):
852
- with st.form(f"edit_labor_form_{i}"):
853
- labor = current_item["labor"][i]
854
- col1, col2 = st.columns(2)
855
- with col1:
856
- labor_name = st.text_input("نوع العمالة", value=labor["name"])
857
- labor_unit = st.text_input("الوحدة", value=labor["unit"])
858
- with col2:
859
- labor_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, value=labor["quantity"])
860
- labor_price = st.number_input("الأجر اليومي (ريال)", min_value=0.0, step=10.0, value=labor["unit_price"])
861
-
862
- col1, col2 = st.columns(2)
863
- with col1:
864
- if st.form_submit_button("حفظ التعديلات"):
865
- labor["name"] = labor_name
866
- labor["unit"] = labor_unit
867
- labor["quantity"] = float(labor_quantity)
868
- labor["unit_price"] = float(labor_price)
869
- labor["total_price"] = labor["quantity"] * labor["unit_price"]
870
- st.session_state.item_analysis_edited = True
871
- st.session_state[f"edit_labor_{i}"] = False
872
- st.rerun()
873
- with col2:
874
- if st.form_submit_button("إلغاء"):
875
- st.session_state[f"edit_labor_{i}"] = False
876
- st.rerun()
877
-
878
- # تأكيد الحذف
879
- if st.session_state.get(f"delete_labor_{i}", False):
880
- st.warning(f"هل أنت متأكد من حذف العمالة: {current_item['labor'][i]['name']}؟")
881
- col1, col2 = st.columns(2)
882
- with col1:
883
- if st.button("نعم، حذف", key=f"confirm_delete_labor_{i}"):
884
- current_item["labor"].pop(i)
885
- st.session_state.item_analysis_edited = True
886
- st.session_state[f"delete_labor_{i}"] = False
887
- st.rerun()
888
- with col2:
889
- if st.button("إلغاء", key=f"cancel_delete_labor_{i}"):
890
- st.session_state[f"delete_labor_{i}"] = False
891
- st.rerun()
892
-
893
- # عرض المجموع الكلي للعمالة
894
- st.markdown(f"<div class='highlight-total'>إجمالي تكلفة العمالة: <span class='total-value'>{labor_total:,.2f}</span> ريال</div>", unsafe_allow_html=True)
895
- else:
896
- st.info("لا توجد عمالة مضافة لهذا البند")
897
-
898
- # تحليل المعدات
899
- with tab3:
900
- st.subheader("تحليل المعدات")
901
-
902
- # إضافة معدات جديدة
903
- with st.expander("إضافة معدات جديدة", expanded=True):
904
- with st.form("add_equipment_form"):
905
- col1, col2 = st.columns(2)
906
- with col1:
907
- new_equipment_name = st.text_input("نوع المعدات")
908
- new_equipment_unit = st.text_input("الوحدة", value="يوم")
909
- with col2:
910
- new_equipment_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, key="new_equipment_quantity")
911
- new_equipment_price = st.number_input("التكلفة اليومية (ريال)", min_value=0.0, step=10.0, key="new_equipment_price")
912
-
913
- submitted = st.form_submit_button("إضافة المعدات")
914
- if submitted and new_equipment_name:
915
- new_equipment = {
916
- "id": f"equipment_{len(current_item['equipment']) + 1}",
917
- "name": new_equipment_name,
918
- "unit": new_equipment_unit,
919
- "quantity": float(new_equipment_quantity),
920
- "unit_price": float(new_equipment_price),
921
- "total_price": float(new_equipment_quantity) * float(new_equipment_price)
922
- }
923
- current_item["equipment"].append(new_equipment)
924
- st.session_state.item_analysis_edited = True
925
- st.success("تمت إضافة المعدات بنجاح")
926
- st.rerun()
927
-
928
- # عرض جدول المعدات
929
- if current_item["equipment"]:
930
- # تحويل البيانات إلى DataFrame
931
- equipment_data = []
932
- for i, equipment in enumerate(current_item["equipment"]):
933
- equipment_data.append({
934
- "م": i + 1,
935
- "نوع المعدات": equipment["name"],
936
- "الوحدة": equipment["unit"],
937
- "عدد الأيام": equipment["quantity"],
938
- "التكلفة اليومية (ريال)": equipment["unit_price"],
939
- "الإجمالي (ريال)": equipment["total_price"]
940
- })
941
-
942
- equipment_df = pd.DataFrame(equipment_data)
943
-
944
- # إضافة صف المجموع
945
- total_row = pd.DataFrame([{
946
- "م": "",
947
- "نوع المعدات": "المجموع",
948
- "الوحدة": "",
949
- "عدد الأيام": "",
950
- "التكلفة اليومية (ريال)": "",
951
- "الإجمالي (ريال)": equipment_total
952
- }])
953
-
954
- # استخدام pd.concat بدلاً من append
955
- equipment_df = pd.concat([equipment_df, total_row], ignore_index=True)
956
-
957
- # عرض الجدول القابل للتعديل
958
- edited_equipment_df = st.data_editor(
959
- equipment_df,
960
- column_config={
961
- "م": st.column_config.NumberColumn("م", width="small"),
962
- "نوع المعدات": st.column_config.TextColumn("نوع المعدات"),
963
- "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
964
- "عدد الأيام": st.column_config.NumberColumn("عدد الأيام", format="%.1f", width="small"),
965
- "التكلفة اليومية (ريال)": st.column_config.NumberColumn("التكلفة اليومية (ريال)", format="%.2f"),
966
- "الإجمالي (ريال)": st.column_config.NumberColumn("الإجمالي (ريال)", format="%.2f")
967
- },
968
- hide_index=True,
969
- key="equipment_table",
970
- on_change=self._update_total_price,
971
- disabled=["م", "نوع المعدات", "الوحدة", "الإجمالي (ريال)"]
972
- )
973
-
974
- # تحديث البيانات بعد التعديل
975
- for i, equipment in enumerate(current_item["equipment"]):
976
- if i < len(edited_equipment_df) - 1: # تجاهل صف المجموع
977
- equipment["quantity"] = edited_equipment_df.iloc[i]["عدد الأيام"]
978
- equipment["unit_price"] = edited_equipment_df.iloc[i]["التكلفة اليومية (ريال)"]
979
- equipment["total_price"] = equipment["quantity"] * equipment["unit_price"]
980
-
981
- # معالجة أزرار التعديل والحذف
982
- for i in range(len(current_item["equipment"])):
983
- col1, col2 = st.columns([1, 1])
984
- with col1:
985
- if st.button(f"تعديل المعدات {i+1}", key=f"edit_equipment_btn_{i}"):
986
- st.session_state[f"edit_equipment_{i}"] = True
987
-
988
- with col2:
989
- if st.button(f"حذف المعدات {i+1}", key=f"delete_equipment_btn_{i}"):
990
- st.session_state[f"delete_equipment_{i}"] = True
991
-
992
- # نموذج التعديل
993
- if st.session_state.get(f"edit_equipment_{i}", False):
994
- with st.form(f"edit_equipment_form_{i}"):
995
- equipment = current_item["equipment"][i]
996
- col1, col2 = st.columns(2)
997
- with col1:
998
- equipment_name = st.text_input("نوع المعدات", value=equipment["name"])
999
- equipment_unit = st.text_input("الوحدة", value=equipment["unit"])
1000
- with col2:
1001
- equipment_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, value=equipment["quantity"])
1002
- equipment_price = st.number_input("التكلفة اليومية (ريال)", min_value=0.0, step=10.0, value=equipment["unit_price"])
1003
-
1004
- col1, col2 = st.columns(2)
1005
- with col1:
1006
- if st.form_submit_button("حفظ التعديلات"):
1007
- equipment["name"] = equipment_name
1008
- equipment["unit"] = equipment_unit
1009
- equipment["quantity"] = float(equipment_quantity)
1010
- equipment["unit_price"] = float(equipment_price)
1011
- equipment["total_price"] = equipment["quantity"] * equipment["unit_price"]
1012
- st.session_state.item_analysis_edited = True
1013
- st.session_state[f"edit_equipment_{i}"] = False
1014
- st.rerun()
1015
- with col2:
1016
- if st.form_submit_button("إلغاء"):
1017
- st.session_state[f"edit_equipment_{i}"] = False
1018
- st.rerun()
1019
-
1020
- # تأكيد الحذف
1021
- if st.session_state.get(f"delete_equipment_{i}", False):
1022
- st.warning(f"هل أنت متأكد من حذف المعدات: {current_item['equipment'][i]['name']}؟")
1023
- col1, col2 = st.columns(2)
1024
- with col1:
1025
- if st.button("نعم، حذف", key=f"confirm_delete_equipment_{i}"):
1026
- current_item["equipment"].pop(i)
1027
- st.session_state.item_analysis_edited = True
1028
- st.session_state[f"delete_equipment_{i}"] = False
1029
- st.rerun()
1030
- with col2:
1031
- if st.button("إلغاء", key=f"cancel_delete_equipment_{i}"):
1032
- st.session_state[f"delete_equipment_{i}"] = False
1033
- st.rerun()
1034
-
1035
- # عرض المجموع الكلي للمعدات
1036
- st.markdown(f"<div class='highlight-total'>إجمالي تكلفة المعدات: <span class='total-value'>{equipment_total:,.2f}</span> ريال</div>", unsafe_allow_html=True)
1037
- else:
1038
- st.info("لا توجد معدات مضافة لهذا البند")
1039
-
1040
- # تحليل المقاولين من الباطن
1041
- with tab4:
1042
- st.subheader("تحليل المقاولين من الباطن")
1043
-
1044
- # إضافة مقاول جديد
1045
- with st.expander("إضافة مقاول من الباطن", expanded=True):
1046
- with st.form("add_subcontractor_form"):
1047
- col1, col2 = st.columns(2)
1048
- with col1:
1049
- new_sub_name = st.text_input("اسم المقاول")
1050
- new_sub_work = st.text_input("نوع العمل")
1051
- with col2:
1052
- new_sub_price = st.number_input("التكلفة الإجمالية (ريال)", min_value=0.0, step=100.0)
1053
-
1054
- submitted = st.form_submit_button("إضافة مقاول")
1055
- if submitted and new_sub_name and new_sub_work:
1056
- new_sub = {
1057
- "id": f"sub_{len(current_item['subcontractors']) + 1}",
1058
- "name": new_sub_name,
1059
- "work": new_sub_work,
1060
- "quantity": 1,
1061
- "unit_price": float(new_sub_price),
1062
- "total_price": float(new_sub_price)
1063
- }
1064
- current_item["subcontractors"].append(new_sub)
1065
- st.session_state.item_analysis_edited = True
1066
- st.success("تمت إضافة المقاول بنجاح")
1067
- st.rerun()
1068
-
1069
- # عرض جدول المقاولين
1070
- if current_item["subcontractors"]:
1071
- # تحويل البيانات إلى DataFrame
1072
- subs_data = []
1073
- for i, sub in enumerate(current_item["subcontractors"]):
1074
- subs_data.append({
1075
- "م": i + 1,
1076
- "اسم المقاول": sub["name"],
1077
- "نوع العمل": sub["work"],
1078
- "التكلفة الإجمالية (ريال)": sub["total_price"]
1079
- })
1080
-
1081
- subs_df = pd.DataFrame(subs_data)
1082
-
1083
- # إضافة صف المجموع
1084
- total_row = pd.DataFrame([{
1085
- "م": "",
1086
- "اسم المقاول": "المجموع",
1087
- "نوع العمل": "",
1088
- "التكلفة الإجمالية (ريال)": subcontractors_total
1089
- }])
1090
-
1091
- # استخدام pd.concat بدلاً من append
1092
- subs_df = pd.concat([subs_df, total_row], ignore_index=True)
1093
-
1094
- # عرض الجدول القابل للتعديل
1095
- edited_subs_df = st.data_editor(
1096
- subs_df,
1097
- column_config={
1098
- "م": st.column_config.NumberColumn("م", width="small"),
1099
- "اسم المقاول": st.column_config.TextColumn("اسم المقاول"),
1100
- "نوع العمل": st.column_config.TextColumn("نوع العمل"),
1101
- "التكلفة الإجمالية (ريال)": st.column_config.NumberColumn("التكلفة الإجمالية (ريال)", format="%.2f")
1102
- },
1103
- hide_index=True,
1104
- key="subs_table",
1105
- on_change=self._update_total_price,
1106
- disabled=["م", "اسم المقاول", "نوع العمل"]
1107
- )
1108
-
1109
- # تحديث البيانات بعد التعديل
1110
- for i, sub in enumerate(current_item["subcontractors"]):
1111
- if i < len(edited_subs_df) - 1: # تجاهل صف المجموع
1112
- sub["total_price"] = edited_subs_df.iloc[i]["التكلفة الإجمالية (ريال)"]
1113
- sub["unit_price"] = sub["total_price"] # للمقاولين، سعر الوحدة = السعر الإجمالي
1114
-
1115
- # معالجة أزرار التعديل والحذف
1116
- for i in range(len(current_item["subcontractors"])):
1117
- col1, col2 = st.columns([1, 1])
1118
- with col1:
1119
- if st.button(f"تعديل المقاول {i+1}", key=f"edit_sub_btn_{i}"):
1120
- st.session_state[f"edit_sub_{i}"] = True
1121
-
1122
- with col2:
1123
- if st.button(f"حذف المقاول {i+1}", key=f"delete_sub_btn_{i}"):
1124
- st.session_state[f"delete_sub_{i}"] = True
1125
-
1126
- # نموذج التعديل
1127
- if st.session_state.get(f"edit_sub_{i}", False):
1128
- with st.form(f"edit_sub_form_{i}"):
1129
- sub = current_item["subcontractors"][i]
1130
- col1, col2 = st.columns(2)
1131
- with col1:
1132
- sub_name = st.text_input("اسم المقاول", value=sub["name"])
1133
- sub_work = st.text_input("نوع العمل", value=sub["work"])
1134
- with col2:
1135
- sub_price = st.number_input("التكلفة الإجمالية (ريال)", min_value=0.0, step=100.0, value=sub["total_price"])
1136
-
1137
- col1, col2 = st.columns(2)
1138
- with col1:
1139
- if st.form_submit_button("حفظ التعديلات"):
1140
- sub["name"] = sub_name
1141
- sub["work"] = sub_work
1142
- sub["total_price"] = float(sub_price)
1143
- sub["unit_price"] = sub["total_price"]
1144
- st.session_state.item_analysis_edited = True
1145
- st.session_state[f"edit_sub_{i}"] = False
1146
- st.rerun()
1147
- with col2:
1148
- if st.form_submit_button("إلغاء"):
1149
- st.session_state[f"edit_sub_{i}"] = False
1150
- st.rerun()
1151
-
1152
- # تأكيد الحذف
1153
- if st.session_state.get(f"delete_sub_{i}", False):
1154
- st.warning(f"هل أنت متأكد من حذف المقاول: {current_item['subcontractors'][i]['name']}؟")
1155
- col1, col2 = st.columns(2)
1156
- with col1:
1157
- if st.button("نعم، حذف", key=f"confirm_delete_sub_{i}"):
1158
- current_item["subcontractors"].pop(i)
1159
- st.session_state.item_analysis_edited = True
1160
- st.session_state[f"delete_sub_{i}"] = False
1161
- st.rerun()
1162
- with col2:
1163
- if st.button("إلغاء", key=f"cancel_delete_sub_{i}"):
1164
- st.session_state[f"delete_sub_{i}"] = False
1165
- st.rerun()
1166
-
1167
- # عرض المجموع الكلي للمقاولين
1168
- st.markdown(f"<div class='highlight-total'>إجمالي تكلفة المقاولين من الباطن: <span class='total-value'>{subcontractors_total:,.2f}</span> ريال</div>", unsafe_allow_html=True)
1169
- else:
1170
- st.info("لا يوجد مقاولون من الباطن مضافون لهذا البند")
1171
-
1172
- # ملخص التكلفة - عرض خارج التبويبات
1173
- st.markdown("<hr>", unsafe_allow_html=True)
1174
- st.subheader("ملخص التكلفة الإجمالية للبند")
1175
-
1176
- # تنسيق ملخص التكلفة
1177
- col1, col2 = st.columns([2, 3])
1178
-
1179
- with col1:
1180
- # عرض ملخص التكلفة كجدول
1181
- summary_data = {
1182
- "عنصر التكلفة": ["تكلفة المواد", "تكلفة العمالة", "تكلفة المعدات", "تكلفة المقاولين من الباطن", "إجمالي التكلفة المباشرة", "المصاريف العمومية والربح", "إجمالي سعر البند"],
1183
- "القيمة (ريال)": [materials_total, labor_total, equipment_total, subcontractors_total, direct_cost, overhead_profit, current_item["total_price"]]
1184
- }
1185
-
1186
- summary_df = pd.DataFrame(summary_data)
1187
-
1188
- # تنسيق الجدول
1189
- st.dataframe(
1190
- summary_df,
1191
- column_config={
1192
- "عنصر التكلفة": st.column_config.TextColumn("عنصر التكلفة"),
1193
- "القيمة (ريال)": st.column_config.NumberColumn("القيمة (ريال)", format="%.2f")
1194
- },
1195
- hide_index=True
1196
- )
1197
-
1198
- with col2:
1199
- # رسم بياني للتكاليف
1200
- if direct_cost > 0:
1201
- # إعداد البيانات للرسم البياني
1202
- labels = ['المواد', 'العمالة', 'المعدات', 'المقاولين', 'الربح']
1203
- sizes = [materials_total, labor_total, equipment_total, subcontractors_total, overhead_profit]
1204
- colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#c2c2f0']
1205
-
1206
- # رسم الرسم البياني
1207
- fig, ax = plt.subplots(figsize=(8, 6))
1208
- wedges, texts, autotexts = ax.pie(
1209
- sizes,
1210
- labels=[get_display(arabic_reshaper.reshape(label)) for label in labels],
1211
- autopct='%1.1f%%',
1212
- startangle=90,
1213
- colors=colors
1214
- )
1215
-
1216
- # تنسيق النص
1217
- for text in texts:
1218
- text.set_fontsize(12)
1219
- for autotext in autotexts:
1220
- autotext.set_fontsize(10)
1221
- autotext.set_color('white')
1222
-
1223
- ax.axis('equal')
1224
- plt.title(get_display(arabic_reshaper.reshape('توزيع تكاليف البند')), fontsize=16)
1225
-
1226
- st.pyplot(fig)
1227
-
1228
- # أزرار الإجراءات
1229
- col1, col2 = st.columns(2)
1230
- with col1:
1231
- if st.button("حفظ التحليل والعودة لجدول الكميات", type="primary", use_container_width=True):
1232
- # تحديث السعر الإجمالي للبند بناءً على التكاليف المباشرة
1233
- if current_item["quantity"] > 0:
1234
- unit_cost = direct_cost / current_item["quantity"]
1235
- current_item["unit_price"] = unit_cost
1236
- current_item["total_price"] = current_item["quantity"] * unit_cost
1237
-
1238
- st.session_state.current_tab = "boq"
1239
- st.session_state.item_analysis_edited = False
1240
- st.success("تم حفظ التحليل بنجاح")
1241
- st.rerun()
1242
-
1243
- with col2:
1244
- if st.button("العودة بدون حفظ", use_container_width=True):
1245
- st.session_state.current_tab = "boq"
1246
- st.session_state.item_analysis_edited = False
1247
- st.rerun()
1248
 
1249
  # تشغيل التطبيق عند تنفيذ الملف مباشرة
1250
  if __name__ == "__main__":
 
27
 
28
  if 'item_analysis_edited' not in st.session_state:
29
  st.session_state.item_analysis_edited = False
30
+
31
+ def _is_projects_dict(self):
32
+ """التحقق مما إذا كان projects قاموساً أم قائمة"""
33
+ return isinstance(st.session_state.projects, dict)
34
 
35
  def _add_custom_css(self):
36
  """إضافة CSS مخصص للتطبيق"""
 
93
  padding: 10px;
94
  margin: 10px 0;
95
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  </style>
97
  """, unsafe_allow_html=True)
98
 
 
107
  # استدعاء دالة run لتنفيذ وظائف التطبيق
108
  self.run()
109
 
 
 
 
 
 
110
  def run(self):
111
  """تشغيل التطبيق"""
112
  st.title("وحدة التسعير المتكاملة")
 
114
  # عرض الشريط العلوي
115
  self._render_top_bar()
116
 
 
 
 
 
 
117
  # عرض المحتوى حسب التبويب المحدد
118
  if st.session_state.current_tab == "boq":
119
  self._render_bill_of_quantities()
 
143
 
144
  def _create_new_project(self):
145
  """إنشاء مشروع جديد"""
146
+ # إنشاء مشروع جديد
147
  new_project = {
148
+ "id": f"proj_{len(st.session_state.projects) + 1}",
149
  "name": f"مشروع رقم {len(st.session_state.projects) + 1}",
150
  "client": "عميل قياسي",
151
  "date": datetime.now().strftime("%Y-%m-%d"),
152
  "budget": 1000000.00,
153
  "items": []
154
  }
155
+
156
+ # إضافة المشروع الجديد حسب نوع هيكل البيانات
157
+ if self._is_projects_dict():
158
+ # إذا كان قاموساً
159
+ project_id = new_project["id"]
160
+ st.session_state.projects[project_id] = new_project
161
+ st.session_state.current_project_id = project_id
162
+ else:
163
+ # إذا كان قائمة
164
+ st.session_state.projects.append(new_project)
165
+ st.session_state.current_project_id = len(st.session_state.projects) - 1
166
+
167
  st.session_state.current_tab = "boq"
168
  st.rerun()
169
 
 
172
  st.header("جدول الكميات")
173
 
174
  # اختيار المشروع
175
+ if self._is_projects_dict():
176
+ # إذا كان قاموساً
177
+ project_options = {proj["name"]: proj_id for proj_id, proj in st.session_state.projects.items()}
178
+ else:
179
+ # إذا كان قائمة
180
+ project_options = {proj["name"]: i for i, proj in enumerate(st.session_state.projects)}
181
+
182
+ # التحقق من وجود مشاريع
183
+ if not project_options:
184
+ st.warning("لا توجد مشاريع. قم بإنشاء مشروع جديد أولاً.")
185
+ return
186
+
187
  selected_project_name = st.selectbox(
188
  "اختر المشروع",
189
  options=list(project_options.keys()),
190
+ index=0 if st.session_state.current_project_id is None else list(project_options.keys()).index(self._get_current_project()["name"])
191
  )
192
 
193
  st.session_state.current_project_id = project_options[selected_project_name]
194
+ current_project = self._get_current_project()
195
 
196
  # عرض معلومات المشروع
197
  col1, col2, col3 = st.columns(3)
 
228
  "subcontractors": []
229
  }
230
  current_project["items"].append(new_item)
231
+ st.success("تمت إضافة البند بنجاح")
 
 
232
  st.rerun()
233
 
234
  # عرض جدول الكميات
235
  if current_project["items"]:
236
+ items_df = pd.DataFrame([
237
+ {
238
+ "م": i + 1,
239
+ "اسم البند": item["name"],
240
+ "الوحدة": item["unit"],
241
+ "الكمية": item["quantity"],
242
+ "سعر الوحدة (ريال)": item["unit_price"],
243
+ "السعر الإجمالي (ريال)": item["total_price"],
244
+ "تحليل": f'<a href="#" onclick="return false;" id="analyze_{i}">تحليل</a>'
245
+ }
246
+ for i, item in enumerate(current_project["items"])
247
+ ])
248
 
249
+ # إضافة صف المجموع
250
+ total_price = sum(item["total_price"] for item in current_project["items"])
251
+ total_row = pd.DataFrame([{
252
+ "م": "",
253
+ "اسم البند": "المجموع",
254
+ "الوحدة": "",
255
+ "الكمية": "",
256
+ "سعر الوحدة (ريال)": "",
257
+ "السعر الإجمالي (ريال)": total_price,
258
+ "تحليل": ""
259
+ }])
260
+
261
+ # استخدام pd.concat بدلاً من append
262
+ items_df = pd.concat([items_df, total_row], ignore_index=True)
263
+
264
+ st.write(items_df.to_html(escape=False, index=False), unsafe_allow_html=True)
265
+
266
+ # معالجة النقر على رابط التحليل
267
+ for i in range(len(current_project["items"])):
268
+ if st.button(f"تحليل البند {i+1}", key=f"analyze_btn_{i}", use_container_width=True):
269
+ st.session_state.current_item_index = i
270
+ st.session_state.current_tab = "item_analysis"
271
+ st.rerun()
272
+ else:
273
+ st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود جديدة.")
274
+
275
+ def _get_current_project(self):
276
+ """الحصول على المشروع الحالي بناءً على نوع هيكل البيانات"""
277
+ if st.session_state.current_project_id is None:
278
+ return None
279
+
280
+ if self._is_projects_dict():
281
+ # إذا كان قاموساً
282
+ return st.session_state.projects[st.session_state.current_project_id]
283
+ else:
284
+ # إذا كان قائمة
285
+ return st.session_state.projects[int(st.session_state.current_project_id)]
286
+
287
+ def _render_item_price_analysis(self):
288
+ """عرض تحليل سعر البند"""
289
+ if st.session_state.current_project_id is None or "current_item_index" not in st.session_state:
290
+ st.warning("الرجاء اختيار مشروع وبند أولاً")
291
+ return
292
+
293
+ current_project = self._get_current_project()
294
+ current_item = current_project["items"][st.session_state.current_item_index]
295
+
296
+ st.header(f"تحليل سعر البند: {current_item['name']}")
297
+
298
+ # معلومات البند
299
+ col1, col2, col3, col4 = st.columns(4)
300
+ with col1:
301
+ st.write(f"**الوحدة:** {current_item['unit']}")
302
+ with col2:
303
+ st.write(f"**الكمية:** {current_item['quantity']}")
304
+ with col3:
305
+ st.write(f"**سعر الوحدة:** {current_item['unit_price']} ريال")
306
+ with col4:
307
+ st.write(f"**السعر الإجمالي:** {current_item['total_price']} ريال")
308
+
309
+ # زر حفظ التغييرات
310
+ if st.session_state.item_analysis_edited:
311
+ if st.button("حفظ التغييرات", type="primary", use_container_width=True):
312
+ # تحديث السعر الإجمالي للبند
313
+ materials_total = sum(material.get("total_price", 0) for material in current_item["materials"])
314
+ labor_total = sum(labor.get("total_price", 0) for labor in current_item["labor"])
315
+ equipment_total = sum(equipment.get("total_price", 0) for equipment in current_item["equipment"])
316
+ subcontractors_total = sum(sub.get("total_price", 0) for sub in current_item["subcontractors"])
317
+
318
+ direct_cost = materials_total + labor_total + equipment_total + subcontractors_total
319
+ current_item["unit_price"] = direct_cost / current_item["quantity"] if current_item["quantity"] > 0 else 0
320
+ current_item["total_price"] = current_item["unit_price"] * current_item["quantity"]
321
+
322
+ st.session_state.item_analysis_edited = False
323
+ st.success("تم حفظ التغييرات بنجاح")
324
+ st.rerun()
325
+
326
+ # تحليل المواد
327
+ st.subheader("تحليل المواد")
328
+
329
+ # إضافة مادة جديدة
330
+ with st.expander("إضافة مادة جديدة"):
331
+ with st.form("add_material_form"):
332
+ col1, col2 = st.columns(2)
333
+ with col1:
334
+ new_material_name = st.text_input("اسم المادة")
335
+ new_material_unit = st.text_input("الوحدة")
336
+ with col2:
337
+ new_material_quantity = st.number_input("الكمية", min_value=0.0, step=0.1)
338
+ new_material_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1)
339
 
340
+ submitted = st.form_submit_button("إضافة المادة")
341
+ if submitted and new_material_name and new_material_unit:
342
+ new_material = {
343
+ "id": f"material_{len(current_item['materials']) + 1}",
344
+ "name": new_material_name,
345
+ "unit": new_material_unit,
346
+ "quantity": float(new_material_quantity),
347
+ "unit_price": float(new_material_price),
348
+ "total_price": float(new_material_quantity) * float(new_material_price)
349
+ }
350
+ current_item["materials"].append(new_material)
351
+ st.session_state.item_analysis_edited = True
352
+ st.success("تمت إضافة المادة بنجاح")
353
+ st.rerun()
354
+
355
+ # عرض جدول المواد
356
+ if current_item["materials"]:
357
+ # تحويل البيانات إلى DataFrame
358
+ materials_data = []
359
+ for i, material in enumerate(current_item["materials"]):
360
+ materials_data.append({
361
+ "م": i + 1,
362
+ "اسم المادة": material["name"],
363
+ "الوحدة": material["unit"],
364
+ "الكمية": material["quantity"],
365
+ "سعر الوحدة (ريال)": material["unit_price"],
366
+ "السعر الإجمالي (ريال)": material["total_price"],
367
+ "الإجراءات": f'<button class="action-button edit-button" id="edit_material_{i}">تعديل</button> <button class="action-button delete-button" id="delete_material_{i}">حذف</button>'
368
+ })
369
+
370
+ materials_df = pd.DataFrame(materials_data)
371
+
372
+ # إضافة صف المجموع
373
+ materials_total = sum(material["total_price"] for material in current_item["materials"])
374
+ total_row = pd.DataFrame([{
375
+ "م": "",
376
+ "اسم المادة": "المجموع",
377
+ "الوحدة": "",
378
+ "الكمية": "",
379
+ "سعر الوحدة (ريال)": "",
380
+ "السعر الإجمالي (ريال)": materials_total,
381
+ "الإجراءات": ""
382
+ }])
383
+
384
+ # استخدام pd.concat بدلاً من append
385
+ materials_df = pd.concat([materials_df, total_row], ignore_index=True)
386
+
387
+ # عرض الجدول القابل للتعديل
388
+ edited_materials_df = st.data_editor(
389
+ materials_df,
390
+ column_config={
391
+ "م": st.column_config.NumberColumn("م", width="small"),
392
+ "اسم المادة": st.column_config.TextColumn("اسم المادة"),
393
+ "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
394
+ "الكمية": st.column_config.NumberColumn("الكمية", format="%.2f", width="small"),
395
+ "سعر الوحدة (ريال)": st.column_config.NumberColumn("سعر الوحدة (ريال)", format="%.2f"),
396
+ "السعر الإجمالي (ريال)": st.column_config.NumberColumn("السعر الإجمالي (ريال)", format="%.2f"),
397
+ "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
398
+ },
399
+ hide_index=True,
400
+ key="materials_table",
401
+ on_change=self._update_total_price,
402
+ disabled=["م", "اسم المادة", "الوحدة", "السعر الإجمالي (ريال)", "الإجراءات"]
403
+ )
404
+
405
+ # تحديث البيانات بعد التعديل
406
+ for i, material in enumerate(current_item["materials"]):
407
+ if i < len(edited_materials_df) - 1: # تجاهل صف المجموع
408
+ material["quantity"] = edited_materials_df.iloc[i]["الكمية"]
409
+ material["unit_price"] = edited_materials_df.iloc[i]["سعر الوحدة (ريال)"]
410
+ material["total_price"] = material["quantity"] * material["unit_price"]
411
+
412
+ # معالجة أزرار التعديل والحذف
413
+ for i in range(len(current_item["materials"])):
414
+ col1, col2 = st.columns([1, 1])
415
  with col1:
416
+ if st.button(f"تعديل المادة {i+1}", key=f"edit_material_btn_{i}"):
417
+ st.session_state[f"edit_material_{i}"] = True
418
+
 
419
  with col2:
420
+ if st.button(f"حذف المادة {i+1}", key=f"delete_material_btn_{i}"):
421
+ st.session_state[f"delete_material_{i}"] = True
 
 
 
422
 
423
  # نموذج التعديل
424
+ if st.session_state.get(f"edit_material_{i}", False):
425
+ with st.form(f"edit_material_form_{i}"):
426
+ material = current_item["materials"][i]
427
  col1, col2 = st.columns(2)
428
  with col1:
429
+ material_name = st.text_input("اسم المادة", value=material["name"])
430
+ material_unit = st.text_input("الوحدة", value=material["unit"])
431
  with col2:
432
+ material_quantity = st.number_input("الكمية", min_value=0.0, step=0.1, value=material["quantity"])
433
+ material_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1, value=material["unit_price"])
434
 
435
  col1, col2 = st.columns(2)
436
  with col1:
437
  if st.form_submit_button("حفظ التعديلات"):
438
+ material["name"] = material_name
439
+ material["unit"] = material_unit
440
+ material["quantity"] = float(material_quantity)
441
+ material["unit_price"] = float(material_price)
442
+ material["total_price"] = material["quantity"] * material["unit_price"]
443
+ st.session_state.item_analysis_edited = True
444
+ st.session_state[f"edit_material_{i}"] = False
445
  st.rerun()
446
  with col2:
447
  if st.form_submit_button("إلغاء"):
448
+ st.session_state[f"edit_material_{i}"] = False
449
  st.rerun()
450
 
451
  # تأكيد الحذف
452
+ if st.session_state.get(f"delete_material_{i}", False):
453
+ st.warning(f"هل أنت متأكد من حذف المادة: {current_item['materials'][i]['name']}؟")
454
  col1, col2 = st.columns(2)
455
  with col1:
456
+ if st.button("نعم، حذف", key=f"confirm_delete_material_{i}"):
457
+ current_item["materials"].pop(i)
458
+ st.session_state.item_analysis_edited = True
459
+ st.session_state[f"delete_material_{i}"] = False
460
  st.rerun()
461
  with col2:
462
+ if st.button("إلغاء", key=f"cancel_delete_material_{i}"):
463
+ st.session_state[f"delete_material_{i}"] = False
464
  st.rerun()
465
 
466
+ # عرض المجموع الكلي للمواد
467
+ st.markdown(f"<div class='highlight-total'>إجمالي تكلفة المواد: <span class='total-value'>{materials_total:,.2f}</span> ريال</div>", unsafe_allow_html=True)
 
 
 
 
 
468
  else:
469
+ st.info("لا توجد مواد مضافة لهذا البند")
470
+
471
+ # تحليل العمالة
472
+ st.subheader("تحليل العمالة")
473
+
474
+ # إضافة عمالة جديدة
475
+ with st.expander("إضافة عمالة جديدة"):
476
+ with st.form("add_labor_form"):
477
+ col1, col2 = st.columns(2)
478
+ with col1:
479
+ new_labor_name = st.text_input("نوع العمالة")
480
+ new_labor_unit = st.text_input("الوحدة", value="يوم")
481
+ with col2:
482
+ new_labor_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5)
483
+ new_labor_price = st.number_input("الأجر اليومي (ريال)", min_value=0.0, step=10.0)
484
+
485
+ submitted = st.form_submit_button("إضافة العمالة")
486
+ if submitted and new_labor_name:
487
+ new_labor = {
488
+ "id": f"labor_{len(current_item['labor']) + 1}",
489
+ "name": new_labor_name,
490
+ "unit": new_labor_unit,
491
+ "quantity": float(new_labor_quantity),
492
+ "unit_price": float(new_labor_price),
493
+ "total_price": float(new_labor_quantity) * float(new_labor_price)
494
+ }
495
+ current_item["labor"].append(new_labor)
496
+ st.session_state.item_analysis_edited = True
497
+ st.success("تمت إضافة العمالة بنجاح")
498
+ st.rerun()
499
 
500
+ # عرض جدول العمالة
501
+ if current_item["labor"]:
502
+ # تحويل البيانات إلى DataFrame
503
+ labor_data = []
504
+ for i, labor in enumerate(current_item["labor"]):
505
+ labor_data.append({
506
+ "م": i + 1,
507
+ "نوع العمالة": labor["name"],
508
+ "الوحدة": labor["unit"],
509
+ "عدد الأيام": labor["quantity"],
510
+ "الأجر اليومي (ريال)": labor["unit_price"],
511
+ "الإجمالي (ريال)": labor["total_price"],
512
+ "الإجراءات": f'<button class="action-button edit-button" id="edit_labor_{i}">تعديل</button> <button class="action-button delete-button" id="delete_labor_{i}">حذف</button>'
513
+ })
514
+
515
+ labor_df = pd.DataFrame(labor_data)
516
 
517
+ # إضافة صف المجموع
518
+ labor_total = sum(labor["total_price"] for labor in current_item["labor"])
519
+ total_row = pd.DataFrame([{
520
+ "م": "",
521
+ "نوع العمالة": "المجموع",
522
+ "الوحدة": "",
523
+ "عدد الأيام": "",
524
+ "الأجر اليومي (ريال)": "",
525
+ "الإجمالي (ريال)": labor_total,
526
+ "الإجراءات": ""
527
+ }])
528
+
529
+ # استخدام pd.concat بدلاً من append
530
+ labor_df = pd.concat([labor_df, total_row], ignore_index=True)
531
+
532
+ # عرض الجدول القابل للتعديل
533
+ edited_labor_df = st.data_editor(
534
+ labor_df,
535
+ column_config={
536
+ "م": st.column_config.NumberColumn("م", width="small"),
537
+ "نوع العمالة": st.column_config.TextColumn("نوع العمالة"),
538
+ "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
539
+ "عدد الأيام": st.column_config.NumberColumn("عدد الأيام", format="%.1f", width="small"),
540
+ "الأجر اليومي (ريال)": st.column_config.NumberColumn("الأجر اليومي (ريال)", format="%.2f"),
541
+ "الإجمالي (ريال)": st.column_config.NumberColumn("الإجمالي (ريال)", format="%.2f"),
542
+ "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
543
+ },
544
+ hide_index=True,
545
+ key="labor_table",
546
+ on_change=self._update_total_price,
547
+ disabled=["م", "نوع العمالة", "الوحدة", "الإجمالي (ريال)", "الإجراءات"]
548
+ )
549
+
550
+ # تحديث البيانات بعد التعديل
551
+ for i, labor in enumerate(current_item["labor"]):
552
+ if i < len(edited_labor_df) - 1: # تجاهل صف المجموع
553
+ labor["quantity"] = edited_labor_df.iloc[i]["عدد الأيام"]
554
+ labor["unit_price"] = edited_labor_df.iloc[i]["الأجر اليومي (ريال)"]
555
+ labor["total_price"] = labor["quantity"] * labor["unit_price"]
556
+
557
+ # معالجة أزرار التعديل والحذف
558
+ for i in range(len(current_item["labor"])):
559
+ col1, col2 = st.columns([1, 1])
560
+ with col1:
561
+ if st.button(f"تعديل العمالة {i+1}", key=f"edit_labor_btn_{i}"):
562
+ st.session_state[f"edit_labor_{i}"] = True
563
+
564
+ with col2:
565
+ if st.button(f"حذف العمالة {i+1}", key=f"delete_labor_btn_{i}"):
566
+ st.session_state[f"delete_labor_{i}"] = True
567
+
568
+ # نموذج التعديل
569
+ if st.session_state.get(f"edit_labor_{i}", False):
570
+ with st.form(f"edit_labor_form_{i}"):
571
+ labor = current_item["labor"][i]
572
+ col1, col2 = st.columns(2)
573
+ with col1:
574
+ labor_name = st.text_input("نوع العمالة", value=labor["name"])
575
+ labor_unit = st.text_input("الوحدة", value=labor["unit"])
576
+ with col2:
577
+ labor_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, value=labor["quantity"])
578
+ labor_price = st.number_input("الأجر اليومي (ريال)", min_value=0.0, step=10.0, value=labor["unit_price"])
579
+
580
+ col1, col2 = st.columns(2)
581
+ with col1:
582
+ if st.form_submit_button("حفظ التعديلات"):
583
+ labor["name"] = labor_name
584
+ labor["unit"] = labor_unit
585
+ labor["quantity"] = float(labor_quantity)
586
+ labor["unit_price"] = float(labor_price)
587
+ labor["total_price"] = labor["quantity"] * labor["unit_price"]
588
+ st.session_state.item_analysis_edited = True
589
+ st.session_state[f"edit_labor_{i}"] = False
590
+ st.rerun()
591
+ with col2:
592
+ if st.form_submit_button("إلغاء"):
593
+ st.session_state[f"edit_labor_{i}"] = False
594
+ st.rerun()
595
+
596
+ # تأكيد الحذف
597
+ if st.session_state.get(f"delete_labor_{i}", False):
598
+ st.warning(f"هل أنت متأكد من حذف العمالة: {current_item['labor'][i]['name']}؟")
599
+ col1, col2 = st.columns(2)
600
+ with col1:
601
+ if st.button("نعم، حذف", key=f"confirm_delete_labor_{i}"):
602
+ current_item["labor"].pop(i)
603
+ st.session_state.item_analysis_edited = True
604
+ st.session_state[f"delete_labor_{i}"] = False
605
+ st.rerun()
606
+ with col2:
607
+ if st.button("إلغاء", key=f"cancel_delete_labor_{i}"):
608
+ st.session_state[f"delete_labor_{i}"] = False
609
+ st.rerun()
610
 
611
+ # عرض المجموع الكلي للعمالة
612
+ st.markdown(f"<div class='highlight-total'>إجمالي تكلفة العمالة: <span class='total-value'>{labor_total:,.2f}</span> ريال</div>", unsafe_allow_html=True)
613
+ else:
614
+ st.info("لا توجد عمالة مضافة لهذا البند")
615
+
616
+ # تحليل المعدات
617
+ st.subheader("تحليل المعدات")
618
+
619
+ # إضافة معدات جديدة
620
+ with st.expander("إضافة معدات جديدة"):
621
+ with st.form("add_equipment_form"):
622
+ col1, col2 = st.columns(2)
623
+ with col1:
624
+ new_equipment_name = st.text_input("نوع المعدات")
625
+ new_equipment_unit = st.text_input("الوحدة", value="يوم")
626
+ with col2:
627
+ new_equipment_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, key="new_equipment_quantity")
628
+ new_equipment_price = st.number_input("التكلفة اليومية (ريال)", min_value=0.0, step=10.0, key="new_equipment_price")
629
+
630
+ submitted = st.form_submit_button("إضافة المعدات")
631
+ if submitted and new_equipment_name:
632
+ new_equipment = {
633
+ "id": f"equipment_{len(current_item['equipment']) + 1}",
634
+ "name": new_equipment_name,
635
+ "unit": new_equipment_unit,
636
+ "quantity": float(new_equipment_quantity),
637
+ "unit_price": float(new_equipment_price),
638
+ "total_price": float(new_equipment_quantity) * float(new_equipment_price)
639
+ }
640
+ current_item["equipment"].append(new_equipment)
641
  st.session_state.item_analysis_edited = True
642
+ st.success("تمت إضافة المعدات بنجاح")
643
  st.rerun()
644
+
645
+ # عرض جدول المعدات
646
+ if current_item["equipment"]:
647
+ # تحويل البيانات إلى DataFrame
648
+ equipment_data = []
649
+ for i, equipment in enumerate(current_item["equipment"]):
650
+ equipment_data.append({
651
+ "م": i + 1,
652
+ "نوع المعدات": equipment["name"],
653
+ "الوحدة": equipment["unit"],
654
+ "عدد الأيام": equipment["quantity"],
655
+ "التكلفة اليومية (ريال)": equipment["unit_price"],
656
+ "الإجمالي (ريال)": equipment["total_price"],
657
+ "الإجراءات": f'<button class="action-button edit-button" id="edit_equipment_{i}">تعديل</button> <button class="action-button delete-button" id="delete_equipment_{i}">حذف</button>'
658
+ })
659
+
660
+ equipment_df = pd.DataFrame(equipment_data)
661
+
662
+ # إضافة صف المجموع
663
+ equipment_total = sum(equipment["total_price"] for equipment in current_item["equipment"])
664
+ total_row = pd.DataFrame([{
665
+ "م": "",
666
+ "نوع المعدات": "المجموع",
667
+ "الوحدة": "",
668
+ "عدد الأيام": "",
669
+ "التكلفة اليومية (ريال)": "",
670
+ "الإجمالي (ريال)": equipment_total,
671
+ "الإجراءات": ""
672
+ }])
673
+
674
+ # استخدام pd.concat بدلاً من append
675
+ equipment_df = pd.concat([equipment_df, total_row], ignore_index=True)
676
+
677
+ # عرض الجدول القابل للتعديل
678
+ edited_equipment_df = st.data_editor(
679
+ equipment_df,
680
+ column_config={
681
+ "م": st.column_config.NumberColumn("م", width="small"),
682
+ "نوع المعدات": st.column_config.TextColumn("نوع المعدات"),
683
+ "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
684
+ "عدد الأيام": st.column_config.NumberColumn("عدد الأيام", format="%.1f", width="small"),
685
+ "التكلفة اليومية (ريال)": st.column_config.NumberColumn("التكلفة اليومية (ريال)", format="%.2f"),
686
+ "الإجمالي (ريال)": st.column_config.NumberColumn("الإجمالي (ريال)", format="%.2f"),
687
+ "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
688
+ },
689
+ hide_index=True,
690
+ key="equipment_table",
691
+ on_change=self._update_total_price,
692
+ disabled=["م", "نوع المعدات", "الوحدة", "الإجمالي (ريال)", "الإجراءات"]
693
+ )
694
+
695
+ # تحديث البيانات بعد التعديل
696
+ for i, equipment in enumerate(current_item["equipment"]):
697
+ if i < len(edited_equipment_df) - 1: # تجاهل صف المجموع
698
+ equipment["quantity"] = edited_equipment_df.iloc[i]["عدد الأيام"]
699
+ equipment["unit_price"] = edited_equipment_df.iloc[i]["التكلفة اليومية (ريال)"]
700
+ equipment["total_price"] = equipment["quantity"] * equipment["unit_price"]
701
+
702
+ # معالجة أزرار التعديل والحذف
703
+ for i in range(len(current_item["equipment"])):
704
+ col1, col2 = st.columns([1, 1])
705
+ with col1:
706
+ if st.button(f"تعديل المعدات {i+1}", key=f"edit_equipment_btn_{i}"):
707
+ st.session_state[f"edit_equipment_{i}"] = True
708
+
709
+ with col2:
710
+ if st.button(f"حذف المعدات {i+1}", key=f"delete_equipment_btn_{i}"):
711
+ st.session_state[f"delete_equipment_{i}"] = True
712
+
713
+ # نموذج التعديل
714
+ if st.session_state.get(f"edit_equipment_{i}", False):
715
+ with st.form(f"edit_equipment_form_{i}"):
716
+ equipment = current_item["equipment"][i]
717
+ col1, col2 = st.columns(2)
718
+ with col1:
719
+ equipment_name = st.text_input("نوع المعدات", value=equipment["name"])
720
+ equipment_unit = st.text_input("الوحدة", value=equipment["unit"])
721
+ with col2:
722
+ equipment_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, value=equipment["quantity"])
723
+ equipment_price = st.number_input("التكلفة اليومية (ريال)", min_value=0.0, step=10.0, value=equipment["unit_price"])
724
+
725
+ col1, col2 = st.columns(2)
726
+ with col1:
727
+ if st.form_submit_button("حفظ التعديلات"):
728
+ equipment["name"] = equipment_name
729
+ equipment["unit"] = equipment_unit
730
+ equipment["quantity"] = float(equipment_quantity)
731
+ equipment["unit_price"] = float(equipment_price)
732
+ equipment["total_price"] = equipment["quantity"] * equipment["unit_price"]
733
+ st.session_state.item_analysis_edited = True
734
+ st.session_state[f"edit_equipment_{i}"] = False
735
+ st.rerun()
736
+ with col2:
737
+ if st.form_submit_button("إلغاء"):
738
+ st.session_state[f"edit_equipment_{i}"] = False
739
+ st.rerun()
740
+
741
+ # تأكيد الحذف
742
+ if st.session_state.get(f"delete_equipment_{i}", False):
743
+ st.warning(f"هل أنت متأكد من حذف المعدات: {current_item['equipment'][i]['name']}؟")
744
+ col1, col2 = st.columns(2)
745
+ with col1:
746
+ if st.button("نعم، حذف", key=f"confirm_delete_equipment_{i}"):
747
+ current_item["equipment"].pop(i)
748
+ st.session_state.item_analysis_edited = True
749
+ st.session_state[f"delete_equipment_{i}"] = False
750
+ st.rerun()
751
+ with col2:
752
+ if st.button("إلغاء", key=f"cancel_delete_equipment_{i}"):
753
+ st.session_state[f"delete_equipment_{i}"] = False
754
+ st.rerun()
755
+
756
+ # عرض المجموع الكلي للمعدات
757
+ st.markdown(f"<div class='highlight-total'>إجمالي تكلفة المعدات: <span class='total-value'>{equipment_total:,.2f}</span> ريال</div>", unsafe_allow_html=True)
758
+ else:
759
+ st.info("لا توجد معدات مضافة لهذا البند")
760
+
761
+ # تحليل المقاولين من الباطن
762
+ st.subheader("تحليل المقاولين من الباطن")
763
+
764
+ # إضافة مقاول جديد
765
+ with st.expander("إضافة مقاول من الباطن"):
766
+ with st.form("add_subcontractor_form"):
767
+ col1, col2 = st.columns(2)
768
+ with col1:
769
+ new_sub_name = st.text_input("اسم المقاول")
770
+ new_sub_work = st.text_input("نوع العمل")
771
+ with col2:
772
+ new_sub_price = st.number_input("التكلفة الإجمالية (ريال)", min_value=0.0, step=100.0)
773
+
774
+ submitted = st.form_submit_button("إضافة مقاول")
775
+ if submitted and new_sub_name and new_sub_work:
776
+ new_sub = {
777
+ "id": f"sub_{len(current_item['subcontractors']) + 1}",
778
+ "name": new_sub_name,
779
+ "work": new_sub_work,
780
+ "quantity": 1,
781
+ "unit_price": float(new_sub_price),
782
+ "total_price": float(new_sub_price)
783
+ }
784
+ current_item["subcontractors"].append(new_sub)
785
+ st.session_state.item_analysis_edited = True
786
+ st.success("تمت إضافة المقاول بنجاح")
787
+ st.rerun()
788
+
789
+ # عرض جدول المقاولين
790
+ if current_item["subcontractors"]:
791
+ # تحويل البيانات إلى DataFrame
792
+ subs_data = []
793
+ for i, sub in enumerate(current_item["subcontractors"]):
794
+ subs_data.append({
795
+ "م": i + 1,
796
+ "اسم المقاول": sub["name"],
797
+ "نوع العمل": sub["work"],
798
+ "التكلفة الإجمالية (ريال)": sub["total_price"],
799
+ "الإجراءات": f'<button class="action-button edit-button" id="edit_sub_{i}">تعديل</button> <button class="action-button delete-button" id="delete_sub_{i}">حذف</button>'
800
+ })
801
+
802
+ subs_df = pd.DataFrame(subs_data)
803
+
804
+ # إضافة صف المجموع
805
+ subs_total = sum(sub["total_price"] for sub in current_item["subcontractors"])
806
+ total_row = pd.DataFrame([{
807
+ "م": "",
808
+ "اسم المقاول": "المجموع",
809
+ "نوع العمل": "",
810
+ "التكلفة الإجمالية (ريال)": subs_total,
811
+ "الإجراءات": ""
812
+ }])
813
+
814
+ # استخدام pd.concat بدلاً من append
815
+ subs_df = pd.concat([subs_df, total_row], ignore_index=True)
816
+
817
+ # عرض الجدول القابل للتعديل
818
+ edited_subs_df = st.data_editor(
819
+ subs_df,
820
+ column_config={
821
+ "م": st.column_config.NumberColumn("م", width="small"),
822
+ "اسم المقاول": st.column_config.TextColumn("اسم المقاول"),
823
+ "نوع العمل": st.column_config.TextColumn("نوع العمل"),
824
+ "التكلفة الإجمالية (ريال)": st.column_config.NumberColumn("التكلفة الإجمالية (ريال)", format="%.2f"),
825
+ "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
826
+ },
827
+ hide_index=True,
828
+ key="subs_table",
829
+ on_change=self._update_total_price,
830
+ disabled=["م", "اسم المقاول", "نوع العمل", "الإجراءات"]
831
+ )
832
+
833
+ # تحديث البيانات بعد التعديل
834
+ for i, sub in enumerate(current_item["subcontractors"]):
835
+ if i < len(edited_subs_df) - 1: # تجاهل صف المجموع
836
+ sub["total_price"] = edited_subs_df.iloc[i]["التكلفة الإجمالية (ريال)"]
837
+ sub["unit_price"] = sub["total_price"] # للمقاولين، سعر الوحدة = السعر الإجمالي
838
+
839
+ # معالجة أزرار التعديل والحذف
840
+ for i in range(len(current_item["subcontractors"])):
841
+ col1, col2 = st.columns([1, 1])
842
+ with col1:
843
+ if st.button(f"تعديل المقاول {i+1}", key=f"edit_sub_btn_{i}"):
844
+ st.session_state[f"edit_sub_{i}"] = True
845
+
846
+ with col2:
847
+ if st.button(f"حذف المقاول {i+1}", key=f"delete_sub_btn_{i}"):
848
+ st.session_state[f"delete_sub_{i}"] = True
849
+
850
+ # نموذج التعديل
851
+ if st.session_state.get(f"edit_sub_{i}", False):
852
+ with st.form(f"edit_sub_form_{i}"):
853
+ sub = current_item["subcontractors"][i]
854
+ col1, col2 = st.columns(2)
855
+ with col1:
856
+ sub_name = st.text_input("اسم المقاول", value=sub["name"])
857
+ sub_work = st.text_input("نوع العمل", value=sub["work"])
858
+ with col2:
859
+ sub_price = st.number_input("التكلفة الإجمالية (ريال)", min_value=0.0, step=100.0, value=sub["total_price"])
860
+
861
+ col1, col2 = st.columns(2)
862
+ with col1:
863
+ if st.form_submit_button("حفظ التعديلات"):
864
+ sub["name"] = sub_name
865
+ sub["work"] = sub_work
866
+ sub["total_price"] = float(sub_price)
867
+ sub["unit_price"] = sub["total_price"]
868
+ st.session_state.item_analysis_edited = True
869
+ st.session_state[f"edit_sub_{i}"] = False
870
+ st.rerun()
871
+ with col2:
872
+ if st.form_submit_button("إلغاء"):
873
+ st.session_state[f"edit_sub_{i}"] = False
874
+ st.rerun()
875
+
876
+ # تأكيد الحذف
877
+ if st.session_state.get(f"delete_sub_{i}", False):
878
+ st.warning(f"هل أنت متأكد من حذف المقاول: {current_item['subcontractors'][i]['name']}؟")
879
+ col1, col2 = st.columns(2)
880
+ with col1:
881
+ if st.button("نعم، حذف", key=f"confirm_delete_sub_{i}"):
882
+ current_item["subcontractors"].pop(i)
883
+ st.session_state.item_analysis_edited = True
884
+ st.session_state[f"delete_sub_{i}"] = False
885
+ st.rerun()
886
+ with col2:
887
+ if st.button("إلغاء", key=f"cancel_delete_sub_{i}"):
888
+ st.session_state[f"delete_sub_{i}"] = False
889
+ st.rerun()
890
+
891
+ # عرض المجموع الكلي للمقاولين
892
+ st.markdown(f"<div class='highlight-total'>إجمالي تكلفة المقاولين من الباطن: <span class='total-value'>{subs_total:,.2f}</span> ريال</div>", unsafe_allow_html=True)
893
+ else:
894
+ st.info("لا يوجد مقاولون من الباطن مضافون لهذا البند")
895
+
896
+ # ملخص التكلفة
897
+ st.subheader("ملخص التكلفة")
898
+
899
+ # حساب التكاليف
900
+ materials_total = sum(material.get("total_price", 0) for material in current_item["materials"])
901
+ labor_total = sum(labor.get("total_price", 0) for labor in current_item["labor"])
902
+ equipment_total = sum(equipment.get("total_price", 0) for equipment in current_item["equipment"])
903
+ subcontractors_total = sum(sub.get("total_price", 0) for sub in current_item["subcontractors"])
904
+
905
+ direct_cost = materials_total + labor_total + equipment_total + subcontractors_total
906
+ overhead_profit = max(0, current_item["total_price"] - direct_cost) # تجنب القيم السالبة
907
+
908
+ # عرض ملخص التكلفة
909
+ st.markdown("<div class='total-summary'>", unsafe_allow_html=True)
910
+
911
+ col1, col2 = st.columns(2)
912
+ with col1:
913
+ st.markdown("<p class='total-label'>تكلفة المواد:</p>", unsafe_allow_html=True)
914
+ st.markdown("<p class='total-label'>تكلفة العمالة:</p>", unsafe_allow_html=True)
915
+ st.markdown("<p class='total-label'>تكلفة المعدات:</p>", unsafe_allow_html=True)
916
+ st.markdown("<p class='total-label'>تكلفة المقاولين من الباطن:</p>", unsafe_allow_html=True)
917
+ st.markdown("<p class='total-label'>إجمالي التكلفة المباشرة:</p>", unsafe_allow_html=True)
918
+ st.markdown("<p class='total-label'>المصاريف العمومية والربح:</p>", unsafe_allow_html=True)
919
+ st.markdown("<p class='total-label'>إجمالي سعر البند:</p>", unsafe_allow_html=True)
920
+
921
+ with col2:
922
+ st.markdown(f"<p>{materials_total:,.2f} ريال</p>", unsafe_allow_html=True)
923
+ st.markdown(f"<p>{labor_total:,.2f} ريال</p>", unsafe_allow_html=True)
924
+ st.markdown(f"<p>{equipment_total:,.2f} ريال</p>", unsafe_allow_html=True)
925
+ st.markdown(f"<p>{subcontractors_total:,.2f} ريال</p>", unsafe_allow_html=True)
926
+ st.markdown(f"<p>{direct_cost:,.2f} ريال</p>", unsafe_allow_html=True)
927
+ st.markdown(f"<p>{overhead_profit:,.2f} ريال</p>", unsafe_allow_html=True)
928
+ st.markdown(f"<p class='total-value'>{current_item['total_price']:,.2f} ريال</p>", unsafe_allow_html=True)
929
+
930
+ st.markdown("</div>", unsafe_allow_html=True)
931
+
932
+ # رسم بياني للتكاليف
933
+ if direct_cost > 0:
934
+ st.subheader("توزيع التكاليف")
935
+
936
+ # إعداد البيانات للرسم البياني
937
+ labels = ['المواد', 'العمالة', 'المعدات', 'المقاولين', 'الربح']
938
+ sizes = [materials_total, labor_total, equipment_total, subcontractors_total, overhead_profit]
939
+ colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#c2c2f0']
940
+
941
+ # رسم الرسم البياني
942
+ fig, ax = plt.subplots(figsize=(10, 6))
943
+ wedges, texts, autotexts = ax.pie(
944
+ sizes,
945
+ labels=[get_display(arabic_reshaper.reshape(label)) for label in labels],
946
+ autopct='%1.1f%%',
947
+ startangle=90,
948
+ colors=colors
949
+ )
950
+
951
+ # تنسيق النص
952
+ for text in texts:
953
+ text.set_fontsize(12)
954
+ for autotext in autotexts:
955
+ autotext.set_fontsize(10)
956
+ autotext.set_color('white')
957
+
958
+ ax.axis('equal')
959
+ plt.title(get_display(arabic_reshaper.reshape('توزيع تكاليف البند')), fontsize=16)
960
+
961
+ st.pyplot(fig)
962
 
963
  def _load_sample_projects(self):
964
  """تحميل مشاريع نموذجية"""
 
967
  "id": "proj_1",
968
  "name": "مشروع إنشاء مبنى سكني",
969
  "client": "شركة الإعمار",
970
+ "date": "2025-06-24",
971
+ "budget": 901000000.00,
972
  "items": [
973
  {
974
  "id": "item_1",
 
1104
  ]
1105
  }
1106
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1107
 
1108
  # تشغيل التطبيق عند تنفيذ الملف مباشرة
1109
  if __name__ == "__main__":