|
""" |
|
وحدة الموارد - التطبيق الرئيسي |
|
""" |
|
|
|
import streamlit as st |
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
from datetime import datetime, timedelta |
|
import time |
|
import io |
|
import os |
|
import json |
|
import base64 |
|
from pathlib import Path |
|
|
|
class ResourcesApp: |
|
"""وحدة الموارد""" |
|
|
|
def __init__(self): |
|
"""تهيئة وحدة الموارد""" |
|
|
|
|
|
if 'resources_data' not in st.session_state: |
|
|
|
np.random.seed(42) |
|
|
|
|
|
n_employees = 50 |
|
employee_ids = [f"EMP-{i+1:03d}" for i in range(n_employees)] |
|
employee_names = [ |
|
"أحمد محمد", "محمد علي", "علي إبراهيم", "إبراهيم خالد", "خالد عبدالله", |
|
"عبدالله سعد", "سعد فهد", "فهد ناصر", "ناصر سلطان", "سلطان عمر", |
|
"عمر يوسف", "يوسف عبدالرحمن", "عبدالرحمن حسن", "حسن أحمد", "أحمد عبدالعزيز", |
|
"عبدالعزيز سعود", "سعود فيصل", "فيصل تركي", "تركي بندر", "بندر سلمان", |
|
"سلمان محمد", "محمد عبدالله", "عبدالله فهد", "فهد سعد", "سعد خالد", |
|
"خالد علي", "علي عمر", "عمر سعيد", "سعيد ماجد", "ماجد فارس", |
|
"فارس نايف", "نايف سامي", "سامي راشد", "راشد وليد", "وليد هاني", |
|
"هاني زياد", "زياد طارق", "طارق عادل", "عادل فراس", "فراس باسم", |
|
"باسم جمال", "جمال كريم", "كريم نبيل", "نبيل هشام", "هشام عماد", |
|
"عماد أيمن", "أيمن رامي", "رامي سمير", "سمير وائل", "وائل مازن" |
|
] |
|
employee_departments = np.random.choice(["الهندسة", "المشتريات", "المالية", "الموارد البشرية", "تقنية المعلومات", "التسويق", "المبيعات"], n_employees) |
|
employee_positions = np.random.choice(["مدير", "مهندس", "محاسب", "مشرف", "أخصائي", "مساعد", "فني"], n_employees) |
|
employee_skills = [ |
|
np.random.choice(["إدارة المشاريع", "التصميم الهندسي", "تحليل البيانات", "إدارة العقود", "التخطيط الاستراتيجي"], |
|
size=np.random.randint(1, 4), |
|
replace=False).tolist() |
|
for _ in range(n_employees) |
|
] |
|
employee_experiences = np.random.randint(1, 20, n_employees) |
|
employee_costs = np.random.randint(5000, 25000, n_employees) |
|
employee_availabilities = np.random.choice([True, False], n_employees, p=[0.8, 0.2]) |
|
employee_ratings = np.random.uniform(3.0, 5.0, n_employees) |
|
|
|
|
|
employees_data = { |
|
"رقم الموظف": employee_ids, |
|
"اسم الموظف": employee_names, |
|
"القسم": employee_departments, |
|
"المنصب": employee_positions, |
|
"المهارات": employee_skills, |
|
"سنوات الخبرة": employee_experiences, |
|
"التكلفة الشهرية": employee_costs, |
|
"متاح": employee_availabilities, |
|
"التقييم": employee_ratings |
|
} |
|
|
|
|
|
n_equipment = 30 |
|
equipment_ids = [f"EQ-{i+1:03d}" for i in range(n_equipment)] |
|
equipment_names = [ |
|
"حفارة كبيرة", "حفارة صغيرة", "جرافة", "شاحنة نقل", "رافعة كبيرة", |
|
"رافعة متوسطة", "رافعة صغيرة", "خلاطة خرسانة", "مضخة خرسانة", "مولد كهرباء كبير", |
|
"مولد كهرباء متوسط", "مولد كهرباء صغير", "ضاغط هواء", "آلة لحام", "معدات قياس", |
|
"معدات اختبار", "سقالات", "قوالب خرسانية", "معدات سباكة", "معدات كهربائية", |
|
"معدات تكييف", "معدات تدفئة", "معدات إضاءة", "معدات سلامة", "معدات إطفاء", |
|
"سيارة نقل صغيرة", "سيارة نقل متوسطة", "سيارة نقل كبيرة", "معدات حفر يدوية", "معدات بناء يدوية" |
|
] |
|
equipment_types = np.random.choice(["حفر", "نقل", "رفع", "خرسانة", "كهرباء", "قياس", "بناء", "سلامة"], n_equipment) |
|
equipment_costs = np.random.randint(500, 5000, n_equipment) |
|
equipment_availabilities = np.random.choice([True, False], n_equipment, p=[0.7, 0.3]) |
|
equipment_conditions = np.random.choice(["ممتاز", "جيد", "متوسط", "سيء"], n_equipment, p=[0.4, 0.3, 0.2, 0.1]) |
|
equipment_locations = np.random.choice(["المستودع", "موقع المشروع 1", "موقع المشروع 2", "موقع المشروع 3", "في الصيانة"], n_equipment) |
|
|
|
|
|
equipment_data = { |
|
"رقم المعدة": equipment_ids, |
|
"اسم المعدة": equipment_names, |
|
"النوع": equipment_types, |
|
"التكلفة اليومية": equipment_costs, |
|
"متاحة": equipment_availabilities, |
|
"الحالة": equipment_conditions, |
|
"الموقع": equipment_locations |
|
} |
|
|
|
|
|
n_materials = 40 |
|
material_ids = [f"MAT-{i+1:03d}" for i in range(n_materials)] |
|
material_names = [ |
|
"خرسانة جاهزة", "حديد تسليح", "طابوق", "أسمنت", "رمل", "بحص", "خشب", "ألمنيوم", "زجاج", "دهان", |
|
"سيراميك", "رخام", "جبس", "عازل مائي", "عازل حراري", "أنابيب PVC", "أسلاك كهربائية", "مفاتيح كهربائية", |
|
"إنارة", "تكييف", "مصاعد", "أبواب خشبية", "أبواب حديدية", "نوافذ ألمنيوم", "نوافذ زجاجية", |
|
"أرضيات خشبية", "أرضيات بلاط", "أرضيات رخام", "أرضيات سيراميك", "أرضيات بورسلين", |
|
"دهان داخلي", "دهان خارجي", "مواد عزل", "مواد تشطيب", "مواد كهربائية", "مواد سباكة", |
|
"مواد تكييف", "مواد إضاءة", "مواد سلامة", "مواد متنوعة" |
|
] |
|
material_units = np.random.choice(["م3", "طن", "م2", "كجم", "لتر", "قطعة", "متر"], n_materials) |
|
material_quantities = np.random.randint(10, 1000, n_materials) |
|
material_costs = np.random.randint(50, 5000, n_materials) |
|
material_suppliers = np.random.choice(["المورد 1", "المورد 2", "المورد 3", "المورد 4", "المورد 5"], n_materials) |
|
material_lead_times = np.random.randint(1, 30, n_materials) |
|
|
|
|
|
materials_data = { |
|
"رقم المادة": material_ids, |
|
"اسم المادة": material_names, |
|
"الوحدة": material_units, |
|
"الكمية المتاحة": material_quantities, |
|
"تكلفة الوحدة": material_costs, |
|
"المورد": material_suppliers, |
|
"مدة التوريد (يوم)": material_lead_times |
|
} |
|
|
|
|
|
n_projects = 10 |
|
project_ids = [f"PRJ-{i+1:03d}" for i in range(n_projects)] |
|
project_names = [ |
|
"مشروع إنشاء مبنى إداري", "مشروع إنشاء مبنى سكني", "مشروع إنشاء مدرسة", |
|
"مشروع إنشاء مستشفى", "مشروع تطوير طرق", "مشروع إنشاء جسر", |
|
"مشروع بنية تحتية", "مشروع إنشاء مركز تجاري", "مشروع إنشاء فندق", |
|
"مشروع إنشاء مصنع" |
|
] |
|
project_locations = np.random.choice(["الرياض", "جدة", "الدمام", "مكة", "المدينة", "أبها", "تبوك"], n_projects) |
|
project_start_dates = [ |
|
(datetime.now() - timedelta(days=np.random.randint(0, 180))).strftime("%Y-%m-%d") |
|
for _ in range(n_projects) |
|
] |
|
project_end_dates = [ |
|
(datetime.strptime(start_date, "%Y-%m-%d") + timedelta(days=np.random.randint(180, 720))).strftime("%Y-%m-%d") |
|
for start_date in project_start_dates |
|
] |
|
project_budgets = np.random.randint(1000000, 50000000, n_projects) |
|
project_statuses = np.random.choice(["قيد التنفيذ", "مكتمل", "متوقف", "مخطط"], n_projects) |
|
|
|
|
|
projects_data = { |
|
"رقم المشروع": project_ids, |
|
"اسم المشروع": project_names, |
|
"الموقع": project_locations, |
|
"تاريخ البدء": project_start_dates, |
|
"تاريخ الانتهاء": project_end_dates, |
|
"الميزانية": project_budgets, |
|
"الحالة": project_statuses |
|
} |
|
|
|
|
|
n_allocations = 100 |
|
allocation_ids = [f"ALLOC-{i+1:03d}" for i in range(n_allocations)] |
|
allocation_projects = np.random.choice(project_ids, n_allocations) |
|
allocation_resource_types = np.random.choice(["موظف", "معدة", "مادة"], n_allocations) |
|
allocation_resource_ids = [] |
|
for res_type in allocation_resource_types: |
|
if res_type == "موظف": |
|
allocation_resource_ids.append(np.random.choice(employee_ids)) |
|
elif res_type == "معدة": |
|
allocation_resource_ids.append(np.random.choice(equipment_ids)) |
|
else: |
|
allocation_resource_ids.append(np.random.choice(material_ids)) |
|
|
|
allocation_start_dates = [ |
|
(datetime.now() - timedelta(days=np.random.randint(0, 90))).strftime("%Y-%m-%d") |
|
for _ in range(n_allocations) |
|
] |
|
allocation_end_dates = [ |
|
(datetime.strptime(start_date, "%Y-%m-%d") + timedelta(days=np.random.randint(30, 180))).strftime("%Y-%m-%d") |
|
for start_date in allocation_start_dates |
|
] |
|
allocation_quantities = np.random.randint(1, 10, n_allocations) |
|
allocation_costs = np.random.randint(5000, 50000, n_allocations) |
|
|
|
|
|
allocations_data = { |
|
"رقم التخصيص": allocation_ids, |
|
"رقم المشروع": allocation_projects, |
|
"نوع المورد": allocation_resource_types, |
|
"رقم المورد": allocation_resource_ids, |
|
"تاريخ البدء": allocation_start_dates, |
|
"تاريخ الانتهاء": allocation_end_dates, |
|
"الكمية": allocation_quantities, |
|
"التكلفة": allocation_costs |
|
} |
|
|
|
|
|
st.session_state.resources_data = { |
|
"employees": pd.DataFrame(employees_data), |
|
"equipment": pd.DataFrame(equipment_data), |
|
"materials": pd.DataFrame(materials_data), |
|
"projects": pd.DataFrame(projects_data), |
|
"allocations": pd.DataFrame(allocations_data) |
|
} |
|
|
|
def render(self): |
|
"""عرض واجهة وحدة الموارد""" |
|
|
|
st.markdown("<h1 class='module-title'>وحدة الموارد</h1>", unsafe_allow_html=True) |
|
|
|
tabs = st.tabs([ |
|
"لوحة المعلومات", |
|
"الموارد البشرية", |
|
"المعدات", |
|
"المواد", |
|
"تخصيص الموارد", |
|
"تخطيط الموارد" |
|
]) |
|
|
|
with tabs[0]: |
|
self._render_dashboard_tab() |
|
|
|
with tabs[1]: |
|
self._render_human_resources_tab() |
|
|
|
with tabs[2]: |
|
self._render_equipment_tab() |
|
|
|
with tabs[3]: |
|
self._render_materials_tab() |
|
|
|
with tabs[4]: |
|
self._render_resource_allocation_tab() |
|
|
|
with tabs[5]: |
|
self._render_resource_planning_tab() |
|
|
|
def _render_dashboard_tab(self): |
|
"""عرض تبويب لوحة المعلومات""" |
|
|
|
st.markdown("### لوحة معلومات الموارد") |
|
|
|
|
|
employees_df = st.session_state.resources_data["employees"] |
|
equipment_df = st.session_state.resources_data["equipment"] |
|
materials_df = st.session_state.resources_data["materials"] |
|
projects_df = st.session_state.resources_data["projects"] |
|
allocations_df = st.session_state.resources_data["allocations"] |
|
|
|
|
|
st.markdown("#### مؤشرات الأداء الرئيسية") |
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
total_employees = len(employees_df) |
|
available_employees = len(employees_df[employees_df["متاح"] == True]) |
|
st.metric("الموظفون", f"{available_employees}/{total_employees}") |
|
|
|
with col2: |
|
total_equipment = len(equipment_df) |
|
available_equipment = len(equipment_df[equipment_df["متاحة"] == True]) |
|
st.metric("المعدات", f"{available_equipment}/{total_equipment}") |
|
|
|
with col3: |
|
total_materials = len(materials_df) |
|
low_stock_materials = len(materials_df[materials_df["الكمية المتاحة"] < 50]) |
|
st.metric("المواد", f"{total_materials}", f"-{low_stock_materials} منخفضة المخزون") |
|
|
|
with col4: |
|
total_projects = len(projects_df) |
|
active_projects = len(projects_df[projects_df["الحالة"] == "قيد التنفيذ"]) |
|
st.metric("المشاريع النشطة", f"{active_projects}/{total_projects}") |
|
|
|
|
|
st.markdown("#### توزيع الموارد البشرية حسب القسم") |
|
|
|
dept_counts = employees_df["القسم"].value_counts().reset_index() |
|
dept_counts.columns = ["القسم", "العدد"] |
|
|
|
fig = px.pie( |
|
dept_counts, |
|
values="العدد", |
|
names="القسم", |
|
title="توزيع الموظفين حسب القسم", |
|
color="القسم" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع المعدات حسب النوع") |
|
|
|
type_counts = equipment_df["النوع"].value_counts().reset_index() |
|
type_counts.columns = ["النوع", "العدد"] |
|
|
|
fig = px.bar( |
|
type_counts, |
|
x="النوع", |
|
y="العدد", |
|
title="توزيع المعدات حسب النوع", |
|
color="النوع", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع المواد حسب المورد") |
|
|
|
supplier_counts = materials_df["المورد"].value_counts().reset_index() |
|
supplier_counts.columns = ["المورد", "العدد"] |
|
|
|
fig = px.pie( |
|
supplier_counts, |
|
values="العدد", |
|
names="المورد", |
|
title="توزيع المواد حسب المورد", |
|
color="المورد" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع تكاليف الموارد") |
|
|
|
|
|
total_employee_cost = employees_df["التكلفة الشهرية"].sum() |
|
|
|
|
|
total_equipment_cost = equipment_df["التكلفة اليومية"].sum() * 30 |
|
|
|
|
|
total_material_cost = (materials_df["الكمية المتاحة"] * materials_df["تكلفة الوحدة"]).sum() |
|
|
|
|
|
cost_distribution = pd.DataFrame({ |
|
"نوع المورد": ["الموظفون", "المعدات", "المواد"], |
|
"التكلفة": [total_employee_cost, total_equipment_cost, total_material_cost] |
|
}) |
|
|
|
fig = px.pie( |
|
cost_distribution, |
|
values="التكلفة", |
|
names="نوع المورد", |
|
title="توزيع تكاليف الموارد", |
|
color="نوع المورد", |
|
color_discrete_map={ |
|
"الموظفون": "#3498db", |
|
"المعدات": "#2ecc71", |
|
"المواد": "#f39c12" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### تخصيص الموارد للمشاريع") |
|
|
|
|
|
project_allocations = allocations_df["رقم المشروع"].value_counts().reset_index() |
|
project_allocations.columns = ["رقم المشروع", "عدد الموارد المخصصة"] |
|
|
|
|
|
project_allocations = project_allocations.merge( |
|
projects_df[["رقم المشروع", "اسم المشروع", "الحالة"]], |
|
on="رقم المشروع", |
|
how="left" |
|
) |
|
|
|
fig = px.bar( |
|
project_allocations, |
|
x="اسم المشروع", |
|
y="عدد الموارد المخصصة", |
|
title="تخصيص الموارد للمشاريع", |
|
color="الحالة", |
|
text_auto=True, |
|
color_discrete_map={ |
|
"قيد التنفيذ": "#3498db", |
|
"مكتمل": "#2ecc71", |
|
"متوقف": "#e74c3c", |
|
"مخطط": "#f39c12" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع أنواع الموارد المخصصة") |
|
|
|
resource_type_counts = allocations_df["نوع المورد"].value_counts().reset_index() |
|
resource_type_counts.columns = ["نوع المورد", "العدد"] |
|
|
|
fig = px.pie( |
|
resource_type_counts, |
|
values="العدد", |
|
names="نوع المورد", |
|
title="توزيع أنواع الموارد المخصصة", |
|
color="نوع المورد", |
|
color_discrete_map={ |
|
"موظف": "#3498db", |
|
"معدة": "#2ecc71", |
|
"مادة": "#f39c12" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
def _render_human_resources_tab(self): |
|
"""عرض تبويب الموارد البشرية""" |
|
|
|
st.markdown("### إدارة الموارد البشرية") |
|
|
|
|
|
employees_df = st.session_state.resources_data["employees"] |
|
|
|
|
|
st.markdown("#### خيارات التصفية") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
selected_departments = st.multiselect( |
|
"القسم", |
|
options=employees_df["القسم"].unique(), |
|
default=employees_df["القسم"].unique() |
|
) |
|
|
|
with col2: |
|
selected_positions = st.multiselect( |
|
"المنصب", |
|
options=employees_df["المنصب"].unique(), |
|
default=employees_df["المنصب"].unique() |
|
) |
|
|
|
with col3: |
|
availability_filter = st.selectbox( |
|
"الإتاحة", |
|
options=["الكل", "متاح فقط", "غير متاح فقط"] |
|
) |
|
|
|
|
|
filtered_df = employees_df[ |
|
employees_df["القسم"].isin(selected_departments) & |
|
employees_df["المنصب"].isin(selected_positions) |
|
] |
|
|
|
if availability_filter == "متاح فقط": |
|
filtered_df = filtered_df[filtered_df["متاح"] == True] |
|
elif availability_filter == "غير متاح فقط": |
|
filtered_df = filtered_df[filtered_df["متاح"] == False] |
|
|
|
|
|
st.markdown("#### قائمة الموظفين") |
|
|
|
st.dataframe( |
|
filtered_df, |
|
column_config={ |
|
"رقم الموظف": st.column_config.TextColumn("رقم الموظف"), |
|
"اسم الموظف": st.column_config.TextColumn("اسم الموظف"), |
|
"القسم": st.column_config.TextColumn("القسم"), |
|
"المنصب": st.column_config.TextColumn("المنصب"), |
|
"المهارات": st.column_config.ListColumn("المهارات"), |
|
"سنوات الخبرة": st.column_config.NumberColumn("سنوات الخبرة"), |
|
"التكلفة الشهرية": st.column_config.NumberColumn("التكلفة الشهرية", format="%.2f ريال"), |
|
"متاح": st.column_config.CheckboxColumn("متاح"), |
|
"التقييم": st.column_config.ProgressColumn("التقييم", min_value=0, max_value=5) |
|
}, |
|
use_container_width=True, |
|
hide_index=True |
|
) |
|
|
|
|
|
st.markdown("#### إحصائيات الموارد البشرية") |
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
total_employees = len(filtered_df) |
|
st.metric("إجمالي الموظفين", f"{total_employees}") |
|
|
|
with col2: |
|
available_employees = len(filtered_df[filtered_df["متاح"] == True]) |
|
availability_rate = available_employees / total_employees * 100 if total_employees > 0 else 0 |
|
st.metric("معدل الإتاحة", f"{availability_rate:.1f}%") |
|
|
|
with col3: |
|
avg_experience = filtered_df["سنوات الخبرة"].mean() |
|
st.metric("متوسط سنوات الخبرة", f"{avg_experience:.1f} سنة") |
|
|
|
with col4: |
|
avg_cost = filtered_df["التكلفة الشهرية"].mean() |
|
st.metric("متوسط التكلفة الشهرية", f"{avg_cost:.0f} ريال") |
|
|
|
|
|
st.markdown("#### توزيع الموظفين حسب القسم") |
|
|
|
dept_counts = filtered_df["القسم"].value_counts().reset_index() |
|
dept_counts.columns = ["القسم", "العدد"] |
|
|
|
fig = px.bar( |
|
dept_counts, |
|
x="القسم", |
|
y="العدد", |
|
title="توزيع الموظفين حسب القسم", |
|
color="القسم", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع الموظفين حسب المنصب") |
|
|
|
position_counts = filtered_df["المنصب"].value_counts().reset_index() |
|
position_counts.columns = ["المنصب", "العدد"] |
|
|
|
fig = px.pie( |
|
position_counts, |
|
values="العدد", |
|
names="المنصب", |
|
title="توزيع الموظفين حسب المنصب", |
|
color="المنصب" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع الموظفين حسب سنوات الخبرة") |
|
|
|
|
|
experience_bins = [0, 3, 5, 10, 15, 20] |
|
experience_labels = ["أقل من 3 سنوات", "3-5 سنوات", "6-10 سنوات", "11-15 سنة", "أكثر من 15 سنة"] |
|
|
|
filtered_df["فئة الخبرة"] = pd.cut(filtered_df["سنوات الخبرة"], bins=experience_bins, labels=experience_labels, right=False) |
|
|
|
experience_counts = filtered_df["فئة الخبرة"].value_counts().reset_index() |
|
experience_counts.columns = ["فئة الخبرة", "العدد"] |
|
|
|
fig = px.bar( |
|
experience_counts, |
|
x="فئة الخبرة", |
|
y="العدد", |
|
title="توزيع الموظفين حسب سنوات الخبرة", |
|
color="فئة الخبرة", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع المهارات") |
|
|
|
|
|
all_skills = [] |
|
for skills_list in filtered_df["المهارات"]: |
|
all_skills.extend(skills_list) |
|
|
|
skill_counts = pd.Series(all_skills).value_counts().reset_index() |
|
skill_counts.columns = ["المهارة", "العدد"] |
|
|
|
fig = px.bar( |
|
skill_counts, |
|
x="المهارة", |
|
y="العدد", |
|
title="توزيع المهارات", |
|
color="المهارة", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### العلاقة بين سنوات الخبرة والتكلفة") |
|
|
|
fig = px.scatter( |
|
filtered_df, |
|
x="سنوات الخبرة", |
|
y="التكلفة الشهرية", |
|
color="القسم", |
|
size="التقييم", |
|
hover_name="اسم الموظف", |
|
hover_data=["المنصب", "متاح"], |
|
title="العلاقة بين سنوات الخبرة والتكلفة الشهرية" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### إضافة موظف جديد") |
|
|
|
with st.form("add_employee_form"): |
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
new_employee_name = st.text_input("اسم الموظف") |
|
new_employee_department = st.selectbox("القسم", options=employees_df["القسم"].unique()) |
|
new_employee_position = st.selectbox("المنصب", options=employees_df["المنصب"].unique()) |
|
new_employee_experience = st.number_input("سنوات الخبرة", min_value=0, max_value=40, value=5) |
|
|
|
with col2: |
|
new_employee_skills = st.multiselect( |
|
"المهارات", |
|
options=["إدارة المشاريع", "التصميم الهندسي", "تحليل البيانات", "إدارة العقود", "التخطيط الاستراتيجي", "إدارة الموارد", "إدارة المخاطر", "إدارة الجودة", "إدارة التكاليف", "إدارة الوقت"] |
|
) |
|
new_employee_cost = st.number_input("التكلفة الشهرية", min_value=3000, max_value=50000, value=10000) |
|
new_employee_available = st.checkbox("متاح", value=True) |
|
new_employee_rating = st.slider("التقييم", min_value=1.0, max_value=5.0, value=4.0, step=0.1) |
|
|
|
submit_button = st.form_submit_button("إضافة موظف") |
|
|
|
if submit_button: |
|
if new_employee_name: |
|
|
|
new_employee_id = f"EMP-{len(employees_df) + 1:03d}" |
|
|
|
|
|
new_employee = pd.DataFrame({ |
|
"رقم الموظف": [new_employee_id], |
|
"اسم الموظف": [new_employee_name], |
|
"القسم": [new_employee_department], |
|
"المنصب": [new_employee_position], |
|
"المهارات": [new_employee_skills], |
|
"سنوات الخبرة": [new_employee_experience], |
|
"التكلفة الشهرية": [new_employee_cost], |
|
"متاح": [new_employee_available], |
|
"التقييم": [new_employee_rating] |
|
}) |
|
|
|
|
|
st.session_state.resources_data["employees"] = pd.concat([employees_df, new_employee], ignore_index=True) |
|
|
|
st.success(f"تم إضافة الموظف {new_employee_name} بنجاح!") |
|
st.rerun() |
|
else: |
|
st.error("يرجى إدخال اسم الموظف") |
|
|
|
def _render_equipment_tab(self): |
|
"""عرض تبويب المعدات""" |
|
|
|
st.markdown("### إدارة المعدات") |
|
|
|
|
|
equipment_df = st.session_state.resources_data["equipment"] |
|
|
|
|
|
st.markdown("#### خيارات التصفية") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
selected_types = st.multiselect( |
|
"النوع", |
|
options=equipment_df["النوع"].unique(), |
|
default=equipment_df["النوع"].unique() |
|
) |
|
|
|
with col2: |
|
selected_conditions = st.multiselect( |
|
"الحالة", |
|
options=equipment_df["الحالة"].unique(), |
|
default=equipment_df["الحالة"].unique() |
|
) |
|
|
|
with col3: |
|
availability_filter = st.selectbox( |
|
"الإتاحة", |
|
options=["الكل", "متاحة فقط", "غير متاحة فقط"], |
|
key="equipment_availability" |
|
) |
|
|
|
|
|
filtered_df = equipment_df[ |
|
equipment_df["النوع"].isin(selected_types) & |
|
equipment_df["الحالة"].isin(selected_conditions) |
|
] |
|
|
|
if availability_filter == "متاحة فقط": |
|
filtered_df = filtered_df[filtered_df["متاحة"] == True] |
|
elif availability_filter == "غير متاحة فقط": |
|
filtered_df = filtered_df[filtered_df["متاحة"] == False] |
|
|
|
|
|
st.markdown("#### قائمة المعدات") |
|
|
|
st.dataframe( |
|
filtered_df, |
|
column_config={ |
|
"رقم المعدة": st.column_config.TextColumn("رقم المعدة"), |
|
"اسم المعدة": st.column_config.TextColumn("اسم المعدة"), |
|
"النوع": st.column_config.TextColumn("النوع"), |
|
"التكلفة اليومية": st.column_config.NumberColumn("التكلفة اليومية", format="%.2f ريال"), |
|
"متاحة": st.column_config.CheckboxColumn("متاحة"), |
|
"الحالة": st.column_config.TextColumn("الحالة"), |
|
"الموقع": st.column_config.TextColumn("الموقع") |
|
}, |
|
use_container_width=True, |
|
hide_index=True |
|
) |
|
|
|
|
|
st.markdown("#### إحصائيات المعدات") |
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
total_equipment = len(filtered_df) |
|
st.metric("إجمالي المعدات", f"{total_equipment}") |
|
|
|
with col2: |
|
available_equipment = len(filtered_df[filtered_df["متاحة"] == True]) |
|
availability_rate = available_equipment / total_equipment * 100 if total_equipment > 0 else 0 |
|
st.metric("معدل الإتاحة", f"{availability_rate:.1f}%") |
|
|
|
with col3: |
|
good_condition = len(filtered_df[filtered_df["الحالة"].isin(["ممتاز", "جيد"])]) |
|
good_condition_rate = good_condition / total_equipment * 100 if total_equipment > 0 else 0 |
|
st.metric("معدل الحالة الجيدة", f"{good_condition_rate:.1f}%") |
|
|
|
with col4: |
|
avg_cost = filtered_df["التكلفة اليومية"].mean() |
|
st.metric("متوسط التكلفة اليومية", f"{avg_cost:.0f} ريال") |
|
|
|
|
|
st.markdown("#### توزيع المعدات حسب النوع") |
|
|
|
type_counts = filtered_df["النوع"].value_counts().reset_index() |
|
type_counts.columns = ["النوع", "العدد"] |
|
|
|
fig = px.bar( |
|
type_counts, |
|
x="النوع", |
|
y="العدد", |
|
title="توزيع المعدات حسب النوع", |
|
color="النوع", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع المعدات حسب الحالة") |
|
|
|
condition_counts = filtered_df["الحالة"].value_counts().reset_index() |
|
condition_counts.columns = ["الحالة", "العدد"] |
|
|
|
fig = px.pie( |
|
condition_counts, |
|
values="العدد", |
|
names="الحالة", |
|
title="توزيع المعدات حسب الحالة", |
|
color="الحالة", |
|
color_discrete_map={ |
|
"ممتاز": "#2ecc71", |
|
"جيد": "#3498db", |
|
"متوسط": "#f39c12", |
|
"سيء": "#e74c3c" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع المعدات حسب الموقع") |
|
|
|
location_counts = filtered_df["الموقع"].value_counts().reset_index() |
|
location_counts.columns = ["الموقع", "العدد"] |
|
|
|
fig = px.bar( |
|
location_counts, |
|
x="الموقع", |
|
y="العدد", |
|
title="توزيع المعدات حسب الموقع", |
|
color="الموقع", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### العلاقة بين نوع المعدة والتكلفة") |
|
|
|
type_cost = filtered_df.groupby("النوع")["التكلفة اليومية"].mean().reset_index() |
|
type_cost.columns = ["النوع", "متوسط التكلفة اليومية"] |
|
|
|
fig = px.bar( |
|
type_cost, |
|
x="النوع", |
|
y="متوسط التكلفة اليومية", |
|
title="متوسط التكلفة اليومية حسب نوع المعدة", |
|
color="النوع", |
|
text_auto=".0f" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### إضافة معدة جديدة") |
|
|
|
with st.form("add_equipment_form"): |
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
new_equipment_name = st.text_input("اسم المعدة") |
|
new_equipment_type = st.selectbox("النوع", options=equipment_df["النوع"].unique()) |
|
new_equipment_cost = st.number_input("التكلفة اليومية", min_value=100, max_value=10000, value=1000) |
|
|
|
with col2: |
|
new_equipment_available = st.checkbox("متاحة", value=True) |
|
new_equipment_condition = st.selectbox("الحالة", options=["ممتاز", "جيد", "متوسط", "سيء"]) |
|
new_equipment_location = st.selectbox("الموقع", options=equipment_df["الموقع"].unique()) |
|
|
|
submit_button = st.form_submit_button("إضافة معدة") |
|
|
|
if submit_button: |
|
if new_equipment_name: |
|
|
|
new_equipment_id = f"EQ-{len(equipment_df) + 1:03d}" |
|
|
|
|
|
new_equipment = pd.DataFrame({ |
|
"رقم المعدة": [new_equipment_id], |
|
"اسم المعدة": [new_equipment_name], |
|
"النوع": [new_equipment_type], |
|
"التكلفة اليومية": [new_equipment_cost], |
|
"متاحة": [new_equipment_available], |
|
"الحالة": [new_equipment_condition], |
|
"الموقع": [new_equipment_location] |
|
}) |
|
|
|
|
|
st.session_state.resources_data["equipment"] = pd.concat([equipment_df, new_equipment], ignore_index=True) |
|
|
|
st.success(f"تم إضافة المعدة {new_equipment_name} بنجاح!") |
|
st.rerun() |
|
else: |
|
st.error("يرجى إدخال اسم المعدة") |
|
|
|
def _render_materials_tab(self): |
|
"""عرض تبويب المواد""" |
|
|
|
st.markdown("### إدارة المواد") |
|
|
|
|
|
materials_df = st.session_state.resources_data["materials"] |
|
|
|
|
|
st.markdown("#### خيارات التصفية") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
selected_units = st.multiselect( |
|
"الوحدة", |
|
options=materials_df["الوحدة"].unique(), |
|
default=materials_df["الوحدة"].unique() |
|
) |
|
|
|
with col2: |
|
selected_suppliers = st.multiselect( |
|
"المورد", |
|
options=materials_df["المورد"].unique(), |
|
default=materials_df["المورد"].unique() |
|
) |
|
|
|
with col3: |
|
stock_filter = st.selectbox( |
|
"المخزون", |
|
options=["الكل", "منخفض المخزون", "مخزون كافي"] |
|
) |
|
|
|
|
|
filtered_df = materials_df[ |
|
materials_df["الوحدة"].isin(selected_units) & |
|
materials_df["المورد"].isin(selected_suppliers) |
|
] |
|
|
|
if stock_filter == "منخفض المخزون": |
|
filtered_df = filtered_df[filtered_df["الكمية المتاحة"] < 50] |
|
elif stock_filter == "مخزون كافي": |
|
filtered_df = filtered_df[filtered_df["الكمية المتاحة"] >= 50] |
|
|
|
|
|
st.markdown("#### قائمة المواد") |
|
|
|
st.dataframe( |
|
filtered_df, |
|
column_config={ |
|
"رقم المادة": st.column_config.TextColumn("رقم المادة"), |
|
"اسم المادة": st.column_config.TextColumn("اسم المادة"), |
|
"الوحدة": st.column_config.TextColumn("الوحدة"), |
|
"الكمية المتاحة": st.column_config.NumberColumn("الكمية المتاحة"), |
|
"تكلفة الوحدة": st.column_config.NumberColumn("تكلفة الوحدة", format="%.2f ريال"), |
|
"المورد": st.column_config.TextColumn("المورد"), |
|
"مدة التوريد (يوم)": st.column_config.NumberColumn("مدة التوريد (يوم)") |
|
}, |
|
use_container_width=True, |
|
hide_index=True |
|
) |
|
|
|
|
|
st.markdown("#### إحصائيات المواد") |
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
total_materials = len(filtered_df) |
|
st.metric("إجمالي المواد", f"{total_materials}") |
|
|
|
with col2: |
|
low_stock_materials = len(filtered_df[filtered_df["الكمية المتاحة"] < 50]) |
|
low_stock_rate = low_stock_materials / total_materials * 100 if total_materials > 0 else 0 |
|
st.metric("نسبة المواد منخفضة المخزون", f"{low_stock_rate:.1f}%") |
|
|
|
with col3: |
|
avg_lead_time = filtered_df["مدة التوريد (يوم)"].mean() |
|
st.metric("متوسط مدة التوريد", f"{avg_lead_time:.1f} يوم") |
|
|
|
with col4: |
|
total_inventory_value = (filtered_df["الكمية المتاحة"] * filtered_df["تكلفة الوحدة"]).sum() |
|
st.metric("إجمالي قيمة المخزون", f"{total_inventory_value:,.0f} ريال") |
|
|
|
|
|
st.markdown("#### توزيع المواد حسب المورد") |
|
|
|
supplier_counts = filtered_df["المورد"].value_counts().reset_index() |
|
supplier_counts.columns = ["المورد", "العدد"] |
|
|
|
fig = px.pie( |
|
supplier_counts, |
|
values="العدد", |
|
names="المورد", |
|
title="توزيع المواد حسب المورد", |
|
color="المورد" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع المواد حسب الوحدة") |
|
|
|
unit_counts = filtered_df["الوحدة"].value_counts().reset_index() |
|
unit_counts.columns = ["الوحدة", "العدد"] |
|
|
|
fig = px.bar( |
|
unit_counts, |
|
x="الوحدة", |
|
y="العدد", |
|
title="توزيع المواد حسب الوحدة", |
|
color="الوحدة", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### المواد منخفضة المخزون") |
|
|
|
low_stock_df = filtered_df[filtered_df["الكمية المتاحة"] < 50].sort_values("الكمية المتاحة") |
|
|
|
if not low_stock_df.empty: |
|
fig = px.bar( |
|
low_stock_df, |
|
x="اسم المادة", |
|
y="الكمية المتاحة", |
|
title="المواد منخفضة المخزون", |
|
color="الكمية المتاحة", |
|
color_continuous_scale="Reds_r", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
else: |
|
st.info("لا توجد مواد منخفضة المخزون") |
|
|
|
|
|
st.markdown("#### العلاقة بين مدة التوريد والمورد") |
|
|
|
supplier_lead_time = filtered_df.groupby("المورد")["مدة التوريد (يوم)"].mean().reset_index() |
|
supplier_lead_time.columns = ["المورد", "متوسط مدة التوريد (يوم)"] |
|
|
|
fig = px.bar( |
|
supplier_lead_time, |
|
x="المورد", |
|
y="متوسط مدة التوريد (يوم)", |
|
title="متوسط مدة التوريد حسب المورد", |
|
color="المورد", |
|
text_auto=".1f" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### إضافة مادة جديدة") |
|
|
|
with st.form("add_material_form"): |
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
new_material_name = st.text_input("اسم المادة") |
|
new_material_unit = st.selectbox("الوحدة", options=materials_df["الوحدة"].unique()) |
|
new_material_quantity = st.number_input("الكمية المتاحة", min_value=0, max_value=10000, value=100) |
|
|
|
with col2: |
|
new_material_cost = st.number_input("تكلفة الوحدة", min_value=1, max_value=10000, value=100) |
|
new_material_supplier = st.selectbox("المورد", options=materials_df["المورد"].unique()) |
|
new_material_lead_time = st.number_input("مدة التوريد (يوم)", min_value=1, max_value=90, value=7) |
|
|
|
submit_button = st.form_submit_button("إضافة مادة") |
|
|
|
if submit_button: |
|
if new_material_name: |
|
|
|
new_material_id = f"MAT-{len(materials_df) + 1:03d}" |
|
|
|
|
|
new_material = pd.DataFrame({ |
|
"رقم المادة": [new_material_id], |
|
"اسم المادة": [new_material_name], |
|
"الوحدة": [new_material_unit], |
|
"الكمية المتاحة": [new_material_quantity], |
|
"تكلفة الوحدة": [new_material_cost], |
|
"المورد": [new_material_supplier], |
|
"مدة التوريد (يوم)": [new_material_lead_time] |
|
}) |
|
|
|
|
|
st.session_state.resources_data["materials"] = pd.concat([materials_df, new_material], ignore_index=True) |
|
|
|
st.success(f"تم إضافة المادة {new_material_name} بنجاح!") |
|
st.rerun() |
|
else: |
|
st.error("يرجى إدخال اسم المادة") |
|
|
|
|
|
st.markdown("#### طلب مواد") |
|
|
|
with st.form("order_materials_form"): |
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
material_to_order = st.selectbox("المادة", options=materials_df["اسم المادة"].unique()) |
|
|
|
with col2: |
|
order_quantity = st.number_input("الكمية المطلوبة", min_value=1, max_value=1000, value=50) |
|
|
|
with col3: |
|
order_date = st.date_input("تاريخ الطلب", value=datetime.now()) |
|
|
|
submit_button = st.form_submit_button("طلب المادة") |
|
|
|
if submit_button: |
|
|
|
st.success(f"تم طلب {order_quantity} {materials_df[materials_df['اسم المادة'] == material_to_order]['الوحدة'].values[0]} من {material_to_order} بنجاح!") |
|
|
|
|
|
material_info = materials_df[materials_df["اسم المادة"] == material_to_order].iloc[0] |
|
lead_time = material_info["مدة التوريد (يوم)"] |
|
expected_delivery = order_date + timedelta(days=lead_time) |
|
|
|
st.info(f"تاريخ التسليم المتوقع: {expected_delivery.strftime('%Y-%m-%d')}") |
|
|
|
|
|
unit_cost = material_info["تكلفة الوحدة"] |
|
total_cost = unit_cost * order_quantity |
|
|
|
st.metric("التكلفة الإجمالية", f"{total_cost:,.2f} ريال") |
|
|
|
def _render_resource_allocation_tab(self): |
|
"""عرض تبويب تخصيص الموارد""" |
|
|
|
st.markdown("### تخصيص الموارد") |
|
|
|
|
|
employees_df = st.session_state.resources_data["employees"] |
|
equipment_df = st.session_state.resources_data["equipment"] |
|
materials_df = st.session_state.resources_data["materials"] |
|
projects_df = st.session_state.resources_data["projects"] |
|
allocations_df = st.session_state.resources_data["allocations"] |
|
|
|
|
|
st.markdown("#### خيارات التصفية") |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
selected_projects = st.multiselect( |
|
"المشروع", |
|
options=projects_df["اسم المشروع"].unique(), |
|
default=projects_df["اسم المشروع"].unique() |
|
) |
|
|
|
with col2: |
|
selected_resource_types = st.multiselect( |
|
"نوع المورد", |
|
options=allocations_df["نوع المورد"].unique(), |
|
default=allocations_df["نوع المورد"].unique() |
|
) |
|
|
|
|
|
selected_project_ids = projects_df[projects_df["اسم المشروع"].isin(selected_projects)]["رقم المشروع"].tolist() |
|
|
|
|
|
filtered_df = allocations_df[ |
|
allocations_df["رقم المشروع"].isin(selected_project_ids) & |
|
allocations_df["نوع المورد"].isin(selected_resource_types) |
|
] |
|
|
|
|
|
merged_df = filtered_df.merge( |
|
projects_df[["رقم المشروع", "اسم المشروع"]], |
|
on="رقم المشروع", |
|
how="left" |
|
) |
|
|
|
|
|
merged_df["اسم المورد"] = "" |
|
|
|
for i, row in merged_df.iterrows(): |
|
if row["نوع المورد"] == "موظف": |
|
resource_name = employees_df[employees_df["رقم الموظف"] == row["رقم المورد"]]["اسم الموظف"].values |
|
if len(resource_name) > 0: |
|
merged_df.at[i, "اسم المورد"] = resource_name[0] |
|
elif row["نوع المورد"] == "معدة": |
|
resource_name = equipment_df[equipment_df["رقم المعدة"] == row["رقم المورد"]]["اسم المعدة"].values |
|
if len(resource_name) > 0: |
|
merged_df.at[i, "اسم المورد"] = resource_name[0] |
|
elif row["نوع المورد"] == "مادة": |
|
resource_name = materials_df[materials_df["رقم المادة"] == row["رقم المورد"]]["اسم المادة"].values |
|
if len(resource_name) > 0: |
|
merged_df.at[i, "اسم المورد"] = resource_name[0] |
|
|
|
|
|
st.markdown("#### قائمة تخصيص الموارد") |
|
|
|
display_df = merged_df[["رقم التخصيص", "اسم المشروع", "نوع المورد", "اسم المورد", "تاريخ البدء", "تاريخ الانتهاء", "الكمية", "التكلفة"]] |
|
|
|
st.dataframe( |
|
display_df, |
|
column_config={ |
|
"رقم التخصيص": st.column_config.TextColumn("رقم التخصيص"), |
|
"اسم المشروع": st.column_config.TextColumn("اسم المشروع"), |
|
"نوع المورد": st.column_config.TextColumn("نوع المورد"), |
|
"اسم المورد": st.column_config.TextColumn("اسم المورد"), |
|
"تاريخ البدء": st.column_config.DateColumn("تاريخ البدء"), |
|
"تاريخ الانتهاء": st.column_config.DateColumn("تاريخ الانتهاء"), |
|
"الكمية": st.column_config.NumberColumn("الكمية"), |
|
"التكلفة": st.column_config.NumberColumn("التكلفة", format="%.2f ريال") |
|
}, |
|
use_container_width=True, |
|
hide_index=True |
|
) |
|
|
|
|
|
st.markdown("#### إحصائيات تخصيص الموارد") |
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
total_allocations = len(merged_df) |
|
st.metric("إجمالي التخصيصات", f"{total_allocations}") |
|
|
|
with col2: |
|
total_cost = merged_df["التكلفة"].sum() |
|
st.metric("إجمالي التكلفة", f"{total_cost:,.0f} ريال") |
|
|
|
with col3: |
|
avg_duration = (pd.to_datetime(merged_df["تاريخ الانتهاء"]) - pd.to_datetime(merged_df["تاريخ البدء"])).mean().days |
|
st.metric("متوسط مدة التخصيص", f"{avg_duration:.0f} يوم") |
|
|
|
with col4: |
|
resource_types = merged_df["نوع المورد"].value_counts() |
|
most_common_type = resource_types.index[0] if not resource_types.empty else "" |
|
st.metric("أكثر أنواع الموارد تخصيصاً", f"{most_common_type}") |
|
|
|
|
|
st.markdown("#### توزيع تخصيص الموارد حسب المشروع") |
|
|
|
project_allocations = merged_df.groupby("اسم المشروع").size().reset_index() |
|
project_allocations.columns = ["اسم المشروع", "عدد التخصيصات"] |
|
|
|
fig = px.bar( |
|
project_allocations, |
|
x="اسم المشروع", |
|
y="عدد التخصيصات", |
|
title="توزيع تخصيص الموارد حسب المشروع", |
|
color="اسم المشروع", |
|
text_auto=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع تخصيص الموارد حسب نوع المورد") |
|
|
|
resource_type_allocations = merged_df.groupby("نوع المورد").size().reset_index() |
|
resource_type_allocations.columns = ["نوع المورد", "عدد التخصيصات"] |
|
|
|
fig = px.pie( |
|
resource_type_allocations, |
|
values="عدد التخصيصات", |
|
names="نوع المورد", |
|
title="توزيع تخصيص الموارد حسب نوع المورد", |
|
color="نوع المورد", |
|
color_discrete_map={ |
|
"موظف": "#3498db", |
|
"معدة": "#2ecc71", |
|
"مادة": "#f39c12" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع تكاليف الموارد حسب المشروع") |
|
|
|
project_costs = merged_df.groupby("اسم المشروع")["التكلفة"].sum().reset_index() |
|
project_costs.columns = ["اسم المشروع", "إجمالي التكلفة"] |
|
|
|
fig = px.bar( |
|
project_costs, |
|
x="اسم المشروع", |
|
y="إجمالي التكلفة", |
|
title="توزيع تكاليف الموارد حسب المشروع", |
|
color="اسم المشروع", |
|
text_auto=".0f" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توزيع تكاليف الموارد حسب نوع المورد") |
|
|
|
resource_type_costs = merged_df.groupby("نوع المورد")["التكلفة"].sum().reset_index() |
|
resource_type_costs.columns = ["نوع المورد", "إجمالي التكلفة"] |
|
|
|
fig = px.pie( |
|
resource_type_costs, |
|
values="إجمالي التكلفة", |
|
names="نوع المورد", |
|
title="توزيع تكاليف الموارد حسب نوع المورد", |
|
color="نوع المورد", |
|
color_discrete_map={ |
|
"موظف": "#3498db", |
|
"معدة": "#2ecc71", |
|
"مادة": "#f39c12" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### إضافة تخصيص جديد") |
|
|
|
with st.form("add_allocation_form"): |
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
new_allocation_project = st.selectbox("المشروع", options=projects_df["اسم المشروع"].unique()) |
|
new_allocation_resource_type = st.selectbox("نوع المورد", options=["موظف", "معدة", "مادة"]) |
|
|
|
|
|
if new_allocation_resource_type == "موظف": |
|
resource_options = employees_df[employees_df["متاح"] == True]["اسم الموظف"].unique() |
|
elif new_allocation_resource_type == "معدة": |
|
resource_options = equipment_df[equipment_df["متاحة"] == True]["اسم المعدة"].unique() |
|
else: |
|
resource_options = materials_df["اسم المادة"].unique() |
|
|
|
new_allocation_resource = st.selectbox("المورد", options=resource_options) |
|
|
|
with col2: |
|
new_allocation_start_date = st.date_input("تاريخ البدء", value=datetime.now()) |
|
new_allocation_end_date = st.date_input("تاريخ الانتهاء", value=datetime.now() + timedelta(days=30)) |
|
new_allocation_quantity = st.number_input("الكمية", min_value=1, max_value=100, value=1) |
|
|
|
submit_button = st.form_submit_button("إضافة تخصيص") |
|
|
|
if submit_button: |
|
|
|
if new_allocation_end_date <= new_allocation_start_date: |
|
st.error("يجب أن يكون تاريخ الانتهاء بعد تاريخ البدء") |
|
else: |
|
|
|
project_id = projects_df[projects_df["اسم المشروع"] == new_allocation_project]["رقم المشروع"].values[0] |
|
|
|
|
|
if new_allocation_resource_type == "موظف": |
|
resource_id = employees_df[employees_df["اسم الموظف"] == new_allocation_resource]["رقم الموظف"].values[0] |
|
|
|
cost = employees_df[employees_df["رقم الموظف"] == resource_id]["التكلفة الشهرية"].values[0] * new_allocation_quantity |
|
elif new_allocation_resource_type == "معدة": |
|
resource_id = equipment_df[equipment_df["اسم المعدة"] == new_allocation_resource]["رقم المعدة"].values[0] |
|
|
|
days = (new_allocation_end_date - new_allocation_start_date).days |
|
cost = equipment_df[equipment_df["رقم المعدة"] == resource_id]["التكلفة اليومية"].values[0] * days * new_allocation_quantity |
|
else: |
|
resource_id = materials_df[materials_df["اسم المادة"] == new_allocation_resource]["رقم المادة"].values[0] |
|
|
|
cost = materials_df[materials_df["رقم المادة"] == resource_id]["تكلفة الوحدة"].values[0] * new_allocation_quantity |
|
|
|
|
|
new_allocation_id = f"ALLOC-{len(allocations_df) + 1:03d}" |
|
|
|
|
|
new_allocation = pd.DataFrame({ |
|
"رقم التخصيص": [new_allocation_id], |
|
"رقم المشروع": [project_id], |
|
"نوع المورد": [new_allocation_resource_type], |
|
"رقم المورد": [resource_id], |
|
"تاريخ البدء": [new_allocation_start_date.strftime("%Y-%m-%d")], |
|
"تاريخ الانتهاء": [new_allocation_end_date.strftime("%Y-%m-%d")], |
|
"الكمية": [new_allocation_quantity], |
|
"التكلفة": [cost] |
|
}) |
|
|
|
|
|
st.session_state.resources_data["allocations"] = pd.concat([allocations_df, new_allocation], ignore_index=True) |
|
|
|
|
|
if new_allocation_resource_type == "موظف": |
|
employees_idx = employees_df[employees_df["رقم الموظف"] == resource_id].index |
|
st.session_state.resources_data["employees"].at[employees_idx[0], "متاح"] = False |
|
elif new_allocation_resource_type == "معدة": |
|
equipment_idx = equipment_df[equipment_df["رقم المعدة"] == resource_id].index |
|
st.session_state.resources_data["equipment"].at[equipment_idx[0], "متاحة"] = False |
|
elif new_allocation_resource_type == "مادة": |
|
materials_idx = materials_df[materials_df["رقم المادة"] == resource_id].index |
|
current_quantity = st.session_state.resources_data["materials"].at[materials_idx[0], "الكمية المتاحة"] |
|
st.session_state.resources_data["materials"].at[materials_idx[0], "الكمية المتاحة"] = max(0, current_quantity - new_allocation_quantity) |
|
|
|
st.success(f"تم إضافة تخصيص {new_allocation_resource_type} ({new_allocation_resource}) لمشروع {new_allocation_project} بنجاح!") |
|
st.rerun() |
|
|
|
def _render_resource_planning_tab(self): |
|
"""عرض تبويب تخطيط الموارد""" |
|
|
|
st.markdown("### تخطيط الموارد") |
|
|
|
|
|
employees_df = st.session_state.resources_data["employees"] |
|
equipment_df = st.session_state.resources_data["equipment"] |
|
materials_df = st.session_state.resources_data["materials"] |
|
projects_df = st.session_state.resources_data["projects"] |
|
allocations_df = st.session_state.resources_data["allocations"] |
|
|
|
|
|
st.markdown("#### المشاريع القادمة") |
|
|
|
upcoming_projects = projects_df[projects_df["الحالة"] == "مخطط"].sort_values("تاريخ البدء") |
|
|
|
if not upcoming_projects.empty: |
|
st.dataframe( |
|
upcoming_projects, |
|
column_config={ |
|
"رقم المشروع": st.column_config.TextColumn("رقم المشروع"), |
|
"اسم المشروع": st.column_config.TextColumn("اسم المشروع"), |
|
"الموقع": st.column_config.TextColumn("الموقع"), |
|
"تاريخ البدء": st.column_config.DateColumn("تاريخ البدء"), |
|
"تاريخ الانتهاء": st.column_config.DateColumn("تاريخ الانتهاء"), |
|
"الميزانية": st.column_config.NumberColumn("الميزانية", format="%.2f ريال"), |
|
"الحالة": st.column_config.TextColumn("الحالة") |
|
}, |
|
use_container_width=True, |
|
hide_index=True |
|
) |
|
else: |
|
st.info("لا توجد مشاريع قادمة") |
|
|
|
|
|
st.markdown("#### توافر الموارد") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
total_employees = len(employees_df) |
|
available_employees = len(employees_df[employees_df["متاح"] == True]) |
|
availability_rate = available_employees / total_employees * 100 if total_employees > 0 else 0 |
|
|
|
st.metric("الموظفون المتاحون", f"{available_employees}/{total_employees}", f"{availability_rate:.1f}%") |
|
|
|
|
|
availability_data = pd.DataFrame({ |
|
"الحالة": ["متاح", "غير متاح"], |
|
"العدد": [available_employees, total_employees - available_employees] |
|
}) |
|
|
|
fig = px.pie( |
|
availability_data, |
|
values="العدد", |
|
names="الحالة", |
|
title="توافر الموظفين", |
|
color="الحالة", |
|
color_discrete_map={ |
|
"متاح": "#2ecc71", |
|
"غير متاح": "#e74c3c" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with col2: |
|
total_equipment = len(equipment_df) |
|
available_equipment = len(equipment_df[equipment_df["متاحة"] == True]) |
|
availability_rate = available_equipment / total_equipment * 100 if total_equipment > 0 else 0 |
|
|
|
st.metric("المعدات المتاحة", f"{available_equipment}/{total_equipment}", f"{availability_rate:.1f}%") |
|
|
|
|
|
availability_data = pd.DataFrame({ |
|
"الحالة": ["متاحة", "غير متاحة"], |
|
"العدد": [available_equipment, total_equipment - available_equipment] |
|
}) |
|
|
|
fig = px.pie( |
|
availability_data, |
|
values="العدد", |
|
names="الحالة", |
|
title="توافر المعدات", |
|
color="الحالة", |
|
color_discrete_map={ |
|
"متاحة": "#2ecc71", |
|
"غير متاحة": "#e74c3c" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with col3: |
|
total_materials = len(materials_df) |
|
low_stock_materials = len(materials_df[materials_df["الكمية المتاحة"] < 50]) |
|
low_stock_rate = low_stock_materials / total_materials * 100 if total_materials > 0 else 0 |
|
|
|
st.metric("المواد منخفضة المخزون", f"{low_stock_materials}/{total_materials}", f"{low_stock_rate:.1f}%") |
|
|
|
|
|
stock_data = pd.DataFrame({ |
|
"حالة المخزون": ["مخزون كافي", "مخزون منخفض"], |
|
"العدد": [total_materials - low_stock_materials, low_stock_materials] |
|
}) |
|
|
|
fig = px.pie( |
|
stock_data, |
|
values="العدد", |
|
names="حالة المخزون", |
|
title="حالة مخزون المواد", |
|
color="حالة المخزون", |
|
color_discrete_map={ |
|
"مخزون كافي": "#2ecc71", |
|
"مخزون منخفض": "#e74c3c" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### الجدول الزمني للموارد") |
|
|
|
|
|
timeline_df = allocations_df.copy() |
|
timeline_df["تاريخ البدء"] = pd.to_datetime(timeline_df["تاريخ البدء"]) |
|
timeline_df["تاريخ الانتهاء"] = pd.to_datetime(timeline_df["تاريخ الانتهاء"]) |
|
|
|
|
|
timeline_df = timeline_df.merge( |
|
projects_df[["رقم المشروع", "اسم المشروع"]], |
|
on="رقم المشروع", |
|
how="left" |
|
) |
|
|
|
|
|
timeline_df["اسم المورد"] = "" |
|
|
|
for i, row in timeline_df.iterrows(): |
|
if row["نوع المورد"] == "موظف": |
|
resource_name = employees_df[employees_df["رقم الموظف"] == row["رقم المورد"]]["اسم الموظف"].values |
|
if len(resource_name) > 0: |
|
timeline_df.at[i, "اسم المورد"] = resource_name[0] |
|
elif row["نوع المورد"] == "معدة": |
|
resource_name = equipment_df[equipment_df["رقم المعدة"] == row["رقم المورد"]]["اسم المعدة"].values |
|
if len(resource_name) > 0: |
|
timeline_df.at[i, "اسم المورد"] = resource_name[0] |
|
elif row["نوع المورد"] == "مادة": |
|
resource_name = materials_df[materials_df["رقم المادة"] == row["رقم المورد"]]["اسم المادة"].values |
|
if len(resource_name) > 0: |
|
timeline_df.at[i, "اسم المورد"] = resource_name[0] |
|
|
|
|
|
fig = px.timeline( |
|
timeline_df, |
|
x_start="تاريخ البدء", |
|
x_end="تاريخ الانتهاء", |
|
y="اسم المورد", |
|
color="اسم المشروع", |
|
hover_name="اسم المشروع", |
|
hover_data=["نوع المورد", "الكمية", "التكلفة"], |
|
title="الجدول الزمني لتخصيص الموارد" |
|
) |
|
|
|
fig.update_yaxes(autorange="reversed") |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("#### توقعات الاحتياجات المستقبلية") |
|
|
|
|
|
project_for_planning = st.selectbox("اختر المشروع للتخطيط", options=upcoming_projects["اسم المشروع"] if not upcoming_projects.empty else ["لا توجد مشاريع قادمة"]) |
|
|
|
if project_for_planning != "لا توجد مشاريع قادمة": |
|
|
|
project_data = upcoming_projects[upcoming_projects["اسم المشروع"] == project_for_planning].iloc[0] |
|
|
|
st.markdown(f"**تاريخ البدء:** {project_data['تاريخ البدء']}") |
|
st.markdown(f"**تاريخ الانتهاء:** {project_data['تاريخ الانتهاء']}") |
|
st.markdown(f"**الميزانية:** {project_data['الميزانية']:,.0f} ريال") |
|
|
|
|
|
st.markdown("##### تقدير الاحتياجات بناءً على المشاريع المماثلة") |
|
|
|
|
|
estimated_resources = { |
|
"الموظفون": { |
|
"مهندس": 5, |
|
"فني": 10, |
|
"مشرف": 2, |
|
"محاسب": 1, |
|
"مساعد": 3 |
|
}, |
|
"المعدات": { |
|
"حفارة كبيرة": 1, |
|
"حفارة صغيرة": 2, |
|
"شاحنة نقل": 3, |
|
"رافعة متوسطة": 1, |
|
"خلاطة خرسانة": 2 |
|
}, |
|
"المواد": { |
|
"خرسانة جاهزة": 500, |
|
"حديد تسليح": 200, |
|
"طابوق": 10000, |
|
"أسمنت": 1000, |
|
"رمل": 300 |
|
} |
|
} |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
st.markdown("**الموظفون المطلوبون:**") |
|
for position, count in estimated_resources["الموظفون"].items(): |
|
st.markdown(f"- {position}: {count}") |
|
|
|
|
|
available_positions = {} |
|
for position, count in estimated_resources["الموظفون"].items(): |
|
available_count = len(employees_df[(employees_df["المنصب"] == position) & (employees_df["متاح"] == True)]) |
|
available_positions[position] = available_count |
|
|
|
if available_count < count: |
|
st.warning(f"نقص في {position}: متاح {available_count}/{count}") |
|
else: |
|
st.success(f"متوفر: {available_count}/{count}") |
|
|
|
with col2: |
|
st.markdown("**المعدات المطلوبة:**") |
|
for equipment_name, count in estimated_resources["المعدات"].items(): |
|
st.markdown(f"- {equipment_name}: {count}") |
|
|
|
|
|
available_equipment = {} |
|
for equipment_name, count in estimated_resources["المعدات"].items(): |
|
available_count = len(equipment_df[(equipment_df["اسم المعدة"] == equipment_name) & (equipment_df["متاحة"] == True)]) |
|
available_equipment[equipment_name] = available_count |
|
|
|
if available_count < count: |
|
st.warning(f"نقص في {equipment_name}: متاح {available_count}/{count}") |
|
else: |
|
st.success(f"متوفر: {available_count}/{count}") |
|
|
|
with col3: |
|
st.markdown("**المواد المطلوبة:**") |
|
for material_name, quantity in estimated_resources["المواد"].items(): |
|
st.markdown(f"- {material_name}: {quantity}") |
|
|
|
|
|
available_materials = {} |
|
for material_name, quantity in estimated_resources["المواد"].items(): |
|
available_quantity = materials_df[materials_df["اسم المادة"] == material_name]["الكمية المتاحة"].sum() |
|
available_materials[material_name] = available_quantity |
|
|
|
if available_quantity < quantity: |
|
st.warning(f"نقص في {material_name}: متاح {available_quantity}/{quantity}") |
|
else: |
|
st.success(f"متوفر: {available_quantity}/{quantity}") |
|
|
|
|
|
st.markdown("##### تقدير تكاليف الموارد") |
|
|
|
|
|
employee_costs = 0 |
|
for position, count in estimated_resources["الموظفون"].items(): |
|
avg_cost = employees_df[employees_df["المنصب"] == position]["التكلفة الشهرية"].mean() |
|
|
|
employee_costs += avg_cost * count * 6 |
|
|
|
|
|
equipment_costs = 0 |
|
for equipment_name, count in estimated_resources["المعدات"].items(): |
|
avg_cost = equipment_df[equipment_df["اسم المعدة"] == equipment_name]["التكلفة اليومية"].mean() |
|
|
|
equipment_costs += avg_cost * count * 180 |
|
|
|
|
|
material_costs = 0 |
|
for material_name, quantity in estimated_resources["المواد"].items(): |
|
avg_cost = materials_df[materials_df["اسم المادة"] == material_name]["تكلفة الوحدة"].mean() |
|
material_costs += avg_cost * quantity |
|
|
|
|
|
total_costs = employee_costs + equipment_costs + material_costs |
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
with col1: |
|
st.metric("تكاليف الموظفين", f"{employee_costs:,.0f} ريال") |
|
|
|
with col2: |
|
st.metric("تكاليف المعدات", f"{equipment_costs:,.0f} ريال") |
|
|
|
with col3: |
|
st.metric("تكاليف المواد", f"{material_costs:,.0f} ريال") |
|
|
|
with col4: |
|
st.metric("إجمالي التكاليف", f"{total_costs:,.0f} ريال") |
|
|
|
|
|
cost_distribution = pd.DataFrame({ |
|
"نوع التكلفة": ["تكاليف الموظفين", "تكاليف المعدات", "تكاليف المواد"], |
|
"التكلفة": [employee_costs, equipment_costs, material_costs] |
|
}) |
|
|
|
fig = px.pie( |
|
cost_distribution, |
|
values="التكلفة", |
|
names="نوع التكلفة", |
|
title="توزيع تكاليف الموارد", |
|
color="نوع التكلفة", |
|
color_discrete_map={ |
|
"تكاليف الموظفين": "#3498db", |
|
"تكاليف المعدات": "#2ecc71", |
|
"تكاليف المواد": "#f39c12" |
|
} |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.markdown("##### توصيات لتخطيط الموارد") |
|
|
|
recommendations = [] |
|
|
|
|
|
for position, count in estimated_resources["الموظفون"].items(): |
|
available_count = available_positions[position] |
|
if available_count < count: |
|
recommendations.append(f"توظيف {count - available_count} {position} إضافي") |
|
|
|
|
|
for equipment_name, count in estimated_resources["المعدات"].items(): |
|
available_count = available_equipment[equipment_name] |
|
if available_count < count: |
|
recommendations.append(f"استئجار {count - available_count} {equipment_name} إضافية") |
|
|
|
|
|
for material_name, quantity in estimated_resources["المواد"].items(): |
|
available_quantity = available_materials[material_name] |
|
if available_quantity < quantity: |
|
recommendations.append(f"شراء {quantity - available_quantity} وحدة إضافية من {material_name}") |
|
|
|
if recommendations: |
|
for recommendation in recommendations: |
|
st.markdown(f"- {recommendation}") |
|
else: |
|
st.success("جميع الموارد المطلوبة متوفرة") |
|
|
|
|
|
if st.button("إنشاء خطة الموارد"): |
|
st.success("تم إنشاء خطة الموارد بنجاح!") |
|
|
|
|
|
st.markdown("##### ملخص خطة الموارد") |
|
st.markdown(f"**المشروع:** {project_for_planning}") |
|
st.markdown(f"**تاريخ البدء:** {project_data['تاريخ البدء']}") |
|
st.markdown(f"**تاريخ الانتهاء:** {project_data['تاريخ الانتهاء']}") |
|
st.markdown(f"**إجمالي تكاليف الموارد:** {total_costs:,.0f} ريال") |
|
st.markdown(f"**نسبة تكاليف الموارد من الميزانية:** {total_costs / project_data['الميزانية'] * 100:.1f}%") |
|
|
|
if recommendations: |
|
st.markdown("**الإجراءات المطلوبة:**") |
|
for recommendation in recommendations: |
|
st.markdown(f"- {recommendation}") |
|
else: |
|
st.markdown("**الإجراءات المطلوبة:** لا توجد إجراءات مطلوبة، جميع الموارد متوفرة") |
|
else: |
|
st.info("لا توجد مشاريع قادمة للتخطيط") |
|
|