Wahbi-AI / modules /scheduling /schedule_app.py
EGYADMIN's picture
Upload 74 files
e305028 verified
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)}")