EGYADMIN commited on
Commit
c26339c
·
verified ·
1 Parent(s): 2fa0345

Update modules/pricing/services/construction_cost_calculator.py

Browse files
modules/pricing/services/construction_cost_calculator.py CHANGED
@@ -1,751 +1,1006 @@
1
  """
2
- حاسبة تكاليف البناء المتكاملة
3
- تتضمن العناصر التالية:
4
  - المواد الخام
5
- - المعدات
6
  - العمالة
 
7
  - المصاريف الإدارية
8
  - هامش الربح
9
  """
10
 
11
- import streamlit as st
12
  import pandas as pd
13
  import numpy as np
14
- import plotly.express as px
15
- import plotly.graph_objects as go
 
 
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- def render_construction_calculator():
19
- """
20
- عرض حاسبة تكاليف البناء المتكاملة
21
- """
22
- st.markdown("<h2 class='module-title'>حاسبة تكاليف البناء المتكاملة</h2>", unsafe_allow_html=True)
23
-
24
- # معلومات المشروع
25
- st.markdown("<h3>معلومات المشروع</h3>", unsafe_allow_html=True)
26
-
27
- col1, col2 = st.columns(2)
28
-
29
- with col1:
30
- project_name = st.text_input("اسم المشروع", "مشروع سكني")
31
- project_location = st.text_input("موقع المشروع", "الرياض - حي النرجس")
32
-
33
- with col2:
34
- project_area = st.number_input("المساحة الإجمالية (م²)", min_value=1, value=500)
35
- project_type = st.selectbox(
36
- "نوع المشروع",
37
- options=[
38
- "سكني", "تجاري", "صناعي", "إداري", "صحي", "تعليمي",
39
- "بنية تحتية", "طرق", "جسور", "أخرى"
40
- ]
41
- )
42
-
43
- # التبويبات الرئيسية للحاسبة
44
- tabs = st.tabs([
45
- "المواد الخام", "المعدات", "العمالة", "المصاريف الإدارية", "هامش الربح", "التقرير النهائي"
46
- ])
47
-
48
- # تعريف المتغيرات العامة
49
- if "materials_cost" not in st.session_state:
50
- st.session_state.materials_cost = 0.0
51
- if "equipment_cost" not in st.session_state:
52
- st.session_state.equipment_cost = 0.0
53
- if "labor_cost" not in st.session_state:
54
- st.session_state.labor_cost = 0.0
55
- if "admin_cost" not in st.session_state:
56
- st.session_state.admin_cost = 0.0
57
- if "profit_margin" not in st.session_state:
58
- st.session_state.profit_margin = 10.0
59
- if "materials" not in st.session_state:
60
- st.session_state.materials = []
61
- if "equipment" not in st.session_state:
62
- st.session_state.equipment = []
63
- if "labor" not in st.session_state:
64
- st.session_state.labor = []
65
- if "admin_expenses" not in st.session_state:
66
- st.session_state.admin_expenses = []
67
-
68
- # تبويب المواد الخام
69
- with tabs[0]:
70
- render_materials_tab()
71
-
72
- # تبويب المعدات
73
- with tabs[1]:
74
- render_equipment_tab()
75
-
76
- # تبويب العمالة
77
- with tabs[2]:
78
- render_labor_tab()
79
-
80
- # تبويب المصاريف الإدارية
81
- with tabs[3]:
82
- render_admin_tab()
83
-
84
- # تبويب هامش الربح
85
- with tabs[4]:
86
- render_profit_tab()
87
-
88
- # تبويب التقرير النهائي
89
- with tabs[5]:
90
- render_final_report(project_name, project_location, project_area, project_type)
91
-
92
-
93
- def render_materials_tab():
94
- """
95
- عرض تبويب المواد الخام
96
- """
97
- st.markdown("<h3>تكاليف المواد الخام</h3>", unsafe_allow_html=True)
98
-
99
- # إضافة مادة جديدة
100
- st.markdown("<h4>إضافة مادة جديدة</h4>", unsafe_allow_html=True)
101
-
102
- col1, col2, col3, col4 = st.columns(4)
103
-
104
- with col1:
105
- material_name = st.text_input("اسم المادة", key="new_material_name")
106
- with col2:
107
- material_quantity = st.number_input("الكمية", min_value=0.0, step=0.1, key="new_material_quantity")
108
- with col3:
109
- material_unit = st.selectbox(
110
- "الوحدة",
111
- options=["م²", "م³", "طن", "كجم", "لتر", "قطعة", "لفة", "كيس", "أخرى"],
112
- key="new_material_unit"
113
- )
114
- with col4:
115
- material_price = st.number_input("السعر للوحدة (ريال)", min_value=0.0, step=0.01, key="new_material_price")
116
-
117
- if st.button("إضافة مادة", key="add_material_btn"):
118
- total_price = material_quantity * material_price
119
- new_material = {
120
- "name": material_name,
121
- "quantity": material_quantity,
122
- "unit": material_unit,
123
- "price": material_price,
124
- "total": total_price
125
  }
126
- st.session_state.materials.append(new_material)
127
- st.success(f"تمت إضافة {material_name} بنجاح!")
128
 
