EGYADMIN commited on
Commit
30cad09
·
verified ·
1 Parent(s): 1edb3ab

Update modules/pricing/pricing_app.py

Browse files
Files changed (1) hide show
  1. modules/pricing/pricing_app.py +340 -1337
modules/pricing/pricing_app.py CHANGED
@@ -1,160 +1,18 @@
1
  import streamlit as st
2
  import pandas as pd
3
  import numpy as np
4
- import matplotlib.pyplot as plt
5
- import matplotlib.font_manager as fm
6
- import arabic_reshaper
7
- from bidi.algorithm import get_display
8
- import io
9
- import base64
10
  from datetime import datetime
11
- import random
12
- import json
13
- import os
14
  import time
15
 
16
- class IntegratedPricingApp:
17
- """وحدة التسعير المتكاملة مع تحليل الأسعار"""
18
 
19
  def __init__(self):
20
- """تهيئة وحدة التسعير المتكاملة"""
21
- # تهيئة البيانات في حالة الجلسة إذا لم تكن موجودة
22
- if 'projects' not in st.session_state:
23
- st.session_state.projects = [
24
- {
25
- 'id': 1,
26
- 'name': 'مشروع تطوير الطرق الداخلية',
27
- 'client': 'وزارة النقل',
28
- 'estimated_value': 5000000,
29
- 'deadline': '2024-06-30',
30
- 'status': 'قيد التسعير',
31
- 'created_at': '2024-01-15',
32
- 'pricing_type': 'قياسي'
33
- },
34
- {
35
- 'id': 2,
36
- 'name': 'مشروع إنشاء مبنى إداري',
37
- 'client': 'شركة التطوير العقاري',
38
- 'estimated_value': 12000000,
39
- 'deadline': '2024-08-15',
40
- 'status': 'قيد التسعير',
41
- 'created_at': '2024-02-01',
42
- 'pricing_type': 'غير متزن'
43
- }
44
- ]
45
-
46
- if 'current_project' not in st.session_state:
47
- st.session_state.current_project = 1
48
-
49
- if 'next_project_id' not in st.session_state:
50
- st.session_state.next_project_id = len(st.session_state.projects) + 1
51
-
52
- if 'show_new_project_form' not in st.session_state:
53
- st.session_state.show_new_project_form = False
54
-
55
- if 'show_edit_project_form' not in st.session_state:
56
- st.session_state.show_edit_project_form = False
57
-
58
- if 'edit_project_id' not in st.session_state:
59
- st.session_state.edit_project_id = None
60
-
61
- if 'boq_items' not in st.session_state:
62
- st.session_state.boq_items = [
63
- {
64
- 'id': 1,
65
- 'project_id': 1,
66
- 'code': 'A-001',
67
- 'description': 'أعمال الحفر والردم',
68
- 'unit': 'م3',
69
- 'quantity': 1500,
70
- 'unit_price': 45,
71
- 'total_price': 67500,
72
- 'resource_type': 'مواد'
73
- },
74
- {
75
- 'id': 2,
76
- 'project_id': 1,
77
- 'code': 'A-002',
78
- 'description': 'توريد وتركيب طبقة أساس',
79
- 'unit': 'م2',
80
- 'quantity': 3000,
81
- 'unit_price': 85,
82
- 'total_price': 255000,
83
- 'resource_type': 'مواد'
84
- },
85
- {
86
- 'id': 3,
87
- 'project_id': 1,
88
- 'code': 'A-003',
89
- 'description': 'توريد وتركيب خرسانة جاهزة',
90
- 'unit': 'م3',
91
- 'quantity': 750,
92
- 'unit_price': 320,
93
- 'total_price': 240000,
94
- 'resource_type': 'مواد'
95
- },
96
- {
97
- 'id': 4,
98
- 'project_id': 2,
99
- 'code': 'B-001',
100
- 'description': 'أعمال الأساسات',
101
- 'unit': 'م3',
102
- 'quantity': 500,
103
- 'unit_price': 450,
104
- 'total_price': 225000,
105
- 'resource_type': 'مواد'
106
- },
107
- {
108
- 'id': 5,
109
- 'project_id': 2,
110
- 'code': 'B-002',
111
- 'description': 'أعمال الهيكل الخرساني',
112
- 'unit': 'م3',
113
- 'quantity': 1200,
114
- 'unit_price': 550,
115
- 'total_price': 660000,
116
- 'resource_type': 'مواد'
117
- }
118
- ]
119
-
120
- if 'next_boq_item_id' not in st.session_state:
121
- st.session_state.next_boq_item_id = len(st.session_state.boq_items) + 1
122
-
123
- if 'show_new_boq_item_form' not in st.session_state:
124
- st.session_state.show_new_boq_item_form = False
125
-
126
- if 'show_edit_boq_item_form' not in st.session_state:
127
- st.session_state.show_edit_boq_item_form = False
128
-
129
- if 'edit_boq_item_id' not in st.session_state:
130
- st.session_state.edit_boq_item_id = None
131
-
132
- if 'show_resource_selector' not in st.session_state:
133
- st.session_state.show_resource_selector = False
134
-
135
- if 'selected_resource_type' not in st.session_state:
136
- st.session_state.selected_resource_type = "المواد"
137
-
138
- # تهيئة معامل تعديل التسعير الغير متزن
139
- if 'unbalanced_pricing_factors' not in st.session_state:
140
- st.session_state.unbalanced_pricing_factors = {
141
- 'early_items_factor': 1.15, # زيادة أسعار البنود المبكرة بنسبة 15%
142
- 'late_items_factor': 0.90, # تخفيض أسعار البنود المتأخرة بنسبة 10%
143
- 'custom_factors': {} # معاملات مخصصة لبنود محددة
144
- }
145
-
146
- # تهيئة حالة حفظ التسعير
147
- if 'saved_pricing' not in st.session_state:
148
- st.session_state.saved_pricing = []
149
-
150
- # تهيئة حالة تحليل سعر البند
151
- if 'item_analysis_edited' not in st.session_state:
152
- st.session_state.item_analysis_edited = False
153
-
154
- # تهيئة قائمة الوحدات المتاحة لتحليل الأسعار
155
  self.unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"]
156
 
157
- # تهيئة فئات التكاليف لتحليل الأسعار
158
  self.cost_categories = [
159
  "مواد",
160
  "عمالة",
@@ -164,1121 +22,60 @@ class IntegratedPricingApp:
164
  "أرباح"
165
  ]
166
 
167
- # تهيئة قائمة تحليل أسعار البنود
168
  if 'items_price_analysis' not in st.session_state:
169
  st.session_state.items_price_analysis = {}
170
-
171
- # تهيئة بيانات المحتوى المحلي
172
- if 'local_content' not in st.session_state:
173
- st.session_state.local_content = {}
174
-
175
- # تهيئة بيانات المخاطر
176
- if 'risk_analysis' not in st.session_state:
177
- st.session_state.risk_analysis = {}
178
-
179
- # تهيئة مرحلة العمل الحالية
180
- if 'current_stage' not in st.session_state:
181
- st.session_state.current_stage = 1
182
-
183
  def render(self):
184
- """طريقة للتوافق مع الواجهة القديمة"""
185
- self.run()
186
-
187
- def run(self):
188
- """تشغيل وحدة التسعير المتكاملة"""
189
- st.title("وحدة التسعير المتكاملة")
190
-
191
- # عرض زر إنشاء تسعير جديد
192
- col1, col2, col3 = st.columns([1, 2, 1])
193
- with col2:
194
- if st.button("➕ إنشاء تسعير جديد", key="create_new_pricing_btn", type="primary"):
195
- st.session_state.show_new_project_form = True
196
-
197
- # عرض نموذج إنشاء تسعير جديد
198
- if st.session_state.show_new_project_form:
199
- self._render_new_project_form()
200
-
201
- # عرض نموذج تعديل المشروع
202
- if st.session_state.show_edit_project_form and st.session_state.edit_project_id is not None:
203
- self._render_edit_project_form()
204
-
205
- # عرض قائمة المشاريع
206
- self._render_projects_list()
207
-
208
- # عرض تفاصيل المشروع الحالي
209
- if st.session_state.current_project:
210
- project_info = self._get_current_project_info()
211
- if project_info:
212
- self._render_project_info(project_info)
213
-
214
- # عرض مراحل العمل
215
- self._render_workflow_stages(project_info)
216
-
217
- def _render_workflow_stages(self, project_info):
218
- """عرض مراحل العمل"""
219
- # تعريف المراحل
220
- stages = [
221
- "إنشاء تسعير جديد",
222
- "تحليل الأسعار",
223
- "المحتوى المحلي",
224
- "تحليل المخاطر",
225
- "جدول الكميات النهائي والتصدير"
226
- ]
227
-
228
- # عرض شريط التقدم
229
- st.markdown("### مراحل العمل")
230
- progress_cols = st.columns(len(stages))
231
-
232
- for i, stage in enumerate(stages):
233
- with progress_cols[i]:
234
- if i + 1 < st.session_state.current_stage:
235
- st.markdown(f"<div style='text-align: center; background-color: #0068c9; color: white; padding: 10px; border-radius: 5px;'>{stage} ✓</div>", unsafe_allow_html=True)
236
- elif i + 1 == st.session_state.current_stage:
237
- st.markdown(f"<div style='text-align: center; background-color: #ff4b4b; color: white; padding: 10px; border-radius: 5px;'>{stage} ⟳</div>", unsafe_allow_html=True)
238
- else:
239
- st.markdown(f"<div style='text-align: center; background-color: #f0f2f6; color: gray; padding: 10px; border-radius: 5px;'>{stage}</div>", unsafe_allow_html=True)
240
-
241
- # عرض المحتوى حسب المرحلة الحالية
242
- if st.session_state.current_stage == 1:
243
- # المرحلة الأولى: إنشاء تسعير جديد
244
- self._render_stage_1_new_pricing(project_info)
245
- elif st.session_state.current_stage == 2:
246
- # المرحلة الثانية: تحليل الأسعار
247
- self._render_stage_2_price_analysis(project_info)
248
- elif st.session_state.current_stage == 3:
249
- # المرحلة الثالثة: المحتوى المحلي
250
- self._render_stage_3_local_content(project_info)
251
- elif st.session_state.current_stage == 4:
252
- # المرحلة الرابعة: تحليل المخاطر
253
- self._render_stage_4_risk_analysis(project_info)
254
- elif st.session_state.current_stage == 5:
255
- # المرحلة الخامسة: جدول الكميات النهائي والتصدير
256
- self._render_stage_5_final_boq(project_info)
257
-
258
- def _render_stage_1_new_pricing(self, project_info):
259
- """عرض المرحلة الأولى: إنشاء تسعير جديد"""
260
- st.markdown("## المرحلة الأولى: إنشاء تسعير جديد")
261
-
262
- # عرض جدول الكميات الأولي
263
- self._render_bill_of_quantities()
264
-
265
- # زر الانتقال للمرحلة التالية
266
- col1, col2, col3 = st.columns([2, 1, 2])
267
- with col2:
268
- if st.button("الانتقال للمرحلة التالية ⟶", key="next_stage_1", type="primary"):
269
- # التحقق من وجود بنود في جدول الكميات
270
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
271
- if not project_items:
272
- st.error("يجب إضافة بند واحد على الأقل في جدول الكميات قبل الانتقال للمرحلة التالية")
273
- else:
274
- st.session_state.current_stage = 2
275
- st.rerun()
276
-
277
- def _render_stage_2_price_analysis(self, project_info):
278
- """عرض المرحلة الثانية: تحليل الأسعار"""
279
- st.markdown("## المرحلة الثانية: تحليل الأسعار")
280
-
281
- # الحصول على بنود المشروع الحالي
282
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
283
-
284
- if not project_items:
285
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
286
- return
287
-
288
- # اختيار البند للتحليل
289
- selected_item_id = st.selectbox(
290
- "اختر البند للتحليل",
291
- options=[item['id'] for item in project_items],
292
- format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
293
- key="select_item_for_analysis_stage2"
294
- )
295
-
296
- # الحصول على البند المحدد
297
- selected_item = next((item for item in project_items if item['id'] == selected_item_id), None)
298
-
299
- if not selected_item:
300
- st.error("لم يتم العثور على البند المحدد.")
301
- return
302
-
303
- # عرض معلومات البند
304
- col1, col2 = st.columns(2)
305
- with col1:
306
- st.write(f"**البند:** {selected_item['description']}")
307
- st.write(f"**الكود:** {selected_item['code']}")
308
- st.write(f"**الوحدة:** {selected_item['unit']}")
309
- with col2:
310
- st.write(f"**الكمية:** {selected_item['quantity']}")
311
- st.write(f"**سعر الوحدة:** {selected_item['unit_price']:,.2f} ريال")
312
- st.write(f"**السعر الإجمالي:** {selected_item['total_price']:,.2f} ريال")
313
 
