', unsafe_allow_html=True)
- # إضافة بند جديد
- with st.expander("إضافة بند جديد"):
- with st.form("add_item_form"):
- col1, col2 = st.columns(2)
- with col1:
- new_item_name = st.text_input("اسم البند")
- new_item_unit = st.text_input("الوحدة")
- with col2:
- new_item_quantity = st.number_input("الكمية", min_value=0.0, step=0.1)
- new_item_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1)
-
- submitted = st.form_submit_button("إضافة البند")
- if submitted and new_item_name and new_item_unit:
- new_item = {
- "id": f"item_{len(current_project['items']) + 1}",
- "name": new_item_name,
- "unit": new_item_unit,
- "quantity": float(new_item_quantity),
- "unit_price": float(new_item_price),
- "total_price": float(new_item_quantity) * float(new_item_price),
- "materials": [],
- "labor": [],
- "equipment": [],
- "subcontractors": []
- }
- current_project["items"].append(new_item)
- st.success("تمت إضافة البند بنجاح")
- st.rerun()
+ project_info = self._get_current_project_info()
+ if not project_info:
+ st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
+ if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
+ st.session_state.pricing_step = "project_info"
+ st.rerun()
+ return
+
+ # الحصول على بنود المشروع الحالي
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
# عرض جدول الكميات
- if current_project["items"]:
- items_df = pd.DataFrame([
+ if project_items:
+ st.subheader("جدول الكميات")
+
+ # إنشاء DataFrame للعرض
+ df = pd.DataFrame([
{
- "م": i + 1,
- "اسم البند": item["name"],
- "الوحدة": item["unit"],
- "الكمية": item["quantity"],
- "سعر الوحدة (ريال)": item["unit_price"],
- "السعر الإجمالي (ريال)": item["total_price"],
- "تحليل": f'تحليل'
+ 'الكود': item['code'],
+ 'الوصف': item['description'],
+ 'الوحدة': item['unit'],
+ 'الكمية': item['quantity'],
+ 'سعر الوحدة': item['unit_price'],
+ 'السعر الإجمالي': item['total_price'],
+ 'نوع المورد': item.get('resource_type', '-')
}
- for i, item in enumerate(current_project["items"])
+ for item in project_items
])
- # إضافة صف المجموع
- total_price = sum(item["total_price"] for item in current_project["items"])
- total_row = pd.DataFrame([{
- "م": "",
- "اسم البند": "المجموع",
- "الوحدة": "",
- "الكمية": "",
- "سعر الوحدة (ريال)": "",
- "السعر الإجمالي (ريال)": total_price,
- "تحليل": ""
- }])
+ # عرض الجدول
+ st.dataframe(df)
- # استخدام pd.concat بدلاً من append
- items_df = pd.concat([items_df, total_row], ignore_index=True)
-
- st.write(items_df.to_html(escape=False, index=False), unsafe_allow_html=True)
+ # عرض إجمالي التكلفة
+ total_cost = sum(item['total_price'] for item in project_items)
+ st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
+ else:
+ st.info("لا توجد بنود في جدول الكميات. يرجى إضافة بنود باستخدام النموذج أدناه.")
+
+ # نموذج إضافة بند جديد
+ with st.expander("إضافة بند جديد", expanded=not project_items):
+ with st.form(key="add_item_form"):
+ # حقول النموذج
+ code = st.text_input("الكود")
+ description = st.text_area("الوصف")
+ unit = st.text_input("الوحدة")
+ quantity = st.number_input("الكمية", min_value=0.0, step=0.1)
+ unit_price = st.number_input("سعر الوحدة (ريال)", min_value=0.0, step=0.1)
+ resource_type = st.selectbox(
+ "نوع المورد",
+ ["مواد", "عمالة", "معدات", "خدمات", "أخرى"]
+ )
+
+ # حساب السعر الإجمالي
+ total_price = quantity * unit_price
+ st.metric("السعر الإجمالي", f"{total_price:,.2f} ريال")
+
+ # زر الإرسال
+ submit_button = st.form_submit_button("إضافة البند")
+
+ if submit_button:
+ # التحقق من صحة البيانات
+ if not code or not description or not unit or quantity <= 0 or unit_price <= 0:
+ st.error("يرجى ملء جميع الحقول المطلوبة")
+ else:
+ # إنشاء كائن البند
+ item_id = f"item_{len(st.session_state.boq_items) + 1}"
+ item_data = {
+ 'id': item_id,
+ 'project_id': st.session_state.current_project,
+ 'code': code,
+ 'description': description,
+ 'unit': unit,
+ 'quantity': quantity,
+ 'unit_price': unit_price,
+ 'total_price': total_price,
+ 'resource_type': resource_type
+ }
+
+ # إضافة البند إلى قائمة البنود
+ added_item = self.storage.add_boq_item(item_data)
+ st.session_state.boq_items.append(added_item)
+
+ st.success(f"تم إضافة البند: {code}")
+ st.rerun()
+
+ # نموذج استيراد بنود من ملف CSV
+ with st.expander("استيراد من CSV"):
+ uploaded_file = st.file_uploader("اختر ملف CSV", type="csv")
- # معالجة النقر على رابط التحليل
- for i in range(len(current_project["items"])):
- if st.button(f"تحليل البند {i+1}", key=f"analyze_btn_{i}", use_container_width=True):
- st.session_state.current_item_index = i
- st.session_state.current_tab = "item_analysis"
+ if uploaded_file is not None:
+ try:
+ # قراءة الملف
+ csv_data = uploaded_file.read().decode('utf-8')
+ csv_reader = csv.reader(io.StringIO(csv_data))
+
+ # تخطي الصف الأول (العناوين)
+ headers = next(csv_reader)
+
+ # قراءة البنود
+ imported_items = []
+ for row in csv_reader:
+ if len(row) >= 6:
+ code = row[0]
+ description = row[1]
+ unit = row[2]
+ quantity = float(row[3])
+ unit_price = float(row[4])
+ resource_type = row[5] if len(row) > 5 else "مواد"
+
+ total_price = quantity * unit_price
+
+ item_id = f"item_{len(st.session_state.boq_items) + len(imported_items) + 1}"
+ item_data = {
+ 'id': item_id,
+ 'project_id': st.session_state.current_project,
+ 'code': code,
+ 'description': description,
+ 'unit': unit,
+ 'quantity': quantity,
+ 'unit_price': unit_price,
+ 'total_price': total_price,
+ 'resource_type': resource_type
+ }
+
+ imported_items.append(item_data)
+
+ if imported_items:
+ if st.button("استيراد البنود", key="import_items_btn"):
+ # إضافة البنود إلى قائمة البنود
+ for item_data in imported_items:
+ added_item = self.storage.add_boq_item(item_data)
+ st.session_state.boq_items.append(added_item)
+
+ st.success(f"تم استيراد {len(imported_items)} بند")
+ st.rerun()
+ else:
+ st.error("لم يتم العثور على بنود صالحة في الملف")
+
+ except Exception as e:
+ st.error(f"حدث خطأ أثناء استيراد الملف: {str(e)}")
+
+ # أزرار التنقل بين الخطوات
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info_btn"):
+ st.session_state.pricing_step = "project_info"
+ st.rerun()
+
+ with col2:
+ if project_items:
+ if st.button("متابعة إلى تحليل التكلفة ⬅️", key="continue_to_cost_analysis", type="primary"):
+ st.session_state.pricing_step = "cost_analysis"
st.rerun()
- else:
- st.info("لا توجد بنود في جدول الكميات. قم بإضافة بنود جديدة.")
-
- def _get_current_project(self):
- """الحصول على المشروع الحالي بناءً على نوع هيكل البيانات"""
- if st.session_state.current_project_id is None:
- return None
+ else:
+ st.warning("يجب إضافة بند واحد على الأقل للمتابعة")
- if self._is_projects_dict():
- # إذا كان قاموساً
- return st.session_state.projects[st.session_state.current_project_id]
- else:
- # إذا كان قائمة
- return st.session_state.projects[int(st.session_state.current_project_id)]
+ st.markdown('
', unsafe_allow_html=True)
- def _render_item_price_analysis(self):
- """عرض تحليل سعر البند"""
- if st.session_state.current_project_id is None or "current_item_index" not in st.session_state:
- st.warning("الرجاء اختيار مشروع وبند أولاً")
+ def _render_cost_analysis_step(self):
+ """عرض خطوة تحليل التكلفة"""
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown('
الخطوة 3: تحليل التكلفة
', unsafe_allow_html=True)
+
+ project_info = self._get_current_project_info()
+ if not project_info:
+ st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
+ if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
+ st.session_state.pricing_step = "project_info"
+ st.rerun()
+ return
+
+ # الحصول على بنود المشروع الحالي
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
+
+ if not project_items:
+ st.warning("لا توجد بنود في جدول الكميات. يرجى العودة إلى الخطوة السابقة لإضافة البنود.")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq_btn"):
+ st.session_state.pricing_step = "boq"
+ st.rerun()
return
- current_project = self._get_current_project()
- current_item = current_project["items"][st.session_state.current_item_index]
+ # تحليل التكلفة
+ st.subheader("تحليل التكلفة حسب نوع المورد")
- st.header(f"تحليل سعر البند: {current_item['name']}")
+ # حساب التكلفة حسب نوع المورد
+ resource_types = {}
+ for item in project_items:
+ resource_type = item.get('resource_type', 'أخرى')
+ if resource_type in resource_types:
+ resource_types[resource_type] += item['total_price']
+ else:
+ resource_types[resource_type] = item['total_price']
- # معلومات البند
- col1, col2, col3, col4 = st.columns(4)
+ # عرض التكلفة حسب نوع المورد
+ resource_df = pd.DataFrame({
+ 'نوع المورد': list(resource_types.keys()),
+ 'التكلفة': list(resource_types.values())
+ })
+
+ # إضافة نسبة التكلفة
+ total_cost = sum(resource_types.values())
+ resource_df['النسبة'] = resource_df['التكلفة'] / total_cost * 100
+
+ # عرض الجدول
+ st.dataframe(resource_df)
+
+ # عرض الرسم البياني
+ fig, ax = plt.subplots(figsize=(10, 6))
+ ax.pie(
+ resource_df['التكلفة'],
+ labels=resource_df['نوع المورد'],
+ autopct='%1.1f%%',
+ startangle=90
+ )
+ ax.axis('equal')
+ st.pyplot(fig)
+
+ # عرض إجمالي التكلفة
+ st.metric("إجمالي التكلفة", f"{total_cost:,.2f} ريال")
+
+ # أزرار التنقل بين الخطوات
+ col1, col2 = st.columns(2)
with col1:
- st.write(f"**الوحدة:** {current_item['unit']}")
+ if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq"):
+ st.session_state.pricing_step = "boq"
+ st.rerun()
+
with col2:
- st.write(f"**الكمية:** {current_item['quantity']}")
- with col3:
- st.write(f"**سعر الوحدة:** {current_item['unit_price']} ريال")
- with col4:
- st.write(f"**السعر الإجمالي:** {current_item['total_price']} ريال")
-
- # زر حفظ التغييرات
- if st.session_state.item_analysis_edited:
- if st.button("حفظ التغييرات", type="primary", use_container_width=True):
- # تحديث السعر الإجمالي للبند
- materials_total = sum(material.get("total_price", 0) for material in current_item["materials"])
- labor_total = sum(labor.get("total_price", 0) for labor in current_item["labor"])
- equipment_total = sum(equipment.get("total_price", 0) for equipment in current_item["equipment"])
- subcontractors_total = sum(sub.get("total_price", 0) for sub in current_item["subcontractors"])
-
- direct_cost = materials_total + labor_total + equipment_total + subcontractors_total
- current_item["unit_price"] = direct_cost / current_item["quantity"] if current_item["quantity"] > 0 else 0
- current_item["total_price"] = current_item["unit_price"] * current_item["quantity"]
-
- st.session_state.item_analysis_edited = False
- st.success("تم حفظ التغييرات بنجاح")
+ if st.button("متابعة إلى استراتيجيات التسعير ⬅️", key="continue_to_pricing_strategies", type="primary"):
+ st.session_state.pricing_step = "pricing_strategies"
st.rerun()
- # تحليل المواد
- st.subheader("تحليل المواد")
+ st.markdown('
', unsafe_allow_html=True)
+
+ project_info = self._get_current_project_info()
+ if not project_info:
+ st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
+ if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
+ st.session_state.pricing_step = "project_info"
+ st.rerun()
+ return
+
+ # الحصول على بنود المشروع الحالي
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
+
+ if not project_items:
+ st.warning("لا توجد بنود في جدول الكميات. يرجى العودة إلى الخطوة السابقة لإضافة البنود.")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq_btn"):
+ st.session_state.pricing_step = "boq"
st.rerun()
+ return
+
+ # تعيين النسبة المستهدفة للمحتوى المحلي
+ local_content_target = st.slider(
+ "النسبة المستهدفة للمحتوى المحلي (%)",
+ min_value=0,
+ max_value=100,
+ value=project_info.get('local_content_target', 40),
+ step=5
+ )
+
+ # تحديث النسبة المستهدفة في المشروع
+ if local_content_target != project_info.get('local_content_target', 40):
+ self.storage.update_project(project_info['id'], {'local_content_target': local_content_target})
+
+ # تحديث حالة الجلسة
+ for i, p in enumerate(st.session_state.projects):
+ if p['id'] == project_info['id']:
+ st.session_state.projects[i]['local_content_target'] = local_content_target
+ break
+
+ # عرض جدول البنود مع معلومات المحتوى المحلي
+ st.subheader("تحديث معلومات المحتوى المحلي")
+
+ # إنشاء DataFrame للعرض
+ df = pd.DataFrame([
+ {
+ 'معرف': item['id'],
+ 'الكود': item['code'],
+ 'الوصف': item['description'],
+ 'نوع المورد': item.get('resource_type', '-'),
+ 'مورد محلي': item.get('is_local_supplier', False),
+ 'نسبة المحتوى المحلي (%)': item.get('local_content_percentage', 0),
+ 'السعر الإجمالي': item['total_price']
+ }
+ for item in project_items
+ ])
+
+ # عرض الجدول
+ edited_df = st.data_editor(
+ df,
+ column_config={
+ 'معرف': st.column_config.TextColumn(disabled=True),
+ 'الكود': st.column_config.TextColumn(disabled=True),
+ 'الوصف': st.column_config.TextColumn(disabled=True),
+ 'نوع المورد': st.column_config.TextColumn(disabled=True),
+ 'مورد محلي': st.column_config.CheckboxColumn(
+ "مورد محلي",
+ help="حدد إذا كان المورد محلي"
+ ),
+ 'نسبة المحتوى المحلي (%)': st.column_config.NumberColumn(
+ "نسبة المحتوى المحلي (%)",
+ min_value=0,
+ max_value=100,
+ step=5,
+ format="%d %%"
+ ),
+ 'السعر الإجمالي': st.column_config.NumberColumn(
+ "السعر الإجمالي",
+ format="%,.2f ريال",
+ disabled=True
+ )
+ },
+ hide_index=True,
+ use_container_width=True,
+ key="local_content_editor"
+ )
- # عرض جدول العمالة
- if current_item["labor"]:
- # تحويل البيانات إلى DataFrame
- labor_data = []
- for i, labor in enumerate(current_item["labor"]):
- labor_data.append({
- "م": i + 1,
- "نوع العمالة": labor["name"],
- "الوحدة": labor["unit"],
- "عدد الأيام": labor["quantity"],
- "الأجر اليومي (ريال)": labor["unit_price"],
- "الإجمالي (ريال)": labor["total_price"],
- "الإجراءات": f' '
+ # تحديث معلومات المحتوى المحلي
+ if st.button("حفظ معلومات المحتوى المحلي", key="save_local_content"):
+ for i, row in edited_df.iterrows():
+ item_id = row['معرف']
+ is_local_supplier = row['مورد محلي']
+ local_content_percentage = row['نسبة المحتوى المحلي (%)']
+
+ # تحديث البند
+ self.storage.update_boq_item(item_id, {
+ 'is_local_supplier': is_local_supplier,
+ 'local_content_percentage': local_content_percentage
})
+
+ # تحديث حالة الجلسة
+ for j, item in enumerate(st.session_state.boq_items):
+ if item['id'] == item_id:
+ st.session_state.boq_items[j]['is_local_supplier'] = is_local_supplier
+ st.session_state.boq_items[j]['local_content_percentage'] = local_content_percentage
+ break
+
+ st.success("تم حفظ معلومات المحتوى المحلي بنجاح")
+
+ # حساب المحتوى المحلي
+ if st.button("حساب المحتوى المحلي", key="calculate_local_content"):
+ # تحديث بنود المشروع بعد التعديل
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
- labor_df = pd.DataFrame(labor_data)
+ # حساب ملخص المحتوى المحلي
+ local_content_summary = self._calculate_local_content_summary(project_info['id'])
- # إضافة صف المجموع
- labor_total = sum(labor["total_price"] for labor in current_item["labor"])
- total_row = pd.DataFrame([{
- "م": "",
- "نوع العمالة": "المجموع",
- "الوحدة": "",
- "عدد الأيام": "",
- "الأجر اليومي (ريال)": "",
- "الإجمالي (ريال)": labor_total,
- "الإجراءات": ""
- }])
+ # تحديث ملخص المحتوى المحلي في المشروع
+ self.storage.update_project(project_info['id'], {'local_content_summary': local_content_summary})
- # استخدام pd.concat بدلاً من append
- labor_df = pd.concat([labor_df, total_row], ignore_index=True)
+ # تحديث حالة الجلسة
+ for i, p in enumerate(st.session_state.projects):
+ if p['id'] == project_info['id']:
+ st.session_state.projects[i]['local_content_summary'] = local_content_summary
+ break
- # عرض الجدول القابل للتعديل
- edited_labor_df = st.data_editor(
- labor_df,
- column_config={
- "م": st.column_config.NumberColumn("م", width="small"),
- "نوع العمالة": st.column_config.TextColumn("نوع العمالة"),
- "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
- "عدد الأيام": st.column_config.NumberColumn("عدد الأيام", format="%.1f", width="small"),
- "الأجر اليومي (ريال)": st.column_config.NumberColumn("الأجر اليومي (ريال)", format="%.2f"),
- "الإجمالي (ريال)": st.column_config.NumberColumn("الإجمالي (ريال)", format="%.2f"),
- "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
- },
- hide_index=True,
- key="labor_table",
- on_change=self._update_total_price,
- disabled=["م", "نوع العمالة", "الوحدة", "الإجمالي (ريال)", "الإجراءات"]
- )
+ st.success("تم حساب المحتوى المحلي بنجاح")
+ st.rerun()
+
+ # عرض ملخص المحتوى المحلي
+ st.subheader("ملخص المحتوى المحلي")
+
+ # الحصول على ملخص المحتوى المحلي
+ local_content_summary = project_info.get('local_content_summary', {
+ 'total_percentage': 0,
+ 'by_category': {
+ 'materials': 0,
+ 'labor': 0,
+ 'services': 0,
+ 'equipment': 0
+ }
+ })
+
+ # عرض النسبة الإجمالية للمحتوى المحلي
+ total_percentage = local_content_summary.get('total_percentage', 0)
+
+ # تحديد لون المؤشر حسب النسبة المستهدفة
+ if total_percentage >= local_content_target:
+ delta_color = "normal" # أخضر
+ else:
+ delta_color = "inverse" # أحمر
+
+ st.metric(
+ "النسبة الإجمالية للمحتوى المحلي",
+ f"{total_percentage:.1f}%",
+ f"{total_percentage - local_content_target:.1f}% من المستهدف",
+ delta_color=delta_color
+ )
+
+ # عرض النسب حسب الفئة
+ st.subheader("المحتوى المحلي حسب الفئة")
+
+ by_category = local_content_summary.get('by_category', {})
+
+ col1, col2, col3, col4 = st.columns(4)
+
+ with col1:
+ st.metric("المواد", f"{by_category.get('materials', 0):.1f}%")
+
+ with col2:
+ st.metric("العمالة", f"{by_category.get('labor', 0):.1f}%")
+
+ with col3:
+ st.metric("الخدمات", f"{by_category.get('services', 0):.1f}%")
+
+ with col4:
+ st.metric("المعدات", f"{by_category.get('equipment', 0):.1f}%")
+
+ # عرض الرسم البياني
+ if by_category:
+ fig, ax = plt.subplots(figsize=(10, 6))
+ categories = list(by_category.keys())
+ percentages = list(by_category.values())
- # تحديث البيانات بعد التعديل
- for i, labor in enumerate(current_item["labor"]):
- if i < len(edited_labor_df) - 1: # تجاهل صف المجموع
- labor["quantity"] = edited_labor_df.iloc[i]["عدد الأيام"]
- labor["unit_price"] = edited_labor_df.iloc[i]["الأجر اليومي (ريال)"]
- labor["total_price"] = labor["quantity"] * labor["unit_price"]
+ ax.bar(categories, percentages)
+ ax.set_ylabel('النسبة المئوية (%)')
+ ax.set_title('المحتوى المحلي حسب الفئة')
+ ax.axhline(y=local_content_target, color='r', linestyle='-', label=f'المستهدف ({local_content_target}%)')
+ ax.legend()
- # معالجة أزرار التعديل والحذف
- for i in range(len(current_item["labor"])):
- col1, col2 = st.columns([1, 1])
- with col1:
- if st.button(f"تعديل العمالة {i+1}", key=f"edit_labor_btn_{i}"):
- st.session_state[f"edit_labor_{i}"] = True
-
- with col2:
- if st.button(f"حذف العمالة {i+1}", key=f"delete_labor_btn_{i}"):
- st.session_state[f"delete_labor_{i}"] = True
-
- # نموذج التعديل
- if st.session_state.get(f"edit_labor_{i}", False):
- with st.form(f"edit_labor_form_{i}"):
- labor = current_item["labor"][i]
- col1, col2 = st.columns(2)
- with col1:
- labor_name = st.text_input("نوع العمالة", value=labor["name"])
- labor_unit = st.text_input("الوحدة", value=labor["unit"])
- with col2:
- labor_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, value=labor["quantity"])
- labor_price = st.number_input("الأجر اليومي (ريال)", min_value=0.0, step=10.0, value=labor["unit_price"])
-
- col1, col2 = st.columns(2)
- with col1:
- if st.form_submit_button("حفظ التعديلات"):
- labor["name"] = labor_name
- labor["unit"] = labor_unit
- labor["quantity"] = float(labor_quantity)
- labor["unit_price"] = float(labor_price)
- labor["total_price"] = labor["quantity"] * labor["unit_price"]
- st.session_state.item_analysis_edited = True
- st.session_state[f"edit_labor_{i}"] = False
- st.rerun()
- with col2:
- if st.form_submit_button("إلغاء"):
- st.session_state[f"edit_labor_{i}"] = False
- st.rerun()
-
- # تأكيد الحذف
- if st.session_state.get(f"delete_labor_{i}", False):
- st.warning(f"هل أنت متأكد من حذف العمالة: {current_item['labor'][i]['name']}؟")
- col1, col2 = st.columns(2)
- with col1:
- if st.button("نعم، حذف", key=f"confirm_delete_labor_{i}"):
- current_item["labor"].pop(i)
- st.session_state.item_analysis_edited = True
- st.session_state[f"delete_labor_{i}"] = False
- st.rerun()
- with col2:
- if st.button("إلغاء", key=f"cancel_delete_labor_{i}"):
- st.session_state[f"delete_labor_{i}"] = False
- st.rerun()
+ st.pyplot(fig)
+
+ # تصدير تقرير المحتوى المحلي
+ if st.button("تصدير تقرير المحتوى المحلي", key="export_local_content_report", type="primary"):
+ # إنشاء تقرير المحتوى المحلي
+ local_content_file = export_local_content_report(
+ project_items,
+ project_info,
+ f"/tmp/local_content_{st.session_state.current_project}.xlsx"
+ )
- # عرض المجموع الكلي للعمالة
- st.markdown(f"
إجمالي تكلفة العمالة: {labor_total:,.2f} ريال
", unsafe_allow_html=True)
- else:
- st.info("لا توجد عمالة مضافة لهذا البند")
+ # عرض رابط التنزيل
+ href = get_download_link(
+ local_content_file,
+ "تحميل تقرير المحتوى المحلي",
+ "excel"
+ )
+ st.markdown(href, unsafe_allow_html=True)
- # تحليل المعدات
- st.subheader("تحليل المعدات")
+ # أزرار التنقل بين الخطوات
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("العودة إلى المراجعة النهائية ➡️", key="back_to_review"):
+ st.session_state.pricing_step = "review"
+ st.rerun()
- # إضافة معدات جديدة
- with st.expander("إضافة معدات جديدة"):
- with st.form("add_equipment_form"):
- col1, col2 = st.columns(2)
- with col1:
- new_equipment_name = st.text_input("نوع المعدات")
- new_equipment_unit = st.text_input("الوحدة", value="يوم")
- with col2:
- new_equipment_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, key="new_equipment_quantity")
- new_equipment_price = st.number_input("التكلفة اليومية (ريال)", min_value=0.0, step=10.0, key="new_equipment_price")
-
- submitted = st.form_submit_button("إضافة المعدات")
- if submitted and new_equipment_name:
- new_equipment = {
- "id": f"equipment_{len(current_item['equipment']) + 1}",
- "name": new_equipment_name,
- "unit": new_equipment_unit,
- "quantity": float(new_equipment_quantity),
- "unit_price": float(new_equipment_price),
- "total_price": float(new_equipment_quantity) * float(new_equipment_price)
- }
- current_item["equipment"].append(new_equipment)
- st.session_state.item_analysis_edited = True
- st.success("تمت إضافة المعدات بنجاح")
+ with col2:
+ if st.button("متابعة إلى تقييم المخاطر ⬅️", key="continue_to_risk_assessment", type="primary"):
+ st.session_state.pricing_step = "risk_assessment"
+ st.rerun()
+
+ st.markdown('
', unsafe_allow_html=True)
+
+ def _calculate_local_content_summary(self, project_id):
+ """حساب ملخص المحتوى المحلي للمشروع"""
+ # الحصول على بنود المشروع
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == project_id]
+
+ if not project_items:
+ return {
+ 'total_percentage': 0,
+ 'by_category': {
+ 'materials': 0,
+ 'labor': 0,
+ 'services': 0,
+ 'equipment': 0
+ }
+ }
+
+ # حساب إجمالي التكلفة
+ total_cost = sum(item['total_price'] for item in project_items)
+
+ if total_cost == 0:
+ return {
+ 'total_percentage': 0,
+ 'by_category': {
+ 'materials': 0,
+ 'labor': 0,
+ 'services': 0,
+ 'equipment': 0
+ }
+ }
+
+ # حساب المحتوى المحلي الإجمالي
+ local_content_value = sum(item['total_price'] * item.get('local_content_percentage', 0) / 100 for item in project_items)
+ total_percentage = local_content_value / total_cost * 100
+
+ # حساب المحتوى المحلي حسب الفئة
+ categories = {}
+ for category in ['materials', 'labor', 'services', 'equipment']:
+ category_items = [item for item in project_items if item.get('resource_type', '-').lower() == category]
+
+ if category_items:
+ category_cost = sum(item['total_price'] for item in category_items)
+ category_local_content = sum(item['total_price'] * item.get('local_content_percentage', 0) / 100 for item in category_items)
+ category_percentage = category_local_content / category_cost * 100 if category_cost > 0 else 0
+ categories[category] = category_percentage
+ else:
+ categories[category] = 0
+
+ # إنشاء ملخص المحتوى المحلي
+ summary = {
+ 'total_percentage': total_percentage,
+ 'by_category': categories
+ }
+
+ return summary
+
+ def _render_risk_assessment_step(self):
+ """عرض خطوة تقييم المخاطر"""
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown('
الخطوة 7: تقييم المخاطر
', unsafe_allow_html=True)
+
+ project_info = self._get_current_project_info()
+ if not project_info:
+ st.error("لم يتم العثور على المشروع. الرجاء العودة إلى الخطوة السابقة.")
+ if st.button("العودة إلى معلومات المشروع ➡️", key="back_to_project_info"):
+ st.session_state.pricing_step = "project_info"
+ st.rerun()
+ return
+
+ # الحصول على بنود المشروع الحالي
+ project_items = [item for item in st.session_state.boq_items if item['project_id'] == st.session_state.current_project]
+
+ if not project_items:
+ st.warning("لا توجد بنود في جدول الكميات. يرجى العودة إلى الخطوة السابقة لإضافة البنود.")
+
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("العودة إلى جدول الكميات ➡️", key="back_to_boq_btn"):
+ st.session_state.pricing_step = "boq"
st.rerun()
+ return
- # عرض جدول المعدات
- if current_item["equipment"]:
- # تحويل البيانات إلى DataFrame
- equipment_data = []
- for i, equipment in enumerate(current_item["equipment"]):
- equipment_data.append({
- "م": i + 1,
- "نوع المعدات": equipment["name"],
- "الوحدة": equipment["unit"],
- "عدد الأيام": equipment["quantity"],
- "التكلفة اليومية (ريال)": equipment["unit_price"],
- "الإجمالي (ريال)": equipment["total_price"],
- "الإجراءات": f' '
- })
+ # حساب إجمالي التكلفة
+ total_cost = sum(item['total_price'] for item in project_items)
+
+ # الحصول على مخاطر المشروع الحالي
+ project_risks = [risk for risk in st.session_state.risks if risk['project_id'] == st.session_state.current_project]
+
+ # استخدام علامات تبويب لتنظيم العرض
+ tabs = st.tabs(["ملخص المخاطر", "قائمة المخاطر", "إضافة مخاطرة جديدة", "تقرير المخاطر"])
+
+ with tabs[0]:
+ # عرض ملخص المخاطر
+ st.subheader("ملخص المخاطر")
- equipment_df = pd.DataFrame(equipment_data)
+ # حساب ملخص المخاطر
+ risk_summary = self._calculate_risk_summary(project_info['id'], total_cost)
- # إضافة صف المجموع
- equipment_total = sum(equipment["total_price"] for equipment in current_item["equipment"])
- total_row = pd.DataFrame([{
- "م": "",
- "نوع المعدات": "المجموع",
- "الوحدة": "",
- "عدد الأيام": "",
- "التكلفة اليومية (ريال)": "",
- "الإجمالي (ريال)": equipment_total,
- "الإجراءات": ""
- }])
+ # عرض الإحصائيات الرئيسية
+ col1, col2, col3 = st.columns(3)
- # استخدام pd.concat بدلاً من append
- equipment_df = pd.concat([equipment_df, total_row], ignore_index=True)
+ with col1:
+ st.metric("إجمالي عدد المخاطر", risk_summary['total_risks'])
- # عرض الجدول القابل للتعديل
- edited_equipment_df = st.data_editor(
- equipment_df,
- column_config={
- "م": st.column_config.NumberColumn("م", width="small"),
- "نوع المعدات": st.column_config.TextColumn("نوع المعدات"),
- "الوحدة": st.column_config.TextColumn("الوحدة", width="small"),
- "عدد الأيام": st.column_config.NumberColumn("عدد الأيام", format="%.1f", width="small"),
- "التكلفة اليومية (ريال)": st.column_config.NumberColumn("التكلفة اليومية (ريال)", format="%.2f"),
- "الإجمالي (ريال)": st.column_config.NumberColumn("الإجمالي (ريال)", format="%.2f"),
- "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
- },
- hide_index=True,
- key="equipment_table",
- on_change=self._update_total_price,
- disabled=["م", "نوع المعدات", "الوحدة", "الإجمالي (ريال)", "الإجراءات"]
- )
+ with col2:
+ st.metric("احتياطي المخاطر المقترح", f"{risk_summary['risk_contingency']:,.2f} ريال")
- # تحديث البيانات بعد التعديل
- for i, equipment in enumerate(current_item["equipment"]):
- if i < len(edited_equipment_df) - 1: # تجاهل صف المجموع
- equipment["quantity"] = edited_equipment_df.iloc[i]["عدد الأيام"]
- equipment["unit_price"] = edited_equipment_df.iloc[i]["التكلفة اليومية (ريال)"]
- equipment["total_price"] = equipment["quantity"] * equipment["unit_price"]
+ with col3:
+ st.metric("نسبة احتياطي المخاطر", f"{risk_summary['risk_contingency_percentage']:.1f}%")
- # معالجة أزرار التعديل والحذف
- for i in range(len(current_item["equipment"])):
- col1, col2 = st.columns([1, 1])
- with col1:
- if st.button(f"تعديل المعدات {i+1}", key=f"edit_equipment_btn_{i}"):
- st.session_state[f"edit_equipment_{i}"] = True
+ # عرض توزيع المخاطر حسب الشدة
+ st.subheader("توزيع المخاطر حسب الشدة")
+
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ st.metric("مخاطر عالية", risk_summary['high_risks'], delta_color="inverse")
+
+ with col2:
+ st.metric("مخاطر متوسطة", risk_summary['medium_risks'], delta_color="off")
+
+ with col3:
+ st.metric("مخاطر منخفضة", risk_summary['low_risks'], delta_color="normal")
+
+ # عرض رسم بياني للمخاطر
+ if project_risks:
+ st.subheader("توزيع المخاطر حسب الفئة")
- with col2:
- if st.button(f"حذف المعدات {i+1}", key=f"delete_equipment_btn_{i}"):
- st.session_state[f"delete_equipment_{i}"] = True
+ # تجميع المخاطر حسب الفئة
+ category_counts = {}
+ for risk in project_risks:
+ category = risk['category']
+ if category in category_counts:
+ category_counts[category] += 1
+ else:
+ category_counts[category] = 1
+
+ # إنشاء DataFrame للرسم البياني
+ chart_data = pd.DataFrame({
+ "الفئة": list(category_counts.keys()),
+ "عدد المخاطر": list(category_counts.values())
+ })
+
+ # عرض الرسم البياني
+ st.bar_chart(chart_data, x="الفئة", y="عدد المخاطر")
- # نموذج التعديل
- if st.session_state.get(f"edit_equipment_{i}", False):
- with st.form(f"edit_equipment_form_{i}"):
- equipment = current_item["equipment"][i]
+ # عرض مصفوفة المخاطر
+ st.subheader("مصفوفة المخاطر")
+
+ # إنشاء مصفوفة المخاطر
+ risk_matrix = self._create_risk_matrix(project_risks)
+
+ # عرض المصفوفة
+ st.dataframe(
+ risk_matrix,
+ column_config={
+ "": st.column_config.TextColumn(""),
+ "1": st.column_config.TextColumn("1 - ضئيل"),
+ "2": st.column_config.TextColumn("2 - منخفض"),
+ "3": st.column_config.TextColumn("3 - متوسط"),
+ "4": st.column_config.TextColumn("4 - عالي"),
+ "5": st.column_config.TextColumn("5 - حرج")
+ },
+ hide_index=True
+ )
+ else:
+ st.info("لا توجد مخاطر مسجلة للمشروع. يرجى إضافة مخاطر لعرض التحليل.")
+
+ with tabs[1]:
+ # عرض قائمة المخاطر
+ st.subheader("قائمة المخاطر")
+
+ if project_risks:
+ # عرض جدول المخاطر
+ risk_df = pd.DataFrame([
+ {
+ 'معرف': risk['id'],
+ 'اسم المخاطرة': risk['name'],
+ 'الفئة': risk['category'],
+ 'الاحتمالية': risk['probability'],
+ 'التأثير': risk['impact'],
+ 'درجة المخاطرة': risk['risk_score'],
+ 'التأثير المالي': risk['cost_impact'],
+ 'التأثير على الجدول': risk['schedule_impact'],
+ 'الحالة': risk['status']
+ }
+ for risk in project_risks
+ ])
+
+ # تنسيق الجدول
+ st.dataframe(
+ risk_df,
+ column_config={
+ 'معرف': st.column_config.NumberColumn(disabled=True),
+ 'اسم المخاطرة': st.column_config.TextColumn(),
+ 'الفئة': st.column_config.TextColumn(),
+ 'الاحتمالية': st.column_config.NumberColumn(format="%d"),
+ 'التأثير': st.column_config.NumberColumn(format="%d"),
+ 'درجة المخاطرة': st.column_config.NumberColumn(format="%d"),
+ 'التأثير المالي': st.column_config.NumberColumn(format="%,.2f ريال"),
+ 'التأثير على الجدول': st.column_config.NumberColumn(format="%d يوم"),
+ 'الحالة': st.column_config.TextColumn()
+ },
+ hide_index=True,
+ use_container_width=True
+ )
+
+ # عرض تفاصيل المخاطر
+ st.subheader("تفاصيل المخاطر")
+
+ # اختيار مخاطرة لعرض تفاصيلها
+ selected_risk_id = st.selectbox(
+ "اختر مخاطرة لعرض التفاصيل",
+ options=[risk['id'] for risk in project_risks],
+ format_func=lambda x: next((risk['name'] for risk in project_risks if risk['id'] == x), ""),
+ key="selected_risk_id"
+ )
+
+ # عرض تفاصيل المخاطرة المختارة
+ if selected_risk_id:
+ selected_risk = next((risk for risk in project_risks if risk['id'] == selected_risk_id), None)
+
+ if selected_risk:
col1, col2 = st.columns(2)
+
with col1:
- equipment_name = st.text_input("نوع المعدات", value=equipment["name"])
- equipment_unit = st.text_input("الوحدة", value=equipment["unit"])
+ st.markdown(f"**اسم المخاطرة:** {selected_risk['name']}")
+ st.markdown(f"**الفئة:** {selected_risk['category']}")
+ st.markdown(f"**الاحتمالية:** {selected_risk['probability']}")
+ st.markdown(f"**التأثير:** {selected_risk['impact']}")
+ st.markdown(f"**درجة المخاطرة:** {selected_risk['risk_score']}")
+
with col2:
- equipment_quantity = st.number_input("عدد الأيام", min_value=0.0, step=0.5, value=equipment["quantity"])
- equipment_price = st.number_input("التكلفة اليومية (ريال)", min_value=0.0, step=10.0, value=equipment["unit_price"])
+ st.markdown(f"**التأثير المالي:** {selected_risk['cost_impact']:,.2f} ريال")
+ st.markdown(f"**التأثير على الجدول الزمني:** {selected_risk['schedule_impact']} يوم")
+ st.markdown(f"**الحالة:** {selected_risk['status']}")
+ st.markdown(f"**تاريخ التحديث:** {selected_risk.get('updated_at', '-')}")
+ st.markdown(f"**الوصف:** {selected_risk.get('description', '-')}")
+ st.markdown(f"**خطة التخفيف:** {selected_risk.get('mitigation_plan', '-')}")
+ st.markdown(f"**خطة الطوارئ:** {selected_risk.get('contingency_plan', '-')}")
+
+ # أزرار تعديل وحذف المخاطرة
col1, col2 = st.columns(2)
+
with col1:
- if st.form_submit_button("حفظ التعديلات"):
- equipment["name"] = equipment_name
- equipment["unit"] = equipment_unit
- equipment["quantity"] = float(equipment_quantity)
- equipment["unit_price"] = float(equipment_price)
- equipment["total_price"] = equipment["quantity"] * equipment["unit_price"]
- st.session_state.item_analysis_edited = True
- st.session_state[f"edit_equipment_{i}"] = False
+ if st.button("تعديل المخاطرة", key="edit_risk_btn"):
+ st.session_state.edit_risk_id = selected_risk_id
st.rerun()
+
with col2:
- if st.form_submit_button("إلغاء"):
- st.session_state[f"edit_equipment_{i}"] = False
+ if st.button("حذف المخاطرة", key="delete_risk_btn"):
+ # حذف المخاطرة
+ self.storage.delete_risk(selected_risk_id)
+
+ # تحديث حالة الجلسة
+ st.session_state.risks = [r for r in st.session_state.risks if r['id'] != selected_risk_id]
+
+ # إعادة حساب ملخص المخاطر
+ self._calculate_risk_summary(project_info['id'], total_cost)
+
+ st.success(f"تم حذف المخاطرة '{selected_risk['name']}' بنجاح")
st.rerun()
- # تأكيد الحذف
- if st.session_state.get(f"delete_equipment_{i}", False):
- st.warning(f"هل أنت متأكد من حذف المعدات: {current_item['equipment'][i]['name']}؟")
- col1, col2 = st.columns(2)
- with col1:
- if st.button("نعم، حذف", key=f"confirm_delete_equipment_{i}"):
- current_item["equipment"].pop(i)
- st.session_state.item_analysis_edited = True
- st.session_state[f"delete_equipment_{i}"] = False
- st.rerun()
- with col2:
- if st.button("إلغاء", key=f"cancel_delete_equipment_{i}"):
- st.session_state[f"delete_equipment_{i}"] = False
- st.rerun()
-
- # عرض المجموع الكلي للمعدات
- st.markdown(f"
إجمالي تكلفة المعدات: {equipment_total:,.2f} ريال
", unsafe_allow_html=True)
- else:
- st.info("لا توجد معدات مضافة لهذا البند")
-
- # تحليل المقاولين من الباطن
- st.subheader("تحليل المقاولين من الباطن")
+ # نموذج تعديل المخاطرة
+ if 'edit_risk_id' in st.session_state:
+ edit_risk_id = st.session_state.edit_risk_id
+ edit_risk = next((risk for risk in project_risks if risk['id'] == edit_risk_id), None)
+
+ if edit_risk:
+ st.subheader(f"تعديل المخاطرة: {edit_risk['name']}")
+
+ with st.form(key="edit_risk_form"):
+ # حقول النموذج
+ risk_name = st.text_input("اسم المخاطرة", value=edit_risk['name'])
+ risk_description = st.text_area("وصف المخاطرة", value=edit_risk.get('description', ''))
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ risk_category = st.selectbox(
+ "فئة المخاطرة",
+ options=["مالية", "فنية", "تعاقدية", "تنظيمية", "بيئية", "أمنية", "أخرى"],
+ index=["مالية", "فنية", "تعاقدية", "تنظيمية", "بيئية", "أمنية", "أخرى"].index(edit_risk['category']) if edit_risk['category'] in ["مالية", "فنية", "تعاقدية", "تنظيمية", "بيئية", "أمنية", "أخرى"] else 0
+ )
+
+ risk_probability = st.slider(
+ "احتمالية حدوث المخاطرة",
+ min_value=1,
+ max_value=5,
+ value=edit_risk['probability'],
+ help="1: ضئيلة جداً، 5: شبه مؤكدة"
+ )
+
+ risk_impact = st.slider(
+ "تأثير المخاطرة",
+ min_value=1,
+ max_value=5,
+ value=edit_risk['impact'],
+ help="1: ضئيل جداً، 5: كارثي"
+ )
+
+ with col2:
+ risk_cost_impact = st.number_input(
+ "التأثير المالي (ريال)",
+ min_value=0.0,
+ value=float(edit_risk['cost_impact']),
+ step=1000.0
+ )
+
+ risk_schedule_impact = st.number_input(
+ "التأثير على الجدول الزمني (أيام)",
+ min_value=0,
+ value=edit_risk['schedule_impact'],
+ step=1
+ )
+
+ risk_status = st.selectbox(
+ "حالة المخاطرة",
+ options=["نشطة", "مغلقة", "تم التخفيف", "حدثت"],
+ index=["نشطة", "مغلقة", "تم التخفيف", "حدثت"].index(edit_risk['status']) if edit_risk['status'] in ["نشطة", "مغلقة", "تم التخفيف", "حدثت"] else 0
+ )
+
+ risk_mitigation_plan = st.text_area("خطة التخفيف", value=edit_risk.get('mitigation_plan', ''))
+ risk_contingency_plan = st.text_area("خطة الطوارئ", value=edit_risk.get('contingency_plan', ''))
+
+ # حساب درجة المخاطرة
+ risk_score = risk_probability * risk_impact
+ st.info(f"درجة المخاطرة: {risk_score}")
+
+ # أزرار الإجراءات
+ col1, col2 = st.columns(2)
+
+ with col1:
+ cancel_button = st.form_submit_button("إلغاء")
+
+ with col2:
+ submit_button = st.form_submit_button("حفظ التغييرات")
+
+ if cancel_button:
+ # إلغاء التعديل
+ del st.session_state.edit_risk_id
+ st.rerun()
+
+ if submit_button:
+ # التحقق من صحة البيانات
+ if not risk_name:
+ st.error("يجب إدخال اسم المخاطرة")
+ else:
+ # تحديث المخاطرة
+ updated_risk = {
+ 'name': risk_name,
+ 'description': risk_description,
+ 'category': risk_category,
+ 'probability': risk_probability,
+ 'impact': risk_impact,
+ 'risk_score': risk_score,
+ 'cost_impact': risk_cost_impact,
+ 'schedule_impact': risk_schedule_impact,
+ 'status': risk_status,
+ 'mitigation_plan': risk_mitigation_plan,
+ 'contingency_plan': risk_contingency_plan
+ }
+
+ # تحديث المخاطرة في التخزين
+ self.storage.update_risk(edit_risk_id, updated_risk)
+
+ # تحديث حالة الجلسة
+ for i, r in enumerate(st.session_state.risks):
+ if r['id'] == edit_risk_id:
+ st.session_state.risks[i].update(updated_risk)
+ break
+
+ # إعادة حساب ملخص المخاطر
+ self._calculate_risk_summary(project_info['id'], total_cost)
+
+ # إزالة معرف التعديل
+ del st.session_state.edit_risk_id
+
+ st.success(f"تم تحديث المخاطرة '{risk_name}' بنجاح")
+ st.rerun()
+ else:
+ st.info("لا توجد مخاطر مسجلة للمشروع. يرجى إضافة مخاطر باستخدام النموذج أدناه.")
- # إضافة مقاول جديد
- with st.expander("إضافة مقاول من الباطن"):
- with st.form("add_subcontractor_form"):
+ with tabs[2]:
+ # نموذج إضافة مخاطرة جديدة
+ st.subheader("إضافة مخاطرة جديدة")
+
+ with st.form(key="add_risk_form"):
+ # حقول النموذج
+ risk_name = st.text_input("اسم المخاطرة")
+ risk_description = st.text_area("وصف المخاطرة")
+
col1, col2 = st.columns(2)
+
with col1:
- new_sub_name = st.text_input("اسم المقاول")
- new_sub_work = st.text_input("نوع العمل")
+ risk_category = st.selectbox(
+ "فئة المخاطرة",
+ options=["مالية", "فنية", "تعاقدية", "تنظيمية", "بيئية", "أمنية", "أخرى"]
+ )
+
+ risk_probability = st.slider(
+ "احتمالية حدوث المخاطرة",
+ min_value=1,
+ max_value=5,
+ value=3,
+ help="1: ضئيلة جداً، 5: شبه مؤكدة"
+ )
+
+ risk_impact = st.slider(
+ "تأثير المخاطرة",
+ min_value=1,
+ max_value=5,
+ value=3,
+ help="1: ضئيل جداً، 5: كارثي"
+ )
+
with col2:
- new_sub_price = st.number_input("التكلفة الإجمالية (ريال)", min_value=0.0, step=100.0)
+ risk_cost_impact = st.number_input(
+ "التأثير المالي (ريال)",
+ min_value=0.0,
+ value=0.0,
+ step=1000.0
+ )
+
+ risk_schedule_impact = st.number_input(
+ "التأثير على الجدول الزمني (أيام)",
+ min_value=0,
+ value=0,
+ step=1
+ )
+
+ risk_status = st.selectbox(
+ "حالة المخاطرة",
+ options=["نشطة", "مغلقة", "تم التخفيف", "حدثت"],
+ index=0
+ )
- submitted = st.form_submit_button("إضافة مقاول")
- if submitted and new_sub_name and new_sub_work:
- new_sub = {
- "id": f"sub_{len(current_item['subcontractors']) + 1}",
- "name": new_sub_name,
- "work": new_sub_work,
- "quantity": 1,
- "unit_price": float(new_sub_price),
- "total_price": float(new_sub_price)
- }
- current_item["subcontractors"].append(new_sub)
- st.session_state.item_analysis_edited = True
- st.success("تمت إضافة المقاول بنجاح")
- st.rerun()
+ risk_mitigation_plan = st.text_area("خطة التخفيف")
+ risk_contingency_plan = st.text_area("خطة الطوارئ")
+
+ # حساب درجة المخاطرة
+ risk_score = risk_probability * risk_impact
+ st.info(f"درجة المخاطرة: {risk_score}")
+
+ # زر الإرسال
+ submit_button = st.form_submit_button("إضافة المخاطرة")
+
+ if submit_button:
+ # التحقق من صحة البيانات
+ if not risk_name:
+ st.error("يجب إدخال اسم المخاطرة")
+ else:
+ # إنشاء معرف فريد
+ risk_id = f"risk_{len(st.session_state.risks) + 1}"
+
+ # إنشاء كائن المخاطرة
+ new_risk = {
+ 'id': risk_id,
+ 'project_id': project_info['id'],
+ 'name': risk_name,
+ 'description': risk_description,
+ 'category': risk_category,
+ 'probability': risk_probability,
+ 'impact': risk_impact,
+ 'risk_score': risk_score,
+ 'cost_impact': risk_cost_impact,
+ 'schedule_impact': risk_schedule_impact,
+ 'status': risk_status,
+ 'mitigation_plan': risk_mitigation_plan,
+ 'contingency_plan': risk_contingency_plan
+ }
+
+ # إضافة المخاطرة إلى التخزين
+ added_risk = self.storage.add_risk(new_risk)
+
+ # تحديث حالة الجلسة
+ st.session_state.risks.append(added_risk)
+
+ # إعادة حساب ملخص المخاطر
+ self._calculate_risk_summary(project_info['id'], total_cost)
+
+ st.success(f"تم إضافة المخاطرة '{risk_name}' بنجاح")
+ st.rerun()
- # عرض جدول المقاولين
- if current_item["subcontractors"]:
- # تحويل البيانات إلى DataFrame
- subs_data = []
- for i, sub in enumerate(current_item["subcontractors"]):
- subs_data.append({
- "م": i + 1,
- "اسم المقاول": sub["name"],
- "نوع العمل": sub["work"],
- "التكلفة الإجمالية (ريال)": sub["total_price"],
- "الإجراءات": f' '
- })
+ with tabs[3]:
+ # تقرير المخاطر
+ st.subheader("تقرير المخاطر")
- subs_df = pd.DataFrame(subs_data)
-
- # إضافة صف المجموع
- subs_total = sum(sub["total_price"] for sub in current_item["subcontractors"])
- total_row = pd.DataFrame([{
- "م": "",
- "اسم المقاول": "المجموع",
- "نوع العمل": "",
- "التكلفة الإجمالية (ريال)": subs_total,
- "الإجراءات": ""
- }])
-
- # استخدام pd.concat بدلاً من append
- subs_df = pd.concat([subs_df, total_row], ignore_index=True)
-
- # عرض الجدول القابل للتعديل
- edited_subs_df = st.data_editor(
- subs_df,
- column_config={
- "م": st.column_config.NumberColumn("م", width="small"),
- "اسم المقاول": st.column_config.TextColumn("اسم المقاول"),
- "نوع العمل": st.column_config.TextColumn("نوع العمل"),
- "التكلفة الإجمالية (ريال)": st.column_config.NumberColumn("التكلفة الإجمالية (ريال)", format="%.2f"),
- "الإجراءات": st.column_config.TextColumn("الإجراءات", width="medium"),
- },
- hide_index=True,
- key="subs_table",
- on_change=self._update_total_price,
- disabled=["م", "اسم المقاول", "نوع العمل", "الإجراءات"]
- )
-
- # تحديث البيانات بعد التعديل
- for i, sub in enumerate(current_item["subcontractors"]):
- if i < len(edited_subs_df) - 1: # تجاهل صف المجموع
- sub["total_price"] = edited_subs_df.iloc[i]["التكلفة الإجمالية (ريال)"]
- sub["unit_price"] = sub["total_price"] # للمقاولين، سعر الوحدة = السعر الإجمالي
-
- # معالجة أزرار التعديل والحذف
- for i in range(len(current_item["subcontractors"])):
- col1, col2 = st.columns([1, 1])
- with col1:
- if st.button(f"تعديل المقاول {i+1}", key=f"edit_sub_btn_{i}"):
- st.session_state[f"edit_sub_{i}"] = True
+ if project_risks:
+ # عرض ملخص التقرير
+ risk_summary = project_info.get('risk_summary', self._calculate_risk_summary(project_info['id'], total_cost))
- with col2:
- if st.button(f"حذف المقاول {i+1}", key=f"delete_sub_btn_{i}"):
- st.session_state[f"delete_sub_{i}"] = True
+ st.markdown(f"""
+ ### ملخص تقرير المخاطر لمشروع: {project_info['name']}
- # نموذج التعديل
- if st.session_state.get(f"edit_sub_{i}", False):
- with st.form(f"edit_sub_form_{i}"):
- sub = current_item["subcontractors"][i]
- col1, col2 = st.columns(2)
- with col1:
- sub_name = st.text_input("اسم المقاول", value=sub["name"])
- sub_work = st.text_input("نوع العمل", value=sub["work"])
- with col2:
- sub_price = st.number_input("التكلفة الإجمالية (ريال)", min_value=0.0, step=100.0, value=sub["total_price"])
-
- col1, col2 = st.columns(2)
- with col1:
- if st.form_submit_button("حفظ التعديلات"):
- sub["name"] = sub_name
- sub["work"] = sub_work
- sub["total_price"] = float(sub_price)
- sub["unit_price"] = sub["total_price"]
- st.session_state.item_analysis_edited = True
- st.session_state[f"edit_sub_{i}"] = False
- st.rerun()
- with col2:
- if st.form_submit_button("إلغاء"):
- st.session_state[f"edit_sub_{i}"] = False
- st.rerun()
+ - **إجمالي عدد المخاطر:** {risk_summary['total_risks']}
+ - **المخاطر العالية:** {risk_summary['high_risks']}
+ - **المخاطر المتوسطة:** {risk_summary['medium_risks']}
+ - **المخاطر المنخفضة:** {risk_summary['low_risks']}
+ - **إجمالي التأثير المالي:** {risk_summary['total_cost_impact']:,.2f} ريال
+ - **إجمالي التأثير على الجدول الزمني:** {risk_summary['total_schedule_impact']} يوم
+ - **احتياطي المخاطر المقترح:** {risk_summary['risk_contingency']:,.2f} ريال ({risk_summary['risk_contingency_percentage']:.1f}% من التكلفة الإجمالية)
+ """)
- # تأكيد الحذف
- if st.session_state.get(f"delete_sub_{i}", False):
- st.warning(f"هل أنت متأكد من حذف المقاول: {current_item['subcontractors'][i]['name']}؟")
- col1, col2 = st.columns(2)
- with col1:
- if st.button("نعم، حذف", key=f"confirm_delete_sub_{i}"):
- current_item["subcontractors"].pop(i)
- st.session_state.item_analysis_edited = True
- st.session_state[f"delete_sub_{i}"] = False
- st.rerun()
- with col2:
- if st.button("إلغاء", key=f"cancel_delete_sub_{i}"):
- st.session_state[f"delete_sub_{i}"] = False
- st.rerun()
-
- # عرض المجموع الكلي للمقاولين
- st.markdown(f"
إجمالي تكلفة المقاولين من الباطن: {subs_total:,.2f} ريال
", unsafe_allow_html=True)
- else:
- st.info("لا يوجد مقاولون من الباطن مضافون لهذا البند")
+ # عرض أعلى المخاطر
+ st.subheader("أعلى المخاطر")
+
+ # ترتيب المخاطر حسب درجة المخاطرة
+ sorted_risks = sorted(project_risks, key=lambda x: x['risk_score'], reverse=True)
+
+ for i, risk in enumerate(sorted_risks[:5]): # عرض أعلى 5 مخاطر
+ risk_color = "🔴" if risk['risk_score'] >= 15 else "🟠" if risk['risk_score'] >= 8 else "🟢"
+ st.markdown(f"""
+ {risk_color} **{risk['name']}** (درجة المخاطرة: {risk['risk_score']})
+ - **الفئة:** {risk['category']}
+ - **الاحتمالية:** {risk['probability']}/5, **التأثير:** {risk['impact']}/5
+ - **التأثير المالي:** {risk['cost_impact']:,.2f} ريال
+ - **خطة التخفيف:** {risk.get('mitigation_plan', '-')}
+ """)
+
+ # زر تصدير التقرير
+ if st.button("تصدير تقرير المخاطر", key="export_risk_report_btn", type="primary"):
+ # إنشاء تقرير المخاطر
+ risk_report_file = export_risk_report(
+ project_risks,
+ project_info,
+ total_cost,
+ f"/tmp/risk_report_{st.session_state.current_project}.xlsx"
+ )
+
+ # عرض رابط التنزيل
+ href = get_download_link(
+ risk_report_file,
+ "تحميل تقرير المخاطر",
+ "excel"
+ )
+ st.markdown(href, unsafe_allow_html=True)
+ else:
+ st.info("لا توجد مخاطر مسجلة للمشروع. يرجى إضافة مخاطر لإنشاء التقرير.")
- # ملخص التكلفة
- st.subheader("ملخص التكلفة")
+ # أزرار التنقل بين الخطوات
+ col1, col2 = st.columns(2)
+ with col1:
+ if st.button("العودة إلى المحتوى المحلي ➡️", key="back_to_local_content"):
+ st.session_state.pricing_step = "local_content"
+ st.rerun()
- # حساب التكاليف
- materials_total = sum(material.get("total_price", 0) for material in current_item["materials"])
- labor_total = sum(labor.get("total_price", 0) for labor in current_item["labor"])
- equipment_total = sum(equipment.get("total_price", 0) for equipment in current_item["equipment"])
- subcontractors_total = sum(sub.get("total_price", 0) for sub in current_item["subcontractors"])
+ with col2:
+ if st.button("إنهاء وعودة للصفحة الرئيسية", key="go_to_home", type="primary"):
+ st.session_state.pricing_step = "project_info"
+ st.rerun()
- direct_cost = materials_total + labor_total + equipment_total + subcontractors_total
- overhead_profit = max(0, current_item["total_price"] - direct_cost) # تجنب القيم السالبة
+ st.markdown('
', unsafe_allow_html=True)
+
+ def _calculate_risk_summary(self, project_id, total_cost):
+ """حساب ملخص المخاطر للمشروع"""
+ # الحصول على مخاطر المشروع
+ project_risks = [risk for risk in st.session_state.risks if risk['project_id'] == project_id]
- # عرض ملخص التكلفة
- st.markdown("
", unsafe_allow_html=True)
+ # حساب عدد المخاطر حسب الشدة
+ high_risks = sum(1 for risk in project_risks if risk['risk_score'] >= 15)
+ medium_risks = sum(1 for risk in project_risks if 8 <= risk['risk_score'] < 15)
+ low_risks = sum(1 for risk in project_risks if risk['risk_score'] < 8)
- col1, col2 = st.columns(2)
- with col1:
- st.markdown("
تكلفة المواد:
", unsafe_allow_html=True)
- st.markdown("
تكلفة العمالة:
", unsafe_allow_html=True)
- st.markdown("
تكلفة المعدات:
", unsafe_allow_html=True)
- st.markdown("
تكلفة المقاولين من الباطن:
", unsafe_allow_html=True)
- st.markdown("
إجمالي التكلفة المباشرة:
", unsafe_allow_html=True)
- st.markdown("
المصاريف العمومية والربح:
", unsafe_allow_html=True)
- st.markdown("
إجمالي سعر البند:
", unsafe_allow_html=True)
+ # حساب إجمالي التأثير المالي والجدول الزمني
+ total_cost_impact = sum(risk['cost_impact'] for risk in project_risks)
+ total_schedule_impact = sum(risk['schedule_impact'] for risk in project_risks)
- with col2:
- st.markdown(f"