|
|
|
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 = st.columns(2) |
|
with col1: |
|
st.write(f"اسم المشروع: {project['project_name']}") |
|
st.write(f"إجمالي القيمة: {project['total_price']:,.2f} ريال") |
|
with col2: |
|
project['project_duration'] = st.number_input( |
|
"مدة المشروع (بالأيام)", |
|
min_value=30, |
|
max_value=1800, |
|
value=project.get('project_duration', 180) |
|
) |
|
|
|
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'] = [] |
|
for item in project['items']: |
|
relative_duration = int((item['total_price'] / project['total_price']) * project['project_duration']) |
|
schedule_item = { |
|
'Task': item.get('description', ''), |
|
'Start': datetime.now().strftime('%Y-%m-%d'), |
|
'Finish': (datetime.now() + timedelta(days=relative_duration)).strftime('%Y-%m-%d'), |
|
'Duration': relative_duration, |
|
'Dependencies': '', |
|
'Progress': 0, |
|
'Resource': '' |
|
} |
|
project['schedule_items'].append(schedule_item) |
|
|
|
def _edit_schedule(self, schedule_items): |
|
return st.data_editor( |
|
pd.DataFrame(schedule_items), |
|
column_config={ |
|
"Task": "البند", |
|
"Start": st.column_config.DateColumn("تاريخ البداية"), |
|
"Finish": st.column_config.DateColumn("تاريخ النهاية"), |
|
"Duration": "المدة (أيام)", |
|
"Dependencies": "الاعتماديات", |
|
"Progress": st.column_config.NumberColumn("نسبة الإنجاز %", min_value=0, max_value=100), |
|
"Resource": "الموارد" |
|
}, |
|
use_container_width=True, |
|
hide_index=True |
|
) |
|
|
|
def _display_gantt_chart(self, schedule_items): |
|
st.subheader("مخطط جانت") |
|
df = pd.DataFrame(schedule_items) |
|
fig = ff.create_gantt( |
|
df, |
|
colors={ |
|
'Complete': 'rgb(0, 255, 100)', |
|
'Incomplete': 'rgb(160, 160, 160)' |
|
}, |
|
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 _display_progress_report(self, schedule_items): |
|
st.subheader("تقرير تقدم المشروع") |
|
df = pd.DataFrame(schedule_items) |
|
avg_progress = df['Progress'].mean() |
|
|
|
col1, col2, col3 = st.columns(3) |
|
with col1: |
|
st.metric("متوسط نسبة الإنجاز", f"{avg_progress:.1f}%") |
|
with col2: |
|
completed_tasks = len(df[df['Progress'] == 100]) |
|
st.metric("البنود المكتملة", f"{completed_tasks} من {len(df)}") |
|
with col3: |
|
not_started = len(df[df['Progress'] == 0]) |
|
st.metric("البنود غير المبدوءة", not_started) |
|
|