314
- # إنشاء تحليل سعر البند إذا لم يكن موجوداً
315
- if selected_item_id not in st.session_state.items_price_analysis:
316
- self._create_default_price_analysis(selected_item_id, selected_item)
317
-
318
- # عرض تحليل السعر
319
- self._render_price_analysis_editor(selected_item_id, selected_item)
320
-
321
- # أزرار التنقل بين المراحل
322
- col1, col2, col3 = st.columns(3)
323
- with col1:
324
- if st.button("⟵ العودة للمرحلة السابقة", key="prev_stage_2"):
325
- st.session_state.current_stage = 1
326
- st.rerun()
327
- with col3:
328
- if st.button("الانتقال للمرحلة التالية ⟶", key="next_stage_2", type="primary"):
329
- st.session_state.current_stage = 3
330
- st.rerun()
331
-
332
- def _render_stage_3_local_content(self, project_info):
333
- """عرض المرحلة الثالثة: المحتوى المحلي"""
334
- st.markdown("## المرحلة الثالثة: المحتوى المحلي")
335
-
336
- # الحصول على بنود المشروع الحالي
337
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
338
-
339
- if not project_items:
340
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
341
  return
342
-
343
- # تهيئة بيانات المحتوى المحلي للمشروع الحالي إذا لم تكن موجودة
344
- project_id = st.session_state.current_project
345
- if project_id not in st.session_state.local_content:
346
- st.session_state.local_content[project_id] = {}
347
- for item in project_items:
348
- st.session_state.local_content[project_id][item['id']] = {
349
- 'local_materials_percentage': 50,
350
- 'local_labor_percentage': 90,
351
- 'local_equipment_percentage': 30,
352
- 'local_subcontractors_percentage': 80,
353
- 'notes': ''
354
- }
355
-
356
- # عرض جدول المحتوى المحلي
357
- st.markdown("### تحديد نسب المحتوى المحلي لكل بند")
358
-
359
- # إنشاء DataFrame للعرض والتعديل
360
- local_content_data = []
361
- for item in project_items:
362
- item_id = item['id']
363
- if item_id in st.session_state.local_content[project_id]:
364
- local_content_data.append({
365
- 'رقم البند': item_id,
366
- 'وصف البند': item['description'],
367
- 'نسبة المواد المحلية (%)': st.session_state.local_content[project_id][item_id]['local_materials_percentage'],
368
- 'نسبة العمالة المحلية (%)': st.session_state.local_content[project_id][item_id]['local_labor_percentage'],
369
- 'نسبة المعدات المحلية (%)': st.session_state.local_content[project_id][item_id]['local_equipment_percentage'],
370
- 'نسبة المقاولين المحليين (%)': st.session_state.local_content[project_id][item_id]['local_subcontractors_percentage'],
371
- 'ملاحظات': st.session_state.local_content[project_id][item_id]['notes']
372
- })
373
-
374
- # إنشاء DataFrame
375
- df = pd.DataFrame(local_content_data)
376
-
377
- # عرض الجدول للتعديل
378
- edited_df = st.data_editor(
379
- df,
380
- use_container_width=True,
381
- column_config={
382
- 'رقم البند': st.column_config.Column('رقم البند', disabled=True),
383
- 'وصف البند': st.column_config.Column('وصف البند', disabled=True),
384
- 'نسبة المواد المحلية (%)': st.column_config.NumberColumn(
385
- 'نسبة المواد المحلية (%)',
386
- min_value=0,
387
- max_value=100,
388
- step=1,
389
- format="%d"
390
- ),
391
- 'نسبة العمالة المحلية (%)': st.column_config.NumberColumn(
392
- 'نسبة العمالة المحلية (%)',
393
- min_value=0,
394
- max_value=100,
395
- step=1,
396
- format="%d"
397
- ),
398
- 'نسبة المعدات المحلية (%)': st.column_config.NumberColumn(
399
- 'نسبة المعدات المحلية (%)',
400
- min_value=0,
401
- max_value=100,
402
- step=1,
403
- format="%d"
404
- ),
405
- 'نسبة المقاولين المحليين (%)': st.column_config.NumberColumn(
406
- 'نسبة المقاولين المحليين (%)',
407
- min_value=0,
408
- max_value=100,
409
- step=1,
410
- format="%d"
411
- ),
412
- 'ملاحظات': st.column_config.TextColumn('ملاحظات')
413
- }
414
- )
415
-
416
- # تحديث البيانات في حالة التعديل
417
- if edited_df is not None and not edited_df.equals(df):
418
- for _, row in edited_df.iterrows():
419
- item_id = row['رقم البند']
420
- st.session_state.local_content[project_id][item_id] = {
421
- 'local_materials_percentage': row['نسبة المواد المحلية (%)'],
422
- 'local_labor_percentage': row['نسبة العمالة المحلية (%)'],
423
- 'local_equipment_percentage': row['نسبة المعدات المحلية (%)'],
424
- 'local_subcontractors_percentage': row['نسبة المقاولين المحليين (%)'],
425
- 'notes': row['ملاحظات']
426
- }
427
-
428
- # حساب وعرض إجمالي المحتوى المحلي للمشروع
429
- total_materials = 0
430
- total_labor = 0
431
- total_equipment = 0
432
- total_subcontractors = 0
433
-
434
- for item in project_items:
435
- item_id = item['id']
436
- if item_id in st.session_state.local_content[project_id]:
437
- total_materials += st.session_state.local_content[project_id][item_id]['local_materials_percentage']
438
- total_labor += st.session_state.local_content[project_id][item_id]['local_labor_percentage']
439
- total_equipment += st.session_state.local_content[project_id][item_id]['local_equipment_percentage']
440
- total_subcontractors += st.session_state.local_content[project_id][item_id]['local_subcontractors_percentage']
441
-
442
- # حساب المتوسط
443
- num_items = len(project_items)
444
- if num_items > 0:
445
- avg_materials = total_materials / num_items
446
- avg_labor = total_labor / num_items
447
- avg_equipment = total_equipment / num_items
448
- avg_subcontractors = total_subcontractors / num_items
449
-
450
- # حساب المتوسط الإجمالي
451
- overall_avg = (avg_materials + avg_labor + avg_equipment + avg_subcontractors) / 4
452
-
453
- # عرض الإحصائيات
454
- st.markdown("### إحصائيات المحتوى المحلي للمشروع")
455
- col1, col2, col3, col4 = st.columns(4)
456
-
457
- with col1:
458
- st.metric("متوسط المواد المحلية", f"{avg_materials:.1f}%")
459
- with col2:
460
- st.metric("متوسط العمالة المحلية", f"{avg_labor:.1f}%")
461
- with col3:
462
- st.metric("متوسط المعدات المحلية", f"{avg_equipment:.1f}%")
463
- with col4:
464
- st.metric("متوسط المقاولين المحليين", f"{avg_subcontractors:.1f}%")
465
-
466
- # عرض المتوسط الإجمالي
467
- st.metric("متوسط المحتوى المحلي الإجمالي للمشروع", f"{overall_avg:.1f}%")
468
 
469
- # أزرار التنقل بين المراحل
470
- col1, col2, col3 = st.columns(3)
471
- with col1:
472
- if st.button("⟵ العودة للمرحلة السابقة", key="prev_stage_3"):
473
- st.session_state.current_stage = 2
474
- st.rerun()
475
- with col3:
476
- if st.button("الانتقال للمرحلة التالية ⟶", key="next_stage_3", type="primary"):
477
- st.session_state.current_stage = 4
478
- st.rerun()
479
-
480
- def _render_stage_4_risk_analysis(self, project_info):
481
- """عرض المرحلة الرابعة: تحليل المخاطر"""
482
- st.markdown("## المرحلة الرابعة: تحليل المخاطر")
483
 
