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("

تحليل أسعار البنود

", 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(""" """, 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('
', 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('
', 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('
', 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('
', 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()