v3 / modules /pricing /price_analysis_component.py
EGYADMIN's picture
Upload 115 files
82676b8 verified
import streamlit as st
import pandas as pd
import numpy as np
from datetime import datetime
import time
class PriceAnalysisComponent:
"""مكون تحليل الأسعار للبنود"""
def __init__(self):
"""تهيئة مكون تحليل الأسعار"""
# تهيئة قائمة الوحدات المتاحة
self.unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"]
# تهيئة فئات التكاليف
self.cost_categories = [
"مواد",
"عمالة",
"معدات",
"مقاولي الباطن",
"مصاريف عامة",
"أرباح"
]
# تهيئة قائمة البنود وتحليل أسعارها
if 'items_price_analysis' not in st.session_state:
st.session_state.items_price_analysis = {}
def render(self):
"""عرض واجهة تحليل الأسعار"""
st.markdown("<h2 class='module-title'>تحليل أسعار البنود</h2>", unsafe_allow_html=True)
# التحقق من وجود بنود في التسعير الحالي
if 'current_pricing' not in st.session_state or 'items' not in st.session_state.current_pricing:
st.warning("ليس هناك بنود للتحليل. يرجى إنشاء تسعير أولاً.")
return
# الحصول على البنود من التسعير الحالي
items = st.session_state.current_pricing['items'].copy()
# عرض قائمة البنود
st.markdown("### قائمة البنود")
st.dataframe(items[['رقم البند', 'وصف البند', 'الوحدة', 'الكمية', 'سعر الوحدة', 'الإجمالي']],
use_container_width=True, hide_index=True)
# اختيار البند لتحليل السعر
selected_item_id = st.selectbox(
"اختر البند لتحليل السعر",
options=items['رقم البند'].tolist(),
format_func=lambda x: f"{x}: {items[items['رقم البند'] == x]['وصف البند'].values[0][:50]}..."
)
if selected_item_id:
# الحصول على البند المحدد
selected_item = items[items['رقم البند'] == selected_item_id].iloc[0]
# عرض تفاصيل البند المختار
col1, col2, col3 = st.columns(3)
with col1:
st.metric("رقم البند", selected_item['رقم البند'])
with col2:
st.metric("الكمية", f"{selected_item['الكمية']} {selected_item['الوحدة']}")
with col3:
st.metric("سعر الوحدة", f"{selected_item['سعر الوحدة']:,.2f} ريال")
st.markdown(f"**وصف البند**: {selected_item['وصف البند']}")
# إنشاء أو تحديث تحليل السعر للبند المحدد
if selected_item_id not in st.session_state.items_price_analysis:
# إنشاء تحليل سعر افتراضي
self._create_default_price_analysis(selected_item_id, selected_item)
# عرض وتحرير تحليل السعر
self._render_price_analysis_editor(selected_item_id, selected_item)
def _create_default_price_analysis(self, item_id, item):
"""إنشاء تحليل سعر افتراضي للبند"""
# إنشاء قائمة مكونات تحليل السعر
components = pd.DataFrame(columns=[
'نوع التكلفة', 'الوصف', 'الكمية', 'الوحدة', 'سعر الوحدة', 'الإجمالي'
])
# إضافة مكونات افتراضية بناءً على نوع البند
is_concrete = 'خرسان' in item['وصف البند']
is_steel = 'حديد' in item['وصف البند'] or 'تسليح' in item['وصف البند']
is_bricks = 'بلوك' in item['وصف البند'] or 'طوب' in item['وصف البند']
is_paint = 'دهان' in item['وصف البند'] or 'طلاء' in item['وصف البند']
is_insulation = 'عزل' in item['وصف البند']
# إضافة المكونات بناءً على نوع البند
if is_concrete:
# مكونات الخرسانة
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['أسمنت', 'رمل', 'حصى', 'عمال وفنيين', 'خلاطات ومعدات صب', 'مصاريف عامة', 'أرباح'],
'الكمية': [350, 0.4, 0.8, 8, 1, 1, 1],
'الوحدة': ['كجم', 'م3', 'م3', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
'سعر الوحدة': [0.5, 100, 120, 50, 500, 100, 150],
'الإجمالي': [175, 40, 96, 400, 500, 100, 150]
})
components = pd.concat([components, default_components], ignore_index=True)
elif is_steel:
# مكونات الحديد
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['حديد التسليح', 'عمال وفنيين', 'معدات ثني وتجهيز الحديد', 'مصاريف عامة', 'أرباح'],
'الكمية': [1000, 10, 1, 1, 1],
'الوحدة': ['كجم', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
'سعر الوحدة': [4.5, 50, 300, 200, 300],
'الإجمالي': [4500, 500, 300, 200, 300]
})
components = pd.concat([components, default_components], ignore_index=True)
elif is_bricks:
# مكونات البلوك
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['بلوك خرساني', 'مونة', 'عمالة بناء', 'مصاريف عامة', 'أرباح'],
'الكمية': [12.5, 0.02, 1, 1, 1],
'الوحدة': ['قطعة', 'م3', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [8, 500, 80, 15, 20],
'الإجمالي': [100, 10, 80, 15, 20]
})
components = pd.concat([components, default_components], ignore_index=True)
elif is_paint:
# مكونات الدهانات
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['دهان', 'مواد تجهيز', 'عمالة دهان', 'مصاريف عامة', 'أرباح'],
'الكمية': [0.4, 0.1, 1, 1, 1],
'الوحدة': ['لتر', 'وحدة', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [80, 20, 35, 5, 10],
'الإجمالي': [32, 2, 35, 5, 10]
})
components = pd.concat([components, default_components], ignore_index=True)
elif is_insulation:
# مكونات العزل
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['مواد عازلة', 'مواد لاصقة', 'عمالة تركيب', 'مصاريف عامة', 'أرباح'],
'الكمية': [1.1, 0.2, 1, 1, 1],
'الوحدة': ['م2', 'كجم', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [60, 30, 25, 10, 15],
'الإجمالي': [66, 6, 25, 10, 15]
})
components = pd.concat([components, default_components], ignore_index=True)
else:
# مكونات عامة افتراضية
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['مواد أساسية', 'عمالة', 'معدات ومعد مساعدة', 'مصاريف عامة', 'أرباح'],
'الكمية': [1, 1, 1, 1, 1],
'الوحدة': [item['الوحدة'], 'وحدة', 'وحدة', 'وحدة', 'وحدة'],
'سعر الوحدة': [
item['سعر الوحدة'] * 0.6,
item['سعر الوحدة'] * 0.2,
item['سعر الوحدة'] * 0.1,
item['سعر الوحدة'] * 0.05,
item['سعر الوحدة'] * 0.05
],
'الإجمالي': [
item['سعر الوحدة'] * 0.6,
item['سعر الوحدة'] * 0.2,
item['سعر الوحدة'] * 0.1,
item['سعر الوحدة'] * 0.05,
item['سعر الوحدة'] * 0.05
]
})
components = pd.concat([components, default_components], ignore_index=True)
# حفظ تحليل السعر للبند
st.session_state.items_price_analysis[item_id] = components
def _render_price_analysis_editor(self, item_id, item):
"""عرض محرر تحليل السعر للبند"""
st.markdown("### تحليل السعر")
# الحصول على مكونات تحليل السعر
components = st.session_state.items_price_analysis[item_id]
# عرض تحليل السعر في محرر بيانات
st.markdown("#### مكونات السعر")
edited_components = st.data_editor(
components,
use_container_width=True,
hide_index=True,
num_rows="dynamic",
column_config={
'نوع التكلفة': st.column_config.SelectboxColumn(
'نوع التكلفة',
help='فئة التكلفة',
options=self.cost_categories
),
'الوحدة': st.column_config.SelectboxColumn(
'الوحدة',
help='وحدة القياس',
options=self.unit_options + ["وحدة", "ساعة", "يوم"]
),
'الكمية': st.column_config.NumberColumn(
'الكمية',
help='الكمية',
min_value=0.0,
format="%.2f"
),
'سعر الوحدة': st.column_config.NumberColumn(
'سعر الوحدة',
help='سعر الوحدة',
min_value=0.0,
format="%.2f"
),
'الإجمالي': st.column_config.NumberColumn(
'الإجمالي',
help='الإجمالي',
min_value=0.0,
format="%.2f"
)
}
)
# إعادة حساب الإجمالي لكل مكون
edited_components['الإجمالي'] = edited_components['الكمية'] * edited_components['سعر الوحدة']
# حفظ التعديلات
st.session_state.items_price_analysis[item_id] = edited_components
# حساب إجمالي تحليل السعر
total_analysis_price = edited_components['الإجمالي'].sum()
unit_price_from_analysis = total_analysis_price / item['الكمية'] if item['الكمية'] > 0 else 0
# عرض ملخص تحليل السعر
st.markdown("#### ملخص تحليل السعر")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("إجمالي تكلفة البند من التحليل", f"{total_analysis_price:,.2f} ريال")
with col2:
st.metric("سعر الوحدة من التحليل", f"{unit_price_from_analysis:,.2f} ريال")
with col3:
# المقارنة مع السعر الأصلي
diff = unit_price_from_analysis - item['سعر الوحدة']
st.metric(
"الفرق عن السعر الأصلي",
f"{diff:,.2f} ريال",
delta=f"{(diff/item['سعر الوحدة']*100) if item['سعر الوحدة'] > 0 else 0:.1f}%"
)
# تحليل توزيع التكاليف حسب الفئة
cost_by_category = edited_components.groupby('نوع التكلفة')['الإجمالي'].sum().reset_index()
# عرض مخطط توزيع التكاليف
st.markdown("#### توزيع التكاليف حسب الفئة")
# عرض توزيع التكاليف في جدول
distribution_df = pd.DataFrame({
'نوع التكلفة': cost_by_category['نوع التكلفة'],
'القيمة': cost_by_category['الإجمالي'],
'النسبة المئوية': (cost_by_category['الإجمالي'] / total_analysis_price * 100).round(2)
})
st.dataframe(
distribution_df,
use_container_width=True,
hide_index=True,
column_config={
'القيمة': st.column_config.NumberColumn(
'القيمة',
help='القيمة',
format="%.2f"
),
'النسبة المئوية': st.column_config.ProgressColumn(
'النسبة المئوية',
help='النسبة المئوية',
format="%.2f%%",
min_value=0,
max_value=100
)
}
)
# أزرار الإجراءات
col1, col2, col3 = st.columns(3)
with col1:
if st.button("تحديث سعر البند", use_container_width=True):
# تحديث سعر البند بناءً على تحليل السعر
items = st.session_state.current_pricing['items'].copy()
item_index = items[items['رقم البند'] == item_id].index[0]
# تحديث سعر الوحدة والإجمالي
items.at[item_index, 'سعر الوحدة'] = unit_price_from_analysis
items.at[item_index, 'الإجمالي'] = unit_price_from_analysis * items.at[item_index, 'الكمية']
# حفظ التعديلات في التسعير الحالي
st.session_state.current_pricing['items'] = items
st.success(f"تم تحديث سعر البند بناءً على تحليل السعر: {unit_price_from_analysis:,.2f} ريال")
time.sleep(0.5)
st.rerun()
with col2:
if st.button("تصدير تحليل السعر", use_container_width=True):
st.success("تم إرسال تحليل السعر للتصدير بنجاح!")
with col3:
if st.button("مسح تحليل السعر", use_container_width=True):
# حذف تحليل السعر للبند
if item_id in st.session_state.items_price_analysis:
del st.session_state.items_price_analysis[item_id]
st.warning("تم مسح تحليل السعر للبند")
time.sleep(0.5)
st.rerun()
def add_to_pricing_app(self, pricing_app):
"""إضافة مكون تحليل الأسعار إلى تطبيق التسعير"""
# إضافة تبويب جديد
if not hasattr(pricing_app, 'tabs'):
pricing_app.tabs = []
if len(pricing_app.tabs) == 4: # إذا كان هناك 4 تبويبات فقط
pricing_app.tabs.append("تحليل أسعار البنود")
# إضافة دالة العرض
pricing_app._render_price_analysis_tab = self.render
def render_integrated_item_input():
"""عرض واجهة إدخال البنود مع تحليل السعر المتكامل"""
# ضبط 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;
}
.pricing-analysis-container {
border: 1px solid #e0e0e0;
border-radius: 10px;
padding: 10px;
margin-top: 10px;
background-color: #f9f9f9;
}
</style>
""", unsafe_allow_html=True)
# تهيئة قائمة الوحدات المتاحة
unit_options = ["م3", "م2", "طن", "متر طولي", "قطعة", "كجم", "لتر"]
# تهيئة فئات التكاليف
cost_categories = [
"مواد",
"عمالة",
"معدات",
"مقاولي الباطن",
"مصاريف عامة",
"أرباح"
]
# إنشاء جدول البنود اذا لم يكن موجوداً
if 'manual_items' not in st.session_state:
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
# إنشاء جدول تحليل الأسعار اذا لم يكن موجوداً
if 'items_price_analysis' not in st.session_state:
st.session_state.items_price_analysis = {}
# عرض واجهة إدخال البنود
st.markdown("### إدخال تفاصيل البنود مع تحليل الأسعار")
# عرض البنود الحالية كجدول للعرض
st.markdown("### جدول البنود الحالية")
st.dataframe(st.session_state.manual_items, use_container_width=True, hide_index=True)
# التبويبات لإضافة بند جديد أو تعديل بند
tabs = st.tabs(["إضافة بند جديد", "تعديل بند حالي"])
with tabs[0]: # إضافة بند جديد
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_id")
new_desc = st.text_area("وصف البند", value="", key="new_desc")
with col2:
new_unit = st.selectbox("الوحدة", options=unit_options, key="new_unit")
new_qty = st.number_input("الكمية", value=0.0, min_value=0.0, format="%.2f", key="new_qty")
# إنشاء تحليل السعر للبند الجديد
st.markdown('<div class="pricing-analysis-container">', unsafe_allow_html=True)
st.markdown("#### تحليل سعر البند")
# التعرف التلقائي على نوع البند من الوصف
is_concrete = False
is_steel = False
is_bricks = False
is_paint = False
is_insulation = False
if new_desc:
is_concrete = 'خرسان' in new_desc
is_steel = 'حديد' in new_desc or 'تسليح' in new_desc
is_bricks = 'بلوك' in new_desc or 'طوب' in new_desc
is_paint = 'دهان' in new_desc or 'طلاء' in new_desc
is_insulation = 'عزل' in new_desc
# تلميح للمستخدم عن التعرف التلقائي
if any([is_concrete, is_steel, is_bricks, is_paint, is_insulation]):
detected_type = ""
if is_concrete:
detected_type = "أعمال خرسانة"
elif is_steel:
detected_type = "أعمال حديد"
elif is_bricks:
detected_type = "أعمال بلوك"
elif is_paint:
detected_type = "أعمال دهانات"
elif is_insulation:
detected_type = "أعمال عزل"
st.info(f"تم التعرف تلقائياً على نوع البند: {detected_type}")
# إنشاء مصفوفة فارغة لمكونات البند
if 'new_components' not in st.session_state:
# إنشاء DataFrame فارغ
new_components = pd.DataFrame(columns=[
'نوع التكلفة', 'الوصف', 'الكمية', 'الوحدة', 'سعر الوحدة', 'الإجمالي'
])
# إضافة مكونات افتراضية بناءً على نوع البند
if is_concrete:
# مكونات الخرسانة
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['أسمنت', 'رمل', 'حصى', 'عمال وفنيين', 'خلاطات ومعدات صب', 'مصاريف عامة', 'أرباح'],
'الكمية': [350, 0.4, 0.8, 8, 1, 1, 1],
'الوحدة': ['كجم', 'م3', 'م3', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
'سعر الوحدة': [0.5, 100, 120, 50, 500, 100, 150],
'الإجمالي': [175, 40, 96, 400, 500, 100, 150]
})
new_components = pd.concat([new_components, default_components], ignore_index=True)
elif is_steel:
# مكونات الحديد
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['حديد التسليح', 'عمال وفنيين', 'معدات ثني وتجهيز الحديد', 'مصاريف عامة', 'أرباح'],
'الكمية': [1000, 10, 1, 1, 1],
'الوحدة': ['كجم', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
'سعر الوحدة': [4.5, 50, 300, 200, 300],
'الإجمالي': [4500, 500, 300, 200, 300]
})
new_components = pd.concat([new_components, default_components], ignore_index=True)
elif is_bricks:
# مكونات البلوك
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['بلوك خرساني', 'مونة', 'عمالة بناء', 'مصاريف عامة', 'أرباح'],
'الكمية': [12.5, 0.02, 1, 1, 1],
'الوحدة': ['قطعة', 'م3', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [8, 500, 80, 15, 20],
'الإجمالي': [100, 10, 80, 15, 20]
})
new_components = pd.concat([new_components, default_components], ignore_index=True)
elif is_paint:
# مكونات الدهانات
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['دهان', 'مواد تجهيز', 'عمالة دهان', 'مصاريف عامة', 'أرباح'],
'الكمية': [0.4, 0.1, 1, 1, 1],
'الوحدة': ['لتر', 'وحدة', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [80, 20, 35, 5, 10],
'الإجمالي': [32, 2, 35, 5, 10]
})
new_components = pd.concat([new_components, default_components], ignore_index=True)
elif is_insulation:
# مكونات العزل
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['مواد عازلة', 'مواد لاصقة', 'عمالة تركيب', 'مصاريف عامة', 'أرباح'],
'الكمية': [1.1, 0.2, 1, 1, 1],
'الوحدة': ['م2', 'كجم', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [60, 30, 25, 10, 15],
'الإجمالي': [66, 6, 25, 10, 15]
})
new_components = pd.concat([new_components, default_components], ignore_index=True)
else:
# مكونات عامة افتراضية
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['مواد أساسية', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الكمية': [1, 1, 1, 1, 1],
'الوحدة': [new_unit if new_unit else 'وحدة', 'وحدة', 'وحدة', 'وحدة', 'وحدة'],
'سعر الوحدة': [100, 50, 30, 20, 20],
'الإجمالي': [100, 50, 30, 20, 20]
})
new_components = pd.concat([new_components, default_components], ignore_index=True)
st.session_state.new_components = new_components
# عرض وتحرير مكونات تحليل السعر
edited_components = st.data_editor(
st.session_state.new_components,
use_container_width=True,
hide_index=True,
num_rows="dynamic",
column_config={
'نوع التكلفة': st.column_config.SelectboxColumn(
'نوع التكلفة',
help='فئة التكلفة',
options=cost_categories
),
'الوحدة': st.column_config.SelectboxColumn(
'الوحدة',
help='وحدة القياس',
options=unit_options + ["وحدة", "ساعة", "يوم"]
),
'الكمية': st.column_config.NumberColumn(
'الكمية',
help='الكمية',
min_value=0.0,
format="%.2f"
),
'سعر الوحدة': st.column_config.NumberColumn(
'سعر الوحدة',
help='سعر الوحدة',
min_value=0.0,
format="%.2f"
),
'الإجمالي': st.column_config.NumberColumn(
'الإجمالي',
help='الإجمالي',
min_value=0.0,
format="%.2f"
)
}
)
# إعادة حساب الإجمالي لكل مكون
edited_components['الإجمالي'] = edited_components['الكمية'] * edited_components['سعر الوحدة']
# حفظ التعديلات
st.session_state.new_components = edited_components
# حساب إجمالي تحليل السعر
total_analysis_price = edited_components['الإجمالي'].sum()
unit_price_from_analysis = total_analysis_price / new_qty if new_qty > 0 else 0
# عرض ملخص تحليل السعر
st.markdown("#### ملخص تحليل السعر")
col1, col2 = st.columns(2)
with col1:
st.metric("إجمالي تكلفة البند من التحليل", f"{total_analysis_price:,.2f} ريال")
with col2:
st.metric("سعر الوحدة المحسوب", f"{unit_price_from_analysis:,.2f} ريال")
st.markdown('</div>', unsafe_allow_html=True)
# استخدام السعر المحسوب
use_calculated_price = st.checkbox("استخدام السعر المحسوب من التحليل", value=True)
# تحديد سعر الوحدة النهائي
if use_calculated_price and new_qty > 0:
new_price = unit_price_from_analysis
else:
new_price = st.number_input("سعر الوحدة", value=unit_price_from_analysis if new_qty > 0 else 0.0, min_value=0.0, format="%.2f", key="new_price")
# حساب الإجمالي
new_total = new_qty * new_price
st.info(f"إجمالي البند الجديد: {new_total:,.2f} ريال")
# مقارنة السعر المدخل مع السعر المحسوب
if not use_calculated_price and new_qty > 0 and unit_price_from_analysis > 0:
price_diff = new_price - unit_price_from_analysis
diff_percentage = (price_diff / unit_price_from_analysis) * 100
if abs(diff_percentage) > 5: # إذا كان الفرق أكبر من 5%
if diff_percentage > 0:
st.warning(f"السعر المدخل أعلى من السعر المحسوب بنسبة {diff_percentage:.2f}%")
else:
st.warning(f"السعر المدخل أقل من السعر المحسوب بنسبة {abs(diff_percentage):.2f}%")
# زر إضافة البند
if st.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.session_state.items_price_analysis[new_id] = st.session_state.new_components.copy()
# إعادة تهيئة مكونات البند الجديد
if 'new_components' in st.session_state:
del st.session_state.new_components
st.success("تم إضافة البند وتحليل السعر بنجاح!")
time.sleep(0.5)
st.rerun()
else:
st.error("يرجى ملء جميع الحقول المطلوبة: رقم البند، الوصف، والكمية يجب أن تكون أكبر من صفر.")
with tabs[1]: # تعديل بند حالي
st.markdown("### تعديل بند حالي مع تحليل السعر")
# اختيار البند للتعديل
edit_item_id = 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]}..."
)
if edit_item_id:
# الحصول على مؤشر الصف للبند المحدد
idx = st.session_state.manual_items[st.session_state.manual_items['رقم البند'] == edit_item_id].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")
# إنشاء أو تحرير تحليل السعر للبند
st.markdown('<div class="pricing-analysis-container">', unsafe_allow_html=True)
st.markdown("#### تحليل سعر البند")
# التحقق مما إذا كان البند له تحليل سعر محفوظ
if edit_item_id in st.session_state.items_price_analysis:
# استخدام تحليل السعر المحفوظ
components = st.session_state.items_price_analysis[edit_item_id]
else:
# إنشاء تحليل سعر افتراضي
components = pd.DataFrame(columns=[
'نوع التكلفة', 'الوصف', 'الكمية', 'الوحدة', 'سعر الوحدة', 'الإجمالي'
])
# فحص نوع البند من الوصف
is_concrete = 'خرسان' in row['وصف البند']
is_steel = 'حديد' in row['وصف البند'] or 'تسليح' in row['وصف البند']
is_bricks = 'بلوك' in row['وصف البند'] or 'طوب' in row['وصف البند']
is_paint = 'دهان' in row['وصف البند'] or 'طلاء' in row['وصف البند']
is_insulation = 'عزل' in row['وصف البند']
# إضافة مكونات افتراضية بناءً على نوع البند
if is_concrete:
# مكونات الخرسانة
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['أسمنت', 'رمل', 'حصى', 'عمال وفنيين', 'خلاطات ومعدات صب', 'مصاريف عامة', 'أرباح'],
'الكمية': [350, 0.4, 0.8, 8, 1, 1, 1],
'الوحدة': ['كجم', 'م3', 'م3', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
'سعر الوحدة': [0.5, 100, 120, 50, 500, 100, 150],
'الإجمالي': [175, 40, 96, 400, 500, 100, 150]
})
components = pd.concat([components, default_components], ignore_index=True)
elif is_steel:
# مكونات الحديد
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['حديد التسليح', 'عمال وفنيين', 'معدات ثني وتجهيز الحديد', 'مصاريف عامة', 'أرباح'],
'الكمية': [1000, 10, 1, 1, 1],
'الوحدة': ['كجم', 'ساعة', 'يوم', 'وحدة', 'وحدة'],
'سعر الوحدة': [4.5, 50, 300, 200, 300],
'الإجمالي': [4500, 500, 300, 200, 300]
})
components = pd.concat([components, default_components], ignore_index=True)
elif is_bricks:
# مكونات البلوك
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['بلوك خرساني', 'مونة', 'عمالة بناء', 'مصاريف عامة', 'أرباح'],
'الكمية': [12.5, 0.02, 1, 1, 1],
'الوحدة': ['قطعة', 'م3', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [8, 500, 80, 15, 20],
'الإجمالي': [100, 10, 80, 15, 20]
})
components = pd.concat([components, default_components], ignore_index=True)
elif is_paint:
# مكونات الدهانات
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['دهان', 'مواد تجهيز', 'عمالة دهان', 'مصاريف عامة', 'أرباح'],
'الكمية': [0.4, 0.1, 1, 1, 1],
'الوحدة': ['لتر', 'وحدة', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [80, 20, 35, 5, 10],
'الإجمالي': [32, 2, 35, 5, 10]
})
components = pd.concat([components, default_components], ignore_index=True)
elif is_insulation:
# مكونات العزل
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'مواد', 'عمالة', 'مصاريف عامة', 'أرباح'],
'الوصف': ['مواد عازلة', 'مواد لاصقة', 'عمالة تركيب', 'مصاريف عامة', 'أرباح'],
'الكمية': [1.1, 0.2, 1, 1, 1],
'الوحدة': ['م2', 'كجم', 'م2', 'وحدة', 'وحدة'],
'سعر الوحدة': [60, 30, 25, 10, 15],
'الإجمالي': [66, 6, 25, 10, 15]
})
components = pd.concat([components, default_components], ignore_index=True)
else:
# مكونات عامة افتراضية
default_components = pd.DataFrame({
'نوع التكلفة': ['مواد', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الوصف': ['مواد أساسية', 'عمالة', 'معدات', 'مصاريف عامة', 'أرباح'],
'الكمية': [1, 1, 1, 1, 1],
'الوحدة': [row['الوحدة'], 'وحدة', 'وحدة', 'وحدة', 'وحدة'],
'سعر الوحدة': [
row['سعر الوحدة'] * 0.6,
row['سعر الوحدة'] * 0.2,
row['سعر الوحدة'] * 0.1,
row['سعر الوحدة'] * 0.05,
row['سعر الوحدة'] * 0.05
],
'الإجمالي': [
row['سعر الوحدة'] * 0.6,
row['سعر الوحدة'] * 0.2,
row['سعر الوحدة'] * 0.1,
row['سعر الوحدة'] * 0.05,
row['سعر الوحدة'] * 0.05
]
})
components = pd.concat([components, default_components], ignore_index=True)
# حفظ تحليل السعر
st.session_state.items_price_analysis[edit_item_id] = components
# عرض وتحرير مكونات تحليل السعر
edited_components = st.data_editor(
components,
use_container_width=True,
hide_index=True,
num_rows="dynamic",
column_config={
'نوع التكلفة': st.column_config.SelectboxColumn(
'نوع التكلفة',
help='فئة التكلفة',
options=cost_categories
),
'الوحدة': st.column_config.SelectboxColumn(
'الوحدة',
help='وحدة القياس',
options=unit_options + ["وحدة", "ساعة", "يوم"]
),
'الكمية': st.column_config.NumberColumn(
'الكمية',
help='الكمية',
min_value=0.0,
format="%.2f"
),
'سعر الوحدة': st.column_config.NumberColumn(
'سعر الوحدة',
help='سعر الوحدة',
min_value=0.0,
format="%.2f"
),
'الإجمالي': st.column_config.NumberColumn(
'الإجمالي',
help='الإجمالي',
min_value=0.0,
format="%.2f"
)
}
)
# إعادة حساب الإجمالي لكل مكون
edited_components['الإجمالي'] = edited_components['الكمية'] * edited_components['سعر الوحدة']
# حفظ التعديلات
st.session_state.items_price_analysis[edit_item_id] = edited_components
# حساب إجمالي تحليل السعر
total_analysis_price = edited_components['الإجمالي'].sum()
unit_price_from_analysis = total_analysis_price / edited_qty if edited_qty > 0 else 0
# عرض ملخص تحليل السعر
st.markdown("#### ملخص تحليل السعر")
col1, col2 = st.columns(2)
with col1:
st.metric("إجمالي تكلفة البند من التحليل", f"{total_analysis_price:,.2f} ريال")
with col2:
st.metric("سعر الوحدة المحسوب", f"{unit_price_from_analysis:,.2f} ريال")
st.markdown('</div>', unsafe_allow_html=True)
# استخدام السعر المحسوب
use_calculated_price = st.checkbox("استخدام السعر المحسوب من التحليل", value=True, key="use_calc_edit")
# تحديد سعر الوحدة النهائي
if use_calculated_price and edited_qty > 0:
edited_price = unit_price_from_analysis
else:
edited_price = st.number_input(
"سعر الوحدة (تعديل)",
value=unit_price_from_analysis if edited_qty > 0 and unit_price_from_analysis > 0 else float(row['سعر الوحدة']),
min_value=0.0,
format="%.2f",
key="edit_price"
)
# حساب الإجمالي
edited_total = edited_qty * edited_price
st.info(f"إجمالي البند بعد التعديل: {edited_total:,.2f} ريال")
# مقارنة السعر المدخل مع السعر المحسوب
if not use_calculated_price and edited_qty > 0 and unit_price_from_analysis > 0:
price_diff = edited_price - unit_price_from_analysis
diff_percentage = (price_diff / unit_price_from_analysis) * 100
if abs(diff_percentage) > 5: # إذا كان الفرق أكبر من 5%
if diff_percentage > 0:
st.warning(f"السعر المدخل أعلى من السعر المحسوب بنسبة {diff_percentage:.2f}%")
else:
st.warning(f"السعر المدخل أقل من السعر المحسوب بنسبة {abs(diff_percentage):.2f}%")
# أزرار الإجراءات
col1, col2, col3 = st.columns(3)
with col1:
if st.button("حفظ التعديلات", use_container_width=True):
# التحقق من صحة البيانات
if edited_id and edited_desc and edited_qty > 0:
# التحقق من تغيير رقم البند
if edited_id != edit_item_id:
# نقل تحليل السعر إلى الرقم الجديد
st.session_state.items_price_analysis[edited_id] = st.session_state.items_price_analysis.pop(edit_item_id)
# تحديث البند
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("تم تحديث البند وتحليل السعر بنجاح!")
time.sleep(0.5)
st.rerun()
else:
st.error("يرجى ملء جميع الحقول المطلوبة: رقم البند، الوصف، والكمية يجب أن تكون أكبر من صفر.")
with col2:
if st.button("استعادة القيم الأصلية", use_container_width=True):
# إعادة تحميل الصفحة لاستعادة القيم الأصلية
st.rerun()
with col3:
if st.button("حذف هذا البند", use_container_width=True):
# حذف تحليل السعر للبند
if edit_item_id in st.session_state.items_price_analysis:
del st.session_state.items_price_analysis[edit_item_id]
# حذف البند
st.session_state.manual_items = st.session_state.manual_items.drop(idx).reset_index(drop=True)
st.warning("تم حذف البند وتحليل السعر!")
time.sleep(0.5)
st.rerun()