484
- # تهيئة بيانات المخاطر للمشروع الحالي إذا لم تكن موجودة
485
- project_id = st.session_state.current_project
486
- if project_id not in st.session_state.risk_analysis:
487
- st.session_state.risk_analysis[project_id] = {
488
- 'risks': [
489
- {
490
- 'id': 1,
491
- 'description': 'تأخر توريد المواد',
492
- 'probability': 'متوسطة',
493
- 'impact': 'عالي',
494
- 'mitigation': 'التعاقد مع موردين بديلين',
495
- 'contingency_percentage': 5
496
- },
497
- {
498
- 'id': 2,
499
- 'description': 'تغير أسعار المواد',
500
- 'probability': 'عالية',
501
- 'impact': 'عالي',
502
- 'mitigation': 'إضافة بند تعديل الأسعار في العقد',
503
- 'contingency_percentage': 8
504
- },
505
- {
506
- 'id': 3,
507
- 'description': 'ظروف جوية غير مواتية',
508
- 'probability': 'منخفضة',
509
- 'impact': 'متوسط',
510
- 'mitigation': 'جدولة الأعمال الخارجية في المواسم المناسبة',
511
- 'contingency_percentage': 3
512
- }
513
- ],
514
- 'overall_contingency': 10
515
- }
516
-
517
- # عرض جدول المخاطر
518
- st.markdown("### تحليل المخاطر وخطط التخفيف")
519
-
520
- # إنشاء DataFrame للعرض والتعديل
521
- risk_data = []
522
- for risk in st.session_state.risk_analysis[project_id]['risks']:
523
- risk_data.append({
524
- 'الرقم': risk['id'],
525
- 'وصف المخاطرة': risk['description'],
526
- 'احتمالية الحدوث': risk['probability'],
527
- 'التأثير': risk['impact'],
528
- 'إجراءات التخفيف': risk['mitigation'],
529
- 'نسبة الطوارئ (%)': risk['contingency_percentage']
530
- })
531
-
532
- # إنشاء DataFrame
533
- df = pd.DataFrame(risk_data)
534
 
535
- # عرض الجدول للتعديل
536
- edited_df = st.data_editor(
537
- df,
538
- use_container_width=True,
539
- num_rows="dynamic",
540
- column_config={
541
- 'الرقم': st.column_config.Column('الرقم', disabled=True),
542
- 'وصف المخاطرة': st.column_config.TextColumn('وصف المخاطرة'),
543
- 'احتمالية الحدوث': st.column_config.SelectboxColumn(
544
- 'احتمالية الحدوث',
545
- options=['منخفضة', 'متوسطة', 'عالية']
546
- ),
547
- 'التأثير': st.column_config.SelectboxColumn(
548
- 'التأثير',
549
- options=['منخفض', 'متوسط', 'عالي']
550
- ),
551
- 'إجراءات التخفيف': st.column_config.TextColumn('إجراءات التخفيف'),
552
- 'نسبة الطوارئ (%)': st.column_config.NumberColumn(
553
- 'نسبة الطوارئ (%)',
554
- min_value=0,
555
- max_value=100,
556
- step=0.5,
557
- format="%.1f"
558
- )
559
- }
560
- )
561
-
562
- # تحديث البيانات في حالة التعديل
563
- if edited_df is not None and not edited_df.equals(df):
564
- # حذف المخاطر الحالية
565
- st.session_state.risk_analysis[project_id]['risks'] = []
566
-
567
- # إضافة المخاطر المعدلة
568
- next_risk_id = 1
569
- for _, row in edited_df.iterrows():
570
- st.session_state.risk_analysis[project_id]['risks'].append({
571
- 'id': next_risk_id,
572
- 'description': row['وصف المخاطرة'],
573
- 'probability': row['احتمالية الحدوث'],
574
- 'impact': row['التأثير'],
575
- 'mitigation': row['إجراءات التخفيف'],
576
- 'contingency_percentage': row['نسبة الطوارئ (%)']
577
- })
578
- next_risk_id += 1
579
-
580
- # تعديل نسبة الطوارئ الإجمالية
581
- st.markdown("### نسبة الطوارئ الإجمالية للمشروع")
582
- overall_contingency = st.slider(
583
- "نسبة الطوارئ الإجمالية (%)",
584
- min_value=0.0,
585
- max_value=30.0,
586
- value=float(st.session_state.risk_analysis[project_id]['overall_contingency']),
587
- step=0.5,
588
- format="%.1f"
589
  )
590
 
591
- # تحديث نسبة الطوارئ الإجمالية
592
- if overall_contingency != st.session_state.risk_analysis[project_id]['overall_contingency']:
593
- st.session_state.risk_analysis[project_id]['overall_contingency'] = overall_contingency
594
-
595
- # حساب و��رض تأثير المخاطر على التكلفة
596
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
597
- total_price = sum(item['total_price'] for item in project_items)
598
- contingency_amount = total_price * (overall_contingency / 100)
599
-
600
- col1, col2, col3 = st.columns(3)
601
- with col1:
602
- st.metric("إجمالي تكلفة المشروع", f"{total_price:,.2f} ريال")
603
- with col2:
604
- st.metric("مبلغ الطوارئ", f"{contingency_amount:,.2f} ريال")
605
- with col3:
606
- st.metric("التكلفة الإجمالية مع الطوارئ", f"{(total_price + contingency_amount):,.2f} ريال")
607
-
608
- # أزرار التنقل بين المراحل
609
- col1, col2, col3 = st.columns(3)
610
- with col1:
611
- if st.button("⟵ العودة للمرحلة السابقة", key="prev_stage_4"):
612
- st.session_state.current_stage = 3
613
- st.rerun()
614
- with col3:
615
- if st.button("الانتقال للمرحلة التالية ⟶", key="next_stage_4", type="primary"):
616
- st.session_state.current_stage = 5
617
- st.rerun()
618
-
619
- def _render_stage_5_final_boq(self, project_info):
620
- """عرض المرحلة الخامسة: جدول الكميات النهائي والتصدير"""
621
- st.markdown("## المرحلة الخامسة: جدول الكميات النهائي والتصدير")
622
-
623
- # الحصول على بنود المشروع الحالي
624
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
625
-
626
- if not project_items:
627
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
628
- return
629
-
630
- # حساب إجمالي تكلفة المشروع
631
- total_price = sum(item['total_price'] for item in project_items)
632
-
633
- # حساب مبلغ الطوارئ
634
- project_id = st.session_state.current_project
635
- if project_id in st.session_state.risk_analysis:
636
- contingency_percentage = st.session_state.risk_analysis[project_id]['overall_contingency']
637
- else:
638
- contingency_percentage = 10 # قيمة افتراضية
639
-
640
- contingency_amount = total_price * (contingency_percentage / 100)
641
-
642
- # إنشاء DataFrame للعرض النهائي
643
- final_boq_data = []
644
- for item in project_items:
645
- final_boq_data.append({
646
- 'الكود': item['code'],
647
- 'وصف البند': item['description'],
648
- 'الوحدة': item['unit'],
649
- 'الكمية': item['quantity'],
650
- 'سعر الوحدة': item['unit_price'],
651
- 'السعر الإجمالي': item['total_price']
652
- })
653
-
654
- # إنشاء DataFrame
655
- df = pd.DataFrame(final_boq_data)
656
-
657
- # عرض جدول الكميات النهائي
658
- st.markdown("### جدول الكميات النهائي")
659
- st.dataframe(
660
- df,
661
- use_container_width=True,
662
- column_config={
663
- 'الكود': st.column_config.Column('الكود'),
664
- 'وصف البند': st.column_config.Column('وصف البند'),
665
- 'الوحدة': st.column_config.Column('الوحدة'),
666
- 'الكمية': st.column_config.NumberColumn('الكمية', format="%.2f"),
667
- 'سعر الوحدة': st.column_config.NumberColumn('سعر الوحدة', format="%.2f ريال"),
668
- 'السعر الإجمالي': st.column_config.NumberColumn('السعر الإجمالي', format="%.2f ريال")
669
- }
670
- )
671
-
672
- # عرض ملخص التكلفة
673
- st.markdown("### ملخص التكلفة")
674
- col1, col2, col3 = st.columns(3)
675
- with col1:
676
- st.metric("إجمالي تكلفة المشروع", f"{total_price:,.2f} ريال")
677
- with col2:
678
- st.metric("مبلغ الطوارئ", f"{contingency_amount:,.2f} ريال")
679
- with col3:
680
- st.metric("التكلفة الإجمالية مع الطوارئ", f"{(total_price + contingency_amount):,.2f} ريال")
681
-
682
- # أزرار التصدير
683
- st.markdown("### تصدير البيانات")
684
- col1, col2, col3 = st.columns(3)
685
-
686
- with col1:
687
- if st.button("تصدير جدول الكميات (CSV)", key="export_csv_btn", use_container_width=True):
688
- # إنشاء CSV للتصدير
689
- csv = df.to_csv(index=False)
690
- b64 = base64.b64encode(csv.encode()).decode()
691
- href = f'<a href="data:file/csv;base64,{b64}" download="boq_{st.session_state.current_project}.csv">تحميل CSV</a>'
692
- st.markdown(href, unsafe_allow_html=True)
693
-
694
- with col2:
695
- if st.button("تصدير تقرير المشروع (PDF)", key="export_pdf_btn", use_container_width=True):
696
- st.info("جاري إعداد تقرير PDF...")
697
- # هنا يمكن إضافة كود لإنشاء ملف PDF
698
-
699
- with col3:
700
- if st.button("حفظ التسعير النهائي", key="save_final_pricing_btn", use_container_width=True, type="primary"):
701
- # تحديث حالة المشروع
702
- for i, p in enumerate(st.session_state.projects):
703
- if p['id'] == st.session_state.current_project:
704
- st.session_state.projects[i]['status'] = 'تم التسعير'
705
- break
706
-
707
- st.success("تم حفظ التسعير النهائي بنجاح!")
708
- time.sleep(1)
709
- st.rerun()
710
-
711
- # أزرار التنقل بين المراحل
712
- col1, col2 = st.columns(2)
713
- with col1:
714
- if st.button("⟵ العودة للمرحلة السابقة", key="prev_stage_5"):
715
- st.session_state.current_stage = 4
716
- st.rerun()
717
-
718
- def _render_new_project_form(self):
719
- """عرض نموذج إنشاء مشروع جديد"""
720
- st.subheader("إنشاء تسعير جديد")
721
-
722
- with st.form(key="new_project_form"):
723
- name = st.text_input("اسم المشروع", key="new_project_name")
724
- client = st.text_input("العميل", key="new_project_client")
725
- estimated_value = st.number_input("القيمة التقديرية", min_value=0.0, format="%f", key="new_project_value")
726
- deadline = st.date_input("الموعد النهائي", key="new_project_deadline")
727
- pricing_type = st.selectbox(
728
- "نوع التسعير",
729
- ["قياسي", "غير متزن", "موجه ربحية", "تنافسي", "استراتيجي"],
730
- key="new_project_pricing_type"
731
- )
732
-
733
- col1, col2 = st.columns(2)
734
- with col1:
735
- submit_button = st.form_submit_button("حفظ")
736
- with col2:
737
- cancel_button = st.form_submit_button("إلغاء")
738
-
739
- if submit_button:
740
- if name and client and estimated_value > 0:
741
- new_project = {
742
- 'id': st.session_state.next_project_id,
743
- 'name': name,
744
- 'client': client,
745
- 'estimated_value': estimated_value,
746
- 'deadline': deadline.strftime("%Y-%m-%d"),
747
- 'status': 'قيد التسعير',
748
- 'created_at': datetime.now().strftime("%Y-%m-%d"),
749
- 'pricing_type': pricing_type
750
- }
751
-
752
- st.session_state.projects.append(new_project)
753
- st.session_state.current_project = new_project['id']
754
- st.session_state.next_project_id += 1
755
- st.session_state.show_new_project_form = False
756
- st.session_state.current_stage = 1 # إعادة تعيين المرحلة الحالية
757
- st.rerun()
758
-
759
- if cancel_button:
760
- st.session_state.show_new_project_form = False
761
- st.rerun()
762
-
763
- def _render_edit_project_form(self):
764
- """عرض نموذج تعديل المشروع"""
765
- project = None
766
- for p in st.session_state.projects:
767
- if p['id'] == st.session_state.edit_project_id:
768
- project = p
769
- break
770
-
771
- if not project:
772
- st.session_state.show_edit_project_form = False
773
- st.rerun()
774
- return
775
-
776
- st.subheader(f"تعديل المشروع: {project['name']}")
777
-
778
- with st.form(key="edit_project_form"):
779
- name = st.text_input("اسم المشروع", value=project['name'], key="edit_project_name")
780
- client = st.text_input("العميل", value=project['client'], key="edit_project_client")
781
- estimated_value = st.number_input(
782
- "القيمة التقديرية",
783
- min_value=0.0,
784
- value=float(project['estimated_value']),
785
- format="%f",
786
- key="edit_project_value"
787
- )
788
- deadline = st.date_input(
789
- "الموعد النهائي",
790
- value=datetime.strptime(project['deadline'], "%Y-%m-%d").date(),
791
- key="edit_project_deadline"
792
- )
793
- status = st.selectbox(
794
- "الحالة",
795
- ["قيد التسعير", "تم التسعير", "تم التقديم", "فائز", "خاسر"],
796
- index=["قيد التسعير", "تم التسعير", "تم التقديم", "فائز", "خاسر"].index(project['status']),
797
- key="edit_project_status"
798
- )
799
- pricing_type = st.selectbox(
800
- "نوع التسعير",
801
- ["قياسي", "غير متزن", "موجه ربحية", "تنافسي", "استراتيجي"],
802
- index=["قياسي", "غير متزن", "موجه ربحية", "تنافسي", "استراتيجي"].index(project.get('pricing_type', 'قياسي')),
803
- key="edit_project_pricing_type"
804
- )
805
 
 
806
  col1, col2, col3 = st.columns(3)
