import os import streamlit as st import pandas as pd import plotly.figure_factory as ff from datetime import datetime, timedelta import plotly.express as px import numpy as np import io import openpyxl class ScheduleApp: def __init__(self): if 'saved_pricing' not in st.session_state: st.session_state.saved_pricing = [] if 'uploaded_files' not in st.session_state: st.session_state.uploaded_files = {} def run(self): st.title("الجدول الزمني للمشروع") # إضافة تبويب للملفات tabs = st.tabs(["جدول الكميات", "ملفات المشروع"]) with tabs[0]: self._handle_boq_tab() with tabs[1]: self._handle_project_files() def _handle_project_files(self): st.subheader("إدارة ملفات المشروع") # رفع الملفات uploaded_file = st.file_uploader( "قم برفع ملفات المشروع", type=['xls', 'xlsx', 'xml', 'xer', 'pmxml', 'mpp', 'vsdx'], help="يمكنك رفع ملفات من برامج مثل Primavera P6, Microsoft Project, Power BI, Visio" ) if uploaded_file: try: # قراءة وتحليل الملف if uploaded_file.name.endswith(('.xls', '.xlsx')): df = pd.read_excel(uploaded_file) st.session_state.uploaded_files[uploaded_file.name] = { 'data': df, 'type': 'excel', 'upload_time': datetime.now() } # عرض معلومات الملف st.success(f"تم استيراد الملف: {uploaded_file.name}") st.write("معلومات الملف:") st.write(f"- عدد الأنشطة: {len(df)}") st.write(f"- الأعمدة: {', '.join(df.columns)}") # عرض البيانات في جدول st.dataframe(df, use_container_width=True) # إنشاء مخطط جانت تفاعلي if 'Start' in df.columns and 'Finish' in df.columns: self._create_interactive_gantt(df) else: st.info(f"تم استلام الملف {uploaded_file.name}. سيتم إضافة دعم لهذا النوع من الملفات قريباً.") except Exception as e: st.error(f"حدث خطأ أثناء معالجة الملف: {str(e)}") # عرض الملفات المحفوظة if st.session_state.uploaded_files: st.subheader("الملفات المحفوظة") for filename, file_info in st.session_state.uploaded_files.items(): with st.expander(filename): st.write(f"نوع الملف: {file_info['type']}") st.write(f"تاريخ الرفع: {file_info['upload_time']}") if file_info['type'] == 'excel': st.dataframe(file_info['data'], use_container_width=True) def _create_interactive_gantt(self, df): st.subheader("مخطط جانت التفاعلي") # تحضير البيانات للمخطط df['Start'] = pd.to_datetime(df['Start']) df['Finish'] = pd.to_datetime(df['Finish']) fig = ff.create_gantt( df, colors={ 'Task': '#2196F3', 'Complete': '#4CAF50' }, index_col='Resource', show_colorbar=True, group_tasks=True, showgrid_x=True, showgrid_y=True ) fig.update_layout( title="مخطط جانت للمشروع", xaxis_title="التاريخ", yaxis_title="الأنشطة", height=600 ) st.plotly_chart(fig, use_container_width=True) def _handle_boq_tab(self): # نفس الكود السابق لمعالجة جدول الكميات source_type = st.radio("اختر مصدر جدول الكميات:", ["جدول كميات محفوظ", "رفع جدول كميات جديد"], key="boq_source") if source_type == "جدول كميات محفوظ": self._handle_saved_boq() else: self._handle_new_boq() def _handle_saved_boq(self): if not st.session_state.saved_pricing: st.warning("لا توجد جداول كميات محفوظة.") return projects = [(p['project_name'], i) for i, p in enumerate(st.session_state.saved_pricing)] selected_project_name = st.selectbox("اختر المشروع", [p[0] for p in projects]) project_index = next(p[1] for p in projects if p[0] == selected_project_name) project = st.session_state.saved_pricing[project_index] self._display_project_schedule(project) def _handle_new_boq(self): uploaded_file = st.file_uploader("قم برفع ملف Excel لجدول الكميات", type=['xlsx', 'xls']) if uploaded_file: try: df = pd.read_excel(uploaded_file) project = self._create_project_from_boq(df) self._display_project_schedule(project) except Exception as e: st.error(f"حدث خطأ أثناء قراءة الملف: {str(e)}") def _create_project_from_boq(self, df): return { 'project_name': 'مشروع جديد', 'items': [row.to_dict() for _, row in df.iterrows()], 'total_price': df.get('الإجمالي', df.get('total_price', 0)).sum(), 'project_duration': 180 } def _display_project_schedule(self, project): if not project: return st.subheader("تفاصيل المشروع") # عرض التفاصيل الأساسية col1, col2, col3 = st.columns(3) with col1: st.write(f"اسم المشروع: {project['project_name']}") st.write(f"رقم المشروع: {project.get('project_number', 'غير محدد')}") st.write(f"العميل: {project.get('client', 'غير محدد')}") with col2: st.write(f"إجمالي القيمة: {project['total_price']:,.2f} ريال") st.write(f"الموقع: {project.get('location', 'غير محدد')}") st.write(f"الحالة: {project.get('status', 'قيد التنفيذ')}") with col3: project['project_duration'] = st.number_input( "مدة المشروع (بالأيام)", min_value=30, max_value=1800, value=project.get('project_duration', 180) ) st.write(f"تاريخ البدء: {project.get('start_date', 'غير محدد')}") st.write(f"تاريخ الانتهاء المتوقع: {project.get('end_date', 'غير محدد')}") # عرض وصف المشروع إذا كان متوفراً if project.get('description'): st.write("وصف المشروع:") st.write(project['description']) self._generate_and_display_schedule(project) def _generate_and_display_schedule(self, project): if 'schedule_items' not in project: self._initialize_schedule_items(project) st.subheader("تحرير الجدول الزمني") edited_df = self._edit_schedule(project['schedule_items']) project['schedule_items'] = edited_df.to_dict('records') self._display_gantt_chart(project['schedule_items']) self._display_progress_report(project['schedule_items']) def _initialize_schedule_items(self, project): project['schedule_items'] = [] start_date = datetime.now() total_project_price = project.get('total_price', 0) if total_project_price == 0: total_project_price = sum(item.get('total_price', 0) for item in project['items']) project['total_price'] = total_project_price current_date = start_date for item in project['items']: item_price = item.get('total_price', 0) if total_project_price > 0: duration = max(1, int((item_price / total_project_price) * project['project_duration'])) else: duration = max(1, int(project['project_duration'] / len(project['items']))) end_date = current_date + timedelta(days=duration) # تجميع المواد المطلوبة من تحليل البند materials_list = [] if 'materials' in item: for material in item['materials']: materials_list.append(f"{material['name']} - {material['quantity']} {material['unit']}") # إنشاء وصف تفصيلي للبند من جدول الكميات # البحث باستخدام وصف البند boq_item = next((x for x in project.get('items', []) if x.get('description', '').strip().lower() == item.get('description', '').strip().lower() or (x.get('code', '') == item.get('code', '') if item.get('code') else False)), None) if boq_item: task_description = f"البند: {boq_item.get('code', '')} - {boq_item.get('description', '')}\n" task_description += f"الكمية: {boq_item.get('quantity', 0)} {boq_item.get('unit', '')}\n" task_description += f"سعر الوحدة: {boq_item.get('unit_price', 0):,.2f} ريال\n" task_description += f"الإجمالي: {boq_item.get('total_price', 0):,.2f} ريال" else: task_description = f"{item.get('code', '')} - {item.get('description', '')}" schedule_item = { 'Task': task_description, 'Start': current_date.strftime('%Y-%m-%d'), 'Finish': end_date.strftime('%Y-%m-%d'), 'Duration': duration, 'Progress': 0, 'Resource': item.get('category', 'الموارد الأساسية'), 'materials': ', '.join(materials_list) if materials_list else '' } project['schedule_items'].append(schedule_item) current_date = end_date def _edit_schedule(self, schedule_items): df = pd.DataFrame(schedule_items) # تحويل التواريخ من نص إلى تاريخ df['Start'] = pd.to_datetime(df['Start']) df['Finish'] = pd.to_datetime(df['Finish']) # إضافة عمود للمواد المطلوبة if 'materials' not in df.columns: df['materials'] = '' # حساب المدة بين تاريخ البداية والنهاية df['Duration'] = (pd.to_datetime(df['Finish']) - pd.to_datetime(df['Start'])).dt.days edited_df = st.data_editor( df, column_config={ "Task": st.column_config.TextColumn( "البند", width="large", help="وصف البند" ), "Start": st.column_config.DatetimeColumn( "تاريخ البداية", format="YYYY-MM-DD", step=86400 ), "Finish": st.column_config.DatetimeColumn( "تاريخ النهاية", format="YYYY-MM-DD", step=86400 ), "Duration": st.column_config.NumberColumn( "المدة (أيام)", min_value=1, format="%d" ), "Progress": st.column_config.ProgressColumn( "نسبة الإنجاز", min_value=0, max_value=100, format="%d%%" ), "Resource": st.column_config.TextColumn( "الموارد", width="medium" ), "materials": st.column_config.TextColumn( "المواد المطلوبة", width="large" ) }, num_rows="dynamic", use_container_width=True, hide_index=True, key="schedule_editor" ) return edited_df def _display_gantt_chart(self, schedule_items): st.subheader("مخطط جانت") df = pd.DataFrame(schedule_items) # تحويل التواريخ إلى datetime df['Start'] = pd.to_datetime(df['Start']) df['Finish'] = pd.to_datetime(df['Finish']) # تأكد من أن كل القيم في عمود Resource هي نصوص df['Resource'] = df['Resource'].astype(str) # إنشاء رقم تسلسلي كمؤشر df['ID'] = range(len(df)) # تحديد الألوان للمهام colors = ['rgb(46, 137, 205)', 'rgb(82, 189, 97)', 'rgb(241, 196, 15)', 'rgb(231, 76, 60)'] fig = ff.create_gantt( df, colors=colors, index_col='ID', show_colorbar=False, # إخفاء شريط الألوان group_tasks=True, showgrid_x=True, showgrid_y=True ) fig.update_layout( title="مخطط جانت للمشروع", xaxis_title="التاريخ", yaxis_title="البنود", height=600 ) st.plotly_chart(fig, use_container_width=True) def _display_progress_report(self, schedule_items): st.subheader("تقرير تقدم المشروع") df = pd.DataFrame(schedule_items) avg_progress = df['Progress'].mean() if 'Progress' in df.columns else 0 col1, col2, col3 = st.columns(3) with col1: st.metric("متوسط نسبة الإنجاز", f"{avg_progress:.1f}%") with col2: completed_tasks = len(df[df['Progress'] == 100]) if 'Progress' in df.columns else 0 st.metric("البنود المكتملة", f"{completed_tasks} من {len(df)}") with col3: not_started = len(df[df['Progress'] == 0]) if 'Progress' in df.columns else len(df) st.metric("البنود غير المبدوءة", not_started) # أزرار الحفظ والتصدير col1, col2 = st.columns(2) with col1: if st.button("💾 حفظ الجدول الزمني", key="save_schedule_btn"): self._save_schedule_to_db(df) st.success("تم حفظ الجدول الزمني بنجاح!") with col2: if st.button("📊 تصدير إلى Excel", key="export_schedule_btn"): self._export_schedule_to_excel(df) def _save_schedule_to_db(self, df): """حفظ الجدول الزمني في قاعدة البيانات""" try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") schedule_data = { 'timestamp': timestamp, 'project_name': st.session_state.current_project.get('name', 'مشروع جديد'), 'schedule_items': df.to_dict('records') } if 'saved_schedules' not in st.session_state: st.session_state.saved_schedules = [] st.session_state.saved_schedules.append(schedule_data) except Exception as e: st.error(f"حدث خطأ أثناء حفظ الجدول الزمني: {str(e)}") def _export_schedule_to_excel(self, df): """تصدير الجدول الزمني إلى ملف Excel""" try: export_path = "data/exports" os.makedirs(export_path, exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") excel_file = f"{export_path}/schedule_{timestamp}.xlsx" # إنشاء كاتب Excel with pd.ExcelWriter(excel_file, engine='openpyxl') as writer: df.to_excel(writer, index=False, sheet_name='الجدول الزمني') worksheet = writer.sheets['الجدول الزمني'] # إضافة معلومات الملخص worksheet['A1'] = f"اسم المشروع: {st.session_state.current_project.get('name', 'مشروع جديد')}" worksheet['A2'] = f"تاريخ التصدير: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" # توفير زر التحميل with open(excel_file, 'rb') as f: excel_data = f.read() st.download_button( label="تحميل ملف Excel", data=excel_data, file_name=f"schedule_{timestamp}.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) st.success("تم تصدير الجدول الزمني بنجاح!") except Exception as e: st.error(f"حدث خطأ أثناء تصدير الجدول الزمني: {str(e)}")