129
- # عرض قائمة المواد المضافة
130
- if st.session_state.materials:
131
- st.markdown("<h4>قائمة المواد المضافة</h4>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- materials_df = pd.DataFrame(st.session_state.materials)
134
- materials_df.columns = ["اسم المادة", "الكمية", "الوحدة", "السعر للوحدة", "التكلفة الإجمالية"]
135
- st.dataframe(materials_df)
 
 
 
 
 
 
136
 
137
- total_materials_cost = sum(item["total"] for item in st.session_state.materials)
138
- st.session_state.materials_cost = total_materials_cost
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- st.markdown(f"<h4>إجمالي تكلفة المواد: <span style='color:var(--primary-color)'>{total_materials_cost:,.2f} ريال</span></h4>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
141
 
142
- # رسم بياني للمواد حسب التكلفة
143
- if len(st.session_state.materials) > 1:
144
- st.markdown("<h4>توزيع تكاليف المواد</h4>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- fig = px.pie(
147
- materials_df,
148
- values="التكلفة الإجمالية",
149
- names="اسم المادة",
150
- title="توزيع تكاليف المواد",
151
- color_discrete_sequence=px.colors.sequential.Teal,
152
- hole=0.4
153
- )
154
- fig.update_layout(
155
- font=dict(family="Almarai, Arial", size=14),
156
- margin=dict(t=50, b=50, l=20, r=20)
157
- )
158
- st.plotly_chart(fig, use_container_width=True)
159
-
160
- st.markdown("---")
161
-
162
- # استيراد بيانات المواد من ملف
163
- st.markdown("<h4>استيراد بيانات المواد من ملف</h4>", unsafe_allow_html=True)
164
- uploaded_file = st.file_uploader("اختر ملف Excel أو CSV", type=["xlsx", "csv"], key="materials_upload")
165
-
166
- if uploaded_file is not None:
167
- if uploaded_file.name.endswith('.csv'):
168
- df = pd.read_csv(uploaded_file)
169
- else:
170
- df = pd.read_excel(uploaded_file)
171
-
172
- st.success("تم استيراد البيانات بنجاح!")
173
- st.dataframe(df)
174
-
175
- if st.button("إضافة المواد من الملف"):
176
- try:
177
- # تحويل أسماء الأعمدة للمطابقة مع النظام
178
- column_mapping = {
179
- "المادة": "name",
180
- "اسم المادة": "name",
181
- "الكمية": "quantity",
182
- "الوحدة": "unit",
183
- "السعر": "price",
184
- "سعر الوحدة": "price"
185
  }
186
-
187
- mapped_df = df.rename(columns=column_mapping)
188
-
189
- # حساب التكلفة الإجمالية لكل مادة
190
- for _, row in mapped_df.iterrows():
191
- total_price = row["quantity"] * row["price"]
192
- new_material = {
193
- "name": row["name"],
194
- "quantity": row["quantity"],
195
- "unit": row["unit"],
196
- "price": row["price"],
197
- "total": total_price
198
- }
199
- st.session_state.materials.append(new_material)
200
-
201
- st.success("تمت إضافة جميع المواد من الملف بنجاح!")
202
-
203
- except Exception as e:
204
- st.error(f"حدث خطأ: {str(e)}")
205
- st.error("تأكد من أن الملف يحتوي على الأعمدة المطلوبة: اسم المادة، الكمية، الوحدة، السعر للوحدة")
206
-
207
-
208
- def render_equipment_tab():
209
- """
210
- عرض تبويب المعدات
211
- """
212
- st.markdown("<h3>تكاليف المعدات</h3>", unsafe_allow_html=True)
213
-
214
- # إضافة معدة جديدة
215
- st.markdown("<h4>إضافة معدة جديدة</h4>", unsafe_allow_html=True)
216
-
217
- col1, col2, col3 = st.columns(3)
218
-
219
- with col1:
220
- equipment_name = st.text_input("اسم المعدة", key="new_equipment_name")
221
- with col2:
222
- rental_type = st.selectbox(
223
- "نوع الإيجار",
224
- options=["يومي", "أسبوعي", "شهري", "سنوي", "مملوكة (استهلاك)"],
225
- key="rental_type"
226
- )
227
- with col3:
228
- usage_period = st.number_input(f"مدة الاستخدام ({rental_type})", min_value=1, value=1, key="usage_period")
229
-
230
- col4, col5, col6 = st.columns(3)
231
-
232
- with col4:
233
- equipment_rate = st.number_input(f"سعر الإيجار لكل ({rental_type}) (ريال)", min_value=0.0, step=0.01, key="equipment_rate")
234
- with col5:
235
- fuel_cost = st.number_input("تكلفة الوقود اليومية (ريال)", min_value=0.0, step=0.01, key="fuel_cost")
236
- with col6:
237
- operator_cost = st.number_input("تكلفة المشغل اليومية (ريال)", min_value=0.0, step=0.01, key="operator_cost")
238
-
239
- # حساب إجمالي التكلفة
240
- rental_days = {
241
- "يومي": 1,
242
- "أسبوعي": 7,
243
- "شهري": 30,
244
- "سنوي": 365,
245
- "مملوكة (استهلاك)": 1
246
- }
247
-
248
- total_days = usage_period * rental_days[rental_type]
249
- total_equipment_cost = equipment_rate * usage_period
250
- total_fuel_cost = fuel_cost * total_days
251
- total_operator_cost = operator_cost * total_days
252
- total_cost = total_equipment_cost + total_fuel_cost + total_operator_cost
253
-
254
- if st.button("إضافة معدة", key="add_equipment_btn"):
255
- new_equipment = {
256
- "name": equipment_name,
257
- "rental_type": rental_type,
258
- "usage_period": usage_period,
259
- "equipment_rate": equipment_rate,
260
- "fuel_cost": fuel_cost,
261
- "operator_cost": operator_cost,
262
- "total": total_cost
263
  }
264
- st.session_state.equipment.append(new_equipment)
265
- st.success(f"تمت إضافة {equipment_name} بنجاح!")
266
-
267
- # عرض تفاصيل الحساب
268
- st.markdown("<div class='card' style='margin-top: 10px;'>", unsafe_allow_html=True)
269
- st.markdown(f"<p>عدد أيام الاستخدام الإجمالية: {total_days} يوم</p>", unsafe_allow_html=True)
270
- st.markdown(f"<p>تكلفة إيجار المعدة: {total_equipment_cost:,.2f} ريال</p>", unsafe_allow_html=True)
271
- st.markdown(f"<p>تكلفة الوقود: {total_fuel_cost:,.2f} ريال</p>", unsafe_allow_html=True)
272
- st.markdown(f"<p>تكلفة المشغل: {total_operator_cost:,.2f} ريال</p>", unsafe_allow_html=True)
273
- st.markdown(f"<h4>التكلفة الإجمالية للمعدة: {total_cost:,.2f} ريال</h4>", unsafe_allow_html=True)
274
- st.markdown("</div>", unsafe_allow_html=True)
 
275
 
276
- # عرض قائمة المعدات المضافة
277
- if st.session_state.equipment:
278
- st.markdown("<h4>قائمة المعدات المضافة</h4>", unsafe_allow_html=True)
279
-
280
- equipment_data = []
281
- for item in st.session_state.equipment:
282
- equipment_data.append({
283
- "اسم المعدة": item["name"],
284
- "نوع الإيجار": item["rental_type"],
285
- "مدة الاستخدام": item["usage_period"],
286
- "إيجار الوحدة": item["equipment_rate"],
287
- "تكلفة الوقود": item["fuel_cost"],
288
- "تكلفة المشغل": item["operator_cost"],
289
- "التكلفة الإجمالية": item["total"]
290
- })
291
 
292
- equipment_df = pd.DataFrame(equipment_data)
293
- st.dataframe(equipment_df)
 
 
 
 
 
 
 
 
 
294
 
295
- total_equipment_cost = sum(item["total"] for item in st.session_state.equipment)
296
- st.session_state.equipment_cost = total_equipment_cost
 
 
 
 
 
297
 
298
- st.markdown(f"<h4>إجمالي تكلفة المعدات: <span style='color:var(--primary-color)'>{total_equipment_cost:,.2f} ريال</span></h4>", unsafe_allow_html=True)
 
299
 
300
- # رسم بياني للمعدات حسب التكلفة
301
- if len(st.session_state.equipment) > 1:
302
- st.markdown("<h4>توزيع تكاليف المعدات</h4>", unsafe_allow_html=True)
303
-
304
- fig = go.Figure()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
- fig.add_trace(go.Bar(
307
- x=[item["اسم المعدة"] for item in equipment_data],
308
- y=[item["التكلفة الإجمالية"] for item in equipment_data],
309
- name="التكلفة الإجمالية",
310
- marker_color="teal"
311
- ))
 
 
 
 
 
312
 
313
- fig.update_layout(
314
- title="تكاليف المعدات",
315
- xaxis_title="المعدة",
316
- yaxis_title="التكلفة (ريال)",
317
- font=dict(family="Almarai, Arial", size=14),
318
- margin=dict(t=50, b=50, l=20, r=20)
319
- )
320
 
321
- st.plotly_chart(fig, use_container_width=True)
322
-
323
-
324
- def render_labor_tab():
325
- """
326
- عرض تبويب العمالة
327
- """
328
- st.markdown("<h3>تكاليف العمالة</h3>", unsafe_allow_html=True)
329
-
330
- # إضافة عامل أو مجموعة عمال
331
- st.markdown("<h4>إضافة عمالة جديدة</h4>", unsafe_allow_html=True)
332
-
333
- col1, col2, col3 = st.columns(3)
334
-
335
- with col1:
336
- labor_type = st.text_input("نوع العمالة", key="new_labor_type")
337
- with col2:
338
- labor_count = st.number_input("العدد", min_value=1, value=1, key="new_labor_count")
339
- with col3:
340
- payment_type = st.selectbox(
341
- "نوع الدفع",
342
- options=["يومي", "أسبوعي", "شهري", "بالقطعة"],
343
- key="new_payment_type"
344
- )
345
-
346
- col4, col5, col6 = st.columns(3)
347
-
348
- with col4:
349
- wage_rate = st.number_input(f"الأجرة ({payment_type}) (ريال)", min_value=0.0, step=0.01, key="new_wage_rate")
350
- with col5:
351
- work_period = st.number_input(f"مدة العمل ({payment_type})", min_value=1, value=30, key="new_work_period")
352
- with col6:
353
- benefits_percent = st.slider("نسبة البدلات والتأمين (%)", min_value=0, max_value=50, value=15, key="new_benefits_percent")
354
-
355
- # حساب إجمالي التكلفة
356
- days_factor = {
357
- "يومي": 1,
358
- "أسبوعي": 7,
359
- "شهري": 30,
360
- "بالقطعة": 1
361
- }
362
-
363
- monthly_days = work_period * days_factor[payment_type] / 30 # تحويل الأيام إلى شهور
364
-
365
- if payment_type == "بالقطعة":
366
- total_labor_cost = labor_count * wage_rate * work_period
367
- else:
368
- # حساب الراتب الشهري
369
- monthly_wage = wage_rate * 30 / days_factor[payment_type]
370
- # حساب تكلفة البدلات والتأمين
371
- benefits_cost = monthly_wage * (benefits_percent / 100)
372
- # إجمالي التكلفة الشهرية
373
- monthly_total_cost = monthly_wage + benefits_cost
374
- # إجمالي التكلفة
375
- total_labor_cost = labor_count * monthly_total_cost * monthly_days
376
-
377
- if st.button("إضافة عمالة"):
378
- new_labor = {
379
- "type": labor_type,
380
- "count": labor_count,
381
- "payment_type": payment_type,
382
- "wage_rate": wage_rate,
383
- "work_period": work_period,
384
- "benefits_percent": benefits_percent,
385
- "total": total_labor_cost
386
  }
387
- st.session_state.labor.append(new_labor)
388
- st.success(f"تمت إضافة {labor_type} بنجاح!")
389
 
390
- # عرض تفاصيل الحساب
391
- st.markdown("<div class='card' style='margin-top: 10px;'>", unsafe_allow_html=True)
392
- if payment_type != "بالقطعة":
393
- monthly_wage = wage_rate * 30 / days_factor[payment_type]
394
- benefits_cost = monthly_wage * (benefits_percent / 100)
395
- monthly_total_cost = monthly_wage + benefits_cost
396
-
397
- st.markdown(f"<p>الراتب الشهري للعامل: {monthly_wage:,.2f} ريال</p>", unsafe_allow_html=True)
398
- st.markdown(f"<p>تكلفة البدلات والتأمين الشهرية: {benefits_cost:,.2f} ريال</p>", unsafe_allow_html=True)
399
- st.markdown(f"<p>إجمالي التكلفة الشهرية للعامل: {monthly_total_cost:,.2f} ريال</p>", unsafe_allow_html=True)
400
- st.markdown(f"<p>مدة العمل بالشهور: {monthly_days:.2f} شهر</p>", unsafe_allow_html=True)
401
- else:
402
- st.markdown(f"<p>سعر القطعة: {wage_rate:,.2f} ريال</p>", unsafe_allow_html=True)
403
- st.markdown(f"<p>عدد القطع: {work_period}</p>", unsafe_allow_html=True)
404
-
405
- st.markdown(f"<p>عدد العمال: {labor_count}</p>", unsafe_allow_html=True)
406
- st.markdown(f"<h4>التكلفة الإجمالية للعمالة: {total_labor_cost:,.2f} ريال</h4>", unsafe_allow_html=True)
407
- st.markdown("</div>", unsafe_allow_html=True)
408
-
409
- # عرض قائمة العمالة المضافة
410
- if st.session_state.labor:
411
- st.markdown("<h4>قائمة العمالة المضافة</h4>", unsafe_allow_html=True)
412
-
413
- labor_data = []
414
- for item in st.session_state.labor:
415
- labor_data.append({
416
- "نوع العمالة": item["type"],
417
- "العدد": item["count"],
418
- "نوع الدفع": item["payment_type"],
419
- "معدل الأجرة": item["wage_rate"],
420
- "مدة العمل": item["work_period"],
421
- "نسبة البدلات": f"{item['benefits_percent']}%",
422
- "التكلفة الإجمالية": item["total"]
 
 
 
 
 
 
 
 
423
  })
424
 
425
- labor_df = pd.DataFrame(labor_data)
426
- st.dataframe(labor_df)
 
 
 
 
 
 
427
 
428
- total_labor_cost = sum(item["total"] for item in st.session_state.labor)
429
- st.session_state.labor_cost = total_labor_cost
 
 
 
 
430
 
431
- st.markdown(f"<h4>إجمالي تكلفة العمالة: <span style='color:var(--primary-color)'>{total_labor_cost:,.2f} ريال</span></h4>", unsafe_allow_html=True)
 
 
 
 
432
 
433
- # رسم بياني للعمالة حسب التكلفة
434
- if len(st.session_state.labor) > 1:
435
- st.markdown("<h4>توزيع تكاليف العمالة</h4>", unsafe_allow_html=True)
 
436
 
437
- fig = px.bar(
438
- labor_df,
439
- x="نوع العمالة",
440
- y="التكلفة الإجمالية",
441
- color="العدد",
442
- title="توزيع تكاليف العمالة",
443
- color_continuous_scale=px.colors.sequential.Teal
444
- )
445
- fig.update_layout(
446
- font=dict(family="Almarai, Arial", size=14),
447
- margin=dict(t=50, b=50, l=20, r=20)
448
- )
449
- st.plotly_chart(fig, use_container_width=True)
450
-
451
-
452
- def render_admin_tab():
453
- """
454
- عرض تبويب المصاريف الإدارية
455
- """
456
- st.markdown("<h3>المصاريف الإدارية والعمومية</h3>", unsafe_allow_html=True)
457
-
458
- # إضافة مصروف جديد
459
- st.markdown("<h4>إضافة مصروف جديد</h4>", unsafe_allow_html=True)
460
-
461
- col1, col2, col3 = st.columns(3)
462
-
463
- with col1:
464
- expense_name = st.text_input("اسم المصروف", key="new_expense_name")
465
- with col2:
466
- expense_type = st.selectbox(
467
- "نوع المصروف",
468
- options=[
469
- "رواتب إدارية", "إيجارات", "مكتبية", "سفر", "تأمين",
470
- "استشارات", "رسوم حكومية", "منافع", "أخرى"
471
- ],
472
- key="new_expense_type"
473
- )
474
- with col3:
475
- expense_amount = st.number_input("المبلغ (ريال)", min_value=0.0, step=100.0, key="new_expense_amount")
476
-
477
- if st.button("إضافة مصروف"):
478
- new_expense = {
479
- "name": expense_name,
480
- "type": expense_type,
481
- "amount": expense_amount
482
  }
483
- st.session_state.admin_expenses.append(new_expense)
484
- st.success(f"تمت إضافة {expense_name} بنجاح!")
485
 
486
- # عرض قائمة المصاريف المضافة
487
- if st.session_state.admin_expenses:
488
- st.markdown("<h4>قائمة المصاريف الإدارية</h4>", unsafe_allow_html=True)
489
-
490
- admin_data = []
491
- for item in st.session_state.admin_expenses:
492
- admin_data.append({
493
- "اسم المصروف": item["name"],
494
- "نوع المصروف": item["type"],
495
- "المبلغ": item["amount"]
496
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
 
498
- admin_df = pd.DataFrame(admin_data)
499
- st.dataframe(admin_df)
 
 
 
 
 
500
 
501
- total_admin_cost = sum(item["amount"] for item in st.session_state.admin_expenses)
502
- st.session_state.admin_cost = total_admin_cost
 
503
 
504
- st.markdown(f"<h4>إجمالي المصاريف الإدارية: <span style='color:var(--primary-color)'>{total_admin_cost:,.2f} ريال</span></h4>", unsafe_allow_html=True)
 
 
 
 
 
505
 
506
- # رسم بياني للمصاريف حسب النوع
507
- if len(st.session_state.admin_expenses) > 1:
508
- st.markdown("<h4>توزيع المصاريف الإدارية حسب النوع</h4>", unsafe_allow_html=True)
 
509
 
510
- # تجميع المصاريف حسب النوع
511
- expense_by_type = admin_df.groupby("نوع المصروف")["المبلغ"].sum().reset_index()
512
 
513
- fig = px.pie(
514
- expense_by_type,
515
- values="المبلغ",
516
- names="نوع المصروف",
517
- title="توزيع المصاريف الإدارية",
518
- color_discrete_sequence=px.colors.sequential.Teal,
519
- hole=0.4
520
- )
521
- fig.update_layout(
522
- font=dict(family="Almarai, Arial", size=14),
523
- margin=dict(t=50, b=50, l=20, r=20)
524
- )
525
- st.plotly_chart(fig, use_container_width=True)
526
-
527
- # نسبة المصاريف الإدارية
528
- st.markdown("<h4>احتساب المصاريف الإدارية بالنسبة المئوية</h4>", unsafe_allow_html=True)
529
-
530
- # حساب التكاليف المباشرة
531
- direct_costs = st.session_state.materials_cost + st.session_state.equipment_cost + st.session_state.labor_cost
532
-
533
- col1, col2 = st.columns(2)
534
-
535
- with col1:
536
- admin_percent = st.slider("نسبة المصاريف الإدارية من التكاليف المباشرة (%)", min_value=0, max_value=30, value=10, key="admin_percent")
537
-
538
- with col2:
539
- calculated_admin_cost = direct_costs * (admin_percent / 100)
540
- st.markdown(f"<div class='card'><h4>المصاريف الإدارية بالنسبة: <span style='color:var(--primary-color)'>{calculated_admin_cost:,.2f} ريال</span></h4></div>", unsafe_allow_html=True)
541
-
542
- if st.button("استخدام النسبة المئوية للمصاريف الإدارية"):
543
- st.session_state.admin_cost = calculated_admin_cost
544
- st.success("تم تحديث إجمالي المصاريف الإدارية بناء على النسبة المئوية!")
545
-
546
-
547
- def render_profit_tab():
548
- """
549
- عرض تبويب هامش الربح
550
- """
551
- st.markdown("<h3>هامش الربح</h3>", unsafe_allow_html=True)
552
-
553
- # حساب التكاليف المباشرة والإجمالية
554
- direct_costs = st.session_state.materials_cost + st.session_state.equipment_cost + st.session_state.labor_cost
555
- total_costs = direct_costs + st.session_state.admin_cost
556
-
557
- # عرض ملخص التكاليف
558
- st.markdown("<div class='card'>", unsafe_allow_html=True)
559
- st.markdown("<h4>ملخص التكاليف</h4>", unsafe_allow_html=True)
560
- st.markdown(f"<p>إجمالي تكلفة المواد: <span style='color:var(--text-medium)'>{st.session_state.materials_cost:,.2f} ريال</span></p>", unsafe_allow_html=True)
561
- st.markdown(f"<p>إجمالي تكلفة المعدات: <span style='color:var(--text-medium)'>{st.session_state.equipment_cost:,.2f} ريال</span></p>", unsafe_allow_html=True)
562
- st.markdown(f"<p>إجمالي تكلفة العمالة: <span style='color:var(--text-medium)'>{st.session_state.labor_cost:,.2f} ريال</span></p>", unsafe_allow_html=True)
563
- st.markdown(f"<p>إجمالي التكاليف المباشرة: <span style='color:var(--primary-color)'>{direct_costs:,.2f} ريال</span></p>", unsafe_allow_html=True)
564
- st.markdown(f"<p>إجمالي المصاريف الإدارية: <span style='color:var(--text-medium)'>{st.session_state.admin_cost:,.2f} ريال</span></p>", unsafe_allow_html=True)
565
- st.markdown(f"<h4>إجمالي التكاليف: <span style='color:var(--primary-color)'>{total_costs:,.2f} ريال</span></h4>", unsafe_allow_html=True)
566
- st.markdown("</div>", unsafe_allow_html=True)
567
-
568
- # تحديد هامش الربح
569
- st.markdown("<h4>تحديد هامش الربح</h4>", unsafe_allow_html=True)
570
-
571
- col1, col2 = st.columns(2)
572
-
573
- with col1:
574
- profit_margin = st.slider("نسبة هامش الربح (%)", min_value=0, max_value=30, value=int(st.session_state.profit_margin), key="profit_margin_slider")
575
- st.session_state.profit_margin = profit_margin
576
-
577
- with col2:
578
- profit_amount = total_costs * (profit_margin / 100)
579
- st.markdown(f"<div class='card'><h4>قيمة هامش الربح: <span style='color:var(--primary-color)'>{profit_amount:,.2f} ريال</span></h4></div>", unsafe_allow_html=True)
580
-
581
- # إجمالي قيمة العرض
582
- total_price = total_costs + profit_amount
583
- st.markdown("<div class='card' style='background: var(--primary-light);'>", unsafe_allow_html=True)
584
- st.markdown(f"<h3>إجمالي قيمة العرض: <span style='color:var(--primary-color)'>{total_price:,.2f} ريال</span></h3>", unsafe_allow_html=True)
585
- st.markdown("</div>", unsafe_allow_html=True)
586
-
587
- # تحليل الحساسية لهامش الربح
588
- st.markdown("<h4>تحليل حساسية هامش الربح</h4>", unsafe_allow_html=True)
589
-
590
- sensitivity_data = []
591
- for margin in range(5, 31, 5):
592
- profit = total_costs * (margin / 100)
593
- total = total_costs + profit
594
- sensitivity_data.append({
595
- "نسبة الربح": f"{margin}%",
596
- "قيمة الربح": profit,
597
- "إجمالي العرض": total
598
- })
599
-
600
- sensitivity_df = pd.DataFrame(sensitivity_data)
601
-
602
- # رسم بياني لتحليل الحساسية
603
- fig = go.Figure()
604
-
605
- fig.add_trace(go.Bar(
606
- x=[item["نسبة الربح"] for item in sensitivity_data],
607
- y=[item["قيمة الربح"] for item in sensitivity_data],
608
- name="قيمة الربح",
609
- marker_color="rgba(14, 165, 165, 0.7)"
610
- ))
611
-
612
- fig.add_trace(go.Scatter(
613
- x=[item["نسبة الربح"] for item in sensitivity_data],
614
- y=[item["إجمالي العرض"] for item in sensitivity_data],
615
- name="إجمالي العرض",
616
- mode="lines+markers",
617
- marker=dict(size=8, color="rgba(255, 154, 60, 1.0)"),
618
- line=dict(width=3, color="rgba(255, 154, 60, 0.7)")
619
- ))
620
-
621
- fig.update_layout(
622
- title="تحليل حساسية هامش الربح",
623
- xaxis_title="نسبة الربح",
624
- yaxis_title="القيمة (ريال)",
625
- font=dict(family="Almarai, Arial", size=14),
626
- margin=dict(t=50, b=50, l=20, r=20),
627
- hovermode="x unified"
628
- )
629
-
630
- st.plotly_chart(fig, use_container_width=True)
631
-
632
- # جدول تحليل الحساسية
633
- st.dataframe(sensitivity_df)
634
-
635
-
636
- def render_final_report(project_name, project_location, project_area, project_type):
637
- """
638
- عرض التقرير النهائي للتكاليف
639
- """
640
- st.markdown("<h3>التقرير النهائي لتكاليف المشروع</h3>", unsafe_allow_html=True)
641
-
642
- # حساب التكاليف المباشرة والإجمالية
643
- direct_costs = st.session_state.materials_cost + st.session_state.equipment_cost + st.session_state.labor_cost
644
- total_costs = direct_costs + st.session_state.admin_cost
645
- profit_amount = total_costs * (st.session_state.profit_margin / 100)
646
- total_price = total_costs + profit_amount
647
-
648
- # معلومات المشروع
649
- st.markdown("<div class='card'>", unsafe_allow_html=True)
650
- st.markdown("<h4>معلومات المشروع</h4>", unsafe_allow_html=True)
651
- col1, col2 = st.columns(2)
652
-
653
- with col1:
654
- st.markdown(f"<p><strong>اسم المشروع:</strong> {project_name}</p>", unsafe_allow_html=True)
655
- st.markdown(f"<p><strong>نوع المشروع:</strong> {project_type}</p>", unsafe_allow_html=True)
656
-
657
- with col2:
658
- st.markdown(f"<p><strong>موقع المشروع:</strong> {project_location}</p>", unsafe_allow_html=True)
659
- st.markdown(f"<p><strong>المساحة الإجمالية:</strong> {project_area} م²</p>", unsafe_allow_html=True)
660
-
661
- st.markdown("</div>", unsafe_allow_html=True)
662
-
663
- # ملخص التكاليف
664
- st.markdown("<div class='card'>", unsafe_allow_html=True)
665
- st.markdown("<h4>ملخص التكاليف</h4>", unsafe_allow_html=True)
666
-
667
- col1, col2 = st.columns(2)
668
-
669
- with col1:
670
- st.markdown(f"<p><strong>تكلفة المواد:</strong> {st.session_state.materials_cost:,.2f} ريال</p>", unsafe_allow_html=True)
671
- st.markdown(f"<p><strong>تكلفة المعدات:</strong> {st.session_state.equipment_cost:,.2f} ريال</p>", unsafe_allow_html=True)
672
- st.markdown(f"<p><strong>تكلفة العمالة:</strong> {st.session_state.labor_cost:,.2f} ريال</p>", unsafe_allow_html=True)
673
- st.markdown(f"<p><strong>إجمالي التكاليف المباشرة:</strong> {direct_costs:,.2f} ريال</p>", unsafe_allow_html=True)
674
-
675
- with col2:
676
- st.markdown(f"<p><strong>المصاريف الإدارية:</strong> {st.session_state.admin_cost:,.2f} ريال</p>", unsafe_allow_html=True)
677
- st.markdown(f"<p><strong>إجمالي التكاليف:</strong> {total_costs:,.2f} ريال</p>", unsafe_allow_html=True)
678
- st.markdown(f"<p><strong>هامش الربح ({st.session_state.profit_margin}%):</strong> {profit_amount:,.2f} ريال</p>", unsafe_allow_html=True)
679
- st.markdown(f"<h4>إجمالي قيمة العرض: {total_price:,.2f} ريال</h4>", unsafe_allow_html=True)
680
-
681
- st.markdown("</div>", unsafe_allow_html=True)
682
-
683
- # عرض التفاصيل بالمتر المربع
684
- if project_area > 0:
685
- per_sqm_cost = total_price / project_area
686
- st.markdown("<div class='card'>", unsafe_allow_html=True)
687
- st.markdown("<h4>تكلفة المتر المربع</h4>", unsafe_allow_html=True)
688
- st.markdown(f"<p>تكلفة المتر المربع الإجمالية: <strong>{per_sqm_cost:,.2f} ريال/م²</strong></p>", unsafe_allow_html=True)
689
- st.markdown("</div>", unsafe_allow_html=True)
690
- else:
691
- st.markdown("<div class='card'>", unsafe_allow_html=True)
692
- st.markdown("<h4>تكلفة المتر المربع</h4>", unsafe_allow_html=True)
693
- st.markdown("<p>يرجى إدخال مساحة صحيحة للمشروع لحساب تكلفة المتر المربع</p>", unsafe_allow_html=True)
694
- st.markdown("</div>", unsafe_allow_html=True)
695
-
696
- # رسم بياني لتوزيع التكاليف
697
- st.markdown("<h4>توزيع التكاليف</h4>", unsafe_allow_html=True)
698
-
699
- # تجنب القسمة على صفر
700
- if total_price > 0:
701
- cost_distribution = [
702
- {"النوع": "المواد", "القيمة": st.session_state.materials_cost, "النسبة": st.session_state.materials_cost / total_price * 100},
703
- {"النوع": "المعدات", "القيمة": st.session_state.equipment_cost, "النسبة": st.session_state.equipment_cost / total_price * 100},
704
- {"النوع": "العمالة", "القيمة": st.session_state.labor_cost, "النسبة": st.session_state.labor_cost / total_price * 100},
705
- {"النوع": "المصاريف الإدارية", "القيمة": st.session_state.admin_cost, "النسبة": st.session_state.admin_cost / total_price * 100},
706
- {"النوع": "هامش الربح", "القيمة": profit_amount, "النسبة": profit_amount / total_price * 100}
707
- ]
708
- else:
709
- # إذا كان المجموع صفر، اجعل جميع النسب المئوية صفر
710
- cost_distribution = [
711
- {"النوع": "المواد", "القيمة": st.session_state.materials_cost, "النسبة": 0},
712
- {"النوع": "المعدات", "القيمة": st.session_state.equipment_cost, "النسبة": 0},
713
- {"النوع": "العمالة", "القيمة": st.session_state.labor_cost, "النسبة": 0},
714
- {"النوع": "المصاريف الإدارية", "القيمة": st.session_state.admin_cost, "النسبة": 0},
715
- {"النوع": "هامش الربح", "القيمة": profit_amount, "النسبة": 0}
716
- ]
717
-
718
- cost_df = pd.DataFrame(cost_distribution)
719
-
720
- fig = px.pie(
721
- cost_df,
722
- values="القيمة",
723
- names="النوع",
724
- title="توزيع التكاليف والأرباح",
725
- color_discrete_sequence=px.colors.sequential.Teal,
726
- hole=0.4
727
- )
728
-
729
- fig.update_traces(textposition='inside', textinfo='percent+label')
730
-
731
- fig.update_layout(
732
- annotations=[dict(text=f"{total_price:,.0f} ريال", x=0.5, y=0.5, font_size=14, showarrow=False)],
733
- font=dict(family="Almarai, Arial", size=14),
734
- margin=dict(t=50, b=50, l=20, r=20)
735
- )
736
-
737
- st.plotly_chart(fig, use_container_width=True)
738
-
739
- # جدول توزيع التكاليف
740
- st.dataframe(cost_df)
741
 
742
- # زر لإنشاء تقرير PDF
743
- col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
 
745
- with col1:
746
- if st.button("تصدير التقرير إلى PDF"):
747
- st.success("تم تصدير التقرير بنجاح!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
 
749
- with col2:
750
- if st.button("حفظ التقرير في قاعدة البيانات"):
751
- st.success("تم حفظ التقرير في قاعدة البيانات بنجاح!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ خدمة حاسبة تكاليف البناء
3
+ تقوم هذه الخدمة بحساب تكاليف البناء بشكل تفصيلي بناءً على المكونات المختلفة:
4
  - المواد الخام
 
5
  - العمالة
6
+ - المعدات
7
  - المصاريف الإدارية
8
  - هامش الربح
9
  """
10
 
 
11
  import pandas as pd
12
  import numpy as np
13
+ from datetime import datetime
14
+ import os
15
+ import json
16
+ import sys
17
+ from typing import Dict, List, Optional, Union, Any
18
 
19
+ # إضافة مسار النظام للوصول لملفات التكوين
20
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
21
+ try:
22
+ import config
23
+ except ImportError:
24
+ # إذا لم يتم العثور على ملف التكوين، نستخدم قيم افتراضية
25
+ class DefaultConfig:
26
+ DATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../data"))
27
+ config = DefaultConfig
28
+
29
+ # إنشاء مجلد البيانات إذا لم يكن موجودًا
30
+ if not os.path.exists(config.DATA_DIR):
31
+ os.makedirs(config.DATA_DIR)
32
 
33
+ class ConstructionCostCalculator:
34
+ """خدمة حاسبة تكاليف البناء"""
35
+
36
+ def __init__(self):
37
+ """تهيئة حاسبة تكاليف البناء"""
38
+ # تحميل بيانات المواد والأسعار المرجعية
39
+ self.material_rates = self._load_material_rates()
40
+ self.labor_rates = self._load_labor_rates()
41
+ self.equipment_rates = self._load_equipment_rates()
42
+
43
+ # النسب الافتراضية للمصاريف الإدارية وهامش الربح
44
+ self.default_admin_expenses_percentage = 0.05 # 5%
45
+ self.default_profit_margin_percentage = 0.10 # 10%
46
+
47
+ # معاملات التعديل الافتراضية
48
+ self.default_adjustment_factors = {
49
+ 'location_factor': 1.0, # معامل الموقع
50
+ 'time_factor': 1.0, # معامل الوقت
51
+ 'risk_factor': 1.0, # معامل المخاطر
52
+ 'market_factor': 1.0 # معامل السوق
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
 
 
54
 
55
+ def _load_material_rates(self) -> Dict[str, Dict[str, Any]]:
56
+ """تحميل أسعار المواد"""
57
+ # محاكاة تحميل البيانات من مصدر بيانات
58
+ material_rates = {
59
+ # مواد الخرسانة
60
+ 'خرسانة جاهزة': {
61
+ 'وحدة': 'م3',
62
+ 'سعر_الوحدة': 750.0,
63
+ 'وصف': 'خرسانة جاهزة بقوة 350 كجم/سم2',
64
+ 'فئة': 'أعمال خرسانية'
65
+ },
66
+ 'حديد تسليح': {
67
+ 'وحدة': 'طن',
68
+ 'سعر_الوحدة': 5500.0,
69
+ 'وصف': 'حديد تسليح قطر 8-32 مم',
70
+ 'فئة': 'أعمال خرسانية'
71
+ },
72
+ 'أسمنت': {
73
+ 'وحدة': 'كيس',
74
+ 'سعر_الوحدة': 30.0,
75
+ 'وصف': 'أسمنت بورتلاندي عادي',
76
+ 'فئة': 'أعمال خرسانية'
77
+ },
78
+ 'رمل': {
79
+ 'وحدة': 'م3',
80
+ 'سعر_الوحدة': 120.0,
81
+ 'وصف': 'رمل خشن للخرسانة',
82
+ 'فئة': 'أعمال خرسانية'
83
+ },
84
+ 'زلط': {
85
+ 'وحدة': 'م3',
86
+ 'سعر_الوحدة': 150.0,
87
+ 'وصف': 'زلط مقاس 10-20 مم للخرسانة',
88
+ 'فئة': 'أعمال خرسانية'
89
+ },
90
+
91
+ # مواد البناء
92
+ 'طوب أحمر': {
93
+ 'وحدة': '1000 قطعة',
94
+ 'سعر_الوحدة': 900.0,
95
+ 'وصف': 'طوب أحمر مقاس 25×12×6 سم',
96
+ 'فئة': 'أعمال بناء'
97
+ },
98
+ 'طوب أسمنتي': {
99
+ 'وحدة': 'قطعة',
100
+ 'سعر_الوحدة': 4.5,
101
+ 'وصف': 'بلوك أسمنتي مقاس 20×20×40 سم',
102
+ 'فئة': 'أعمال بناء'
103
+ },
104
+ 'مونة بناء': {
105
+ 'وحدة': 'م3',
106
+ 'سعر_الوحدة': 350.0,
107
+ 'وصف': 'مونة أسمنتية للبناء',
108
+ 'فئة': 'أعمال بناء'
109
+ },
110
+
111
+ # مواد التشطيبات
112
+ 'بلاط سيراميك': {
113
+ 'وحدة': 'م2',
114
+ 'سعر_الوحدة': 120.0,
115
+ 'وصف': 'بلاط سيراميك للأرضيات مقاس 40×40 سم',
116
+ 'فئة': 'تشطيبات'
117
+ },
118
+ 'بلاط بورسلين': {
119
+ 'وحدة': 'م2',
120
+ 'سعر_الوحدة': 180.0,
121
+ 'وصف': 'بلاط بورسلين للأرضيات مقاس 60×60 سم',
122
+ 'فئة': 'تشطيبات'
123
+ },
124
+ 'دهانات بلاستيك': {
125
+ 'وحدة': 'لتر',
126
+ 'سعر_الوحدة': 35.0,
127
+ 'وصف': 'دهان بلاستيك أساس وتشطيب',
128
+ 'فئة': 'تشطيبات'
129
+ },
130
+ 'جبس بورد': {
131
+ 'وحدة': 'م2',
132
+ 'سعر_الوحدة': 95.0,
133
+ 'وصف': 'ألواح جبس بورد سمك 12 مم',
134
+ 'فئة': 'تشطيبات'
135
+ },
136
+
137
+ # مواد العزل
138
+ 'عزل مائي': {
139
+ 'وحدة': 'م2',
140
+ 'سعر_الوحدة': 45.0,
141
+ 'وصف': 'عزل مائي من البيتومين المؤكسد',
142
+ 'فئة': 'أعمال عزل'
143
+ },
144
+ 'عزل حراري': {
145
+ 'وحدة': 'م2',
146
+ 'سعر_الوحدة': 65.0,
147
+ 'وصف': 'ألواح عزل حراري من البوليسترين سمك 5 سم',
148
+ 'فئة': 'أعمال عزل'
149
+ }
150
+ }
151
 
152
+ # محاولة تحميل البيانات من ملف إذا كان متاحًا
153
+ try:
154
+ file_path = os.path.join(config.DATA_DIR, 'material_rates.json')
155
+ if os.path.exists(file_path):
156
+ with open(file_path, 'r', encoding='utf-8') as f:
157
+ loaded_data = json.load(f)
158
+ material_rates.update(loaded_data)
159
+ except Exception as e:
160
+ print(f"خطأ في تحميل بيانات أسعار المواد: {str(e)}")
161
 
162
+ return material_rates
163
+
164
+ def _load_labor_rates(self) -> Dict[str, Dict[str, Any]]:
165
+ """تحميل أسعار العمالة"""
166
+ # محاكاة تحميل البيانات من مصدر بيانات
167
+ labor_rates = {
168
+ # عمالة الخرسانات
169
+ 'نجار مسلح': {
170
+ 'وحدة': 'يوم',
171
+ 'سعر_الوحدة': 250.0,
172
+ 'وصف': 'نجار مسلح لأعمال الشدات والفرم',
173
+ 'فئة': 'أعمال خرسانية',
174
+ 'إنتاجية_يومية': {
175
+ 'شدة أساسات': 12, # متر مربع
176
+ 'شدة أعمدة': 10, # متر مربع
177
+ 'شدة أسقف': 12 # متر مربع
178
+ }
179
+ },
180
+ 'حداد مسلح': {
181
+ 'وحدة': 'يوم',
182
+ 'سعر_الوحدة': 250.0,
183
+ 'وصف': 'حداد مسلح لأعمال حديد التسليح',
184
+ 'فئة': 'أعمال خرسانية',
185
+ 'إنتاجية_يومية': {
186
+ 'تجهيز وتركيب حديد أساسات': 700, # كجم
187
+ 'تجهيز وتركيب حديد أعمدة': 600, # كجم
188
+ 'تجهيز وتركيب حديد أسقف': 650 # كجم
189
+ }
190
+ },
191
+ 'عامل خرسانة': {
192
+ 'وحدة': 'يوم',
193
+ 'سعر_الوحدة': 150.0,
194
+ 'وصف': 'عامل لصب وتسوية الخرسانة',
195
+ 'فئة': 'أعمال خرسانية',
196
+ 'إنتاجية_يومية': {
197
+ 'صب خرسانة': 15 # متر مكعب
198
+ }
199
+ },
200
+
201
+ # عمالة البناء
202
+ 'بناء': {
203
+ 'وحدة': 'يوم',
204
+ 'سعر_الوحدة': 200.0,
205
+ 'وصف': 'عامل بناء للطوب والبلوك',
206
+ 'فئة': 'أعمال بناء',
207
+ 'إنتاجية_يومية': {
208
+ 'بناء طوب أحمر': 500, # قطعة
209
+ 'بناء بلوك أسمنتي': 80 # قطعة
210
+ }
211
+ },
212
+ 'مساعد بناء': {
213
+ 'وحدة': 'يوم',
214
+ 'سعر_الوحدة': 120.0,
215
+ 'وصف': 'مساعد عامل بناء',
216
+ 'فئة': 'أعمال بناء',
217
+ 'إنتاجية_يومية': {}
218
+ },
219
+
220
+ # عمالة التشطيبات
221
+ 'مبلط': {
222
+ 'وحدة': 'يوم',
223
+ 'سعر_الوحدة': 250.0,
224
+ 'وصف': 'عامل تركيب بلاط وسيراميك',
225
+ 'فئة': 'تشطيبات',
226
+ 'إنتاجية_يومية': {
227
+ 'تركيب سيراميك أرضيات': 15, # متر مربع
228
+ 'تركيب سيراميك حوائط': 12, # متر مربع
229
+ 'تركيب بورسلين': 12 # متر مربع
230
+ }
231
+ },
232
+ 'نقاش': {
233
+ 'وحدة': 'يوم',
234
+ 'سعر_الوحدة': 200.0,
235
+ 'وصف': 'عامل دهانات',
236
+ 'فئة': 'تشطيبات',
237
+ 'إنتاجية_يومية': {
238
+ 'دهانات بلاستيك': 35, # متر مربع
239
+ 'دهانات زيتية': 25 # متر مربع
240
+ }
241
+ },
242
+ 'كهربائي': {
243
+ 'وحدة': 'يوم',
244
+ 'سعر_الوحدة': 270.0,
245
+ 'وصف': 'فني كهرباء',
246
+ 'فئة': 'تشطيبات',
247
+ 'إنتاجية_يومية': {
248
+ 'تأسيس نقاط كهرباء': 15, # نقطة
249
+ 'تركيب لوحات توزيع': 2 # لوحة
250
+ }
251
+ },
252
+ 'سباك': {
253
+ 'وحدة': 'يوم',
254
+ 'سعر_الوحدة': 250.0,
255
+ 'وصف': 'فني سباكة',
256
+ 'فئة': 'تشطيبات',
257
+ 'إنتاجية_يومية': {
258
+ 'تأسيس نقاط صرف': 8, # نقطة
259
+ 'تأسيس نقاط تغذية': 10, # نقطة
260
+ 'تركيب أطقم حمامات': 2 # طقم
261
+ }
262
+ },
263
+
264
+ # مراقبة وإشراف
265
+ 'مهندس موقع': {
266
+ 'وحدة': 'يوم',
267
+ 'سعر_الوحدة': 500.0,
268
+ 'وصف': 'مهندس إشراف موقع',
269
+ 'فئة': 'إشراف'
270
+ },
271
+ 'مراقب فني': {
272
+ 'وحدة': 'يوم',
273
+ 'سعر_الوحدة': 300.0,
274
+ 'وصف': 'مراقب فني للتنفيذ',
275
+ 'فئة': 'إشراف'
276
+ }
277
+ }
278
 
279
+ # محاولة تحميل البيانات من ملف إذا كان متاحًا
280
+ try:
281
+ file_path = os.path.join(config.DATA_DIR, 'labor_rates.json')
282
+ if os.path.exists(file_path):
283
+ with open(file_path, 'r', encoding='utf-8') as f:
284
+ loaded_data = json.load(f)
285
+ labor_rates.update(loaded_data)
286
+ except Exception as e:
287
+ print(f"خطأ في تحميل بيانات أسعار العمالة: {str(e)}")
288
 
289
+ return labor_rates
290
+
291
+ def _load_equipment_rates(self) -> Dict[str, Dict[str, Any]]:
292
+ """تحميل أسعار المعدات"""
293
+ # محاكاة تحميل البيانات من مصدر بيانات
294
+ equipment_rates = {
295
+ # معدات الحفر والتسوية
296
+ 'حفار صغير': {
297
+ 'وحدة': 'يوم',
298
+ 'سعر_الوحدة': 1200.0,
299
+ 'وصف': 'حفار صغير (بوبكات) بقدرة 70 حصان',
300
+ 'فئة': 'معدات حفر',
301
+ 'إنتاجية_يومية': {
302
+ 'حفر في تربة عادية': 60 # متر مكعب
303
+ }
304
+ },
305
+ 'حفار متوسط': {
306
+ 'وحدة': 'يوم',
307
+ 'سعر_الوحدة': 2500.0,
308
+ 'وصف': 'حفار متوسط الحجم بقدرة 150 حصان',
309
+ 'فئة': 'معدات حفر',
310
+ 'إنتاجية_يومية': {
311
+ 'حفر في تربة عادية': 200 # متر مكعب
312
+ }
313
+ },
314
+ 'لودر': {
315
+ 'وحدة': 'يوم',
316
+ 'سعر_الوحدة': 2000.0,
317
+ 'وصف': 'لودر أمامي لنقل التربة',
318
+ 'فئة': 'معدات حفر',
319
+ 'إنتاجية_يومية': {
320
+ 'تحميل تربة': 300, # متر مكعب
321
+ 'تسوية موقع': 1500 # متر مربع
322
+ }
323
+ },
324
+ 'جريدر': {
325
+ 'وحدة': 'يوم',
326
+ 'سعر_الوحدة': 2200.0,
327
+ 'وصف': 'جريدر لتسوية الموقع',
328
+ 'فئة': 'معدات حفر',
329
+ 'إنتاجية_يومية': {
330
+ 'تسوية طرق': 3000 # متر مربع
331
+ }
332
+ },
333
 
334
+ # معدات الخرسانة
335
+ 'خلاطة خرسانة': {
336
+ 'وحدة': 'يوم',
337
+ 'سعر_الوحدة': 350.0,
338
+ 'وصف': 'خلاطة خرسانة بسعة 0.5 متر مكعب',
339
+ 'فئة': 'معدات خرسانة',
340
+ 'إنتاجية_يومية': {
341
+ 'خلط خرسانة': 15 # متر مكعب
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  }
343
+ },
344
+ 'هزاز خرسانة': {
345
+ 'وحدة': 'يوم',
346
+ 'سعر_الوحدة': 150.0,
347
+ 'وصف': 'هزاز خرسانة كهربائي',
348
+ 'فئة': 'معدات خرسانة',
349
+ 'إنتاجية_يومية': {
350
+ 'دمك خرسانة': 40 # متر مكعب
351
+ }
352
+ },
353
+ 'شاحنة خرسانة جاهزة': {
354
+ 'وحدة': 'يوم',
355
+ 'سعر_الوحدة': 3000.0,
356
+ 'وصف': 'شاحنة خرسانة جاهزة (مكسر) سعة 8 متر مكعب',
357
+ 'فئة': 'معدات خرسانة',
358
+ 'إنتاجية_يومية': {
359
+ 'نقل وصب خرسانة': 50 # متر مكعب
360
+ }
361
+ },
362
+ 'مضخة خرسانة': {
363
+ 'وحدة': 'يوم',
364
+ 'سعر_الوحدة': 5000.0,
365
+ 'وصف': 'مضخة خرسانة بذراع 42 متر',
366
+ 'فئة': 'معدات خرسانة',
367
+ 'إنتاجية_يومية': {
368
+ 'ضخ خرسانة': 120 # متر مكعب
369
+ }
370
+ },
371
+
372
+ # معدات رفع ونقل
373
+ 'رافعة برجية': {
374
+ 'وحدة': 'شهر',
375
+ 'سعر_الوحدة': 35000.0,
376
+ 'وصف': 'رافعة برجية بارتفاع 40 متر',
377
+ 'فئة': 'معدات رفع',
378
+ },
379
+ 'ونش شوكة': {
380
+ 'وحدة': 'يوم',
381
+ 'سعر_الوحدة': 1500.0,
382
+ 'وصف': 'ونش شوكة لرفع مواد البناء',
383
+ 'فئة': 'معدات رفع',
384
+ 'إنتاجية_يومية': {
385
+ 'رفع ونقل مواد': 100 # طن
386
+ }
387
+ },
388
+ 'شاحنة نقل': {
389
+ 'وحدة': 'يوم',
390
+ 'سعر_الوحدة': 1200.0,
391
+ 'وصف': 'شاحنة نقل حمولة 20 طن',
392
+ 'فئة': 'معدات نقل',
393
+ 'إنتاجية_يومية': {
394
+ 'نقل مواد': 80 # طن
395
+ }
396
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  }
398
+
399
+ # محاولة تحميل البيانات من ملف إذا كان متاحًا
400
+ try:
401
+ file_path = os.path.join(config.DATA_DIR, 'equipment_rates.json')
402
+ if os.path.exists(file_path):
403
+ with open(file_path, 'r', encoding='utf-8') as f:
404
+ loaded_data = json.load(f)
405
+ equipment_rates.update(loaded_data)
406
+ except Exception as e:
407
+ print(f"خطأ في تحميل بيانات أسعار المعدات: {str(e)}")
408
+
409
+ return equipment_rates
410
 
411
+ def calculate_item_cost(self, item_data: Dict[str, Any]) -> Dict[str, Any]:
412
+ """
413
+ حساب تكلفة بند محدد بكافة مكوناته
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
+ المعلمات:
416
+ item_data (dict): بيانات البند، تتضمن:
417
+ - وصف_البند (str): وصف البند
418
+ - الكمية (float): كمية البند
419
+ - الوحدة (str): وحدة القياس
420
+ - المواد (list): قائمة المواد المستخدمة وكمياتها
421
+ - العمالة (list): قائمة العمالة المستخدمة وعددها
422
+ - المعدات (list): قائمة المعدات المستخدمة وساعات عملها
423
+ - المصاريف_الإدارية (float, optional): نسبة المصاريف الإدارية (افتراضياً 5%)
424
+ - هامش_الربح (float, optional): نسبة هامش الربح (افتراضياً 10%)
425
+ - عوامل_التعديل (dict, optional): عوامل تعديل التكلفة
426
 
427
+ العوائد:
428
+ dict: تفاصيل تكلفة البند بكافة عناصرها
429
+ """
430
+ # استخراج البيانات الأساسية للبند
431
+ item_description = item_data.get('وصف_البند', 'بند غير محدد')
432
+ quantity = item_data.get('الكمية', 0.0)
433
+ unit = item_data.get('الوحدة', 'وحدة')
434
 
435
+ # حساب تكلفة المواد
436
+ materials_cost = self._calculate_materials_cost(item_data.get('المواد', []))
437
 
438
+ # حساب تكلفة العمالة
439
+ labor_cost = self._calculate_labor_cost(item_data.get('العمالة', []))
440
+
441
+ # حساب تكلفة المعدات
442
+ equipment_cost = self._calculate_equipment_cost(item_data.get('المعدات', []))
443
+
444
+ # حساب التكلفة المباشرة الإجمالية
445
+ direct_cost = materials_cost['الإجمالي'] + labor_cost['الإجمالي'] + equipment_cost['الإجمالي']
446
+
447
+ # حساب المصاريف الإدارية
448
+ admin_percentage = item_data.get('المصاريف_الإدارية', self.default_admin_expenses_percentage)
449
+ admin_cost = direct_cost * admin_percentage
450
+
451
+ # حساب هامش الربح
452
+ profit_percentage = item_data.get('هامش_الربح', self.default_profit_margin_percentage)
453
+ profit_margin = (direct_cost + admin_cost) * profit_percentage
454
+
455
+ # حساب التكلفة الإجمالية
456
+ total_cost = direct_cost + admin_cost + profit_margin
457
+
458
+ # حساب سعر الوحدة
459
+ unit_price = total_cost / quantity if quantity > 0 else 0.0
460
+
461
+ # تطبيق عوامل التعديل إذا وجدت
462
+ adjustment_factors = item_data.get('عوامل_التعديل', self.default_adjustment_factors)
463
+ adjustment_factor = self._calculate_adjustment_factor(adjustment_factors)
464
+
465
+ adjusted_unit_price = unit_price * adjustment_factor
466
+ adjusted_total_cost = total_cost * adjustment_factor
467
+
468
+ # إعداد النتائج
469
+ result = {
470
+ 'وصف_البند': item_description,
471
+ 'الكمية': quantity,
472
+ 'الوحدة': unit,
473
+ 'تكاليف_مباشرة': {
474
+ 'المواد': materials_cost,
475
+ 'العمالة': labor_cost,
476
+ 'المعدات': equipment_cost,
477
+ 'إجمالي_تكاليف_مباشرة': direct_cost
478
+ },
479
+ 'مصاريف_إدارية': {
480
+ 'نسبة': admin_percentage * 100,
481
+ 'قيمة': admin_cost
482
+ },
483
+ 'هامش_ربح': {
484
+ 'نسبة': profit_percentage * 100,
485
+ 'قيمة': profit_margin
486
+ },
487
+ 'التكلفة_الإجمالية': total_cost,
488
+ 'سعر_الوحدة': unit_price,
489
+ 'عوامل_التعديل': {
490
+ 'المعامل_الإجمالي': adjustment_factor,
491
+ 'التفاصيل': adjustment_factors
492
+ },
493
+ 'السعر_المعدل': {
494
+ 'سعر_الوحدة': adjusted_unit_price,
495
+ 'إجمالي': adjusted_total_cost
496
+ }
497
+ }
498
+
499
+ return result
500
+
501
+ def _calculate_materials_cost(self, materials: List[Dict[str, Any]]) -> Dict[str, Any]:
502
+ """
503
+ حساب تكلفة المواد
504
+
505
+ المعلمات:
506
+ materials (list): قائمة المواد المستخدمة وكمياتها
507
+ - الاسم (str): اسم المادة
508
+ - الكمية (float): الكمية المستخدمة
509
+ - الوحدة (str, optional): وحدة القياس
510
+ - سعر_الوحدة (float, optional): سعر الوحدة (يستخدم السعر من البيانات المرجعية إذا لم يتم تحديده)
511
+
512
+ العوائد:
513
+ dict: تفاصيل تكلفة المواد
514
+ """
515
+ materials_details = []
516
+ total_cost = 0.0
517
+
518
+ for material in materials:
519
+ material_name = material.get('الاسم', '')
520
+ quantity = material.get('الكمية', 0.0)
521
 
522
+ # البحث عن سعر المادة من البيانات المرجعية إذا لم يتم تحديده
523
+ if 'سعر_الوحدة' in material:
524
+ unit_price = material.get('سعر_الوحدة', 0.0)
525
+ unit = material.get('الوحدة', 'وحدة')
526
+ elif material_name in self.material_rates:
527
+ ref_material = self.material_rates[material_name]
528
+ unit_price = ref_material.get('سعر_الوحدة', 0.0)
529
+ unit = ref_material.get('وحدة', 'وحدة')
530
+ else:
531
+ unit_price = 0.0
532
+ unit = material.get('الوحدة', 'وحدة')
533
 
534
+ # حساب التكلفة
535
+ cost = quantity * unit_price
536
+ total_cost += cost
 
 
 
 
537
 
538
+ # إضافة التفاصيل
539
+ materials_details.append({
540
+ 'الاسم': material_name,
541
+ 'الكمية': quantity,
542
+ 'الوحدة': unit,
543
+ 'سعر_الوحدة': unit_price,
544
+ 'التكلفة': cost
545
+ })
546
+
547
+ return {
548
+ 'التفاصيل': materials_details,
549
+ 'الإجمالي': total_cost
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  }
 
 
551
 
552
+ def _calculate_labor_cost(self, labor: List[Dict[str, Any]]) -> Dict[str, Any]:
553
+ """
554
+ حساب تكلفة العمالة
555
+
556
+ المعلمات:
557
+ labor (list): قائمة العمالة المستخدمة وعددها
558
+ - النوع (str): نوع العامل
559
+ - العدد (int): عدد العمال
560
+ - المدة (float): مدة العمل بالأيام
561
+ - سعر_اليوم (float, optional): أجر اليوم (يستخدم السعر من البيانات المرجعية إذا لم يتم تحديده)
562
+
563
+ العوائد:
564
+ dict: تفاصيل تكلفة العمالة
565
+ """
566
+ labor_details = []
567
+ total_cost = 0.0
568
+
569
+ for worker in labor:
570
+ worker_type = worker.get('النوع', '')
571
+ count = worker.get('العدد', 0)
572
+ duration = worker.get('المدة', 0.0)
573
+
574
+ # البحث عن سعر العامل من البيانات المرجعية إذا لم يتم تحديده
575
+ if 'سعر_اليوم' in worker:
576
+ daily_rate = worker.get('سعر_اليوم', 0.0)
577
+ elif worker_type in self.labor_rates:
578
+ daily_rate = self.labor_rates[worker_type].get('سعر_الوحدة', 0.0)
579
+ else:
580
+ daily_rate = 0.0
581
+
582
+ # حساب التكلفة
583
+ cost = count * duration * daily_rate
584
+ total_cost += cost
585
+
586
+ # إضافة التفاصيل
587
+ labor_details.append({
588
+ 'النوع': worker_type,
589
+ 'العدد': count,
590
+ 'المدة': duration,
591
+ 'سعر_اليوم': daily_rate,
592
+ 'التكلفة': cost
593
  })
594
 
595
+ return {
596
+ 'التفاصيل': labor_details,
597
+ 'الإجمالي': total_cost
598
+ }
599
+
600
+ def _calculate_equipment_cost(self, equipment: List[Dict[str, Any]]) -> Dict[str, Any]:
601
+ """
602
+ حساب تكلفة المعدات
603
 
604
+ المعلمات:
605
+ equipment (list): قائمة المعدات المستخدمة وساعات عملها
606
+ - النوع (str): نوع المعدة
607
+ - العدد (int): عدد المعدات
608
+ - المدة (float): مدة الاستخدام بالأيام
609
+ - سعر_اليوم (float, optional): أجر اليوم (يستخدم السعر من البيانات المرجعية إذا لم يتم تحديده)
610
 
611
+ العوائد:
612
+ dict: تفاصيل تكلفة المعدات
613
+ """
614
+ equipment_details = []
615
+ total_cost = 0.0
616
 
617
+ for equip in equipment:
618
+ equip_type = equip.get('النوع', '')
619
+ count = equip.get('العدد', 0)
620
+ duration = equip.get('المدة', 0.0)
621
 
622
+ # البحث عن سعر المعدة من البيانات المرجعية إذا لم يتم تحديده
623
+ if 'سعر_اليوم' in equip:
624
+ daily_rate = equip.get('سعر_اليوم', 0.0)
625
+ elif equip_type in self.equipment_rates:
626
+ daily_rate = self.equipment_rates[equip_type].get('سعر_الوحدة', 0.0)
627
+ else:
628
+ daily_rate = 0.0
629
+
630
+ # حساب التكلفة
631
+ cost = count * duration * daily_rate
632
+ total_cost += cost
633
+
634
+ # إضافة التفاصيل
635
+ equipment_details.append({
636
+ 'النوع': equip_type,
637
+ 'العدد': count,
638
+ 'المدة': duration,
639
+ 'سعر_اليوم': daily_rate,
640
+ 'التكلفة': cost
641
+ })
642
+
643
+ return {
644
+ 'التفاصيل': equipment_details,
645
+ 'الإجمالي': total_cost
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
  }
 
 
647
 
648
+ def _calculate_adjustment_factor(self, factors: Dict[str, float]) -> float:
649
+ """
650
+ حساب المعامل الإجمالي لتعديل التكلفة
651
+
652
+ المعلمات:
653
+ factors (dict): عوامل التعديل
654
+
655
+ العوائد:
656
+ float: المعامل الإجمالي
657
+ """
658
+ # دمج العوامل المحددة مع العوامل الافتراضية
659
+ effective_factors = self.default_adjustment_factors.copy()
660
+ effective_factors.update(factors)
661
+
662
+ # حساب المعامل الإجمالي
663
+ total_factor = 1.0
664
+ for factor in effective_factors.values():
665
+ total_factor *= factor
666
+
667
+ return total_factor
668
+
669
+ def calculate_project_cost(self, project_data: Dict[str, Any]) -> Dict[str, Any]:
670
+ """
671
+ حساب التكلفة الإجمالية لمشروع بناء كامل
672
+
673
+ المعلمات:
674
+ project_data (dict): بيانات المشروع، تتضمن:
675
+ - اسم_المشروع (str): اسم المشروع
676
+ - وصف_المشروع (str): وصف المشروع
677
+ - البنود (list): قائمة بنود المشروع
678
+ - المصاريف_الإدارية (float, optional): نسبة المصاريف الإدارية الإجمالية (افتراضياً 5%)
679
+ - هامش_الربح (float, optional): نسبة هامش الربح الإجمالي (افتراضياً 10%)
680
+ - عوامل_التعديل (dict, optional): عوامل تعديل التكلفة للمشروع
681
 
682
+ العوائد:
683
+ dict: تفاصيل تكلفة المشروع بكافة عناصرها
684
+ """
685
+ # استخراج البيانات الأساسية للمشروع
686
+ project_name = project_data.get('اسم_المشروع', 'مشروع غير محدد')
687
+ project_description = project_data.get('وصف_المشروع', '')
688
+ items = project_data.get('البنود', [])
689
 
690
+ # استخراج النسب الإجمالية
691
+ admin_percentage = project_data.get('المصاريف_الإدارية', self.default_admin_expenses_percentage)
692
+ profit_percentage = project_data.get('هامش_الربح', self.default_profit_margin_percentage)
693
 
694
+ # حساب تكلفة كل بند
695
+ items_costs = []
696
+ total_direct_cost = 0.0
697
+ total_materials_cost = 0.0
698
+ total_labor_cost = 0.0
699
+ total_equipment_cost = 0.0
700
 
701
+ for item_data in items:
702
+ # تحديث نسب المصاريف والربح للبند إذا لم تكن محددة
703
+ if 'المصاريف_الإدارية' not in item_data:
704
+ item_data['المصاريف_الإدارية'] = admin_percentage
705
 
706
+ if 'هامش_الربح' not in item_data:
707
+ item_data['هامش_الربح'] = profit_percentage
708
 
709
+ # حساب تكلفة البند
710
+ item_cost = self.calculate_item_cost(item_data)
711
+ items_costs.append(item_cost)
712
+
713
+ # تحديث الإجماليات
714
+ total_materials_cost += item_cost['تكاليف_مباشرة']['المواد']['الإجمالي']
715
+ total_labor_cost += item_cost['تكاليف_مباشرة']['العمالة']['الإجمالي']
716
+ total_equipment_cost += item_cost['تكاليف_مباشرة']['المعدات']['الإجمالي']
717
+ total_direct_cost += item_cost['تكاليف_مباشرة']['إجمالي_تكاليف_مباشرة']
718
+
719
+ # حساب المصاريف الإدارية
720
+ admin_cost = total_direct_cost * admin_percentage
721
+
722
+ # حساب هامش الربح
723
+ profit_margin = (total_direct_cost + admin_cost) * profit_percentage
724
+
725
+ # حساب التكلفة الإجمالية
726
+ total_cost = total_direct_cost + admin_cost + profit_margin
727
+
728
+ # تطبيق عوامل التعديل إذا وجدت
729
+ adjustment_factors = project_data.get('عوامل_التعديل', self.default_adjustment_factors)
730
+ adjustment_factor = self._calculate_adjustment_factor(adjustment_factors)
731
+
732
+ adjusted_total_cost = total_cost * adjustment_factor
733
+
734
+ # إعداد النتائج
735
+ result = {
736
+ 'اسم_المشروع': project_name,
737
+ 'وصف_المشروع': project_description,
738
+ 'تكاليف_مباشرة': {
739
+ 'المواد': {
740
+ 'الإجمالي': total_materials_cost,
741
+ 'النسبة_المئوية': (total_materials_cost / total_direct_cost * 100) if total_direct_cost > 0 else 0
742
+ },
743
+ 'العمالة': {
744
+ 'الإجمالي': total_labor_cost,
745
+ 'النسبة_المئوية': (total_labor_cost / total_direct_cost * 100) if total_direct_cost > 0 else 0
746
+ },
747
+ 'المعدات': {
748
+ 'الإجمالي': total_equipment_cost,
749
+ 'النسبة_المئوية': (total_equipment_cost / total_direct_cost * 100) if total_direct_cost > 0 else 0
750
+ },
751
+ 'إجمالي_تكاليف_مباشرة': total_direct_cost
752
+ },
753
+ 'مصاريف_إدارية': {
754
+ 'نسبة': admin_percentage * 100,
755
+ 'قيمة': admin_cost
756
+ },
757
+ 'هامش_ربح': {
758
+ 'نسبة': profit_percentage * 100,
759
+ 'قيمة': profit_margin
760
+ },
761
+ 'التكلفة_الإجمالية': total_cost,
762
+ 'عوامل_التعديل': {
763
+ 'المعامل_الإجمالي': adjustment_factor,
764
+ 'التفاصيل': adjustment_factors
765
+ },
766
+ 'التكلفة_النهائية_المعدلة': adjusted_total_cost,
767
+ 'تفاصيل_البنود': items_costs,
768
+ 'عدد_البنود': len(items)
769
+ }
770
+
771
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
 
773
+ def get_rate_info(self, item_type: str, item_name: str) -> Dict[str, Any]:
774
+ """
775
+ الحصول على معلومات تفصيلية عن معدل وسعر عنصر محدد (مادة، عمالة، معدة)
776
+
777
+ المعلمات:
778
+ item_type (str): نوع العنصر - 'مادة'، 'عمالة'، 'معدة'
779
+ item_name (str): اسم العنصر
780
+
781
+ العوائد:
782
+ dict: معلومات تفصيلية عن العنصر
783
+ """
784
+ # تحديد القاموس المناسب حسب نوع العنصر
785
+ if item_type == 'مادة':
786
+ rates_dict = self.material_rates
787
+ elif item_type == 'عمالة':
788
+ rates_dict = self.labor_rates
789
+ elif item_type == 'معدة':
790
+ rates_dict = self.equipment_rates
791
+ else:
792
+ return {'خطأ': 'نوع العنصر غير صحيح'}
793
+
794
+ # البحث عن العنصر في القاموس
795
+ if item_name in rates_dict:
796
+ return rates_dict[item_name]
797
+ else:
798
+ return {'خطأ': 'العنصر غير موجود'}
799
 
800
+ def get_all_rates(self, item_type: str = None, category: str = None) -> Dict[str, Any]:
801
+ """
802
+ الحصول على قوائم معدلات الأسعار (لجميع المواد أو العمالة أو المعدات)
803
+
804
+ المعلمات:
805
+ item_type (str, optional): نوع العنصر - 'مادة'، 'عمالة'، 'معدة'، أو None لجميع الأنواع
806
+ category (str, optional): فئة محددة للتصفية
807
+
808
+ العوائد:
809
+ dict: قوائم معدلات الأسعار
810
+ """
811
+ result = {}
812
+
813
+ # جمع المواد حسب الفئة
814
+ if item_type is None or item_type == 'مادة':
815
+ materials = {}
816
+ for name, info in self.material_rates.items():
817
+ if category is None or info.get('فئة') == category:
818
+ materials[name] = info
819
+ result['المواد'] = materials
820
+
821
+ # جمع العمالة حسب الفئة
822
+ if item_type is None or item_type == 'عمالة':
823
+ labor = {}
824
+ for name, info in self.labor_rates.items():
825
+ if category is None or info.get('فئة') == category:
826
+ labor[name] = info
827
+ result['العمالة'] = labor
828
+
829
+ # جمع المعدات حسب الفئة
830
+ if item_type is None or item_type == 'معدة':
831
+ equipment = {}
832
+ for name, info in self.equipment_rates.items():
833
+ if category is None or info.get('فئة') == category:
834
+ equipment[name] = info
835
+ result['المعدات'] = equipment
836
+
837
+ return result
838
 
839
+ def generate_sample_project_data(self) -> Dict[str, Any]:
840
+ """
841
+ توليد بيانات نموذجية لمشروع بناء صغير للاختبار
842
+
843
+ العوائد:
844
+ dict: بيانات المشروع النموذجية
845
+ """
846
+ # إنشاء بيانات المشروع
847
+ project_data = {
848
+ 'اسم_المشروع': 'مبنى سكني صغير',
849
+ 'وصف_المشروع': 'مبنى سكني مكون من دور أرضي بمساحة 250 متر مربع',
850
+ 'المصاريف_الإدارية': 0.05, # 5%
851
+ 'هامش_الربح': 0.10, # 10%
852
+ 'عوامل_التعديل': {
853
+ 'location_factor': 1.2, # معامل الموقع (منطقة مرتفعة التكلفة)
854
+ 'time_factor': 1.0, # معامل الوقت
855
+ 'risk_factor': 1.05, # معامل المخاطر
856
+ 'market_factor': 1.0 # معامل السوق
857
+ },
858
+ 'البنود': [
859
+ # الأساسات
860
+ {
861
+ 'وصف_البند': 'حفر الأساسات بعمق 2 متر',
862
+ 'الكمية': 150.0,
863
+ 'الوحدة': 'م3',
864
+ 'المواد': [],
865
+ 'العمالة': [
866
+ {'النوع': 'عامل خرسانة', 'العدد': 4, 'المدة': 3}
867
+ ],
868
+ 'المعدات': [
869
+ {'النوع': 'حفار متوسط', 'العدد': 1, 'المدة': 2}
870
+ ]
871
+ },
872
+ {
873
+ 'وصف_البند': 'توريد وصب خرسانة عادية للأساسات',
874
+ 'الكمية': 25.0,
875
+ 'الوحدة': 'م3',
876
+ 'المواد': [
877
+ {'الاسم': 'خرسانة جاهزة', 'الكمية': 25.0}
878
+ ],
879
+ 'العمالة': [
880
+ {'النوع': 'عامل خرسانة', 'العدد': 6, 'المدة': 1}
881
+ ],
882
+ 'المعدات': [
883
+ {'النوع': 'مضخة خرسانة', 'العدد': 1, 'المدة': 0.5}
884
+ ]
885
+ },
886
+ {
887
+ 'وصف_البند': 'توريد وتركيب حديد تسليح للأساسات',
888
+ 'الكمية': 3.5,
889
+ 'الوحدة': 'طن',
890
+ 'المواد': [
891
+ {'الاسم': 'حديد تسليح', 'الكمية': 3.5}
892
+ ],
893
+ 'العمالة': [
894
+ {'النوع': 'حداد مسلح', 'العدد': 4, 'المدة': 3}
895
+ ],
896
+ 'المعدات': []
897
+ },
898
+ {
899
+ 'وصف_البند': 'نجارة وفك شدة الأساسات',
900
+ 'الكمية': 120.0,
901
+ 'الوحدة': 'م2',
902
+ 'المواد': [],
903
+ 'العمالة': [
904
+ {'النوع': 'نجار مسلح', 'العدد': 4, 'المدة': 3}
905
+ ],
906
+ 'المعدات': []
907
+ },
908
+ {
909
+ 'وصف_البند': 'توريد وصب خرسانة مسلحة للأساسات',
910
+ 'الكمية': 30.0,
911
+ 'الوحدة': 'م3',
912
+ 'المواد': [
913
+ {'الاسم': 'خرسانة جاهزة', 'الكمية': 30.0}
914
+ ],
915
+ 'العمالة': [
916
+ {'النوع': 'عامل خرسانة', 'العدد': 6, 'المدة': 1}
917
+ ],
918
+ 'المعدات': [
919
+ {'النوع': 'مضخة خرسانة', 'العدد': 1, 'المدة': 0.5},
920
+ {'النوع': 'هزاز خرسانة', 'العدد': 2, 'المدة': 1}
921
+ ]
922
+ },
923
+
924
+ # الأعمدة والأسقف
925
+ {
926
+ 'وصف_البند': 'توريد وتركيب حديد تسليح للأعمدة',
927
+ 'الكمية': 2.8,
928
+ 'الوحدة': 'طن',
929
+ 'المواد': [
930
+ {'الاسم': 'حديد تسليح', 'الكمية': 2.8}
931
+ ],
932
+ 'العمالة': [
933
+ {'النوع': 'حداد مسلح', 'العدد': 4, 'المدة': 3}
934
+ ],
935
+ 'المعدات': []
936
+ },
937
+ {
938
+ 'وصف_البند': 'نجارة وفك شدة الأعمدة',
939
+ 'الكمية': 85.0,
940
+ 'الوحدة': 'م2',
941
+ 'المواد': [],
942
+ 'العمالة': [
943
+ {'النوع': 'نجار مسلح', 'العدد': 3, 'المدة': 3}
944
+ ],
945
+ 'المعدات': []
946
+ },
947
+ {
948
+ 'وصف_البند': 'توريد وصب خرسانة مسلحة للأعمدة',
949
+ 'الكمية': 12.0,
950
+ 'الوحدة': 'م3',
951
+ 'المواد': [
952
+ {'الاسم': 'خرسانة جاهزة', 'الكمية': 12.0}
953
+ ],
954
+ 'العمالة': [
955
+ {'النوع': 'عامل خرسانة', 'العدد': 4, 'المدة': 1}
956
+ ],
957
+ 'المعدات': [
958
+ {'النوع': 'مضخة خرسانة', 'العدد': 1, 'المدة': 0.5},
959
+ {'النوع': 'هزاز خرسانة', 'العدد': 2, 'المدة': 1}
960
+ ]
961
+ },
962
+
963
+ # أعمال البناء
964
+ {
965
+ 'وصف_البند': 'توريد وبناء حوائط من الطوب الأحمر',
966
+ 'الكمية': 220.0,
967
+ 'الوحدة': 'م2',
968
+ 'المواد': [
969
+ {'الاسم': 'طوب أحمر', 'الكمية': 16.5} # بالألف
970
+ ],
971
+ 'العمالة': [
972
+ {'النوع': 'بناء', 'العدد': 4, 'المدة': 8},
973
+ {'النوع': 'مساعد بناء', 'العدد': 4, 'المدة': 8}
974
+ ],
975
+ 'المعدات': []
976
+ },
977
+
978
+ # أعمال التشطيبات
979
+ {
980
+ 'وصف_البند': 'توريد وتركيب بلاط سيراميك للأرضيات',
981
+ 'الكمية': 250.0,
982
+ 'الوحدة': 'م2',
983
+ 'المواد': [
984
+ {'الاسم': 'بلاط سيراميك', 'الكمية': 250.0}
985
+ ],
986
+ 'العمالة': [
987
+ {'النوع': 'مبلط', 'العدد': 4, 'المدة': 7}
988
+ ],
989
+ 'المعدات': []
990
+ },
991
+ {
992
+ 'وصف_البند': 'توريد وتنفيذ دهانات للحوائط',
993
+ 'الكمية': 450.0,
994
+ 'الوحدة': 'م2',
995
+ 'المواد': [
996
+ {'الاسم': 'دهانات بلاستيك', 'الكمية': 90.0} # بالتر
997
+ ],
998
+ 'العمالة': [
999
+ {'النوع': 'نقاش', 'العدد': 3, 'المدة': 8}
1000
+ ],
1001
+ 'المعدات': []
1002
+ }
1003
+ ]
1004
+ }
1005
+
1006
+ return project_data