807
- with col1:
808
- submit_button = st.form_submit_button("حفظ")
809
- with col2:
810
- cancel_button = st.form_submit_button("إلغاء")
811
- with col3:
812
- delete_button = st.form_submit_button("حذف المشروع", type="primary")
813
-
814
- if submit_button:
815
- if name and client and estimated_value > 0:
816
- for i, p in enumerate(st.session_state.projects):
817
- if p['id'] == st.session_state.edit_project_id:
818
- st.session_state.projects[i]['name'] = name
819
- st.session_state.projects[i]['client'] = client
820
- st.session_state.projects[i]['estimated_value'] = estimated_value
821
- st.session_state.projects[i]['deadline'] = deadline.strftime("%Y-%m-%d")
822
- st.session_state.projects[i]['status'] = status
823
- st.session_state.projects[i]['pricing_type'] = pricing_type
824
- break
825
-
826
- st.session_state.show_edit_project_form = False
827
- st.rerun()
828
-
829
- if cancel_button:
830
- st.session_state.show_edit_project_form = False
831
- st.rerun()
832
 
833
- if delete_button:
834
- for i, p in enumerate(st.session_state.projects):
835
- if p['id'] == st.session_state.edit_project_id:
836
- st.session_state.projects.pop(i)
837
- break
838
-
839
- # حذف بنود جدول الكميات المرتبطة بالمشروع
840
- st.session_state.boq_items = [item for item in st.session_state.boq_items if item['project_id'] != st.session_state.edit_project_id]
841
-
842
- st.session_state.show_edit_project_form = False
843
- if st.session_state.projects:
844
- st.session_state.current_project = st.session_state.projects[0]['id']
845
- else:
846
- st.session_state.current_project = None
847
-
848
- st.rerun()
849
-
850
- def _render_projects_list(self):
851
- """عرض قائمة المشاريع"""
852
- st.subheader("قائمة المشاريع")
853
-
854
- if not st.session_state.projects:
855
- st.info("لا توجد مشاريع. قم بإنشاء مشروع جديد للبدء.")
856
- return
857
-
858
- # إنشاء DataFrame من قائمة المشاريع
859
- df = pd.DataFrame(st.session_state.projects)
860
- if len(df) > 0 and 'id' in df.columns:
861
- df = df[['id', 'name', 'client', 'estimated_value', 'deadline', 'status', 'pricing_type']]
862
- df.columns = ['الرقم', 'اسم المشروع', 'العميل', 'القيمة التقديرية', 'الموعد النهائي', 'الحالة', 'نوع التسعير']
863
-
864
- # تنسيق القيمة التقديرية
865
- df['القيمة التقديرية'] = df['القيمة التقديرية'].apply(lambda x: f"{x:,.2f} ريال")
866
-
867
- # عرض الجدول
868
- st.dataframe(df, use_container_width=True)
869
-
870
- # اختيار المشروع
871
- col1, col2 = st.columns(2)
872
  with col1:
873
- project_ids = [p['id'] for p in st.session_state.projects]
874
- if st.session_state.current_project in project_ids:
875
- current_index = project_ids.index(st.session_state.current_project)
876
- else:
877
- current_index = 0 if project_ids else None
878
-
879
- if current_index is not None and project_ids:
880
- selected_project_id = st.selectbox(
881
- "اختر المشروع",
882
- options=project_ids,
883
- format_func=lambda x: next((p['name'] for p in st.session_state.projects if p['id'] == x), ""),
884
- index=current_index,
885
- key="select_project"
886
- )
887
-
888
- if selected_project_id != st.session_state.current_project:
889
- st.session_state.current_project = selected_project_id
890
- st.rerun()
891
-
892
- with col2:
893
- if st.button("تعديل المشروع", key="edit_project_btn"):
894
- st.session_state.edit_project_id = st.session_state.current_project
895
- st.session_state.show_edit_project_form = True
896
- st.rerun()
897
- else:
898
- st.info("لا توجد مشاريع. قم بإنشاء مشروع جديد للبدء.")
899
-
900
- def _get_current_project_info(self):
901
- """الحصول على معلومات المشروع الحالي"""
902
- for project in st.session_state.projects:
903
- if project['id'] == st.session_state.current_project:
904
- return project
905
- return None
906
-
907
- def _render_project_info(self, project):
908
- """عرض معلومات المشروع"""
909
- st.header(f"تسعير: {project['name']}")
910
-
911
- col1, col2, col3, col4 = st.columns(4)
912
- with col1:
913
- st.metric("العميل", project['client'])
914
- with col2:
915
- st.metric("القيمة التقديرية", f"{project['estimated_value']:,.2f} ريال")
916
- with col3:
917
- st.metric("الموعد النهائي", project['deadline'])
918
- with col4:
919
- st.metric("نوع التسعير", project['pricing_type'])
920
 
