v3 / modules /pricing /pricing_app.py
EGYADMIN's picture
Upload 115 files
82676b8 verified
"""
تطبيق وحدة التسعير المتكاملة
"""
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
import random
import os
import time
import io
# ملاحظة: نحن لا نستخدم st.set_page_config هنا لأنه يجب أن يكون في ملف app.py الرئيسي فقط
# تحسين المظهر العام باستخدام CSS
st.markdown("""
<style>
/* ألوان النظام */
:root {
--primary-color: #1E8A8A;
--primary-light: rgba(30, 138, 138, 0.2);
--secondary-color: #134e4a;
--accent-color: #f0b429;
--background-light: #f8fafc;
--text-color: #334155;
--heading-color: #475569;
--border-color: #e2e8f0;
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
}
/* المظهر العام */
.main {
background-color: var(--background-light);
color: var(--text-color);
padding: 1rem 1.5rem;
border-radius: 10px;
}
.main .block-container {
max-width: 1200px;
margin: 0 auto;
}
/* العناوين والنصوص */
h1, h2, h3, h4, h5, h6 {
color: var(--heading-color);
font-weight: 700 !important;
}
h1 {
background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 2.5rem !important;
text-align: center;
padding: 0.5rem 0;
margin-bottom: 1rem;
}
h2 {
border-bottom: 2px solid var(--primary-color);
padding-bottom: 0.5rem;
margin-top: 1.5rem;
}
h3 {
color: var(--secondary-color);
font-size: 1.5rem !important;
}
/* خلفية المحتوى */
.stApp {
background-color: var(--background-light);
}
/* تصميم القوائم والتبويبات */
.stTabs {
background-color: white;
border-radius: 10px;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
padding: 1rem;
margin-bottom: 1rem;
}
.stTabs [data-baseweb="tab-list"] {
gap: 1rem;
background-color: transparent;
}
.stTabs [data-baseweb="tab"] {
height: 45px;
border-radius: 5px;
padding: 0 20px;
background-color: #f1f5f9;
color: var(--heading-color);
font-weight: 600;
}
.stTabs [aria-selected="true"] {
background-color: var(--primary-color) !important;
color: white !important;
}
/* تنسيق الأزرار */
.stButton > button {
border-radius: 8px;
padding: 0.5rem 1rem;
font-weight: 600;
transition: all 0.3s ease;
min-height: 45px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border: none;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* أزرار متنوعة حسب الوظيفة */
.primary-btn > button {
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
color: white;
}
.success-btn > button {
background: linear-gradient(90deg, #059669, #065f46);
color: white;
}
.warning-btn > button {
background: linear-gradient(90deg, #d97706, #b45309);
color: white;
}
.danger-btn > button {
background: linear-gradient(90deg, #dc2626, #9f1239);
color: white;
}
.info-btn > button {
background: linear-gradient(90deg, #0891b2, #0e7490);
color: white;
}
/* البطاقات والمربعات */
.stExpander {
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
}
.stExpander > div:first-child {
background-color: white;
padding: 0.75rem;
}
.css-1qg05tj {
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
background-color: white;
margin-bottom: 1rem;
}
/* الجداول */
.dataframe {
border-collapse: collapse;
border-radius: 8px;
overflow: hidden;
margin-bottom: 1rem;
width: 100%;
}
.dataframe th {
background-color: var(--primary-light);
color: var(--primary-color);
font-weight: 600;
text-align: right;
padding: 0.75rem 1rem;
border: none;
}
.dataframe td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border-color);
border-right: none;
border-left: none;
}
.dataframe tr:nth-child(even) {
background-color: rgba(0,0,0,0.02);
}
.dataframe tr:hover {
background-color: rgba(0,0,0,0.05);
}
/* حقول الإدخال */
.stTextInput > div > div, .stNumberInput > div > div {
border-radius: 8px;
border: 1px solid var(--border-color);
}
.stTextInput > div > div:focus-within, .stNumberInput > div > div:focus-within {
border-color: var(--primary-color);
box-shadow: 0 0 0 1px var(--primary-light);
}
.stSelectbox > div[data-baseweb="select"] {
border-radius: 8px;
}
/* الشريط الجانبي */
.css-6qob1r.e1fqkh3o3 {
background-color: #134e4a;
background-image: linear-gradient(to bottom, #134e4a, #0f766e);
color: white;
}
.css-6qob1r.e1fqkh3o3 .css-ue6h4q.e1fqkh3o4 {
color: white;
border-bottom: 1px solid rgba(255,255,255,0.1);
padding-bottom: 1rem;
margin-bottom: 1rem;
}
.css-6qob1r.e1fqkh3o3 [data-testid="stSidebarNav"] {
padding-top: 1rem;
}
.css-6qob1r.e1fqkh3o3 [data-testid="stSidebarNav"] a {
color: white;
padding: 0.5rem 1rem;
border-radius: 5px;
transition: all 0.3s ease;
}
.css-6qob1r.e1fqkh3o3 [data-testid="stSidebarNav"] a:hover {
background-color: rgba(255,255,255,0.1);
}
/* الرسوم البيانية */
.js-plotly-plot {
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
padding: 1rem;
background-color: white;
}
/* تنسيقات المؤشرات والأرقام */
[data-testid="stMetricValue"] {
font-size: 1.5rem !important;
font-weight: 700 !important;
color: var(--primary-color);
}
[data-testid="stMetricLabel"] {
font-weight: 600;
}
[data-testid="stMetricDelta"] {
font-size: 0.875rem !important;
font-weight: 600 !important;
}
/* حاويات المحتوى */
.container-card {
background-color: white;
border-radius: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
padding: 1.5rem;
margin-bottom: 1.5rem;
}
/* تنسيق التنبيهات */
.stAlert {
border-radius: 8px;
}
/* تنسيق التقدم */
.stProgress > div > div {
border-radius: 10px;
height: 10px;
}
/* تنسيق التقويم */
.stDateInput {
border-radius: 8px;
}
</style>
""", unsafe_allow_html=True)
# تحسين شكل الأزرار بشكل متقدم
st.markdown("""
<style>
/* أنماط الأزرار المتقدمة */
.stButton > button {
border-radius: 10px !important;
padding: 0.6rem 1.5rem !important;
font-weight: 700 !important;
letter-spacing: 0.5px !important;
font-family: 'Almarai', sans-serif !important;
text-align: center !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 10px rgba(0,0,0,0.1) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
position: relative !important;
overflow: hidden !important;
border: none !important;
}
.stButton > button:hover {
transform: translateY(-3px) !important;
box-shadow: 0 8px 15px rgba(0,0,0,0.15) !important;
}
.stButton > button:active {
transform: translateY(0) !important;
box-shadow: 0 2px 5px rgba(0,0,0,0.1) !important;
}
/* أنماط الأزرار الرئيسية */
.primary-btn > button {
background: linear-gradient(135deg, #1E8A8A, #0F766E) !important;
color: white !important;
}
.primary-btn > button::before {
content: "" !important;
position: absolute !important;
top: 0 !important;
left: -100% !important;
width: 100% !important;
height: 100% !important;
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,0.2), rgba(255,255,255,0)) !important;
transform: skewX(-25deg) !important;
transition: all 0.75s ease !important;
}
.primary-btn > button:hover::before {
left: 100% !important;
}
/* أنماط الأزرار الثانوية */
.secondary-btn > button {
background: linear-gradient(135deg, #475569, #334155) !important;
color: white !important;
}
.secondary-btn > button::before {
content: "" !important;
position: absolute !important;
top: 0 !important;
left: -100% !important;
width: 100% !important;
height: 100% !important;
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,0.2), rgba(255,255,255,0)) !important;
transform: skewX(-25deg) !important;
transition: all 0.75s ease !important;
}
.secondary-btn > button:hover::before {
left: 100% !important;
}
/* أنماط أزرار النجاح */
.success-btn > button {
background: linear-gradient(135deg, #10b981, #059669) !important;
color: white !important;
}
.success-btn > button::before {
content: "" !important;
position: absolute !important;
top: 0 !important;
left: -100% !important;
width: 100% !important;
height: 100% !important;
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,0.2), rgba(255,255,255,0)) !important;
transform: skewX(-25deg) !important;
transition: all 0.75s ease !important;
}
.success-btn > button:hover::before {
left: 100% !important;
}
/* أنماط أزرار التحذير */
.warning-btn > button {
background: linear-gradient(135deg, #f59e0b, #d97706) !important;
color: white !important;
}
.warning-btn > button::before {
content: "" !important;
position: absolute !important;
top: 0 !important;
left: -100% !important;
width: 100% !important;
height: 100% !important;
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,0.2), rgba(255,255,255,0)) !important;
transform: skewX(-25deg) !important;
transition: all 0.75s ease !important;
}
.warning-btn > button:hover::before {
left: 100% !important;
}
/* أنماط أزرار الخطر */
.danger-btn > button {
background: linear-gradient(135deg, #ef4444, #b91c1c) !important;
color: white !important;
}
.danger-btn > button::before {
content: "" !important;
position: absolute !important;
top: 0 !important;
left: -100% !important;
width: 100% !important;
height: 100% !important;
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,0.2), rgba(255,255,255,0)) !important;
transform: skewX(-25deg) !important;
transition: all 0.75s ease !important;
}
.danger-btn > button:hover::before {
left: 100% !important;
}
/* أنماط أزرار المعلومات */
.info-btn > button {
background: linear-gradient(135deg, #3b82f6, #1d4ed8) !important;
color: white !important;
}
.info-btn > button::before {
content: "" !important;
position: absolute !important;
top: 0 !important;
left: -100% !important;
width: 100% !important;
height: 100% !important;
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,0.2), rgba(255,255,255,0)) !important;
transform: skewX(-25deg) !important;
transition: all 0.75s ease !important;
}
.info-btn > button:hover::before {
left: 100% !important;
}
/* أنماط الأزرار العضوية */
.glass-btn > button {
background: rgba(255, 255, 255, 0.2) !important;
backdrop-filter: blur(10px) !important;
border: 1px solid rgba(255, 255, 255, 0.3) !important;
color: var(--primary-color) !important;
}
.glass-btn > button:hover {
background: rgba(255, 255, 255, 0.3) !important;
border: 1px solid rgba(255, 255, 255, 0.4) !important;
}
/* أنماط الأزرار المسطحة */
.flat-btn > button {
background: transparent !important;
border: 2px solid var(--primary-color) !important;
color: var(--primary-color) !important;
box-shadow: none !important;
}
.flat-btn > button:hover {
background: var(--primary-light) !important;
box-shadow: none !important;
}
/* أنماط أزرار الإجراءات */
.action-btn > button {
display: flex !important;
align-items: center !important;
justify-content: center !important;
gap: 8px !important;
}
/* تنسيق أزرار مع أيقونات */
.icon-btn > button {
padding: 0.5rem !important;
min-width: 40px !important;
height: 40px !important;
border-radius: 50% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
</style>
""", unsafe_allow_html=True)
# وظيفة مساعدة لإنشاء أزرار بتنسيقات مختلفة
def styled_button(label, key, type="primary", on_click=None, args=None, full_width=False, icon=None):
"""
إنشاء زر بتنسيق معين
:param label: نص الزر
:param key: مفتاح الزر الفريد
:param type: نوع التنسيق ('primary', 'secondary', 'success', 'warning', 'danger', 'info', 'glass', 'flat')
:param on_click: الدالة التي سيتم تنفيذها عند النقر
:param args: معاملات الدالة
:param full_width: هل يأخذ الزر العرض كاملاً
:param icon: أيقونة لعرضها قبل النص (emoji)
:return: زر مُنسّق
"""
# استخدام مكونات Streamlit فقط بدون HTML
with st.container():
# إنشاء مساحة تعرض الزر فقط
col1 = st.columns([1])
# إضافة الأيقونة للنص إذا تم تزويدها
display_label = f"{icon} {label}" if icon else label
# إنشاء الزر مباشرة باستخدام Streamlit
clicked = col1[0].button(
display_label,
key=key,
on_click=on_click,
args=args,
use_container_width=full_width
)
return clicked
# وظيفة لإنشاء أزرار أيقونات صغيرة
def icon_button(icon, key, type="primary", on_click=None, args=None, tooltip=""):
"""
إنشاء زر أيقونة صغير
:param icon: الأيقونة (emoji)
:param key: مفتاح الزر الفريد
:param type: نوع التنسيق
:param on_click: الدالة التي سيتم تنفيذها عند النقر
:param args: معاملات الدالة
:param tooltip: تلميح عند تمرير المؤشر فوق الزر
:return: زر أيقونة
"""
# استخدام مكونات Streamlit فقط
with st.container():
# إضافة تلميح باستخدام Streamlit
if tooltip:
st.caption(tooltip)
# إنشاء زر الأيقونة
clicked = st.button(
icon,
key=key,
on_click=on_click,
args=args,
help=tooltip # استخدام خاصية help لعرض التلميح
)
return clicked
from modules.pricing.services.standard_pricing import StandardPricing
from modules.pricing.services.unbalanced_pricing import UnbalancedPricing
from modules.pricing.services.local_content_calculator import LocalContentCalculator
from modules.pricing.services.price_prediction import PricePrediction
from modules.pricing.services.construction_cost_calculator import ConstructionCostCalculator
from modules.pricing.services.construction_templates import ConstructionTemplates
from modules.pricing.services.templates_catalog.templates_catalog import TemplatesCatalog
from utils.excel_handler import export_to_excel
from utils.pdf_handler import export_pricing_to_pdf, export_pricing_with_analysis_to_pdf
from utils.helpers import format_number, format_currency, create_directory_if_not_exists
class PricingApp:
"""وحدة التسعير المتكاملة"""
def __init__(self):
"""تهيئة وحدة التسعير المتكاملة"""
self.pricing_methods = [
"التسعير القياسي",
"التسعير غير المتزن",
"التسعير التنافسي",
"التسعير الموجه بالربحية"
]
# تهيئة خدمات التسعير
self.standard_pricing = StandardPricing()
self.unbalanced_pricing = UnbalancedPricing()
self.local_content = LocalContentCalculator()
self.price_prediction = PricePrediction()
self.construction_calculator = ConstructionCostCalculator()
self.construction_templates = ConstructionTemplates()
self.templates_catalog = TemplatesCatalog(self.construction_templates)
def render(self):
"""عرض واجهة وحدة التسعير"""
# استخدام مكونات Streamlit مباشرة بدلاً من HTML
st.title("وحدة التسعير المتكاملة")
tabs = st.tabs([
"إنشاء تسعير جديد",
"تحليل سعر البند",
"نموذج التسعير الشامل",
"التسعير غير المتزن",
"المحتوى المحلي",
"حاسبة تكاليف البناء",
"كتالوج البنود النموذجية",
"الأدوات المساعدة"
])
with tabs[0]:
self._render_new_pricing_tab()
with tabs[1]:
self._render_item_analysis_tab()
with tabs[2]:
self._render_comprehensive_pricing_tab()
with tabs[3]:
self._render_unbalanced_pricing_tab()
with tabs[4]:
self._render_local_content_tab()
with tabs[5]:
self._render_construction_calculator_tab()
with tabs[6]:
self._render_templates_catalog_tab()
with tabs[7]:
self._render_utilities_tab()
def _render_templates_catalog_tab(self):
"""عرض تبويب كتالوج البنود النموذجية"""
st.markdown("### كتالوج البنود النموذجية")
# شرح كتالوج البنود النموذجية
with st.expander("دليل استخدام كتالوج البنود النموذجية", expanded=False):
st.markdown("""
**كتالوج البنود النموذجية** هو مكتبة شاملة من البنود الجاهزة لمختلف أنواع الأعمال الإنشائية (خرسانة، حديد، عزل، تشطيبات، إلخ).
### مميزات الكتالوج:
- تفاصيل دقيقة للمواد والعمالة والمعدات المطلوبة لكل بند.
- تحليل تكلفة تفصيلي يمكن استخدامه مباشرة في عروض الأسعار.
- ربط مباشر مع حاسبة تكاليف البناء وحاسبة الأسعار.
### كيفية الاستخدام:
- استخدام البنود النموذجية مباشرة في مشاريعك.
- تعديل البنود النموذجية لتناسب متطلبات المشروع.
- إضافة بنود جديدة إلى الكتالوج للاستخدام المستقبلي.
""")
# عرض الكتالوج باستخدام مكون TemplatesCatalog
self.templates_catalog.render()
def _render_item_analysis_tab(self):
"""عرض تبويب تحليل سعر البند"""
st.markdown("### تحليل سعر البند")
# التحقق من وجود تسعير حالي
if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None:
st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.")
return
# اختيار البند للتحليل
if 'current_pricing' in st.session_state and st.session_state.current_pricing is not None:
items = st.session_state.current_pricing['items']
item_options = items['رقم البند'].tolist()
selected_item = st.selectbox("اختر البند للتحليل", item_options, key="item_analysis_selector")
if selected_item:
item_data = items[items['رقم البند'] == selected_item].iloc[0]
st.markdown(f"### تحليل البند: {selected_item}")
st.markdown(f"**وصف البند**: {item_data['وصف البند']}")
st.markdown(f"**الوحدة**: {item_data['الوحدة']}")
st.markdown(f"**الكمية**: {item_data['الكمية']}")
st.markdown(f"**سعر الوحدة**: {item_data['سعر الوحدة']:,.2f} ريال")
# تحليل مكونات السعر
st.markdown("### تحليل مكونات السعر")
# عناصر التكلفة الافتراضية
cost_components = {
'المواد': 0.6, # 60% من التكلفة
'العمالة': 0.25, # 25% من التكلفة
'المعدات': 0.1, # 10% من التكلفة
'نفقات عامة': 0.05 # 5% من التكلفة
}
# حساب تكلفة كل عنصر
unit_price = item_data['سعر الوحدة']
component_values = {k: v * unit_price for k, v in cost_components.items()}
# عرض مكونات التكلفة في جدول
components_df = pd.DataFrame({
'العنصر': component_values.keys(),
'نسبة من التكلفة': [f"{v*100:.1f}%" for v in cost_components.values()],
'القيمة (ريال)': [f"{v:,.2f}" for v in component_values.values()]
})
st.table(components_df)
# رسم بياني لمكونات التكلفة
fig = px.pie(
names=list(component_values.keys()),
values=list(component_values.values()),
title='توزيع مكونات التكلفة'
)
st.plotly_chart(fig)
# تحليل تاريخي للأسعار
st.markdown("### تحليل تاريخي للأسعار")
# بيانات تاريخية افتراضية
historical_data = {
'التاريخ': ['2020-01', '2020-07', '2021-01', '2021-07', '2022-01', '2022-07', '2023-01', '2023-07'],
'السعر': [
unit_price * 0.7,
unit_price * 0.75,
unit_price * 0.8,
unit_price * 0.85,
unit_price * 0.9,
unit_price * 0.95,
unit_price,
unit_price * 1.05
]
}
hist_df = pd.DataFrame(historical_data)
# رسم بياني للتحليل التاريخي
fig = px.line(
hist_df,
x='التاريخ',
y='السعر',
title='تطور سعر الوحدة عبر الزمن',
markers=True
)
st.plotly_chart(fig)
# المقارنة مع الأسعار المرجعية
st.markdown("### المقارنة مع الأسعار المرجعية")
# بيانات مرجعية افتراضية
reference_data = {
'المصدر': ['قاعدة البيانات الداخلية', 'دليل الأسعار الاسترشادي', 'متوسط أسعار السوق', 'أسعار المشاريع المماثلة'],
'السعر المرجعي': [
unit_price * 0.95,
unit_price * 1.05,
unit_price * 1.1,
unit_price * 0.9
]
}
ref_df = pd.DataFrame(reference_data)
ref_df['الفرق عن السعر الحالي'] = ref_df['السعر المرجعي'] - unit_price
ref_df['نسبة الفرق'] = (ref_df['الفرق عن السعر الحالي'] / unit_price * 100).round(2).astype(str) + '%'
st.table(ref_df)
def _render_new_pricing_tab(self):
"""عرض تبويب إنشاء تسعير جديد"""
st.markdown("### إنشاء تسعير جديد")
col1, col2 = st.columns(2)
with col1:
tender_name = st.text_input("اسم المناقصة", key="tender_name_input")
client = st.text_input("الجهة المالكة", key="client_input")
pricing_method = st.selectbox("طريقة التسعير", self.pricing_methods, key="pricing_method_selector")
with col2:
tender_number = st.text_input("رقم المناقصة", key="tender_number_input")
location = st.text_input("الموقع", key="location_input")
submission_date = st.date_input("تاريخ التقديم", key="submission_date_input")
# خيارات بيانات البنود
st.markdown("### بيانات البنود")
data_source = st.radio(
"مصدر بيانات البنود",
["إدخال يدوي", "استيراد من Excel", "استيراد من وحدة تحليل المستندات", "استيراد من وحدة المشاريع"],
key="data_source_radio"
)
if data_source == "إدخال يدوي":
# ضبط CSS لتحسين ظهور الواجهة العربية
st.markdown("""
<style>
input, .stTextArea textarea {
direction: rtl;
text-align: right;
font-family: 'Arial', 'Tahoma', sans-serif !important;
}
.stTextInput > div > div > input {
text-align: right;
direction: rtl;
}
</style>
""", unsafe_allow_html=True)
# تهيئة قائمة الوحدات المتاحة
unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"]
# إنشاء بيانات افتراضية إذا لم تكن موجودة
if 'manual_items' not in st.session_state:
# إنشاء DataFrame فارغ
manual_items = pd.DataFrame(columns=[
'رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي'
])
# إضافة بضعة صفوف افتراضية
default_items = pd.DataFrame({
'رقم البند': ["A1", "A2", "A3", "A4", "A5"],
'وصف البند': [
"توريد وتركيب أعمال الخرسانة المسلحة للأساسات",
"توريد وتركيب حديد التسليح للأساسات",
"أعمال العزل المائي للأساسات",
"أعمال الردم والدك للأساسات",
"توريد وتركيب أعمال الخرسانة المسلحة للأعمدة"
],
'الوحدة': ["م3", "طن", "م2", "م3", "م3"],
'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0],
'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0],
'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0]
})
manual_items = pd.concat([manual_items, default_items])
st.session_state.manual_items = manual_items
# عرض واجهة إدخال البنود
st.markdown("### إدخال تفاصيل البنود")
# التحقق من استخدام طريقة الإدخال البسيطة
use_simple_input = st.checkbox("استخدام طريقة الإدخال البسيطة", value=True, key="use_simple_input_checkbox")
if use_simple_input:
# عرض البنود الحالية كجدول للعرض فقط
st.markdown("### جدول البنود الحالية")
st.dataframe(st.session_state.manual_items, use_container_width=True, hide_index=True)
# إضافة بند جديد
st.markdown("### إضافة بند جديد")
col1, col2 = st.columns(2)
with col1:
new_id = st.text_input("رقم البند", value=f"A{len(st.session_state.manual_items)+1}", key="new_item_id")
new_desc = st.text_area("وصف البند", value="", key="new_item_description")
with col2:
new_unit = st.selectbox("الوحدة", options=unit_options, key="new_item_unit")
new_qty = st.number_input("الكمية", value=0.0, min_value=0.0, format="%.2f", key="new_item_qty")
new_price = st.number_input("سعر الوحدة", value=0.0, min_value=0.0, format="%.2f", key="new_item_price")
new_total = new_qty * new_price
st.info(f"إجمالي البند الجديد: {new_total:,.2f} ريال")
if st.button("إضافة البند", key="add_item_button"):
# التحقق من صحة البيانات
if new_id and new_desc and new_qty > 0:
# إنشاء صف جديد
new_row = pd.DataFrame({
'رقم البند': [new_id],
'وصف البند': [new_desc],
'الوحدة': [new_unit],
'الكمية': [float(new_qty)],
'سعر الوحدة': [float(new_price)],
'الإجمالي': [float(new_total)]
})
# إضافة الصف إلى DataFrame
st.session_state.manual_items = pd.concat([st.session_state.manual_items, new_row], ignore_index=True)
st.success("تم إضافة البند بنجاح!")
st.rerun()
else:
st.error("يرجى ملء جميع الحقول المطلوبة: رقم البند، الوصف، والكمية يجب أن تكون أكبر من صفر.")
# تعديل البنود الحالية
st.markdown("### تعديل البنود الحالية")
# تحديد البند المراد تعديله
item_to_edit = st.selectbox(
"اختر البند للتعديل",
options=st.session_state.manual_items['رقم البند'].tolist(),
format_func=lambda x: f"{x}: {st.session_state.manual_items[st.session_state.manual_items['رقم البند'] == x]['وصف البند'].values[0][:30]}...",
key="item_to_edit_selector"
)
if item_to_edit:
# الحصول على مؤشر الصف للبند المحدد
idx = st.session_state.manual_items[st.session_state.manual_items['رقم البند'] == item_to_edit].index[0]
row = st.session_state.manual_items.loc[idx]
# إنشاء نموذج تعديل
col1, col2 = st.columns(2)
with col1:
edited_id = st.text_input("رقم البند (تعديل)", value=row['رقم البند'], key="edit_id")
edited_desc = st.text_area("وصف البند (تعديل)", value=row['وصف البند'], key="edit_desc")
with col2:
edited_unit = st.selectbox(
"الوحدة (تعديل)",
options=unit_options,
index=unit_options.index(row['الوحدة']) if row['الوحدة'] in unit_options else 0,
key="edit_unit"
)
edited_qty = st.number_input("الكمية (تعديل)", value=float(row['الكمية']), min_value=0.0, format="%.2f", key="edit_qty")
edited_price = st.number_input("سعر الوحدة (تعديل)", value=float(row['سعر الوحدة']), min_value=0.0, format="%.2f", key="edit_price")
edited_total = edited_qty * edited_price
st.info(f"إجمالي البند بعد التعديل: {edited_total:,.2f} ريال")
col1, col2 = st.columns(2)
with col1:
if st.button("حفظ التعديلات", key="save_edit_button"):
# تحديث البند
st.session_state.manual_items.at[idx, 'رقم البند'] = edited_id
st.session_state.manual_items.at[idx, 'وصف البند'] = edited_desc
st.session_state.manual_items.at[idx, 'الوحدة'] = edited_unit
st.session_state.manual_items.at[idx, 'الكمية'] = edited_qty
st.session_state.manual_items.at[idx, 'سعر الوحدة'] = edited_price
st.session_state.manual_items.at[idx, 'الإجمالي'] = edited_total
st.success("تم تحديث البند بنجاح!")
st.rerun()
with col2:
if st.button("حذف هذا البند", key="delete_item_button"):
st.session_state.manual_items = st.session_state.manual_items.drop(idx).reset_index(drop=True)
st.warning("تم حذف البند!")
st.rerun()
# المجموع الكلي
total = st.session_state.manual_items['الإجمالي'].sum()
st.metric("المجموع الكلي", f"{total:,.2f} ريال")
# جعل هذه البيانات متاحة للاستخدام في الخطوات التالية
edited_items = st.session_state.manual_items.copy()
else:
# عرض رسالة توضح أن طريقة الإدخال البسيطة هي الأفضل
st.warning("لتجنب مشاكل عدم التوافق في أنواع البيانات، يُفضل استخدام طريقة الإدخال البسيطة.")
# محاولة استخدام المحرر القياسي مع معالجة الأخطاء
try:
# تحويل البيانات إلى الأنواع المناسبة
for col in st.session_state.manual_items.columns:
if col in ['رقم البند', 'وصف البند', 'الوحدة']:
st.session_state.manual_items[col] = st.session_state.manual_items[col].astype(str)
# عرض المحرر (للقراءة فقط)
st.dataframe(st.session_state.manual_items, use_container_width=True, hide_index=True)
# إنشاء نظام تعديل منفصل
st.markdown("### تعديل أسعار الوحدات")
for idx, row in st.session_state.manual_items.iterrows():
col1, col2 = st.columns([3, 1])
with col1:
st.text(f"{row['رقم البند']}: {row['وصف البند'][:50]}")
with col2:
price = st.number_input(
f"سعر الوحدة ({row['الوحدة']})",
value=float(row['سعر الوحدة']),
min_value=0.0,
key=f"price_{idx}"
)
# تحديث السعر والإجمالي
st.session_state.manual_items.at[idx, 'سعر الوحدة'] = price
st.session_state.manual_items.at[idx, 'الإجمالي'] = price * row['الكمية']
# المجموع الكلي
total = st.session_state.manual_items['الإجمالي'].sum()
st.metric("المجموع الكلي", f"{total:,.2f} ريال")
# جعل هذه البيانات متاحة للاستخدام في الخطوات التالية
edited_items = st.session_state.manual_items.copy()
except Exception as e:
st.error(f"حدث خطأ: {str(e)}")
st.info("يرجى استخدام طريقة الإدخال البسيطة لتجنب هذه المشكلة.")
elif data_source == "استيراد من Excel":
uploaded_file = st.file_uploader("رفع ملف Excel", type=["xlsx", "xls"])
if uploaded_file is not None:
st.success("تم رفع الملف بنجاح")
# محاكاة قراءة الملف
st.markdown("### معاينة البيانات المستوردة")
# إنشاء بيانات افتراضية
import_items = pd.DataFrame({
'رقم البند': ["A1", "A2", "A3", "A4", "A5", "A6", "A7"],
'وصف البند': [
"توريد وتركيب أعمال الخرسانة المسلحة للأساسات",
"توريد وتركيب حديد التسليح للأساسات",
"أعمال العزل المائي للأساسات",
"أعمال الردم والدك للأساسات",
"توريد وتركيب أعمال الخرسانة المسلحة للأعمدة",
"توريد وتركيب حديد التسليح للأعمدة",
"أعمال البلوك للجدران"
],
'الوحدة': ["م3", "طن", "م2", "م3", "م3", "طن", "م2"],
'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0, 10.0, 400.0],
'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
})
st.dataframe(import_items)
if st.button("استيراد البيانات", key="import_excel_button"):
st.session_state.manual_items = import_items.copy()
st.session_state.manual_items_modified = True
st.success("تم استيراد البيانات بنجاح!")
st.rerun()
elif data_source == "استيراد من وحدة تحليل المستندات":
available_documents = [
"كراسة شروط مشروع توسعة مستشفى الملك فهد",
"جدول كميات صيانة محطات المياه",
"مخططات إنشاء مدرسة ثانوية"
]
selected_doc = st.selectbox("اختر المستند", available_documents, key="document_selector")
if styled_button("استيراد البيانات من تحليل المستند", key="import_doc_analysis_button", type="info", icon="📄", full_width=True):
# محاكاة استيراد البيانات
with st.spinner("جاري استيراد البيانات..."):
time.sleep(2)
# إنشاء بيانات افتراضية
doc_items = pd.DataFrame({
'رقم البند': ["A1", "A2", "A3", "A4", "A5", "A6", "A7"],
'وصف البند': [
"توريد وتركيب أعمال الخرسانة المسلحة للأساسات",
"توريد وتركيب حديد التسليح للأساسات",
"أعمال العزل المائي للأساسات",
"أعمال الردم والدك للأساسات",
"توريد وتركيب أعمال الخرسانة المسلحة للأعمدة",
"توريد وتركيب حديد التسليح للأعمدة",
"أعمال البلوك للجدران"
],
'الوحدة': ["م3", "طن", "م2", "م3", "م3", "طن", "م2"],
'الكمية': [250.0, 25.0, 500.0, 300.0, 120.0, 10.0, 400.0],
'سعر الوحدة': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
'الإجمالي': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
})
st.session_state.manual_items = doc_items.copy()
st.success("تم استيراد البيانات من تحليل المستند بنجاح!")
st.dataframe(doc_items)
elif data_source == "استيراد من وحدة المشاريع":
# قائمة المشاريع المتاحة للاستيراد منها
available_projects = [
"مشروع تطوير طريق الملك عبدالعزيز",
"مشروع إنشاء محطة تحلية المياه بالجبيل",
"مشروع توسعة مستشفى الملك فهد",
"مشروع إنشاء مجمع سكني بالرياض"
]
selected_project = st.selectbox("اختر المشروع", available_projects, key="project_selector")
if styled_button("استيراد البيانات من المشروع", key="import_project_data_button", type="primary", icon="🏗️", full_width=True):
# محاكاة استيراد البيانات من المشروع
with st.spinner("جاري استيراد بيانات المشروع..."):
time.sleep(1.5)
# إنشاء بيانات مشروع افتراضية بناءً على المشروع المختار
if selected_project == "مشروع تطوير طريق الملك عبدالعزيز":
project_items = pd.DataFrame({
'رقم البند': ["R1", "R2", "R3", "R4", "R5"],
'وصف البند': [
"أعمال الحفر والردم للطريق",
"توريد وتنفيذ طبقة الأساس",
"طبقة الأسفلت الأولى",
"طبقة الأسفلت النهائية",
"أعمال الدهانات والعلامات"
],
'الوحدة': ["م3", "م2", "م2", "م2", "م.ط"],
'الكمية': [5000.0, 8000.0, 8000.0, 8000.0, 4000.0],
'سعر الوحدة': [45.0, 120.0, 85.0, 95.0, 25.0],
'الإجمالي': [225000.0, 960000.0, 680000.0, 760000.0, 100000.0]
})
elif selected_project == "مشروع إنشاء محطة تحلية المياه بالجبيل":
project_items = pd.DataFrame({
'رقم البند': ["W1", "W2", "W3", "W4", "W5"],
'وصف البند': [
"أعمال الخرسانة المسلحة للخزانات",
"توريد وتركيب معدات التحلية",
"أعمال التمديدات والأنابيب",
"تشطيبات المباني الإدارية",
"أعمال الكهرباء والتحكم"
],
'الوحدة': ["م3", "قطعة", "م.ط", "م2", "مقطوعية"],
'الكمية': [1800.0, 12.0, 5000.0, 1200.0, 1.0],
'سعر الوحدة': [1200.0, 250000.0, 850.0, 750.0, 1500000.0],
'الإجمالي': [2160000.0, 3000000.0, 4250000.0, 900000.0, 1500000.0]
})
elif selected_project == "مشروع توسعة مستشفى الملك فهد":
project_items = pd.DataFrame({
'رقم البند': ["H1", "H2", "H3", "H4", "H5", "H6"],
'وصف البند': [
"أعمال الهيكل الخرساني",
"أعمال البناء والجدران",
"التشطيبات الداخلية",
"الأنظمة الكهربائية والميكانيكية",
"تجهيزات طبية",
"أعمال الموقع العام"
],
'الوحدة': ["م3", "م2", "م2", "غرفة", "قطعة", "م2"],
'الكمية': [2800.0, 4500.0, 7500.0, 120.0, 45.0, 3000.0],
'سعر الوحدة': [1500.0, 650.0, 1200.0, 35000.0, 80000.0, 350.0],
'الإجمالي': [4200000.0, 2925000.0, 9000000.0, 4200000.0, 3600000.0, 1050000.0]
})
else: # مشروع إنشاء مجمع سكني بالرياض
project_items = pd.DataFrame({
'رقم البند': ["B1", "B2", "B3", "B4", "B5", "B6", "B7"],
'وصف البند': [
"الهياكل الخرسانية للفلل",
"الجدران والقواطع الداخلية",
"التشطيبات الخارجية",
"التشطيبات الداخلية",
"أعمال الكهرباء والسباكة",
"الملحقات والحدائق",
"البنية التحتية للموقع"
],
'الوحدة': ["م3", "م2", "م2", "م2", "فيلا", "م2", "مقطوعية"],
'الكمية': [3500.0, 12000.0, 8000.0, 18000.0, 25.0, 5000.0, 1.0],
'سعر الوحدة': [1350.0, 450.0, 380.0, 750.0, 85000.0, 280.0, 2500000.0],
'الإجمالي': [4725000.0, 5400000.0, 3040000.0, 13500000.0, 2125000.0, 1400000.0, 2500000.0]
})
st.session_state.manual_items = project_items.copy()
st.success(f"تم استيراد بيانات المشروع '{selected_project}' بنجاح!")
st.dataframe(project_items)
# زر بدء التسعير
if styled_button("بدء التسعير", key="start_pricing_button", type="success", icon="✅", full_width=True):
# تحقق من صحة البيانات
if 'manual_items' in st.session_state and not st.session_state.manual_items.empty:
# التأكد من حساب الإجمالي قبل الحفظ
st.session_state.manual_items['الإجمالي'] = st.session_state.manual_items['الكمية'] * st.session_state.manual_items['سعر الوحدة']
# حفظ بيانات التسعير الحالي
st.session_state.current_pricing = {
'name': tender_name,
'number': tender_number,
'client': client,
'location': location,
'method': pricing_method,
'submission_date': submission_date,
'items': st.session_state.manual_items.copy(),
'status': 'جديد',
'created_at': datetime.now()
}
# الانتقال إلى تبويب نموذج التسعير الشامل
st.success("تم إنشاء التسعير بنجاح! يمكنك الانتقال إلى نموذج التسعير الشامل.")
else:
st.error("يرجى إدخال بيانات البنود أولاً.")
def _render_comprehensive_pricing_tab(self):
"""عرض تبويب نموذج التسعير الشامل"""
st.markdown("### نموذج التسعير الشامل")
# التحقق من وجود تسعير حالي
if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None:
st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.")
return
# عرض معلومات التسعير الحالي
pricing = st.session_state.current_pricing
col1, col2, col3 = st.columns(3)
with col1:
st.metric("اسم المناقصة", pricing['name'])
st.metric("الجهة المالكة", pricing['client'])
with col2:
st.metric("رقم المناقصة", pricing['number'])
st.metric("تاريخ التقديم", pricing['submission_date'].strftime("%Y-%m-%d"))
with col3:
st.metric("طريقة التسعير", pricing['method'])
st.metric("الموقع", pricing['location'])
# عرض البنود والتسعير
st.markdown("### بنود التسعير")
items = pricing['items'].copy()
# إضافة أسعار الوحدة للمحاكاة
if 'سعر الوحدة' in items.columns and (items['سعر الوحدة'] == 0).all():
items['سعر الوحدة'] = [
round(random.uniform(1000, 3000), 2), # الخرسانة
round(random.uniform(5000, 7000), 2), # الحديد
round(random.uniform(100, 200), 2), # العزل
round(random.uniform(50, 100), 2), # الردم
round(random.uniform(1200, 3500), 2), # الخرسانة للأعمدة
]
if len(items) > 5:
for i in range(5, len(items)):
items.at[i, 'سعر الوحدة'] = round(random.uniform(500, 5000), 2)
# حساب الإجمالي
items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة']
# عرض البنود
st.dataframe(items, use_container_width=True, hide_index=True)
# ✅ التوصية الذكية باستخدام OpenAI
with st.expander("🔍 توليد توصية ذكية باستخدام AI"):
if styled_button("توليد توصية ذكية باستخدام AI", key="gen_ai_recommendation_btn", type="secondary", icon="🤖", full_width=True):
import openai
import os
# تهيئة عميل OpenAI - استخدام واجهة الإصدار الجديد فقط (1.0.0 وما فوق)
api_key = os.environ.get("ai")
client = openai.OpenAI(api_key=api_key)
items_df = items.copy()
prompt = f"""قم بتحليل الجدول التالي للبنود في مشروع إنشاء، وقدم توصية ذكية لتحسين التسعير وضمان التوازن المالي. الجدول يحتوي على البنود، الكميات، الأسعار، والإجماليات:\n\n{items_df.to_string(index=False)}\n\nالتوصية:\n"""
try:
with st.spinner("جاري توليد التوصية..."):
# استخدام واجهة OpenAI الجديدة
response = client.chat.completions.create(
model="gpt-4o", # استخدام أحدث نموذج من OpenAI GPT-4o
messages=[
{"role": "system", "content": "أنت خبير في تسعير مشاريع البناء والبنية التحتية."},
{"role": "user", "content": prompt}
],
temperature=0.4,
max_tokens=500
)
# استخراج محتوى الرسالة من واجهة OpenAI الجديدة
recommendation = response.choices[0].message.content
st.success("تم توليد التوصية بنجاح!")
st.markdown("#### التوصية الذكية:")
st.info(recommendation)
except Exception as e:
st.error(f"حدث خطأ أثناء الاتصال بنموذج OpenAI: {e}")
st.info("يجب التأكد من تثبيت أحدث إصدار من مكتبة OpenAI: `pip install openai --upgrade`")
# واجهة تعديل أسعار الوحدات
st.markdown("### تعديل أسعار الوحدات")
# تقسيم البنود إلى مجموعتين للعرض
col1, col2 = st.columns(2)
half = len(items) // 2 + len(items) % 2
with col1:
for idx in range(half):
if idx < len(items):
row = items.iloc[idx]
price = st.number_input(
f"{row['رقم البند']}: {row['وصف البند'][:30]}... ({row['الوحدة']})",
value=float(row['سعر الوحدة']),
min_value=0.0,
key=f"price1_{idx}"
)
items.at[idx, 'سعر الوحدة'] = price
items.at[idx, 'الإجمالي'] = price * items.at[idx, 'الكمية']
with col2:
for idx in range(half, len(items)):
row = items.iloc[idx]
price = st.number_input(
f"{row['رقم البند']}: {row['وصف البند'][:30]}... ({row['الوحدة']})",
value=float(row['سعر الوحدة']),
min_value=0.0,
key=f"price2_{idx}"
)
items.at[idx, 'سعر الوحدة'] = price
items.at[idx, 'الإجمالي'] = price * items.at[idx, 'الكمية']
# حساب وعرض إجماليات التسعير
total_price = items['الإجمالي'].sum()
st.markdown("### إجماليات التسعير")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("إجمالي التكاليف المباشرة", f"{total_price:,.2f} ريال")
with col2:
overhead_percentage = st.slider("نسبة المصاريف العامة والأرباح (%)", 5, 30, 15)
overhead_value = total_price * overhead_percentage / 100
st.metric("المصاريف العامة والأرباح", f"{overhead_value:,.2f} ريال")
with col3:
grand_total = total_price + overhead_value
st.metric("الإجمالي النهائي", f"{grand_total:,.2f} ريال")
# رسم بياني لتوزيع التكاليف
st.markdown("### تحليل التكاليف")
# حساب النسب المئوية لكل بند
pie_data = items.copy()
pie_data['نسبة من إجمالي التكاليف'] = pie_data['الإجمالي'] / total_price * 100
fig = px.pie(
pie_data,
values='نسبة من إجمالي التكاليف',
names='وصف البند',
title='توزيع التكاليف حسب البنود',
hole=0.4
)
st.plotly_chart(fig, use_container_width=True)
# أزرار العمليات
col1, col2, col3 = st.columns(3)
with col1:
if styled_button("حفظ التسعير", key="save_comprehensive_pricing_button", type="primary", icon="💾"):
# تحديث بيانات التسعير الحالي
st.session_state.current_pricing['items'] = items.copy()
st.success("تم حفظ التسعير بنجاح!")
with col2:
if styled_button("تصدير إلى Excel", key="export_to_excel_button", type="info", icon="📊"):
try:
# إنشاء دليل الصادرات إذا لم يكن موجودًا
export_dir = "exports"
create_directory_if_not_exists(export_dir)
# تصدير البيانات إلى ملف Excel
file_path = os.path.join(export_dir, f"تسعير_{tender_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx")
export_to_excel(items, file_path, tender_name)
st.success(f"تم تصدير التسعير إلى Excel بنجاح! مسار الملف: {file_path}")
except Exception as e:
st.error(f"حدث خطأ أثناء تصدير البيانات: {str(e)}")
if styled_button("تصدير إلى PDF", key="export_to_pdf_button", type="info", icon="📄"):
try:
# إنشاء دليل الصادرات إذا لم يكن موجودًا
export_dir = "exports"
create_directory_if_not_exists(export_dir)
# تصدير البيانات إلى ملف PDF
file_path = os.path.join(export_dir, f"تسعير_{tender_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf")
# التحقق من وجود بيانات تحليل الأسعار
if 'items_price_analysis' in st.session_state and st.session_state.items_price_analysis:
# استخراج المعلومات الأساسية للمشروع
project_info = {
'اسم_المشروع': tender_name,
'وصف_المشروع': f"مناقصة رقم: {tender_number} - الجهة المالكة: {client} - الموقع: {location}"
}
# تصدير البيانات مع تحليل الأسعار
export_pricing_with_analysis_to_pdf(
items,
st.session_state.items_price_analysis,
file_path,
title=f"تسعير مناقصة: {tender_name}",
project_info=project_info
)
else:
# تصدير البيانات بدون تحليل الأسعار
export_pricing_to_pdf(
items,
file_path,
title=f"تسعير مناقصة: {tender_name}",
description=f"مناقصة رقم: {tender_number} - الجهة المالكة: {client} - الموقع: {location}"
)
st.success(f"تم تصدير التسعير إلى PDF بنجاح! مسار الملف: {file_path}")
except Exception as e:
st.error(f"حدث خطأ أثناء تصدير البيانات: {str(e)}")
with col3:
if styled_button("تحليل المخاطر المالية", key="financial_risk_analysis_button", type="warning", icon="⚠️"):
st.success("تم إرسال الطلب إلى وحدة تحليل المخاطر!")
def _render_unbalanced_pricing_tab(self):
"""عرض تبويب التسعير غير المتزن"""
st.markdown("### التسعير غير المتزن")
# التحقق من وجود تسعير حالي
if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None:
st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.")
return
# شرح التسعير غير المتزن
with st.expander("ما هو التسعير غير المتزن؟", expanded=False):
st.markdown("""
**التسعير غير المتزن** هو استراتيجية تسعير تقوم على توزيع التكاليف بين بنود المناقصة بشكل غير متساوٍ، مع الحفاظ على إجمالي قيمة العطاء.
### استراتيجيات التسعير غير المتزن:
1. **التحميل الأمامي (Front Loading)**: زيادة أسعار البنود المبكرة في المشروع للحصول على تدفق نقدي أفضل في بداية المشروع.
2. **التحميل الخلفي (Back Loading)**: زيادة أسعار البنود المتأخرة في المشروع.
3. **تحميل البنود المؤكدة**: زيادة أسعار البنود التي من المؤكد تنفيذها بالكميات المحددة.
4. **تخفيض أسعار البنود المحتملة**: تخفيض أسعار البنود التي قد تزيد كمياتها أثناء التنفيذ.
### مزايا التسعير غير المتزن:
- تحسين التدفق النقدي للمشروع.
- تعظيم الربحية في حالة التغييرات والأوامر التغييرية.
- زيادة فرص الفوز بالمناقصة.
### مخاطر التسعير غير المتزن:
- قد يتم رفض العطاء إذا كان عدم التوازن واضحاً.
- قد تتأثر السمعة سلباً إذا تم استخدامه بشكل مفرط.
- قد يؤدي إلى خسائر إذا لم يتم تنفيذ البنود ذات الأسعار العالية.
""")
# عرض بنود التسعير الحالي
items = st.session_state.current_pricing['items'].copy()
# إضافة عمود إستراتيجية التسعير
if 'إستراتيجية التسعير' not in items.columns:
items['إستراتيجية التسعير'] = 'متوازن'
st.markdown("### إستراتيجية التسعير غير المتزن")
# اختيار الإستراتيجية
strategy = st.selectbox(
"اختر إستراتيجية التسعير",
[
"تحميل أمامي (Front Loading)",
"تحميل البنود المؤكدة",
"تخفيض البنود المحتمل زيادتها",
"إستراتيجية مخصصة"
],
key="pricing_strategy_selector"
)
# تطبيق الإستراتيجية المختارة
if strategy == "تحميل أمامي (Front Loading)":
# محاكاة تحميل أمامي
items_count = len(items)
early_items = items.iloc[:items_count//3].index
middle_items = items.iloc[items_count//3:2*items_count//3].index
late_items = items.iloc[2*items_count//3:].index
# تطبيق الزيادة والنقصان
for idx in early_items:
items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.3 # زيادة 30%
items.at[idx, 'إستراتيجية التسعير'] = 'زيادة'
for idx in middle_items:
items.at[idx, 'إستراتيجية التسعير'] = 'متوازن'
for idx in late_items:
items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.7 # نقص 30%
items.at[idx, 'إستراتيجية التسعير'] = 'نقص'
elif strategy == "تحميل البنود المؤكدة":
# محاكاة - اعتبار بعض البنود مؤكدة
confirmed_items = [0, 2, 4] # الأصفار-مستندة
variable_items = [idx for idx in range(len(items)) if idx not in confirmed_items]
# تطبيق الزيادة والنقصان
for idx in confirmed_items:
if idx < len(items):
items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.25 # زيادة 25%
items.at[idx, 'إستراتيجية التسعير'] = 'زيادة'
for idx in variable_items:
if idx < len(items):
items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.85 # نقص 15%
items.at[idx, 'إستراتيجية التسعير'] = 'نقص'
elif strategy == "تخفيض البنود المحتمل زيادتها":
# محاكاة - اعتبار بعض البنود محتمل زيادتها
variable_items = [1, 3] # الأصفار-مستندة
other_items = [idx for idx in range(len(items)) if idx not in variable_items]
# تطبيق الزيادة والنقصان
for idx in variable_items:
if idx < len(items):
items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 0.7 # نقص 30%
items.at[idx, 'إستراتيجية التسعير'] = 'نقص'
for idx in other_items:
if idx < len(items):
items.at[idx, 'سعر الوحدة'] = items.at[idx, 'سعر الوحدة'] * 1.15 # زيادة 15%
items.at[idx, 'إستراتيجية التسعير'] = 'زيادة'
else: # إستراتيجية مخصصة
st.markdown("### تعديل أسعار البنود يدوياً")
st.markdown("قم بتعديل أسعار البنود وإستراتيجية التسعير يدوياً من خلال النموذج أدناه.")
# إضافة واجهة لتعديل الأسعار يدوياً
if 'edit_items' not in st.session_state:
st.session_state.edit_items = items.copy()
# عرض نموذج التعديل لكل بند
for index, row in items.iterrows():
with st.container():
col1, col2, col3, col4 = st.columns([3, 1, 1, 1])
with col1:
st.markdown(f"**البند:** {row['وصف البند']}")
with col2:
original_price = st.session_state.current_pricing['items'].iloc[index]['سعر الوحدة']
new_price = st.number_input(
"سعر الوحدة الجديد",
min_value=0.01,
value=float(row['سعر الوحدة']),
key=f"price_{index}"
)
items.at[index, 'سعر الوحدة'] = new_price
with col3:
percent_change = ((new_price - original_price) / original_price) * 100
st.metric(
"نسبة التغيير",
f"{percent_change:.1f}%",
delta=f"{new_price - original_price:.2f}"
)
with col4:
strategy_options = ['متوازن', 'زيادة', 'نقص']
current_strategy = row['إستراتيجية التسعير']
strategy_index = strategy_options.index(current_strategy) if current_strategy in strategy_options else 0
new_strategy = st.selectbox(
"الإستراتيجية",
options=strategy_options,
index=strategy_index,
key=f"strategy_{index}"
)
items.at[index, 'إستراتيجية التسعير'] = new_strategy
st.markdown("---")
# حساب الإجمالي بعد التعديل
items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة']
# تعيين ألوان للإستراتيجيات وتنسيق الجدول بشكل متقدم
def highlight_row(row):
strategy = row['إستراتيجية التسعير']
styles = [''] * len(row)
# تطبيق لون خلفية لكل صف حسب الإستراتيجية
if strategy == 'زيادة':
background = 'linear-gradient(90deg, rgba(168, 230, 207, 0.3), rgba(168, 230, 207, 0.1))'
text_color = '#1F7A8C'
elif strategy == 'نقص':
background = 'linear-gradient(90deg, rgba(255, 154, 162, 0.3), rgba(255, 154, 162, 0.1))'
text_color = '#9D2A45'
else:
background = 'linear-gradient(90deg, rgba(220, 237, 255, 0.3), rgba(220, 237, 255, 0.1))'
text_color = '#555555'
# تطبيق النمط على جميع الخلايا في الصف
for i in range(len(styles)):
styles[i] = f'background: {background}; color: {text_color}; border-bottom: 1px solid #ddd;'
# تطبيق نمط خاص على خلية الإستراتيجية
if strategy == 'زيادة':
styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #a8e6cf; color: #007263; font-weight: bold; border-radius: 5px; text-align: center;'
elif strategy == 'نقص':
styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #ff9aa2; color: #9D2A45; font-weight: bold; border-radius: 5px; text-align: center;'
else:
styles[list(row.index).index('إستراتيجية التسعير')] = 'background-color: #dceeff; color: #555555; font-weight: bold; border-radius: 5px; text-align: center;'
# تنسيق عمود السعر
price_idx = list(row.index).index('سعر الوحدة')
styles[price_idx] = styles[price_idx] + 'font-weight: bold;'
# تنسيق عمود الإجمالي
total_idx = list(row.index).index('الإجمالي')
styles[total_idx] = styles[total_idx] + 'font-weight: bold;'
return styles
# عرض الجدول مع تنسيق متقدم
st.subheader("بنود التسعير غير المتزن")
# تطبيق التنسيق على الجدول
styled_items = items.style.apply(highlight_row, axis=1)
# تنسيق تنسيق الأرقام
styled_items = styled_items.format({
'الكمية': '{:,.2f}',
'سعر الوحدة': '{:,.2f}',
'الإجمالي': '{:,.2f}'
})
st.dataframe(styled_items, use_container_width=True, height=None)
# المقارنة بين التسعير المتوازن وغير المتوازن
st.subheader("مقارنة التسعير المتوازن وغير المتوازن")
original_items = st.session_state.current_pricing['items'].copy()
original_total = original_items['الإجمالي'].sum()
unbalanced_total = items['الإجمالي'].sum()
# عرض بطاقات المقارنة بتصميم متقدم
st.markdown("""
<style>
.metric-container {
background: linear-gradient(to right, #f1f8ff, #ffffff);
border-radius: 10px;
padding: 15px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
text-align: center;
border: 1px solid #e6f2ff;
}
.metric-title {
color: #555;
font-size: 0.9em;
margin-bottom: 5px;
}
.metric-value {
color: #1F7A8C;
font-size: 1.8em;
font-weight: bold;
margin: 5px 0;
}
.metric-delta {
font-size: 0.9em;
font-weight: bold;
padding: 3px 8px;
border-radius: 10px;
display: inline-block;
margin-top: 5px;
}
.positive-delta {
background-color: rgba(40, 167, 69, 0.1);
color: #28a745;
}
.negative-delta {
background-color: rgba(220, 53, 69, 0.1);
color: #dc3545;
}
.neutral-delta {
background-color: rgba(108, 117, 125, 0.1);
color: #6c757d;
}
</style>
""", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
st.markdown("""
<div class="metric-container">
<div class="metric-title">إجمالي التسعير المتوازن</div>
<div class="metric-value">{:,.2f} ريال</div>
<div class="metric-delta neutral-delta">التسعير الأصلي</div>
</div>
""".format(original_total), unsafe_allow_html=True)
with col2:
st.markdown("""
<div class="metric-container">
<div class="metric-title">إجمالي التسعير غير المتوازن</div>
<div class="metric-value">{:,.2f} ريال</div>
<div class="metric-delta {}">بعد إعادة توزيع الأسعار</div>
</div>
""".format(
unbalanced_total,
"positive-delta" if unbalanced_total > original_total else "negative-delta" if unbalanced_total < original_total else "neutral-delta"
), unsafe_allow_html=True)
with col3:
diff = unbalanced_total - original_total
delta_percent = diff/original_total*100 if original_total > 0 else 0
st.markdown("""
<div class="metric-container">
<div class="metric-title">الفرق بين التسعيرين</div>
<div class="metric-value">{:,.2f} ريال</div>
<div class="metric-delta {}">نسبة الفرق: {:+.1f}%</div>
</div>
""".format(
diff,
"positive-delta" if diff > 0 else "negative-delta" if diff < 0 else "neutral-delta",
delta_percent
), unsafe_allow_html=True)
# المعايرة للحفاظ على إجمالي التسعير
if abs(diff) > 1: # إذا كان هناك فرق كبير
if styled_button("معايرة الأسعار للحفاظ على إجمالي التسعير", key="calibrate_prices_button", type="primary", icon="⚖️", full_width=True):
# تعديل الأسعار للحفاظ على إجمالي التكلفة
adjustment_factor = original_total / unbalanced_total
items['سعر الوحدة'] = items['سعر الوحدة'] * adjustment_factor
items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة']
st.success(f"تم تعديل الأسعار للحفاظ على إجمالي التسعير الأصلي ({original_total:,.2f} ريال)")
st.dataframe(items, use_container_width=True)
# رسم بياني للمقارنة
st.subheader("تحليل بصري للتسعير غير المتوازن")
# إعداد البيانات للرسم البياني
chart_data = pd.DataFrame({
'وصف البند': original_items['وصف البند'],
'التسعير المتوازن': original_items['الإجمالي'],
'التسعير غير المتوازن': items['الإجمالي']
})
# إضافة عمود للنسبة المئوية للتغيير
chart_data['نسبة التغيير'] = (chart_data['التسعير غير المتوازن'] - chart_data['التسعير المتوازن']) / chart_data['التسعير المتوازن'] * 100
# تحديد لون الأعمدة بناءً على نسبة التغيير
bar_colors = []
for change in chart_data['نسبة التغيير']:
if change > 5: # زيادة كبيرة
bar_colors.append('#1F7A8C') # أزرق مخضر
elif change > 0: # زيادة صغيرة
bar_colors.append('#81B29A') # أخضر فاتح
elif change > -5: # نقص صغير
bar_colors.append('#F2CC8F') # أصفر
else: # نقص كبير
bar_colors.append('#E07A5F') # أحمر
# التبويب بين مخططات مختلفة للمقارنة
chart_tabs = st.tabs(["مخطط شريطي", "مخطط مقارنة", "مخطط نسبة التغيير"])
with chart_tabs[0]: # رسم بياني شريطي
# رسم بياني شريطي للمقارنة
fig = go.Figure()
fig.add_trace(go.Bar(
x=chart_data['وصف البند'],
y=chart_data['التسعير المتوازن'],
name='التسعير المتوازن',
marker_color='rgba(55, 83, 109, 0.7)'
))
fig.add_trace(go.Bar(
x=chart_data['وصف البند'],
y=chart_data['التسعير غير المتوازن'],
name='التسعير غير المتوازن',
marker_color=bar_colors
))
fig.update_layout(
title='مقارنة بين التسعير المتوازن وغير المتوازن',
xaxis_tickfont_size=14,
yaxis=dict(
title='الإجمالي (ريال)',
titlefont_size=16,
tickfont_size=14,
),
legend=dict(
x=0.01,
y=0.99,
bgcolor='rgba(255, 255, 255, 0.8)',
bordercolor='rgba(0, 0, 0, 0.1)',
borderwidth=1
),
barmode='group',
bargap=0.15,
bargroupgap=0.1,
plot_bgcolor='rgba(240, 249, 255, 0.5)',
margin=dict(t=50, b=50, l=20, r=20)
)
st.plotly_chart(fig, use_container_width=True)
with chart_tabs[1]: # رسم مقارنة
# رسم مقارنة بين التسعيرين
fig = go.Figure()
# إضافة خط للتسعير المتوازن
fig.add_trace(go.Scatter(
x=chart_data['وصف البند'],
y=chart_data['التسعير المتوازن'],
name='التسعير المتوازن',
mode='lines+markers',
line=dict(color='rgb(55, 83, 109)', width=3),
marker=dict(size=10, color='rgb(55, 83, 109)')
))
# إضافة نقاط للتسعير غير المتوازن
fig.add_trace(go.Scatter(
x=chart_data['وصف البند'],
y=chart_data['التسعير غير المتوازن'],
name='التسعير غير المتوازن',
mode='lines+markers',
line=dict(color='rgb(26, 118, 255)', width=3),
marker=dict(
size=12,
color=bar_colors,
line=dict(width=2, color='white')
)
))
# تحديثات التخطيط
fig.update_layout(
title='مقارنة مرئية بين استراتيجيات التسعير',
xaxis_tickfont_size=14,
yaxis=dict(
title='القيمة الإجمالية (ريال)',
titlefont_size=16,
tickfont_size=14,
gridcolor='rgba(200, 200, 200, 0.2)'
),
legend=dict(
x=0.01,
y=0.99,
bgcolor='rgba(255, 255, 255, 0.8)',
bordercolor='rgba(0, 0, 0, 0.1)',
borderwidth=1
),
plot_bgcolor='rgba(240, 249, 255, 0.5)',
margin=dict(t=50, b=50, l=20, r=20)
)
st.plotly_chart(fig, use_container_width=True)
with chart_tabs[2]: # مخطط نسبة التغيير
# مخطط للنسبة المئوية للتغيير
fig = go.Figure()
# إضافة أعمدة لنسبة التغيير مع ألوان مختلفة حسب القيمة
fig.add_trace(go.Bar(
x=chart_data['وصف البند'],
y=chart_data['نسبة التغيير'],
name='نسبة التغيير',
marker_color=bar_colors,
text=[f"{val:.1f}%" for val in chart_data['نسبة التغيير']],
textposition='auto'
))
# إضافة خط أفقي عند الصفر
fig.add_shape(
type="line",
x0=-0.5,
y0=0,
x1=len(chart_data['وصف البند'])-0.5,
y1=0,
line=dict(
color="black",
width=2,
dash="dash",
)
)
# تحديثات التخطيط
fig.update_layout(
title='نسبة التغيير في أسعار البنود (%)',
xaxis_tickfont_size=14,
yaxis=dict(
title='نسبة التغيير (%)',
titlefont_size=16,
tickfont_size=14,
gridcolor='rgba(200, 200, 200, 0.2)',
zeroline=True,
zerolinecolor='black',
zerolinewidth=2
),
plot_bgcolor='rgba(240, 249, 255, 0.5)',
margin=dict(t=50, b=50, l=20, r=20)
)
st.plotly_chart(fig, use_container_width=True)
# إضافة جدول مع نسب التغيير
st.markdown("#### جدول مفصل بنسب التغيير")
# إعداد بيانات الجدول
table_data = chart_data[['وصف البند', 'التسعير المتوازن', 'التسعير غير المتوازن', 'نسبة التغيير']]
# تنسيق الجدول
def highlight_change(row):
change = row['نسبة التغيير']
if change > 5:
return ['', '', '', 'background-color: rgba(31, 122, 140, 0.3); color: #1F7A8C; font-weight: bold;']
elif change > 0:
return ['', '', '', 'background-color: rgba(129, 178, 154, 0.3); color: #2A9D8F; font-weight: bold;']
elif change > -5:
return ['', '', '', 'background-color: rgba(242, 204, 143, 0.3); color: #BC6C25; font-weight: bold;']
else:
return ['', '', '', 'background-color: rgba(224, 122, 95, 0.3); color: #AE2012; font-weight: bold;']
# تطبيق التنسيق
styled_table = table_data.style.apply(highlight_change, axis=1).format({
'التسعير المتوازن': '{:,.2f} ريال',
'التسعير غير المتوازن': '{:,.2f} ريال',
'نسبة التغيير': '{:+.1f}%'
})
st.dataframe(styled_table, use_container_width=True)
# قسم لإضافة أو حذف البنود
st.markdown("### إدارة بنود التسعير")
# إضافة بند جديد
with st.expander("إضافة بند جديد"):
col1, col2, col3, col4 = st.columns([3, 1, 1, 1])
with col1:
new_item_desc = st.text_input("وصف البند", placeholder="أدخل وصف البند الجديد")
with col2:
new_item_unit = st.selectbox(
"الوحدة",
options=["م3", "م2", "متر طولي", "طن", "قطعة", "كجم", "لتر"],
index=0,
key="construction_item_unit"
)
with col3:
new_item_qty = st.number_input("الكمية", min_value=0.1, value=1.0, format="%.2f")
with col4:
new_item_price = st.number_input("سعر الوحدة", min_value=0.1, value=100.0, format="%.2f")
if st.button("إضافة البند"):
if new_item_desc:
# إنشاء رقم البند الجديد
new_id = f"UB{len(items)+1}"
# إنشاء صف جديد
new_row = pd.DataFrame({
'رقم البند': [new_id],
'وصف البند': [new_item_desc],
'الوحدة': [new_item_unit],
'الكمية': [float(new_item_qty)],
'سعر الوحدة': [float(new_item_price)],
'الإجمالي': [float(new_item_qty * new_item_price)],
'إستراتيجية التسعير': ['متوازن']
})
# إضافة الصف إلى DataFrame
items = pd.concat([items, new_row], ignore_index=True)
# تحديث الإجمالي
items['الإجمالي'] = items['الكمية'] * items['سعر الوحدة']
st.success(f"تم إضافة البند \"{new_item_desc}\" بنجاح!")
st.rerun()
else:
st.warning("يرجى إدخال وصف للبند")
# حذف بند
with st.expander("حذف بند"):
if len(items) > 0:
# قائمة بالبنود الحالية
item_options = [f"{row['رقم البند']} - {row['وصف البند']}" for idx, row in items.iterrows()]
selected_item_to_delete = st.selectbox(
"اختر البند المراد حذفه",
options=item_options,
key="item_to_delete_selector"
)
# استخراج رقم البند
item_id_to_delete = selected_item_to_delete.split(" - ")[0]
if st.button("حذف البند", key="delete_item_button_2"):
# البحث عن البند وحذفه
items = items[items['رقم البند'] != item_id_to_delete]
st.success(f"تم حذف البند {item_id_to_delete} بنجاح!")
st.rerun()
else:
st.info("لا توجد بنود لحذفها")
# أزرار الحفظ والتصدير مع تصميم محسن
st.divider()
st.subheader("حفظ وتصدير البيانات")
st.markdown("""
<style>
.action-card {
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
border-left: 5px solid #1F7A8C;
transition: all 0.3s ease;
}
.action-card:hover {
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.action-icon {
color: #1F7A8C;
font-size: 24px;
margin-bottom: 10px;
}
.action-title {
color: #333;
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.action-desc {
color: #666;
font-size: 14px;
margin-bottom: 15px;
}
</style>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
# بطاقة حفظ التسعير
st.markdown("""
<div class="action-card">
<div class="action-icon">💾</div>
<div class="action-title">حفظ التسعير غير المتوازن</div>
<div class="action-desc">قم بحفظ التسعير الحالي في المشروع لاستخدامه لاحقاً في التقارير وفي إجمالي التسعير.</div>
</div>
""", unsafe_allow_html=True)
# زر حفظ التسعير غير المتوازن
if st.button("حفظ التسعير غير المتوازن", type="primary", use_container_width=True, key="save_unbalanced_pricing_button"):
st.session_state.current_pricing['items'] = items.copy()
st.session_state.current_pricing['method'] = "التسعير غير المتزن"
st.success("تم حفظ التسعير غير المتوازن بنجاح!")
st.balloons() # إضافة تأثير احتفالي عند الحفظ
with col2:
# بطاقة تصدير التسعير
st.markdown("""
<div class="action-card">
<div class="action-icon">📊</div>
<div class="action-title">تصدير البيانات</div>
<div class="action-desc">قم بتصدير جدول التسعير الحالي بصيغة CSV لاستخدامه في برامج أخرى مثل Excel.</div>
</div>
""", unsafe_allow_html=True)
# زر تصدير التسعير
export_button = st.button("تجهيز ملف للتصدير", use_container_width=True, key="prepare_export_file_button")
if export_button:
# تحويل البيانات إلى CSV
csv = items.to_csv(index=False)
st.success("تم تجهيز ملف التصدير بنجاح! يمكنك تنزيله الآن.")
# تقديم البيانات للتنزيل
st.download_button(
label="تنزيل ملف CSV",
data=csv,
file_name="unbalanced_pricing.csv",
mime="text/csv",
use_container_width=True
)
def _render_construction_calculator_tab(self):
"""عرض تبويب حاسبة تكاليف البناء المتكاملة"""
# استدعاء حاسبة تكاليف البناء المتكاملة الجديدة
from .construction_calculator import render_construction_calculator
st.markdown("### حاسبة تكاليف البناء المتكاملة")
# شرح حاسبة تكاليف البناء
with st.expander("دليل استخدام حاسبة تكاليف البناء", expanded=False):
st.markdown("""
**حاسبة تكاليف البناء المتكاملة** هي أداة تساعد في حساب تكاليف البناء بشكل تفصيلي، مع مراعاة جميع عناصر التكلفة:
### مكونات التكلفة:
- المواد الخام
- العمالة
- المعدات
- المصاريف الإدارية
- هامش الربح
### كيفية الاستخدام:
1. اختر إما حساب تكلفة بند واحد أو حساب تكلفة مشروع.
2. أدخل بيانات المواد والعمالة والمعدات.
3. حدد نسب المصاريف الإدارية وهامش الربح.
4. اضبط عوامل التعديل حسب ظروف المشروع.
5. احصل على تحليل تفصيلي للتكاليف والسعر النهائي.
### مميزات الحاسبة:
- قاعدة بيانات مدمجة للأسعار المرجعية للمواد والعمالة.
- تحليل نسب مساهمة كل عنصر في التكلفة.
- إمكانية تعديل عوامل التكلفة حسب الموقع والظروف.
- تصدير النتائج بتنسيقات متعددة.
- الربط مع وحدة التسعير لنقل النتائج مباشرة إلى جدول البنود.
- قاعدة بيانات للبنود النموذجية في أعمال المقاولات.
""")
render_construction_calculator()
# شرح حاسبة تكاليف البناء
with st.expander("دليل استخدام حاسبة تكاليف البناء", expanded=False):
st.markdown("""
**حاسبة تكاليف البناء المتكاملة** هي أداة تساعد في حساب تكاليف البناء بشكل تفصيلي، مع مراعاة جميع عناصر التكلفة:
### مكونات التكلفة:
1. **المواد الخام**: جميع المواد المستخدمة في البناء مثل الخرسانة، الحديد، الطوب، الأسمنت، وغيرها.
2. **العمالة**: تكاليف جميع العمالة المطلوبة بمختلف تخصصاتها.
3. **المعدات**: تكاليف استخدام أو استئجار المعدات اللازمة للمشروع.
4. **المصاريف الإدارية**: النفقات العامة والإدارية للمشروع (نسبة من التكلفة المباشرة).
5. **هامش الربح**: نسبة الربح المضافة على التكلفة.
### طريقة الاستخدام:
1. اختر إما حساب تكلفة بند واحد أو حساب تكلفة مشروع كامل.
2. أدخل بيانات المواد والعمالة والمعدات المستخدمة.
3. حدد نسب المصاريف الإدارية وهامش الربح.
4. اضبط عوامل التعديل حسب ظروف المشروع.
5. احصل على تحليل تفصيلي للتكاليف والسعر النهائي.
### ميزات الحاسبة:
- قاعدة بيانات مدمجة للأسعار المرجعية للمواد والعمالة والمعدات.
- تحليل نسب مساهمة كل عنصر في التكلفة الإجمالية.
- إمكانية تعديل عوامل التكلفة حسب الموقع والظروف السوقية.
- تصدير النتائج بتنسيقات مختلفة.
- الربط مع وحدة التسعير لنقل النتائج مباشرة إلى جدول التسعير.
- قاعدة بيانات للبنود النموذجية في أعمال المقاولات (مناهل، مواسير، إسفلت، إلخ).
""")
# تبويبات حاسبة التكاليف
calc_tabs = st.tabs(["حساب تكلفة بند", "حساب تكلفة مشروع", "قوائم الأسعار المرجعية", "كتالوج أعمال المقاولات"])
with calc_tabs[0]: # حساب تكلفة بند
st.markdown("#### حساب تكلفة بند بناء")
# تهيئة بيانات البند الافتراضية إذا لم تكن موجودة
if 'construction_item' not in st.session_state:
st.session_state.construction_item = {
'وصف_البند': "توريد وصب خرسانة مسلحة للأساسات",
'الكمية': 25.0,
'الوحدة': "م3",
'المواد': [
{'الاسم': 'خرسانة جاهزة', 'الكمية': 25.0, 'الوحدة': 'م3', 'سعر_الوحدة': 750.0},
{'الاسم': 'حديد تسليح', 'الكمية': 3.5, 'الوحدة': 'طن', 'سعر_الوحدة': 5500.0}
],
'العمالة': [
{'النوع': 'عامل خرسانة', 'العدد': 6, 'المدة': 1.0, 'سعر_اليوم': 150.0},
{'النوع': 'حداد مسلح', 'العدد': 4, 'المدة': 3.0, 'سعر_اليوم': 250.0},
{'النوع': 'نجار مسلح', 'العدد': 4, 'المدة': 3.0, 'سعر_اليوم': 250.0}
],
'المعدات': [
{'النوع': 'مضخة خرسانة', 'العدد': 1.0, 'المدة': 0.5, 'سعر_اليوم': 5000.0},
{'النوع': 'هزاز خرسانة', 'العدد': 2.0, 'المدة': 1.0, 'سعر_اليوم': 150.0}
],
'المصاريف_الإدارية': 0.05, # 5%
'هامش_الربح': 0.10, # 10%
'عوامل_التعديل': {
'location_factor': 1.0,
'time_factor': 1.0,
'risk_factor': 1.0,
'market_factor': 1.0
}
}
# نموذج إدخال بيانات البند
col1, col2 = st.columns(2)
with col1:
st.session_state.construction_item['وصف_البند'] = st.text_area(
"وصف البند",
value=st.session_state.construction_item['وصف_البند'],
key="construction_item_description"
)
with col2:
st.session_state.construction_item['الكمية'] = st.number_input(
"الكمية",
value=st.session_state.construction_item['الكمية'],
min_value=0.1,
format="%.2f"
)
unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"]
st.session_state.construction_item['الوحدة'] = st.selectbox(
"الوحدة",
options=unit_options,
index=unit_options.index(st.session_state.construction_item['الوحدة']) if st.session_state.construction_item['الوحدة'] in unit_options else 0,
key="construction_item_unit_2"
)
# إدخال تفاصيل المواد
st.markdown("#### تفاصيل المواد")
material_controls = []
for i, material in enumerate(st.session_state.construction_item['المواد']):
col1, col2, col3, col4, col5 = st.columns([3, 2, 1, 2, 1])
with col1:
material_name = st.text_input(
"اسم المادة",
value=material['الاسم'],
key=f"material_name_{i}"
)
with col2:
material_qty = st.number_input(
"الكمية",
value=material['الكمية'],
min_value=0.0,
format="%.2f",
key=f"material_qty_{i}"
)
with col3:
material_unit = st.selectbox(
"الوحدة",
options=unit_options,
index=unit_options.index(material['الوحدة']) if material['الوحدة'] in unit_options else 0,
key=f"material_unit_{i}"
)
with col4:
material_price = st.number_input(
"سعر الوحدة",
value=material['سعر_الوحدة'],
min_value=0.0,
format="%.2f",
key=f"material_price_{i}"
)
with col5:
delete_button = st.button("حذف", key=f"delete_material_{i}")
material_controls.append({
'الاسم': material_name,
'الكمية': material_qty,
'الوحدة': material_unit,
'سعر_الوحدة': material_price,
'delete': delete_button
})
# إضافة مادة جديدة
if st.button("إضافة مادة جديدة"):
st.session_state.construction_item['المواد'].append({
'الاسم': '',
'الكمية': 0.0,
'الوحدة': 'م3',
'سعر_الوحدة': 0.0
})
st.rerun()
# تحديث أو حذف المواد
new_materials = []
for i, control in enumerate(material_controls):
if not control['delete']:
new_materials.append({
'الاسم': control['الاسم'],
'الكمية': control['الكمية'],
'الوحدة': control['الوحدة'],
'سعر_الوحدة': control['سعر_الوحدة']
})
if len(new_materials) != len(st.session_state.construction_item['المواد']):
st.session_state.construction_item['المواد'] = new_materials
st.rerun()
else:
for i, material in enumerate(new_materials):
st.session_state.construction_item['المواد'][i] = material
# إدخال تفاصيل العمالة
st.markdown("#### تفاصيل العمالة")
labor_controls = []
for i, labor in enumerate(st.session_state.construction_item['العمالة']):
col1, col2, col3, col4, col5 = st.columns([3, 1, 1, 2, 1])
with col1:
labor_type = st.text_input(
"نوع العامل",
value=labor['النوع'],
key=f"labor_type_{i}"
)
with col2:
labor_count = st.number_input(
"العدد",
value=float(labor['العدد']),
min_value=1.0,
step=1.0,
key=f"labor_count_{i}"
)
with col3:
labor_days = st.number_input(
"المدة (أيام)",
value=labor['المدة'],
min_value=0.1,
format="%.1f",
key=f"labor_days_{i}"
)
with col4:
labor_daily_rate = st.number_input(
"سعر اليوم",
value=labor['سعر_اليوم'],
min_value=0.0,
format="%.2f",
key=f"labor_daily_rate_{i}"
)
with col5:
delete_button = st.button("حذف", key=f"delete_labor_{i}")
labor_controls.append({
'النوع': labor_type,
'العدد': labor_count,
'المدة': labor_days,
'سعر_اليوم': labor_daily_rate,
'delete': delete_button
})
# إضافة عامل جديد
if st.button("إضافة عامل جديد"):
st.session_state.construction_item['العمالة'].append({
'النوع': '',
'العدد': 1.0,
'المدة': 1.0,
'سعر_اليوم': 0.0
})
st.rerun()
# تحديث أو حذف العمالة
new_labor = []
for i, control in enumerate(labor_controls):
if not control['delete']:
new_labor.append({
'النوع': control['النوع'],
'العدد': control['العدد'],
'المدة': control['المدة'],
'سعر_اليوم': control['سعر_اليوم']
})
if len(new_labor) != len(st.session_state.construction_item['العمالة']):
st.session_state.construction_item['العمالة'] = new_labor
st.rerun()
else:
for i, labor in enumerate(new_labor):
st.session_state.construction_item['العمالة'][i] = labor
# إدخال تفاصيل المعدات
st.markdown("#### تفاصيل المعدات")
equipment_controls = []
for i, equipment in enumerate(st.session_state.construction_item['المعدات']):
col1, col2, col3, col4, col5 = st.columns([3, 1, 1, 2, 1])
with col1:
equip_type = st.text_input(
"نوع المعدة",
value=equipment['النوع'],
key=f"equip_type_{i}"
)
with col2:
equip_count = st.number_input(
"العدد",
value=float(equipment['العدد']),
min_value=1.0,
step=1.0,
key=f"equip_count_{i}"
)
with col3:
equip_days = st.number_input(
"المدة (أيام)",
value=equipment['المدة'],
min_value=0.1,
format="%.1f",
key=f"equip_days_{i}"
)
with col4:
equip_daily_rate = st.number_input(
"سعر اليوم",
value=equipment['سعر_اليوم'],
min_value=0.0,
format="%.2f",
key=f"equip_daily_rate_{i}"
)
with col5:
delete_button = st.button("حذف", key=f"delete_equipment_{i}")
equipment_controls.append({
'النوع': equip_type,
'العدد': equip_count,
'المدة': equip_days,
'سعر_اليوم': equip_daily_rate,
'delete': delete_button
})
# إضافة معدة جديدة
if st.button("إضافة معدة جديدة"):
st.session_state.construction_item['المعدات'].append({
'النوع': '',
'العدد': 1.0,
'المدة': 1.0,
'سعر_اليوم': 0.0
})
st.rerun()
# تحديث أو حذف المعدات
new_equipment = []
for i, control in enumerate(equipment_controls):
if not control['delete']:
new_equipment.append({
'النوع': control['النوع'],
'العدد': control['العدد'],
'المدة': control['المدة'],
'سعر_اليوم': control['سعر_اليوم']
})
if len(new_equipment) != len(st.session_state.construction_item['المعدات']):
st.session_state.construction_item['المعدات'] = new_equipment
st.rerun()
else:
for i, equipment in enumerate(new_equipment):
st.session_state.construction_item['المعدات'][i] = equipment
# النسب والعوامل
st.markdown("#### المصاريف الإدارية وهامش الربح")
col1, col2 = st.columns(2)
with col1:
st.session_state.construction_item['المصاريف_الإدارية'] = st.slider(
"نسبة المصاريف الإدارية (%)",
min_value=0.0,
max_value=20.0,
value=st.session_state.construction_item['المصاريف_الإدارية'] * 100,
step=0.5
) / 100
with col2:
st.session_state.construction_item['هامش_الربح'] = st.slider(
"نسبة هامش الربح (%)",
min_value=0.0,
max_value=30.0,
value=st.session_state.construction_item['هامش_الربح'] * 100,
step=0.5
) / 100
# عوامل التعديل
st.markdown("#### عوامل تعديل التكلفة")
col1, col2 = st.columns(2)
with col1:
st.session_state.construction_item['عوامل_التعديل']['location_factor'] = st.slider(
"معامل الموقع",
min_value=0.5,
max_value=2.0,
value=st.session_state.construction_item['عوامل_التعديل']['location_factor'],
step=0.05,
help="يؤثر في التكلفة حسب صعوبة أو سهولة الوصول للموقع وتوفر الخدمات"
)
st.session_state.construction_item['عوامل_التعديل']['time_factor'] = st.slider(
"معامل الوقت",
min_value=0.8,
max_value=1.5,
value=st.session_state.construction_item['عوامل_التعديل']['time_factor'],
step=0.05,
help="يؤثر في التكلفة حسب الجدول الزمني للمشروع وضرورة الإسراع في التنفيذ"
)
with col2:
st.session_state.construction_item['عوامل_التعديل']['risk_factor'] = st.slider(
"معامل المخاطر",
min_value=1.0,
max_value=1.5,
value=st.session_state.construction_item['عوامل_التعديل']['risk_factor'],
step=0.05,
help="يؤثر في التكلفة حسب المخاطر المتوقعة في المشروع"
)
st.session_state.construction_item['عوامل_التعديل']['market_factor'] = st.slider(
"معامل السوق",
min_value=0.8,
max_value=1.3,
value=st.session_state.construction_item['عوامل_التعديل']['market_factor'],
step=0.05,
help="يؤثر في التكلفة حسب حالة السوق الحالية وتغيرات الأسعار"
)
# صف أزرار العمليات
col1, col2 = st.columns(2)
with col1:
# زر حساب التكلفة
if st.button("حساب تكلفة البند", type="primary"):
with st.spinner("جاري حساب التكلفة..."):
# حساب التكلفة باستخدام الحاسبة
item_cost = self.construction_calculator.calculate_item_cost(st.session_state.construction_item)
st.session_state.item_cost_result = item_cost
with col2:
# زر استيراد بند من كتالوج أعمال المقاولات
if st.button("استيراد بند من الكتالوج"):
# تهيئة session state للاختيار من الكتالوج
if 'show_catalog_selection' not in st.session_state:
st.session_state.show_catalog_selection = True
else:
st.session_state.show_catalog_selection = True
st.rerun()
# عرض واجهة اختيار البند من الكتالوج
if 'show_catalog_selection' in st.session_state and st.session_state.show_catalog_selection:
st.markdown("#### اختيار بند من كتالوج أعمال المقاولات")
# الحصول على فئات البنود
categories = self.construction_templates.get_all_templates()['categories']
category_options = [f"{cat_data['name']}" for cat_id, cat_data in categories.items()]
category_ids = list(categories.keys())
selected_category_index = st.selectbox(
"اختر فئة البند",
options=range(len(category_options)),
format_func=lambda i: category_options[i],
key="construction_category_selector"
)
selected_category_id = category_ids[selected_category_index]
# الحصول على البنود في الفئة المحددة
templates = self.construction_templates.get_templates_by_category(selected_category_id)
if templates:
template_options = [f"{template['name']}" for template in templates]
selected_template_index = st.selectbox(
"اختر البند",
options=range(len(template_options)),
format_func=lambda i: template_options[i],
key="construction_template_selector"
)
selected_template = templates[selected_template_index]
# عرض تفاصيل البند المحدد
st.markdown(f"**وصف البند**: {selected_template['description']}")
st.markdown(f"**الوحدة**: {selected_template['unit']}")
if st.button("استخدام هذا البند", type="primary"):
# تحويل البند إلى صيغة الحاسبة
template_id = selected_template['id']
construction_item = self.construction_templates.convert_template_to_item(template_id)
# تحديث بيانات البند في session state
st.session_state.construction_item = construction_item
st.session_state.show_catalog_selection = False
st.rerun()
else:
st.info("لا توجد بنود في هذه الفئة")
if st.button("إلغاء"):
st.session_state.show_catalog_selection = False
st.rerun()
# عرض نتائج الحساب
if 'item_cost_result' in st.session_state:
st.markdown("### نتائج حساب تكلفة البند")
result = st.session_state.item_cost_result
# ملخص البند
st.markdown(f"**البند:** {result['وصف_البند']}")
st.markdown(f"**الكمية:** {result['الكمية']} {result['الوحدة']}")
# المكونات الرئيسية للتكلفة
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric(
"تكلفة المواد",
f"{result['تكاليف_مباشرة']['المواد']['الإجمالي']:,.2f} ريال"
)
with col2:
st.metric(
"تكلفة العمالة",
f"{result['تكاليف_مباشرة']['العمالة']['الإجمالي']:,.2f} ريال"
)
with col3:
st.metric(
"تكلفة المعدات",
f"{result['تكاليف_مباشرة']['المعدات']['الإجمالي']:,.2f} ريال"
)
with col4:
st.metric(
"التكلفة المباشرة",
f"{result['تكاليف_مباشرة']['إجمالي_تكاليف_مباشرة']:,.2f} ريال"
)
# تفاصيل المصاريف والربح والسعر النهائي
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
f"المصاريف الإدارية ({result['مصاريف_إدارية']['نسبة']}%)",
f"{result['مصاريف_إدارية']['قيمة']:,.2f} ريال"
)
with col2:
st.metric(
f"هامش الربح ({result['هامش_ربح']['نسبة']}%)",
f"{result['هامش_ربح']['قيمة']:,.2f} ريال"
)
with col3:
st.metric(
"التكلفة الإجمالية",
f"{result['التكلفة_الإجمالية']:,.2f} ريال"
)
# التكلفة بعد تطبيق عوامل التعديل
adjustment_factor = result['عوامل_التعديل']['المعامل_الإجمالي']
st.metric(
f"السعر النهائي المعدل (معامل التعديل: {adjustment_factor:.2f})",
f"{result['السعر_المعدل']['إجمالي']:,.2f} ريال",
delta=f"{(adjustment_factor - 1) * 100:.1f}%"
)
# سعر الوحدة وزر نقل السعر إلى جدول التسعير
col1, col2 = st.columns([2, 1])
with col1:
st.metric(
f"سعر الوحدة ({result['الوحدة']})",
f"{result['السعر_المعدل']['سعر_الوحدة']:,.2f} ريال"
)
with col2:
if st.button("نقل السعر إلى جدول التسعير", key="transfer_to_pricing"):
if 'manual_items' not in st.session_state:
st.session_state.manual_items = pd.DataFrame(columns=[
'رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي'
])
# إنشاء رقم البند الجديد
new_id = f"B{len(st.session_state.manual_items)+1}"
# إنشاء صف جديد
new_row = pd.DataFrame({
'رقم البند': [new_id],
'وصف البند': [result['وصف_البند']],
'الوحدة': [result['الوحدة']],
'الكمية': [float(result['الكمية'])],
'سعر الوحدة': [float(result['السعر_المعدل']['سعر_الوحدة'])],
'الإجمالي': [float(result['السعر_المعدل']['إجمالي'])]
})
# إضافة الصف إلى DataFrame
st.session_state.manual_items = pd.concat([st.session_state.manual_items, new_row], ignore_index=True)
# إنشاء حالة التسعير الحالي إذا لم تكن موجودة
if 'current_pricing' not in st.session_state:
st.session_state.current_pricing = {
'name': 'تسعير جديد',
'method': 'تسعير مستورد من حاسبة تكاليف البناء',
'items': st.session_state.manual_items
}
else:
# تحديث البنود في التسعير الحالي
st.session_state.current_pricing['items'] = st.session_state.manual_items
st.success(f"تم نقل البند \"{result['وصف_البند']}\" إلى جدول التسعير بنجاح!")
# تفاصيل التكلفة - المواد
st.markdown("#### تفاصيل تكلفة المواد")
if len(result['تكاليف_مباشرة']['المواد']['التفاصيل']) > 0:
materials_df = pd.DataFrame(result['تكاليف_مباشرة']['المواد']['التفاصيل'])
st.dataframe(materials_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد مواد مضافة")
# تفاصيل التكلفة - العمالة
st.markdown("#### تفاصيل تكلفة العمالة")
if len(result['تكاليف_مباشرة']['العمالة']['التفاصيل']) > 0:
labor_df = pd.DataFrame(result['تكاليف_مباشرة']['العمالة']['التفاصيل'])
st.dataframe(labor_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد عمالة مضافة")
# تفاصيل التكلفة - المعدات
st.markdown("#### تفاصيل تكلفة المعدات")
if len(result['تكاليف_مباشرة']['المعدات']['التفاصيل']) > 0:
equipment_df = pd.DataFrame(result['تكاليف_مباشرة']['المعدات']['التفاصيل'])
st.dataframe(equipment_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد معدات مضافة")
# رسم بياني لتوزيع التكلفة
st.markdown("#### توزيع مكونات التكلفة")
cost_components = {
'المواد': result['تكاليف_مباشرة']['المواد']['الإجمالي'],
'العمالة': result['تكاليف_مباشرة']['العمالة']['الإجمالي'],
'المعدات': result['تكاليف_مباشرة']['المعدات']['الإجمالي'],
'المصاريف الإدارية': result['مصاريف_إدارية']['قيمة'],
'هامش الربح': result['هامش_ربح']['قيمة']
}
colors = ['#2E86C1', '#28B463', '#EB984E', '#8E44AD', '#C0392B']
fig = px.pie(
values=list(cost_components.values()),
names=list(cost_components.keys()),
title='توزيع مكونات التكلفة',
color_discrete_sequence=colors
)
st.plotly_chart(fig, use_container_width=True)
with calc_tabs[1]: # حساب تكلفة مشروع
st.markdown("#### حساب تكلفة مشروع كامل")
# تهيئة بيانات المشروع الافتراضية إذا لم تكن موجودة
if 'construction_project' not in st.session_state:
st.session_state.construction_project = self.construction_calculator.generate_sample_project_data()
# نموذج إدخال بيانات المشروع
col1, col2 = st.columns(2)
with col1:
st.session_state.construction_project['اسم_المشروع'] = st.text_input(
"اسم المشروع",
value=st.session_state.construction_project['اسم_المشروع']
)
with col2:
st.session_state.construction_project['وصف_المشروع'] = st.text_area(
"وصف المشروع",
value=st.session_state.construction_project['وصف_المشروع'],
height=100,
key="construction_project_description"
)
# النسب والعوامل الإجمالية للمشروع
st.markdown("#### النسب والعوامل الإجمالية للمشروع")
col1, col2 = st.columns(2)
with col1:
st.session_state.construction_project['المصاريف_الإدارية'] = st.slider(
"نسبة المصاريف الإدارية الإجمالية (%)",
min_value=0.0,
max_value=20.0,
value=st.session_state.construction_project['المصاريف_الإدارية'] * 100,
step=0.5,
key="project_admin_expenses"
) / 100
with col2:
st.session_state.construction_project['هامش_الربح'] = st.slider(
"نسبة هامش الربح الإجمالي (%)",
min_value=0.0,
max_value=30.0,
value=st.session_state.construction_project['هامش_الربح'] * 100,
step=0.5,
key="project_profit_margin"
) / 100
# عوامل التعديل للمشروع
st.markdown("#### عوامل تعديل التكلفة للمشروع")
col1, col2 = st.columns(2)
with col1:
st.session_state.construction_project['عوامل_التعديل']['location_factor'] = st.slider(
"معامل الموقع",
min_value=0.5,
max_value=2.0,
value=st.session_state.construction_project['عوامل_التعديل']['location_factor'],
step=0.05,
help="يؤثر في التكلفة حسب صعوبة أو سهولة الوصول للموقع وتوفر الخدمات",
key="project_location_factor"
)
st.session_state.construction_project['عوامل_التعديل']['time_factor'] = st.slider(
"معامل الوقت",
min_value=0.8,
max_value=1.5,
value=st.session_state.construction_project['عوامل_التعديل']['time_factor'],
step=0.05,
help="يؤثر في التكلفة حسب الجدول الزمني للمشروع وضرورة الإسراع في التنفيذ",
key="project_time_factor"
)
with col2:
st.session_state.construction_project['عوامل_التعديل']['risk_factor'] = st.slider(
"معامل المخاطر",
min_value=1.0,
max_value=1.5,
value=st.session_state.construction_project['عوامل_التعديل']['risk_factor'],
step=0.05,
help="يؤثر في التكلفة حسب المخاطر المتوقعة في المشروع",
key="project_risk_factor"
)
st.session_state.construction_project['عوامل_التعديل']['market_factor'] = st.slider(
"معامل السوق",
min_value=0.8,
max_value=1.3,
value=st.session_state.construction_project['عوامل_التعديل']['market_factor'],
step=0.05,
help="يؤثر في التكلفة حسب حالة السوق الحالية وتغيرات الأسعار",
key="project_market_factor"
)
# زر حساب تكلفة المشروع
if st.button("حساب تكلفة المشروع", type="primary"):
with st.spinner("جاري حساب تكلفة المشروع..."):
# حساب التكلفة باستخدام الحاسبة
project_cost = self.construction_calculator.calculate_project_cost(st.session_state.construction_project)
st.session_state.project_cost_result = project_cost
# عرض نتائج حساب المشروع
if 'project_cost_result' in st.session_state:
st.markdown("### نتائج حساب تكلفة المشروع")
result = st.session_state.project_cost_result
# ملخص المشروع
st.markdown(f"**المشروع:** {result['اسم_المشروع']}")
st.markdown(f"**الوصف:** {result['وصف_المشروع']}")
st.markdown(f"**عدد البنود:** {result['عدد_البنود']} بند")
# المكونات الرئيسية للتكلفة
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"إجمالي تكلفة المواد",
f"{result['تكاليف_مباشرة']['المواد']['الإجمالي']:,.2f} ريال",
delta=f"{result['تكاليف_مباشرة']['المواد']['النسبة_المئوية']:.1f}% من التكلفة المباشرة"
)
with col2:
st.metric(
"إجمالي تكلفة العمالة",
f"{result['تكاليف_مباشرة']['العمالة']['الإجمالي']:,.2f} ريال",
delta=f"{result['تكاليف_مباشرة']['العمالة']['النسبة_المئوية']:.1f}% من التكلفة المباشرة"
)
with col3:
st.metric(
"إجمالي تكلفة المعدات",
f"{result['تكاليف_مباشرة']['المعدات']['الإجمالي']:,.2f} ريال",
delta=f"{result['تكاليف_مباشرة']['المعدات']['النسبة_المئوية']:.1f}% من التكلفة المباشرة"
)
# إجمالي التكاليف المباشرة
st.metric(
"إجمالي التكاليف المباشرة",
f"{result['تكاليف_مباشرة']['إجمالي_تكاليف_مباشرة']:,.2f} ريال"
)
# تفاصيل المصاريف والربح والسعر النهائي
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
f"المصاريف الإدارية ({result['مصاريف_إدارية']['نسبة']}%)",
f"{result['مصاريف_إدارية']['قيمة']:,.2f} ريال"
)
with col2:
st.metric(
f"هامش الربح ({result['هامش_ربح']['نسبة']}%)",
f"{result['هامش_ربح']['قيمة']:,.2f} ريال"
)
with col3:
st.metric(
"التكلفة الإجمالية",
f"{result['التكلفة_الإجمالية']:,.2f} ريال"
)
# التكلفة بعد تطبيق عوامل التعديل
adjustment_factor = result['عوامل_التعديل']['المعامل_الإجمالي']
st.metric(
f"السعر النهائي المعدل (معامل التعديل: {adjustment_factor:.2f})",
f"{result['التكلفة_النهائية_المعدلة']:,.2f} ريال",
delta=f"{(adjustment_factor - 1) * 100:.1f}%"
)
# رسم بياني لتوزيع التكلفة
st.markdown("#### توزيع مكونات التكلفة")
cost_components = {
'المواد': result['تكاليف_مباشرة']['المواد']['الإجمالي'],
'العمالة': result['تكاليف_مباشرة']['العمالة']['الإجمالي'],
'المعدات': result['تكاليف_مباشرة']['المعدات']['الإجمالي'],
'المصاريف الإدارية': result['مصاريف_إدارية']['قيمة'],
'هامش الربح': result['هامش_ربح']['قيمة']
}
colors = ['#2E86C1', '#28B463', '#EB984E', '#8E44AD', '#C0392B']
fig = px.pie(
values=list(cost_components.values()),
names=list(cost_components.keys()),
title='توزيع مكونات التكلفة',
color_discrete_sequence=colors
)
st.plotly_chart(fig, use_container_width=True)
# جدول تفاصيل البنود
st.markdown("#### تفاصيل بنود المشروع")
items_data = []
for i, item in enumerate(result['تفاصيل_البنود']):
items_data.append({
'رقم': i + 1,
'الوصف': item['وصف_البند'],
'الكمية': item['الكمية'],
'الوحدة': item['الوحدة'],
'سعر الوحدة': item['سعر_الوحدة'],
'الإجمالي': item['التكلفة_الإجمالية'],
'بعد التعديل': item['السعر_المعدل']['إجمالي']
})
if len(items_data) > 0:
items_df = pd.DataFrame(items_data)
# تنسيق الجدول لعرض الأرقام بشكل أفضل
def highlight_row(row):
"""تنسيق الجدول مع تمييز الصفوف بالتناوب"""
color = '#F0F8FF' if row.name % 2 == 0 else 'white'
return ['background-color: %s' % color] * len(row)
styled_df = items_df.style.apply(highlight_row, axis=1)
styled_df = styled_df.format({
'الكمية': '{:,.2f}',
'سعر الوحدة': '{:,.2f}',
'الإجمالي': '{:,.2f}',
'بعد التعديل': '{:,.2f}'
})
st.dataframe(styled_df, use_container_width=True)
else:
st.info("لا توجد بنود في المشروع")
with calc_tabs[2]: # قوائم الأسعار المرجعية
st.markdown("#### قوائم الأسعار المرجعية")
ref_tabs = st.tabs(["قائمة المواد", "قائمة العمالة", "قائمة المعدات"])
with ref_tabs[0]: # قائمة المواد
st.markdown("#### قائمة أسعار المواد المرجعية")
# الحصول على قائمة المواد
materials = self.construction_calculator.get_all_rates(item_type='مادة')
if materials and 'المواد' in materials:
# تحويل قاموس المواد إلى DataFrame
materials_list = []
for name, data in materials['المواد'].items():
materials_list.append({
'اسم المادة': name,
'الوحدة': data.get('وحدة', ''),
'سعر الوحدة': data.get('سعر_الوحدة', 0.0),
'الوصف': data.get('وصف', ''),
'الفئة': data.get('فئة', '')
})
if materials_list:
materials_df = pd.DataFrame(materials_list)
# تصفية حسب الفئة
categories = ['الكل'] + sorted(materials_df['الفئة'].unique().tolist())
selected_category = st.selectbox("تصفية حسب الفئة", categories, key="materials_cat_filter_section1")
if selected_category != 'الكل':
filtered_df = materials_df[materials_df['الفئة'] == selected_category]
else:
filtered_df = materials_df
# تنسيق الجدول
def highlight_materials_row(row):
"""تنسيق الجدول مع تمييز الصفوف بالتناوب"""
color = '#F0F8FF' if row.name % 2 == 0 else 'white'
return ['background-color: %s' % color] * len(row)
styled_df = filtered_df.style.apply(highlight_materials_row, axis=1)
styled_df = styled_df.format({
'سعر الوحدة': '{:,.2f}'
})
st.dataframe(styled_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد مواد في قاعدة البيانات")
else:
st.info("لا توجد قائمة مواد متاحة")
with ref_tabs[1]: # قائمة العمالة
st.markdown("#### قائمة أسعار العمالة المرجعية")
# الحصول على قائمة العمالة
labor = self.construction_calculator.get_all_rates(item_type='عمالة')
if labor and 'العمالة' in labor:
# تحويل قاموس العمالة إلى DataFrame
labor_list = []
for name, data in labor['العمالة'].items():
labor_list.append({
'نوع العامل': name,
'وحدة الأجر': data.get('وحدة', ''),
'سعر الوحدة': data.get('سعر_الوحدة', 0.0),
'الوصف': data.get('وصف', ''),
'الفئة': data.get('فئة', '')
})
if labor_list:
labor_df = pd.DataFrame(labor_list)
# تصفية حسب الفئة
categories = ['الكل'] + sorted(labor_df['الفئة'].unique().tolist())
selected_category = st.selectbox("تصفية حسب الفئة", categories, key="labor_cat_filter_section1")
if selected_category != 'الكل':
filtered_df = labor_df[labor_df['الفئة'] == selected_category]
else:
filtered_df = labor_df
# تنسيق الجدول
def highlight_labor_row(row):
"""تنسيق الجدول مع تمييز الصفوف بالتناوب"""
color = '#F0F8FF' if row.name % 2 == 0 else 'white'
return ['background-color: %s' % color] * len(row)
styled_df = filtered_df.style.apply(highlight_labor_row, axis=1)
styled_df = styled_df.format({
'سعر الوحدة': '{:,.2f}'
})
st.dataframe(styled_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد عمالة في قاعدة البيانات")
else:
st.info("لا توجد قائمة عمالة متاحة")
with ref_tabs[2]: # قائمة المعدات
st.markdown("#### قائمة أسعار المعدات المرجعية")
# الحصول على قائمة المعدات
equipment = self.construction_calculator.get_all_rates(item_type='معدة')
if equipment and 'المعدات' in equipment:
# تحويل قاموس المعدات إلى DataFrame
equipment_list = []
for name, data in equipment['المعدات'].items():
equipment_list.append({
'نوع المعدة': name,
'وحدة الإيجار': data.get('وحدة', ''),
'سعر الوحدة': data.get('سعر_الوحدة', 0.0),
'الوصف': data.get('وصف', ''),
'الفئة': data.get('فئة', '')
})
if equipment_list:
equipment_df = pd.DataFrame(equipment_list)
# تصفية حسب الفئة
categories = ['الكل'] + sorted(equipment_df['الفئة'].unique().tolist())
selected_category = st.selectbox("تصفية حسب الفئة", categories, key="equipment_cat_filter_section1")
if selected_category != 'الكل':
filtered_df = equipment_df[equipment_df['الفئة'] == selected_category]
else:
filtered_df = equipment_df
# تنسيق الجدول
def highlight_equipment_row(row):
"""تنسيق الجدول مع تمييز الصفوف بالتناوب"""
color = '#F0F8FF' if row.name % 2 == 0 else 'white'
return ['background-color: %s' % color] * len(row)
styled_df = filtered_df.style.apply(highlight_equipment_row, axis=1)
styled_df = styled_df.format({
'سعر الوحدة': '{:,.2f}'
})
st.dataframe(styled_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد معدات في قاعدة البيانات")
else:
st.info("لا توجد قائمة معدات متاحة")
with calc_tabs[3]: # كتالوج أعمال المقاولات
st.markdown("#### كتالوج أعمال المقاولات")
# شرح كتالوج أعمال المقاولات
with st.expander("معلومات عن كتالوج أعمال المقاولات", expanded=False):
st.markdown("""
**كتالوج أعمال المقاولات** هو قاعدة بيانات شاملة للبنود النموذجية المستخدمة في مشاريع المقاولات ويوفر:
- بنود جاهزة لمختلف أنواع الأعمال الإنشائية (خرسانة مناهل مواسير طرق إلخ).
- تفاصيل دقيقة للمواد والعمالة والمعدات المطلوبة لكل بند.
- تحليل تكلفة تفصيلي يمكن استخدامه مباشرة في عروض الأسعار والمناقصات.
- ربط مباشر مع حاسبة تكاليف البناء وحاسبة التسعير.
**استخدامات الكتالوج:**
1. استخدام البنود النموذجية مباشرة في التسعير.
2. تعديل البنود النموذجية لتناسب متطلبات المشروع.
3. إضافة بنود جديدة إلى الكتالوج للاستخدام المستقبلي.
""")
# الفئات وعرض محتوى الكتالوج
category_col, template_col = st.columns([1, 2])
with category_col:
st.markdown("### فئات البنود")
# الحصول على فئات البنود
templates = self.construction_templates.get_all_templates()
categories = templates['categories']
for cat_id, cat_data in categories.items():
st.markdown(f"#### {cat_data['name']}")
st.markdown(f"{cat_data['description']}")
if st.button(f"عرض بنود {cat_data['name']}", key=f"cat_btn_{cat_id}"):
st.session_state.selected_category = cat_id
st.rerun()
with template_col:
st.markdown("### البنود النموذجية")
selected_category = st.session_state.get("selected_category", list(categories.keys())[0])
# عرض البنود في الفئة المحددة
cat_templates = self.construction_templates.get_templates_by_category(selected_category)
if cat_templates:
st.markdown(f"#### بنود فئة {categories[selected_category]['name']}")
for template in cat_templates:
with st.expander(template['name'], expanded=False):
st.markdown(f"**الوصف**: {template['description']}")
st.markdown(f"**الوحدة**: {template['unit']}")
# عرض مكونات البند
st.markdown("##### مكونات البند")
# المواد
materials = template['components']['materials']
if materials:
materials_df = pd.DataFrame(materials)
st.markdown("**المواد:**")
st.dataframe(materials_df, hide_index=True)
# العمالة
labor = template['components']['labor']
if labor:
labor_df = pd.DataFrame(labor)
st.markdown("**العمالة:**")
st.dataframe(labor_df, hide_index=True)
# المعدات
equipment = template['components']['equipment']
if equipment:
equipment_df = pd.DataFrame(equipment)
st.markdown("**المعدات:**")
st.dataframe(equipment_df, hide_index=True)
# أزرار العمليات
col1, col2 = st.columns(2)
with col1:
if st.button("استخدام هذا البند في حاسبة التكاليف", key=f"use_template_{template['id']}"):
# تحويل البند إلى صيغة الحاسبة
construction_item = self.construction_templates.convert_template_to_item(template['id'])
# تحديث بيانات البند في session state
st.session_state.construction_item = construction_item
st.session_state.active_tab = 0 # الانتقال إلى تبويب حساب تكلفة البند
st.rerun()
with col2:
if st.button("إضافة مباشرة إلى جدول التسعير", key=f"add_to_pricing_{template['id']}"):
# تحويل البند إلى صيغة الحاسبة
construction_item = self.construction_templates.convert_template_to_item(template['id'])
# حساب التكلفة
item_cost = self.construction_calculator.calculate_item_cost(construction_item)
# إضافة البند إلى جدول التسعير
if 'manual_items' not in st.session_state:
st.session_state.manual_items = pd.DataFrame(columns=[
'رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي'
])
# إنشاء رقم البند الجديد
new_id = f"C{len(st.session_state.manual_items)+1}"
# إنشاء صف جديد
new_row = pd.DataFrame({
'رقم البند': [new_id],
'وصف البند': [item_cost['وصف_البند']],
'الوحدة': [item_cost['الوحدة']],
'الكمية': [float(item_cost['الكمية'])],
'سعر الوحدة': [float(item_cost['السعر_المعدل']['سعر_الوحدة'])],
'الإجمالي': [float(item_cost['السعر_المعدل']['إجمالي'])]
})
# إضافة الصف إلى DataFrame
st.session_state.manual_items = pd.concat([st.session_state.manual_items, new_row], ignore_index=True)
# إنشاء حالة التسعير الحالي إذا لم تكن موجودة
if 'current_pricing' not in st.session_state:
st.session_state.current_pricing = {
'name': 'تسعير جديد',
'method': 'تسعير مستورد من كتالوج المقاولات',
'items': st.session_state.manual_items
}
else:
# تحديث البنود في التسعير الحالي
st.session_state.current_pricing['items'] = st.session_state.manual_items
st.success(f"تم إضافة البند \"{item_cost['وصف_البند']}\" إلى جدول التسعير بنجاح!")
# إضافة قسم لإضافة بند جديد إلى الكتالوج
st.markdown("### إضافة بند جديد إلى الكتالوج")
if st.button("إضافة البند الحالي إلى الكتالوج"):
if 'item_cost_result' in st.session_state:
# عرض نموذج لإضافة البند إلى الكتالوج
st.session_state.show_add_to_catalog = True
st.rerun()
else:
st.warning("يجب حساب تكلفة البند أولاً في تبويب 'حساب تكلفة بند' قبل إضافته إلى الكتالوج.")
# عرض نموذج إضافة البند إلى الكتالوج
if 'show_add_to_catalog' in st.session_state and st.session_state.show_add_to_catalog:
st.markdown("#### إضافة البند الحالي إلى كتالوج المقاولات")
# اختيار الفئة
category_options = [f"{cat_data['name']}" for cat_id, cat_data in categories.items()]
category_ids = list(categories.keys())
selected_category_index = st.selectbox(
"اختر فئة البند",
options=range(len(category_options)),
format_func=lambda i: category_options[i],
key="new_template_category"
)
selected_category_id = category_ids[selected_category_index]
# معلومات البند
item_result = st.session_state.item_cost_result
template_name = st.text_input("اسم البند في الكتالوج", value=item_result['وصف_البند'][:50])
template_description = st.text_area("وصف البند", value=item_result['وصف_البند'], key="template_item_description")
# الكلمات المفتاحية
tags_input = st.text_input("الكلمات المفتاحية (مفصولة بفواصل)", value="بناء, مقاولات")
tags = [tag.strip() for tag in tags_input.split(",")]
if st.button("إضافة إلى الكتالوج", type="primary"):
# تحويل البند إلى صيغة قالب
template_data = {
"category": selected_category_id,
"name": template_name,
"description": template_description,
"unit": item_result['الوحدة'],
"components": {
"materials": item_result['تكاليف_مباشرة']['المواد']['التفاصيل'],
"labor": item_result['تكاليف_مباشرة']['العمالة']['التفاصيل'],
"equipment": item_result['تكاليف_مباشرة']['المعدات']['التفاصيل']
},
"admin_expenses": item_result['مصاريف_إدارية']['نسبة'] / 100,
"profit_margin": item_result['هامش_ربح']['نسبة'] / 100,
"tags": tags
}
# إضافة القالب إلى الكتالوج
template_id = self.construction_templates.add_template(template_data)
st.success(f"تم إضافة البند \"{template_name}\" إلى كتالوج المقاولات بنجاح!")
st.session_state.show_add_to_catalog = False
st.rerun()
if st.button("إلغاء", key="cancel_add_to_catalog"):
st.session_state.show_add_to_catalog = False
st.rerun()
with ref_tabs[0]: # قائمة المواد
st.markdown("#### قائمة أسعار المواد المرجعية")
# الحصول على قائمة المواد
materials = self.construction_calculator.get_all_rates(item_type='مادة')
if materials and 'المواد' in materials:
# تحويل قاموس المواد إلى DataFrame
materials_list = []
for name, data in materials['المواد'].items():
materials_list.append({
'اسم المادة': name,
'الوحدة': data.get('وحدة', ''),
'سعر الوحدة': data.get('سعر_الوحدة', 0.0),
'الوصف': data.get('وصف', ''),
'الفئة': data.get('فئة', '')
})
if materials_list:
materials_df = pd.DataFrame(materials_list)
# تصفية حسب الفئة
categories = ['الكل'] + sorted(materials_df['الفئة'].unique().tolist())
selected_category = st.selectbox("تصفية حسب الفئة", categories, key="materials_cat_filter_section2")
if selected_category != 'الكل':
filtered_df = materials_df[materials_df['الفئة'] == selected_category]
else:
filtered_df = materials_df
# تنسيق الجدول
def highlight_materials_row(row):
"""تنسيق الجدول مع تمييز الصفوف بالتناوب"""
color = '#F0F8FF' if row.name % 2 == 0 else 'white'
return ['background-color: %s' % color] * len(row)
styled_df = filtered_df.style.apply(highlight_materials_row, axis=1)
styled_df = styled_df.format({
'سعر الوحدة': '{:,.2f}'
})
st.dataframe(styled_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد مواد في قاعدة البيانات")
else:
st.info("لا توجد قائمة مواد متاحة")
with ref_tabs[1]: # قائمة العمالة
st.markdown("#### قائمة أسعار العمالة المرجعية")
# الحصول على قائمة العمالة
labor = self.construction_calculator.get_all_rates(item_type='عمالة')
if labor and 'العمالة' in labor:
# تحويل قاموس العمالة إلى DataFrame
labor_list = []
for name, data in labor['العمالة'].items():
labor_list.append({
'نوع العامل': name,
'وحدة الأجر': data.get('وحدة', ''),
'سعر الوحدة': data.get('سعر_الوحدة', 0.0),
'الوصف': data.get('وصف', ''),
'الفئة': data.get('فئة', '')
})
if labor_list:
labor_df = pd.DataFrame(labor_list)
# تصفية حسب الفئة
categories = ['الكل'] + sorted(labor_df['الفئة'].unique().tolist())
selected_category = st.selectbox("تصفية حسب الفئة", categories, key="labor_cat_filter_section2")
if selected_category != 'الكل':
filtered_df = labor_df[labor_df['الفئة'] == selected_category]
else:
filtered_df = labor_df
# تنسيق الجدول
def highlight_labor_row(row):
"""تنسيق الجدول مع تمييز الصفوف بالتناوب"""
color = '#F0F8FF' if row.name % 2 == 0 else 'white'
return ['background-color: %s' % color] * len(row)
styled_df = filtered_df.style.apply(highlight_labor_row, axis=1)
styled_df = styled_df.format({
'سعر الوحدة': '{:,.2f}'
})
st.dataframe(styled_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد عمالة في قاعدة البيانات")
else:
st.info("لا توجد قائمة عمالة متاحة")
with ref_tabs[2]: # قائمة المعدات
st.markdown("#### قائمة أسعار المعدات المرجعية")
# الحصول على قائمة المعدات
equipment = self.construction_calculator.get_all_rates(item_type='معدة')
if equipment and 'المعدات' in equipment:
# تحويل قاموس المعدات إلى DataFrame
equipment_list = []
for name, data in equipment['المعدات'].items():
equipment_list.append({
'نوع المعدة': name,
'وحدة الأجر': data.get('وحدة', ''),
'سعر الوحدة': data.get('سعر_الوحدة', 0.0),
'الوصف': data.get('وصف', ''),
'الفئة': data.get('فئة', '')
})
if equipment_list:
equipment_df = pd.DataFrame(equipment_list)
# تصفية حسب الفئة
categories = ['الكل'] + sorted(equipment_df['الفئة'].unique().tolist())
selected_category = st.selectbox("تصفية حسب الفئة", categories, key="equipment_cat_filter_section2")
if selected_category != 'الكل':
filtered_df = equipment_df[equipment_df['الفئة'] == selected_category]
else:
filtered_df = equipment_df
# تنسيق الجدول
def highlight_equipment_row(row):
"""تنسيق الجدول مع تمييز الصفوف بالتناوب"""
color = '#F0F8FF' if row.name % 2 == 0 else 'white'
return ['background-color: %s' % color] * len(row)
styled_df = filtered_df.style.apply(highlight_equipment_row, axis=1)
styled_df = styled_df.format({
'سعر الوحدة': '{:,.2f}'
})
st.dataframe(styled_df, use_container_width=True, hide_index=True)
else:
st.info("لا توجد معدات في قاعدة البيانات")
else:
st.info("لا توجد قائمة معدات متاحة")
def _render_local_content_tab(self):
"""عرض تبويب المحتوى المحلي"""
st.markdown("<h2 style='text-align: center; background: linear-gradient(to right, #1F7A8C, #2A9D8F); color: white; padding: 15px; border-radius: 8px;'>تحليل المحتوى المحلي</h2>", unsafe_allow_html=True)
# التحقق من وجود تسعير حالي
if 'current_pricing' not in st.session_state or st.session_state.current_pricing is None:
st.warning("ليس هناك تسعير حالي. يرجى إنشاء تسعير جديد أولاً.")
return
# شرح المحتوى المحلي
with st.expander("ما هو المحتوى المحلي؟", expanded=False):
st.markdown("""
**المحتوى المحلي** هو نسبة المنتجات والخدمات والقوى العاملة المحلية المستخدمة في المشروع. يهدف إلى زيادة مساهمة المنتجات والخدمات المحلية في المشاريع.
### مكونات المحتوى المحلي:
1. **المنتجات**: المنتجات والمواد المصنعة محلياً.
2. **الخدمات**: الخدمات المقدمة من شركات محلية.
3. **القوى العاملة**: العمالة والكوادر الفنية والإدارية المحلية.
### أهمية المحتوى المحلي:
- تعزيز الاقتصاد المحلي وخلق فرص عمل.
- تحقيق أهداف رؤية 2030 في زيادة المحتوى المحلي.
- التأهل للمشاريع الحكومية التي تتطلب نسبة محتوى محلي محددة.
- الحصول على حوافز وأفضلية في المناقصات الحكومية.
### متطلبات المحتوى المحلي:
- نسبة المحتوى المحلي للقوى العاملة: 80%
- نسبة المحتوى المحلي للمنتجات: 70%
- نسبة المحتوى المحلي للخدمات: 60%
""")
# عرض لوحة إدخال بيانات المحتوى المحلي
st.markdown("### بيانات المحتوى المحلي")
# التبويبات لأنواع المحتوى المحلي
lc_tabs = st.tabs(["المنتجات", "الخدمات", "القوى العاملة", "التحليل"])
with lc_tabs[0]: # المنتجات
st.markdown("#### بيانات المنتجات")
# إنشاء بيانات افتراضية للمنتجات إذا لم تكن موجودة
if 'local_content_products' not in st.session_state:
st.session_state.local_content_products = pd.DataFrame({
'المنتج': [
"خرسانة مسلحة",
"حديد تسليح",
"بلوك خرساني",
"عزل مائي",
"دهانات"
],
'الكمية': [250, 25, 400, 500, 600],
'سعر_الوحدة': [1200, 6000, 200, 100, 50],
'التكلفة_الإجمالية': [300000, 150000, 80000, 50000, 30000],
'نسبة_المحتوى_المحلي': [0.95, 0.70, 0.98, 0.60, 0.80]
})
# حساب التكلفة الإجمالية
st.session_state.local_content_products['التكلفة_الإجمالية'] = st.session_state.local_content_products['الكمية'] * st.session_state.local_content_products['سعر_الوحدة']
# نموذج إضافة منتج جديد
st.markdown("#### إضافة منتج جديد")
col1, col2, col3, col4 = st.columns(4)
with col1:
new_product_name = st.text_input("اسم المنتج", key="new_product_name", value="")
with col2:
new_product_quantity = st.number_input("الكمية", key="new_product_quantity", min_value=0, value=0)
with col3:
new_product_price = st.number_input("سعر الوحدة", key="new_product_price", min_value=0, value=0)
with col4:
new_product_local_content = st.slider("نسبة المحتوى المحلي", key="new_product_local_content", min_value=0.0, max_value=1.0, value=0.8, step=0.01, format="%.2f")
if st.button("إضافة المنتج"):
if new_product_name:
# حساب التكلفة الإجمالية
total_cost = new_product_quantity * new_product_price
# إضافة منتج جديد للجدول
new_product = pd.DataFrame({
'المنتج': [new_product_name],
'الكمية': [new_product_quantity],
'سعر_الوحدة': [new_product_price],
'التكلفة_الإجمالية': [total_cost],
'نسبة_المحتوى_المحلي': [new_product_local_content]
})
# إضافة المنتج الجديد للجدول الحالي
st.session_state.local_content_products = pd.concat([st.session_state.local_content_products, new_product], ignore_index=True)
st.success(f"تم إضافة المنتج {new_product_name} بنجاح!")
else:
st.warning("يرجى إدخال اسم المنتج")
# عرض جدول البنود مع إمكانية التعديل
st.markdown("#### جدول المنتجات")
edited_products = st.data_editor(
st.session_state.local_content_products,
use_container_width=True,
hide_index=True,
key="products_editor"
)
st.session_state.local_content_products = edited_products
# زر لحذف المنتجات المحددة
if st.button("حذف المنتجات المحددة"):
st.session_state.local_content_products = pd.DataFrame({
'المنتج': [],
'الكمية': [],
'سعر_الوحدة': [],
'التكلفة_الإجمالية': [],
'نسبة_المحتوى_المحلي': []
})
st.success("تم حذف جميع المنتجات!")
st.rerun()
# عرض ملخص المنتجات
total_products_cost = edited_products['التكلفة_الإجمالية'].sum()
avg_local_content = (edited_products['التكلفة_الإجمالية'] * edited_products['نسبة_المحتوى_المحلي']).sum() / total_products_cost if total_products_cost > 0 else 0
st.markdown(f"""
**إجمالي تكلفة المنتجات**: {total_products_cost:,.2f} ريال
**متوسط نسبة المحتوى المحلي للمنتجات**: {avg_local_content*100:.2f}%
**المستهدف**: 70%
**الحالة**: {"✅ ملتزم" if avg_local_content >= 0.7 else "❌ غير ملتزم"}
""")
with lc_tabs[1]: # الخدمات
st.markdown("#### بيانات الخدمات")
# إنشاء بيانات افتراضية للخدمات إذا لم تكن موجودة
if 'local_content_services' not in st.session_state:
st.session_state.local_content_services = pd.DataFrame({
'الخدمة': [
"تصميم معماري",
"إشراف هندسي",
"خدمات نقل",
"خدمات أمن وسلامة",
"صيانة ونظافة"
],
'التكلفة': [100000, 120000, 50000, 30000, 20000],
'نسبة_المحتوى_المحلي': [0.90, 0.85, 0.90, 0.95, 0.95]
})
# نموذج إضافة خدمة جديدة
st.markdown("#### إضافة خدمة جديدة")
col1, col2, col3 = st.columns(3)
with col1:
new_service_name = st.text_input("اسم الخدمة", key="new_service_name", value="")
with col2:
new_service_cost = st.number_input("التكلفة", key="new_service_cost", min_value=0, value=0)
with col3:
new_service_local_content = st.slider("نسبة المحتوى المحلي", key="new_service_local_content", min_value=0.0, max_value=1.0, value=0.8, step=0.01, format="%.2f")
if st.button("إضافة الخدمة"):
if new_service_name:
# إضافة خدمة جديدة للجدول
new_service = pd.DataFrame({
'الخدمة': [new_service_name],
'التكلفة': [new_service_cost],
'نسبة_المحتوى_المحلي': [new_service_local_content]
})
# إضافة الخدمة الجديدة للجدول الحالي
st.session_state.local_content_services = pd.concat([st.session_state.local_content_services, new_service], ignore_index=True)
st.success(f"تم إضافة الخدمة {new_service_name} بنجاح!")
else:
st.warning("يرجى إدخال اسم الخدمة")
# عرض جدول الخدمات مع إمكانية التعديل
st.markdown("#### جدول الخدمات")
edited_services = st.data_editor(
st.session_state.local_content_services,
use_container_width=True,
hide_index=True,
key="services_editor"
)
st.session_state.local_content_services = edited_services
# زر لحذف الخدمات المحددة
if st.button("حذف الخدمات المحددة"):
st.session_state.local_content_services = pd.DataFrame({
'الخدمة': [],
'التكلفة': [],
'نسبة_المحتوى_المحلي': []
})
st.success("تم حذف جميع الخدمات!")
st.rerun()
# عرض ملخص الخدمات
total_services_cost = edited_services['التكلفة'].sum()
avg_local_content = (edited_services['التكلفة'] * edited_services['نسبة_المحتوى_المحلي']).sum() / total_services_cost if total_services_cost > 0 else 0
st.markdown(f"""
**إجمالي تكلفة الخدمات**: {total_services_cost:,.2f} ريال
**متوسط نسبة المحتوى المحلي للخدمات**: {avg_local_content*100:.2f}%
**المستهدف**: 60%
**الحالة**: {"✅ ملتزم" if avg_local_content >= 0.6 else "❌ غير ملتزم"}
""")
with lc_tabs[2]: # القوى العاملة
st.markdown("#### بيانات القوى العاملة")
# إنشاء بيانات افتراضية للقوى العاملة إذا لم تكن موجودة
if 'local_content_labor' not in st.session_state:
st.session_state.local_content_labor = pd.DataFrame({
'فئة_العمالة': [
"مهندسون",
"فنيون",
"عمال بناء",
"إداريون",
"مشرفون"
],
'العدد': [5, 10, 30, 3, 4],
'الراتب_الشهري': [15000, 8000, 3000, 10000, 12000],
'المدة_بالأشهر': [12, 12, 12, 12, 12],
'نسبة_المحتوى_المحلي': [0.75, 0.65, 0.60, 0.90, 0.80]
})
# حساب التكلفة الإجمالية
st.session_state.local_content_labor['التكلفة_الإجمالية'] = st.session_state.local_content_labor['العدد'] * st.session_state.local_content_labor['الراتب_الشهري'] * st.session_state.local_content_labor['المدة_بالأشهر']
# نموذج إضافة فئة عمالة جديدة
st.markdown("#### إضافة فئة عمالة جديدة")
col1, col2 = st.columns(2)
with col1:
new_labor_category = st.text_input("فئة العمالة", key="new_labor_category", value="")
new_labor_count = st.number_input("العدد", key="new_labor_count", min_value=0, value=0)
with col2:
new_labor_salary = st.number_input("الراتب الشهري", key="new_labor_salary", min_value=0, value=0)
new_labor_months = st.number_input("المدة بالأشهر", key="new_labor_months", min_value=1, value=12)
new_labor_local_content = st.slider("نسبة المحتوى المحلي", key="new_labor_local_content", min_value=0.0, max_value=1.0, value=0.8, step=0.01, format="%.2f")
if st.button("إضافة فئة العمالة"):
if new_labor_category:
# حساب التكلفة الإجمالية
total_cost = new_labor_count * new_labor_salary * new_labor_months
# إضافة فئة عمالة جديدة للجدول
new_labor = pd.DataFrame({
'فئة_العمالة': [new_labor_category],
'العدد': [new_labor_count],
'الراتب_الشهري': [new_labor_salary],
'المدة_بالأشهر': [new_labor_months],
'نسبة_المحتوى_المحلي': [new_labor_local_content],
'التكلفة_الإجمالية': [total_cost]
})
# إضافة فئة العمالة الجديدة للجدول الحالي
st.session_state.local_content_labor = pd.concat([st.session_state.local_content_labor, new_labor], ignore_index=True)
st.success(f"تم إضافة فئة العمالة {new_labor_category} بنجاح!")
else:
st.warning("يرجى إدخال اسم فئة العمالة")
# عرض جدول القوى العاملة مع إمكانية التعديل
st.markdown("#### جدول القوى العاملة")
edited_labor = st.data_editor(
st.session_state.local_content_labor,
use_container_width=True,
hide_index=True,
key="labor_editor"
)
# إعادة حساب التكلفة الإجمالية بعد التعديل
edited_labor['التكلفة_الإجمالية'] = edited_labor['العدد'] * edited_labor['الراتب_الشهري'] * edited_labor['المدة_بالأشهر']
st.session_state.local_content_labor = edited_labor
# زر لحذف فئات العمالة المحددة
if st.button("حذف فئات العمالة المحددة"):
st.session_state.local_content_labor = pd.DataFrame({
'فئة_العمالة': [],
'العدد': [],
'الراتب_الشهري': [],
'المدة_بالأشهر': [],
'نسبة_المحتوى_المحلي': [],
'التكلفة_الإجمالية': []
})
st.success("تم حذف جميع فئات العمالة!")
st.rerun()
# عرض ملخص القوى العاملة
total_labor_cost = edited_labor['التكلفة_الإجمالية'].sum()
avg_local_content = (edited_labor['التكلفة_الإجمالية'] * edited_labor['نسبة_المحتوى_المحلي']).sum() / total_labor_cost if total_labor_cost > 0 else 0
st.markdown(f"""
**إجمالي تكلفة القوى العاملة**: {total_labor_cost:,.2f} ريال
**متوسط نسبة المحتوى المحلي للقوى العاملة**: {avg_local_content*100:.2f}%
**المستهدف**: 80%
**الحالة**: {"✅ ملتزم" if avg_local_content >= 0.8 else "❌ غير ملتزم"}
""")
with lc_tabs[3]: # التحليل
st.markdown("#### تحليل المحتوى المحلي")
# حساب المحتوى المحلي الإجمالي
try:
# تجميع بيانات تحليل المحتوى المحلي
products_cost = st.session_state.local_content_products['التكلفة_الإجمالية'].sum()
products_local_content = (st.session_state.local_content_products['التكلفة_الإجمالية'] * st.session_state.local_content_products['نسبة_المحتوى_المحلي']).sum() / products_cost if products_cost > 0 else 0
services_cost = st.session_state.local_content_services['التكلفة'].sum()
services_local_content = (st.session_state.local_content_services['التكلفة'] * st.session_state.local_content_services['نسبة_المحتوى_المحلي']).sum() / services_cost if services_cost > 0 else 0
labor_cost = st.session_state.local_content_labor['التكلفة_الإجمالية'].sum()
labor_local_content = (st.session_state.local_content_labor['التكلفة_الإجمالية'] * st.session_state.local_content_labor['نسبة_المحتوى_المحلي']).sum() / labor_cost if labor_cost > 0 else 0
# حساب الوزن النسبي لكل مكون
total_cost = products_cost + services_cost + labor_cost
products_weight = products_cost / total_cost if total_cost > 0 else 0
services_weight = services_cost / total_cost if total_cost > 0 else 0
labor_weight = labor_cost / total_cost if total_cost > 0 else 0
# حساب المحتوى المحلي الإجمالي
total_local_content = (products_local_content * products_weight) + (services_local_content * services_weight) + (labor_local_content * labor_weight)
# عرض ملخص المحتوى المحلي
st.markdown("### ملخص المحتوى المحلي")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("إجمالي التكاليف", f"{total_cost:,.2f} ريال")
with col2:
st.metric("نسبة المحتوى المحلي الإجمالية", f"{total_local_content*100:.2f}%")
with col3:
target_local_content = 0.7 # 70%
st.metric("الحالة", "ملتزم" if total_local_content >= target_local_content else "غير ملتزم", delta=f"{(total_local_content - target_local_content)*100:.2f}%")
# عرض رسم بياني للمقارنة
st.markdown("### تحليل بصري للمحتوى المحلي")
# رسم بياني شريطي لنسب المحتوى المحلي
categories = ['المنتجات', 'الخدمات', 'القوى العاملة', 'الإجمالي']
actual_values = [products_local_content * 100, services_local_content * 100, labor_local_content * 100, total_local_content * 100]
target_values = [70, 60, 80, 70] # المستهدفات
# تهيئة البيانات للرسم البياني
chart_data = pd.DataFrame({
'الفئة': categories,
'النسبة الفعلية': actual_values,
'النسبة المستهدفة': target_values
})
# رسم بياني شريطي للمقارنة
fig = go.Figure()
fig.add_trace(go.Bar(
x=chart_data['الفئة'],
y=chart_data['النسبة الفعلية'],
name='النسبة الفعلية',
marker_color='rgb(26, 118, 255)'
))
fig.add_trace(go.Bar(
x=chart_data['الفئة'],
y=chart_data['النسبة المستهدفة'],
name='النسبة المستهدفة',
marker_color='rgb(55, 83, 109)'
))
fig.update_layout(
title='مقارنة بين النسب الفعلية والمستهدفة للمحتوى المحلي',
xaxis_tickfont_size=14,
yaxis=dict(
title='النسبة %',
titlefont_size=16,
tickfont_size=14,
),
legend=dict(
x=0,
y=1.0,
bgcolor='rgba(255, 255, 255, 0)',
bordercolor='rgba(255, 255, 255, 0)'
),
barmode='group',
bargap=0.15,
bargroupgap=0.1
)
st.plotly_chart(fig, use_container_width=True)
# عرض توصيات لتحسين نسبة المحتوى المحلي
st.markdown("### توصيات لتحسين نسبة المحتوى المحلي")
recommendations = []
if products_local_content < 0.7:
recommendations.append("- زيادة نسبة المحتوى المحلي للمنتجات من خلال:")
recommendations.append(" - البحث عن موردين محليين للمنتجات ذات النسبة المنخفضة")
recommendations.append(" - استبدال المنتجات المستوردة ببدائل محلية")
recommendations.append(" - التعاون مع المصانع المحلية لتوطين صناعة المنتجات")
if services_local_content < 0.6:
recommendations.append("- زيادة نسبة المحتوى المحلي للخدمات من خلال:")
recommendations.append(" - التعاقد مع شركات خدمات محلية")
recommendations.append(" - تحويل الخدمات المستعان بها من الخارج إلى شركات محلية")
recommendations.append(" - تأهيل الشركات المحلية لتقديم الخدمات المطلوبة")
if labor_local_content < 0.8:
recommendations.append("- زيادة نسبة المحتوى المحلي للقوى العاملة من خلال:")
recommendations.append(" - زيادة توظيف الكوادر المحلية")
recommendations.append(" - تدريب وتأهيل العمالة المحلية")
recommendations.append(" - استبدال العمالة الأجنبية بكوادر محلية تدريجياً")
if total_local_content < 0.7:
recommendations.append("- زيادة نسبة المحتوى المحلي الإجمالية من خلال:")
recommendations.append(" - إعادة توزيع الميزانية لصالح المكونات ذات النسبة العالية من المحتوى المحلي")
recommendations.append(" - وضع خطة مرحلية لزيادة المحتوى المحلي")
recommendations.append(" - التعاون مع اللجنة المحلية لزيادة المحتوى المحلي")
if recommendations:
for rec in recommendations:
st.markdown(rec)
else:
st.success("تهانينا! نسبة المحتوى المحلي متوافقة مع المتطلبات.")
# حساب تأثير المحتوى المحلي على التسعير
st.markdown("### تأثير المحتوى المحلي على التسعير")
# تحديد عامل تعديل السعر بناءً على نسبة المحتوى المحلي
price_adjustment_factor = 1.0
if total_local_content >= 0.9:
price_adjustment_factor = 0.92 # خصم 8% للمحتوى المحلي العالي جداً
price_discount = "8%"
elif total_local_content >= 0.8:
price_adjustment_factor = 0.94 # خصم 6% للمحتوى المحلي العالي
price_discount = "6%"
elif total_local_content >= 0.7:
price_adjustment_factor = 0.96 # خصم 4% للمحتوى المحلي المتوسط
price_discount = "4%"
elif total_local_content >= 0.6:
price_adjustment_factor = 0.98 # خصم 2% للمحتوى المحلي المنخفض
price_discount = "2%"
else:
price_adjustment_factor = 1.0 # لا خصم
price_discount = "0%"
# عرض تأثير المحتوى المحلي على التسعير
original_total = st.session_state.current_pricing['items']['الإجمالي'].sum()
adjusted_total = original_total * price_adjustment_factor
discount_amount = original_total - adjusted_total
col1, col2, col3 = st.columns(3)
with col1:
st.metric("إجمالي التسعير الأصلي", f"{original_total:,.2f} ريال")
with col2:
st.metric("نسبة الخصم بسبب المحتوى المحلي", price_discount)
with col3:
st.metric("إجمالي التسعير بعد الخصم", f"{adjusted_total:,.2f} ريال", delta=f"-{discount_amount:,.2f} ريال")
# أزرار العمليات
col1, col2 = st.columns(2)
with col1:
if st.button("حفظ تحليل المحتوى المحلي"):
# حفظ بيانات المحتوى المحلي في التسعير الحالي
st.session_state.current_pricing['local_content'] = {
'products': st.session_state.local_content_products.copy(),
'services': st.session_state.local_content_services.copy(),
'labor': st.session_state.local_content_labor.copy(),
'total_local_content': total_local_content,
'price_adjustment_factor': price_adjustment_factor
}
st.success("تم حفظ تحليل المحتوى المحلي بنجاح!")
with col2:
if st.button("تصدير تقرير المحتوى المحلي"):
st.success("تم تصدير تقرير المحتوى المحلي بنجاح!")
except Exception as e:
st.error(f"حدث خطأ أثناء تحليل المحتوى المحلي: {str(e)}")
st.warning("تأكد من إدخال بيانات المحتوى المحلي بشكل صحيح في التبويبات السابقة.")
def _render_utilities_tab(self):
"""عرض تبويب الأدوات المساعدة"""
import json
import copy
from datetime import datetime
st.markdown("## الأدوات المساعدة")
utilities_tab1, utilities_tab2, utilities_tab3, utilities_tab4, utilities_tab5 = st.tabs([
"الرسوم البيانية المتقدمة",
"استيراد/تصدير الإعدادات",
"النسخ الاحتياطي والاستعادة",
"مقارنة نماذج التسعير",
"إنشاء التقارير"
])
with utilities_tab1:
st.markdown("### الرسوم البيانية المتقدمة لتحليل التكلفة")
if 'item_cost_result' in st.session_state:
result = st.session_state.item_cost_result
# تبويب الرسوم البيانية
chart_tab1, chart_tab2, chart_tab3 = st.tabs(["توزيع التكلفة", "مقارنة المكونات", "تأثير العوامل"])
with chart_tab1:
# رسم بياني دائري لتوزيع التكلفة
fig = go.Figure(data=[go.Pie(
labels=["المواد", "العمالة", "المعدات", "المصاريف الإدارية", "هامش الربح"],
values=[
result['تكاليف_مباشرة']['المواد']['الإجمالي'],
result['تكاليف_مباشرة']['العمالة']['الإجمالي'],
result['تكاليف_مباشرة']['المعدات']['الإجمالي'],
result['مصاريف_إدارية']['قيمة'],
result['هامش_ربح']['قيمة']
],
hole=.3,
marker_colors=['#36a2eb', '#ff6384', '#4bc0c0', '#ffcd56', '#9966ff']
)])
fig.update_layout(title_text="توزيع مكونات التكلفة")
st.plotly_chart(fig, use_container_width=True)
with chart_tab2:
# رسم بياني شريطي للمكونات الرئيسية
categories = ["المواد", "العمالة", "المعدات"]
values = [
result['تكاليف_مباشرة']['المواد']['الإجمالي'],
result['تكاليف_مباشرة']['العمالة']['الإجمالي'],
result['تكاليف_مباشرة']['المعدات']['الإجمالي']
]
fig = go.Figure(data=[go.Bar(x=categories, y=values, marker_color=['#36a2eb', '#ff6384', '#4bc0c0'])])
fig.update_layout(title_text="مقارنة بين المكونات الرئيسية للتكلفة")
st.plotly_chart(fig, use_container_width=True)
with chart_tab3:
# مخطط شريطي يوضح تأثير عوامل التعديل على التكلفة
original_cost = result['التكلفة_الإجمالية']
final_cost = result['السعر_المعدل']['إجمالي']
# التحقق من وجود العوامل، وإضافتها بقيم افتراضية إذا كانت غير موجودة
if 'عوامل_التعديل' not in result:
result['عوامل_التعديل'] = {}
# إضافة المفاتيح الناقصة بقيم افتراضية
default_factors = {
'location_factor': 1.0,
'time_factor': 1.0,
'risk_factor': 1.0,
'market_factor': 1.0
}
for key, default_value in default_factors.items():
if key not in result['عوامل_التعديل']:
result['عوامل_التعديل'][key] = default_value
factors = {
"معامل الموقع": result['عوامل_التعديل']['location_factor'],
"معامل الوقت": result['عوامل_التعديل']['time_factor'],
"معامل المخاطر": result['عوامل_التعديل']['risk_factor'],
"معامل السوق": result['عوامل_التعديل']['market_factor']
}
# حساب القيمة المضافة من كل عامل
factor_effects = {}
for factor_name, factor_value in factors.items():
factor_effects[factor_name] = original_cost * (factor_value - 1)
fig = go.Figure()
fig.add_trace(go.Waterfall(
name="تأثير العوامل",
orientation="v",
measure=["absolute"] + ["relative"] * len(factor_effects) + ["total"],
x=["التكلفة الأصلية"] + list(factor_effects.keys()) + ["التكلفة النهائية"],
y=[original_cost] + list(factor_effects.values()) + [0],
text=[f"{original_cost:,.2f}"] + [f"{val:,.2f}" for val in factor_effects.values()] + [f"{final_cost:,.2f}"],
connector={"line": {"color": "rgb(63, 63, 63)"}},
))
fig.update_layout(title_text="تأثير عوامل التعديل على التكلفة النهائية")
st.plotly_chart(fig, use_container_width=True)
else:
st.warning("لم يتم العثور على بيانات تحليل التكلفة. يرجى إجراء تحليل تكلفة في تبويب 'تحليل سعر البند' أولاً.")
with utilities_tab2:
st.markdown("### استيراد/تصدير إعدادات التسعير")
export_col, import_col = st.columns(2)
with export_col:
st.markdown("#### تصدير الإعدادات الحالية")
if st.button("تصدير إعدادات التسعير", key="export_pricing_settings"):
pricing_settings = {
"construction_item": st.session_state.construction_item if 'construction_item' in st.session_state else None,
"current_pricing": st.session_state.current_pricing if 'current_pricing' in st.session_state else None
}
settings_json = json.dumps(pricing_settings, ensure_ascii=False, indent=2)
st.download_button(
label="تنزيل إعدادات التسعير",
data=settings_json,
file_name="pricing_settings.json",
mime="application/json",
key="download_settings_button"
)
with import_col:
st.markdown("#### استيراد إعدادات سابقة")
uploaded_file = st.file_uploader("استيراد إعدادات تسعير سابقة", type=["json"], key="upload_pricing_settings")
if uploaded_file is not None:
try:
settings_data = json.loads(uploaded_file.getvalue().decode('utf-8'))
# تحديث بيانات التسعير في الجلسة
if 'construction_item' in settings_data and settings_data['construction_item']:
st.session_state.construction_item = settings_data['construction_item']
if 'current_pricing' in settings_data and settings_data['current_pricing']:
st.session_state.current_pricing = settings_data['current_pricing']
st.success("تم استيراد الإعدادات بنجاح!")
st.rerun()
except Exception as e:
st.error(f"حدث خطأ أثناء استيراد الإعدادات: {str(e)}")
with utilities_tab3:
st.markdown("### النسخ الاحتياطي والاستعادة")
backup_tab1, backup_tab2 = st.tabs(["إنشاء نسخة احتياطية", "استعادة من نسخة احتياطية"])
with backup_tab1:
st.markdown("#### إنشاء نسخة احتياطية كاملة")
st.markdown("تقوم هذه العملية بإنشاء نسخة احتياطية كاملة لجميع بيانات التسعير الحالية.")
if st.button("إنشاء نسخة احتياطية كاملة", key="create_full_backup"):
backup_data = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"construction_item": st.session_state.construction_item if 'construction_item' in st.session_state else None,
"current_pricing": st.session_state.current_pricing if 'current_pricing' in st.session_state else None,
"pricing_models": st.session_state.pricing_models if 'pricing_models' in st.session_state else [],
"manual_items": st.session_state.manual_items.to_dict('records') if 'manual_items' in st.session_state else []
}
backup_json = json.dumps(backup_data, ensure_ascii=False, indent=2)
filename = f"wahbi_pricing_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
st.download_button(
label="تنزيل النسخة الاحتياطية",
data=backup_json,
file_name=filename,
mime="application/json",
key="download_backup_button"
)
st.success("تم إنشاء النسخة الاحتياطية بنجاح!")
with backup_tab2:
st.markdown("#### استعادة من نسخة احتياطية")
st.markdown("يمكنك استعادة بيانات التسعير من نسخة احتياطية سابقة.")
backup_file = st.file_uploader("استعادة من نسخة احتياطية", type=["json"], key="restore_backup_file")
if backup_file is not None:
if st.button("استعادة البيانات", key="restore_from_backup"):
try:
backup_data = json.loads(backup_file.getvalue().decode('utf-8'))
# استعادة البيانات إلى الحالة الحالية
if 'construction_item' in backup_data and backup_data['construction_item']:
st.session_state.construction_item = backup_data['construction_item']
if 'current_pricing' in backup_data and backup_data['current_pricing']:
st.session_state.current_pricing = backup_data['current_pricing']
if 'pricing_models' in backup_data:
st.session_state.pricing_models = backup_data['pricing_models']
if 'manual_items' in backup_data and backup_data['manual_items']:
st.session_state.manual_items = pd.DataFrame(backup_data['manual_items'])
st.success(f"تم استعادة البيانات من النسخة الاحتياطية بنجاح! (تاريخ النسخة: {backup_data.get('timestamp', 'غير معروف')})")
st.rerun()
except Exception as e:
st.error(f"حدث خطأ أثناء استعادة البيانات: {str(e)}")
with utilities_tab4:
st.markdown("### مقارنة نماذج التسعير")
st.markdown("هذه الأداة تتيح لك مقارنة نماذج التسعير المختلفة واختيار الأفضل منها.")
# تهيئة قائمة نماذج التسعير إذا لم تكن موجودة
if 'pricing_models' not in st.session_state:
st.session_state.pricing_models = []
# إضافة النموذج الحالي للمقارنة
if st.button("إضافة النموذج الحالي للمقارنة", key="add_current_model"):
if 'current_pricing' in st.session_state and st.session_state.current_pricing is not None:
model_name = st.session_state.current_pricing.get('name', 'نموذج بدون اسم')
model_data = copy.deepcopy(st.session_state.current_pricing)
# تحقق من عدم وجود نموذج بنفس الاسم
exists = False
for model in st.session_state.pricing_models:
if model.get('name') == model_name:
exists = True
break
if not exists:
st.session_state.pricing_models.append(model_data)
st.success(f"تم إضافة نموذج '{model_name}' للمقارنة!")
else:
st.warning("يوجد نموذج بنفس الاسم في المقارنة بالفعل!")
else:
st.error("لا يوجد نموذج تسعير حالي للإضافة. يرجى إنشاء تسعير جديد أولاً.")
# عرض جدول المقارنة إذا كان هناك نماذج مضافة
if len(st.session_state.pricing_models) > 0:
st.markdown("### جدول مقارنة نماذج التسعير")
comparison_data = []
for model in st.session_state.pricing_models:
# حساب إجمالي التكلفة
items_df = pd.DataFrame(model.get('items', {}))
total_price = 0
if not items_df.empty and 'الإجمالي' in items_df.columns:
total_price = items_df['الإجمالي'].sum()
comparison_data.append({
"اسم النموذج": model.get('name', 'غير معروف'),
"طريقة التسعير": model.get('method', 'غير معروفة'),
"إجمالي التكلفة": f"{total_price:,.2f} ريال",
"عدد البنود": len(items_df) if not items_df.empty else 0,
})
comparison_df = pd.DataFrame(comparison_data)
st.dataframe(comparison_df, use_container_width=True, hide_index=True)
# عرض رسم بياني للمقارنة
if len(comparison_data) > 1:
st.markdown("### رسم بياني للمقارنة")
# استخراج البيانات للرسم البياني
models = [data["اسم النموذج"] for data in comparison_data]
values = [float(data["إجمالي التكلفة"].replace("ريال", "").replace(",", "")) for data in comparison_data]
# رسم بياني شريطي
fig = go.Figure(data=[
go.Bar(x=models, y=values, marker_color='rgb(26, 118, 255)')
])
fig.update_layout(
title="مقارنة التكلفة الإجمالية بين نماذج التسعير",
xaxis_title="نموذج التسعير",
yaxis_title="التكلفة الإجمالية (ريال)"
)
st.plotly_chart(fig, use_container_width=True)
# أزرار إدارة النماذج
col1, col2 = st.columns(2)
with col1:
if st.button("حذف جميع النماذج", key="clear_comparison"):
st.session_state.pricing_models = []
st.success("تم مسح جميع نماذج المقارنة!")
st.rerun()
with col2:
# تحديد نموذج للحذف
model_to_delete = st.selectbox(
"اختر نموذج للحذف من المقارنة",
options=[model.get('name', f"نموذج {i+1}") for i, model in enumerate(st.session_state.pricing_models)],
key="model_to_delete"
)
if st.button("حذف النموذج المحدد", key="delete_selected_model"):
for i, model in enumerate(st.session_state.pricing_models):
if model.get('name') == model_to_delete:
st.session_state.pricing_models.pop(i)
st.warning(f"تم حذف النموذج '{model_to_delete}'")
st.rerun()
break
else:
st.info("لا توجد نماذج للمقارنة حالياً. يرجى إضافة النموذج الحالي للمقارنة أولاً.")
with utilities_tab5:
st.markdown("### إنشاء تقارير التسعير")
st.markdown("يمكنك استخدام هذه الأداة لإنشاء تقارير مفصلة عن التسعير.")
report_type = st.selectbox(
"اختر نوع التقرير",
options=["تقرير ملخص", "تقرير تفصيلي", "تقرير المقارنة"],
key="report_type_selector"
)
if st.button("إنشاء التقرير", key="generate_report_button"):
if report_type == "تقرير ملخص" and 'current_pricing' in st.session_state and st.session_state.current_pricing is not None:
# إنشاء تقرير ملخص
if isinstance(st.session_state.current_pricing.get('items'), pd.DataFrame):
df = st.session_state.current_pricing['items'].copy()
# حساب الإجماليات
total_price = df['الإجمالي'].sum() if 'الإجمالي' in df.columns else 0
# تقدير تكاليف المكونات
materials_cost = total_price * 0.6 # تقدير تقريبي للمواد
labor_cost = total_price * 0.25 # تقدير تقريبي للعمالة
equipment_cost = total_price * 0.15 # تقدير تقريبي للمعدات
admin_cost = total_price * 0.05 # تقدير تقريبي للمصاريف الإدارية
profit_margin = total_price * 0.1 # تقدير تقريبي لهامش الربح
final_total = total_price * 1.15 # إجمالي بعد إضافة المصاريف الإدارية وهامش الربح
# إنشاء جدول الملخص
summary = pd.DataFrame({
"بند التكلفة": ["إجمالي المواد", "إجمالي الأجور", "إجمالي المعدات", "المصاريف الإدارية", "هامش الربح", "الإجمالي النهائي"],
"القيمة": [
materials_cost,
labor_cost,
equipment_cost,
admin_cost,
profit_margin,
final_total
]
})
# تنسيق التقرير
styled_df = summary.style.format({
"القيمة": "{:,.2f} ريال"
})
# تحويل الجدول إلى HTML
html = f"""
<html dir="rtl">
<head>
<meta charset="UTF-8">
<title>تقرير ملخص التسعير</title>
<style>
body {{ font-family: 'Arial', 'Tahoma', sans-serif; direction: rtl; }}
h1, h2 {{ color: #075e54; text-align: center; }}
table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }}
th {{ background-color: #075e54; color: white; padding: 10px; text-align: right; }}
td {{ padding: 8px; text-align: right; border-bottom: 1px solid #ddd; }}
tr:nth-child(even) {{ background-color: #f9f9f9; }}
.header {{ background-color: #f2f2f2; padding: 20px; margin-bottom: 20px; }}
.footer {{ margin-top: 30px; text-align: center; font-size: 0.8em; color: #666; }}
</style>
</head>
<body>
<div class="header">
<h1>تقرير ملخص التسعير</h1>
<h2>{st.session_state.current_pricing.get('name', 'تسعير بدون اسم')}</h2>
<p>تاريخ التقرير: {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
</div>
<h3>ملخص التكاليف</h3>
{styled_df.to_html()}
<h3>البيانات الأساسية</h3>
<ul>
<li>عدد البنود: {len(df)}</li>
<li>طريقة التسعير: {st.session_state.current_pricing.get('method', 'غير محددة')}</li>
</ul>
<div class="footer">
<p>تم إنشاء هذا التقرير بواسطة نظام وهبي للتحليل المتقدم للمناقصات</p>
</div>
</body>
</html>
"""
# تقديم زر التنزيل
st.download_button(
label="تنزيل التقرير الملخص",
data=html,
file_name="pricing_summary_report.html",
mime="text/html",
key="download_summary_report"
)
st.success("تم إنشاء التقرير الملخص بنجاح!")
else:
st.error("تعذر قراءة بيانات التسعير الحالي. يرجى التأكد من وجود تسعير صالح.")
elif report_type == "تقرير تفصيلي" and 'current_pricing' in st.session_state and st.session_state.current_pricing is not None:
# سيتم تنفيذ التقرير التفصيلي
st.info("جاري إعداد التقرير التفصيلي...")
# هنا يمكن تنفيذ كود إنشاء التقرير التفصيلي
st.success("تم إنشاء التقرير التفصيلي. سيتم تطوير هذه الميزة قريباً.")
elif report_type == "تقرير المقارنة" and 'pricing_models' in st.session_state and len(st.session_state.pricing_models) > 0:
# سيتم تنفيذ تقرير المقارنة
st.info("جاري إعداد تقرير المقارنة...")
# هنا يمكن تنفيذ كود إنشاء تقرير المقارنة
st.success("تم إنشاء تقرير المقارنة. سيتم تطوير هذه الميزة قريباً.")
else:
st.error("لا توجد بيانات كافية لإنشاء التقرير المطلوب. يرجى التأكد من وجود تسعير أو نماذج مقارنة.")