921
- def _render_bill_of_quantities(self):
922
- """عرض جدول الكميات"""
923
- st.subheader("جدول الكميات")
924
-
925
- # زر إضافة بند جديد
926
- col1, col2, col3 = st.columns([1, 1, 2])
927
- with col1:
928
- if st.button("➕ إضافة بند جديد", key="add_boq_item_btn"):
929
- st.session_state.show_new_boq_item_form = True
930
- st.session_state.show_resource_selector = False
931
-
932
- with col2:
933
- if st.button("📋 سحب من الموارد", key="add_from_resources_btn"):
934
- st.session_state.show_resource_selector = True
935
- st.session_state.show_new_boq_item_form = False
936
-
937
- # عرض نموذج إضافة بند جديد
938
- if st.session_state.show_new_boq_item_form:
939
- self._render_new_boq_item_form()
940
-
941
- # عرض نموذج تعديل البند
942
- if st.session_state.show_edit_boq_item_form and st.session_state.edit_boq_item_id is not None:
943
- self._render_edit_boq_item_form()
944
-
945
- # عرض محدد الموارد
946
- if st.session_state.show_resource_selector:
947
- self._render_resource_selector()
948
-
949
- # الحصول على بنود المشروع الحالي
950
- project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
951
-
952
- if not project_items:
953
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود للبدء.")
954
- return
955
-
956
- # إنشاء DataFrame من بنود المشروع
957
- df = pd.DataFrame(project_items)
958
- df = df[['id', 'code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
959
-
960
- # تحويل الجدول إلى جدول قابل للتعديل
961
- edited_df = st.data_editor(
962
- df,
963
- column_config={
964
- "id": st.column_config.Column("الرقم", disabled=True),
965
- "code": st.column_config.Column("الكود"),
966
- "description": st.column_config.Column("الوصف"),
967
- "unit": st.column_config.Column("الوحدة"),
968
- "quantity": st.column_config.NumberColumn("الكمية", min_value=0.0, format="%.2f", step=0.1),
969
- "unit_price": st.column_config.NumberColumn("سعر الوحدة", min_value=0.0, format="%.2f ريال", step=0.1),
970
- "total_price": st.column_config.NumberColumn("السعر الإجمالي", format="%.2f ريال", disabled=True),
971
- "resource_type": st.column_config.SelectboxColumn("نوع المورد", options=["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"])
972
- },
973
- use_container_width=True,
974
- key="edit_boq_items"
975
- )
976
-
977
- # تحديث البيانات في حالة التعديل
978
- if edited_df is not None and not edited_df.equals(df):
979
- for i, row in edited_df.iterrows():
980
- item_id = row['id']
981
- for j, item in enumerate(st.session_state.boq_items):
982
- if item['id'] == item_id:
983
- st.session_state.boq_items[j]['code'] = row['code']
984
- st.session_state.boq_items[j]['description'] = row['description']
985
- st.session_state.boq_items[j]['unit'] = row['unit']
986
- st.session_state.boq_items[j]['quantity'] = row['quantity']
987
- st.session_state.boq_items[j]['unit_price'] = row['unit_price']
988
- st.session_state.boq_items[j]['total_price'] = row['quantity'] * row['unit_price']
989
- st.session_state.boq_items[j]['resource_type'] = row['resource_type']
990
- break
991
-
992
- st.success("تم تحديث جدول الكميات بنجاح")
993
- st.rerun()
994
-
995
- # حساب المجموع الكلي
996
- total_price = sum(item['total_price'] for item in project_items)
997
- st.metric("إجمالي جدول الكميات", f"{total_price:,.2f} ريال")
998
-
999
- # أزرار التصدير والتعديل
1000
- col1, col2 = st.columns(2)
1001
- with col1:
1002
- if st.button("تصدير جدول الكميات", key="export_boq_btn_1"):
1003
- # إنشاء CSV للتصدير
1004
- export_df = pd.DataFrame(project_items)
1005
- export_df = export_df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'resource_type']]
1006
- export_df.columns = ['الكود', 'الوصف', 'الوحدة', 'الكمية', 'سعر الوحدة', 'السعر الإجمالي', 'نوع المورد']
1007
-
1008
- csv = export_df.to_csv(index=False)
1009
- b64 = base64.b64encode(csv.encode()).decode()
1010
- href = f'<a href="data:file/csv;base64,{b64}" download="boq_{st.session_state.current_project}.csv">تحميل CSV</a>'
1011
- st.markdown(href, unsafe_allow_html=True)
1012
-
1013
- with col2:
1014
- if len(project_items) > 0:
1015
- selected_item_id = st.selectbox(
1016
- "اختر بند للتعديل",
1017
- options=[item['id'] for item in project_items],
1018
- format_func=lambda x: next((item['description'] for item in project_items if item['id'] == x), ""),
1019
- key="select_boq_item"
1020
- )
1021
-
1022
- if st.button("تعديل البند", key="edit_boq_item_btn"):
1023
- st.session_state.edit_boq_item_id = selected_item_id
1024
- st.session_state.show_edit_boq_item_form = True
1025
- st.rerun()
1026
-
1027
- def _render_new_boq_item_form(self):
1028
- """عرض نموذج إضافة بند جديد"""
1029
- st.subheader("إضافة بند جديد")
1030
-
1031
- with st.form(key="new_boq_item_form"):
1032
- code = st.text_input("الكود", key="new_boq_item_code")
1033
- description = st.text_input("الوصف", key="new_boq_item_description")
1034
-
1035
- col1, col2 = st.columns(2)
1036
- with col1:
1037
- unit = st.text_input("الوحدة", key="new_boq_item_unit")
1038
  with col2:
1039
- resource_type = st.selectbox(
1040
- "نوع المورد",
1041
- ["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"],
1042
- key="new_boq_item_resource_type"
1043
- )
1044
-
1045
- col3, col4 = st.columns(2)
1046
- with col3:
1047
- quantity = st.number_input("الكمية", min_value=0.0, format="%f", key="new_boq_item_quantity")
1048
- with col4:
1049
- unit_price = st.number_input("سعر الوحدة", min_value=0.0, format="%f", key="new_boq_item_unit_price")
1050
-
1051
- total_price = quantity * unit_price
1052
- st.metric("السعر الإجمالي", f"{total_price:,.2f} ريال")
1053
-
1054
- col5, col6 = st.columns(2)
1055
- with col5:
1056
- submit_button = st.form_submit_button("حفظ")
1057
- with col6:
1058
- cancel_button = st.form_submit_button("إلغاء")
1059
-
1060
- if submit_button:
1061
- if code and description and unit and quantity > 0 and unit_price > 0:
1062
- new_item = {
1063
- 'id': st.session_state.next_boq_item_id,
1064
- 'project_id': st.session_state.current_project,
1065
- 'code': code,
1066
- 'description': description,
1067
- 'unit': unit,
1068
- 'quantity': quantity,
1069
- 'unit_price': unit_price,
1070
- 'total_price': total_price,
1071
- 'resource_type': resource_type
1072
- }
1073
-
1074
- st.session_state.boq_items.append(new_item)
1075
- st.session_state.next_boq_item_id += 1
1076
- st.session_state.show_new_boq_item_form = False
1077
- st.rerun()
1078
-
1079
- if cancel_button:
1080
- st.session_state.show_new_boq_item_form = False
1081
- st.rerun()
1082
 
1083
- def _render_edit_boq_item_form(self):
1084
- """عرض نموذج تعديل البند"""
1085
- item = None
1086
- for i in st.session_state.boq_items:
1087
- if i['id'] == st.session_state.edit_boq_item_id:
1088
- item = i
1089
- break
1090
-
1091
- if not item:
1092
- st.session_state.show_edit_boq_item_form = False
1093
- st.rerun()
1094
- return
1095
-
1096
- st.subheader(f"تعديل البند: {item['description']}")
1097
-
1098
- with st.form(key="edit_boq_item_form"):
1099
- code = st.text_input("الكود", value=item['code'], key="edit_boq_item_code")
1100
- description = st.text_input("الوصف", value=item['description'], key="edit_boq_item_description")
1101
-
1102
- col1, col2 = st.columns(2)
1103
- with col1:
1104
- unit = st.text_input("الوحدة", value=item['unit'], key="edit_boq_item_unit")
1105
- with col2:
1106
- resource_type = st.selectbox(
1107
- "نوع المورد",
1108
- ["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"],
1109
- index=["مواد", "عمالة", "معدات", "مقاولين من الباطن", "أخرى"].index(item['resource_type']) if 'resource_type' in item else 0,
1110
- key="edit_boq_item_resource_type"
1111
- )
1112
-
1113
- col3, col4 = st.columns(2)
1114
  with col3:
1115
- quantity = st.number_input("الكمية", min_value=0.0, value=float(item['quantity']), format="%f", key="edit_boq_item_quantity")
1116
- with col4:
1117
- unit_price = st.number_input("سعر الوحدة", min_value=0.0, value=float(item['unit_price']), format="%f", key="edit_boq_item_unit_price")
1118
-
1119
- total_price = quantity * unit_price
1120
- st.metric("السعر الإجمالي", f"{total_price:,.2f} ريال")
1121
 
1122
- col5, col6, col7 = st.columns(3)
1123
- with col5:
1124
- submit_button = st.form_submit_button("حفظ")
1125
- with col6:
1126
- cancel_button = st.form_submit_button("إلغاء")
1127
- with col7:
1128
- delete_button = st.form_submit_button("حذف البند", type="primary")
1129
-
1130
- if submit_button:
1131
- if code and description and unit and quantity > 0 and unit_price > 0:
1132
- for i, itm in enumerate(st.session_state.boq_items):
1133
- if itm['id'] == st.session_state.edit_boq_item_id:
1134
- st.session_state.boq_items[i]['code'] = code
1135
- st.session_state.boq_items[i]['description'] = description
1136
- st.session_state.boq_items[i]['unit'] = unit
1137
- st.session_state.boq_items[i]['quantity'] = quantity
1138
- st.session_state.boq_items[i]['unit_price'] = unit_price
1139
- st.session_state.boq_items[i]['total_price'] = total_price
1140
- st.session_state.boq_items[i]['resource_type'] = resource_type
1141
- break
1142
-
1143
- st.session_state.show_edit_boq_item_form = False
1144
- st.rerun()
1145
-
1146
- if cancel_button:
1147
- st.session_state.show_edit_boq_item_form = False
1148
- st.rerun()
1149
 
1150
- if delete_button:
1151
- for i, itm in enumerate(st.session_state.boq_items):
1152
- if itm['id'] == st.session_state.edit_boq_item_id:
1153
- st.session_state.boq_items.pop(i)
1154
- break
1155
-
1156
- st.session_state.show_edit_boq_item_form = False
1157
- st.rerun()
1158
 
1159
- def _render_resource_selector(self):
1160
- """عرض محدد الموارد"""
1161
- st.subheader("سحب من الموارد المسجلة")
1162
-
1163
- # اختيار نوع المورد
1164
- resource_type = st.selectbox(
1165
- "نوع المورد",
1166
- ["المواد", "العمالة", "المعدات", "المقاولين من الباطن"],
1167
- index=["المواد", "العمالة", "المعدات", "المقاولين من الباطن"].index(st.session_state.selected_resource_type),
1168
- key="resource_type_selector"
1169
- )
1170
-
1171
- if resource_type != st.session_state.selected_resource_type:
1172
- st.session_state.selected_resource_type = resource_type
1173
- st.rerun()
1174
-
1175
- # الحصول على الموارد المناسبة
1176
- resources = []
1177
- if resource_type == "المواد" and 'materials' in st.session_state:
1178
- resources = st.session_state.materials
1179
- elif resource_type == "العمالة" and 'labor' in st.session_state:
1180
- resources = st.session_state.labor
1181
- elif resource_type == "المعدات" and 'equipment' in st.session_state:
1182
- resources = st.session_state.equipment
1183
- elif resource_type == "المقاولين من الباطن" and 'subcontractors' in st.session_state:
1184
- resources = st.session_state.subcontractors
1185
-
1186
- if not resources:
1187
- st.info(f"لا توجد موارد مسجلة من نوع {resource_type}. يرجى إضافة موارد في وحدة الموارد أولاً.")
1188
-
1189
- col1, col2 = st.columns(2)
1190
- with col2:
1191
- if st.button("إلغاء", key="cancel_resource_selector"):
1192
- st.session_state.show_resource_selector = False
1193
- st.rerun()
1194
- return
1195
-
1196
- # إنشاء DataFrame من الموارد
1197
- df = pd.DataFrame(resources)
1198
- if 'id' in df.columns and 'name' in df.columns and 'unit' in df.columns and 'price' in df.columns:
1199
- df = df[['id', 'name', 'category', 'unit', 'price']]
1200
- df.columns = ['الرقم', 'الاسم', 'الفئة', 'الوحدة', 'السعر']
1201
-
1202
- # تنسيق السعر
1203
- df['السعر'] = df['السعر'].apply(lambda x: f"{x:,.2f} ريال")
1204
-
1205
- # عرض الجدول
1206
- st.dataframe(df, use_container_width=True)
1207
-
1208
- with st.form(key="add_from_resource_form"):
1209
- col1, col2 = st.columns(2)
1210
- with col1:
1211
- selected_resource_id = st.selectbox(
1212
- "اختر المورد",
1213
- options=[r['id'] for r in resources],
1214
- format_func=lambda x: next((r['name'] for r in resources if r['id'] == x), ""),
1215
- key="select_resource"
1216
- )
1217
-
1218
- with col2:
1219
- quantity = st.number_input("الكمية", min_value=0.1, value=1.0, format="%f", key="resource_quantity")
1220
-
1221
- # الحصول على المورد المحدد
1222
- selected_resource = next((r for r in resources if r['id'] == selected_resource_id), None)
1223
- if selected_resource:
1224
- total_price = quantity * selected_resource['price']
1225
- st.metric("السعر الإجمالي", f"{total_price:,.2f} ريال")
1226
-
1227
- col3, col4 = st.columns(2)
1228
- with col3:
1229
- submit_button = st.form_submit_button("إضافة إلى جدول الكميات")
1230
- with col4:
1231
- cancel_button = st.form_submit_button("إلغاء")
1232
-
1233
- if submit_button and selected_resource and quantity > 0:
1234
- # تحويل نوع المورد إلى الصيغة المناسبة
1235
- resource_type_map = {
1236
- "المواد": "مواد",
1237
- "العمالة": "عمالة",
1238
- "المعدات": "معدات",
1239
- "المقاولين من الباطن": "مقاولين من الباطن"
1240
- }
1241
-
1242
- # إنشاء كود فريد
1243
- resource_code_prefix = {
1244
- "المواد": "M",
1245
- "العمالة": "L",
1246
- "المعدات": "E",
1247
- "المقاولين من الباطن": "S"
1248
- }
1249
-
1250
- code_prefix = resource_code_prefix.get(resource_type, "X")
1251
- code = f"{code_prefix}-{selected_resource['id']:03d}"
1252
-
1253
- # إضافة البند إلى جدول الكميات
1254
- new_item = {
1255
- 'id': st.session_state.next_boq_item_id,
1256
- 'project_id': st.session_state.current_project,
1257
- 'code': code,
1258
- 'description': selected_resource['name'],
1259
- 'unit': selected_resource['unit'],
1260
- 'quantity': quantity,
1261
- 'unit_price': selected_resource['price'],
1262
- 'total_price': quantity * selected_resource['price'],
1263
- 'resource_type': resource_type_map.get(resource_type, "أخرى"),
1264
- 'resource_id': selected_resource['id']
1265
- }
1266
-
1267
- st.session_state.boq_items.append(new_item)
1268
- st.session_state.next_boq_item_id += 1
1269
- st.session_state.show_resource_selector = False
1270
- st.rerun()
1271
-
1272
- if cancel_button:
1273
- st.session_state.show_resource_selector = False
1274
- st.rerun()
1275
- else:
1276
- st.error("تنسيق بيانات الموارد غير صحيح. يرجى التأكد من وجود الحقول المطلوبة.")
1277
-
1278
- if st.button("إلغاء", key="cancel_resource_selector_error"):
1279
- st.session_state.show_resource_selector = False
1280
- st.rerun()
1281
-
1282
  def _create_default_price_analysis(self, item_id, item):
1283
  """إنشاء تحليل سعر افتراضي للبند"""
1284
  # إنشاء قائمة مكونات تحليل السعر
@@ -1287,11 +84,11 @@ class IntegratedPricingApp:
1287
  ])
1288
 
1289
  # إضافة مكونات افتراضية بناءً على نوع البند
1290
- is_concrete = 'خرسان' in item['description']
1291
- is_steel = 'حديد' in item['description'] or 'تسليح' in item['description']
1292
- is_bricks = 'بلوك' in item['description'] or 'طوب' in item['description']
1293
- is_paint = 'دهان' in item['description'] or 'طلاء' in item['description']
1294
- is_insulation = 'عزل' in item['description']
1295
 
1296
  # إضافة المكونات بناءً على نوع البند
1297
  if is_concrete:
@@ -1360,20 +157,20 @@ class IntegratedPricingApp:
1360
  'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
1361
  'الوصف': ['مواد أساسية', 'عمالة', 'معدات ومعد مساعدة', 'مصاريف عامة', 'أرباح'],
1362
  'الكمية': [1, 1, 1, 1, 1],
1363
- 'الوحدة': [item['unit'], 'وحدة', 'وحدة', 'وحدة', 'وحدة'],
1364
  'سعر الوحدة': [
1365
- item['unit_price'] * 0.6,
1366
- item['unit_price'] * 0.2,
1367
- item['unit_price'] * 0.1,
1368
- item['unit_price'] * 0.05,
1369
- item['unit_price'] * 0.05
1370
  ],
1371
  'الإجمالي': [
1372
- item['unit_price'] * 0.6,
1373
- item['unit_price'] * 0.2,
1374
- item['unit_price'] * 0.1,
1375
- item['unit_price'] * 0.05,
1376
- item['unit_price'] * 0.05
1377
  ]
1378
  })
1379
  components = pd.concat([components, default_components], ignore_index=True)
@@ -1436,7 +233,7 @@ class IntegratedPricingApp:
1436
 
1437
  # حساب إجمالي تحليل السعر
1438
  total_analysis_price = edited_components['الإجمالي'].sum()
1439
- unit_price_from_analysis = total_analysis_price / item['quantity'] if item['quantity'] > 0 else 0
1440
 
1441
  # عرض ملخص تحليل السعر
1442
  st.markdown("#### ملخص تحليل السعر")
@@ -1451,11 +248,11 @@ class IntegratedPricingApp:
1451
 
1452
  with col3:
1453
  # المقارنة مع السعر الأصلي
1454
- diff = unit_price_from_analysis - item['unit_price']
1455
  st.metric(
1456
  "الفرق عن السعر الأصلي",
1457
  f"{diff:,.2f} ريال",
1458
- delta=f"{(diff/item['unit_price']*100) if item['unit_price'] > 0 else 0:.1f}%"
1459
  )
1460
 
1461
  # تحليل توزيع التكاليف حسب الفئة
@@ -1497,11 +294,15 @@ class IntegratedPricingApp:
1497
  with col1:
1498
  if st.button("تحديث سعر البند", use_container_width=True):
1499
  # تحديث سعر البند بناءً على تحليل السعر
1500
- for i, itm in enumerate(st.session_state.boq_items):
1501
- if itm['id'] == item_id:
1502
- st.session_state.boq_items[i]['unit_price'] = unit_price_from_analysis
1503
- st.session_state.boq_items[i]['total_price'] = unit_price_from_analysis * itm['quantity']
1504
- break
 
 
 
 
1505
 
1506
  st.success(f"تم تحديث سعر البند بناءً على تحليل السعر: {unit_price_from_analysis:,.2f} ريال")
1507
  time.sleep(0.5)
@@ -1520,89 +321,291 @@ class IntegratedPricingApp:
1520
  st.warning("تم مسح تحليل السعر للبند")
1521
  time.sleep(0.5)
1522
  st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
1523
 
1524
- # تشغيل التطبيق
1525
- def main():
1526
- # ضبط CSS لتحسين ظهور الواجهة العربية
1527
- st.set_page_config(
1528
- page_title="وحدة التسعير المتكاملة",
1529
- page_icon="💰",
1530
- layout="wide",
1531
- initial_sidebar_state="collapsed"
1532
- )
1533
 
1534
- # إضافة CSS للواجهة العربية
1535
  st.markdown("""
1536
  <style>
1537
- body {
1538
- direction: rtl;
1539
- }
1540
- .stTextInput > div > div > input {
1541
- text-align: right;
1542
- direction: rtl;
1543
- }
1544
- .stNumberInput > div > div > input {
1545
- text-align: right;
1546
- direction: rtl;
1547
- }
1548
- .stTextArea > div > div > textarea {
1549
- text-align: right;
1550
- direction: rtl;
1551
- }
1552
- .stSelectbox > div > div > div {
1553
- text-align: right;
1554
- direction: rtl;
1555
- }
1556
- .stMultiselect > div > div > div {
1557
- text-align: right;
1558
- direction: rtl;
1559
- }
1560
- .stHeader, .stSubheader, p, .stMarkdown, h1, h2, h3, h4, h5, h6 {
1561
- text-align: right;
1562
- direction: rtl;
1563
- }
1564
- button {
1565
- direction: rtl;
1566
- }
1567
- .stDataFrame {
1568
  direction: rtl;
1569
- }
1570
- .stDataFrame > div {
1571
- direction: rtl;
1572
- }
1573
- .stDataFrame th {
1574
  text-align: right;
 
1575
  }
1576
- .stDataFrame td {
1577
- text-align: right;
1578
- }
1579
- .stTabs [data-baseweb="tab-list"] {
1580
- direction: rtl;
1581
- }
1582
- .stTabs [data-baseweb="tab"] {
1583
- direction: rtl;
1584
- }
1585
- .stTabs [data-baseweb="tab-panel"] {
1586
- direction: rtl;
1587
- }
1588
- .stMetric {
1589
- direction: rtl;
1590
  text-align: right;
1591
- }
1592
- .stMetric > div {
1593
  direction: rtl;
1594
- text-align: right;
1595
  }
1596
- .stMetric label {
1597
- direction: rtl;
1598
- text-align: right;
 
 
 
1599
  }
1600
  </style>
1601
  """, unsafe_allow_html=True)
1602
 
1603
- # إنشاء وتشغيل التطبيق
1604
- app = IntegratedPricingApp()
1605
- app.run()
1606
-
1607
- if __name__ == "__main__":
1608
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import pandas as pd
3
  import numpy as np
 
 
 
 
 
 
4
  from datetime import datetime
 
 
 
5
  import time
6
 
7
+ class PriceAnalysisComponent:
8
+ """مكون تحليل الأسعار للبنود"""
9
 
10
  def __init__(self):
11
+ """تهيئة مكون تحليل الأسعار"""
12
+ # تهيئة قائمة الوحدات المتاحة
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  self.unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"]
14
 
15
+ # تهيئة فئات التكاليف
16
  self.cost_categories = [
17
  "مواد",
18
  "عمالة",
 
22
  "أرباح"
23
  ]
24
 
25
+ # تهيئة قائمة البنود وتحليل أسعارها
26
  if 'items_price_analysis' not in st.session_state:
27
  st.session_state.items_price_analysis = {}
28
+
 
 
 
 
 
 
 
 
 
 
 
 
29
  def render(self):
30
+ """عرض واجهة تحليل الأسعار"""
31
+ st.markdown("<h2 class='module-title'>تحليل أسعار البنود</h2>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ # التحقق من وجود بنود في التسعير الحالي
34
+ if 'current_pricing' not in st.session_state or 'items' not in st.session_state.current_pricing:
35
+ st.warning("ليس هناك بنود للتحليل. يرجى إنشاء تسعير أولاً.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ # الحصول على البنود من التسعير الحالي
39
+ items = st.session_state.current_pricing['items'].copy()
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ # عرض قائمة البنود
42
+ st.markdown("### قائمة البنود")
43
+ st.dataframe(items[['رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي']],
44
+ use_container_width=True, hide_index=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ # اختيار البند لتحليل السعر
47
+ selected_item_id = st.selectbox(
48
+ "اختر البند لتحليل السعر",
49
+ options=items['رقم البند'].tolist(),
50
+ format_func=lambda x: f"{x}: {items[items['رقم البند'] == x]['وصف البند'].values[0][:50]}..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  )
52
 
53
+ if selected_item_id:
54
+ # الحصول على البند المحدد
55
+ selected_item = items[items['رقم البند'] == selected_item_id].iloc[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ # عرض تفاصيل البند المختار
58
  col1, col2, col3 = st.columns(3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  with col1:
61
+ st.metric("رقم البند", selected_item['رقم البند'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  with col2:
64
+ st.metric("الكمية", f"{selected_item['الكمية']} {selected_item['الوحدة']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  with col3:
67
+ st.metric("سعر الوحدة", f"{selected_item['سعر الوحدة']:,.2f} ريال")
 
 
 
 
 
68
 
69
+ st.markdown(f"**وصف البند**: {selected_item['وصف البند']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ # إنشاء أو تحديث تحليل السعر للبند المحدد
72
+ if selected_item_id not in st.session_state.items_price_analysis:
73
+ # إنشاء تحليل سعر افتراضي
74
+ self._create_default_price_analysis(selected_item_id, selected_item)
 
 
 
 
75
 
76
+ # عرض وتحرير تحليل السعر
77
+ self._render_price_analysis_editor(selected_item_id, selected_item)
78
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  def _create_default_price_analysis(self, item_id, item):
80
  """إنشاء تحليل سعر افتراضي للبند"""
81
  # إنشاء قائمة مكونات تحليل السعر
 
84
  ])
85
 
86
  # إضافة مكونات افتراضية بناءً على نوع البند
87
+ is_concrete = 'خرسان' in item['وصف البند']
88
+ is_steel = 'حديد' in item['وصف البند'] or 'تسليح' in item['وصف البند']
89
+ is_bricks = 'بلوك' in item['وصف البند'] or 'طوب' in item['وصف البند']
90
+ is_paint = 'دهان' in item['وصف البند'] or 'طلاء' in item['وصف البند']
91
+ is_insulation = 'عزل' in item['وصف البند']
92
 
93
  # إضافة المكونات بناءً على نوع البند
94
  if is_concrete:
 
157
  'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
158
  'الوصف': ['مواد أساسية', 'عمالة', 'معدات ومعد مساعدة', 'مصاريف عامة', 'أرباح'],
159
  'الكمية': [1, 1, 1, 1, 1],
160
+ 'الوحدة': [item['الوحدة'], 'وحدة', 'وحدة', 'وحدة', 'وحدة'],
161
  'سعر الوحدة': [
162
+ item['سعر الوحدة'] * 0.6,
163
+ item['سعر الوحدة'] * 0.2,
164
+ item['سعر الوحدة'] * 0.1,
165
+ item['سعر الوحدة'] * 0.05,
166
+ item['سعر الوحدة'] * 0.05
167
  ],
168
  'الإجمالي': [
169
+ item['سعر الوحدة'] * 0.6,
170
+ item['سعر الوحدة'] * 0.2,
171
+ item['سعر الوحدة'] * 0.1,
172
+ item['سعر الوحدة'] * 0.05,
173
+ item['سعر الوحدة'] * 0.05
174
  ]
175
  })
176
  components = pd.concat([components, default_components], ignore_index=True)
 
233
 
234
  # حساب إجمالي تحليل السعر
235
  total_analysis_price = edited_components['الإجمالي'].sum()
236
+ unit_price_from_analysis = total_analysis_price / item['الكمية'] if item['الكمية'] > 0 else 0
237
 
238
  # عرض ملخص تحليل السعر
239
  st.markdown("#### ملخص تحليل السعر")
 
248
 
249
  with col3:
250
  # المقارنة مع السعر الأصلي
251
+ diff = unit_price_from_analysis - item['سعر الوحدة']
252
  st.metric(
253
  "الفرق عن السعر الأصلي",
254
  f"{diff:,.2f} ريال",
255
+ delta=f"{(diff/item['سعر الوحدة']*100) if item['سعر الوحدة'] > 0 else 0:.1f}%"
256
  )
257
 
258
  # تحليل توزيع التكاليف حسب الفئة
 
294
  with col1:
295
  if st.button("تحديث سعر البند", use_container_width=True):
296
  # تحديث سعر البند بناءً على تحليل السعر
297
+ items = st.session_state.current_pricing['items'].copy()
298
+ item_index = items[items['رقم البند'] == item_id].index[0]
299
+
300
+ # تحديث سعر الوحدة والإجمالي
301
+ items.at[item_index, 'سعر الوحدة'] = unit_price_from_analysis
302
+ items.at[item_index, 'الإجمالي'] = unit_price_from_analysis * items.at[item_index, 'الكمية']
303
+
304
+ # حفظ التعديلات في التسعير الحالي
305
+ st.session_state.current_pricing['items'] = items
306
 
307
  st.success(f"تم تحديث سعر البند بناءً على تحليل السعر: {unit_price_from_analysis:,.2f} ريال")
308
  time.sleep(0.5)
 
321
  st.warning("تم مسح تحليل السعر للبند")
322
  time.sleep(0.5)
323
  st.rerun()
324
+
325
+ def add_to_pricing_app(self, pricing_app):
326
+ """إضافة مكون تحليل الأسعار إلى تطبيق التسعير"""
327
+ # إضافة تبويب جديد
328
+ if not hasattr(pricing_app, 'tabs'):
329
+ pricing_app.tabs = []
330
+
331
+ if len(pricing_app.tabs) == 4: # إذا كان هناك 4 تبويبات فقط
332
+ pricing_app.tabs.append("تحليل أسعار البنود")
333
+
334
+ # إضافة دالة العرض
335
+ pricing_app._render_price_analysis_tab = self.render
336
 
337
+ def render_integrated_item_input():
338
+ """عرض واجهة إدخال البنود مع تحليل السعر المتكامل"""
 
 
 
 
 
 
 
339
 
340
+ # ضبط CSS لتحسين ظهور الواجهة العربية
341
  st.markdown("""
342
  <style>
343
+ input, .stTextArea textarea {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  direction: rtl;
 
 
 
 
 
345
  text-align: right;
346
+ font-family: 'Arial', 'Tahoma', sans-serif !important;
347
  }
348
+ .stTextInput > div > div > input {
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  text-align: right;
 
 
350
  direction: rtl;
 
351
  }
352
+ .pricing-analysis-container {
353
+ border: 1px solid #e0e0e0;
354
+ border-radius: 10px;
355
+ padding: 10px;
356
+ margin-top: 10px;
357
+ background-color: #f9f9f9;
358
  }
359
  </style>
360
  """, unsafe_allow_html=True)
361
 
362
+ # تهيئة قائمة الوحدات المتاحة
363
+ unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"]
364
+
365
+ # تهيئة فئا�� التكاليف
366
+ cost_categories = [
367
+ "مواد",
368
+ "عمالة",
369
+ "معدات",
370
+ "مقاولي الباطن",
371
+ "مصاريف عامة",
372
+ "أرباح"
373
+ ]
374
+
375
+ # إنشاء جدول البنود اذا لم يكن موجوداً
376
+ if 'manual_items' not in st.session_state:
377
+ manual_items = pd.DataFrame(columns=[
378
+ 'رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي'
379
+ ])
380
+
381
+ # إضافة بضعة صفوف افتراضية
382
+ default_items = pd.DataFrame({
383
+ 'رقم البند': ["A1", "A2", "A3", "A4", "A5"],
384
+ 'وصف البند': [
385
+ "توريد وتركيب أعمال الخرسانة المسلحة للأساسات",
386
+ "توريد وتركيب حديد التسليح للأساسات",
387
+ "أعمال العزل المائي للأساسات",
388
+ "أعمال الردم والدك للأساسات",
389
+ "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة"
390
+ ],
391
+ 'الوحدة': ["م3", "طن", "م2", "م3", "م3"],
392
+ 'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0],
393
+ 'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0],
394
+ 'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0]
395
+ })
396
+
397
+ manual_items = pd.concat([manual_items, default_items])
398
+ st.session_state.manual_items = manual_items
399
+
400
+ # إنشاء جدول تحليل الأسعار اذا لم يكن موجوداً
401
+ if 'items_price_analysis' not in st.session_state:
402
+ st.session_state.items_price_analysis = {}
403
+
404
+ # عرض واجهة إدخال البنود
405
+ st.markdown("### إدخال تفاصيل البنود مع تحليل الأسعار")
406
+
407
+ # عرض البنود الحالية كجدول للعرض
408
+ st.markdown("### جدول البنود الحالية")
409
+ st.dataframe(st.session_state.manual_items, use_container_width=True, hide_index=True)
410
+
411
+ # التبويبات لإضافة بند جديد أو تعديل بند
412
+ tabs = st.tabs(["إضافة بند جديد", "تعديل بند حالي"])
413
+
414
+ with tabs[0]: # إضافة بند جديد
415
+ st.markdown("### إضافة بند جديد مع تحليل السعر")
416
+
417
+ col1, col2 = st.columns(2)
418
+
419
+ with col1:
420
+ new_id = st.text_input("رقم البند", value=f"A{len(st.session_state.manual_items)+1}", key="new_id")
421
+ new_desc = st.text_area("وصف البند", value="", key="new_desc")
422
+
423
+ with col2:
424
+ new_unit = st.selectbox("الوحدة", options=unit_options, key="new_unit")
425
+ new_qty = st.number_input("الكمية", value=0.0, min_value=0.0, format="%.2f", key="new_qty")
426
+
427
+ # إنشاء تحليل السعر للبند الجديد
428
+ st.markdown('<div class="pricing-analysis-container">', unsafe_allow_html=True)
429
+ st.markdown("#### تحليل سعر البند")
430
+
431
+ # التعرف التلقائي على نوع البند من الوصف
432
+ is_concrete = False
433
+ is_steel = False
434
+ is_bricks = False
435
+ is_paint = False
436
+ is_insulation = False
437
+
438
+ if new_desc:
439
+ is_concrete = 'خرسان' in new_desc
440
+ is_steel = 'حديد' in new_desc or 'تسليح' in new_desc
441
+ is_bricks = 'بلوك' in new_desc or 'طوب' in new_desc
442
+ is_paint = 'دهان' in new_desc or 'طلاء' in new_desc
443
+ is_insulation = 'عزل' in new_desc
444
+
445
+ # تلميح للمستخدم عن التعرف التلقائي
446
+ if any([is_concrete, is_steel, is_bricks, is_paint, is_insulation]):
447
+ detected_type = ""
448
+ if is_concrete:
449
+ detected_type = "أعمال خرسانة"
450
+ elif is_steel:
451
+ detected_type = "أعمال حديد"
452
+ elif is_bricks:
453
+ detected_type = "أعمال بلوك"
454
+ elif is_paint:
455
+ detected_type = "أعمال دهانات"
456
+ elif is_insulation:
457
+ detected_type = "أعمال عزل"
458
+
459
+ st.info(f"تم التعرف تلقائياً على نوع البند: {detected_type}")
460
+
461
+ # إنشاء مصفوفة فارغة لمكونات البند
462
+ if 'new_components' not in st.session_state:
463
+ # إنشاء DataFrame فارغ
464
+ new_components = pd.DataFrame(columns=[
465
+ 'نوع التكلفة', 'الوصف', 'الكمية', 'الوحدة', 'سعر الوحدة', 'الإجمالي'
466
+ ])
467
+
468
+ # إضافة مكونات افتراضية بناءً على نوع البند
469
+ if is_concrete:
470
+ # مكونات الخرسانة
471
+ default_components = pd.DataFrame({
472
+ 'نوع التكلفة': ['مواد', 'مواد', 'مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
473
+ 'الوصف': ['أسمنت', 'رمل', 'حصى', 'عمال وفنيين', 'خلاطات ومعدات صب', 'مصاريف عامة', 'أرباح'],
474
+ 'الكمية': [350, 0.4, 0.8, 8, 1, 1, 1],
475
+ 'الوحدة': ['كجم', 'م3', 'م3', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
476
+ 'سعر الوحدة': [0.5, 100, 120, 50, 500, 100, 150],
477
+ 'الإجمالي': [175, 40, 96, 400, 500, 100, 150]
478
+ })
479
+ new_components = pd.concat([new_components, default_components], ignore_index=True)
480
+
481
+ elif is_steel:
482
+ # مكونات الحديد
483
+ default_components = pd.DataFrame({
484
+ 'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
485
+ 'الوصف': ['حديد التسليح', 'عمال وفنيين', 'معدات ثني وتجهيز الحديد', 'مصاريف عامة', 'أرباح'],
486
+ 'الكمية': [1000, 10, 1, 1, 1],
487
+ 'الوحدة': ['كجم', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
488
+ 'سعر الوحدة': [4.5, 50, 300, 200, 300],
489
+ 'الإجمالي': [4500, 500, 300, 200, 300]
490
+ })
491
+ new_components = pd.concat([new_components, default_components], ignore_index=True)
492
+
493
+ elif is_bricks:
494
+ # مكونات البلوك
495
+ default_components = pd.DataFrame({
496
+ 'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
497
+ 'الوصف': ['بلوك خرساني', 'مونة', 'عمالة بناء', 'مصاريف عامة', 'أرباح'],
498
+ 'الكمية': [12.5, 0.02, 1, 1, 1],
499
+ 'الوحدة': ['قطعة', 'م3', 'م2', 'وحدة', 'وحدة'],
500
+ 'سعر الوحدة': [8, 500, 80, 15, 20],
501
+ 'الإجمالي': [100, 10, 80, 15, 20]
502
+ })
503
+ new_components = pd.concat([new_components, default_components], ignore_index=True)
504
+
505
+ elif is_paint:
506
+ # مكونات الدهانات
507
+ default_components = pd.DataFrame({
508
+ 'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
509
+ 'الوصف': ['دهان', 'مواد تجهيز', 'عمالة دهان', 'مصاريف عامة', 'أرباح'],
510
+ 'الكمية': [0.4, 0.1, 1, 1, 1],
511
+ 'الوحدة': ['لتر', 'وحدة', 'م2', 'وحدة', 'وحدة'],
512
+ 'سعر الوحدة': [80, 20, 35, 5, 10],
513
+ 'الإجمالي': [32, 2, 35, 5, 10]
514
+ })
515
+ new_components = pd.concat([new_components, default_components], ignore_index=True)
516
+
517
+ elif is_insulation:
518
+ # مكونات العزل
519
+ default_components = pd.DataFrame({
520
+ 'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
521
+ 'الوصف': ['مواد عازلة', 'مواد لاصقة', 'عمالة تركيب', 'مصاريف عامة', 'أرباح'],
522
+ 'الكمية': [1.1, 0.2, 1, 1, 1],
523
+ 'الوحدة': ['م2', 'كجم', 'م2', 'وحدة', 'وحدة'],
524
+ 'سعر الوحدة': [60, 30, 25, 10, 15],
525
+ 'الإجمالي': [66, 6, 25, 10, 15]
526
+ })
527
+ new_components = pd.concat([new_components, default_components], ignore_index=True)
528
+
529
+ else:
530
+ # مكونات عامة افتراضية
531
+ default_components = pd.DataFrame({
532
+ 'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
533
+ 'الوصف': ['مواد أساسية', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
534
+ 'الكمية': [1, 1, 1, 1, 1],
535
+ 'الوحدة': [new_unit if new_unit else 'وحدة', 'وحدة', 'وحدة', 'وحدة', 'وحدة'],
536
+ 'سعر الوحدة': [100, 50, 30, 20, 20],
537
+ 'الإجمالي': [100, 50, 30, 20, 20]
538
+ })
539
+ new_components = pd.concat([new_components, default_components], ignore_index=True)
540
+
541
+ st.session_state.new_components = new_components
542
+
543
+ # عرض وتحرير مكونات تحليل السعر
544
+ edited_components = st.data_editor(
545
+ st.session_state.new_components,
546
+ use_container_width=True,
547
+ hide_index=True,
548
+ num_rows="dynamic",
549
+ column_config={
550
+ 'نوع التكلفة': st.column_config.SelectboxColumn(
551
+ 'نوع التكلفة',
552
+ help='فئة التكلفة',
553
+ options=cost_categories
554
+ ),
555
+ 'الوحدة': st.column_config.SelectboxColumn(
556
+ 'الوحدة',
557
+ help='وحدة القياس',
558
+ options=unit_options + ["وحدة", "ساعة", "يوم"]
559
+ ),
560
+ 'الكمية': st.column_config.NumberColumn(
561
+ 'الكمية',
562
+ help='الكمية',
563
+ min_value=0.0,
564
+ format="%.2f"
565
+ ),
566
+ 'سعر الوحدة': st.column_config.NumberColumn(
567
+ 'سعر الوحدة',
568
+ help='سعر الوحدة',
569
+ min_value=0.0,
570
+ format="%.2f"
571
+ ),
572
+ 'الإجمالي': st.column_config.NumberColumn(
573
+ 'الإجمالي',
574
+ help='الإجمالي',
575
+ min_value=0.0,
576
+ format="%.2f"
577
+ )
578
+ }
579
+ )
580
+
581
+ # إعادة حساب الإجمالي لكل مكون
582
+ edited_components['الإجمالي'] = edited_components['الكمية'] * edited_components['سعر الوحدة']
583
+
584
+ # حفظ التعديلات
585
+ st.session_state.new_components = edited_components
586
+
587
+ # حساب إجمالي تحليل السعر
588
+ total_analysis_price = edited_components['الإجمالي'].sum()
589
+ unit_price_from_analysis = total_analysis_price / new_qty if new_qty > 0 else 0
590
+
591
+ # عرض ملخص تحليل السعر
592
+ st.markdown("#### ملخص تحليل السعر")
593
+
594
+ col1, col2 = st.columns(2)
595
+
596
+ with col1:
597
+ st.metric("إجمالي تكلفة البند من التحليل", f"{total_analysis_price:,.2f} ريال")
598
+
599
+ with col2:
600
+ st.metric("سعر الوحدة المحسوب", f"{unit_price_from_analysis:,.2f} ريال")
601
+
602
+ st.markdown('</div>', unsafe_allow_html=True)
603
+
604
+ # استخدام السعر المحسوب
605
+ use_calculated_price = st.checkbox("استخدام السعر المحسوب من التحليل", value=True)
606
+
607
+ # تحديد سعر الوحدة النهائي
608
+ if use_calculated_price and new_qty > 0:
609
+ new_unit_price = unit_price_from_analysis
610
+ else:
611
+ new_unit_price = st.number_input