EGYADMIN commited on
Commit
4500ed3
·
verified ·
1 Parent(s): e89d05b

Upload 15 files

Browse files
app.py CHANGED
@@ -1,174 +1,38 @@
1
  import streamlit as st
2
  import sys
3
- from pathlib import Path
4
-
5
- # إضافة مسار المشروع للنظام
6
- sys.path.append(str(Path(__file__).parent.parent))
7
-
8
- # استيراد مدير التكوين
9
- from config_manager import ConfigManager
10
-
11
- # استيراد الوحدات
12
- from modules.document_analysis.document_app import DocumentAnalysisApp
13
- from modules.pricing.pricing_app import PricingApp
14
- from modules.resources.resources_app import ResourcesApp
15
- from modules.risk_analysis.risk_analyzer import RiskAnalysisApp
16
- from modules.project_management.project_management_app import ProjectsApp
17
- from modules.maps.maps_app import MapsApp
18
- from modules.notifications.notifications_app import NotificationsApp
19
- from modules.document_comparison.document_comparison_app import DocumentComparisonApp
20
- from modules.translation.translation_app import TranslationApp
21
- from modules.ai_assistant.ai_app import AIAssistantApp
22
- from modules.data_analysis.data_analysis_app import DataAnalysisApp
23
- from styling.enhanced_ui import UIEnhancer
24
-
25
- # تهيئة مدير التكوين
26
- config_manager = ConfigManager()
27
-
28
- # تكوين الصفحة
29
- config_manager.set_page_config_if_needed(
30
- page_title="نظام تحليل المناقصات",
31
- page_icon="📊",
32
- layout="wide",
33
- initial_sidebar_state="expanded",
34
- menu_items={
35
- 'Get Help': 'https://www.example.com/help',
36
- 'Report a bug': "https://www.example.com/bug",
37
- 'About': "### نظام تحليل المناقصات\nالإصدار 2.0.0"
38
- }
39
- )
40
-
41
- # تطبيق التنسيق العام
42
- ui_enhancer = UIEnhancer(page_title="نظام تحليل المناقصات", page_icon="📊")
43
- ui_enhancer.apply_global_styles()
44
-
45
- # إنشاء قائمة العناصر
46
- menu_items = [
47
- {"name": "لوحة المعلومات", "icon": "house"},
48
- {"name": "المناقصات والعقود", "icon": "file-text"},
49
- {"name": "تحليل المستندات", "icon": "file-earmark-text"},
50
- {"name": "نظام التسعير", "icon": "calculator"},
51
- {"name": "حاسبة تكاليف البناء", "icon": "building"},
52
- {"name": "الموارد والتكاليف", "icon": "people"},
53
- {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
54
- {"name": "إدارة المشاريع", "icon": "kanban"},
55
- {"name": "الخرائط والمواقع", "icon": "geo-alt"},
56
- {"name": "الجدول الزمني", "icon": "calendar3"},
57
- {"name": "الإشعارات", "icon": "bell"},
58
- {"name": "مقارنة المستندات", "icon": "files"},
59
- {"name": "الترجمة", "icon": "translate"},
60
- {"name": "المساعد الذكي", "icon": "robot"},
61
- {"name": "تحليل البيانات", "icon": "bar-chart"},
62
- {"name": "الإعدادات", "icon": "gear"}
63
- ]
64
-
65
- # إنشاء الشريط الجانبي
66
- selected = ui_enhancer.create_sidebar(menu_items)
67
-
68
- # تحديد الوحدة المطلوبة بناءً على اختيار المستخدم
69
- if selected == "لوحة المعلومات":
70
- ui_enhancer.create_header("لوحة المعلومات", "نظرة عامة على المناقصات والمشاريع")
71
-
72
- # عرض لوحة المعلومات
73
- col1, col2, col3 = st.columns(3)
74
-
75
- with col1:
76
- ui_enhancer.create_metric_card("المناقصات النشطة", "12", "+3", ui_enhancer.COLORS['primary'])
77
-
78
- with col2:
79
- ui_enhancer.create_metric_card("المشاريع قيد التنفيذ", "8", "+1", ui_enhancer.COLORS['success'])
80
-
81
- with col3:
82
- ui_enhancer.create_metric_card("المناقصات المقدمة", "24", "+5", ui_enhancer.COLORS['info'])
83
-
84
- # عرض الإشعارات الأخيرة
85
- st.markdown("### الإشعارات الأخيرة")
86
-
87
- notifications = [
88
- {"title": "موعد تقديم مناقصة", "project": "إنشاء مبنى مستشفى الولادة والأطفال", "date": "2025-04-05", "priority": "عالية"},
89
- {"title": "تحديث مستندات", "project": "صيانة وتطوير طريق الملك عبدالله", "date": "2025-03-28", "priority": "متوسطة"},
90
- {"title": "اجتماع مراجعة التسعير", "project": "إنشاء محطة معالجة مياه الصرف الصحي", "date": "2025-03-25", "priority": "عالية"}
91
- ]
92
-
93
- for notification in notifications:
94
- with st.container():
95
- col1, col2 = st.columns([4, 1])
96
- with col1:
97
- st.markdown(f"**{notification['title']}** - {notification['project']}")
98
- st.caption(f"التاريخ: {notification['date']} | الأولوية: {notification['priority']}")
99
- with col2:
100
- st.button("عرض", key=f"view_{notification['title']}")
101
- st.divider()
102
-
103
- elif selected == "تحليل المستندات":
104
- document_app = DocumentAnalysisApp()
105
- document_app.run()
106
-
107
- elif selected == "نظام التسعير":
108
- pricing_app = PricingApp()
109
- pricing_app.run()
110
-
111
- elif selected == "الموارد والتكاليف":
112
- resources_app = ResourcesApp()
113
- resources_app.run()
114
-
115
- elif selected == "تحليل المخاطر":
116
- risk_app = RiskAnalysisApp()
117
- risk_app.run()
118
-
119
- elif selected == "إدارة المشاريع":
120
- projects_app = ProjectsApp()
121
- projects_app.run()
122
-
123
- elif selected == "الخرائط والمواقع":
124
- maps_app = MapsApp()
125
- maps_app.run()
126
-
127
- elif selected == "الإشعارات":
128
- notifications_app = NotificationsApp()
129
- notifications_app.run()
130
-
131
- elif selected == "مقارنة المستندات":
132
- document_comparison_app = DocumentComparisonApp()
133
- document_comparison_app.run()
134
-
135
- elif selected == "الترجمة":
136
- translation_app = TranslationApp()
137
- translation_app.run()
138
-
139
- elif selected == "المساعد الذكي":
140
- ai_app = AIAssistantApp()
141
- ai_app.run()
142
-
143
- elif selected == "تحليل البيانات":
144
- data_analysis_app = DataAnalysisApp()
145
- data_analysis_app.run()
146
-
147
- elif selected == "الإعدادات":
148
- ui_enhancer.create_header("الإعدادات", "إعدادات النظام والحساب")
149
-
150
- # عرض إعدادات النظام
151
- st.markdown("### إعدادات النظام")
152
-
153
- tabs = st.tabs(["إعدادات عامة", "الواجهة", "الأمان", "مفاتيح API"])
154
-
155
- with tabs[0]:
156
- st.checkbox("تفعيل الإشعارات", value=True)
157
- st.checkbox("حفظ تلقائي للبيانات", value=True)
158
- st.selectbox("اللغة", ["العربية", "English"])
159
- st.selectbox("المنطقة الزمنية", ["توقيت الرياض (GMT+3)", "توقيت جرينتش (GMT)"])
160
-
161
- with tabs[1]:
162
- st.radio("النمط", ["فاتح", "داكن", "تلقائي (حسب نظام التشغيل)"])
163
- st.slider("حجم الخط", 12, 20, 16)
164
- st.color_picker("لون التمييز", "#1E88E5")
165
-
166
- with tabs[2]:
167
- st.checkbox("تفعيل المصادقة الثنائية", value=False)
168
- st.number_input("مدة الجلسة (دقائق)", min_value=5, max_value=120, value=30)
169
- st.button("تغيير كلمة المرور")
170
-
171
- with tabs[3]:
172
- st.text_input("مفتاح OpenAI API", type="password")
173
- st.text_input("مفتاح Google Maps API", type="password")
174
- st.button("حفظ مفاتيح API")
 
1
  import streamlit as st
2
  import sys
3
+ import os
4
+ import pandas as pd
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ import plotly.express as px
9
+ import plotly.graph_objects as go
10
+
11
+ # إضافة مسار الوحدات
12
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
13
+
14
+ # استيراد التطبيق المتكامل
15
+ from pricing_system.integrated_app import IntegratedApp
16
+
17
+ def main():
18
+ """
19
+ الدالة الرئيسية لتشغيل التطبيق
20
+ """
21
+ # تعيين عنوان الصفحة وأيقونة التطبيق
22
+ st.set_page_config(
23
+ page_title="نظام تحليل المناقصات",
24
+ page_icon="📊",
25
+ layout="wide",
26
+ initial_sidebar_state="expanded"
27
+ )
28
+
29
+ # تطبيق الأنماط الموحدة
30
+ with open("pricing_system/static/css/unified_style.css") as f:
31
+ st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
32
+
33
+ # إنشاء وتشغيل التطبيق المتكامل
34
+ app = IntegratedApp()
35
+ app.run()
36
+
37
+ if __name__ == "__main__":
38
+ main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fixed_pricing_app.py ADDED
@@ -0,0 +1,544 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وحدة التسعير - التطبيق الرئيسي
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ import plotly.express as px
10
+ import plotly.graph_objects as go
11
+ from datetime import datetime
12
+ import time
13
+ import io
14
+ import os
15
+ import json
16
+ import base64
17
+ from pathlib import Path
18
+
19
+ class PricingApp:
20
+ """وحدة التسعير"""
21
+
22
+ def __init__(self):
23
+ """تهيئة وحدة التسعير"""
24
+
25
+ # تهيئة حالة الجلسة
26
+ if 'bill_of_quantities' not in st.session_state:
27
+ st.session_state.bill_of_quantities = [
28
+ {
29
+ 'id': 1,
30
+ 'code': 'A-001',
31
+ 'description': 'أعمال الحفر والردم',
32
+ 'unit': 'م3',
33
+ 'quantity': 1500,
34
+ 'unit_price': 45,
35
+ 'total_price': 67500,
36
+ 'category': 'أعمال ترابية'
37
+ },
38
+ {
39
+ 'id': 2,
40
+ 'code': 'A-002',
41
+ 'description': 'توريد وصب خرسانة عادية',
42
+ 'unit': 'م3',
43
+ 'quantity': 250,
44
+ 'unit_price': 350,
45
+ 'total_price': 87500,
46
+ 'category': 'أعمال خرسانية'
47
+ },
48
+ {
49
+ 'id': 3,
50
+ 'code': 'A-003',
51
+ 'description': 'توريد وصب خرسانة مسلحة للأساسات',
52
+ 'unit': 'م3',
53
+ 'quantity': 180,
54
+ 'unit_price': 450,
55
+ 'total_price': 81000,
56
+ 'category': 'أعمال خرسانية'
57
+ },
58
+ {
59
+ 'id': 4,
60
+ 'code': 'A-004',
61
+ 'description': 'توريد وصب خرسانة مسلحة للأعمدة',
62
+ 'unit': 'م3',
63
+ 'quantity': 120,
64
+ 'unit_price': 500,
65
+ 'total_price': 60000,
66
+ 'category': 'أعمال خرسانية'
67
+ },
68
+ {
69
+ 'id': 5,
70
+ 'code': 'A-005',
71
+ 'description': 'توريد وتركيب حديد تسليح',
72
+ 'unit': 'طن',
73
+ 'quantity': 45,
74
+ 'unit_price': 3000,
75
+ 'total_price': 135000,
76
+ 'category': 'أعمال حديد'
77
+ },
78
+ {
79
+ 'id': 6,
80
+ 'code': 'A-006',
81
+ 'description': 'توريد وبناء طابوق',
82
+ 'unit': 'م2',
83
+ 'quantity': 1200,
84
+ 'unit_price': 45,
85
+ 'total_price': 54000,
86
+ 'category': 'أعمال بناء'
87
+ },
88
+ {
89
+ 'id': 7,
90
+ 'code': 'A-007',
91
+ 'description': 'أعمال اللياسة والتشطيبات',
92
+ 'unit': 'م2',
93
+ 'quantity': 2400,
94
+ 'unit_price': 35,
95
+ 'total_price': 84000,
96
+ 'category': 'أعمال تشطيبات'
97
+ },
98
+ {
99
+ 'id': 8,
100
+ 'code': 'A-008',
101
+ 'description': 'أعمال الدهانات',
102
+ 'unit': 'م2',
103
+ 'quantity': 2400,
104
+ 'unit_price': 25,
105
+ 'total_price': 60000,
106
+ 'category': 'أعمال تشطيبات'
107
+ },
108
+ {
109
+ 'id': 9,
110
+ 'code': 'A-009',
111
+ 'description': 'توريد وتركيب أبواب خشبية',
112
+ 'unit': 'عدد',
113
+ 'quantity': 24,
114
+ 'unit_price': 750,
115
+ 'total_price': 18000,
116
+ 'category': 'أعمال نجارة'
117
+ },
118
+ {
119
+ 'id': 10,
120
+ 'code': 'A-010',
121
+ 'description': 'توريد وتركيب نوافذ ألمنيوم',
122
+ 'unit': 'م2',
123
+ 'quantity': 120,
124
+ 'unit_price': 350,
125
+ 'total_price': 42000,
126
+ 'category': 'أعمال ألمنيوم'
127
+ }
128
+ ]
129
+
130
+ if 'cost_analysis' not in st.session_state:
131
+ st.session_state.cost_analysis = [
132
+ {
133
+ 'id': 1,
134
+ 'category': 'تكاليف مباشرة',
135
+ 'subcategory': 'مواد',
136
+ 'description': 'خرسانة',
137
+ 'amount': 120000,
138
+ 'percentage': 17.9
139
+ },
140
+ {
141
+ 'id': 2,
142
+ 'category': 'تكاليف مباشرة',
143
+ 'subcategory': 'مواد',
144
+ 'description': 'حديد تسليح',
145
+ 'amount': 135000,
146
+ 'percentage': 20.1
147
+ },
148
+ {
149
+ 'id': 3,
150
+ 'category': 'تكاليف مباشرة',
151
+ 'subcategory': 'مواد',
152
+ 'description': 'طابوق',
153
+ 'amount': 54000,
154
+ 'percentage': 8.1
155
+ },
156
+ {
157
+ 'id': 4,
158
+ 'category': 'تكاليف مباشرة',
159
+ 'subcategory': 'عمالة',
160
+ 'description': 'عمالة تنفيذ',
161
+ 'amount': 120000,
162
+ 'percentage': 17.9
163
+ },
164
+ {
165
+ 'id': 5,
166
+ 'category': 'تكاليف مباشرة',
167
+ 'subcategory': 'معدات',
168
+ 'description': 'معدات إنشائية',
169
+ 'amount': 85000,
170
+ 'percentage': 12.7
171
+ },
172
+ {
173
+ 'id': 6,
174
+ 'category': 'تكاليف غير مباشرة',
175
+ 'subcategory': 'إدارة',
176
+ 'description': 'إدارة المشروع',
177
+ 'amount': 45000,
178
+ 'percentage': 6.7
179
+ },
180
+ {
181
+ 'id': 7,
182
+ 'category': 'تكاليف غير مباشرة',
183
+ 'subcategory': 'إدارة',
184
+ 'description': 'إشراف هندسي',
185
+ 'amount': 35000,
186
+ 'percentage': 5.2
187
+ },
188
+ {
189
+ 'id': 8,
190
+ 'category': 'تكاليف غير مباشرة',
191
+ 'subcategory': 'عامة',
192
+ 'description': 'تأمينات وضمانات',
193
+ 'amount': 25000,
194
+ 'percentage': 3.7
195
+ },
196
+ {
197
+ 'id': 9,
198
+ 'category': 'تكاليف غير مباشرة',
199
+ 'subcategory': 'عامة',
200
+ 'description': 'مصاريف إدارية',
201
+ 'amount': 30000,
202
+ 'percentage': 4.5
203
+ },
204
+ {
205
+ 'id': 10,
206
+ 'category': 'أرباح',
207
+ 'subcategory': 'أرباح',
208
+ 'description': 'هامش الربح',
209
+ 'amount': 55000,
210
+ 'percentage': 8.2
211
+ }
212
+ ]
213
+
214
+ if 'price_scenarios' not in st.session_state:
215
+ st.session_state.price_scenarios = [
216
+ {
217
+ 'id': 1,
218
+ 'name': 'السيناريو الأساسي',
219
+ 'description': 'التسعير الأساسي مع هامش ربح 8%',
220
+ 'total_cost': 615000,
221
+ 'profit_margin': 8.2,
222
+ 'total_price': 670000,
223
+ 'is_active': True
224
+ },
225
+ {
226
+ 'id': 2,
227
+ 'name': 'سيناريو تنافسي',
228
+ 'description': 'تخفيض هامش الربح للمنافسة',
229
+ 'total_cost': 615000,
230
+ 'profit_margin': 5.0,
231
+ 'total_price': 650000,
232
+ 'is_active': False
233
+ },
234
+ {
235
+ 'id': 3,
236
+ 'name': 'سيناريو مرتفع',
237
+ 'description': 'زيادة هامش الربح للمشاريع ذات المخاطر العالية',
238
+ 'total_cost': 615000,
239
+ 'profit_margin': 12.0,
240
+ 'total_price': 700000,
241
+ 'is_active': False
242
+ }
243
+ ]
244
+
245
+ def run(self):
246
+ """تشغيل وحدة التسعير"""
247
+ # استدعاء دالة العرض
248
+ self.render()
249
+
250
+ def render(self):
251
+ """عرض واجهة وحدة التسعير"""
252
+
253
+ st.markdown("<h1 class='module-title'>وحدة التسعير</h1>", unsafe_allow_html=True)
254
+
255
+ tabs = st.tabs([
256
+ "لوحة التحكم",
257
+ "جدول الكميات",
258
+ "تحليل التكاليف",
259
+ "سيناريوهات التسعير",
260
+ "المقارنة التنافسية",
261
+ "التقارير"
262
+ ])
263
+
264
+ with tabs[0]:
265
+ self._render_dashboard_tab()
266
+
267
+ with tabs[1]:
268
+ self._render_bill_of_quantities_tab()
269
+
270
+ with tabs[2]:
271
+ self._render_cost_analysis_tab()
272
+
273
+ with tabs[3]:
274
+ self._render_pricing_scenarios_tab()
275
+
276
+ with tabs[4]:
277
+ self._render_competitive_analysis_tab()
278
+
279
+ with tabs[5]:
280
+ self._render_reports_tab()
281
+
282
+ def _render_dashboard_tab(self):
283
+ """عرض تبويب لوحة التحكم"""
284
+
285
+ st.markdown("### لوحة تحكم التسعير")
286
+
287
+ # عرض ملخص التسعير
288
+ col1, col2, col3, col4 = st.columns(4)
289
+
290
+ # حساب إجمالي التكاليف
291
+ total_direct_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف مباشرة')
292
+ total_indirect_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف غير مباشرة')
293
+ total_profit = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'أرباح')
294
+ total_cost = total_direct_cost + total_indirect_cost
295
+ total_price = total_cost + total_profit
296
+
297
+ with col1:
298
+ st.metric("إجمالي التكاليف المباشرة", f"{total_direct_cost:,.0f} ريال")
299
+
300
+ with col2:
301
+ st.metric("إجمالي التكاليف غير المباشرة", f"{total_indirect_cost:,.0f} ريال")
302
+
303
+ with col3:
304
+ st.metric("إجمالي التكاليف", f"{total_cost:,.0f} ريال")
305
+
306
+ with col4:
307
+ st.metric("السعر الإجمالي", f"{total_price:,.0f} ريال")
308
+
309
+ # عرض توزيع التكاليف
310
+ st.markdown("### توزيع التكاليف")
311
+
312
+ # تجميع البيانات حسب الفئة
313
+ cost_categories = {}
314
+
315
+ for item in st.session_state.cost_analysis:
316
+ category = item['category']
317
+ if category in cost_categories:
318
+ cost_categories[category] += item['amount']
319
+ else:
320
+ cost_categories[category] = item['amount']
321
+
322
+ # إنشاء DataFrame للرسم البياني
323
+ cost_df = pd.DataFrame({
324
+ 'الفئة': list(cost_categories.keys()),
325
+ 'المبلغ': list(cost_categories.values())
326
+ })
327
+
328
+ # إنشاء رسم بياني دائري
329
+ fig = px.pie(
330
+ cost_df,
331
+ values='المبلغ',
332
+ names='الفئة',
333
+ title='توزيع التكاليف حسب الفئة',
334
+ color_discrete_sequence=px.colors.qualitative.Set3
335
+ )
336
+
337
+ st.plotly_chart(fig, use_container_width=True)
338
+
339
+ # عرض توزيع التكاليف المباشرة
340
+ st.markdown("### توزيع التكاليف المباشرة")
341
+
342
+ # تجميع البيانات حسب الفئة الفرعية للتكاليف المباشرة
343
+ direct_cost_subcategories = {}
344
+
345
+ for item in st.session_state.cost_analysis:
346
+ if item['category'] == 'تكاليف مباشرة':
347
+ subcategory = item['subcategory']
348
+ if subcategory in direct_cost_subcategories:
349
+ direct_cost_subcategories[subcategory] += item['amount']
350
+ else:
351
+ direct_cost_subcategories[subcategory] = item['amount']
352
+
353
+ # إنشاء DataFrame للرسم البياني
354
+ direct_cost_df = pd.DataFrame({
355
+ 'الفئة الفرعية': list(direct_cost_subcategories.keys()),
356
+ 'المبلغ': list(direct_cost_subcategories.values())
357
+ })
358
+
359
+ # إنشاء رسم بياني دائري
360
+ fig = px.pie(
361
+ direct_cost_df,
362
+ values='المبلغ',
363
+ names='الفئة الفرعية',
364
+ title='توزيع التكاليف المباشرة',
365
+ color_discrete_sequence=px.colors.qualitative.Pastel
366
+ )
367
+
368
+ st.plotly_chart(fig, use_container_width=True)
369
+
370
+ # عرض توزيع التكاليف غير المباشرة
371
+ st.markdown("### توزيع التكاليف غير المباشرة")
372
+
373
+ # تجميع البيانات حسب الفئة الفرعية للتكاليف غير المباشرة
374
+ indirect_cost_subcategories = {}
375
+
376
+ for item in st.session_state.cost_analysis:
377
+ if item['category'] == 'تكاليف غير مباشرة':
378
+ subcategory = item['subcategory']
379
+ if subcategory in indirect_cost_subcategories:
380
+ indirect_cost_subcategories[subcategory] += item['amount']
381
+ else:
382
+ indirect_cost_subcategories[subcategory] = item['amount']
383
+
384
+ # إنشاء DataFrame للرسم البياني
385
+ indirect_cost_df = pd.DataFrame({
386
+ 'الفئة الفرعية': list(indirect_cost_subcategories.keys()),
387
+ 'المبلغ': list(indirect_cost_subcategories.values())
388
+ })
389
+
390
+ # إنشاء رسم بياني دائري
391
+ fig = px.pie(
392
+ indirect_cost_df,
393
+ values='المبلغ',
394
+ names='الفئة الفرعية',
395
+ title='توزيع التكاليف غير المباشرة',
396
+ color_discrete_sequence=px.colors.qualitative.Pastel1
397
+ )
398
+
399
+ st.plotly_chart(fig, use_container_width=True)
400
+
401
+ def _render_bill_of_quantities_tab(self):
402
+ """عرض تبويب جدول الكميات"""
403
+
404
+ st.markdown("### جدول الكميات")
405
+
406
+ # إنشاء DataFrame من بيانات جدول الكميات
407
+ boq_df = pd.DataFrame(st.session_state.bill_of_quantities)
408
+
409
+ # عرض جدول الكميات
410
+ st.dataframe(
411
+ boq_df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'category']],
412
+ column_config={
413
+ 'code': 'الكود',
414
+ 'description': 'الوصف',
415
+ 'unit': 'الوحدة',
416
+ 'quantity': 'الكمية',
417
+ 'unit_price': st.column_config.NumberColumn('سعر الوحدة', format='%d ريال'),
418
+ 'total_price': st.column_config.NumberColumn('السعر الإجمالي', format='%d ريال'),
419
+ 'category': 'الفئة'
420
+ },
421
+ hide_index=True,
422
+ use_container_width=True
423
+ )
424
+
425
+ # إضافة بند جديد
426
+ st.markdown("### إضافة بند جديد")
427
+
428
+ col1, col2 = st.columns(2)
429
+
430
+ with col1:
431
+ new_code = st.text_input("الكود", key="new_boq_code")
432
+ new_description = st.text_input("الوصف", key="new_boq_description")
433
+ new_unit = st.selectbox("الوحدة", ["م3", "م2", "طن", "عدد", "متر طولي"], key="new_boq_unit")
434
+
435
+ with col2:
436
+ new_quantity = st.number_input("الكمية", min_value=0.0, step=1.0, key="new_boq_quantity")
437
+ new_unit_price = st.number_input("سعر الوحدة", min_value=0.0, step=10.0, key="new_boq_unit_price")
438
+ new_category = st.selectbox(
439
+ "الفئة",
440
+ [
441
+ "أعمال ترابية",
442
+ "أعمال خرسانية",
443
+ "أعمال حديد",
444
+ "أعمال بناء",
445
+ "أعمال تشطيبات",
446
+ "أعمال نجارة",
447
+ "أعمال ألمنيوم",
448
+ "أعمال كهربائية",
449
+ "أعمال ميكانيكية",
450
+ "أعمال صحية"
451
+ ],
452
+ key="new_boq_category"
453
+ )
454
+
455
+ if st.button("إضافة البند", key="add_boq_item"):
456
+ if new_code and new_description and new_quantity > 0 and new_unit_price > 0:
457
+ # حساب السعر الإجمالي
458
+ new_total_price = new_quantity * new_unit_price
459
+
460
+ # إضافة بند جديد
461
+ new_id = max([item['id'] for item in st.session_state.bill_of_quantities]) + 1
462
+
463
+ st.session_state.bill_of_quantities.append({
464
+ 'id': new_id,
465
+ 'code': new_code,
466
+ 'description': new_description,
467
+ 'unit': new_unit,
468
+ 'quantity': new_quantity,
469
+ 'unit_price': new_unit_price,
470
+ 'total_price': new_total_price,
471
+ 'category': new_category
472
+ })
473
+
474
+ st.success(f"تمت إضافة البند بنجاح: {new_description}")
475
+
476
+ # تحديث الصفحة لعرض البند الجديد
477
+ st.rerun()
478
+ else:
479
+ st.error("يرجى إدخال جميع البيانات المطلوبة بشكل صحيح")
480
+
481
+ # عرض ملخص جدول الكميات (إزالة التكرار)
482
+ st.markdown("### ملخص جدول الكميات")
483
+
484
+ # تجميع البيانات حسب الفئة
485
+ category_totals = {}
486
+ for item in st.session_state.bill_of_quantities:
487
+ category = item['category']
488
+ if category in category_totals:
489
+ category_totals[category] += item['total_price']
490
+ else:
491
+ category_totals[category] = item['total_price']
492
+
493
+ # إنشاء DataFrame للرسم البياني
494
+ category_df = pd.DataFrame({
495
+ 'الفئة': list(category_totals.keys()),
496
+ 'المبلغ': list(category_totals.values())
497
+ })
498
+
499
+ # ترتيب البيانات تنازليًا حسب المبلغ
500
+ category_df = category_df.sort_values('المبلغ', ascending=False)
501
+
502
+ # إنشاء رسم بياني شريطي
503
+ fig = px.bar(
504
+ category_df,
505
+ x='الفئة',
506
+ y='المبلغ',
507
+ title='إجمالي تكلفة البنود حسب الفئة',
508
+ color='الفئة',
509
+ text_auto=True
510
+ )
511
+
512
+ st.plotly_chart(fig, use_container_width=True)
513
+
514
+ # حساب إجمالي جدول الكميات
515
+ total_boq = sum(item['total_price'] for item in st.session_state.bill_of_quantities)
516
+
517
+ # يجب إضافة باقي الدوال المفقودة هنا
518
+ def _render_cost_analysis_tab(self):
519
+ """عرض تبويب تحليل التكاليف"""
520
+ # تنفيذ هذه الدالة حسب متطلبات التطبيق
521
+ st.markdown("### تحليل التكاليف")
522
+ # محتوى مؤقت
523
+ st.info("تبويب تحليل التكاليف قيد التطوير")
524
+
525
+ def _render_pricing_scenarios_tab(self):
526
+ """عرض تبويب سيناريوهات التسعير"""
527
+ # تنفيذ هذه الدالة حسب متطلبات التطبيق
528
+ st.markdown("### سيناريوهات التسعير")
529
+ # محتوى مؤقت
530
+ st.info("تبويب سيناريوهات التسعير قيد التطوير")
531
+
532
+ def _render_competitive_analysis_tab(self):
533
+ """عرض تبويب المقارنة التنافسية"""
534
+ # تنفيذ هذه الدالة حسب متطلبات التطبيق
535
+ st.markdown("### المقارنة التنافسية")
536
+ # محتوى مؤقت
537
+ st.info("تبويب المقارنة التنافسية قيد التطوير")
538
+
539
+ def _render_reports_tab(self):
540
+ """عرض تبويب التقارير"""
541
+ # تنفيذ هذه الدالة حسب متطلبات التطبيق
542
+ st.markdown("### التقارير")
543
+ # محتوى مؤقت
544
+ st.info("تبويب التقارير قيد التطوير")
fixed_pricing_app_complete.py ADDED
@@ -0,0 +1,1760 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وحدة التسعير - التطبيق الرئيسي
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ import plotly.express as px
10
+ import plotly.graph_objects as go
11
+ from datetime import datetime
12
+ import time
13
+ import io
14
+ import os
15
+ import json
16
+ import base64
17
+ from pathlib import Path
18
+
19
+ class PricingApp:
20
+ """وحدة التسعير"""
21
+
22
+ def __init__(self):
23
+ """تهيئة وحدة التسعير"""
24
+
25
+ # تهيئة حالة الجلسة
26
+ if 'bill_of_quantities' not in st.session_state:
27
+ st.session_state.bill_of_quantities = [
28
+ {
29
+ 'id': 1,
30
+ 'code': 'A-001',
31
+ 'description': 'أعمال الحفر والردم',
32
+ 'unit': 'م3',
33
+ 'quantity': 1500,
34
+ 'unit_price': 45,
35
+ 'total_price': 67500,
36
+ 'category': 'أعمال ترابية'
37
+ },
38
+ {
39
+ 'id': 2,
40
+ 'code': 'A-002',
41
+ 'description': 'توريد وصب خرسانة عادية',
42
+ 'unit': 'م3',
43
+ 'quantity': 250,
44
+ 'unit_price': 350,
45
+ 'total_price': 87500,
46
+ 'category': 'أعمال خرسانية'
47
+ },
48
+ {
49
+ 'id': 3,
50
+ 'code': 'A-003',
51
+ 'description': 'توريد وصب خرسانة مسلحة للأساسات',
52
+ 'unit': 'م3',
53
+ 'quantity': 180,
54
+ 'unit_price': 450,
55
+ 'total_price': 81000,
56
+ 'category': 'أعمال خرسانية'
57
+ },
58
+ {
59
+ 'id': 4,
60
+ 'code': 'A-004',
61
+ 'description': 'توريد وصب خرسانة مسلحة للأعمدة',
62
+ 'unit': 'م3',
63
+ 'quantity': 120,
64
+ 'unit_price': 500,
65
+ 'total_price': 60000,
66
+ 'category': 'أعمال خرسانية'
67
+ },
68
+ {
69
+ 'id': 5,
70
+ 'code': 'A-005',
71
+ 'description': 'توريد وتركيب حديد تسليح',
72
+ 'unit': 'طن',
73
+ 'quantity': 45,
74
+ 'unit_price': 3000,
75
+ 'total_price': 135000,
76
+ 'category': 'أعمال حديد'
77
+ },
78
+ {
79
+ 'id': 6,
80
+ 'code': 'A-006',
81
+ 'description': 'توريد وبناء طابوق',
82
+ 'unit': 'م2',
83
+ 'quantity': 1200,
84
+ 'unit_price': 45,
85
+ 'total_price': 54000,
86
+ 'category': 'أعمال بناء'
87
+ },
88
+ {
89
+ 'id': 7,
90
+ 'code': 'A-007',
91
+ 'description': 'أعمال اللياسة والتشطيبات',
92
+ 'unit': 'م2',
93
+ 'quantity': 2400,
94
+ 'unit_price': 35,
95
+ 'total_price': 84000,
96
+ 'category': 'أعمال تشطيبات'
97
+ },
98
+ {
99
+ 'id': 8,
100
+ 'code': 'A-008',
101
+ 'description': 'أعمال الدهانات',
102
+ 'unit': 'م2',
103
+ 'quantity': 2400,
104
+ 'unit_price': 25,
105
+ 'total_price': 60000,
106
+ 'category': 'أعمال تشطيبات'
107
+ },
108
+ {
109
+ 'id': 9,
110
+ 'code': 'A-009',
111
+ 'description': 'توريد وتركيب أبواب خشبية',
112
+ 'unit': 'عدد',
113
+ 'quantity': 24,
114
+ 'unit_price': 750,
115
+ 'total_price': 18000,
116
+ 'category': 'أعمال نجارة'
117
+ },
118
+ {
119
+ 'id': 10,
120
+ 'code': 'A-010',
121
+ 'description': 'توريد وتركيب نوافذ ألمنيوم',
122
+ 'unit': 'م2',
123
+ 'quantity': 120,
124
+ 'unit_price': 350,
125
+ 'total_price': 42000,
126
+ 'category': 'أعمال ألمنيوم'
127
+ }
128
+ ]
129
+
130
+ if 'cost_analysis' not in st.session_state:
131
+ st.session_state.cost_analysis = [
132
+ {
133
+ 'id': 1,
134
+ 'category': 'تكاليف مباشرة',
135
+ 'subcategory': 'مواد',
136
+ 'description': 'خرسانة',
137
+ 'amount': 120000,
138
+ 'percentage': 17.9
139
+ },
140
+ {
141
+ 'id': 2,
142
+ 'category': 'تكاليف مباشرة',
143
+ 'subcategory': 'مواد',
144
+ 'description': 'حديد تسليح',
145
+ 'amount': 135000,
146
+ 'percentage': 20.1
147
+ },
148
+ {
149
+ 'id': 3,
150
+ 'category': 'تكاليف مباشرة',
151
+ 'subcategory': 'مواد',
152
+ 'description': 'طابوق',
153
+ 'amount': 54000,
154
+ 'percentage': 8.1
155
+ },
156
+ {
157
+ 'id': 4,
158
+ 'category': 'تكاليف مباشرة',
159
+ 'subcategory': 'عمالة',
160
+ 'description': 'عمالة تنفيذ',
161
+ 'amount': 120000,
162
+ 'percentage': 17.9
163
+ },
164
+ {
165
+ 'id': 5,
166
+ 'category': 'تكاليف مباشرة',
167
+ 'subcategory': 'معدات',
168
+ 'description': 'معدات إنشائية',
169
+ 'amount': 85000,
170
+ 'percentage': 12.7
171
+ },
172
+ {
173
+ 'id': 6,
174
+ 'category': 'تكاليف غير مباشرة',
175
+ 'subcategory': 'إدارة',
176
+ 'description': 'إدارة المشروع',
177
+ 'amount': 45000,
178
+ 'percentage': 6.7
179
+ },
180
+ {
181
+ 'id': 7,
182
+ 'category': 'تكاليف غير مباشرة',
183
+ 'subcategory': 'إدارة',
184
+ 'description': 'إشراف هندسي',
185
+ 'amount': 35000,
186
+ 'percentage': 5.2
187
+ },
188
+ {
189
+ 'id': 8,
190
+ 'category': 'تكاليف غير مباشرة',
191
+ 'subcategory': 'عامة',
192
+ 'description': 'تأمينات وضمانات',
193
+ 'amount': 25000,
194
+ 'percentage': 3.7
195
+ },
196
+ {
197
+ 'id': 9,
198
+ 'category': 'تكاليف غير مباشرة',
199
+ 'subcategory': 'عامة',
200
+ 'description': 'مصاريف إدارية',
201
+ 'amount': 30000,
202
+ 'percentage': 4.5
203
+ },
204
+ {
205
+ 'id': 10,
206
+ 'category': 'أرباح',
207
+ 'subcategory': 'أرباح',
208
+ 'description': 'هامش الربح',
209
+ 'amount': 55000,
210
+ 'percentage': 8.2
211
+ }
212
+ ]
213
+
214
+ if 'price_scenarios' not in st.session_state:
215
+ st.session_state.price_scenarios = [
216
+ {
217
+ 'id': 1,
218
+ 'name': 'السيناريو الأساسي',
219
+ 'description': 'التسعير الأساسي مع هامش ربح 8%',
220
+ 'total_cost': 615000,
221
+ 'profit_margin': 8.2,
222
+ 'total_price': 670000,
223
+ 'is_active': True
224
+ },
225
+ {
226
+ 'id': 2,
227
+ 'name': 'سيناريو تنافسي',
228
+ 'description': 'تخفيض هامش الربح للمنافسة',
229
+ 'total_cost': 615000,
230
+ 'profit_margin': 5.0,
231
+ 'total_price': 650000,
232
+ 'is_active': False
233
+ },
234
+ {
235
+ 'id': 3,
236
+ 'name': 'سيناريو مرتفع',
237
+ 'description': 'زيادة هامش الربح للمشاريع ذات المخاطر العالية',
238
+ 'total_cost': 615000,
239
+ 'profit_margin': 12.0,
240
+ 'total_price': 700000,
241
+ 'is_active': False
242
+ }
243
+ ]
244
+
245
+ # إضافة بيانات المقارنة التنافسية
246
+ if 'competitive_analysis' not in st.session_state:
247
+ st.session_state.competitive_analysis = [
248
+ {
249
+ 'id': 1,
250
+ 'competitor': 'شركة الإنشاءات المتحدة',
251
+ 'project_type': 'مباني سكنية',
252
+ 'price_per_sqm': 1800,
253
+ 'delivery_time': 12,
254
+ 'quality_rating': 4.2,
255
+ 'market_share': 15.5
256
+ },
257
+ {
258
+ 'id': 2,
259
+ 'competitor': 'مجموعة البناء الحديث',
260
+ 'project_type': 'مباني سكنية',
261
+ 'price_per_sqm': 2100,
262
+ 'delivery_time': 10,
263
+ 'quality_rating': 4.5,
264
+ 'market_share': 18.2
265
+ },
266
+ {
267
+ 'id': 3,
268
+ 'competitor': 'شركة الإعمار الدولية',
269
+ 'project_type': 'مباني سكنية',
270
+ 'price_per_sqm': 2300,
271
+ 'delivery_time': 14,
272
+ 'quality_rating': 4.7,
273
+ 'market_share': 22.0
274
+ },
275
+ {
276
+ 'id': 4,
277
+ 'competitor': 'مؤسسة البناء المتكامل',
278
+ 'project_type': 'مباني سكنية',
279
+ 'price_per_sqm': 1750,
280
+ 'delivery_time': 15,
281
+ 'quality_rating': 3.8,
282
+ 'market_share': 12.5
283
+ },
284
+ {
285
+ 'id': 5,
286
+ 'competitor': 'شركتنا',
287
+ 'project_type': 'مباني سكنية',
288
+ 'price_per_sqm': 1950,
289
+ 'delivery_time': 11,
290
+ 'quality_rating': 4.4,
291
+ 'market_share': 14.8
292
+ }
293
+ ]
294
+
295
+ def run(self):
296
+ """تشغيل وحدة التسعير"""
297
+ # استدعاء دالة العرض
298
+ self.render()
299
+
300
+ def render(self):
301
+ """عرض واجهة وحدة التسعير"""
302
+
303
+ st.markdown("<h1 class='module-title'>وحدة التسعير</h1>", unsafe_allow_html=True)
304
+
305
+ tabs = st.tabs([
306
+ "لوحة التحكم",
307
+ "جدول الكميات",
308
+ "تحليل التكاليف",
309
+ "سيناريوهات التسعير",
310
+ "المقارنة التنافسية",
311
+ "التقارير"
312
+ ])
313
+
314
+ with tabs[0]:
315
+ self._render_dashboard_tab()
316
+
317
+ with tabs[1]:
318
+ self._render_bill_of_quantities_tab()
319
+
320
+ with tabs[2]:
321
+ self._render_cost_analysis_tab()
322
+
323
+ with tabs[3]:
324
+ self._render_pricing_scenarios_tab()
325
+
326
+ with tabs[4]:
327
+ self._render_competitive_analysis_tab()
328
+
329
+ with tabs[5]:
330
+ self._render_reports_tab()
331
+
332
+ def _render_dashboard_tab(self):
333
+ """عرض تبويب لوحة التحكم"""
334
+
335
+ st.markdown("### لوحة تحكم التسعير")
336
+
337
+ # عرض ملخص التسعير
338
+ col1, col2, col3, col4 = st.columns(4)
339
+
340
+ # حساب إجمالي التكاليف
341
+ total_direct_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف مباشرة')
342
+ total_indirect_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف غير مباشرة')
343
+ total_profit = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'أرباح')
344
+ total_cost = total_direct_cost + total_indirect_cost
345
+ total_price = total_cost + total_profit
346
+
347
+ with col1:
348
+ st.metric("إجمالي التكاليف المباشرة", f"{total_direct_cost:,.0f} ريال")
349
+
350
+ with col2:
351
+ st.metric("إجمالي التكاليف غير المباشرة", f"{total_indirect_cost:,.0f} ريال")
352
+
353
+ with col3:
354
+ st.metric("إجمالي التكاليف", f"{total_cost:,.0f} ريال")
355
+
356
+ with col4:
357
+ st.metric("السعر الإجمالي", f"{total_price:,.0f} ريال")
358
+
359
+ # عرض توزيع التكاليف
360
+ st.markdown("### توزيع التكاليف")
361
+
362
+ # تجميع البيانات حسب الفئة
363
+ cost_categories = {}
364
+
365
+ for item in st.session_state.cost_analysis:
366
+ category = item['category']
367
+ if category in cost_categories:
368
+ cost_categories[category] += item['amount']
369
+ else:
370
+ cost_categories[category] = item['amount']
371
+
372
+ # إنشاء DataFrame للرسم البياني
373
+ cost_df = pd.DataFrame({
374
+ 'الفئة': list(cost_categories.keys()),
375
+ 'المبلغ': list(cost_categories.values())
376
+ })
377
+
378
+ # إنشاء رسم بياني دائري
379
+ fig = px.pie(
380
+ cost_df,
381
+ values='المبلغ',
382
+ names='الفئة',
383
+ title='توزيع التكاليف حسب الفئة',
384
+ color_discrete_sequence=px.colors.qualitative.Set3
385
+ )
386
+
387
+ st.plotly_chart(fig, use_container_width=True)
388
+
389
+ # عرض توزيع التكاليف المباشرة
390
+ st.markdown("### توزيع التكاليف المباشرة")
391
+
392
+ # تجميع البيانات حسب الفئة الفرعية للتكاليف المباشرة
393
+ direct_cost_subcategories = {}
394
+
395
+ for item in st.session_state.cost_analysis:
396
+ if item['category'] == 'تكاليف مباشرة':
397
+ subcategory = item['subcategory']
398
+ if subcategory in direct_cost_subcategories:
399
+ direct_cost_subcategories[subcategory] += item['amount']
400
+ else:
401
+ direct_cost_subcategories[subcategory] = item['amount']
402
+
403
+ # إنشاء DataFrame للرسم البياني
404
+ direct_cost_df = pd.DataFrame({
405
+ 'الفئة الفرعية': list(direct_cost_subcategories.keys()),
406
+ 'المبلغ': list(direct_cost_subcategories.values())
407
+ })
408
+
409
+ # إنشاء رسم بياني دائري
410
+ fig = px.pie(
411
+ direct_cost_df,
412
+ values='المبلغ',
413
+ names='الفئة الفرعية',
414
+ title='توزيع التكاليف المباشرة',
415
+ color_discrete_sequence=px.colors.qualitative.Pastel
416
+ )
417
+
418
+ st.plotly_chart(fig, use_container_width=True)
419
+
420
+ # عرض توزيع التكاليف غير المباشرة
421
+ st.markdown("### توزيع التكاليف غير المباشرة")
422
+
423
+ # تجميع البيانات حسب الفئة الفرعية للتكاليف غير المباشرة
424
+ indirect_cost_subcategories = {}
425
+
426
+ for item in st.session_state.cost_analysis:
427
+ if item['category'] == 'تكاليف غير مباشرة':
428
+ subcategory = item['subcategory']
429
+ if subcategory in indirect_cost_subcategories:
430
+ indirect_cost_subcategories[subcategory] += item['amount']
431
+ else:
432
+ indirect_cost_subcategories[subcategory] = item['amount']
433
+
434
+ # إنشاء DataFrame للرسم البياني
435
+ indirect_cost_df = pd.DataFrame({
436
+ 'الفئة الفرعية': list(indirect_cost_subcategories.keys()),
437
+ 'المبلغ': list(indirect_cost_subcategories.values())
438
+ })
439
+
440
+ # إنشاء رسم بياني دائري
441
+ fig = px.pie(
442
+ indirect_cost_df,
443
+ values='المبلغ',
444
+ names='الفئة الفرعية',
445
+ title='توزيع التكاليف غير المباشرة',
446
+ color_discrete_sequence=px.colors.qualitative.Pastel1
447
+ )
448
+
449
+ st.plotly_chart(fig, use_container_width=True)
450
+
451
+ def _render_bill_of_quantities_tab(self):
452
+ """عرض تبويب جدول الكميات"""
453
+
454
+ st.markdown("### جدول الكميات")
455
+
456
+ # إنشاء DataFrame من بيانات جدول الكميات
457
+ boq_df = pd.DataFrame(st.session_state.bill_of_quantities)
458
+
459
+ # عرض جدول الكميات
460
+ st.dataframe(
461
+ boq_df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'category']],
462
+ column_config={
463
+ 'code': 'الكود',
464
+ 'description': 'الوصف',
465
+ 'unit': 'الوحدة',
466
+ 'quantity': 'الكمية',
467
+ 'unit_price': st.column_config.NumberColumn('سعر الوحدة', format='%d ريال'),
468
+ 'total_price': st.column_config.NumberColumn('السعر الإجمالي', format='%d ريال'),
469
+ 'category': 'الفئة'
470
+ },
471
+ hide_index=True,
472
+ use_container_width=True
473
+ )
474
+
475
+ # إضافة بند جديد
476
+ st.markdown("### إضافة بند جديد")
477
+
478
+ col1, col2 = st.columns(2)
479
+
480
+ with col1:
481
+ new_code = st.text_input("الكود", key="new_boq_code")
482
+ new_description = st.text_input("الوصف", key="new_boq_description")
483
+ new_unit = st.selectbox("الوحدة", ["م3", "م2", "طن", "عدد", "متر طولي"], key="new_boq_unit")
484
+
485
+ with col2:
486
+ new_quantity = st.number_input("الكمية", min_value=0.0, step=1.0, key="new_boq_quantity")
487
+ new_unit_price = st.number_input("سعر الوحدة", min_value=0.0, step=10.0, key="new_boq_unit_price")
488
+ new_category = st.selectbox(
489
+ "الفئة",
490
+ [
491
+ "أعمال ترابية",
492
+ "أعمال خرسانية",
493
+ "أعمال حديد",
494
+ "أعمال بناء",
495
+ "أعمال تشطيبات",
496
+ "أعمال نجارة",
497
+ "أعمال ألمنيوم",
498
+ "أعمال كهربائية",
499
+ "أعمال ميكانيكية",
500
+ "أعمال صحية"
501
+ ],
502
+ key="new_boq_category"
503
+ )
504
+
505
+ if st.button("إضافة البند", key="add_boq_item"):
506
+ if new_code and new_description and new_quantity > 0 and new_unit_price > 0:
507
+ # حساب السعر الإجمالي
508
+ new_total_price = new_quantity * new_unit_price
509
+
510
+ # إضافة بند جديد
511
+ new_id = max([item['id'] for item in st.session_state.bill_of_quantities]) + 1
512
+
513
+ st.session_state.bill_of_quantities.append({
514
+ 'id': new_id,
515
+ 'code': new_code,
516
+ 'description': new_description,
517
+ 'unit': new_unit,
518
+ 'quantity': new_quantity,
519
+ 'unit_price': new_unit_price,
520
+ 'total_price': new_total_price,
521
+ 'category': new_category
522
+ })
523
+
524
+ st.success(f"تمت إضافة البند بنجاح: {new_description}")
525
+
526
+ # تحديث الصفحة لعرض البند الجديد
527
+ st.rerun()
528
+ else:
529
+ st.error("يرجى إدخال جميع البيانات المطلوبة بشكل صحيح")
530
+
531
+ # عرض ملخص جدول الكميات (إزالة التكرار)
532
+ st.markdown("### ملخص جدول الكميات")
533
+
534
+ # تجميع البيانات حسب الفئة
535
+ category_totals = {}
536
+ for item in st.session_state.bill_of_quantities:
537
+ category = item['category']
538
+ if category in category_totals:
539
+ category_totals[category] += item['total_price']
540
+ else:
541
+ category_totals[category] = item['total_price']
542
+
543
+ # إنشاء DataFrame للرسم البياني
544
+ category_df = pd.DataFrame({
545
+ 'الفئة': list(category_totals.keys()),
546
+ 'المبلغ': list(category_totals.values())
547
+ })
548
+
549
+ # ترتيب البيانات تنازليًا حسب المبلغ
550
+ category_df = category_df.sort_values('المبلغ', ascending=False)
551
+
552
+ # إنشاء رسم بياني شريطي
553
+ fig = px.bar(
554
+ category_df,
555
+ x='الفئة',
556
+ y='المبلغ',
557
+ title='إجمالي تكلفة البنود حسب الفئة',
558
+ color='الفئة',
559
+ text_auto=True
560
+ )
561
+
562
+ st.plotly_chart(fig, use_container_width=True)
563
+
564
+ # حساب إجمالي جدول الكميات
565
+ total_boq = sum(item['total_price'] for item in st.session_state.bill_of_quantities)
566
+
567
+ def _render_cost_analysis_tab(self):
568
+ """عرض تبويب تحليل التكاليف"""
569
+
570
+ st.markdown("### تحليل التكاليف")
571
+
572
+ # عرض جدول تحليل التكاليف
573
+ cost_df = pd.DataFrame(st.session_state.cost_analysis)
574
+
575
+ st.dataframe(
576
+ cost_df[['category', 'subcategory', 'description', 'amount', 'percentage']],
577
+ column_config={
578
+ 'category': 'الفئة',
579
+ 'subcategory': 'الفئة الفرعية',
580
+ 'description': 'الوصف',
581
+ 'amount': st.column_config.NumberColumn('المبلغ', format='%d ريال'),
582
+ 'percentage': st.column_config.NumberColumn('النسبة المئوية', format='%.1f%%')
583
+ },
584
+ hide_index=True,
585
+ use_container_width=True
586
+ )
587
+
588
+ # إضافة بند تكلفة جديد
589
+ st.markdown("### إضافة بند تكلفة جديد")
590
+
591
+ col1, col2 = st.columns(2)
592
+
593
+ with col1:
594
+ new_category = st.selectbox(
595
+ "الفئة",
596
+ ["تكاليف مباشرة", "تكاليف غير مباشرة", "أرباح"],
597
+ key="new_cost_category"
598
+ )
599
+
600
+ # تحديد الفئات الفرعية بناءً على الفئة المختارة
601
+ subcategory_options = []
602
+ if new_category == "تكاليف مباشرة":
603
+ subcategory_options = ["مواد", "عمالة", "معدات"]
604
+ elif new_category == "تكاليف غير مباشرة":
605
+ subcategory_options = ["إدارة", "عامة", "تمويل"]
606
+ else:
607
+ subcategory_options = ["أرباح"]
608
+
609
+ new_subcategory = st.selectbox(
610
+ "الفئة الفرعية",
611
+ subcategory_options,
612
+ key="new_cost_subcategory"
613
+ )
614
+
615
+ new_description = st.text_input("الوصف", key="new_cost_description")
616
+
617
+ with col2:
618
+ new_amount = st.number_input("المبلغ", min_value=0.0, step=1000.0, key="new_cost_amount")
619
+
620
+ # حساب إجمالي التكاليف الحالية
621
+ total_cost = sum(item['amount'] for item in st.session_state.cost_analysis)
622
+
623
+ # حساب النسبة المئوية التقريبية
624
+ if total_cost > 0:
625
+ estimated_percentage = (new_amount / total_cost) * 100
626
+ else:
627
+ estimated_percentage = 0
628
+
629
+ st.metric("النسبة المئوية التقديرية", f"{estimated_percentage:.1f}%")
630
+
631
+ if st.button("إضافة بند التكلفة", key="add_cost_item"):
632
+ if new_description and new_amount > 0:
633
+ # إضافة بند جديد
634
+ new_id = max([item['id'] for item in st.session_state.cost_analysis]) + 1
635
+
636
+ # حساب النسبة المئوية الفعلية بعد إضافة البند الجديد
637
+ new_total = total_cost + new_amount
638
+
639
+ # إعادة حساب النسب المئوية لجميع البنود
640
+ for item in st.session_state.cost_analysis:
641
+ item['percentage'] = (item['amount'] / new_total) * 100
642
+
643
+ # إضافة البند الجديد
644
+ st.session_state.cost_analysis.append({
645
+ 'id': new_id,
646
+ 'category': new_category,
647
+ 'subcategory': new_subcategory,
648
+ 'description': new_description,
649
+ 'amount': new_amount,
650
+ 'percentage': (new_amount / new_total) * 100
651
+ })
652
+
653
+ st.success(f"تمت إضافة بند التكلفة بنجاح: {new_description}")
654
+
655
+ # تحديث الصفحة لعرض البند الجديد
656
+ st.rerun()
657
+ else:
658
+ st.error("يرجى إدخال جميع البيانات المطلوبة بشكل صحيح")
659
+
660
+ # تحليل التكاليف حسب الفئة والفئة الفرعية
661
+ st.markdown("### تحليل التكاليف حسب الفئة والفئة الفرعية")
662
+
663
+ # تجميع البيانات حسب الفئة والفئة الفرعية
664
+ cost_by_category_subcategory = {}
665
+
666
+ for item in st.session_state.cost_analysis:
667
+ category = item['category']
668
+ subcategory = item['subcategory']
669
+ key = f"{category} - {subcategory}"
670
+
671
+ if key in cost_by_category_subcategory:
672
+ cost_by_category_subcategory[key] += item['amount']
673
+ else:
674
+ cost_by_category_subcategory[key] = item['amount']
675
+
676
+ # إنشاء DataFrame للرسم البياني
677
+ cost_category_subcategory_df = pd.DataFrame({
678
+ 'الفئة والفئة الفرعية': list(cost_by_category_subcategory.keys()),
679
+ 'المبلغ': list(cost_by_category_subcategory.values())
680
+ })
681
+
682
+ # ترتيب البيانات تنازليًا حسب المبلغ
683
+ cost_category_subcategory_df = cost_category_subcategory_df.sort_values('المبلغ', ascending=False)
684
+
685
+ # إنشاء رسم بياني شريطي
686
+ fig = px.bar(
687
+ cost_category_subcategory_df,
688
+ x='الفئة والفئة الفرعية',
689
+ y='المبلغ',
690
+ title='تحليل التكاليف حسب الفئة والفئة الفرعية',
691
+ color='الفئة والفئة الفرعية',
692
+ text_auto=True
693
+ )
694
+
695
+ st.plotly_chart(fig, use_container_width=True)
696
+
697
+ # تحليل نسب التكاليف
698
+ st.markdown("### تحليل نسب التكاليف")
699
+
700
+ # إنشاء رسم بياني للنسب المئوية
701
+ percentage_df = pd.DataFrame(st.session_state.cost_analysis)
702
+
703
+ fig = px.treemap(
704
+ percentage_df,
705
+ path=['category', 'subcategory', 'description'],
706
+ values='amount',
707
+ title='تحليل هيكل التكاليف',
708
+ color='percentage',
709
+ color_continuous_scale='RdBu',
710
+ color_continuous_midpoint=np.average(percentage_df['percentage'])
711
+ )
712
+
713
+ st.plotly_chart(fig, use_container_width=True)
714
+
715
+ # تحليل اتجاهات التكاليف (بيانات افتراضية)
716
+ st.markdown("### تحليل اتجاهات التكاليف")
717
+
718
+ # إنشاء بيانات افتراضية للاتجاهات
719
+ months = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو']
720
+ direct_costs = [510000, 520000, 515000, 525000, 530000, 514000]
721
+ indirect_costs = [130000, 135000, 132000, 138000, 140000, 135000]
722
+
723
+ # إنشاء DataFrame للرسم البياني
724
+ trends_df = pd.DataFrame({
725
+ 'الشهر': months * 2,
726
+ 'نوع التكلفة': ['تكاليف مباشرة'] * 6 + ['تكاليف غير مباشرة'] * 6,
727
+ 'المبلغ': direct_costs + indirect_costs
728
+ })
729
+
730
+ # إنشاء رسم بياني خطي
731
+ fig = px.line(
732
+ trends_df,
733
+ x='الشهر',
734
+ y='المبلغ',
735
+ color='نوع التكلفة',
736
+ title='اتجاهات التكاليف على مدار الأشهر الستة الماضية',
737
+ markers=True
738
+ )
739
+
740
+ st.plotly_chart(fig, use_container_width=True)
741
+
742
+ def _render_pricing_scenarios_tab(self):
743
+ """عرض تبويب سيناريوهات التسعير"""
744
+
745
+ st.markdown("### سيناريوهات التسعير")
746
+
747
+ # عرض جدول سيناريوهات التسعير
748
+ scenarios_df = pd.DataFrame(st.session_state.price_scenarios)
749
+
750
+ st.dataframe(
751
+ scenarios_df[['name', 'description', 'total_cost', 'profit_margin', 'total_price', 'is_active']],
752
+ column_config={
753
+ 'name': 'اسم السيناريو',
754
+ 'description': 'الوصف',
755
+ 'total_cost': st.column_config.NumberColumn('إجمالي التكلفة', format='%d ريال'),
756
+ 'profit_margin': st.column_config.NumberColumn('هامش الربح', format='%.1f%%'),
757
+ 'total_price': st.column_config.NumberColumn('السعر الإجمالي', format='%d ريال'),
758
+ 'is_active': st.column_config.CheckboxColumn('نشط')
759
+ },
760
+ hide_index=True,
761
+ use_container_width=True
762
+ )
763
+
764
+ # إنشاء سيناريو جديد
765
+ st.markdown("### إنشاء سيناريو جديد")
766
+
767
+ col1, col2 = st.columns(2)
768
+
769
+ # حساب إجمالي التكاليف
770
+ total_cost = sum(item['amount'] for item in st.session_state.cost_analysis
771
+ if item['category'] != 'أرباح')
772
+
773
+ with col1:
774
+ new_name = st.text_input("اسم السيناريو", key="new_scenario_name")
775
+ new_description = st.text_input("وصف السيناريو", key="new_scenario_description")
776
+
777
+ with col2:
778
+ new_profit_margin = st.slider(
779
+ "هامش الربح (%)",
780
+ min_value=0.0,
781
+ max_value=30.0,
782
+ value=10.0,
783
+ step=0.5,
784
+ key="new_scenario_profit_margin"
785
+ )
786
+
787
+ # حساب السعر الإجمالي بناءً على هامش الربح
788
+ profit_amount = total_cost * (new_profit_margin / 100)
789
+ new_total_price = total_cost + profit_amount
790
+
791
+ st.metric("إجمالي التكلفة", f"{total_cost:,.0f} ريال")
792
+ st.metric("السعر الإجمالي المقترح", f"{new_total_price:,.0f} ريال")
793
+
794
+ if st.button("إضافة السيناريو", key="add_scenario"):
795
+ if new_name and new_description:
796
+ # إضافة سيناريو جديد
797
+ new_id = max([item['id'] for item in st.session_state.price_scenarios]) + 1
798
+
799
+ st.session_state.price_scenarios.append({
800
+ 'id': new_id,
801
+ 'name': new_name,
802
+ 'description': new_description,
803
+ 'total_cost': total_cost,
804
+ 'profit_margin': new_profit_margin,
805
+ 'total_price': new_total_price,
806
+ 'is_active': False
807
+ })
808
+
809
+ st.success(f"تمت إضافة السيناريو بنجاح: {new_name}")
810
+
811
+ # تحديث الصفحة لعرض السيناريو الجديد
812
+ st.rerun()
813
+ else:
814
+ st.error("يرجى إدخال جميع البيانات المطلوبة بشكل صحيح")
815
+
816
+ # مقارنة السيناريوهات
817
+ st.markdown("### مقارنة السيناريوهات")
818
+
819
+ # إنشاء رسم بياني للمقارنة
820
+ fig = go.Figure()
821
+
822
+ for scenario in st.session_state.price_scenarios:
823
+ fig.add_trace(go.Bar(
824
+ name=scenario['name'],
825
+ x=['التكلفة', 'الربح', 'السعر الإجمالي'],
826
+ y=[
827
+ scenario['total_cost'],
828
+ scenario['total_price'] - scenario['total_cost'],
829
+ scenario['total_price']
830
+ ],
831
+ text=[
832
+ f"{scenario['total_cost']:,.0f}",
833
+ f"{scenario['total_price'] - scenario['total_cost']:,.0f}",
834
+ f"{scenario['total_price']:,.0f}"
835
+ ],
836
+ textposition='auto'
837
+ ))
838
+
839
+ fig.update_layout(
840
+ title='مقارنة السيناريوهات',
841
+ barmode='group',
842
+ xaxis_title='العنصر',
843
+ yaxis_title='المبلغ (ريال)'
844
+ )
845
+
846
+ st.plotly_chart(fig, use_container_width=True)
847
+
848
+ # تحليل حساسية هامش الربح
849
+ st.markdown("### تحليل حساسية هامش الربح")
850
+
851
+ # إنشاء بيانات لتحليل الحساسية
852
+ profit_margins = list(range(5, 26, 1)) # من 5% إلى 25%
853
+ total_prices = [total_cost * (1 + margin/100) for margin in profit_margins]
854
+
855
+ # إنشاء DataFrame للرسم البياني
856
+ sensitivity_df = pd.DataFrame({
857
+ 'هامش الربح (%)': profit_margins,
858
+ 'السعر الإجمالي': total_prices
859
+ })
860
+
861
+ # إنشاء رسم بياني خطي
862
+ fig = px.line(
863
+ sensitivity_df,
864
+ x='هامش الربح (%)',
865
+ y='السعر الإجمالي',
866
+ title='تحليل حساسية هامش الربح',
867
+ markers=True
868
+ )
869
+
870
+ # إضافة خط أفقي يمثل السعر التنافسي (افتراضي)
871
+ competitive_price = 650000
872
+ fig.add_hline(
873
+ y=competitive_price,
874
+ line_dash="dash",
875
+ line_color="red",
876
+ annotation_text="السعر التنافسي",
877
+ annotation_position="bottom right"
878
+ )
879
+
880
+ st.plotly_chart(fig, use_container_width=True)
881
+
882
+ # تفعيل/تعطيل السيناريوهات
883
+ st.markdown("### تفعيل/تعطيل السيناريوهات")
884
+
885
+ for i, scenario in enumerate(st.session_state.price_scenarios):
886
+ col1, col2 = st.columns([4, 1])
887
+
888
+ with col1:
889
+ st.write(f"**{scenario['name']}**: {scenario['description']}")
890
+
891
+ with col2:
892
+ is_active = st.checkbox(
893
+ "تفعيل",
894
+ value=scenario['is_active'],
895
+ key=f"activate_scenario_{scenario['id']}"
896
+ )
897
+
898
+ # تحديث حالة التفعيل
899
+ if is_active != scenario['is_active']:
900
+ # إذا تم تفعيل سيناريو، قم بتعطيل جميع السيناريوهات الأخرى
901
+ if is_active:
902
+ for j, other_scenario in enumerate(st.session_state.price_scenarios):
903
+ if j != i:
904
+ other_scenario['is_active'] = False
905
+
906
+ # تحديث حالة السيناريو الحالي
907
+ scenario['is_active'] = is_active
908
+
909
+ # تحديث الصفحة
910
+ st.rerun()
911
+
912
+ def _render_competitive_analysis_tab(self):
913
+ """عرض تبويب المقارنة التنافسية"""
914
+
915
+ st.markdown("### المقارنة التنافسية")
916
+
917
+ # عرض جدول المقارنة التنافسية
918
+ competitive_df = pd.DataFrame(st.session_state.competitive_analysis)
919
+
920
+ st.dataframe(
921
+ competitive_df[['competitor', 'project_type', 'price_per_sqm', 'delivery_time', 'quality_rating', 'market_share']],
922
+ column_config={
923
+ 'competitor': 'المنافس',
924
+ 'project_type': 'نوع المشروع',
925
+ 'price_per_sqm': st.column_config.NumberColumn('السعر لكل متر مربع', format='%d ريال'),
926
+ 'delivery_time': st.column_config.NumberColumn('مدة التسليم (شهر)', format='%d'),
927
+ 'quality_rating': st.column_config.NumberColumn('تقييم الجودة', format='%.1f/5.0'),
928
+ 'market_share': st.column_config.NumberColumn('الحصة السوقية', format='%.1f%%')
929
+ },
930
+ hide_index=True,
931
+ use_container_width=True
932
+ )
933
+
934
+ # إضافة منافس جديد
935
+ st.markdown("### إضافة منافس جديد")
936
+
937
+ col1, col2 = st.columns(2)
938
+
939
+ with col1:
940
+ new_competitor = st.text_input("اسم المنافس", key="new_competitor_name")
941
+ new_project_type = st.selectbox(
942
+ "نوع المشروع",
943
+ ["مباني سكنية", "مباني تجارية", "مباني صناعية", "بنية تحتية"],
944
+ key="new_competitor_project_type"
945
+ )
946
+ new_price_per_sqm = st.number_input(
947
+ "السعر لكل متر مربع (ريال)",
948
+ min_value=0,
949
+ step=50,
950
+ key="new_competitor_price"
951
+ )
952
+
953
+ with col2:
954
+ new_delivery_time = st.number_input(
955
+ "مدة التسليم (شهر)",
956
+ min_value=1,
957
+ max_value=36,
958
+ step=1,
959
+ key="new_competitor_delivery"
960
+ )
961
+ new_quality_rating = st.slider(
962
+ "تقييم الجودة",
963
+ min_value=1.0,
964
+ max_value=5.0,
965
+ value=3.5,
966
+ step=0.1,
967
+ key="new_competitor_quality"
968
+ )
969
+ new_market_share = st.number_input(
970
+ "الحصة السوقية (%)",
971
+ min_value=0.0,
972
+ max_value=100.0,
973
+ step=0.5,
974
+ key="new_competitor_market_share"
975
+ )
976
+
977
+ if st.button("إضافة منافس", key="add_competitor"):
978
+ if new_competitor and new_price_per_sqm > 0:
979
+ # إضافة منافس جديد
980
+ new_id = max([item['id'] for item in st.session_state.competitive_analysis]) + 1
981
+
982
+ st.session_state.competitive_analysis.append({
983
+ 'id': new_id,
984
+ 'competitor': new_competitor,
985
+ 'project_type': new_project_type,
986
+ 'price_per_sqm': new_price_per_sqm,
987
+ 'delivery_time': new_delivery_time,
988
+ 'quality_rating': new_quality_rating,
989
+ 'market_share': new_market_share
990
+ })
991
+
992
+ st.success(f"تمت إضافة المنافس بنجاح: {new_competitor}")
993
+
994
+ # تحديث الصفحة لعرض المنافس الجديد
995
+ st.rerun()
996
+ else:
997
+ st.error("يرجى إدخال جميع البيانات المطلوبة بشكل صحيح")
998
+
999
+ # تحليل مقارنة الأسعار
1000
+ st.markdown("### مقارنة الأسعار")
1001
+
1002
+ # إنشاء DataFrame للرسم البياني
1003
+ price_comparison_df = pd.DataFrame(st.session_state.competitive_analysis)
1004
+
1005
+ # ترتيب البيانات تصاعديًا حسب السعر
1006
+ price_comparison_df = price_comparison_df.sort_values('price_per_sqm')
1007
+
1008
+ # إنشاء رسم بياني شريطي
1009
+ fig = px.bar(
1010
+ price_comparison_df,
1011
+ x='competitor',
1012
+ y='price_per_sqm',
1013
+ title='مقارنة الأسعار لكل متر مربع',
1014
+ color='competitor',
1015
+ text_auto=True
1016
+ )
1017
+
1018
+ # تمييز شركتنا
1019
+ for i, competitor in enumerate(price_comparison_df['competitor']):
1020
+ if competitor == 'شركتنا':
1021
+ fig.data[0].marker.color = ['blue' if x != i else 'red' for x in range(len(price_comparison_df))]
1022
+ break
1023
+
1024
+ st.plotly_chart(fig, use_container_width=True)
1025
+
1026
+ # تحليل مقارنة الجودة والسعر
1027
+ st.markdown("### مقارنة الجودة والسعر")
1028
+
1029
+ # إنشاء رسم بياني للعلاقة بين السعر والجودة
1030
+ fig = px.scatter(
1031
+ price_comparison_df,
1032
+ x='price_per_sqm',
1033
+ y='quality_rating',
1034
+ size='market_share',
1035
+ color='competitor',
1036
+ title='العلاقة بين السعر والجودة والحصة السوقية',
1037
+ labels={
1038
+ 'price_per_sqm': 'السعر لكل متر مربع (ريال)',
1039
+ 'quality_rating': 'تقييم الجودة',
1040
+ 'market_share': 'الحصة السوقية (%)'
1041
+ },
1042
+ text='competitor'
1043
+ )
1044
+
1045
+ fig.update_traces(textposition='top center')
1046
+
1047
+ # إضافة خط اتجاه
1048
+ fig.update_layout(
1049
+ shapes=[
1050
+ dict(
1051
+ type='line',
1052
+ x0=min(price_comparison_df['price_per_sqm']),
1053
+ y0=min(price_comparison_df['quality_rating']),
1054
+ x1=max(price_comparison_df['price_per_sqm']),
1055
+ y1=max(price_comparison_df['quality_rating']),
1056
+ line=dict(color='gray', dash='dash')
1057
+ )
1058
+ ]
1059
+ )
1060
+
1061
+ st.plotly_chart(fig, use_container_width=True)
1062
+
1063
+ # تحليل مقارنة مدة التسليم
1064
+ st.markdown("### مقارنة مدة التسليم")
1065
+
1066
+ # ترتيب البيانات تصاعديًا حسب مدة التسليم
1067
+ delivery_comparison_df = price_comparison_df.sort_values('delivery_time')
1068
+
1069
+ # إنشاء رسم بياني شريطي
1070
+ fig = px.bar(
1071
+ delivery_comparison_df,
1072
+ x='competitor',
1073
+ y='delivery_time',
1074
+ title='مقارنة مدة التسليم (شهر)',
1075
+ color='competitor',
1076
+ text_auto=True
1077
+ )
1078
+
1079
+ # تمييز شركتنا
1080
+ for i, competitor in enumerate(delivery_comparison_df['competitor']):
1081
+ if competitor == 'شركتنا':
1082
+ fig.data[0].marker.color = ['blue' if x != i else 'red' for x in range(len(delivery_comparison_df))]
1083
+ break
1084
+
1085
+ st.plotly_chart(fig, use_container_width=True)
1086
+
1087
+ # تحليل الحصة السوقية
1088
+ st.markdown("### تحليل الحصة السوقية")
1089
+
1090
+ # إنشاء رسم بياني دائري للحصة السوقية
1091
+ fig = px.pie(
1092
+ price_comparison_df,
1093
+ values='market_share',
1094
+ names='competitor',
1095
+ title='توزيع الحصة السوقية',
1096
+ hole=0.4
1097
+ )
1098
+
1099
+ st.plotly_chart(fig, use_container_width=True)
1100
+
1101
+ def _render_reports_tab(self):
1102
+ """عرض تبويب التقارير"""
1103
+
1104
+ st.markdown("### تقارير التسعير")
1105
+
1106
+ # اختيار نوع التقرير
1107
+ report_type = st.selectbox(
1108
+ "اختر نوع التقرير",
1109
+ [
1110
+ "ملخص التسعير",
1111
+ "تقرير جدول الكميات",
1112
+ "تقرير تحليل التكاليف",
1113
+ "تقرير سيناريوهات التسعير",
1114
+ "تقرير المقارنة التنافسية",
1115
+ "التقرير الشامل"
1116
+ ]
1117
+ )
1118
+
1119
+ # عرض معلومات المشروع
1120
+ col1, col2 = st.columns(2)
1121
+
1122
+ with col1:
1123
+ project_name = st.text_input("اسم المشروع", "مشروع إنشاء مبنى سكني")
1124
+ client_name = st.text_input("اسم العميل", "شركة التطوير العقاري")
1125
+
1126
+ with col2:
1127
+ project_location = st.text_input("موقع المشروع", "الرياض، المملكة العربية السعودية")
1128
+ report_date = st.date_input("تاريخ التقرير", datetime.now())
1129
+
1130
+ # إنشاء التقرير
1131
+ if st.button("إنشاء التقرير"):
1132
+ st.markdown("### معاينة التقرير")
1133
+
1134
+ # عرض ترويسة التقرير
1135
+ st.markdown(f"""
1136
+ ## {report_type}
1137
+ **اسم المشروع:** {project_name}
1138
+ **اسم العميل:** {client_name}
1139
+ **موقع المشروع:** {project_location}
1140
+ **تاريخ التقرير:** {report_date.strftime('%Y-%m-%d')}
1141
+ """)
1142
+
1143
+ # عرض محتوى التقرير حسب النوع المختار
1144
+ if report_type == "ملخص التسعير" or report_type == "التقرير الشامل":
1145
+ self._render_pricing_summary_report()
1146
+
1147
+ if report_type == "تقرير جدول الكميات" or report_type == "التقرير الشامل":
1148
+ self._render_boq_report()
1149
+
1150
+ if report_type == "تقرير تحليل التكاليف" or report_type == "التقرير الشامل":
1151
+ self._render_cost_analysis_report()
1152
+
1153
+ if report_type == "تقرير سيناريوهات التسعير" or report_type == "التقرير الشامل":
1154
+ self._render_pricing_scenarios_report()
1155
+
1156
+ if report_type == "تقرير المقارنة التنافسية" or report_type == "التقرير الشامل":
1157
+ self._render_competitive_analysis_report()
1158
+
1159
+ # خيارات تصدير التقرير
1160
+ st.markdown("### تصدير التقرير")
1161
+
1162
+ export_format = st.radio(
1163
+ "اختر صيغة التصدير",
1164
+ ["PDF", "Excel", "Word"],
1165
+ horizontal=True
1166
+ )
1167
+
1168
+ if st.button("تصدير التقرير"):
1169
+ st.success(f"تم تصدير التقرير بصيغة {export_format} بنجاح!")
1170
+
1171
+ def _render_pricing_summary_report(self):
1172
+ """عرض تقرير ملخص التسعير"""
1173
+
1174
+ st.markdown("## ملخص التسعير")
1175
+
1176
+ # حساب إجمالي التكاليف
1177
+ total_direct_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف مباشرة')
1178
+ total_indirect_cost = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'تكاليف غير مباشرة')
1179
+ total_profit = sum(item['amount'] for item in st.session_state.cost_analysis if item['category'] == 'أرباح')
1180
+ total_cost = total_direct_cost + total_indirect_cost
1181
+ total_price = total_cost + total_profit
1182
+
1183
+ # عرض ملخص التكاليف
1184
+ st.markdown("### ملخص التكاليف")
1185
+
1186
+ summary_data = {
1187
+ 'البند': ['التكاليف المباشرة', 'التكاليف غير المباشرة', 'إجمالي التكاليف', 'هامش الربح', 'السعر الإجمالي'],
1188
+ 'المبلغ (ريال)': [total_direct_cost, total_indirect_cost, total_cost, total_profit, total_price],
1189
+ 'النسبة المئوية': [
1190
+ total_direct_cost / total_price * 100,
1191
+ total_indirect_cost / total_price * 100,
1192
+ total_cost / total_price * 100,
1193
+ total_profit / total_price * 100,
1194
+ 100.0
1195
+ ]
1196
+ }
1197
+
1198
+ summary_df = pd.DataFrame(summary_data)
1199
+
1200
+ st.dataframe(
1201
+ summary_df,
1202
+ column_config={
1203
+ 'البند': st.column_config.TextColumn('البند'),
1204
+ 'المبلغ (ريال)': st.column_config.NumberColumn('المبلغ (ريال)', format='%d'),
1205
+ 'النسبة المئوية': st.column_config.NumberColumn('النسبة المئوية', format='%.1f%%')
1206
+ },
1207
+ hide_index=True,
1208
+ use_container_width=True
1209
+ )
1210
+
1211
+ # عرض رسم بياني للملخص
1212
+ fig = px.pie(
1213
+ summary_df.iloc[0:3], # استخدام أول 3 صفوف فقط (التكاليف)
1214
+ values='المبلغ (ريال)',
1215
+ names='البند',
1216
+ title='توزيع التكاليف',
1217
+ color_discrete_sequence=px.colors.qualitative.Set3
1218
+ )
1219
+
1220
+ st.plotly_chart(fig, use_container_width=True)
1221
+
1222
+ # عرض رسم بياني للسعر الإجمالي
1223
+ fig = px.pie(
1224
+ summary_df.iloc[[2, 3]], # استخدام صفوف التكاليف والربح
1225
+ values='المبلغ (ريال)',
1226
+ names='البند',
1227
+ title='تركيبة السعر الإجمالي',
1228
+ color_discrete_sequence=px.colors.qualitative.Pastel
1229
+ )
1230
+
1231
+ st.plotly_chart(fig, use_container_width=True)
1232
+
1233
+ def _render_boq_report(self):
1234
+ """عرض تقرير جدول الكميات"""
1235
+
1236
+ st.markdown("## تقرير جدول الكميات")
1237
+
1238
+ # عرض جدول الكميات
1239
+ boq_df = pd.DataFrame(st.session_state.bill_of_quantities)
1240
+
1241
+ st.dataframe(
1242
+ boq_df[['code', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'category']],
1243
+ column_config={
1244
+ 'code': 'الكود',
1245
+ 'description': 'الوصف',
1246
+ 'unit': 'الوحدة',
1247
+ 'quantity': 'الكمية',
1248
+ 'unit_price': st.column_config.NumberColumn('سعر الوحدة', format='%d ريال'),
1249
+ 'total_price': st.column_config.NumberColumn('السعر الإجمالي', format='%d ريال'),
1250
+ 'category': 'الفئة'
1251
+ },
1252
+ hide_index=True,
1253
+ use_container_width=True
1254
+ )
1255
+
1256
+ # عرض ملخص جدول الكميات حسب الفئة
1257
+ st.markdown("### ملخص جدول الكميات حسب الفئة")
1258
+
1259
+ # تجميع البيانات حسب الفئة
1260
+ category_totals = {}
1261
+ for item in st.session_state.bill_of_quantities:
1262
+ category = item['category']
1263
+ if category in category_totals:
1264
+ category_totals[category] += item['total_price']
1265
+ else:
1266
+ category_totals[category] = item['total_price']
1267
+
1268
+ # إنشاء DataFrame للملخص
1269
+ category_summary_df = pd.DataFrame({
1270
+ 'الفئة': list(category_totals.keys()),
1271
+ 'المبلغ الإجمالي': list(category_totals.values())
1272
+ })
1273
+
1274
+ # حساب النسبة المئوية
1275
+ total_boq = sum(category_totals.values())
1276
+ category_summary_df['النسبة المئوية'] = category_summary_df['المبلغ الإجمالي'] / total_boq * 100
1277
+
1278
+ # ترتيب البيانات تنازليًا حسب المبلغ
1279
+ category_summary_df = category_summary_df.sort_values('المبلغ الإجمالي', ascending=False)
1280
+
1281
+ st.dataframe(
1282
+ category_summary_df,
1283
+ column_config={
1284
+ 'الفئة': st.column_config.TextColumn('الفئة'),
1285
+ 'المبلغ الإجمالي': st.column_config.NumberColumn('المبلغ الإجمالي', format='%d ريال'),
1286
+ 'النسبة المئوية': st.column_config.NumberColumn('النسبة المئوية', format='%.1f%%')
1287
+ },
1288
+ hide_index=True,
1289
+ use_container_width=True
1290
+ )
1291
+
1292
+ # عرض رسم بياني للملخص
1293
+ fig = px.pie(
1294
+ category_summary_df,
1295
+ values='المبلغ الإجمالي',
1296
+ names='الفئة',
1297
+ title='توزيع تكاليف البنود حسب الفئة',
1298
+ color_discrete_sequence=px.colors.qualitative.Set3
1299
+ )
1300
+
1301
+ st.plotly_chart(fig, use_container_width=True)
1302
+
1303
+ # عرض رسم بي��ني شريطي للملخص
1304
+ fig = px.bar(
1305
+ category_summary_df,
1306
+ x='الفئة',
1307
+ y='المبلغ الإجمالي',
1308
+ title='إجمالي تكلفة البنود حسب الفئة',
1309
+ color='الفئة',
1310
+ text_auto=True
1311
+ )
1312
+
1313
+ st.plotly_chart(fig, use_container_width=True)
1314
+
1315
+ def _render_cost_analysis_report(self):
1316
+ """عرض تقرير تحليل التكاليف"""
1317
+
1318
+ st.markdown("## تقرير تحليل التكاليف")
1319
+
1320
+ # عرض جدول تحليل التكاليف
1321
+ cost_df = pd.DataFrame(st.session_state.cost_analysis)
1322
+
1323
+ st.dataframe(
1324
+ cost_df[['category', 'subcategory', 'description', 'amount', 'percentage']],
1325
+ column_config={
1326
+ 'category': 'الفئة',
1327
+ 'subcategory': 'الفئة الفرعية',
1328
+ 'description': 'الوصف',
1329
+ 'amount': st.column_config.NumberColumn('المبلغ', format='%d ريال'),
1330
+ 'percentage': st.column_config.NumberColumn('النسبة المئوية', format='%.1f%%')
1331
+ },
1332
+ hide_index=True,
1333
+ use_container_width=True
1334
+ )
1335
+
1336
+ # عرض ملخص تحليل التكاليف حسب الفئة
1337
+ st.markdown("### ملخص تحليل التكاليف حسب الفئة")
1338
+
1339
+ # تجميع البيانات حسب الفئة
1340
+ category_totals = {}
1341
+ for item in st.session_state.cost_analysis:
1342
+ category = item['category']
1343
+ if category in category_totals:
1344
+ category_totals[category] += item['amount']
1345
+ else:
1346
+ category_totals[category] = item['amount']
1347
+
1348
+ # إنشاء DataFrame للملخص
1349
+ category_summary_df = pd.DataFrame({
1350
+ 'الفئة': list(category_totals.keys()),
1351
+ 'المبلغ الإجمالي': list(category_totals.values())
1352
+ })
1353
+
1354
+ # حساب النسبة المئوية
1355
+ total_cost = sum(category_totals.values())
1356
+ category_summary_df['النسبة المئوية'] = category_summary_df['المبلغ الإجمالي'] / total_cost * 100
1357
+
1358
+ # ترتيب البيانات تنازليًا حسب المبلغ
1359
+ category_summary_df = category_summary_df.sort_values('المبلغ الإجمالي', ascending=False)
1360
+
1361
+ st.dataframe(
1362
+ category_summary_df,
1363
+ column_config={
1364
+ 'الفئة': st.column_config.TextColumn('الفئة'),
1365
+ 'المبلغ الإجمالي': st.column_config.NumberColumn('المبلغ الإجمالي', format='%d ريال'),
1366
+ 'النسبة المئوية': st.column_config.NumberColumn('النسبة المئوية', format='%.1f%%')
1367
+ },
1368
+ hide_index=True,
1369
+ use_container_width=True
1370
+ )
1371
+
1372
+ # عرض رسم بياني للملخص
1373
+ fig = px.pie(
1374
+ category_summary_df,
1375
+ values='المبلغ الإجمالي',
1376
+ names='الفئة',
1377
+ title='توزيع التكاليف حسب الفئة',
1378
+ color_discrete_sequence=px.colors.qualitative.Set3
1379
+ )
1380
+
1381
+ st.plotly_chart(fig, use_container_width=True)
1382
+
1383
+ # عرض ملخص تحليل التكاليف حسب الفئة والفئة الفرعية
1384
+ st.markdown("### ملخص تحليل التكاليف حسب الفئة والفئة الفرعية")
1385
+
1386
+ # تجميع البيانات حسب الفئة والفئة الفرعية
1387
+ subcategory_totals = {}
1388
+ for item in st.session_state.cost_analysis:
1389
+ category = item['category']
1390
+ subcategory = item['subcategory']
1391
+ key = f"{category} - {subcategory}"
1392
+ if key in subcategory_totals:
1393
+ subcategory_totals[key] += item['amount']
1394
+ else:
1395
+ subcategory_totals[key] = item['amount']
1396
+
1397
+ # إنشاء DataFrame للملخص
1398
+ subcategory_summary_df = pd.DataFrame({
1399
+ 'الفئة والفئة الفرعية': list(subcategory_totals.keys()),
1400
+ 'المبلغ الإجمالي': list(subcategory_totals.values())
1401
+ })
1402
+
1403
+ # حساب النسبة المئوية
1404
+ subcategory_summary_df['النسبة المئوية'] = subcategory_summary_df['المبلغ الإجمالي'] / total_cost * 100
1405
+
1406
+ # ترتيب البيانات تنازليًا حسب المبلغ
1407
+ subcategory_summary_df = subcategory_summary_df.sort_values('المبلغ الإجمالي', ascending=False)
1408
+
1409
+ st.dataframe(
1410
+ subcategory_summary_df,
1411
+ column_config={
1412
+ 'الفئة والفئة الفرعية': st.column_config.TextColumn('الفئة والفئة الفرعية'),
1413
+ 'المبلغ الإجمالي': st.column_config.NumberColumn('المبلغ الإجمالي', format='%d ريال'),
1414
+ 'النسبة المئوية': st.column_config.NumberColumn('النسبة المئوية', format='%.1f%%')
1415
+ },
1416
+ hide_index=True,
1417
+ use_container_width=True
1418
+ )
1419
+
1420
+ # عرض رسم بياني للملخص
1421
+ fig = px.bar(
1422
+ subcategory_summary_df,
1423
+ x='الفئة والفئة الفرعية',
1424
+ y='المبلغ الإجمالي',
1425
+ title='توزيع التكاليف حسب الفئة والفئة الفرعية',
1426
+ color='الفئة والفئة الفرعية',
1427
+ text_auto=True
1428
+ )
1429
+
1430
+ st.plotly_chart(fig, use_container_width=True)
1431
+
1432
+ # عرض تحليل هيكل التكاليف
1433
+ st.markdown("### تحليل هيكل التكاليف")
1434
+
1435
+ # إنشاء رسم بياني للنسب المئوية
1436
+ fig = px.treemap(
1437
+ cost_df,
1438
+ path=['category', 'subcategory', 'description'],
1439
+ values='amount',
1440
+ title='تحليل هيكل التكاليف',
1441
+ color='percentage',
1442
+ color_continuous_scale='RdBu',
1443
+ color_continuous_midpoint=np.average(cost_df['percentage'])
1444
+ )
1445
+
1446
+ st.plotly_chart(fig, use_container_width=True)
1447
+
1448
+ def _render_pricing_scenarios_report(self):
1449
+ """عرض تقرير سيناريوهات التسعير"""
1450
+
1451
+ st.markdown("## تقرير سيناريوهات التسعير")
1452
+
1453
+ # عرض جدول سيناريوهات التسعير
1454
+ scenarios_df = pd.DataFrame(st.session_state.price_scenarios)
1455
+
1456
+ st.dataframe(
1457
+ scenarios_df[['name', 'description', 'total_cost', 'profit_margin', 'total_price', 'is_active']],
1458
+ column_config={
1459
+ 'name': 'اسم السيناريو',
1460
+ 'description': 'الوصف',
1461
+ 'total_cost': st.column_config.NumberColumn('إجمالي التكلفة', format='%d ريال'),
1462
+ 'profit_margin': st.column_config.NumberColumn('هامش الربح', format='%.1f%%'),
1463
+ 'total_price': st.column_config.NumberColumn('السعر الإجمالي', format='%d ريال'),
1464
+ 'is_active': st.column_config.CheckboxColumn('نشط')
1465
+ },
1466
+ hide_index=True,
1467
+ use_container_width=True
1468
+ )
1469
+
1470
+ # عرض مقارنة السيناريوهات
1471
+ st.markdown("### مقارنة السيناريوهات")
1472
+
1473
+ # إنشاء رسم بياني للمقارنة
1474
+ fig = go.Figure()
1475
+
1476
+ for scenario in st.session_state.price_scenarios:
1477
+ fig.add_trace(go.Bar(
1478
+ name=scenario['name'],
1479
+ x=['التكلفة', 'الربح', 'السعر الإجمالي'],
1480
+ y=[
1481
+ scenario['total_cost'],
1482
+ scenario['total_price'] - scenario['total_cost'],
1483
+ scenario['total_price']
1484
+ ],
1485
+ text=[
1486
+ f"{scenario['total_cost']:,.0f}",
1487
+ f"{scenario['total_price'] - scenario['total_cost']:,.0f}",
1488
+ f"{scenario['total_price']:,.0f}"
1489
+ ],
1490
+ textposition='auto'
1491
+ ))
1492
+
1493
+ fig.update_layout(
1494
+ title='مقارنة السيناريوهات',
1495
+ barmode='group',
1496
+ xaxis_title='العنصر',
1497
+ yaxis_title='المبلغ (ريال)'
1498
+ )
1499
+
1500
+ st.plotly_chart(fig, use_container_width=True)
1501
+
1502
+ # عرض مقارنة هوامش الربح
1503
+ st.markdown("### مقارنة هوامش الربح")
1504
+
1505
+ # إنشاء DataFrame للمقارنة
1506
+ profit_comparison_df = pd.DataFrame({
1507
+ 'السيناريو': [scenario['name'] for scenario in st.session_state.price_scenarios],
1508
+ 'هامش الربح (%)': [scenario['profit_margin'] for scenario in st.session_state.price_scenarios],
1509
+ 'مبلغ الربح (ريال)': [scenario['total_price'] - scenario['total_cost'] for scenario in st.session_state.price_scenarios]
1510
+ })
1511
+
1512
+ # ترتيب البيانات تنازليًا حسب هامش الربح
1513
+ profit_comparison_df = profit_comparison_df.sort_values('هامش الربح (%)', ascending=False)
1514
+
1515
+ # إنشاء رسم بياني شريطي
1516
+ fig = px.bar(
1517
+ profit_comparison_df,
1518
+ x='السيناريو',
1519
+ y='هامش الربح (%)',
1520
+ title='مقارنة هوامش الربح',
1521
+ color='السيناريو',
1522
+ text_auto=True
1523
+ )
1524
+
1525
+ st.plotly_chart(fig, use_container_width=True)
1526
+
1527
+ # عرض تحليل حساسية هامش الربح
1528
+ st.markdown("### تحليل حساسية هامش الربح")
1529
+
1530
+ # الحصول على التكلفة الإجمالية من السيناريو النشط أو الأول
1531
+ active_scenario = next((s for s in st.session_state.price_scenarios if s['is_active']), st.session_state.price_scenarios[0])
1532
+ total_cost = active_scenario['total_cost']
1533
+
1534
+ # إنشاء بيانات لتحليل الحساسية
1535
+ profit_margins = list(range(5, 26, 1)) # من 5% إلى 25%
1536
+ total_prices = [total_cost * (1 + margin/100) for margin in profit_margins]
1537
+
1538
+ # إنشاء DataFrame للرسم البياني
1539
+ sensitivity_df = pd.DataFrame({
1540
+ 'هامش الربح (%)': profit_margins,
1541
+ 'السعر الإجمالي': total_prices
1542
+ })
1543
+
1544
+ # إنشاء رسم بياني خطي
1545
+ fig = px.line(
1546
+ sensitivity_df,
1547
+ x='هامش الربح (%)',
1548
+ y='السعر الإجمالي',
1549
+ title='تحليل حساسية هامش الربح',
1550
+ markers=True
1551
+ )
1552
+
1553
+ # إضافة خط أفقي يمثل السعر التنافسي (افتراضي)
1554
+ competitive_price = 650000
1555
+ fig.add_hline(
1556
+ y=competitive_price,
1557
+ line_dash="dash",
1558
+ line_color="red",
1559
+ annotation_text="السعر التنافسي",
1560
+ annotation_position="bottom right"
1561
+ )
1562
+
1563
+ st.plotly_chart(fig, use_container_width=True)
1564
+
1565
+ def _render_competitive_analysis_report(self):
1566
+ """عرض تقرير المقارنة التنافسية"""
1567
+
1568
+ st.markdown("## تقرير المقارنة التنافسية")
1569
+
1570
+ # عرض جدول المقارنة التنافسية
1571
+ competitive_df = pd.DataFrame(st.session_state.competitive_analysis)
1572
+
1573
+ st.dataframe(
1574
+ competitive_df[['competitor', 'project_type', 'price_per_sqm', 'delivery_time', 'quality_rating', 'market_share']],
1575
+ column_config={
1576
+ 'competitor': 'المنافس',
1577
+ 'project_type': 'نوع المشروع',
1578
+ 'price_per_sqm': st.column_config.NumberColumn('السعر لكل متر مربع', format='%d ريال'),
1579
+ 'delivery_time': st.column_config.NumberColumn('مدة التسليم (شهر)', format='%d'),
1580
+ 'quality_rating': st.column_config.NumberColumn('تقييم الجودة', format='%.1f/5.0'),
1581
+ 'market_share': st.column_config.NumberColumn('الحصة السوقية', format='%.1f%%')
1582
+ },
1583
+ hide_index=True,
1584
+ use_container_width=True
1585
+ )
1586
+
1587
+ # عرض مقارنة الأسعار
1588
+ st.markdown("### مقارنة الأسعار")
1589
+
1590
+ # ترتيب البيانات تصاعديًا حسب السعر
1591
+ price_comparison_df = competitive_df.sort_values('price_per_sqm')
1592
+
1593
+ # إنشاء رسم بياني شريطي
1594
+ fig = px.bar(
1595
+ price_comparison_df,
1596
+ x='competitor',
1597
+ y='price_per_sqm',
1598
+ title='مقارنة الأسعار لكل متر مربع',
1599
+ color='competitor',
1600
+ text_auto=True
1601
+ )
1602
+
1603
+ # تمييز شركتنا
1604
+ for i, competitor in enumerate(price_comparison_df['competitor']):
1605
+ if competitor == 'شركتنا':
1606
+ fig.data[0].marker.color = ['blue' if x != i else 'red' for x in range(len(price_comparison_df))]
1607
+ break
1608
+
1609
+ st.plotly_chart(fig, use_container_width=True)
1610
+
1611
+ # عرض مقارنة الجودة والسعر
1612
+ st.markdown("### مقارنة الجودة والسعر")
1613
+
1614
+ # إنشاء رسم بياني للعلاقة بين السعر والجودة
1615
+ fig = px.scatter(
1616
+ price_comparison_df,
1617
+ x='price_per_sqm',
1618
+ y='quality_rating',
1619
+ size='market_share',
1620
+ color='competitor',
1621
+ title='العلاقة بين السعر والجودة والحصة السوقية',
1622
+ labels={
1623
+ 'price_per_sqm': 'السعر لكل متر مربع (ريال)',
1624
+ 'quality_rating': 'تقييم الجودة',
1625
+ 'market_share': 'الحصة السوقية (%)'
1626
+ },
1627
+ text='competitor'
1628
+ )
1629
+
1630
+ fig.update_traces(textposition='top center')
1631
+
1632
+ # إضافة خط اتجاه
1633
+ fig.update_layout(
1634
+ shapes=[
1635
+ dict(
1636
+ type='line',
1637
+ x0=min(price_comparison_df['price_per_sqm']),
1638
+ y0=min(price_comparison_df['quality_rating']),
1639
+ x1=max(price_comparison_df['price_per_sqm']),
1640
+ y1=max(price_comparison_df['quality_rating']),
1641
+ line=dict(color='gray', dash='dash')
1642
+ )
1643
+ ]
1644
+ )
1645
+
1646
+ st.plotly_chart(fig, use_container_width=True)
1647
+
1648
+ # عرض مقارنة مدة التسليم
1649
+ st.markdown("### مقارنة مدة التسليم")
1650
+
1651
+ # ترتيب البيانات تصاعديًا حسب مدة التسليم
1652
+ delivery_comparison_df = competitive_df.sort_values('delivery_time')
1653
+
1654
+ # إنشاء رسم بياني شريطي
1655
+ fig = px.bar(
1656
+ delivery_comparison_df,
1657
+ x='competitor',
1658
+ y='delivery_time',
1659
+ title='مقارنة مدة التسليم (شهر)',
1660
+ color='competitor',
1661
+ text_auto=True
1662
+ )
1663
+
1664
+ # تمييز شركتنا
1665
+ for i, competitor in enumerate(delivery_comparison_df['competitor']):
1666
+ if competitor == 'شركتنا':
1667
+ fig.data[0].marker.color = ['blue' if x != i else 'red' for x in range(len(delivery_comparison_df))]
1668
+ break
1669
+
1670
+ st.plotly_chart(fig, use_container_width=True)
1671
+
1672
+ # عرض تحليل الحصة السوقية
1673
+ st.markdown("### تحليل الحصة السوقية")
1674
+
1675
+ # إنشاء رسم بياني دائري للحصة السوقية
1676
+ fig = px.pie(
1677
+ competitive_df,
1678
+ values='market_share',
1679
+ names='competitor',
1680
+ title='توزيع الحصة السوقية',
1681
+ hole=0.4
1682
+ )
1683
+
1684
+ st.plotly_chart(fig, use_container_width=True)
1685
+
1686
+ # عرض تحليل الموقع التنافسي
1687
+ st.markdown("### تحليل الموقع التنافسي")
1688
+
1689
+ # إيجاد بيانات شركتنا
1690
+ our_company = next((item for item in st.session_state.competitive_analysis if item['competitor'] == 'شركتنا'), None)
1691
+
1692
+ if our_company:
1693
+ # حساب متوسطات السوق
1694
+ avg_price = competitive_df['price_per_sqm'].mean()
1695
+ avg_delivery = competitive_df['delivery_time'].mean()
1696
+ avg_quality = competitive_df['quality_rating'].mean()
1697
+
1698
+ # إنشاء بيانات المقارنة
1699
+ comparison_data = {
1700
+ 'المؤشر': ['السعر لكل متر مربع', 'مدة التسليم', 'تقييم الجودة'],
1701
+ 'قيمة شركتنا': [our_company['price_per_sqm'], our_company['delivery_time'], our_company['quality_rating']],
1702
+ 'متوسط السوق': [avg_price, avg_delivery, avg_quality],
1703
+ 'الفرق (%)': [
1704
+ (our_company['price_per_sqm'] - avg_price) / avg_price * 100,
1705
+ (our_company['delivery_time'] - avg_delivery) / avg_delivery * 100,
1706
+ (our_company['quality_rating'] - avg_quality) / avg_quality * 100
1707
+ ]
1708
+ }
1709
+
1710
+ comparison_df = pd.DataFrame(comparison_data)
1711
+
1712
+ st.dataframe(
1713
+ comparison_df,
1714
+ column_config={
1715
+ 'المؤشر': st.column_config.TextColumn('المؤشر'),
1716
+ 'قيمة شركتنا': st.column_config.NumberColumn('قيمة شركتنا'),
1717
+ 'متوسط السوق': st.column_config.NumberColumn('متوسط السوق'),
1718
+ 'الفرق (%)': st.column_config.NumberColumn('الفرق (%)', format='%+.1f%%')
1719
+ },
1720
+ hide_index=True,
1721
+ use_container_width=True
1722
+ )
1723
+
1724
+ # إنشاء رسم بياني راداري للموقع التنافسي
1725
+ # تحويل البيانات إلى نسب مئوية للمقارنة
1726
+ max_price = competitive_df['price_per_sqm'].max()
1727
+ min_price = competitive_df['price_per_sqm'].min()
1728
+ price_range = max_price - min_price
1729
+
1730
+ max_delivery = competitive_df['delivery_time'].max()
1731
+ min_delivery = competitive_df['delivery_time'].min()
1732
+ delivery_range = max_delivery - min_delivery
1733
+
1734
+ # ملاحظة: نقوم بعكس مقياس السعر ومدة التسليم لأن القيم الأقل أفضل
1735
+ normalized_price = 100 - ((our_company['price_per_sqm'] - min_price) / price_range * 100) if price_range > 0 else 50
1736
+ normalized_delivery = 100 - ((our_company['delivery_time'] - min_delivery) / delivery_range * 100) if delivery_range > 0 else 50
1737
+ normalized_quality = (our_company['quality_rating'] / 5) * 100
1738
+ normalized_market_share = (our_company['market_share'] / competitive_df['market_share'].max()) * 100
1739
+
1740
+ # إنشاء رسم بياني راداري
1741
+ fig = go.Figure()
1742
+
1743
+ fig.add_trace(go.Scatterpolar(
1744
+ r=[normalized_price, normalized_delivery, normalized_quality, normalized_market_share],
1745
+ theta=['السعر التنافسي', 'سرعة التسليم', 'الجودة', 'الحصة السوقية'],
1746
+ fill='toself',
1747
+ name='شركتنا'
1748
+ ))
1749
+
1750
+ fig.update_layout(
1751
+ polar=dict(
1752
+ radialaxis=dict(
1753
+ visible=True,
1754
+ range=[0, 100]
1755
+ )
1756
+ ),
1757
+ title='تحليل الموقع التنافسي لشركتنا'
1758
+ )
1759
+
1760
+ st.plotly_chart(fig, use_container_width=True)
pricing_system/docs/user_guide.md ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # نظام تحليل المناقصات وتسعير المشاريع - دليل التثبيت والاستخدام
2
+
3
+ ## مقدمة
4
+
5
+ نظام تحليل المناقصات وتسعير المشاريع هو نظام متكامل يساعد المهندسين في تسعير المناقصات والمشاريع بطريقة احترافية ودقيقة. يوفر النظام مجموعة شاملة من الأدوات والوظائف التي تغطي جميع جوانب عملية التسعير، بدءًا من إدارة جداول الكميات وحتى تحليل المحتوى المحلي وإصدار التقارير.
6
+
7
+ ## متطلبات النظام
8
+
9
+ - Python 3.8 أو أحدث
10
+ - Streamlit 1.10.0 أو أحدث
11
+ - Pandas 1.3.0 أو أحدث
12
+ - NumPy 1.20.0 أو أحدث
13
+ - Matplotlib 3.4.0 أو أحدث
14
+ - Seaborn 0.11.0 أو أحدث
15
+ - Plotly 5.3.0 أو أحدث
16
+ - OpenPyXL 3.0.0 أو أحدث
17
+ - XlsxWriter 3.0.0 أو أحدث
18
+
19
+ ## التثبيت
20
+
21
+ 1. قم بتثبيت Python من الموقع الرسمي: https://www.python.org/downloads/
22
+ 2. قم بتثبيت المكتبات المطلوبة باستخدام الأمر التالي:
23
+
24
+ ```bash
25
+ pip install streamlit pandas numpy matplotlib seaborn plotly openpyxl xlsxwriter
26
+ ```
27
+
28
+ 3. قم بتنزيل ملفات النظام وفك ضغطها في المجلد المطلوب.
29
+ 4. انتقل إلى مجلد النظام واستخدم الأمر التالي لتشغيل النظام:
30
+
31
+ ```bash
32
+ streamlit run app.py
33
+ ```
34
+
35
+ ## هيكل النظام
36
+
37
+ يتكون النظام من الوحدات الرئيسية التالية:
38
+
39
+ 1. **وحدة التسعير (PricingApp)**:
40
+ - إدارة جداول الكميات (BOQ)
41
+ - تحليل التكاليف
42
+ - سيناريوهات التسعير
43
+ - التحليل التنافسي
44
+ - التقارير
45
+
46
+ 2. **وحدة الموارد (ResourcesApp)**:
47
+ - إدارة المعدات
48
+ - إدارة المواد
49
+ - إدارة العمالة
50
+ - إدارة مقاولي الباطن
51
+
52
+ 3. **وحدة الكتالوجات**:
53
+ - كتالوج المعدات
54
+ - كتالوج المواد
55
+ - كتالوج العمالة
56
+ - كتالوج مقاولي الباطن
57
+
58
+ 4. **وحدة التحليل الذكي للأسعار**:
59
+ - تحليل تكاليف البنود
60
+ - تحليل المواد والمعدات والعمالة
61
+ - تحليل التكاليف غير المباشرة
62
+ - تحليل هامش الربح
63
+
64
+ 5. **وحدة الإدارات المساندة**:
65
+ - إدارة تكاليف الإدارات المساندة
66
+ - توزيع التكاليف غير المباشرة
67
+
68
+ 6. **وحدة استراتيجيات التسعير**:
69
+ - التسعير القياسي
70
+ - التسعير المتزن
71
+ - التسعير غير المتزن
72
+ - التسعير الموجه للربحية
73
+ - التسعير بالتجميع
74
+ - التسعير بالمحتوى المحلي
75
+
76
+ 7. **وحدة تحليل المحتوى المحلي**:
77
+ - حساب نسبة المحتوى المحلي
78
+ - تحليل المحتوى المحلي حسب نوع الموارد
79
+ - توصيات لتحسين نسبة المحتوى المحلي
80
+
81
+ ## دليل الاستخدام
82
+
83
+ ### الشاشة الرئيسية
84
+
85
+ عند تشغيل النظام، ستظهر الشاشة الرئيسية التي تحتوي على قائمة بالوحدات الرئيسية:
86
+
87
+ - التسعير
88
+ - الموارد
89
+ - المشاريع
90
+ - التقارير
91
+ - الإعدادات
92
+
93
+ اختر الوحدة المطلوبة للانتقال إليها.
94
+
95
+ ### وحدة التسعير
96
+
97
+ تتضمن وحدة التسعير العديد من علامات التبويب:
98
+
99
+ #### جدول الكميات (BOQ)
100
+
101
+ - **استيراد جدول الكميات**: يمكنك استيراد جدول الكميات من ملف Excel.
102
+ - **إضافة بنود يدويًا**: يمكنك إضافة بنود جديدة يدويًا.
103
+ - **تحرير البنود**: يمكنك تحرير البنود الموجودة.
104
+ - **التحليل الذكي للبنود**: يمكنك تحليل كل بند إلى مكوناته (مواد، معدات، عمالة، تكاليف غير مباشرة).
105
+
106
+ #### تحليل التكاليف
107
+
108
+ - **تحليل التكاليف الإجمالية**: عرض تحليل التكاليف الإجمالية للمشروع.
109
+ - **تحليل التكاليف حسب البنود**: عرض تحليل التكاليف لكل بند.
110
+ - **تحليل المواد الأكثر تكلفة**: عرض المواد الأكثر تكلفة في المشروع.
111
+ - **تحليل المعدات الأكثر تكلفة**: عرض المعدات الأكثر تكلفة في المشروع.
112
+
113
+ #### سيناريوهات التسعير
114
+
115
+ - **استراتيجيات التسعير**: يمكنك اختيار استراتيجية التسعير المناسبة وتطبيقها.
116
+ - **مقارنة استراتيجيات التسعير**: يمكنك مقارنة نتائج استراتيجيات التسعير ال��ختلفة.
117
+
118
+ #### كتالوجات الموارد
119
+
120
+ - **كتالوج المعدات**: إدارة كتالوج المعدات واستيراده من Excel.
121
+ - **كتالوج المواد**: إدارة كتالوج المواد واستيراده من Excel.
122
+ - **كتالوج العمالة**: إدارة كتالوج العمالة واستيراده من Excel.
123
+ - **كتالوج مقاولي الباطن**: إدارة كتالوج مقاولي الباطن واستيراده من Excel.
124
+
125
+ #### الإدارات المساندة
126
+
127
+ - **إدارة الإدارات المساندة**: إضافة وتحرير الإدارات المساندة.
128
+ - **تحليل تكاليف الإدارات المساندة**: عرض تحليل تكاليف الإدارات المساندة.
129
+ - **توزيع التكاليف**: توزيع تكاليف الإدارات المساندة على بنود المشروع.
130
+
131
+ #### المحتوى المحلي
132
+
133
+ - **تحليل المحتوى المحلي**: عرض تحليل المحتوى المحلي للمشروع.
134
+ - **تحليل المحتوى المحلي حسب نوع الموارد**: عرض تحليل المحتوى المحلي حسب نوع الموارد.
135
+ - **توصيات لتحسين نسبة المحتوى المحلي**: عرض توصيات لتحسين نسبة المحتوى المحلي.
136
+
137
+ ### وحدة الموارد
138
+
139
+ تتضمن وحدة الموارد إدارة الموارد المختلفة:
140
+
141
+ - **إدارة المعدات**: إضافة وتحرير المعدات.
142
+ - **إدارة المواد**: إضافة وتحرير المواد.
143
+ - **إدارة العمالة**: إضافة وتحرير العمالة.
144
+ - **إدارة مقاولي الباطن**: إضافة وتحرير مقاولي الباطن.
145
+
146
+ ### وحدة المشاريع
147
+
148
+ تتضمن وحدة المشاريع إدارة المشاريع:
149
+
150
+ - **قائمة المشاريع**: عرض قائمة المشاريع.
151
+ - **إضافة مشروع جديد**: إضافة مشروع جديد.
152
+ - **تفاصيل المشروع**: عرض تفاصيل المشروع.
153
+
154
+ ### وحدة التقارير
155
+
156
+ تتضمن وحدة التقارير إنشاء وعرض التقارير المختلفة:
157
+
158
+ - **تقرير ملخص المشروع**: إنشاء تقرير ملخص للمشروع.
159
+ - **تقرير تحليل التكاليف**: إنشاء تقرير تحليل التكاليف.
160
+ - **تقرير سيناريوهات التسعير**: إنشاء تقرير سيناريوهات التسعير.
161
+ - **تقرير المحتوى المحلي**: إنشاء تقرير المحتوى المحلي.
162
+ - **تقرير الإدارات المساندة**: إنشاء تقرير الإدارات المساندة.
163
+ - **تقرير المقارنة التنافسية**: إنشاء تقرير المقارنة التنافسية.
164
+ - **تقرير الموارد المستخدمة**: إنشاء تقرير الموارد المستخدمة.
165
+
166
+ ### وحدة الإعدادات
167
+
168
+ تتضمن وحدة الإعدادات تخصيص إعدادات النظام:
169
+
170
+ - **إعدادات عامة**: تخصيص الإعدادات العامة مثل اللغة والسمة.
171
+ - **إعدادات المستخدم**: تخصيص إعدادات المستخدم.
172
+ - **إعدادات النظام**: تخصيص إعدادات النظام.
173
+ - **النسخ الاحتياطي**: إنشاء واستعادة النسخ الاحتياطية.
174
+
175
+ ## الميزات الرئيسية
176
+
177
+ ### 1. إدارة البنود (BOQ)
178
+
179
+ - استيراد جداول الكميات من Excel
180
+ - إدخال البنود يدويًا وتحريرها
181
+ - تحليل كل بند إلى مكوناته
182
+
183
+ ### 2. التحليل الذكي للأسعار
184
+
185
+ - تحليل تفصيلي لتكاليف المواد والمعدات والعمالة
186
+ - حساب المصاريف غير المباشرة وهامش الربح
187
+ - تحليل التكاليف الإجمالية للمشروع
188
+
189
+ ### 3. كتالوجات الموارد
190
+
191
+ - كتالوج شامل للمعدات المستخدمة في مشاريع البنية التحتية والصرف الصحي والطرق والسيول والكباري
192
+ - كتالوج شامل للمواد المستخدمة في هذه المشاريع مع الأسعار التقريبية للسوق السعودي
193
+ - كتالوج شامل للعمالة والمهندسين مع الأسعار بالساعة واليوم والأسبوع والشهر
194
+ - كتالوج شامل لمقاولي الباطن المتخصصين في أعمال اليوتيلتيز والكهرباء وأنظمة ITC وCCTV وأنظمة التحكم وشبكات الري
195
+
196
+ ### 4. إدارة الإدارات المساندة
197
+
198
+ - إدارة تكاليف الإدارات المساندة المختلفة
199
+ - توزيع التكاليف غير المباشرة على بنود المشروع
200
+
201
+ ### 5. استراتيجيات التسعير المتقدمة
202
+
203
+ - التسعير القياسي: تحديد سعر كل بند بناءً على تكلفته الفعلية مضافاً إليها نسبة ربح ث��بتة
204
+ - التسعير المتزن: توزيع هامش الربح بشكل متوازن على جميع بنود المشروع مع مراعاة المخاطر
205
+ - التسعير غير المتزن: زيادة أسعار البنود المبكرة في المشروع وتخفيض أسعار البنود المتأخرة
206
+ - التسعير الموجه للربحية: زيادة أسعار البنود ذات التكلفة المنخفضة والكميات الكبيرة
207
+ - التسعير بالتجميع: تجميع البنود المتشابهة وتسعيرها كمجموعة واحدة
208
+ - التسعير بالمحتوى المحلي: زيادة نسبة المحتوى المحلي في المشروع لتحقيق متطلبات الجهات المالكة
209
+
210
+ ### 6. تحليل المحتوى المحلي
211
+
212
+ - حساب نسبة المحتوى المحلي في المشروع
213
+ - تحليل المحتوى المحلي حسب نوع الموارد
214
+ - توصيات لتحسين نسبة المحتوى المحلي
215
+
216
+ ### 7. التقارير المتقدمة
217
+
218
+ - تقارير تفصيلية وإجمالية للمشروع
219
+ - تقارير تحليل التكاليف
220
+ - تقارير سيناريوهات التسعير
221
+ - تقارير المحتوى المحلي
222
+ - تقارير الإدارات المساندة
223
+ - تقارير المقارنة التنافسية
224
+ - تقارير الموارد المستخدمة
225
+
226
+ ## الدعم الفني
227
+
228
+ للحصول على الدعم الفني، يرجى التواصل معنا عبر:
229
+
230
+ - البريد الإلكتروني: [email protected]
231
+ - الهاتف: +966 12 345 6789
232
+
233
+ ## حقوق الملكية
234
+
235
+ جميع حقوق الملكية محفوظة © 2025 نظام تحليل المناقصات وتسعير المشاريع.
pricing_system/integrated_app.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sys
3
+ import os
4
+
5
+ # إضافة مسار الوحدات الجديدة
6
+ sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7
+
8
+ # استيراد إطار التكامل
9
+ from pricing_system.integration_framework import IntegrationFramework
10
+
11
+ # استيراد الوحدات الأصلية
12
+ from modules.pricing.pricing_app import PricingApp
13
+ from modules.resources.resources_app import ResourcesApp
14
+
15
+ class IntegratedApp:
16
+ """
17
+ التطبيق المتكامل الذي يجمع بين النظام القديم والنظام الجديد
18
+ """
19
+
20
+ def __init__(self):
21
+ """
22
+ تهيئة التطبيق المتكامل
23
+ """
24
+ # تهيئة حالة الجلسة إذا لم تكن موجودة
25
+ if 'integrated_app_initialized' not in st.session_state:
26
+ st.session_state.integrated_app_initialized = True
27
+ st.session_state.current_module = "pricing" # القيمة الافتراضية
28
+
29
+ # إنشاء مثيلات من الوحدات الأصلية
30
+ self.pricing_app = PricingApp()
31
+ self.resources_app = ResourcesApp()
32
+
33
+ # إنشاء مثيل من إطار التكامل
34
+ self.integration_framework = IntegrationFramework()
35
+
36
+ # ربط الوحدات مع إطار التكامل
37
+ self.integration_framework.connect_pricing_app(self.pricing_app)
38
+ self.integration_framework.connect_resources_app(self.resources_app)
39
+
40
+ def run(self):
41
+ """
42
+ تشغيل التطبيق المتكامل
43
+ """
44
+ # عرض شعار التطبيق والعنوان
45
+ st.sidebar.image("logo.png", width=200)
46
+ st.sidebar.title("نظام تحليل المناقصات")
47
+
48
+ # عرض قائمة الوحدات الرئيسية
49
+ main_modules = ["التسعير", "الموارد", "المشاريع", "التقارير", "الإعدادات"]
50
+ selected_module = st.sidebar.radio("اختر الوحدة:", main_modules)
51
+
52
+ # تحديد الوحدة الحالية
53
+ if selected_module == "التسعير":
54
+ st.session_state.current_module = "pricing"
55
+ elif selected_module == "الموارد":
56
+ st.session_state.current_module = "resources"
57
+ elif selected_module == "المشاريع":
58
+ st.session_state.current_module = "projects"
59
+ elif selected_module == "التقارير":
60
+ st.session_state.current_module = "reports"
61
+ elif selected_module == "الإعدادات":
62
+ st.session_state.current_module = "settings"
63
+
64
+ # عرض الوحدة المختارة
65
+ if st.session_state.current_module == "pricing":
66
+ self.pricing_app.render()
67
+ elif st.session_state.current_module == "resources":
68
+ self.resources_app.render()
69
+ elif st.session_state.current_module == "projects":
70
+ self._render_projects_module()
71
+ elif st.session_state.current_module == "reports":
72
+ self._render_reports_module()
73
+ elif st.session_state.current_module == "settings":
74
+ self._render_settings_module()
75
+
76
+ def _render_projects_module(self):
77
+ """
78
+ عرض وحدة المشاريع
79
+ """
80
+ st.title("إدارة المشاريع")
81
+
82
+ # عرض قائمة المشاريع
83
+ st.subheader("قائمة المشاريع")
84
+
85
+ # بيانات المشاريع النموذجية
86
+ projects = [
87
+ {"name": "مشروع البنية التحتية للحي السكني", "client": "وزارة الإسكان", "status": "قيد التنفيذ"},
88
+ {"name": "مشروع تطوير شبكة الصرف الصحي", "client": "وزارة البيئة والمياه والزراعة", "status": "مكتمل"},
89
+ {"name": "مشروع إنشاء طريق سريع", "client": "وزارة النقل", "status": "قيد التخطيط"},
90
+ {"name": "مشروع بناء جسر", "client": "أمانة المنطقة الشرقية", "status": "قيد التنفيذ"},
91
+ {"name": "مشروع تصريف مياه السيول", "client": "وزارة البيئة والمياه والزراعة", "status": "قيد التخطيط"}
92
+ ]
93
+
94
+ # عرض المشاريع في جدول
95
+ for i, project in enumerate(projects):
96
+ col1, col2, col3, col4 = st.columns([3, 2, 2, 1])
97
+ with col1:
98
+ st.write(project["name"])
99
+ with col2:
100
+ st.write(project["client"])
101
+ with col3:
102
+ status_color = "green" if project["status"] == "مكتمل" else "blue" if project["status"] == "قيد التنفيذ" else "orange"
103
+ st.markdown(f"<span style='color:{status_color}'>{project['status']}</span>", unsafe_allow_html=True)
104
+ with col4:
105
+ if st.button("عرض", key=f"view_project_{i}"):
106
+ st.session_state.selected_project = project
107
+
108
+ # إضافة مشروع جديد
109
+ st.subheader("إضافة مشروع جديد")
110
+
111
+ with st.form("add_project_form"):
112
+ project_name = st.text_input("اسم المشروع")
113
+ project_client = st.text_input("الجهة المالكة")
114
+ project_location = st.text_input("الموقع")
115
+ project_type = st.selectbox(
116
+ "نوع المشروع",
117
+ ["بنية تحتية", "صرف صحي", "طرق", "سيول", "كباري", "مباني", "أخرى"]
118
+ )
119
+ project_budget = st.number_input("الميزانية التقديرية (ريال)", min_value=0.0, step=100000.0)
120
+
121
+ col1, col2 = st.columns(2)
122
+ with col1:
123
+ project_start_date = st.date_input("تاريخ البدء")
124
+ with col2:
125
+ project_end_date = st.date_input("تاريخ الانتهاء المتوقع")
126
+
127
+ project_description = st.text_area("وصف المشروع")
128
+
129
+ submit_button = st.form_submit_button("إضافة المشروع")
130
+
131
+ if submit_button:
132
+ if project_name and project_client:
133
+ st.success(f"تمت إضافة مشروع {project_name} بنجاح")
134
+ else:
135
+ st.error("يرجى إدخال اسم المشروع والجهة المالكة")
136
+
137
+ def _render_reports_module(self):
138
+ """
139
+ عرض وحدة التقارير
140
+ """
141
+ st.title("التقارير")
142
+
143
+ # أنواع التقارير
144
+ report_types = [
145
+ "تقرير ملخص المشروع",
146
+ "تقرير تحليل التكاليف",
147
+ "تقرير سيناريوهات التسعير",
148
+ "تقرير المحتوى المحلي",
149
+ "تقرير الإدارات المساندة",
150
+ "تقرير المقارنة التنافسية",
151
+ "تقرير الموارد المستخدمة"
152
+ ]
153
+
154
+ # اختيار نوع التقرير
155
+ selected_report = st.selectbox("اختر نوع التقرير", report_types)
156
+
157
+ # اختيار المشروع
158
+ projects = [
159
+ "مشروع البنية التحتية للحي السكني",
160
+ "مشروع تطوير شبكة الصرف الصحي",
161
+ "مشروع إنشاء طريق سريع",
162
+ "مشروع بناء جسر",
163
+ "مشروع تصريف مياه السيول"
164
+ ]
165
+
166
+ selected_project = st.selectbox("اختر المشروع", projects)
167
+
168
+ # خيارات التقرير
169
+ st.subheader("خيارات التقرير")
170
+
171
+ col1, col2 = st.columns(2)
172
+
173
+ with col1:
174
+ include_charts = st.checkbox("تضمين الرسوم البيانية", value=True)
175
+ include_details = st.checkbox("تضمين التفاصيل", value=True)
176
+
177
+ with col2:
178
+ report_format = st.radio("تنسيق التقرير", ["PDF", "Excel", "Word"])
179
+ include_logo = st.checkbox("تضمين الشعار", value=True)
180
+
181
+ # إنشاء التقرير
182
+ if st.button("إنشاء التقرير"):
183
+ st.success(f"تم إنشاء {selected_report} لـ {selected_project} بنجاح")
184
+
185
+ # عرض نموذج للتقرير
186
+ st.subheader("معاينة التقرير")
187
+
188
+ if selected_report == "تقرير ملخص المشروع":
189
+ st.write("**معلومات المشروع:**")
190
+ st.write(f"- اسم المشروع: {selected_project}")
191
+ st.write("- الجهة المالكة: وزارة الإسكان")
192
+ st.write("- الموقع: الرياض")
193
+ st.write("- تاريخ البدء: 2023-01-15")
194
+ st.write("- تاريخ الانتهاء المتوقع: 2024-06-30")
195
+ st.write("- الميزانية التقديرية: 25,000,000 ريال")
196
+
197
+ st.write("**ملخص التكاليف:**")
198
+ st.write("- إجمالي التكاليف المباشرة: 18,500,000 ريال")
199
+ st.write("- إجمالي التكاليف غير المباشرة: 3,700,000 ريال")
200
+ st.write("- هامش الربح: 2,800,000 ريال")
201
+ st.write("- إجمالي سعر المشروع: 25,000,000 ريال")
202
+
203
+ if include_charts:
204
+ st.subheader("الرسوم البيانية")
205
+ st.bar_chart({"التكاليف المباشرة": 18.5, "التكاليف غير المباشرة": 3.7, "هامش الربح": 2.8})
206
+
207
+ elif selected_report == "تقرير المحتوى المحلي":
208
+ st.write("**تحليل المحتوى المحلي:**")
209
+ st.write("- نسبة المحتوى المحلي: 42.5%")
210
+ st.write("- النسبة المستهدفة: 40.0%")
211
+ st.write("- الفائض: 2.5%")
212
+
213
+ st.write("**تفاصيل المحتوى المحلي حسب نوع الموارد:**")
214
+ st.write("- المواد: 35.2%")
215
+ st.write("- المعدات: 48.7%")
216
+ st.write("- العمالة: 52.3%")
217
+ st.write("- مقاولي الباطن: 38.1%")
218
+
219
+ if include_charts:
220
+ st.subheader("الرسوم البيانية")
221
+ st.bar_chart({"المواد": 35.2, "المعدات": 48.7, "العمالة": 52.3, "مقاولي الباطن": 38.1})
222
+
223
+ # خيارات تصدير التقرير
224
+ st.download_button(
225
+ label=f"تحميل التقرير بتنسيق {report_format}",
226
+ data=b"تقرير نموذجي",
227
+ file_name=f"{selected_report}_{selected_project}.{report_format.lower()}",
228
+ mime="application/octet-stream"
229
+ )
230
+
231
+ def _render_settings_module(self):
232
+ """
233
+ عرض وحدة الإعدادات
234
+ """
235
+ st.title("الإعدادات")
236
+
237
+ tab1, tab2, tab3, tab4 = st.tabs(["إعدادات عامة", "إعدادات المستخدم", "إعدادات النظام", "النسخ الاحتياطي"])
238
+
239
+ with tab1:
240
+ st.subheader("الإعدادات العامة")
241
+
242
+ # إعدادات اللغة
243
+ st.write("**إعدادات اللغة:**")
244
+ language = st.selectbox("اللغة", ["العربية", "English"])
245
+
246
+ # إعدادات العرض
247
+ st.write("**إعدادات العرض:**")
248
+ theme = st.selectbox("السمة", ["الافتراضية", "داكنة", "فاتحة"])
249
+ sidebar_position = st.radio("موضع الشريط الجانبي", ["يمين", "يسار"])
250
+
251
+ # إعدادات التنبيهات
252
+ st.write("**إعدادات التنبيهات:**")
253
+ enable_notifications = st.checkbox("تفعيل التنبيهات", value=True)
254
+ notification_sound = st.checkbox("تفعيل صوت التنبيهات", value=True)
255
+
256
+ if st.button("حفظ الإعدادات العامة"):
257
+ st.success("تم حفظ الإعدادات العامة بنجاح")
258
+
259
+ with tab2:
260
+ st.subheader("إعدادات المستخدم")
261
+
262
+ # معلومات المستخدم
263
+ st.write("**معلومات المستخدم:**")
264
+ user_name = st.text_input("اسم المستخدم", value="محمد أحمد")
265
+ user_email = st.text_input("البريد الإلكتروني", value="[email protected]")
266
+ user_phone = st.text_input("رقم الهاتف", value="0555555555")
267
+
268
+ # تغيير كلمة المرور
269
+ st.write("**تغيير كلمة المرور:**")
270
+ current_password = st.text_input("كلمة المرور الحالية", type="password")
271
+ new_password = st.text_input("كلمة المرور الجديدة", type="password")
272
+ confirm_password = st.text_input("تأكيد كلمة المرور الجديدة", type="password")
273
+
274
+ if st.button("حفظ إعدادات المستخدم"):
275
+ if new_password and new_password == confirm_password:
276
+ st.success("تم حفظ إعدادات المستخدم بنجاح")
277
+ elif new_password and new_password != confirm_password:
278
+ st.error("كلمة المرور الجديدة وتأكيدها غير متطابقين")
279
+ else:
280
+ st.success("تم حفظ إعدادات المستخدم بنجاح")
281
+
282
+ with tab3:
283
+ st.subheader("إعدادات النظام")
284
+
285
+ # إعدادات قاعدة البيانات
286
+ st.write("**إعدادات قاعدة البيانات:**")
287
+ db_type = st.selectbox("نوع قاعدة البيانات", ["SQLite", "MySQL", "PostgreSQL"])
288
+ db_host = st.text_input("عنوان الخادم", value="localhost")
289
+ db_port = st.text_input("المنفذ", value="3306")
290
+ db_name = st.text_input("اسم قاعدة البيانات", value="tender_analysis_db")
291
+
292
+ # إعدادات النسخ الاحتياطي التلقائي
293
+ st.write("**إعدادات النسخ الاحتياطي التلقائي:**")
294
+ auto_backup = st.checkbox("تفعيل النسخ الاحتياطي التلقائي", value=True)
295
+ backup_frequency = st.selectbox("تكرار ال��سخ الاحتياطي", ["يومي", "أسبوعي", "شهري"])
296
+ backup_time = st.time_input("وقت النسخ الاحتياطي")
297
+
298
+ if st.button("حفظ إعدادات النظام"):
299
+ st.success("تم حفظ إعدادات النظام بنجاح")
300
+
301
+ with tab4:
302
+ st.subheader("النسخ الاحتياطي")
303
+
304
+ # إنشاء نسخة احتياطية
305
+ st.write("**إنشاء نسخة احتياطية:**")
306
+ backup_type = st.radio("نوع النسخ الاحتياطي", ["كامل", "جزئي"])
307
+
308
+ if backup_type == "جزئي":
309
+ st.multiselect(
310
+ "اختر البيانات المراد نسخها",
311
+ ["المشاريع", "المناقصات", "الموارد", "التسعير", "المستخدمين", "الإعدادات"]
312
+ )
313
+
314
+ if st.button("إنشاء نسخة احتياطية"):
315
+ st.success("تم إنشاء النسخة الاحتياطية بنجاح")
316
+ st.download_button(
317
+ label="تحميل النسخة الاحتياطية",
318
+ data=b"نسخة احتياطية نموذجية",
319
+ file_name="backup_2025-04-01.zip",
320
+ mime="application/zip"
321
+ )
322
+
323
+ # استعادة نسخة احتياطية
324
+ st.write("**استعادة نسخة احتياطية:**")
325
+ uploaded_file = st.file_uploader("اختر ملف النسخة الاحتياطية", type=["zip"])
326
+
327
+ if uploaded_file is not None:
328
+ if st.button("استعادة النسخة الاحتياطية"):
329
+ st.success("تم استعادة النسخة الاحتياطية بنجاح")
330
+
331
+ # تشغيل التطبيق المتكامل
332
+ if __name__ == "__main__":
333
+ app = IntegratedApp()
334
+ app.run()
pricing_system/integration_framework.py ADDED
@@ -0,0 +1,1383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sys
3
+ import os
4
+ import pandas as pd
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ import plotly.express as px
9
+ import plotly.graph_objects as go
10
+
11
+ # إضافة مسار الوحدات الجديدة
12
+ sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'pricing_system'))
13
+
14
+ # استيراد الوحدات الجديدة
15
+ from modules.catalogs.equipment_catalog import EquipmentCatalog
16
+ from modules.catalogs.materials_catalog import MaterialsCatalog
17
+ from modules.catalogs.labor_catalog import LaborCatalog
18
+ from modules.catalogs.subcontractors_catalog import SubcontractorsCatalog
19
+ from modules.analysis.smart_price_analysis import SmartPriceAnalysis
20
+ from modules.indirect_support.indirect_support_management import IndirectSupportManagement
21
+ from modules.pricing_strategies.pricing_strategies import PricingStrategies
22
+
23
+ class IntegrationFramework:
24
+ """
25
+ إطار التكامل بين النظام الجديد والنظام القديم
26
+ يربط بين وحدات PricingApp و ResourcesApp مع الوحدات الجديدة
27
+ """
28
+
29
+ def __init__(self):
30
+ """
31
+ تهيئة إطار التكامل وإنشاء مثيلات من جميع الوحدات
32
+ """
33
+ # تهيئة حالة الجلسة إذا لم تكن موجودة
34
+ if 'integration_initialized' not in st.session_state:
35
+ st.session_state.integration_initialized = True
36
+
37
+ # تهيئة وحدات الكتالوج
38
+ st.session_state.equipment_catalog = EquipmentCatalog()
39
+ st.session_state.materials_catalog = MaterialsCatalog()
40
+ st.session_state.labor_catalog = LaborCatalog()
41
+ st.session_state.subcontractors_catalog = SubcontractorsCatalog()
42
+
43
+ # تهيئة وحدات التحليل والتسعير
44
+ st.session_state.smart_price_analysis = SmartPriceAnalysis()
45
+ st.session_state.indirect_support = IndirectSupportManagement()
46
+ st.session_state.pricing_strategies = PricingStrategies()
47
+
48
+ # تهيئة بيانات المشروع
49
+ st.session_state.project_data = {
50
+ 'name': '',
51
+ 'location': '',
52
+ 'client': '',
53
+ 'start_date': None,
54
+ 'end_date': None,
55
+ 'budget': 0.0,
56
+ 'boq_items': [],
57
+ 'resources': [],
58
+ 'pricing_strategy': 'standard',
59
+ 'indirect_costs': {},
60
+ 'profit_margin': 0.15,
61
+ 'local_content_target': 0.40, # هدف المحتوى المحلي (رؤية 2030)
62
+ }
63
+
64
+ # تهيئة بيانات التحليل
65
+ st.session_state.analysis_results = {
66
+ 'direct_costs': 0.0,
67
+ 'indirect_costs': 0.0,
68
+ 'total_costs': 0.0,
69
+ 'profit': 0.0,
70
+ 'total_price': 0.0,
71
+ 'local_content_percentage': 0.0,
72
+ }
73
+
74
+ def connect_pricing_app(self, pricing_app):
75
+ """
76
+ ربط وحدة PricingApp مع إطار التكامل
77
+
78
+ Args:
79
+ pricing_app: مثيل من فئة PricingApp
80
+ """
81
+ self.pricing_app = pricing_app
82
+
83
+ # إضافة الوظائف الجديدة إلى PricingApp
84
+ pricing_app._render_bill_of_quantities_tab_original = pricing_app._render_bill_of_quantities_tab
85
+ pricing_app._render_bill_of_quantities_tab = self._enhanced_render_bill_of_quantities_tab
86
+
87
+ pricing_app._render_cost_analysis_tab_original = pricing_app._render_cost_analysis_tab
88
+ pricing_app._render_cost_analysis_tab = self._enhanced_render_cost_analysis_tab
89
+
90
+ pricing_app._render_pricing_scenarios_tab_original = pricing_app._render_pricing_scenarios_tab
91
+ pricing_app._render_pricing_scenarios_tab = self._enhanced_render_pricing_scenarios_tab
92
+
93
+ # إضافة علامات تبويب جديدة
94
+ pricing_app.tabs.extend([
95
+ "كتالوجات الموارد",
96
+ "الإدارات المساندة",
97
+ "المحتوى المحلي"
98
+ ])
99
+
100
+ # إضافة دوال العرض للعلامات الجديدة
101
+ pricing_app._render_resource_catalogs_tab = self._render_resource_catalogs_tab
102
+ pricing_app._render_indirect_support_tab = self._render_indirect_support_tab
103
+ pricing_app._render_local_content_tab = self._render_local_content_tab
104
+
105
+ # تحديث دالة العرض الرئيسية
106
+ pricing_app_render_original = pricing_app.render
107
+
108
+ def enhanced_render():
109
+ """
110
+ دالة العرض المحسنة لـ PricingApp
111
+ """
112
+ st.sidebar.title("نظام التسعير الذكي")
113
+ selected_tab = st.sidebar.radio("اختر القسم:", pricing_app.tabs)
114
+
115
+ if selected_tab == "جدول الكميات":
116
+ pricing_app._render_bill_of_quantities_tab()
117
+ elif selected_tab == "تحليل التكاليف":
118
+ pricing_app._render_cost_analysis_tab()
119
+ elif selected_tab == "سيناريوهات التسعير":
120
+ pricing_app._render_pricing_scenarios_tab()
121
+ elif selected_tab == "التحليل التنافسي":
122
+ pricing_app._render_competitive_analysis_tab()
123
+ elif selected_tab == "التقارير":
124
+ pricing_app._render_reports_tab()
125
+ elif selected_tab == "كتالوجات الموارد":
126
+ pricing_app._render_resource_catalogs_tab()
127
+ elif selected_tab == "الإدارات المساندة":
128
+ pricing_app._render_indirect_support_tab()
129
+ elif selected_tab == "المحتوى المحلي":
130
+ pricing_app._render_local_content_tab()
131
+
132
+ pricing_app.render = enhanced_render
133
+
134
+ def connect_resources_app(self, resources_app):
135
+ """
136
+ ربط وحدة ResourcesApp مع إطار التكامل
137
+
138
+ Args:
139
+ resources_app: مثيل من فئة ResourcesApp
140
+ """
141
+ self.resources_app = resources_app
142
+
143
+ # إضافة الوظائف الجديدة إلى ResourcesApp
144
+ resources_app_render_original = resources_app.render
145
+
146
+ def enhanced_resources_render():
147
+ """
148
+ دالة العرض المحسنة لـ ResourcesApp
149
+ """
150
+ st.sidebar.title("إدارة الموارد")
151
+
152
+ # استدعاء دالة العرض الأصلية
153
+ resources_app_render_original()
154
+
155
+ # إضافة زر للوصول إلى كتالوجات الموارد
156
+ if st.sidebar.button("عرض كتالوجات الموارد", key="resources_catalogs_btn"):
157
+ st.session_state.current_view = "resource_catalogs"
158
+ self._render_resource_catalogs_tab()
159
+
160
+ resources_app.render = enhanced_resources_render
161
+
162
+ # ربط كتالوجات الموارد مع ResourcesApp
163
+ resources_app.equipment_catalog = st.session_state.equipment_catalog
164
+ resources_app.materials_catalog = st.session_state.materials_catalog
165
+ resources_app.labor_catalog = st.session_state.labor_catalog
166
+ resources_app.subcontractors_catalog = st.session_state.subcontractors_catalog
167
+
168
+ def _enhanced_render_bill_of_quantities_tab(self):
169
+ """
170
+ النسخة المحسنة من دالة عرض علامة تبويب جدول الكميات
171
+ تدمج الوظائف الجديدة مع الوظائف الموجودة
172
+ """
173
+ st.header("جدول الكميات (BOQ)")
174
+
175
+ # استدعاء الدالة الأصلية
176
+ self.pricing_app._render_bill_of_quantities_tab_original()
177
+
178
+ # إضافة وظائف جديدة
179
+ st.subheader("استيراد جدول الكميات من Excel")
180
+
181
+ uploaded_file = st.file_uploader("اختر ملف Excel", type=["xlsx", "xls"], key="boq_uploader")
182
+ if uploaded_file is not None:
183
+ try:
184
+ df = pd.read_excel(uploaded_file)
185
+ st.success(f"تم استيراد {len(df)} بند بنجاح")
186
+ st.dataframe(df)
187
+
188
+ if st.button("إضافة البنود إلى المشروع", key="add_imported_items"):
189
+ # إضافة البنود المستوردة إلى بيانات المشروع
190
+ for _, row in df.iterrows():
191
+ item = {
192
+ 'item_code': row.get('كود البند', ''),
193
+ 'description': row.get('وصف البند', ''),
194
+ 'unit': row.get('الوحدة', ''),
195
+ 'quantity': float(row.get('الكمية', 0)),
196
+ 'unit_price': float(row.get('سعر الوحدة', 0)),
197
+ 'total_price': float(row.get('الإجمالي', 0)),
198
+ }
199
+ st.session_state.project_data['boq_items'].append(item)
200
+
201
+ st.success("تم إضافة البنود إلى المشروع بنجاح")
202
+
203
+ # تحليل البنود باستخدام وحدة التحليل الذكي
204
+ st.session_state.smart_price_analysis.analyze_boq_items(st.session_state.project_data['boq_items'])
205
+
206
+ except Exception as e:
207
+ st.error(f"حدث خطأ أثناء استيراد الملف: {str(e)}")
208
+
209
+ # إضافة قسم التحليل الذكي للبنود
210
+ st.subheader("التحليل الذكي للبنود")
211
+
212
+ if len(st.session_state.project_data['boq_items']) > 0:
213
+ selected_item_index = st.selectbox(
214
+ "اختر البند للتحليل",
215
+ range(len(st.session_state.project_data['boq_items'])),
216
+ format_func=lambda i: f"{st.session_state.project_data['boq_items'][i]['item_code']} - {st.session_state.project_data['boq_items'][i]['description']}"
217
+ )
218
+
219
+ selected_item = st.session_state.project_data['boq_items'][selected_item_index]
220
+
221
+ # عرض تحليل البند
222
+ analysis_result = st.session_state.smart_price_analysis.get_item_analysis(selected_item_index)
223
+
224
+ if analysis_result:
225
+ col1, col2 = st.columns(2)
226
+
227
+ with col1:
228
+ st.write("**تحليل تكاليف البند:**")
229
+ st.write(f"- تكلفة المواد: {analysis_result['materials_cost']} ريال")
230
+ st.write(f"- تكلفة المعدات: {analysis_result['equipment_cost']} ريال")
231
+ st.write(f"- تكلفة العمالة: {analysis_result['labor_cost']} ريال")
232
+ st.write(f"- تكاليف غير مباشرة: {analysis_result['indirect_cost']} ريال")
233
+ st.write(f"- هامش الربح: {analysis_result['profit_margin']} ريال")
234
+ st.write(f"- **إجمالي سعر البند: {analysis_result['total_price']} ريال**")
235
+
236
+ with col2:
237
+ # رسم بياني دائري لتوزيع التكاليف
238
+ fig = px.pie(
239
+ values=[
240
+ analysis_result['materials_cost'],
241
+ analysis_result['equipment_cost'],
242
+ analysis_result['labor_cost'],
243
+ analysis_result['indirect_cost'],
244
+ analysis_result['profit_margin']
245
+ ],
246
+ names=['المواد', 'المعدات', 'العمالة', 'تكاليف غير مباشرة', 'هامش الربح'],
247
+ title="توزيع تكاليف البند"
248
+ )
249
+ st.plotly_chart(fig)
250
+
251
+ # تحرير مكونات البند
252
+ st.subheader("تحرير مكونات البند")
253
+
254
+ tab1, tab2, tab3, tab4 = st.tabs(["المواد", "المعدات", "العمالة", "مقاولي الباطن"])
255
+
256
+ with tab1:
257
+ st.write("**المواد المستخدمة في البند:**")
258
+
259
+ # عرض المواد الحالية
260
+ if 'materials' in analysis_result:
261
+ for i, material in enumerate(analysis_result['materials']):
262
+ col1, col2, col3, col4, col5 = st.columns([3, 2, 2, 2, 1])
263
+ with col1:
264
+ st.text(material['name'])
265
+ with col2:
266
+ st.text(f"الكمية: {material['quantity']} {material['unit']}")
267
+ with col3:
268
+ st.text(f"السعر: {material['price']} ريال/{material['unit']}")
269
+ with col4:
270
+ st.text(f"الإجمالي: {material['total']} ريال")
271
+ with col5:
272
+ if st.button("حذف", key=f"del_mat_{i}"):
273
+ analysis_result['materials'].pop(i)
274
+ st.session_state.smart_price_analysis.update_item_analysis(selected_item_index, analysis_result)
275
+ st.experimental_rerun()
276
+
277
+ # إضافة مواد جديدة
278
+ st.subheader("إضافة مادة جديدة")
279
+
280
+ # عرض قائمة المواد من الكتالوج
281
+ catalog_materials = st.session_state.materials_catalog.get_materials_list()
282
+ selected_material = st.selectbox("اختر المادة من الكتالوج", catalog_materials, key="mat_select")
283
+
284
+ material_details = st.session_state.materials_catalog.get_material_details(selected_material)
285
+
286
+ quantity = st.number_input("الكمية", min_value=0.0, step=0.1, key="mat_quantity")
287
+
288
+ if st.button("إضافة المادة", key="add_material_btn"):
289
+ if 'materials' not in analysis_result:
290
+ analysis_result['materials'] = []
291
+
292
+ new_material = {
293
+ 'name': selected_material,
294
+ 'quantity': quantity,
295
+ 'unit': material_details['unit'],
296
+ 'price': material_details['price'],
297
+ 'total': quantity * material_details['price'],
298
+ 'is_local': material_details['is_local']
299
+ }
300
+
301
+ analysis_result['materials'].append(new_material)
302
+ st.session_state.smart_price_analysis.update_item_analysis(selected_item_index, analysis_result)
303
+ st.success(f"تمت إضافة {selected_material} بنجاح")
304
+ st.experimental_rerun()
305
+
306
+ with tab2:
307
+ st.write("**المعدات المستخدمة في البند:**")
308
+
309
+ # عرض المعدات الحالية
310
+ if 'equipment' in analysis_result:
311
+ for i, equipment in enumerate(analysis_result['equipment']):
312
+ col1, col2, col3, col4, col5 = st.columns([3, 2, 2, 2, 1])
313
+ with col1:
314
+ st.text(equipment['name'])
315
+ with col2:
316
+ st.text(f"المدة: {equipment['duration']} {equipment['duration_unit']}")
317
+ with col3:
318
+ st.text(f"السعر: {equipment['price']} ريال/{equipment['duration_unit']}")
319
+ with col4:
320
+ st.text(f"الإجمالي: {equipment['total']} ريال")
321
+ with col5:
322
+ if st.button("حذف", key=f"del_equip_{i}"):
323
+ analysis_result['equipment'].pop(i)
324
+ st.session_state.smart_price_analysis.update_item_analysis(selected_item_index, analysis_result)
325
+ st.experimental_rerun()
326
+
327
+ # إضافة معدات جديدة
328
+ st.subheader("إضافة معدة جديدة")
329
+
330
+ # عرض قائمة المعدات من الكتالوج
331
+ catalog_equipment = st.session_state.equipment_catalog.get_equipment_list()
332
+ selected_equipment = st.selectbox("اختر المعدة من الكتالوج", catalog_equipment, key="equip_select")
333
+
334
+ equipment_details = st.session_state.equipment_catalog.get_equipment_details(selected_equipment)
335
+
336
+ duration = st.number_input("المدة", min_value=0.0, step=0.5, key="equip_duration")
337
+ duration_unit = st.selectbox("وحدة المدة", ["ساعة", "يوم", "أسبوع", "شهر"], key="equip_duration_unit")
338
+
339
+ if st.button("إضافة المعدة", key="add_equipment_btn"):
340
+ if 'equipment' not in analysis_result:
341
+ analysis_result['equipment'] = []
342
+
343
+ # تحويل السعر حسب وحدة المدة
344
+ price_per_unit = equipment_details['price_per_hour']
345
+ if duration_unit == "يوم":
346
+ price_per_unit = equipment_details['price_per_day']
347
+ elif duration_unit == "أسبوع":
348
+ price_per_unit = equipment_details['price_per_week']
349
+ elif duration_unit == "شهر":
350
+ price_per_unit = equipment_details['price_per_month']
351
+
352
+ new_equipment = {
353
+ 'name': selected_equipment,
354
+ 'duration': duration,
355
+ 'duration_unit': duration_unit,
356
+ 'price': price_per_unit,
357
+ 'total': duration * price_per_unit,
358
+ 'is_local': equipment_details['is_local']
359
+ }
360
+
361
+ analysis_result['equipment'].append(new_equipment)
362
+ st.session_state.smart_price_analysis.update_item_analysis(selected_item_index, analysis_result)
363
+ st.success(f"تمت إضافة {selected_equipment} بنجاح")
364
+ st.experimental_rerun()
365
+
366
+ with tab3:
367
+ st.write("**العمالة المستخدمة في البند:**")
368
+
369
+ # عرض العمالة الحالية
370
+ if 'labor' in analysis_result:
371
+ for i, labor in enumerate(analysis_result['labor']):
372
+ col1, col2, col3, col4, col5 = st.columns([3, 2, 2, 2, 1])
373
+ with col1:
374
+ st.text(labor['name'])
375
+ with col2:
376
+ st.text(f"المدة: {labor['duration']} {labor['duration_unit']}")
377
+ with col3:
378
+ st.text(f"السعر: {labor['price']} ريال/{labor['duration_unit']}")
379
+ with col4:
380
+ st.text(f"الإجمالي: {labor['total']} ريال")
381
+ with col5:
382
+ if st.button("حذف", key=f"del_labor_{i}"):
383
+ analysis_result['labor'].pop(i)
384
+ st.session_state.smart_price_analysis.update_item_analysis(selected_item_index, analysis_result)
385
+ st.experimental_rerun()
386
+
387
+ # إضافة عمالة جديدة
388
+ st.subheader("إضافة عمالة جديدة")
389
+
390
+ # عرض قائمة العمالة من الكتالوج
391
+ catalog_labor = st.session_state.labor_catalog.get_labor_list()
392
+ selected_labor = st.selectbox("اختر العمالة من الكتالوج", catalog_labor, key="labor_select")
393
+
394
+ labor_details = st.session_state.labor_catalog.get_labor_details(selected_labor)
395
+
396
+ duration = st.number_input("المدة", min_value=0.0, step=0.5, key="labor_duration")
397
+ duration_unit = st.selectbox("وحدة المدة", ["ساعة", "يوم", "أسبوع", "شهر"], key="labor_duration_unit")
398
+
399
+ if st.button("إضافة العمالة", key="add_labor_btn"):
400
+ if 'labor' not in analysis_result:
401
+ analysis_result['labor'] = []
402
+
403
+ # تحويل السعر حسب وحدة المدة
404
+ price_per_unit = labor_details['price_per_hour']
405
+ if duration_unit == "يوم":
406
+ price_per_unit = labor_details['price_per_day']
407
+ elif duration_unit == "أسبوع":
408
+ price_per_unit = labor_details['price_per_week']
409
+ elif duration_unit == "شهر":
410
+ price_per_unit = labor_details['price_per_month']
411
+
412
+ new_labor = {
413
+ 'name': selected_labor,
414
+ 'duration': duration,
415
+ 'duration_unit': duration_unit,
416
+ 'price': price_per_unit,
417
+ 'total': duration * price_per_unit,
418
+ 'is_local': labor_details['is_local']
419
+ }
420
+
421
+ analysis_result['labor'].append(new_labor)
422
+ st.session_state.smart_price_analysis.update_item_analysis(selected_item_index, analysis_result)
423
+ st.success(f"تمت إضافة {selected_labor} بنجاح")
424
+ st.experimental_rerun()
425
+
426
+ with tab4:
427
+ st.write("**مقاولي الباطن المستخدمين في البند:**")
428
+
429
+ # عرض مقاولي الباطن الحاليين
430
+ if 'subcontractors' in analysis_result:
431
+ for i, subcontractor in enumerate(analysis_result['subcontractors']):
432
+ col1, col2, col3, col4 = st.columns([3, 3, 3, 1])
433
+ with col1:
434
+ st.text(subcontractor['name'])
435
+ with col2:
436
+ st.text(f"نوع العمل: {subcontractor['work_type']}")
437
+ with col3:
438
+ st.text(f"الإجمالي: {subcontractor['total']} ريال")
439
+ with col4:
440
+ if st.button("حذف", key=f"del_sub_{i}"):
441
+ analysis_result['subcontractors'].pop(i)
442
+ st.session_state.smart_price_analysis.update_item_analysis(selected_item_index, analysis_result)
443
+ st.experimental_rerun()
444
+
445
+ # إضافة مقاول باطن جديد
446
+ st.subheader("إضافة مقاول باطن جديد")
447
+
448
+ # عرض قائمة مقاولي الباطن من الكتالوج
449
+ catalog_subcontractors = st.session_state.subcontractors_catalog.get_subcontractors_list()
450
+ selected_subcontractor = st.selectbox("اختر مقاول الباطن من الكتالوج", catalog_subcontractors, key="sub_select")
451
+
452
+ subcontractor_details = st.session_state.subcontractors_catalog.get_subcontractor_details(selected_subcontractor)
453
+
454
+ work_description = st.text_area("وصف العمل", key="sub_work_desc")
455
+ total_price = st.number_input("السعر الإجمالي", min_value=0.0, step=1000.0, key="sub_price")
456
+
457
+ if st.button("إضافة مقاول الباطن", key="add_sub_btn"):
458
+ if 'subcontractors' not in analysis_result:
459
+ analysis_result['subcontractors'] = []
460
+
461
+ new_subcontractor = {
462
+ 'name': selected_subcontractor,
463
+ 'work_type': subcontractor_details['specialization'],
464
+ 'work_description': work_description,
465
+ 'total': total_price,
466
+ 'is_local': subcontractor_details['is_local']
467
+ }
468
+
469
+ analysis_result['subcontractors'].append(new_subcontractor)
470
+ st.session_state.smart_price_analysis.update_item_analysis(selected_item_index, analysis_result)
471
+ st.success(f"تمت إضافة {selected_subcontractor} بنجاح")
472
+ st.experimental_rerun()
473
+
474
+ def _enhanced_render_cost_analysis_tab(self):
475
+ """
476
+ النسخة المحسنة من دالة عرض علامة تبويب تحليل التكاليف
477
+ تدمج الوظائف الجديدة مع الوظائف الموجودة
478
+ """
479
+ st.header("تحليل التكاليف")
480
+
481
+ # استدعاء الدالة الأصلية
482
+ self.pricing_app._render_cost_analysis_tab_original()
483
+
484
+ # إضافة وظائف جديدة
485
+ st.subheader("التحليل الذكي للتكاليف")
486
+
487
+ # تحليل التكاليف الإجمالية للمشروع
488
+ if len(st.session_state.project_data['boq_items']) > 0:
489
+ # تحليل التكاليف حسب النوع
490
+ cost_analysis = st.session_state.smart_price_analysis.get_project_cost_analysis()
491
+
492
+ col1, col2 = st.columns(2)
493
+
494
+ with col1:
495
+ st.write("**تحليل التكاليف الإجمالية للمشروع:**")
496
+ st.write(f"- تكلفة المواد: {cost_analysis['total_materials_cost']} ريال")
497
+ st.write(f"- تكلفة المعدات: {cost_analysis['total_equipment_cost']} ريال")
498
+ st.write(f"- تكلفة العمالة: {cost_analysis['total_labor_cost']} ريال")
499
+ st.write(f"- تكلفة مقاولي الباطن: {cost_analysis['total_subcontractors_cost']} ريال")
500
+ st.write(f"- التكاليف غير المباشرة: {cost_analysis['total_indirect_cost']} ريال")
501
+ st.write(f"- هامش الربح: {cost_analysis['total_profit_margin']} ريال")
502
+ st.write(f"- **إجمالي تكلفة المشروع: {cost_analysis['total_project_cost']} ريال**")
503
+ st.write(f"- **إجمالي سعر المشروع: {cost_analysis['total_project_price']} ريال**")
504
+
505
+ with col2:
506
+ # رسم بياني دائري لتوزيع التكاليف
507
+ fig = px.pie(
508
+ values=[
509
+ cost_analysis['total_materials_cost'],
510
+ cost_analysis['total_equipment_cost'],
511
+ cost_analysis['total_labor_cost'],
512
+ cost_analysis['total_subcontractors_cost'],
513
+ cost_analysis['total_indirect_cost'],
514
+ cost_analysis['total_profit_margin']
515
+ ],
516
+ names=['المواد', 'المعدات', 'العمالة', 'مقاولي الباطن', 'تكاليف غير مباشرة', 'هامش الربح'],
517
+ title="توزيع تكاليف المشروع"
518
+ )
519
+ st.plotly_chart(fig)
520
+
521
+ # تحليل التكاليف حسب البنود
522
+ st.subheader("تحليل التكاليف حسب البنود")
523
+
524
+ items_df = pd.DataFrame([
525
+ {
526
+ 'البند': item['item_code'] + ' - ' + item['description'],
527
+ 'التكلفة المباشرة': analysis['direct_cost'],
528
+ 'التكلفة غير المباشرة': analysis['indirect_cost'],
529
+ 'هامش الربح': analysis['profit_margin'],
530
+ 'إجمالي السعر': analysis['total_price']
531
+ }
532
+ for item, analysis in zip(
533
+ st.session_state.project_data['boq_items'],
534
+ st.session_state.smart_price_analysis.get_all_items_analysis()
535
+ )
536
+ ])
537
+
538
+ st.dataframe(items_df)
539
+
540
+ # رسم بياني شريطي للتكاليف حسب البنود
541
+ fig = px.bar(
542
+ items_df,
543
+ x='البند',
544
+ y=['التكلفة المباشرة', 'التكلفة غير المباشرة', 'هامش الربح'],
545
+ title="توزيع التكاليف حسب البنود",
546
+ barmode='stack'
547
+ )
548
+ st.plotly_chart(fig)
549
+
550
+ # تحليل المواد الأكثر تكلفة
551
+ st.subheader("المواد الأكثر تكلفة في المشروع")
552
+
553
+ top_materials = st.session_state.smart_price_analysis.get_top_materials(limit=10)
554
+
555
+ if top_materials:
556
+ materials_df = pd.DataFrame(top_materials)
557
+
558
+ fig = px.bar(
559
+ materials_df,
560
+ x='name',
561
+ y='total_cost',
562
+ title="المواد الأكثر تكلفة في المشروع",
563
+ labels={'name': 'المادة', 'total_cost': 'التكلفة الإجمالية (ريال)'}
564
+ )
565
+ st.plotly_chart(fig)
566
+
567
+ # تحليل المعدات الأكثر تكلفة
568
+ st.subheader("المعدات الأكثر تكلفة في المشروع")
569
+
570
+ top_equipment = st.session_state.smart_price_analysis.get_top_equipment(limit=10)
571
+
572
+ if top_equipment:
573
+ equipment_df = pd.DataFrame(top_equipment)
574
+
575
+ fig = px.bar(
576
+ equipment_df,
577
+ x='name',
578
+ y='total_cost',
579
+ title="المعدات الأكثر تكلفة في المشروع",
580
+ labels={'name': 'المعدة', 'total_cost': 'التكلفة الإجمالية (ريال)'}
581
+ )
582
+ st.plotly_chart(fig)
583
+
584
+ def _enhanced_render_pricing_scenarios_tab(self):
585
+ """
586
+ النسخة المحسنة من دالة عرض علامة تبويب سيناريوهات التسعير
587
+ تدمج الوظائف الجديدة مع الوظائف الموجودة
588
+ """
589
+ st.header("سيناريوهات التسعير")
590
+
591
+ # استدعاء الدالة الأصلية
592
+ self.pricing_app._render_pricing_scenarios_tab_original()
593
+
594
+ # إضافة وظائف جديدة
595
+ st.subheader("استراتيجيات التسعير المتقدمة")
596
+
597
+ # عرض استراتيجيات التسعير المتاحة
598
+ strategy_options = [
599
+ "التسعير القياسي",
600
+ "التسعير المتزن",
601
+ "التسعير غير المتزن",
602
+ "التسعير الموجه للربحية",
603
+ "التسعير بالتجميع",
604
+ "التسعير بالمحتوى المحلي"
605
+ ]
606
+
607
+ selected_strategy = st.selectbox(
608
+ "اختر استراتيجية التسعير",
609
+ strategy_options,
610
+ key="pricing_strategy_select"
611
+ )
612
+
613
+ # عرض وصف الاستراتيجية المختارة
614
+ strategy_descriptions = {
615
+ "التسعير القياسي": "تعتمد على تحديد سعر كل بند بناءً على تكلفته الفعلية مضافاً إليها نسبة ربح ثابتة.",
616
+ "التسعير المتزن": "تعتمد على توزيع هامش الربح بشكل متوازن على جميع بنود المشروع مع مراعاة المخاطر.",
617
+ "التسعير غير المتزن": "تعتمد على زيادة أسعار البنود المبكرة في المشروع وتخفيض أسعار البنود المتأخرة.",
618
+ "التسعير الموجه للربحية": "تعتمد على زيادة أسعار البنود ذات التكلفة المنخفضة والكميات الكبيرة.",
619
+ "التسعير بالتجميع": "تعتمد على تجميع البنود المتشابهة وتسعيرها كمجموعة واحدة.",
620
+ "التسعير بالمحتوى المحلي": "تعتمد على زيادة نسبة المحتوى المحلي في المشروع لتحقيق متطلبات الجهات المالكة."
621
+ }
622
+
623
+ st.info(strategy_descriptions[selected_strategy])
624
+
625
+ # تطبيق الاستراتيجية المختارة
626
+ if st.button("تطبيق الاستراتيجية", key="apply_strategy_btn"):
627
+ strategy_map = {
628
+ "التسعير القياسي": "standard",
629
+ "التسعير المتزن": "balanced",
630
+ "التسعير غير المتزن": "unbalanced",
631
+ "التسعير الموجه للربحية": "profit_oriented",
632
+ "التسعير بالتجميع": "bundling",
633
+ "التسعير بالمحتوى المحلي": "local_content"
634
+ }
635
+
636
+ strategy_key = strategy_map[selected_strategy]
637
+
638
+ # تطبيق الاستراتيجية على المشروع
639
+ result = st.session_state.pricing_strategies.apply_strategy(
640
+ strategy_key,
641
+ st.session_state.project_data,
642
+ st.session_state.smart_price_analysis
643
+ )
644
+
645
+ if result['success']:
646
+ st.success(f"تم تطبيق استراتيجية {selected_strategy} بنجاح")
647
+
648
+ # عرض نتائج تطبيق الاستراتيجية
649
+ st.subheader("نتائج تطبيق الاستراتيجية")
650
+
651
+ col1, col2 = st.columns(2)
652
+
653
+ with col1:
654
+ st.write("**ملخص النتائج:**")
655
+ st.write(f"- إجمالي التكلفة: {result['total_cost']} ريال")
656
+ st.write(f"- إجمالي السعر: {result['total_price']} ريال")
657
+ st.write(f"- هامش الربح: {result['profit_margin']} ريال")
658
+ st.write(f"- نسبة الربح: {result['profit_percentage']:.2f}%")
659
+
660
+ if 'local_content_percentage' in result:
661
+ st.write(f"- نسبة المحتوى المحلي: {result['local_content_percentage']:.2f}%")
662
+
663
+ with col2:
664
+ # رسم بياني للمقارنة بين التكلفة والسعر
665
+ fig = go.Figure()
666
+
667
+ fig.add_trace(go.Bar(
668
+ x=['التكلفة', 'السعر'],
669
+ y=[result['total_cost'], result['total_price']],
670
+ marker_color=['#1f77b4', '#2ca02c']
671
+ ))
672
+
673
+ fig.update_layout(
674
+ title="مقارنة بين التكلفة والسعر",
675
+ xaxis_title="",
676
+ yaxis_title="القيمة (ريال)"
677
+ )
678
+
679
+ st.plotly_chart(fig)
680
+
681
+ # عرض تفاصيل البنود بعد تطبيق الاستراتيجية
682
+ st.subheader("تفاصيل البنود بعد تطبيق الاستراتيجية")
683
+
684
+ items_df = pd.DataFrame([
685
+ {
686
+ 'البند': item['item_code'] + ' - ' + item['description'],
687
+ 'التكلفة': item_result['cost'],
688
+ 'السعر': item_result['price'],
689
+ 'هامش الربح': item_result['profit'],
690
+ 'نسبة الربح': f"{item_result['profit_percentage']:.2f}%"
691
+ }
692
+ for item, item_result in zip(
693
+ st.session_state.project_data['boq_items'],
694
+ result['items_result']
695
+ )
696
+ ])
697
+
698
+ st.dataframe(items_df)
699
+
700
+ # رسم بياني شريطي للمقارنة بين التكلفة والسعر لكل بند
701
+ fig = px.bar(
702
+ items_df,
703
+ x='البند',
704
+ y=['التكلفة', 'السعر'],
705
+ title="مقارنة بين التكلفة والسعر لكل بند",
706
+ barmode='group'
707
+ )
708
+ st.plotly_chart(fig)
709
+ else:
710
+ st.error(f"حدث خطأ أثناء تطبيق الاستراتيجية: {result['message']}")
711
+
712
+ # مقارنة استراتيجيات التسعير
713
+ st.subheader("مقارنة استراتيجيات التسعير")
714
+
715
+ if st.button("مقارنة جميع الاستراتيجيات", key="compare_strategies_btn"):
716
+ comparison_result = st.session_state.pricing_strategies.compare_strategies(
717
+ st.session_state.project_data,
718
+ st.session_state.smart_price_analysis
719
+ )
720
+
721
+ if comparison_result['success']:
722
+ st.success("تمت مقارنة استراتيجيات التسعير بنجاح")
723
+
724
+ # عرض نتائج المقارنة
725
+ comparison_df = pd.DataFrame([
726
+ {
727
+ 'الاستراتيجية': strategy_options[i],
728
+ 'إجمالي التكلفة': result['total_cost'],
729
+ 'إجمالي السعر': result['total_price'],
730
+ 'هامش الربح': result['profit_margin'],
731
+ 'نسبة الربح': f"{result['profit_percentage']:.2f}%",
732
+ 'نسبة المحتوى المحلي': f"{result.get('local_content_percentage', 0):.2f}%"
733
+ }
734
+ for i, result in enumerate(comparison_result['strategies_result'])
735
+ ])
736
+
737
+ st.dataframe(comparison_df)
738
+
739
+ # رسم بياني للمقارنة بين الاستراتيجيات
740
+ fig = px.bar(
741
+ comparison_df,
742
+ x='الاستراتيجية',
743
+ y=['إجمالي التكلفة', 'إجمالي السعر', 'هامش الربح'],
744
+ title="مقارنة بين استراتيجيات التسعير",
745
+ barmode='group'
746
+ )
747
+ st.plotly_chart(fig)
748
+
749
+ # رسم بياني لنسبة الربح
750
+ profit_df = pd.DataFrame([
751
+ {
752
+ 'الاستراتيجية': strategy_options[i],
753
+ 'نسبة الربح': result['profit_percentage']
754
+ }
755
+ for i, result in enumerate(comparison_result['strategies_result'])
756
+ ])
757
+
758
+ fig = px.bar(
759
+ profit_df,
760
+ x='الاستراتيجية',
761
+ y='نسبة الربح',
762
+ title="مقارنة نسبة الربح بين استراتيجيات التسعير"
763
+ )
764
+ st.plotly_chart(fig)
765
+
766
+ # رسم بياني لنسبة المحتوى المحلي
767
+ local_content_df = pd.DataFrame([
768
+ {
769
+ 'الاستراتيجية': strategy_options[i],
770
+ 'نسبة المحتوى المحلي': result.get('local_content_percentage', 0)
771
+ }
772
+ for i, result in enumerate(comparison_result['strategies_result'])
773
+ ])
774
+
775
+ fig = px.bar(
776
+ local_content_df,
777
+ x='الاستراتيجية',
778
+ y='نسبة المحتوى المحلي',
779
+ title="مقارنة نسبة المحتوى المحلي بين استراتيجيات التسعير"
780
+ )
781
+ st.plotly_chart(fig)
782
+ else:
783
+ st.error(f"حدث خطأ أثناء مقارنة الاستراتيجيات: {comparison_result['message']}")
784
+
785
+ def _render_resource_catalogs_tab(self):
786
+ """
787
+ دالة عرض علامة تبويب كتالوجات الموارد
788
+ """
789
+ st.header("كتالوجات الموارد")
790
+
791
+ tab1, tab2, tab3, tab4 = st.tabs(["المعدات", "المواد", "العمالة", "مقاولي الباطن"])
792
+
793
+ with tab1:
794
+ st.subheader("كتالوج المعدات")
795
+
796
+ # عرض قائمة المعدات
797
+ equipment_list = st.session_state.equipment_catalog.get_equipment_list()
798
+
799
+ if equipment_list:
800
+ equipment_df = pd.DataFrame([
801
+ {
802
+ 'المعدة': equipment,
803
+ 'النوع': st.session_state.equipment_catalog.get_equipment_details(equipment)['type'],
804
+ 'سعر الساعة': st.session_state.equipment_catalog.get_equipment_details(equipment)['price_per_hour'],
805
+ 'سعر اليوم': st.session_state.equipment_catalog.get_equipment_details(equipment)['price_per_day'],
806
+ 'سعر الأسبوع': st.session_state.equipment_catalog.get_equipment_details(equipment)['price_per_week'],
807
+ 'سعر الشهر': st.session_state.equipment_catalog.get_equipment_details(equipment)['price_per_month'],
808
+ 'محلي': "نعم" if st.session_state.equipment_catalog.get_equipment_details(equipment)['is_local'] else "لا"
809
+ }
810
+ for equipment in equipment_list
811
+ ])
812
+
813
+ st.dataframe(equipment_df)
814
+ else:
815
+ st.info("لا توجد معدات في الكتالوج")
816
+
817
+ # إضافة معدة جديدة
818
+ st.subheader("إضافة معدة جديدة")
819
+
820
+ with st.form("add_equipment_form"):
821
+ equipment_name = st.text_input("اسم المعدة")
822
+ equipment_type = st.selectbox(
823
+ "نوع المعدة",
824
+ [
825
+ "حفار", "لودر", "بلدوزر", "جريدر", "دكاك", "شاحنة", "خلاطة خرسانة",
826
+ "رافعة", "مولد كهرباء", "مضخة مياه", "كسارة", "معدة أخرى"
827
+ ]
828
+ )
829
+ equipment_description = st.text_area("وصف المعدة")
830
+
831
+ col1, col2 = st.columns(2)
832
+
833
+ with col1:
834
+ price_per_hour = st.number_input("سعر الساعة (ريال)", min_value=0.0, step=10.0)
835
+ price_per_day = st.number_input("سعر اليوم (ريال)", min_value=0.0, step=100.0)
836
+
837
+ with col2:
838
+ price_per_week = st.number_input("سعر الأسبوع (ريال)", min_value=0.0, step=500.0)
839
+ price_per_month = st.number_input("سعر الشهر (ريال)", min_value=0.0, step=1000.0)
840
+
841
+ is_local = st.checkbox("معدة محلية")
842
+
843
+ submit_button = st.form_submit_button("إضافة المعدة")
844
+
845
+ if submit_button:
846
+ if equipment_name:
847
+ equipment_details = {
848
+ 'name': equipment_name,
849
+ 'type': equipment_type,
850
+ 'description': equipment_description,
851
+ 'price_per_hour': price_per_hour,
852
+ 'price_per_day': price_per_day,
853
+ 'price_per_week': price_per_week,
854
+ 'price_per_month': price_per_month,
855
+ 'is_local': is_local
856
+ }
857
+
858
+ st.session_state.equipment_catalog.add_equipment(equipment_name, equipment_details)
859
+ st.success(f"تمت إضافة {equipment_name} بنجاح")
860
+ st.experimental_rerun()
861
+ else:
862
+ st.error("يرجى إدخال اسم المعدة")
863
+
864
+ # استيراد كتالوج المعدات من Excel
865
+ st.subheader("استيراد كتالوج المعدات من Excel")
866
+
867
+ uploaded_file = st.file_uploader("اختر ملف Excel", type=["xlsx", "xls"], key="equipment_catalog_uploader")
868
+ if uploaded_file is not None:
869
+ if st.button("استيراد الكتالوج", key="import_equipment_catalog_btn"):
870
+ try:
871
+ result = st.session_state.equipment_catalog.import_from_excel(uploaded_file)
872
+ st.success(f"تم استيراد {result['count']} معدة بنجاح")
873
+ st.experimental_rerun()
874
+ except Exception as e:
875
+ st.error(f"حدث خطأ أثناء استيراد الكتالوج: {str(e)}")
876
+
877
+ with tab2:
878
+ st.subheader("كتالوج المواد")
879
+
880
+ # عرض قائمة المواد
881
+ materials_list = st.session_state.materials_catalog.get_materials_list()
882
+
883
+ if materials_list:
884
+ materials_df = pd.DataFrame([
885
+ {
886
+ 'المادة': material,
887
+ 'الوصف': st.session_state.materials_catalog.get_material_details(material)['description'],
888
+ 'الوحدة': st.session_state.materials_catalog.get_material_details(material)['unit'],
889
+ 'السعر': st.session_state.materials_catalog.get_material_details(material)['price'],
890
+ 'محلي': "نعم" if st.session_state.materials_catalog.get_material_details(material)['is_local'] else "لا"
891
+ }
892
+ for material in materials_list
893
+ ])
894
+
895
+ st.dataframe(materials_df)
896
+ else:
897
+ st.info("لا توجد مواد في الكتالوج")
898
+
899
+ # إضافة مادة جديدة
900
+ st.subheader("إضافة مادة جديدة")
901
+
902
+ with st.form("add_material_form"):
903
+ material_name = st.text_input("اسم المادة")
904
+ material_description = st.text_area("وصف المادة")
905
+ material_unit = st.selectbox(
906
+ "وحدة القياس",
907
+ ["م3", "م2", "م.ط", "طن", "كجم", "لتر", "قطعة", "حزمة", "وحدة"]
908
+ )
909
+ material_price = st.number_input("السعر (ريال)", min_value=0.0, step=1.0)
910
+ is_local = st.checkbox("مادة محلية")
911
+
912
+ submit_button = st.form_submit_button("إضافة المادة")
913
+
914
+ if submit_button:
915
+ if material_name:
916
+ material_details = {
917
+ 'name': material_name,
918
+ 'description': material_description,
919
+ 'unit': material_unit,
920
+ 'price': material_price,
921
+ 'is_local': is_local
922
+ }
923
+
924
+ st.session_state.materials_catalog.add_material(material_name, material_details)
925
+ st.success(f"تمت إضافة {material_name} بنجاح")
926
+ st.experimental_rerun()
927
+ else:
928
+ st.error("يرجى إدخال اسم المادة")
929
+
930
+ # استيراد كتالوج المواد من Excel
931
+ st.subheader("استيراد كتالوج المواد من Excel")
932
+
933
+ uploaded_file = st.file_uploader("اختر ملف Excel", type=["xlsx", "xls"], key="materials_catalog_uploader")
934
+ if uploaded_file is not None:
935
+ if st.button("استيراد الكتالوج", key="import_materials_catalog_btn"):
936
+ try:
937
+ result = st.session_state.materials_catalog.import_from_excel(uploaded_file)
938
+ st.success(f"تم استيراد {result['count']} مادة بنجاح")
939
+ st.experimental_rerun()
940
+ except Exception as e:
941
+ st.error(f"حدث خطأ أثناء استيراد الكتالوج: {str(e)}")
942
+
943
+ with tab3:
944
+ st.subheader("كتالوج العمالة")
945
+
946
+ # عرض قائمة العمالة
947
+ labor_list = st.session_state.labor_catalog.get_labor_list()
948
+
949
+ if labor_list:
950
+ labor_df = pd.DataFrame([
951
+ {
952
+ 'العمالة': labor,
953
+ 'النوع': st.session_state.labor_catalog.get_labor_details(labor)['type'],
954
+ 'سعر الساعة': st.session_state.labor_catalog.get_labor_details(labor)['price_per_hour'],
955
+ 'سعر اليوم': st.session_state.labor_catalog.get_labor_details(labor)['price_per_day'],
956
+ 'سعر الأسبوع': st.session_state.labor_catalog.get_labor_details(labor)['price_per_week'],
957
+ 'سعر الشهر': st.session_state.labor_catalog.get_labor_details(labor)['price_per_month'],
958
+ 'محلي': "نعم" if st.session_state.labor_catalog.get_labor_details(labor)['is_local'] else "لا"
959
+ }
960
+ for labor in labor_list
961
+ ])
962
+
963
+ st.dataframe(labor_df)
964
+ else:
965
+ st.info("لا توجد عمالة في الكتالوج")
966
+
967
+ # إضافة عمالة جديدة
968
+ st.subheader("إضافة عمالة جديدة")
969
+
970
+ with st.form("add_labor_form"):
971
+ labor_name = st.text_input("اسم العمالة")
972
+ labor_type = st.selectbox(
973
+ "نوع العمالة",
974
+ [
975
+ "مهندس مدني", "مهندس معماري", "مهندس ميكانيكي", "مهندس كهربائي",
976
+ "مساح", "مراقب", "فني", "عامل", "سائق", "حداد", "نجار", "عامل بناء",
977
+ "كهربائي", "سباك", "دهان", "لحام", "عمالة أخرى"
978
+ ]
979
+ )
980
+ labor_description = st.text_area("وصف العمالة")
981
+
982
+ col1, col2 = st.columns(2)
983
+
984
+ with col1:
985
+ price_per_hour = st.number_input("سعر الساعة (ريال)", min_value=0.0, step=5.0, key="labor_hour_price")
986
+ price_per_day = st.number_input("سعر اليوم (ريال)", min_value=0.0, step=50.0, key="labor_day_price")
987
+
988
+ with col2:
989
+ price_per_week = st.number_input("سعر الأسبوع (ريال)", min_value=0.0, step=200.0, key="labor_week_price")
990
+ price_per_month = st.number_input("سعر الشهر (ريال)", min_value=0.0, step=500.0, key="labor_month_price")
991
+
992
+ is_local = st.checkbox("عمالة محلية")
993
+
994
+ submit_button = st.form_submit_button("إضافة العمالة")
995
+
996
+ if submit_button:
997
+ if labor_name:
998
+ labor_details = {
999
+ 'name': labor_name,
1000
+ 'type': labor_type,
1001
+ 'description': labor_description,
1002
+ 'price_per_hour': price_per_hour,
1003
+ 'price_per_day': price_per_day,
1004
+ 'price_per_week': price_per_week,
1005
+ 'price_per_month': price_per_month,
1006
+ 'is_local': is_local
1007
+ }
1008
+
1009
+ st.session_state.labor_catalog.add_labor(labor_name, labor_details)
1010
+ st.success(f"تمت إضافة {labor_name} بنجاح")
1011
+ st.experimental_rerun()
1012
+ else:
1013
+ st.error("يرجى إدخال اسم العمالة")
1014
+
1015
+ # استيراد كتالوج العمالة من Excel
1016
+ st.subheader("استيراد كتالوج العمالة من Excel")
1017
+
1018
+ uploaded_file = st.file_uploader("اختر ملف Excel", type=["xlsx", "xls"], key="labor_catalog_uploader")
1019
+ if uploaded_file is not None:
1020
+ if st.button("استيراد الكتالوج", key="import_labor_catalog_btn"):
1021
+ try:
1022
+ result = st.session_state.labor_catalog.import_from_excel(uploaded_file)
1023
+ st.success(f"تم استيراد {result['count']} عمالة بنجاح")
1024
+ st.experimental_rerun()
1025
+ except Exception as e:
1026
+ st.error(f"حدث خطأ أثناء استيراد الكتالوج: {str(e)}")
1027
+
1028
+ with tab4:
1029
+ st.subheader("كتالوج مقاولي الباطن")
1030
+
1031
+ # عرض قائمة مقاولي الباطن
1032
+ subcontractors_list = st.session_state.subcontractors_catalog.get_subcontractors_list()
1033
+
1034
+ if subcontractors_list:
1035
+ subcontractors_df = pd.DataFrame([
1036
+ {
1037
+ 'مقاول الباطن': subcontractor,
1038
+ 'التخصص': st.session_state.subcontractors_catalog.get_subcontractor_details(subcontractor)['specialization'],
1039
+ 'الوصف': st.session_state.subcontractors_catalog.get_subcontractor_details(subcontractor)['description'],
1040
+ 'معلومات الاتصال': st.session_state.subcontractors_catalog.get_subcontractor_details(subcontractor)['contact_info'],
1041
+ 'محلي': "نعم" if st.session_state.subcontractors_catalog.get_subcontractor_details(subcontractor)['is_local'] else "لا"
1042
+ }
1043
+ for subcontractor in subcontractors_list
1044
+ ])
1045
+
1046
+ st.dataframe(subcontractors_df)
1047
+ else:
1048
+ st.info("لا يوجد مقاولي باطن في الكتالوج")
1049
+
1050
+ # إضافة مقاول باطن جديد
1051
+ st.subheader("إضافة مقاول باطن جديد")
1052
+
1053
+ with st.form("add_subcontractor_form"):
1054
+ subcontractor_name = st.text_input("اسم مقاول الباطن")
1055
+ subcontractor_specialization = st.selectbox(
1056
+ "التخصص",
1057
+ [
1058
+ "أعمال كهربائية", "أعمال ميكانيكية", "أعمال سباكة", "أعمال تكييف",
1059
+ "أعمال ITC", "أعمال CCTV", "أنظمة التحكم في الوصول", "شبكات الري",
1060
+ "أعمال الحفر", "أعمال الخرسانة", "أعمال التشطيبات", "أعمال الأسفلت",
1061
+ "أعمال العزل", "أعمال الألمنيوم والزجاج", "تخصص آخر"
1062
+ ]
1063
+ )
1064
+ subcontractor_description = st.text_area("وصف مقاول الباطن")
1065
+ subcontractor_contact_info = st.text_input("معلومات الاتصال")
1066
+ is_local = st.checkbox("مقاول باطن محلي")
1067
+
1068
+ submit_button = st.form_submit_button("إضافة مقاول الباطن")
1069
+
1070
+ if submit_button:
1071
+ if subcontractor_name:
1072
+ subcontractor_details = {
1073
+ 'name': subcontractor_name,
1074
+ 'specialization': subcontractor_specialization,
1075
+ 'description': subcontractor_description,
1076
+ 'contact_info': subcontractor_contact_info,
1077
+ 'is_local': is_local
1078
+ }
1079
+
1080
+ st.session_state.subcontractors_catalog.add_subcontractor(subcontractor_name, subcontractor_details)
1081
+ st.success(f"تمت إضافة {subcontractor_name} بنجاح")
1082
+ st.experimental_rerun()
1083
+ else:
1084
+ st.error("يرجى إدخال اسم مقاول الباطن")
1085
+
1086
+ # استيراد كتالوج مقاولي الباطن من Excel
1087
+ st.subheader("استيراد كتالوج مقاولي الباطن من Excel")
1088
+
1089
+ uploaded_file = st.file_uploader("اختر ملف Excel", type=["xlsx", "xls"], key="subcontractors_catalog_uploader")
1090
+ if uploaded_file is not None:
1091
+ if st.button("استيراد الكتالوج", key="import_subcontractors_catalog_btn"):
1092
+ try:
1093
+ result = st.session_state.subcontractors_catalog.import_from_excel(uploaded_file)
1094
+ st.success(f"تم استيراد {result['count']} مقاول باطن بنجاح")
1095
+ st.experimental_rerun()
1096
+ except Exception as e:
1097
+ st.error(f"حدث خطأ أثناء استيراد الكتالوج: {str(e)}")
1098
+
1099
+ def _render_indirect_support_tab(self):
1100
+ """
1101
+ دالة عرض علامة تبويب الإدارات المساندة
1102
+ """
1103
+ st.header("إدارة الإدارات المساندة")
1104
+
1105
+ # عرض قائمة الإدارات المساندة
1106
+ indirect_departments = st.session_state.indirect_support.get_departments()
1107
+
1108
+ if indirect_departments:
1109
+ departments_df = pd.DataFrame([
1110
+ {
1111
+ 'الإدارة': department,
1112
+ 'التكلفة الشهرية': st.session_state.indirect_support.get_department_details(department)['monthly_cost'],
1113
+ 'عدد الموظفين': st.session_state.indirect_support.get_department_details(department)['employees_count'],
1114
+ 'نسبة التخصيص': f"{st.session_state.indirect_support.get_department_details(department)['allocation_percentage']:.2f}%"
1115
+ }
1116
+ for department in indirect_departments
1117
+ ])
1118
+
1119
+ st.dataframe(departments_df)
1120
+
1121
+ # رسم بياني لتوزيع التكاليف حسب الإدارات
1122
+ fig = px.pie(
1123
+ departments_df,
1124
+ values='التكلفة الشهرية',
1125
+ names='الإدارة',
1126
+ title="توزيع التكاليف حسب الإدارات المساندة"
1127
+ )
1128
+ st.plotly_chart(fig)
1129
+ else:
1130
+ st.info("لا توجد إدارات مساندة")
1131
+
1132
+ # إضافة إدارة مساندة جديدة
1133
+ st.subheader("إضافة إدارة مساندة جديدة")
1134
+
1135
+ with st.form("add_department_form"):
1136
+ department_name = st.text_input("اسم الإدارة")
1137
+ department_description = st.text_area("وصف الإدارة")
1138
+ monthly_cost = st.number_input("التكلفة الشهرية (ريال)", min_value=0.0, step=1000.0)
1139
+ employees_count = st.number_input("عدد الموظفين", min_value=0, step=1)
1140
+ allocation_percentage = st.slider("نسبة التخصيص للمشروع (%)", min_value=0.0, max_value=100.0, step=5.0)
1141
+
1142
+ submit_button = st.form_submit_button("إضافة الإدارة")
1143
+
1144
+ if submit_button:
1145
+ if department_name:
1146
+ department_details = {
1147
+ 'name': department_name,
1148
+ 'description': department_description,
1149
+ 'monthly_cost': monthly_cost,
1150
+ 'employees_count': employees_count,
1151
+ 'allocation_percentage': allocation_percentage
1152
+ }
1153
+
1154
+ st.session_state.indirect_support.add_department(department_name, department_details)
1155
+ st.success(f"تمت إضافة {department_name} بنجاح")
1156
+ st.experimental_rerun()
1157
+ else:
1158
+ st.error("يرجى إدخال اسم الإدارة")
1159
+
1160
+ # تحليل تكاليف الإدارات المساندة
1161
+ st.subheader("تحليل تكاليف الإدارات المساندة")
1162
+
1163
+ if indirect_departments:
1164
+ # حساب إجمالي تكاليف الإدارات المساندة
1165
+ total_monthly_cost = st.session_state.indirect_support.get_total_monthly_cost()
1166
+ total_allocated_cost = st.session_state.indirect_support.get_total_allocated_cost()
1167
+
1168
+ col1, col2 = st.columns(2)
1169
+
1170
+ with col1:
1171
+ st.write(f"**إجمالي التكلفة الشهرية للإدارات المساندة:** {total_monthly_cost} ريال")
1172
+ st.write(f"**إجمالي التكلفة المخصصة للمشروع:** {total_allocated_cost} ريال")
1173
+
1174
+ with col2:
1175
+ # رسم بياني للمقارنة بين التكلفة الكلية والتكلفة المخصصة
1176
+ fig = go.Figure()
1177
+
1178
+ fig.add_trace(go.Bar(
1179
+ x=['التكلفة الشهرية الكلية', 'التكلفة المخصصة للمشروع'],
1180
+ y=[total_monthly_cost, total_allocated_cost],
1181
+ marker_color=['#1f77b4', '#2ca02c']
1182
+ ))
1183
+
1184
+ fig.update_layout(
1185
+ title="مقارنة بين التكلفة الكلية والتكلفة المخصصة",
1186
+ xaxis_title="",
1187
+ yaxis_title="القيمة (ريال)"
1188
+ )
1189
+
1190
+ st.plotly_chart(fig)
1191
+
1192
+ # توزيع التكاليف المخصصة على بنود المشروع
1193
+ st.subheader("توزيع التكاليف المخصصة على بنود المشروع")
1194
+
1195
+ distribution_method = st.selectbox(
1196
+ "اختر طريقة توزيع التكاليف",
1197
+ ["بالتساوي", "حسب قيمة البند", "حسب مدة البند"]
1198
+ )
1199
+
1200
+ if st.button("توزيع التكاليف", key="distribute_costs_btn"):
1201
+ if len(st.session_state.project_data['boq_items']) > 0:
1202
+ distribution_result = st.session_state.indirect_support.distribute_costs(
1203
+ st.session_state.project_data['boq_items'],
1204
+ distribution_method
1205
+ )
1206
+
1207
+ if distribution_result['success']:
1208
+ st.success("تم توزيع التكاليف بنجاح")
1209
+
1210
+ # عرض نتائج التوزيع
1211
+ distribution_df = pd.DataFrame([
1212
+ {
1213
+ 'البند': item['item_code'] + ' - ' + item['description'],
1214
+ 'التكلفة المباشرة': item['direct_cost'] if 'direct_cost' in item else 0,
1215
+ 'التكلفة غير المباشرة المخصصة': cost,
1216
+ 'إجمالي التكلفة': (item['direct_cost'] if 'direct_cost' in item else 0) + cost
1217
+ }
1218
+ for item, cost in zip(
1219
+ st.session_state.project_data['boq_items'],
1220
+ distribution_result['distributed_costs']
1221
+ )
1222
+ ])
1223
+
1224
+ st.dataframe(distribution_df)
1225
+
1226
+ # رسم بياني لتوزيع التكاليف غير المباشرة
1227
+ fig = px.bar(
1228
+ distribution_df,
1229
+ x='البند',
1230
+ y=['التكلفة المباشرة', 'التكلفة غير المباشرة المخصصة'],
1231
+ title="توزيع التكاليف على بنود المشروع",
1232
+ barmode='stack'
1233
+ )
1234
+ st.plotly_chart(fig)
1235
+ else:
1236
+ st.error(f"حدث خطأ أثناء توزيع التكاليف: {distribution_result['message']}")
1237
+ else:
1238
+ st.error("لا توجد بنود في المشروع")
1239
+ else:
1240
+ st.warning("يرجى إضافة إدارات مساندة أولاً")
1241
+
1242
+ def _render_local_content_tab(self):
1243
+ """
1244
+ دالة عرض علامة تبويب المحتوى المحلي
1245
+ """
1246
+ st.header("تحليل المحتوى المحلي")
1247
+
1248
+ # عرض معلومات المحتوى المحلي
1249
+ st.write("""
1250
+ يهدف تحليل المحتوى المحلي إلى قياس مدى مساهمة المشروع في تحقيق أهداف رؤية 2030 للمملكة العربية السعودية
1251
+ من خلال زيادة نسبة المحتوى المحلي في المشاريع والمناقصات.
1252
+ """)
1253
+
1254
+ # تحديد هدف المحتوى المحلي
1255
+ st.subheader("هدف المحتوى المحلي")
1256
+
1257
+ local_content_target = st.slider(
1258
+ "نسبة المحتوى المحلي المستهدفة (%)",
1259
+ min_value=0.0,
1260
+ max_value=100.0,
1261
+ value=float(st.session_state.project_data['local_content_target'] * 100),
1262
+ step=5.0,
1263
+ key="local_content_target_slider"
1264
+ )
1265
+
1266
+ st.session_state.project_data['local_content_target'] = local_content_target / 100
1267
+
1268
+ # تحليل المحتوى المحلي الحالي
1269
+ st.subheader("تحليل المحتوى المحلي الحالي")
1270
+
1271
+ if len(st.session_state.project_data['boq_items']) > 0:
1272
+ # حساب نسبة المحتوى المحلي
1273
+ local_content_analysis = st.session_state.pricing_strategies.analyze_local_content(
1274
+ st.session_state.project_data,
1275
+ st.session_state.smart_price_analysis
1276
+ )
1277
+
1278
+ if local_content_analysis['success']:
1279
+ col1, col2 = st.columns(2)
1280
+
1281
+ with col1:
1282
+ st.write(f"**نسبة المحتوى المحلي الحالية:** {local_content_analysis['local_content_percentage']:.2f}%")
1283
+ st.write(f"**النسبة المستهدفة:** {local_content_target:.2f}%")
1284
+
1285
+ if local_content_analysis['local_content_percentage'] >= local_content_target:
1286
+ st.success("تم تحقيق هدف المحتوى المحلي")
1287
+ else:
1288
+ st.warning(f"لم يتم تحقيق هدف المحتوى المحلي، الفارق: {local_content_target - local_content_analysis['local_content_percentage']:.2f}%")
1289
+
1290
+ with col2:
1291
+ # رسم بياني للمقارنة بين النسبة الحالية والنسبة المستهدفة
1292
+ fig = go.Figure()
1293
+
1294
+ fig.add_trace(go.Bar(
1295
+ x=['النسبة الحالية', 'النسبة المستهدفة'],
1296
+ y=[local_content_analysis['local_content_percentage'], local_content_target],
1297
+ marker_color=['#1f77b4', '#2ca02c']
1298
+ ))
1299
+
1300
+ fig.update_layout(
1301
+ title="مقارنة بين نسبة المحتوى المحلي الحالية والمستهدفة",
1302
+ xaxis_title="",
1303
+ yaxis_title="النسبة (%)"
1304
+ )
1305
+
1306
+ st.plotly_chart(fig)
1307
+
1308
+ # تحليل المحتوى المحلي حسب نوع الموارد
1309
+ st.subheader("تحليل المحتوى المحلي حسب نوع الموارد")
1310
+
1311
+ resources_local_content = local_content_analysis['resources_local_content']
1312
+
1313
+ resources_df = pd.DataFrame([
1314
+ {
1315
+ 'نوع المورد': resource_type,
1316
+ 'التكلفة الإجمالية': resources_local_content[resource_type]['total_cost'],
1317
+ 'تكلفة الموارد المحلية': resources_local_content[resource_type]['local_cost'],
1318
+ 'نسبة المحتوى المحلي': f"{resources_local_content[resource_type]['percentage']:.2f}%"
1319
+ }
1320
+ for resource_type in resources_local_content
1321
+ ])
1322
+
1323
+ st.dataframe(resources_df)
1324
+
1325
+ # رسم بياني لنسبة المحتوى المحلي حسب نوع الموارد
1326
+ fig = px.bar(
1327
+ resources_df,
1328
+ x='نوع المورد',
1329
+ y=['التكلفة الإجمالية', 'تكلفة الموارد المحلية'],
1330
+ title="تحليل المحتوى المحلي حسب نوع الموارد",
1331
+ barmode='group'
1332
+ )
1333
+ st.plotly_chart(fig)
1334
+
1335
+ # رسم بياني لنسبة المحتوى المحلي حسب نوع الموارد
1336
+ percentages = [resources_local_content[resource_type]['percentage'] for resource_type in resources_local_content]
1337
+ resource_types = list(resources_local_content.keys())
1338
+
1339
+ fig = px.bar(
1340
+ x=resource_types,
1341
+ y=percentages,
1342
+ title="نسبة المحتوى المحلي حسب نوع الموارد",
1343
+ labels={'x': 'نوع المورد', 'y': 'نسبة المحتوى المحلي (%)'}
1344
+ )
1345
+ st.plotly_chart(fig)
1346
+
1347
+ # توصيات لتحسين نسبة المحتوى المحلي
1348
+ st.subheader("توصيات لتحسين نسبة المحتوى المحلي")
1349
+
1350
+ if local_content_analysis['local_content_percentage'] < local_content_target:
1351
+ recommendations = local_content_analysis['recommendations']
1352
+
1353
+ for i, recommendation in enumerate(recommendations):
1354
+ st.write(f"{i+1}. {recommendation}")
1355
+
1356
+ # تطبيق استراتيجية المحتوى المحلي
1357
+ st.subheader("تطبيق استراتيجية المحتوى المحلي")
1358
+
1359
+ if st.button("تطبيق استراتيجية المحتوى المحلي", key="apply_local_content_strategy_btn"):
1360
+ result = st.session_state.pricing_strategies.apply_strategy(
1361
+ 'local_content',
1362
+ st.session_state.project_data,
1363
+ st.session_state.smart_price_analysis
1364
+ )
1365
+
1366
+ if result['success']:
1367
+ st.success("تم تطبيق استراتيجية المحتوى المحلي بنجاح")
1368
+
1369
+ # عرض نتائج تطبيق الاستراتيجية
1370
+ st.write(f"**نسبة المحتوى المحلي بعد تطبيق الاستراتيجية:** {result['local_content_percentage']:.2f}%")
1371
+
1372
+ if result['local_content_percentage'] >= local_content_target:
1373
+ st.success("تم تحقيق هدف المحتوى المحلي بنجاح")
1374
+ else:
1375
+ st.warning(f"لم يتم تحقيق هدف المحتوى المحلي بالكامل، الفارق: {local_content_target - result['local_content_percentage']:.2f}%")
1376
+ else:
1377
+ st.error(f"حدث خطأ أثناء تطبيق الاستراتيجية: {result['message']}")
1378
+ else:
1379
+ st.success("تم تحقيق هدف المحتوى المحلي بالفعل")
1380
+ else:
1381
+ st.error(f"حدث خطأ أثناء تحليل المحتوى المحلي: {local_content_analysis['message']}")
1382
+ else:
1383
+ st.warning("لا توجد بنود في المشروع")
pricing_system/modules/analysis/smart_price_analysis.py ADDED
@@ -0,0 +1,1743 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وحدة التحليل الذكي للأسعار - تحليل أسعار البنود بطريقة ذكية
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import plotly.express as px
9
+ import plotly.graph_objects as go
10
+ import os
11
+ import json
12
+ from datetime import datetime
13
+ import io
14
+ import math
15
+
16
+ class SmartPriceAnalysis:
17
+ """فئة التحليل الذكي للأسعار"""
18
+
19
+ def __init__(self):
20
+ """تهيئة وحدة التحليل الذكي للأسعار"""
21
+
22
+ # تهيئة حالة الجلسة للتحليل الذكي للأسعار
23
+ if 'smart_price_analysis' not in st.session_state:
24
+ self._initialize_smart_price_analysis()
25
+
26
+ # الوصول إلى كتالوجات الموارد
27
+ self.equipment_catalog = self._get_equipment_catalog()
28
+ self.materials_catalog = self._get_materials_catalog()
29
+ self.labor_catalog = self._get_labor_catalog()
30
+ self.subcontractors_catalog = self._get_subcontractors_catalog()
31
+
32
+ def _initialize_smart_price_analysis(self):
33
+ """تهيئة بيانات التحليل الذكي للأسعار"""
34
+
35
+ # إنشاء بيانات افتراضية للتحليل الذكي للأسعار
36
+ st.session_state.smart_price_analysis = {
37
+ "price_components": {
38
+ "materials": 0.45, # نسبة المواد من إجمالي التكلفة
39
+ "equipment": 0.25, # نسبة المعدات من إجمالي التكلفة
40
+ "labor": 0.20, # نسبة العمالة من إجمالي التكلفة
41
+ "subcontractors": 0.10 # نسبة مقاولي الباطن من إجمالي التكلفة
42
+ },
43
+ "indirect_costs": {
44
+ "overhead": 0.10, # نسبة المصاريف العمومية والإدارية
45
+ "profit": 0.15, # نسبة الربح
46
+ "contingency": 0.05, # نسبة الطوارئ
47
+ "bonds": 0.02, # نسبة الضمانات
48
+ "insurance": 0.03 # نسبة التأمين
49
+ },
50
+ "local_content": {
51
+ "target": 0.40, # النسبة المستهدفة للمحتوى المحلي
52
+ "materials_local": 0.30, # نسبة المواد المحلية
53
+ "equipment_local": 0.20, # نسبة المعدات المحلية
54
+ "labor_local": 0.80, # نسبة العمالة المحلية
55
+ "subcontractors_local": 0.60 # نسبة مقاولي الباطن المحليين
56
+ },
57
+ "productivity_factors": {
58
+ "weather": 1.0, # عامل الطقس
59
+ "location": 1.0, # عامل الموقع
60
+ "complexity": 1.0, # عامل التعقيد
61
+ "schedule": 1.0, # عامل الجدول الزمني
62
+ "resources": 1.0 # عامل الموارد
63
+ },
64
+ "analysis_history": [], # سجل تحليلات الأسعار
65
+ "current_item": None # البند الحالي قيد التحليل
66
+ }
67
+
68
+ # إنشاء بيانات افتراضية لبنود جدول الكميات
69
+ if 'boq_items' not in st.session_state:
70
+ self._initialize_boq_items()
71
+
72
+ def _initialize_boq_items(self):
73
+ """تهيئة بيانات بنود جدول الكميات"""
74
+
75
+ # إنشاء بيانات افتراضية لبنود جدول الكميات
76
+ boq_items = [
77
+ {
78
+ "id": "C-001",
79
+ "description": "توريد وصب خرسانة مسلحة للأساسات",
80
+ "unit": "م3",
81
+ "quantity": 500,
82
+ "unit_price": 1200,
83
+ "total_price": 600000,
84
+ "category": "أعمال الخرسانة",
85
+ "subcategory": "أساسات",
86
+ "analyzed": False,
87
+ "components": {}
88
+ },
89
+ {
90
+ "id": "C-002",
91
+ "description": "توريد وصب خرسانة مسلحة للأعمدة",
92
+ "unit": "م3",
93
+ "quantity": 300,
94
+ "unit_price": 1500,
95
+ "total_price": 450000,
96
+ "category": "أعمال الخرسانة",
97
+ "subcategory": "أعمدة",
98
+ "analyzed": False,
99
+ "components": {}
100
+ },
101
+ {
102
+ "id": "E-001",
103
+ "description": "حفر وردم لزوم الأساسات",
104
+ "unit": "م3",
105
+ "quantity": 800,
106
+ "unit_price": 80,
107
+ "total_price": 64000,
108
+ "category": "أعمال الحفر والردم",
109
+ "subcategory": "حفر",
110
+ "analyzed": False,
111
+ "components": {}
112
+ },
113
+ {
114
+ "id": "R-001",
115
+ "description": "توريد وتركيب طبقة أساس من الحصى المدكوك",
116
+ "unit": "م2",
117
+ "quantity": 2000,
118
+ "unit_price": 120,
119
+ "total_price": 240000,
120
+ "category": "أعمال الطرق",
121
+ "subcategory": "طبقة أساس",
122
+ "analyzed": False,
123
+ "components": {}
124
+ },
125
+ {
126
+ "id": "R-002",
127
+ "description": "توريد وفرش طبقة إسفلتية سمك 5 سم",
128
+ "unit": "م2",
129
+ "quantity": 2000,
130
+ "unit_price": 180,
131
+ "total_price": 360000,
132
+ "category": "أعمال الطرق",
133
+ "subcategory": "طبقة إسفلتية",
134
+ "analyzed": False,
135
+ "components": {}
136
+ },
137
+ {
138
+ "id": "S-001",
139
+ "description": "توريد وتركيب مواسير صرف صحي قطر 300 مم",
140
+ "unit": "م.ط",
141
+ "quantity": 1500,
142
+ "unit_price": 450,
143
+ "total_price": 675000,
144
+ "category": "أعمال الصرف الصحي",
145
+ "subcategory": "مواسير",
146
+ "analyzed": False,
147
+ "components": {}
148
+ },
149
+ {
150
+ "id": "S-002",
151
+ "description": "توريد وتركيب غرف تفتيش قطر 1.0 م",
152
+ "unit": "عدد",
153
+ "quantity": 50,
154
+ "unit_price": 3500,
155
+ "total_price": 175000,
156
+ "category": "أعمال الصرف الصحي",
157
+ "subcategory": "غرف تفتيش",
158
+ "analyzed": False,
159
+ "components": {}
160
+ },
161
+ {
162
+ "id": "E-002",
163
+ "description": "توريد وتركيب كابلات كهربائية جهد منخفض",
164
+ "unit": "م.ط",
165
+ "quantity": 3000,
166
+ "unit_price": 120,
167
+ "total_price": 360000,
168
+ "category": "أعمال الكهرباء",
169
+ "subcategory": "كابلات",
170
+ "analyzed": False,
171
+ "components": {}
172
+ },
173
+ {
174
+ "id": "E-003",
175
+ "description": "توريد وتركيب أعمدة إنارة ارتفاع 10 م",
176
+ "unit": "عدد",
177
+ "quantity": 80,
178
+ "unit_price": 5000,
179
+ "total_price": 400000,
180
+ "category": "أعمال الكهرباء",
181
+ "subcategory": "إنارة",
182
+ "analyzed": False,
183
+ "components": {}
184
+ },
185
+ {
186
+ "id": "W-001",
187
+ "description": "توريد وتركيب مواسير مياه قطر 200 مم",
188
+ "unit": "م.ط",
189
+ "quantity": 2000,
190
+ "unit_price": 350,
191
+ "total_price": 700000,
192
+ "category": "أعمال المياه",
193
+ "subcategory": "مواسير",
194
+ "analyzed": False,
195
+ "components": {}
196
+ }
197
+ ]
198
+
199
+ # تخزين البيانات في حالة الجلسة
200
+ st.session_state.boq_items = pd.DataFrame(boq_items)
201
+
202
+ def _get_equipment_catalog(self):
203
+ """الحصول على كتالوج المعدات"""
204
+
205
+ # التحقق من وجود كتالوج المعدات في حالة الجلسة
206
+ if 'equipment_catalog' in st.session_state:
207
+ return st.session_state.equipment_catalog
208
+
209
+ # إذا لم يكن موجوداً، إنشاء كتالوج افتراضي
210
+ equipment_data = []
211
+
212
+ # تخزين البيانات في حالة الجلسة
213
+ st.session_state.equipment_catalog = pd.DataFrame(equipment_data)
214
+
215
+ return st.session_state.equipment_catalog
216
+
217
+ def _get_materials_catalog(self):
218
+ """الحصول على كتالوج المواد"""
219
+
220
+ # التحقق من وجود كتالوج المواد في حالة الجلسة
221
+ if 'materials_catalog' in st.session_state:
222
+ return st.session_state.materials_catalog
223
+
224
+ # إذا لم يكن موجوداً، إنشاء كتالوج افتراضي
225
+ materials_data = []
226
+
227
+ # تخزين البيانات في حالة الجلسة
228
+ st.session_state.materials_catalog = pd.DataFrame(materials_data)
229
+
230
+ return st.session_state.materials_catalog
231
+
232
+ def _get_labor_catalog(self):
233
+ """الحصول على كتالوج العمالة"""
234
+
235
+ # التحقق من وجود كتالوج العمالة في حالة الجلسة
236
+ if 'labor_catalog' in st.session_state:
237
+ return st.session_state.labor_catalog
238
+
239
+ # إذا لم يكن موجوداً، إنشاء كتالوج افتراضي
240
+ labor_data = []
241
+
242
+ # تخزين البيانات في حالة الجلسة
243
+ st.session_state.labor_catalog = pd.DataFrame(labor_data)
244
+
245
+ return st.session_state.labor_catalog
246
+
247
+ def _get_subcontractors_catalog(self):
248
+ """الحصول على كتالوج مقاولي الباطن"""
249
+
250
+ # التحقق من وجود كتالوج مقاولي الباطن في حالة الجلسة
251
+ if 'subcontractors_catalog' in st.session_state:
252
+ return st.session_state.subcontractors_catalog
253
+
254
+ # إذا لم يكن موجوداً، إنشاء كتالوج افتراضي
255
+ subcontractors_data = []
256
+
257
+ # تخزين البيانات في حالة الجلسة
258
+ st.session_state.subcontractors_catalog = pd.DataFrame(subcontractors_data)
259
+
260
+ return st.session_state.subcontractors_catalog
261
+
262
+ def render(self):
263
+ """عرض واجهة التحليل الذكي للأسعار"""
264
+
265
+ st.markdown("## التحليل الذكي للأسعار")
266
+
267
+ # إنشاء تبويبات لعرض التحليل الذكي للأسعار
268
+ tabs = st.tabs([
269
+ "تحليل البنود",
270
+ "إعدادات التحليل",
271
+ "تقارير التحليل",
272
+ "المحتوى المحلي"
273
+ ])
274
+
275
+ with tabs[0]:
276
+ self._render_item_analysis_tab()
277
+
278
+ with tabs[1]:
279
+ self._render_analysis_settings_tab()
280
+
281
+ with tabs[2]:
282
+ self._render_analysis_reports_tab()
283
+
284
+ with tabs[3]:
285
+ self._render_local_content_tab()
286
+
287
+ def _render_item_analysis_tab(self):
288
+ """عرض تبويب تحليل البنود"""
289
+
290
+ st.markdown("### تحليل بنود جدول الكميات")
291
+
292
+ # استخراج البيانات
293
+ boq_items = st.session_state.boq_items
294
+
295
+ # إنشاء فلاتر للعرض
296
+ col1, col2, col3 = st.columns(3)
297
+
298
+ with col1:
299
+ # فلتر حسب الفئة
300
+ categories = ["الكل"] + sorted(boq_items["category"].unique().tolist())
301
+ selected_category = st.selectbox("اختر فئة البند", categories, key="item_analysis_category")
302
+
303
+ with col2:
304
+ # فلتر حسب الفئة الفرعية
305
+ if selected_category != "الكل":
306
+ subcategories = ["الكل"] + sorted(boq_items[boq_items["category"] == selected_category]["subcategory"].unique().tolist())
307
+ else:
308
+ subcategories = ["الكل"] + sorted(boq_items["subcategory"].unique().tolist())
309
+
310
+ selected_subcategory = st.selectbox("اختر التخصص", subcategories, key="item_analysis_subcategory")
311
+
312
+ with col3:
313
+ # فلتر حسب حالة التحليل
314
+ analysis_status = ["الكل", "تم التحليل", "لم يتم التحليل"]
315
+ selected_status = st.selectbox("اختر حالة التحليل", analysis_status, key="item_analysis_status")
316
+
317
+ # تطبيق الفلاتر
318
+ filtered_df = boq_items.copy()
319
+
320
+ if selected_category != "الكل":
321
+ filtered_df = filtered_df[filtered_df["category"] == selected_category]
322
+
323
+ if selected_subcategory != "الكل":
324
+ filtered_df = filtered_df[filtered_df["subcategory"] == selected_subcategory]
325
+
326
+ if selected_status != "الكل":
327
+ if selected_status == "تم التحليل":
328
+ filtered_df = filtered_df[filtered_df["analyzed"] == True]
329
+ else:
330
+ filtered_df = filtered_df[filtered_df["analyzed"] == False]
331
+
332
+ # عرض البيانات
333
+ if not filtered_df.empty:
334
+ # عرض عدد النتائج
335
+ st.info(f"تم العثور على {len(filtered_df)} بند")
336
+
337
+ # إنشاء جدول للعرض
338
+ display_df = filtered_df[["id", "description", "unit", "quantity", "unit_price", "total_price", "analyzed"]].copy()
339
+ display_df.columns = ["الكود", "الوصف", "الوحدة", "الكمية", "سعر الوحدة", "الإجمالي", "تم التحليل"]
340
+ display_df["تم التحليل"] = display_df["تم التحليل"].map({True: "✅", False: "❌"})
341
+
342
+ # عرض الجدول
343
+ st.dataframe(display_df, use_container_width=True)
344
+
345
+ # اختيار بند للتحليل
346
+ st.markdown("#### اختر بند للتحليل")
347
+
348
+ selected_item_id = st.selectbox("اختر كود البند", filtered_df["id"].tolist(), key="item_analysis_selected_id")
349
+
350
+ # استخراج البند المختار
351
+ selected_item = filtered_df[filtered_df["id"] == selected_item_id].iloc[0]
352
+
353
+ # عرض تفاصيل البند
354
+ st.markdown(f"**البند:** {selected_item['description']}")
355
+ st.markdown(f"**الوحدة:** {selected_item['unit']} | **الكمية:** {selected_item['quantity']} | **سعر الوحدة:** {selected_item['unit_price']} ريال | **الإجمالي:** {selected_item['total_price']} ريال")
356
+
357
+ # تحليل البند
358
+ st.markdown("#### تحليل البند")
359
+
360
+ # التحقق من حالة التحليل
361
+ if selected_item["analyzed"]:
362
+ # عرض نتائج التحليل السابق
363
+ st.success("تم تحليل هذا البند مسبقاً")
364
+
365
+ # استخراج مكونات البند
366
+ components = selected_item["components"]
367
+
368
+ # عرض مكونات البند
369
+ self._display_item_components(selected_item)
370
+
371
+ # زر إعادة التحليل
372
+ if st.button("إعادة تحليل البند", key="reanalyze_button"):
373
+ # تعيين البند الحالي
374
+ st.session_state.smart_price_analysis["current_item"] = selected_item.to_dict()
375
+
376
+ # إعادة توجيه إلى صفحة التحليل
377
+ st.experimental_rerun()
378
+ else:
379
+ # تحليل البند لأول مرة
380
+ if st.button("تحليل البند", key="analyze_button"):
381
+ # تعيين البند الحالي
382
+ st.session_state.smart_price_analysis["current_item"] = selected_item.to_dict()
383
+
384
+ # إعادة توجيه إلى صفحة التحليل
385
+ st.experimental_rerun()
386
+
387
+ # التحقق من وجود بند حالي قيد التحليل
388
+ current_item = st.session_state.smart_price_analysis["current_item"]
389
+
390
+ if current_item and current_item["id"] == selected_item_id:
391
+ # عرض نموذج التحليل
392
+ self._render_analysis_form(current_item)
393
+ else:
394
+ st.warning("لا يوجد بنود تطابق معايير البحث")
395
+
396
+ def _render_analysis_form(self, item):
397
+ """عرض نموذج تحليل البند"""
398
+
399
+ st.markdown("### تحليل البند")
400
+ st.markdown(f"**البند:** {item['description']}")
401
+ st.markdown(f"**الوحدة:** {item['unit']} | **الكمية:** {item['quantity']} | **سعر الوحدة:** {item['unit_price']} ريال | **الإجمالي:** {item['total_price']} ريال")
402
+
403
+ # استخراج نسب المكونات
404
+ price_components = st.session_state.smart_price_analysis["price_components"]
405
+
406
+ # حساب قيم المكونات
407
+ materials_value = item["unit_price"] * price_components["materials"]
408
+ equipment_value = item["unit_price"] * price_components["equipment"]
409
+ labor_value = item["unit_price"] * price_components["labor"]
410
+ subcontractors_value = item["unit_price"] * price_components["subcontractors"]
411
+
412
+ # إنشاء نموذج التحليل
413
+ with st.form("analysis_form"):
414
+ st.markdown("#### تحليل سعر الوحدة")
415
+
416
+ # المواد
417
+ st.markdown("##### المواد")
418
+ materials_col1, materials_col2 = st.columns(2)
419
+
420
+ with materials_col1:
421
+ materials_percentage = st.slider(
422
+ "نسبة المواد من سعر الوحدة",
423
+ min_value=0.0,
424
+ max_value=1.0,
425
+ value=price_components["materials"],
426
+ step=0.01,
427
+ format="%g%%",
428
+ key="materials_percentage"
429
+ ) * 100
430
+
431
+ with materials_col2:
432
+ materials_amount = st.number_input(
433
+ "قيمة المواد (ريال)",
434
+ min_value=0.0,
435
+ value=float(materials_value),
436
+ step=10.0,
437
+ key="materials_amount"
438
+ )
439
+
440
+ # إضافة المواد
441
+ materials_items = []
442
+
443
+ st.markdown("إضافة المواد")
444
+
445
+ for i in range(3): # السماح بإضافة 3 مواد كحد أقصى
446
+ material_col1, material_col2, material_col3, material_col4 = st.columns([3, 1, 1, 1])
447
+
448
+ with material_col1:
449
+ material_name = st.text_input(
450
+ "اسم المادة",
451
+ key=f"material_name_{i}"
452
+ )
453
+
454
+ with material_col2:
455
+ material_unit = st.text_input(
456
+ "الوحدة",
457
+ key=f"material_unit_{i}"
458
+ )
459
+
460
+ with material_col3:
461
+ material_quantity = st.number_input(
462
+ "الكمية",
463
+ min_value=0.0,
464
+ step=0.1,
465
+ key=f"material_quantity_{i}"
466
+ )
467
+
468
+ with material_col4:
469
+ material_price = st.number_input(
470
+ "السعر",
471
+ min_value=0.0,
472
+ step=10.0,
473
+ key=f"material_price_{i}"
474
+ )
475
+
476
+ if material_name and material_unit and material_quantity > 0 and material_price > 0:
477
+ materials_items.append({
478
+ "name": material_name,
479
+ "unit": material_unit,
480
+ "quantity": material_quantity,
481
+ "price": material_price,
482
+ "total": material_quantity * material_price
483
+ })
484
+
485
+ # المعدات
486
+ st.markdown("##### المعدات")
487
+ equipment_col1, equipment_col2 = st.columns(2)
488
+
489
+ with equipment_col1:
490
+ equipment_percentage = st.slider(
491
+ "نسبة المعدات من سعر الوحدة",
492
+ min_value=0.0,
493
+ max_value=1.0,
494
+ value=price_components["equipment"],
495
+ step=0.01,
496
+ format="%g%%",
497
+ key="equipment_percentage"
498
+ ) * 100
499
+
500
+ with equipment_col2:
501
+ equipment_amount = st.number_input(
502
+ "قيمة المعدات (ريال)",
503
+ min_value=0.0,
504
+ value=float(equipment_value),
505
+ step=10.0,
506
+ key="equipment_amount"
507
+ )
508
+
509
+ # إضافة المعدات
510
+ equipment_items = []
511
+
512
+ st.markdown("إضافة المعدات")
513
+
514
+ for i in range(3): # السماح بإضافة 3 معدات كحد أقصى
515
+ equipment_col1, equipment_col2, equipment_col3, equipment_col4 = st.columns([3, 1, 1, 1])
516
+
517
+ with equipment_col1:
518
+ equipment_name = st.text_input(
519
+ "اسم المعدة",
520
+ key=f"equipment_name_{i}"
521
+ )
522
+
523
+ with equipment_col2:
524
+ equipment_unit = st.text_input(
525
+ "الوحدة",
526
+ key=f"equipment_unit_{i}"
527
+ )
528
+
529
+ with equipment_col3:
530
+ equipment_quantity = st.number_input(
531
+ "الكمية",
532
+ min_value=0.0,
533
+ step=0.1,
534
+ key=f"equipment_quantity_{i}"
535
+ )
536
+
537
+ with equipment_col4:
538
+ equipment_price = st.number_input(
539
+ "السعر",
540
+ min_value=0.0,
541
+ step=10.0,
542
+ key=f"equipment_price_{i}"
543
+ )
544
+
545
+ if equipment_name and equipment_unit and equipment_quantity > 0 and equipment_price > 0:
546
+ equipment_items.append({
547
+ "name": equipment_name,
548
+ "unit": equipment_unit,
549
+ "quantity": equipment_quantity,
550
+ "price": equipment_price,
551
+ "total": equipment_quantity * equipment_price
552
+ })
553
+
554
+ # العمالة
555
+ st.markdown("##### العمالة")
556
+ labor_col1, labor_col2 = st.columns(2)
557
+
558
+ with labor_col1:
559
+ labor_percentage = st.slider(
560
+ "نسبة العمالة من سعر الوحدة",
561
+ min_value=0.0,
562
+ max_value=1.0,
563
+ value=price_components["labor"],
564
+ step=0.01,
565
+ format="%g%%",
566
+ key="labor_percentage"
567
+ ) * 100
568
+
569
+ with labor_col2:
570
+ labor_amount = st.number_input(
571
+ "قيمة العمالة (ريال)",
572
+ min_value=0.0,
573
+ value=float(labor_value),
574
+ step=10.0,
575
+ key="labor_amount"
576
+ )
577
+
578
+ # إضافة العمالة
579
+ labor_items = []
580
+
581
+ st.markdown("إضافة العمالة")
582
+
583
+ for i in range(3): # السماح بإضافة 3 عمال كحد أقصى
584
+ labor_col1, labor_col2, labor_col3, labor_col4 = st.columns([3, 1, 1, 1])
585
+
586
+ with labor_col1:
587
+ labor_name = st.text_input(
588
+ "المسمى الوظيفي",
589
+ key=f"labor_name_{i}"
590
+ )
591
+
592
+ with labor_col2:
593
+ labor_unit = st.text_input(
594
+ "الوحدة",
595
+ key=f"labor_unit_{i}"
596
+ )
597
+
598
+ with labor_col3:
599
+ labor_quantity = st.number_input(
600
+ "الكمية",
601
+ min_value=0.0,
602
+ step=0.1,
603
+ key=f"labor_quantity_{i}"
604
+ )
605
+
606
+ with labor_col4:
607
+ labor_price = st.number_input(
608
+ "السعر",
609
+ min_value=0.0,
610
+ step=10.0,
611
+ key=f"labor_price_{i}"
612
+ )
613
+
614
+ if labor_name and labor_unit and labor_quantity > 0 and labor_price > 0:
615
+ labor_items.append({
616
+ "name": labor_name,
617
+ "unit": labor_unit,
618
+ "quantity": labor_quantity,
619
+ "price": labor_price,
620
+ "total": labor_quantity * labor_price
621
+ })
622
+
623
+ # مقاولي الباطن
624
+ st.markdown("##### مقاولي الباطن")
625
+ subcontractors_col1, subcontractors_col2 = st.columns(2)
626
+
627
+ with subcontractors_col1:
628
+ subcontractors_percentage = st.slider(
629
+ "نسبة مقاولي الباطن من سعر الوحدة",
630
+ min_value=0.0,
631
+ max_value=1.0,
632
+ value=price_components["subcontractors"],
633
+ step=0.01,
634
+ format="%g%%",
635
+ key="subcontractors_percentage"
636
+ ) * 100
637
+
638
+ with subcontractors_col2:
639
+ subcontractors_amount = st.number_input(
640
+ "قيمة مقاولي الباطن (ريال)",
641
+ min_value=0.0,
642
+ value=float(subcontractors_value),
643
+ step=10.0,
644
+ key="subcontractors_amount"
645
+ )
646
+
647
+ # إضافة مقاولي الباطن
648
+ subcontractors_items = []
649
+
650
+ st.markdown("إضافة مقاولي الباطن")
651
+
652
+ for i in range(2): # السماح بإضافة 2 مقاول باطن كحد أقصى
653
+ subcontractor_col1, subcontractor_col2, subcontractor_col3 = st.columns([4, 1, 1])
654
+
655
+ with subcontractor_col1:
656
+ subcontractor_name = st.text_input(
657
+ "اسم مقاول الباطن",
658
+ key=f"subcontractor_name_{i}"
659
+ )
660
+
661
+ with subcontractor_col2:
662
+ subcontractor_work = st.text_input(
663
+ "نوع العمل",
664
+ key=f"subcontractor_work_{i}"
665
+ )
666
+
667
+ with subcontractor_col3:
668
+ subcontractor_price = st.number_input(
669
+ "السعر",
670
+ min_value=0.0,
671
+ step=10.0,
672
+ key=f"subcontractor_price_{i}"
673
+ )
674
+
675
+ if subcontractor_name and subcontractor_work and subcontractor_price > 0:
676
+ subcontractors_items.append({
677
+ "name": subcontractor_name,
678
+ "work": subcontractor_work,
679
+ "price": subcontractor_price
680
+ })
681
+
682
+ # التكاليف غير المباشرة
683
+ st.markdown("##### التكاليف غير المباشرة")
684
+
685
+ # استخراج نسب التكاليف غير المباشرة
686
+ indirect_costs = st.session_state.smart_price_analysis["indirect_costs"]
687
+
688
+ indirect_col1, indirect_col2, indirect_col3 = st.columns(3)
689
+
690
+ with indirect_col1:
691
+ overhead_percentage = st.slider(
692
+ "نسبة المصاريف العمومية والإدارية",
693
+ min_value=0.0,
694
+ max_value=0.5,
695
+ value=indirect_costs["overhead"],
696
+ step=0.01,
697
+ format="%g%%",
698
+ key="overhead_percentage"
699
+ ) * 100
700
+
701
+ with indirect_col2:
702
+ profit_percentage = st.slider(
703
+ "نسبة الربح",
704
+ min_value=0.0,
705
+ max_value=0.5,
706
+ value=indirect_costs["profit"],
707
+ step=0.01,
708
+ format="%g%%",
709
+ key="profit_percentage"
710
+ ) * 100
711
+
712
+ with indirect_col3:
713
+ contingency_percentage = st.slider(
714
+ "نسبة الطوارئ",
715
+ min_value=0.0,
716
+ max_value=0.2,
717
+ value=indirect_costs["contingency"],
718
+ step=0.01,
719
+ format="%g%%",
720
+ key="contingency_percentage"
721
+ ) * 100
722
+
723
+ # زر حفظ التحليل
724
+ submit_button = st.form_submit_button("حفظ التحليل")
725
+
726
+ if submit_button:
727
+ # التحقق من صحة البيانات
728
+ total_percentage = (materials_percentage + equipment_percentage + labor_percentage + subcontractors_percentage) / 100
729
+
730
+ if abs(total_percentage - 1.0) > 0.01:
731
+ st.error("مجموع نسب المكونات يجب أن يساوي 100%")
732
+ else:
733
+ # حساب إجمالي المكونات
734
+ materials_total = sum([item["total"] for item in materials_items]) if materials_items else materials_amount
735
+ equipment_total = sum([item["total"] for item in equipment_items]) if equipment_items else equipment_amount
736
+ labor_total = sum([item["total"] for item in labor_items]) if labor_items else labor_amount
737
+ subcontractors_total = sum([item["price"] for item in subcontractors_items]) if subcontractors_items else subcontractors_amount
738
+
739
+ # حساب التكاليف المباشرة
740
+ direct_cost = materials_total + equipment_total + labor_total + subcontractors_total
741
+
742
+ # حساب التكاليف غير المباشرة
743
+ overhead_amount = direct_cost * (overhead_percentage / 100)
744
+ profit_amount = direct_cost * (profit_percentage / 100)
745
+ contingency_amount = direct_cost * (contingency_percentage / 100)
746
+
747
+ # حساب إجمالي التكاليف
748
+ total_cost = direct_cost + overhead_amount + profit_amount + contingency_amount
749
+
750
+ # إنشاء مكونات البند
751
+ components = {
752
+ "materials": {
753
+ "percentage": materials_percentage / 100,
754
+ "amount": materials_amount,
755
+ "items": materials_items
756
+ },
757
+ "equipment": {
758
+ "percentage": equipment_percentage / 100,
759
+ "amount": equipment_amount,
760
+ "items": equipment_items
761
+ },
762
+ "labor": {
763
+ "percentage": labor_percentage / 100,
764
+ "amount": labor_amount,
765
+ "items": labor_items
766
+ },
767
+ "subcontractors": {
768
+ "percentage": subcontractors_percentage / 100,
769
+ "amount": subcontractors_amount,
770
+ "items": subcontractors_items
771
+ },
772
+ "indirect_costs": {
773
+ "overhead": {
774
+ "percentage": overhead_percentage / 100,
775
+ "amount": overhead_amount
776
+ },
777
+ "profit": {
778
+ "percentage": profit_percentage / 100,
779
+ "amount": profit_amount
780
+ },
781
+ "contingency": {
782
+ "percentage": contingency_percentage / 100,
783
+ "amount": contingency_amount
784
+ }
785
+ },
786
+ "direct_cost": direct_cost,
787
+ "total_cost": total_cost,
788
+ "analysis_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
789
+ }
790
+
791
+ # تحديث البند في جدول الكميات
792
+ boq_items = st.session_state.boq_items
793
+ item_index = boq_items[boq_items["id"] == item["id"]].index[0]
794
+
795
+ boq_items.at[item_index, "analyzed"] = True
796
+ boq_items.at[item_index, "components"] = components
797
+
798
+ # تحديث حالة الجلسة
799
+ st.session_state.boq_items = boq_items
800
+
801
+ # إضافة التحليل إلى سجل التحليلات
802
+ analysis_history = st.session_state.smart_price_analysis["analysis_history"]
803
+ analysis_history.append({
804
+ "item_id": item["id"],
805
+ "item_description": item["description"],
806
+ "unit_price": item["unit_price"],
807
+ "components": components,
808
+ "analysis_date": components["analysis_date"]
809
+ })
810
+
811
+ st.session_state.smart_price_analysis["analysis_history"] = analysis_history
812
+
813
+ # إعادة تعيين البند الحالي
814
+ st.session_state.smart_price_analysis["current_item"] = None
815
+
816
+ # عرض رسالة نجاح
817
+ st.success(f"تم تحليل البند {item['id']} بنجاح!")
818
+
819
+ # إعادة توجيه إلى صفحة التحليل
820
+ st.experimental_rerun()
821
+
822
+ def _display_item_components(self, item):
823
+ """عرض مكونات البند"""
824
+
825
+ # استخراج مكونات البند
826
+ components = item["components"]
827
+
828
+ if not components:
829
+ st.warning("لم يتم تحليل هذا البند بعد")
830
+ return
831
+
832
+ # عرض ملخص التحليل
833
+ st.markdown("#### ملخص التحليل")
834
+
835
+ # عرض تاريخ التحليل
836
+ st.markdown(f"**تاريخ التحليل:** {components['analysis_date']}")
837
+
838
+ # عرض التكاليف المباشرة وغير المباشرة
839
+ col1, col2 = st.columns(2)
840
+
841
+ with col1:
842
+ st.markdown(f"**التكاليف المباشرة:** {components['direct_cost']:.2f} ريال")
843
+ st.markdown(f"**التكاليف غير المباشرة:** {(components['total_cost'] - components['direct_cost']):.2f} ريال")
844
+
845
+ with col2:
846
+ st.markdown(f"**إجمالي التكاليف:** {components['total_cost']:.2f} ريال")
847
+ st.markdown(f"**سعر الوحدة:** {item['unit_price']:.2f} ريال")
848
+
849
+ # عرض نسب المكونات
850
+ st.markdown("#### نسب المكونات")
851
+
852
+ # إنشاء بيانات الرسم البياني
853
+ components_data = {
854
+ "المكون": ["المواد", "المعدات", "العمالة", "مقاولي الباطن"],
855
+ "النسبة": [
856
+ components["materials"]["percentage"] * 100,
857
+ components["equipment"]["percentage"] * 100,
858
+ components["labor"]["percentage"] * 100,
859
+ components["subcontractors"]["percentage"] * 100
860
+ ],
861
+ "القيمة": [
862
+ components["materials"]["amount"],
863
+ components["equipment"]["amount"],
864
+ components["labor"]["amount"],
865
+ components["subcontractors"]["amount"]
866
+ ]
867
+ }
868
+
869
+ # إنشاء رسم بياني دائري
870
+ fig = px.pie(
871
+ components_data,
872
+ values="النسبة",
873
+ names="المكون",
874
+ title="توزيع مكونات سعر الوحدة",
875
+ color="المكون",
876
+ hover_data=["القيمة"]
877
+ )
878
+
879
+ st.plotly_chart(fig, use_container_width=True)
880
+
881
+ # عرض تفاصيل المكونات
882
+ st.markdown("#### تفاصيل المكونات")
883
+
884
+ # إنشاء تبويبات لعرض تفاصيل المكونات
885
+ component_tabs = st.tabs(["المواد", "المعدات", "العمالة", "مقاولي الباطن", "التكاليف غير المباشرة"])
886
+
887
+ with component_tabs[0]:
888
+ # عرض تفاصيل المواد
889
+ st.markdown("##### المواد")
890
+ st.markdown(f"**نسبة المواد:** {components['materials']['percentage'] * 100:.2f}%")
891
+ st.markdown(f"**قيمة المواد:** {components['materials']['amount']:.2f} ريال")
892
+
893
+ # عرض قائمة المواد
894
+ if components["materials"]["items"]:
895
+ materials_df = pd.DataFrame(components["materials"]["items"])
896
+ materials_df.columns = ["اسم المادة", "الوحدة", "الكمية", "السعر", "الإجمالي"]
897
+
898
+ st.dataframe(materials_df, use_container_width=True)
899
+ else:
900
+ st.info("لم يتم إضافة مواد محددة")
901
+
902
+ with component_tabs[1]:
903
+ # عرض تفاصيل المعدات
904
+ st.markdown("##### المعدات")
905
+ st.markdown(f"**نسبة المعدات:** {components['equipment']['percentage'] * 100:.2f}%")
906
+ st.markdown(f"**قيمة المعدات:** {components['equipment']['amount']:.2f} ريال")
907
+
908
+ # عرض قائمة المعدات
909
+ if components["equipment"]["items"]:
910
+ equipment_df = pd.DataFrame(components["equipment"]["items"])
911
+ equipment_df.columns = ["اسم المعدة", "الوحدة", "الكمية", "السعر", "الإجمالي"]
912
+
913
+ st.dataframe(equipment_df, use_container_width=True)
914
+ else:
915
+ st.info("لم يتم إضافة معدات محددة")
916
+
917
+ with component_tabs[2]:
918
+ # عرض تفاصيل العمالة
919
+ st.markdown("##### العمالة")
920
+ st.markdown(f"**نسبة العمالة:** {components['labor']['percentage'] * 100:.2f}%")
921
+ st.markdown(f"**قيمة العمالة:** {components['labor']['amount']:.2f} ريال")
922
+
923
+ # عرض قائمة العمالة
924
+ if components["labor"]["items"]:
925
+ labor_df = pd.DataFrame(components["labor"]["items"])
926
+ labor_df.columns = ["المسمى الوظيفي", "الوحدة", "الكمية", "السعر", "الإجمالي"]
927
+
928
+ st.dataframe(labor_df, use_container_width=True)
929
+ else:
930
+ st.info("لم يتم إضافة عمالة محددة")
931
+
932
+ with component_tabs[3]:
933
+ # عرض تفاصيل مقاولي الباطن
934
+ st.markdown("##### مقاولي الباطن")
935
+ st.markdown(f"**نسبة مقاولي الباطن:** {components['subcontractors']['percentage'] * 100:.2f}%")
936
+ st.markdown(f"**قيمة مقاولي الباطن:** {components['subcontractors']['amount']:.2f} ريال")
937
+
938
+ # عرض قائمة مقاولي الباطن
939
+ if components["subcontractors"]["items"]:
940
+ subcontractors_df = pd.DataFrame(components["subcontractors"]["items"])
941
+ subcontractors_df.columns = ["اسم مقاول الباطن", "نوع العمل", "السعر"]
942
+
943
+ st.dataframe(subcontractors_df, use_container_width=True)
944
+ else:
945
+ st.info("لم يتم إضافة مقاولي باطن محددين")
946
+
947
+ with component_tabs[4]:
948
+ # عرض تفاصيل التكاليف غير المباشرة
949
+ st.markdown("##### التكاليف غير المباشرة")
950
+
951
+ # إنشاء بيانات التكاليف غير المباشرة
952
+ indirect_costs = components["indirect_costs"]
953
+
954
+ indirect_data = {
955
+ "البند": ["المصاريف العمومية والإدارية", "الربح", "الطوارئ"],
956
+ "النسبة": [
957
+ indirect_costs["overhead"]["percentage"] * 100,
958
+ indirect_costs["profit"]["percentage"] * 100,
959
+ indirect_costs["contingency"]["percentage"] * 100
960
+ ],
961
+ "القيمة": [
962
+ indirect_costs["overhead"]["amount"],
963
+ indirect_costs["profit"]["amount"],
964
+ indirect_costs["contingency"]["amount"]
965
+ ]
966
+ }
967
+
968
+ # إنشاء جدول للعرض
969
+ indirect_df = pd.DataFrame(indirect_data)
970
+
971
+ st.dataframe(indirect_df, use_container_width=True)
972
+
973
+ # إنشاء رسم بياني للتكاليف غير المباشرة
974
+ fig = px.bar(
975
+ indirect_df,
976
+ x="البند",
977
+ y="القيمة",
978
+ title="توزيع التكاليف غير المباشرة",
979
+ color="البند",
980
+ text_auto=True
981
+ )
982
+
983
+ st.plotly_chart(fig, use_container_width=True)
984
+
985
+ def _render_analysis_settings_tab(self):
986
+ """عرض تبويب إعدادات التحليل"""
987
+
988
+ st.markdown("### إعدادات التحليل الذكي للأسعار")
989
+
990
+ # استخراج إعدادات التحليل
991
+ price_components = st.session_state.smart_price_analysis["price_components"]
992
+ indirect_costs = st.session_state.smart_price_analysis["indirect_costs"]
993
+ productivity_factors = st.session_state.smart_price_analysis["productivity_factors"]
994
+
995
+ # إنشاء نموذج إعدادات التحليل
996
+ with st.form("analysis_settings_form"):
997
+ st.markdown("#### نسب مكونات السعر الافتراضية")
998
+
999
+ # نسب مكونات السعر
1000
+ components_col1, components_col2 = st.columns(2)
1001
+
1002
+ with components_col1:
1003
+ materials_percentage = st.slider(
1004
+ "نسبة المواد من سعر الوحدة",
1005
+ min_value=0.0,
1006
+ max_value=1.0,
1007
+ value=price_components["materials"],
1008
+ step=0.01,
1009
+ format="%g%%",
1010
+ key="settings_materials_percentage"
1011
+ ) * 100
1012
+
1013
+ equipment_percentage = st.slider(
1014
+ "نسبة المعدات من سعر الوحدة",
1015
+ min_value=0.0,
1016
+ max_value=1.0,
1017
+ value=price_components["equipment"],
1018
+ step=0.01,
1019
+ format="%g%%",
1020
+ key="settings_equipment_percentage"
1021
+ ) * 100
1022
+
1023
+ with components_col2:
1024
+ labor_percentage = st.slider(
1025
+ "نسبة العمالة من سعر الوحدة",
1026
+ min_value=0.0,
1027
+ max_value=1.0,
1028
+ value=price_components["labor"],
1029
+ step=0.01,
1030
+ format="%g%%",
1031
+ key="settings_labor_percentage"
1032
+ ) * 100
1033
+
1034
+ subcontractors_percentage = st.slider(
1035
+ "نسبة مقاولي الباطن من سعر الوحدة",
1036
+ min_value=0.0,
1037
+ max_value=1.0,
1038
+ value=price_components["subcontractors"],
1039
+ step=0.01,
1040
+ format="%g%%",
1041
+ key="settings_subcontractors_percentage"
1042
+ ) * 100
1043
+
1044
+ # التكاليف غير المباشرة
1045
+ st.markdown("#### نسب التكاليف غير المباشرة الافتراضية")
1046
+
1047
+ indirect_col1, indirect_col2, indirect_col3 = st.columns(3)
1048
+
1049
+ with indirect_col1:
1050
+ overhead_percentage = st.slider(
1051
+ "نسبة المصاريف العمومية والإدارية",
1052
+ min_value=0.0,
1053
+ max_value=0.5,
1054
+ value=indirect_costs["overhead"],
1055
+ step=0.01,
1056
+ format="%g%%",
1057
+ key="settings_overhead_percentage"
1058
+ ) * 100
1059
+
1060
+ with indirect_col2:
1061
+ profit_percentage = st.slider(
1062
+ "نسبة الربح",
1063
+ min_value=0.0,
1064
+ max_value=0.5,
1065
+ value=indirect_costs["profit"],
1066
+ step=0.01,
1067
+ format="%g%%",
1068
+ key="settings_profit_percentage"
1069
+ ) * 100
1070
+
1071
+ with indirect_col3:
1072
+ contingency_percentage = st.slider(
1073
+ "نسبة الطوارئ",
1074
+ min_value=0.0,
1075
+ max_value=0.2,
1076
+ value=indirect_costs["contingency"],
1077
+ step=0.01,
1078
+ format="%g%%",
1079
+ key="settings_contingency_percentage"
1080
+ ) * 100
1081
+
1082
+ # عوامل الإنتاجية
1083
+ st.markdown("#### عوامل الإنتاجية")
1084
+
1085
+ productivity_col1, productivity_col2 = st.columns(2)
1086
+
1087
+ with productivity_col1:
1088
+ weather_factor = st.slider(
1089
+ "عامل الطقس",
1090
+ min_value=0.5,
1091
+ max_value=1.5,
1092
+ value=productivity_factors["weather"],
1093
+ step=0.1,
1094
+ key="settings_weather_factor"
1095
+ )
1096
+
1097
+ location_factor = st.slider(
1098
+ "عامل الموقع",
1099
+ min_value=0.5,
1100
+ max_value=1.5,
1101
+ value=productivity_factors["location"],
1102
+ step=0.1,
1103
+ key="settings_location_factor"
1104
+ )
1105
+
1106
+ complexity_factor = st.slider(
1107
+ "عامل التعقيد",
1108
+ min_value=0.5,
1109
+ max_value=1.5,
1110
+ value=productivity_factors["complexity"],
1111
+ step=0.1,
1112
+ key="settings_complexity_factor"
1113
+ )
1114
+
1115
+ with productivity_col2:
1116
+ schedule_factor = st.slider(
1117
+ "عامل الجدول الزمني",
1118
+ min_value=0.5,
1119
+ max_value=1.5,
1120
+ value=productivity_factors["schedule"],
1121
+ step=0.1,
1122
+ key="settings_schedule_factor"
1123
+ )
1124
+
1125
+ resources_factor = st.slider(
1126
+ "عامل ��لموارد",
1127
+ min_value=0.5,
1128
+ max_value=1.5,
1129
+ value=productivity_factors["resources"],
1130
+ step=0.1,
1131
+ key="settings_resources_factor"
1132
+ )
1133
+
1134
+ # زر حفظ الإعدادات
1135
+ submit_button = st.form_submit_button("حفظ الإعدادات")
1136
+
1137
+ if submit_button:
1138
+ # التحقق من صحة البيانات
1139
+ total_percentage = (materials_percentage + equipment_percentage + labor_percentage + subcontractors_percentage) / 100
1140
+
1141
+ if abs(total_percentage - 1.0) > 0.01:
1142
+ st.error("مجموع نسب المكونات يجب أن يساوي 100%")
1143
+ else:
1144
+ # تحديث نسب مكونات السعر
1145
+ price_components["materials"] = materials_percentage / 100
1146
+ price_components["equipment"] = equipment_percentage / 100
1147
+ price_components["labor"] = labor_percentage / 100
1148
+ price_components["subcontractors"] = subcontractors_percentage / 100
1149
+
1150
+ # تحديث نسب التكاليف غير المباشرة
1151
+ indirect_costs["overhead"] = overhead_percentage / 100
1152
+ indirect_costs["profit"] = profit_percentage / 100
1153
+ indirect_costs["contingency"] = contingency_percentage / 100
1154
+
1155
+ # تحديث عوامل الإنتاجية
1156
+ productivity_factors["weather"] = weather_factor
1157
+ productivity_factors["location"] = location_factor
1158
+ productivity_factors["complexity"] = complexity_factor
1159
+ productivity_factors["schedule"] = schedule_factor
1160
+ productivity_factors["resources"] = resources_factor
1161
+
1162
+ # تحديث حالة الجلسة
1163
+ st.session_state.smart_price_analysis["price_components"] = price_components
1164
+ st.session_state.smart_price_analysis["indirect_costs"] = indirect_costs
1165
+ st.session_state.smart_price_analysis["productivity_factors"] = productivity_factors
1166
+
1167
+ # عرض رسالة نجاح
1168
+ st.success("تم حفظ إعدادات التحليل بنجاح!")
1169
+
1170
+ # عرض الإعدادات الحالية
1171
+ st.markdown("### الإعدادات الحالية")
1172
+
1173
+ # عرض نسب مكونات السعر
1174
+ st.markdown("#### نسب مكونات السعر")
1175
+
1176
+ # إنشاء بيانات الرسم البياني
1177
+ components_data = {
1178
+ "المكون": ["المواد", "المعدات", "العمالة", "مقاولي الباطن"],
1179
+ "النسبة": [
1180
+ price_components["materials"] * 100,
1181
+ price_components["equipment"] * 100,
1182
+ price_components["labor"] * 100,
1183
+ price_components["subcontractors"] * 100
1184
+ ]
1185
+ }
1186
+
1187
+ # إنشاء رسم بياني دائري
1188
+ fig = px.pie(
1189
+ components_data,
1190
+ values="النسبة",
1191
+ names="المكون",
1192
+ title="توزيع مكونات سعر الوحدة",
1193
+ color="المكون"
1194
+ )
1195
+
1196
+ st.plotly_chart(fig, use_container_width=True)
1197
+
1198
+ # عرض نسب التكاليف غير المباشرة
1199
+ st.markdown("#### نسب التكاليف غير المباشرة")
1200
+
1201
+ # إنشاء بيانات الرسم البياني
1202
+ indirect_data = {
1203
+ "البند": ["المصاريف العمومية والإدارية", "الربح", "الطوارئ"],
1204
+ "النسبة": [
1205
+ indirect_costs["overhead"] * 100,
1206
+ indirect_costs["profit"] * 100,
1207
+ indirect_costs["contingency"] * 100
1208
+ ]
1209
+ }
1210
+
1211
+ # إنشاء رسم بياني شريطي
1212
+ fig = px.bar(
1213
+ indirect_data,
1214
+ x="البند",
1215
+ y="النسبة",
1216
+ title="نسب التكاليف غير المباشرة",
1217
+ color="البند",
1218
+ text_auto=True
1219
+ )
1220
+
1221
+ st.plotly_chart(fig, use_container_width=True)
1222
+
1223
+ # عرض عوامل الإنتاجية
1224
+ st.markdown("#### عوامل الإنتاجية")
1225
+
1226
+ # إنشاء بيانات الرسم البياني
1227
+ productivity_data = {
1228
+ "العامل": ["الطقس", "الموقع", "التعقيد", "الجدول الزمني", "الموارد"],
1229
+ "القيمة": [
1230
+ productivity_factors["weather"],
1231
+ productivity_factors["location"],
1232
+ productivity_factors["complexity"],
1233
+ productivity_factors["schedule"],
1234
+ productivity_factors["resources"]
1235
+ ]
1236
+ }
1237
+
1238
+ # إنشاء رسم بياني شريطي
1239
+ fig = px.bar(
1240
+ productivity_data,
1241
+ x="العامل",
1242
+ y="القيمة",
1243
+ title="عوامل الإنتاجية",
1244
+ color="العامل",
1245
+ text_auto=True
1246
+ )
1247
+
1248
+ # إضافة خط أفقي عند القيمة 1.0
1249
+ fig.add_shape(
1250
+ type="line",
1251
+ x0=-0.5,
1252
+ y0=1.0,
1253
+ x1=4.5,
1254
+ y1=1.0,
1255
+ line=dict(
1256
+ color="red",
1257
+ width=2,
1258
+ dash="dash"
1259
+ )
1260
+ )
1261
+
1262
+ st.plotly_chart(fig, use_container_width=True)
1263
+
1264
+ def _render_analysis_reports_tab(self):
1265
+ """عرض تبويب تقارير التحليل"""
1266
+
1267
+ st.markdown("### تقارير التحليل الذكي للأسعار")
1268
+
1269
+ # استخراج البيانات
1270
+ boq_items = st.session_state.boq_items
1271
+ analysis_history = st.session_state.smart_price_analysis["analysis_history"]
1272
+
1273
+ # عرض ملخص التحليل
1274
+ st.markdown("#### ملخص التحليل")
1275
+
1276
+ # حساب عدد البنود المحللة وغير المحللة
1277
+ analyzed_count = len(boq_items[boq_items["analyzed"] == True])
1278
+ not_analyzed_count = len(boq_items[boq_items["analyzed"] == False])
1279
+
1280
+ # عرض نسبة التحليل
1281
+ analysis_percentage = analyzed_count / len(boq_items) * 100 if len(boq_items) > 0 else 0
1282
+
1283
+ st.markdown(f"**عدد البنود المحللة:** {analyzed_count} من أصل {len(boq_items)} ({analysis_percentage:.2f}%)")
1284
+
1285
+ # إنشاء مؤشر التقدم
1286
+ st.progress(analysis_percentage / 100)
1287
+
1288
+ # عرض توزيع البنود المحللة حسب الفئة
1289
+ st.markdown("#### توزيع البنود المحللة حسب الفئة")
1290
+
1291
+ # حساب عدد البنود المحللة لكل فئة
1292
+ category_analysis = boq_items.groupby(["category", "analyzed"]).size().unstack(fill_value=0).reset_index()
1293
+
1294
+ if True in category_analysis.columns:
1295
+ category_analysis.columns = ["الفئة", "غير محلل", "محلل"]
1296
+
1297
+ # إنشاء رسم بياني شريطي
1298
+ fig = px.bar(
1299
+ category_analysis,
1300
+ x="الفئة",
1301
+ y=["محلل", "غير محلل"],
1302
+ title="توزيع البنود المحللة حسب الفئة",
1303
+ barmode="stack",
1304
+ color_discrete_map={"محلل": "green", "غير محلل": "red"}
1305
+ )
1306
+
1307
+ st.plotly_chart(fig, use_container_width=True)
1308
+ else:
1309
+ st.info("لا يوجد بنود محللة بعد")
1310
+
1311
+ # عرض توزيع مكونات الأسعار
1312
+ st.markdown("#### توزيع مكونات الأسعار")
1313
+
1314
+ # التحقق من وجود بنود محللة
1315
+ if analyzed_count > 0:
1316
+ # استخراج البنود المحللة
1317
+ analyzed_items = boq_items[boq_items["analyzed"] == True]
1318
+
1319
+ # إنشاء قائمة لتخزين بيانات المكونات
1320
+ components_data = []
1321
+
1322
+ # استخراج بيانات المكونات
1323
+ for _, item in analyzed_items.iterrows():
1324
+ components = item["components"]
1325
+
1326
+ components_data.append({
1327
+ "id": item["id"],
1328
+ "description": item["description"],
1329
+ "unit_price": item["unit_price"],
1330
+ "materials_percentage": components["materials"]["percentage"],
1331
+ "equipment_percentage": components["equipment"]["percentage"],
1332
+ "labor_percentage": components["labor"]["percentage"],
1333
+ "subcontractors_percentage": components["subcontractors"]["percentage"],
1334
+ "materials_amount": components["materials"]["amount"],
1335
+ "equipment_amount": components["equipment"]["amount"],
1336
+ "labor_amount": components["labor"]["amount"],
1337
+ "subcontractors_amount": components["subcontractors"]["amount"]
1338
+ })
1339
+
1340
+ # إنشاء DataFrame
1341
+ components_df = pd.DataFrame(components_data)
1342
+
1343
+ # حساب متوسط النسب
1344
+ avg_materials_percentage = components_df["materials_percentage"].mean() * 100
1345
+ avg_equipment_percentage = components_df["equipment_percentage"].mean() * 100
1346
+ avg_labor_percentage = components_df["labor_percentage"].mean() * 100
1347
+ avg_subcontractors_percentage = components_df["subcontractors_percentage"].mean() * 100
1348
+
1349
+ # عرض متوسط النسب
1350
+ st.markdown("##### متوسط نسب المكونات")
1351
+
1352
+ avg_components_data = {
1353
+ "المكون": ["المواد", "المعدات", "العمالة", "مقاولي الباطن"],
1354
+ "النسبة": [
1355
+ avg_materials_percentage,
1356
+ avg_equipment_percentage,
1357
+ avg_labor_percentage,
1358
+ avg_subcontractors_percentage
1359
+ ]
1360
+ }
1361
+
1362
+ # إنشاء رسم بياني دائري
1363
+ fig = px.pie(
1364
+ avg_components_data,
1365
+ values="النسبة",
1366
+ names="المكون",
1367
+ title="متوسط نسب مكونات الأسعار",
1368
+ color="المكون"
1369
+ )
1370
+
1371
+ st.plotly_chart(fig, use_container_width=True)
1372
+
1373
+ # عرض توزيع النسب حسب البند
1374
+ st.markdown("##### توزيع نسب المكونات حسب البند")
1375
+
1376
+ # إنشاء بيانات للرسم البياني
1377
+ item_components_data = []
1378
+
1379
+ for _, row in components_df.iterrows():
1380
+ item_components_data.extend([
1381
+ {"البند": row["id"], "المكون": "المواد", "النسبة": row["materials_percentage"] * 100},
1382
+ {"البند": row["id"], "المكون": "المعدات", "النسبة": row["equipment_percentage"] * 100},
1383
+ {"البند": row["id"], "المكون": "العمالة", "النسبة": row["labor_percentage"] * 100},
1384
+ {"البند": row["id"], "المكون": "مقاولي الباطن", "النسبة": row["subcontractors_percentage"] * 100}
1385
+ ])
1386
+
1387
+ # إنشاء DataFrame
1388
+ item_components_df = pd.DataFrame(item_components_data)
1389
+
1390
+ # إنشاء رسم بياني شريطي
1391
+ fig = px.bar(
1392
+ item_components_df,
1393
+ x="البند",
1394
+ y="النسبة",
1395
+ color="المكون",
1396
+ title="توزيع نسب المكونات حسب البند",
1397
+ barmode="stack"
1398
+ )
1399
+
1400
+ st.plotly_chart(fig, use_container_width=True)
1401
+
1402
+ # عرض مقارنة أسعار الوحدة
1403
+ st.markdown("##### مقارنة أسعار الوحدة")
1404
+
1405
+ # إنشاء بيانات للرسم البياني
1406
+ unit_price_data = []
1407
+
1408
+ for _, row in components_df.iterrows():
1409
+ unit_price_data.extend([
1410
+ {"البند": row["id"], "المكون": "المواد", "القيمة": row["materials_amount"]},
1411
+ {"البند": row["id"], "المكون": "المعدات", "القيمة": row["equipment_amount"]},
1412
+ {"البند": row["id"], "المكون": "العمالة", "القيمة": row["labor_amount"]},
1413
+ {"البند": row["id"], "المكون": "مقاولي الباطن", "القيمة": row["subcontractors_amount"]}
1414
+ ])
1415
+
1416
+ # إنشاء DataFrame
1417
+ unit_price_df = pd.DataFrame(unit_price_data)
1418
+
1419
+ # إنشاء رسم بياني شريطي
1420
+ fig = px.bar(
1421
+ unit_price_df,
1422
+ x="البند",
1423
+ y="القيمة",
1424
+ color="المكون",
1425
+ title="مقارنة مكونات أسعار الوحدة",
1426
+ barmode="stack"
1427
+ )
1428
+
1429
+ # إضافة خط لسعر الوحدة
1430
+ for i, row in components_df.iterrows():
1431
+ fig.add_shape(
1432
+ type="line",
1433
+ x0=i - 0.4,
1434
+ y0=row["unit_price"],
1435
+ x1=i + 0.4,
1436
+ y1=row["unit_price"],
1437
+ line=dict(
1438
+ color="red",
1439
+ width=2,
1440
+ dash="dash"
1441
+ )
1442
+ )
1443
+
1444
+ st.plotly_chart(fig, use_container_width=True)
1445
+ else:
1446
+ st.info("لا يوجد بنود محللة بعد")
1447
+
1448
+ # عرض سجل التحليلات
1449
+ st.markdown("#### سجل التحليلات")
1450
+
1451
+ if analysis_history:
1452
+ # عرض عدد التحليلات
1453
+ st.markdown(f"**عدد التحليلات:** {len(analysis_history)}")
1454
+
1455
+ # عرض آخر 5 تحليلات
1456
+ st.markdown("##### آخر 5 تحليلات")
1457
+
1458
+ for i, analysis in enumerate(analysis_history[-5:]):
1459
+ st.markdown(f"**{i+1}. البند:** {analysis['item_id']} - {analysis['item_description']}")
1460
+ st.markdown(f"**تاريخ التحليل:** {analysis['analysis_date']}")
1461
+ st.markdown(f"**سعر الوحدة:** {analysis['unit_price']} ريال")
1462
+ st.markdown("---")
1463
+ else:
1464
+ st.info("لا يوجد سجل تحليلات بعد")
1465
+
1466
+ def _render_local_content_tab(self):
1467
+ """عرض تبويب المحتوى المحلي"""
1468
+
1469
+ st.markdown("### تحليل المحتوى المحلي")
1470
+
1471
+ # استخراج بيانات المحتوى المحلي
1472
+ local_content = st.session_state.smart_price_analysis["local_content"]
1473
+
1474
+ # إنشاء نموذج إعدادات المحتوى المحلي
1475
+ with st.form("local_content_form"):
1476
+ st.markdown("#### إعدادات المحتوى المحلي")
1477
+
1478
+ # النسبة المستهدفة للمحتوى المحلي
1479
+ target_percentage = st.slider(
1480
+ "النسبة المستهدفة للمحتوى المحلي",
1481
+ min_value=0.0,
1482
+ max_value=1.0,
1483
+ value=local_content["target"],
1484
+ step=0.01,
1485
+ format="%g%%",
1486
+ key="local_content_target"
1487
+ ) * 100
1488
+
1489
+ # نسب المحتوى المحلي لكل مكون
1490
+ st.markdown("#### نسب المحتوى المحلي لكل مكون")
1491
+
1492
+ local_col1, local_col2 = st.columns(2)
1493
+
1494
+ with local_col1:
1495
+ materials_local = st.slider(
1496
+ "نسبة المواد المحلية",
1497
+ min_value=0.0,
1498
+ max_value=1.0,
1499
+ value=local_content["materials_local"],
1500
+ step=0.01,
1501
+ format="%g%%",
1502
+ key="materials_local"
1503
+ ) * 100
1504
+
1505
+ equipment_local = st.slider(
1506
+ "نسبة المعدات المحلية",
1507
+ min_value=0.0,
1508
+ max_value=1.0,
1509
+ value=local_content["equipment_local"],
1510
+ step=0.01,
1511
+ format="%g%%",
1512
+ key="equipment_local"
1513
+ ) * 100
1514
+
1515
+ with local_col2:
1516
+ labor_local = st.slider(
1517
+ "نسبة العمالة المحلية",
1518
+ min_value=0.0,
1519
+ max_value=1.0,
1520
+ value=local_content["labor_local"],
1521
+ step=0.01,
1522
+ format="%g%%",
1523
+ key="labor_local"
1524
+ ) * 100
1525
+
1526
+ subcontractors_local = st.slider(
1527
+ "نسبة مقاولي الباطن المحليين",
1528
+ min_value=0.0,
1529
+ max_value=1.0,
1530
+ value=local_content["subcontractors_local"],
1531
+ step=0.01,
1532
+ format="%g%%",
1533
+ key="subcontractors_local"
1534
+ ) * 100
1535
+
1536
+ # زر حفظ الإعدادات
1537
+ submit_button = st.form_submit_button("حفظ إعدادات المحتوى المحلي")
1538
+
1539
+ if submit_button:
1540
+ # تحديث إعدادات المحتوى المحلي
1541
+ local_content["target"] = target_percentage / 100
1542
+ local_content["materials_local"] = materials_local / 100
1543
+ local_content["equipment_local"] = equipment_local / 100
1544
+ local_content["labor_local"] = labor_local / 100
1545
+ local_content["subcontractors_local"] = subcontractors_local / 100
1546
+
1547
+ # تحديث حالة الجلسة
1548
+ st.session_state.smart_price_analysis["local_content"] = local_content
1549
+
1550
+ # عرض رسالة نجاح
1551
+ st.success("تم حفظ إعدادات المحتوى المحلي بنجاح!")
1552
+
1553
+ # حساب نسبة المحتوى المحلي الفعلية
1554
+ st.markdown("#### حساب نسبة المحتوى المحلي الفعلية")
1555
+
1556
+ # استخراج نسب مكونات السعر
1557
+ price_components = st.session_state.smart_price_analysis["price_components"]
1558
+
1559
+ # حساب نسبة المحتوى المحلي الفعلية
1560
+ actual_local_content = (
1561
+ price_components["materials"] * local_content["materials_local"] +
1562
+ price_components["equipment"] * local_content["equipment_local"] +
1563
+ price_components["labor"] * local_content["labor_local"] +
1564
+ price_components["subcontractors"] * local_content["subcontractors_local"]
1565
+ )
1566
+
1567
+ # عرض نسبة المحتوى المحلي الفعلية
1568
+ st.markdown(f"**نسبة المحتوى المحلي الفعلية:** {actual_local_content * 100:.2f}%")
1569
+ st.markdown(f"**النسبة المستهدفة للمحتوى المحلي:** {local_content['target'] * 100:.2f}%")
1570
+
1571
+ # عرض مؤشر التقدم
1572
+ progress_percentage = min(actual_local_content / local_content["target"], 1.0) if local_content["target"] > 0 else 0
1573
+
1574
+ st.progress(progress_percentage)
1575
+
1576
+ # عرض حالة المحتوى المحلي
1577
+ if actual_local_content >= local_content["target"]:
1578
+ st.success("تم تحقيق النسبة المستهدفة للمحتوى المحلي")
1579
+ else:
1580
+ st.warning("لم يتم تحقيق النسبة المستهدفة للمحتوى المحلي")
1581
+
1582
+ # عرض مساهمة كل مكون في المحتوى المحلي
1583
+ st.markdown("#### مساهمة كل مكون في المحتوى المحلي")
1584
+
1585
+ # حساب مساهمة كل مكون
1586
+ materials_contribution = price_components["materials"] * local_content["materials_local"]
1587
+ equipment_contribution = price_components["equipment"] * local_content["equipment_local"]
1588
+ labor_contribution = price_components["labor"] * local_content["labor_local"]
1589
+ subcontractors_contribution = price_components["subcontractors"] * local_content["subcontractors_local"]
1590
+
1591
+ # إنشاء بيانات الرسم البياني
1592
+ contribution_data = {
1593
+ "المكون": ["المواد", "المعدات", "العمالة", "مقاولي الباطن"],
1594
+ "المساهمة": [
1595
+ materials_contribution * 100,
1596
+ equipment_contribution * 100,
1597
+ labor_contribution * 100,
1598
+ subcontractors_contribution * 100
1599
+ ]
1600
+ }
1601
+
1602
+ # إنشاء رسم بياني شريطي
1603
+ fig = px.bar(
1604
+ contribution_data,
1605
+ x="المكون",
1606
+ y="المساهمة",
1607
+ title="مساهمة كل مكون في المحتوى المحلي",
1608
+ color="المكون",
1609
+ text_auto=True
1610
+ )
1611
+
1612
+ st.plotly_chart(fig, use_container_width=True)
1613
+
1614
+ # عرض توصيات لتحسين نسبة المحتوى المحلي
1615
+ st.markdown("#### توصيات لتحسين نسبة المحتوى المحلي")
1616
+
1617
+ if actual_local_content < local_content["target"]:
1618
+ # حساب الفجوة
1619
+ gap = local_content["target"] - actual_local_content
1620
+
1621
+ st.markdown(f"**الفجوة الحالية:** {gap * 100:.2f}%")
1622
+
1623
+ # تحديد المكونات التي يمكن تحسينها
1624
+ components_to_improve = []
1625
+
1626
+ if local_content["materials_local"] < 1.0:
1627
+ components_to_improve.append({
1628
+ "name": "المواد",
1629
+ "current": local_content["materials_local"],
1630
+ "weight": price_components["materials"],
1631
+ "potential": price_components["materials"] * (1.0 - local_content["materials_local"])
1632
+ })
1633
+
1634
+ if local_content["equipment_local"] < 1.0:
1635
+ components_to_improve.append({
1636
+ "name": "المعدات",
1637
+ "current": local_content["equipment_local"],
1638
+ "weight": price_components["equipment"],
1639
+ "potential": price_components["equipment"] * (1.0 - local_content["equipment_local"])
1640
+ })
1641
+
1642
+ if local_content["labor_local"] < 1.0:
1643
+ components_to_improve.append({
1644
+ "name": "العمالة",
1645
+ "current": local_content["labor_local"],
1646
+ "weight": price_components["labor"],
1647
+ "potential": price_components["labor"] * (1.0 - local_content["labor_local"])
1648
+ })
1649
+
1650
+ if local_content["subcontractors_local"] < 1.0:
1651
+ components_to_improve.append({
1652
+ "name": "مقاولي الباطن",
1653
+ "current": local_content["subcontractors_local"],
1654
+ "weight": price_components["subcontractors"],
1655
+ "potential": price_components["subcontractors"] * (1.0 - local_content["subcontractors_local"])
1656
+ })
1657
+
1658
+ # ترتيب المكونات حسب إمكانية التحسين
1659
+ components_to_improve.sort(key=lambda x: x["potential"], reverse=True)
1660
+
1661
+ # عرض التوصيات
1662
+ for component in components_to_improve:
1663
+ st.markdown(f"**{component['name']}:** زيادة نسبة {component['name']} المحلية من {component['current'] * 100:.2f}% إلى {min(component['current'] + gap / component['weight'], 1.0) * 100:.2f}%")
1664
+ else:
1665
+ st.success("تم تحقيق النسبة المستهدفة للمحتوى المحلي")
1666
+
1667
+ def calculate_item_price(self, item_data):
1668
+ """حساب سعر البند بناءً على مكوناته"""
1669
+
1670
+ # استخراج مكونات البند
1671
+ materials = item_data.get("materials", [])
1672
+ equipment = item_data.get("equipment", [])
1673
+ labor = item_data.get("labor", [])
1674
+ subcontractors = item_data.get("subcontractors", [])
1675
+
1676
+ # حساب تكلفة المواد
1677
+ materials_cost = sum([material["quantity"] * material["price"] for material in materials])
1678
+
1679
+ # حساب تكلفة المعدات
1680
+ equipment_cost = sum([equipment_item["quantity"] * equipment_item["price"] for equipment_item in equipment])
1681
+
1682
+ # حساب تكلفة العمالة
1683
+ labor_cost = sum([labor_item["quantity"] * labor_item["price"] for labor_item in labor])
1684
+
1685
+ # حساب تكلفة مقاولي الباطن
1686
+ subcontractors_cost = sum([subcontractor["price"] for subcontractor in subcontractors])
1687
+
1688
+ # حساب التكاليف المباشرة
1689
+ direct_cost = materials_cost + equipment_cost + labor_cost + subcontractors_cost
1690
+
1691
+ # استخراج نسب التكاليف غير المباشرة
1692
+ indirect_costs = st.session_state.smart_price_analysis["indirect_costs"]
1693
+
1694
+ # حساب التكاليف غير المباشرة
1695
+ overhead_amount = direct_cost * indirect_costs["overhead"]
1696
+ profit_amount = direct_cost * indirect_costs["profit"]
1697
+ contingency_amount = direct_cost * indirect_costs["contingency"]
1698
+
1699
+ # حساب إجمالي التكاليف
1700
+ total_cost = direct_cost + overhead_amount + profit_amount + contingency_amount
1701
+
1702
+ return total_cost
1703
+
1704
+ def calculate_local_content(self, item_data):
1705
+ """حساب نسبة المحتوى المحلي للبند"""
1706
+
1707
+ # استخراج مكونات البند
1708
+ materials = item_data.get("materials", [])
1709
+ equipment = item_data.get("equipment", [])
1710
+ labor = item_data.get("labor", [])
1711
+ subcontractors = item_data.get("subcontractors", [])
1712
+
1713
+ # استخراج نسب المحتوى المحلي
1714
+ local_content = st.session_state.smart_price_analysis["local_content"]
1715
+
1716
+ # حساب تكلفة المواد
1717
+ materials_cost = sum([material["quantity"] * material["price"] for material in materials])
1718
+
1719
+ # حساب تكلفة المعدات
1720
+ equipment_cost = sum([equipment_item["quantity"] * equipment_item["price"] for equipment_item in equipment])
1721
+
1722
+ # حساب تكلفة العمالة
1723
+ labor_cost = sum([labor_item["quantity"] * labor_item["price"] for labor_item in labor])
1724
+
1725
+ # حساب تكلفة مقاولي الباطن
1726
+ subcontractors_cost = sum([subcontractor["price"] for subcontractor in subcontractors])
1727
+
1728
+ # حساب التكاليف المباشرة
1729
+ direct_cost = materials_cost + equipment_cost + labor_cost + subcontractors_cost
1730
+
1731
+ # حساب المحتوى المحلي
1732
+ local_materials = materials_cost * local_content["materials_local"]
1733
+ local_equipment = equipment_cost * local_content["equipment_local"]
1734
+ local_labor = labor_cost * local_content["labor_local"]
1735
+ local_subcontractors = subcontractors_cost * local_content["subcontractors_local"]
1736
+
1737
+ # حساب إجمالي المحتوى المحلي
1738
+ total_local_content = local_materials + local_equipment + local_labor + local_subcontractors
1739
+
1740
+ # حساب نسبة المحتوى المحلي
1741
+ local_content_percentage = total_local_content / direct_cost if direct_cost > 0 else 0
1742
+
1743
+ return local_content_percentage
pricing_system/modules/catalogs/equipment_catalog.py ADDED
@@ -0,0 +1,1669 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ كتالوج المعدات - وحدة إدارة معدات المقاولات
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import plotly.express as px
9
+ import os
10
+ import json
11
+ from datetime import datetime
12
+ import io
13
+
14
+ class EquipmentCatalog:
15
+ """كتالوج المعدات"""
16
+
17
+ def __init__(self):
18
+ """تهيئة كتالوج المعدات"""
19
+
20
+ # تهيئة حالة الجلسة لكتالوج المعدات
21
+ if 'equipment_catalog' not in st.session_state:
22
+ # إنشاء بيانات افتراضية للمعدات
23
+ self._initialize_equipment_catalog()
24
+
25
+ def _initialize_equipment_catalog(self):
26
+ """تهيئة بيانات كتالوج المعدات"""
27
+
28
+ # تعريف فئات المعدات
29
+ equipment_categories = [
30
+ "معدات الحفر والردم",
31
+ "معدات النقل",
32
+ "معدات الرفع",
33
+ "معدات الخرسانة",
34
+ "معدات الطرق",
35
+ "معدات الصرف الصحي",
36
+ "معدات السيول والكباري",
37
+ "معدات الضغط والتثبيت",
38
+ "معدات التوليد والطاقة",
39
+ "معدات القياس والمساحة"
40
+ ]
41
+
42
+ # إنشاء قائمة المعدات
43
+ equipment_data = []
44
+
45
+ # 1. معدات الحفر والردم
46
+ equipment_data.extend([
47
+ {
48
+ "id": "EQ-001",
49
+ "name": "حفارة هيدروليكية كبيرة",
50
+ "category": "معدات الحفر والردم",
51
+ "subcategory": "حفارات",
52
+ "brand": "كاتربيلر",
53
+ "model": "CAT 336",
54
+ "capacity": "2.5 م3",
55
+ "production_rate": "150 م3/ساعة",
56
+ "hourly_cost": 350,
57
+ "daily_cost": 2800,
58
+ "weekly_cost": 16800,
59
+ "monthly_cost": 67200,
60
+ "fuel_consumption": "35 لتر/ساعة",
61
+ "maintenance_period": "250 ساعة",
62
+ "maintenance_cost": 5000,
63
+ "operator_required": True,
64
+ "description": "حفارة هيدروليكية كبيرة مناسبة لأعمال الحفر الثقيلة ومشاريع البنية التحتية الكبيرة",
65
+ "image_url": "https://example.com/cat336.jpg"
66
+ },
67
+ {
68
+ "id": "EQ-002",
69
+ "name": "حفارة هيدروليكية متوسطة",
70
+ "category": "معدات الحفر والردم",
71
+ "subcategory": "حفارات",
72
+ "brand": "كاتربيلر",
73
+ "model": "CAT 320",
74
+ "capacity": "1.5 م3",
75
+ "production_rate": "100 م3/ساعة",
76
+ "hourly_cost": 250,
77
+ "daily_cost": 2000,
78
+ "weekly_cost": 12000,
79
+ "monthly_cost": 48000,
80
+ "fuel_consumption": "25 لتر/ساعة",
81
+ "maintenance_period": "250 ساعة",
82
+ "maintenance_cost": 3500,
83
+ "operator_required": True,
84
+ "description": "حفارة هيدروليكية متوسطة الحجم مناسبة لمعظم مشاريع البنية التحتية",
85
+ "image_url": "https://example.com/cat320.jpg"
86
+ },
87
+ {
88
+ "id": "EQ-003",
89
+ "name": "حفارة هيدروليكية صغيرة",
90
+ "category": "معدات الحفر والردم",
91
+ "subcategory": "حفارات",
92
+ "brand": "كاتربيلر",
93
+ "model": "CAT 308",
94
+ "capacity": "0.8 م3",
95
+ "production_rate": "50 م3/ساعة",
96
+ "hourly_cost": 150,
97
+ "daily_cost": 1200,
98
+ "weekly_cost": 7200,
99
+ "monthly_cost": 28800,
100
+ "fuel_consumption": "15 لتر/ساعة",
101
+ "maintenance_period": "200 ساعة",
102
+ "maintenance_cost": 2000,
103
+ "operator_required": True,
104
+ "description": "حفارة هيدروليكية صغيرة مناسبة للمشاريع الصغيرة والمساحات الضيقة",
105
+ "image_url": "https://example.com/cat308.jpg"
106
+ },
107
+ {
108
+ "id": "EQ-004",
109
+ "name": "بلدوزر كبير",
110
+ "category": "معدات الحفر والردم",
111
+ "subcategory": "بلدوزرات",
112
+ "brand": "كاتربيلر",
113
+ "model": "D9",
114
+ "capacity": "13.5 م3",
115
+ "production_rate": "300 م3/ساعة",
116
+ "hourly_cost": 400,
117
+ "daily_cost": 3200,
118
+ "weekly_cost": 19200,
119
+ "monthly_cost": 76800,
120
+ "fuel_consumption": "45 لتر/ساعة",
121
+ "maintenance_period": "250 ساعة",
122
+ "maintenance_cost": 6000,
123
+ "operator_required": True,
124
+ "description": "بلدوزر كبير لأعمال التسوية والدفع في المشاريع الكبيرة",
125
+ "image_url": "https://example.com/catd9.jpg"
126
+ },
127
+ {
128
+ "id": "EQ-005",
129
+ "name": "بلدوزر متوسط",
130
+ "category": "معدات الحفر والردم",
131
+ "subcategory": "بلدوزرات",
132
+ "brand": "كاتربيلر",
133
+ "model": "D7",
134
+ "capacity": "8.5 م3",
135
+ "production_rate": "200 م3/ساعة",
136
+ "hourly_cost": 300,
137
+ "daily_cost": 2400,
138
+ "weekly_cost": 14400,
139
+ "monthly_cost": 57600,
140
+ "fuel_consumption": "35 لتر/ساعة",
141
+ "maintenance_period": "250 ساعة",
142
+ "maintenance_cost": 4500,
143
+ "operator_required": True,
144
+ "description": "بلدوزر متوسط الحجم مناسب لمعظم مشاريع البنية التحتية",
145
+ "image_url": "https://example.com/catd7.jpg"
146
+ },
147
+ {
148
+ "id": "EQ-006",
149
+ "name": "لودر أمامي كبير",
150
+ "category": "معدات الحفر والردم",
151
+ "subcategory": "لودرات",
152
+ "brand": "كاتربيلر",
153
+ "model": "980",
154
+ "capacity": "5.5 م3",
155
+ "production_rate": "250 م3/ساعة",
156
+ "hourly_cost": 300,
157
+ "daily_cost": 2400,
158
+ "weekly_cost": 14400,
159
+ "monthly_cost": 57600,
160
+ "fuel_consumption": "30 لتر/ساعة",
161
+ "maintenance_period": "250 ساعة",
162
+ "maintenance_cost": 4000,
163
+ "operator_required": True,
164
+ "description": "لودر أمامي كبير لأعمال التحميل في المشاريع الكبيرة",
165
+ "image_url": "https://example.com/cat980.jpg"
166
+ },
167
+ {
168
+ "id": "EQ-007",
169
+ "name": "لودر أمامي متوسط",
170
+ "category": "معدات الحفر والردم",
171
+ "subcategory": "لودرات",
172
+ "brand": "كاتربيلر",
173
+ "model": "950",
174
+ "capacity": "3.5 م3",
175
+ "production_rate": "180 م3/ساعة",
176
+ "hourly_cost": 250,
177
+ "daily_cost": 2000,
178
+ "weekly_cost": 12000,
179
+ "monthly_cost": 48000,
180
+ "fuel_consumption": "25 لتر/ساعة",
181
+ "maintenance_period": "250 ساعة",
182
+ "maintenance_cost": 3500,
183
+ "operator_required": True,
184
+ "description": "لودر أمامي متوسط الحجم مناسب لمعظم مشاريع البنية التحتية",
185
+ "image_url": "https://example.com/cat950.jpg"
186
+ },
187
+ {
188
+ "id": "EQ-008",
189
+ "name": "باكهو لودر",
190
+ "category": "معدات الحفر والردم",
191
+ "subcategory": "لودرات",
192
+ "brand": "جي سي بي",
193
+ "model": "3CX",
194
+ "capacity": "1.0 م3",
195
+ "production_rate": "60 م3/ساعة",
196
+ "hourly_cost": 150,
197
+ "daily_cost": 1200,
198
+ "weekly_cost": 7200,
199
+ "monthly_cost": 28800,
200
+ "fuel_consumption": "12 لتر/ساعة",
201
+ "maintenance_period": "200 ساعة",
202
+ "maintenance_cost": 2000,
203
+ "operator_required": True,
204
+ "description": "باكهو لودر متعدد الاستخدامات للحفر والتحميل",
205
+ "image_url": "https://example.com/jcb3cx.jpg"
206
+ },
207
+ {
208
+ "id": "EQ-009",
209
+ "name": "جريدر",
210
+ "category": "معدات الحفر والردم",
211
+ "subcategory": "معدات تسوية",
212
+ "brand": "كاتربيلر",
213
+ "model": "140",
214
+ "capacity": "3.7 م عرض الشفرة",
215
+ "production_rate": "2000 م2/ساعة",
216
+ "hourly_cost": 250,
217
+ "daily_cost": 2000,
218
+ "weekly_cost": 12000,
219
+ "monthly_cost": 48000,
220
+ "fuel_consumption": "20 لتر/ساعة",
221
+ "maintenance_period": "250 ساعة",
222
+ "maintenance_cost": 3000,
223
+ "operator_required": True,
224
+ "description": "جريدر لتسوية الطرق والمساحات",
225
+ "image_url": "https://example.com/cat140.jpg"
226
+ },
227
+ {
228
+ "id": "EQ-010",
229
+ "name": "سكريبر",
230
+ "category": "معدات الحفر والردم",
231
+ "subcategory": "معدات تسوية",
232
+ "brand": "كاتربيلر",
233
+ "model": "621",
234
+ "capacity": "21 م3",
235
+ "production_rate": "400 م3/ساعة",
236
+ "hourly_cost": 350,
237
+ "daily_cost": 2800,
238
+ "weekly_cost": 16800,
239
+ "monthly_cost": 67200,
240
+ "fuel_consumption": "35 لتر/ساعة",
241
+ "maintenance_period": "250 ساعة",
242
+ "maintenance_cost": 5000,
243
+ "operator_required": True,
244
+ "description": "سكريبر لنقل وتسوية التربة لمسافات متوسطة",
245
+ "image_url": "https://example.com/cat621.jpg"
246
+ }
247
+ ])
248
+
249
+ # 2. معدات النقل
250
+ equipment_data.extend([
251
+ {
252
+ "id": "EQ-011",
253
+ "name": "شاحنة قلاب كبيرة",
254
+ "category": "معدات النقل",
255
+ "subcategory": "شاحنات قلاب",
256
+ "brand": "مان",
257
+ "model": "TGS 40.480",
258
+ "capacity": "30 م3",
259
+ "production_rate": "30 م3/رحلة",
260
+ "hourly_cost": 200,
261
+ "daily_cost": 1600,
262
+ "weekly_cost": 9600,
263
+ "monthly_cost": 38400,
264
+ "fuel_consumption": "25 لتر/ساعة",
265
+ "maintenance_period": "300 ساعة",
266
+ "maintenance_cost": 3000,
267
+ "operator_required": True,
268
+ "description": "شاحنة قلاب كبيرة لنقل مواد الحفر والردم",
269
+ "image_url": "https://example.com/mantgs.jpg"
270
+ },
271
+ {
272
+ "id": "EQ-012",
273
+ "name": "شاحنة قلاب متوسطة",
274
+ "category": "معدات النقل",
275
+ "subcategory": "شاحنات قلاب",
276
+ "brand": "مرسيدس",
277
+ "model": "Actros 3341",
278
+ "capacity": "20 م3",
279
+ "production_rate": "20 م3/رحلة",
280
+ "hourly_cost": 180,
281
+ "daily_cost": 1440,
282
+ "weekly_cost": 8640,
283
+ "monthly_cost": 34560,
284
+ "fuel_consumption": "20 لتر/ساعة",
285
+ "maintenance_period": "300 ساعة",
286
+ "maintenance_cost": 2500,
287
+ "operator_required": True,
288
+ "description": "شاحنة قلاب متوسطة لنقل مواد الحفر والردم",
289
+ "image_url": "https://example.com/actros.jpg"
290
+ },
291
+ {
292
+ "id": "EQ-013",
293
+ "name": "شاحنة خلاطة خرسانة",
294
+ "category": "معدات النقل",
295
+ "subcategory": "شاحنات خرسانة",
296
+ "brand": "مرسيدس",
297
+ "model": "Actros 3236",
298
+ "capacity": "8 م3",
299
+ "production_rate": "8 م3/رحلة",
300
+ "hourly_cost": 200,
301
+ "daily_cost": 1600,
302
+ "weekly_cost": 9600,
303
+ "monthly_cost": 38400,
304
+ "fuel_consumption": "20 لتر/ساعة",
305
+ "maintenance_period": "300 ساعة",
306
+ "maintenance_cost": 3000,
307
+ "operator_required": True,
308
+ "description": "شاحنة خلاطة خرسانة لنقل الخرسانة الجاهزة",
309
+ "image_url": "https://example.com/mixer.jpg"
310
+ },
311
+ {
312
+ "id": "EQ-014",
313
+ "name": "شاحنة نقل مياه",
314
+ "category": "معدات النقل",
315
+ "subcategory": "شاحنات مياه",
316
+ "brand": "مان",
317
+ "model": "TGS 33.360",
318
+ "capacity": "20000 لتر",
319
+ "production_rate": "20000 لتر/رحلة",
320
+ "hourly_cost": 150,
321
+ "daily_cost": 1200,
322
+ "weekly_cost": 7200,
323
+ "monthly_cost": 28800,
324
+ "fuel_consumption": "18 لتر/ساعة",
325
+ "maintenance_period": "300 ساعة",
326
+ "maintenance_cost": 2000,
327
+ "operator_required": True,
328
+ "description": "شاحنة نقل مياه للمشاريع والرش",
329
+ "image_url": "https://example.com/watertruck.jpg"
330
+ },
331
+ {
332
+ "id": "EQ-015",
333
+ "name": "شاحنة نقل معدات",
334
+ "category": "معدات النقل",
335
+ "subcategory": "شاحنات نقل",
336
+ "brand": "فولفو",
337
+ "model": "FH16",
338
+ "capacity": "60 طن",
339
+ "production_rate": "60 طن/رحلة",
340
+ "hourly_cost": 250,
341
+ "daily_cost": 2000,
342
+ "weekly_cost": 12000,
343
+ "monthly_cost": 48000,
344
+ "fuel_consumption": "25 لتر/ساعة",
345
+ "maintenance_period": "300 ساعة",
346
+ "maintenance_cost": 3500,
347
+ "operator_required": True,
348
+ "description": "شاحنة نقل معدات ثقيلة (لوبد)",
349
+ "image_url": "https://example.com/lowbed.jpg"
350
+ }
351
+ ])
352
+
353
+ # 3. معدات الرفع
354
+ equipment_data.extend([
355
+ {
356
+ "id": "EQ-016",
357
+ "name": "رافعة برجية",
358
+ "category": "معدات الرفع",
359
+ "subcategory": "رافعات برجية",
360
+ "brand": "ليبهر",
361
+ "model": "200 EC-H",
362
+ "capacity": "10 طن",
363
+ "production_rate": "20 رفعة/ساعة",
364
+ "hourly_cost": 400,
365
+ "daily_cost": 3200,
366
+ "weekly_cost": 19200,
367
+ "monthly_cost": 76800,
368
+ "fuel_consumption": "30 كيلوواط/ساعة",
369
+ "maintenance_period": "300 ساعة",
370
+ "maintenance_cost": 5000,
371
+ "operator_required": True,
372
+ "description": "رافعة برجية للمشاريع الإنشائية الكبيرة",
373
+ "image_url": "https://example.com/towercrane.jpg"
374
+ },
375
+ {
376
+ "id": "EQ-017",
377
+ "name": "رافعة متحركة كبيرة",
378
+ "category": "معدات الرفع",
379
+ "subcategory": "رافعات متحركة",
380
+ "brand": "ليبهر",
381
+ "model": "LTM 1200",
382
+ "capacity": "200 طن",
383
+ "production_rate": "15 رفعة/ساعة",
384
+ "hourly_cost": 600,
385
+ "daily_cost": 4800,
386
+ "weekly_cost": 28800,
387
+ "monthly_cost": 115200,
388
+ "fuel_consumption": "40 لتر/ساعة",
389
+ "maintenance_period": "250 ساعة",
390
+ "maintenance_cost": 8000,
391
+ "operator_required": True,
392
+ "description": "رافعة متحركة كبيرة للأحمال الثقيلة",
393
+ "image_url": "https://example.com/mobilecrane.jpg"
394
+ },
395
+ {
396
+ "id": "EQ-018",
397
+ "name": "رافعة متحركة متوسطة",
398
+ "category": "معدات الرفع",
399
+ "subcategory": "رافعات متحركة",
400
+ "brand": "ليبهر",
401
+ "model": "LTM 1070",
402
+ "capacity": "70 طن",
403
+ "production_rate": "15 رفعة/ساعة",
404
+ "hourly_cost": 400,
405
+ "daily_cost": 3200,
406
+ "weekly_cost": 19200,
407
+ "monthly_cost": 76800,
408
+ "fuel_consumption": "30 لتر/ساعة",
409
+ "maintenance_period": "250 ساعة",
410
+ "maintenance_cost": 6000,
411
+ "operator_required": True,
412
+ "description": "رافعة متحركة متوسطة للاستخدامات المتنوعة",
413
+ "image_url": "https://example.com/mobilecrane2.jpg"
414
+ },
415
+ {
416
+ "id": "EQ-019",
417
+ "name": "رافعة شوكية",
418
+ "category": "معدات الرفع",
419
+ "subcategory": "رافعات شوكية",
420
+ "brand": "كاتربيلر",
421
+ "model": "DP70N",
422
+ "capacity": "7 طن",
423
+ "production_rate": "30 رفعة/ساعة",
424
+ "hourly_cost": 150,
425
+ "daily_cost": 1200,
426
+ "weekly_cost": 7200,
427
+ "monthly_cost": 28800,
428
+ "fuel_consumption": "12 لتر/ساعة",
429
+ "maintenance_period": "200 ساعة",
430
+ "maintenance_cost": 2000,
431
+ "operator_required": True,
432
+ "description": "رافعة شوكية لنقل المواد في الموقع",
433
+ "image_url": "https://example.com/forklift.jpg"
434
+ },
435
+ {
436
+ "id": "EQ-020",
437
+ "name": "رافعة سلة",
438
+ "category": "معدات الرفع",
439
+ "subcategory": "رافعات سلة",
440
+ "brand": "جيني",
441
+ "model": "S-85",
442
+ "capacity": "227 كجم",
443
+ "production_rate": "ارتفاع 26 متر",
444
+ "hourly_cost": 200,
445
+ "daily_cost": 1600,
446
+ "weekly_cost": 9600,
447
+ "monthly_cost": 38400,
448
+ "fuel_consumption": "10 لتر/ساعة",
449
+ "maintenance_period": "200 ساعة",
450
+ "maintenance_cost": 2500,
451
+ "operator_required": True,
452
+ "description": "رافعة سلة للوصول إلى الارتفاعات",
453
+ "image_url": "https://example.com/boomlift.jpg"
454
+ }
455
+ ])
456
+
457
+ # 4. معدات الخرسانة
458
+ equipment_data.extend([
459
+ {
460
+ "id": "EQ-021",
461
+ "name": "خلاطة خرسانة مركزية",
462
+ "category": "معدات الخرسانة",
463
+ "subcategory": "خلاطات",
464
+ "brand": "ليبهر",
465
+ "model": "Betomix 3.0",
466
+ "capacity": "120 م3/ساعة",
467
+ "production_rate": "120 م3/ساعة",
468
+ "hourly_cost": 800,
469
+ "daily_cost": 6400,
470
+ "weekly_cost": 38400,
471
+ "monthly_cost": 153600,
472
+ "fuel_consumption": "60 كيلوواط/ساعة",
473
+ "maintenance_period": "500 ساعة",
474
+ "maintenance_cost": 10000,
475
+ "operator_required": True,
476
+ "description": "محطة خلط خرسانة مركزية للمشاريع الكبيرة",
477
+ "image_url": "https://example.com/batchplant.jpg"
478
+ },
479
+ {
480
+ "id": "EQ-022",
481
+ "name": "خلاطة خرسانة متنقلة",
482
+ "category": "معدات الخرسانة",
483
+ "subcategory": "خلاطات",
484
+ "brand": "كارمكس",
485
+ "model": "MCP-30",
486
+ "capacity": "30 م3/ساعة",
487
+ "production_rate": "30 م3/ساعة",
488
+ "hourly_cost": 300,
489
+ "daily_cost": 2400,
490
+ "weekly_cost": 14400,
491
+ "monthly_cost": 57600,
492
+ "fuel_consumption": "25 كيلوواط/ساعة",
493
+ "maintenance_period": "300 ساعة",
494
+ "maintenance_cost": 5000,
495
+ "operator_required": True,
496
+ "description": "محطة خلط خرسانة متنقلة للمشاريع المتوسطة",
497
+ "image_url": "https://example.com/mobilemixer.jpg"
498
+ },
499
+ {
500
+ "id": "EQ-023",
501
+ "name": "مضخة خرسانة ثابتة",
502
+ "category": "معدات الخرسانة",
503
+ "subcategory": "مضخات",
504
+ "brand": "بوتزميستر",
505
+ "model": "BSA 1409",
506
+ "capacity": "90 م3/ساعة",
507
+ "production_rate": "90 م3/ساعة",
508
+ "hourly_cost": 350,
509
+ "daily_cost": 2800,
510
+ "weekly_cost": 16800,
511
+ "monthly_cost": 67200,
512
+ "fuel_consumption": "30 كيلوواط/ساعة",
513
+ "maintenance_period": "300 ساعة",
514
+ "maintenance_cost": 6000,
515
+ "operator_required": True,
516
+ "description": "مضخة خرسانة ثابتة للمشاريع الكبيرة",
517
+ "image_url": "https://example.com/concretepump.jpg"
518
+ },
519
+ {
520
+ "id": "EQ-024",
521
+ "name": "مضخة خرسانة متحركة",
522
+ "category": "معدات الخرسانة",
523
+ "subcategory": "مضخات",
524
+ "brand": "بوتزميستر",
525
+ "model": "M42-5",
526
+ "capacity": "160 م3/ساعة",
527
+ "production_rate": "160 م3/ساعة",
528
+ "hourly_cost": 500,
529
+ "daily_cost": 4000,
530
+ "weekly_cost": 24000,
531
+ "monthly_cost": 96000,
532
+ "fuel_consumption": "40 لتر/ساعة",
533
+ "maintenance_period": "250 ساعة",
534
+ "maintenance_cost": 8000,
535
+ "operator_required": True,
536
+ "description": "مضخة خرسانة متحركة بذراع 42 متر",
537
+ "image_url": "https://example.com/boomconcretepump.jpg"
538
+ },
539
+ {
540
+ "id": "EQ-025",
541
+ "name": "هزاز خرسانة",
542
+ "category": "معدات الخرسانة",
543
+ "subcategory": "هزازات",
544
+ "brand": "واكر نيوسن",
545
+ "model": "IREN",
546
+ "capacity": "غير محدد",
547
+ "production_rate": "غير محدد",
548
+ "hourly_cost": 20,
549
+ "daily_cost": 160,
550
+ "weekly_cost": 960,
551
+ "monthly_cost": 3840,
552
+ "fuel_consumption": "غير محدد",
553
+ "maintenance_period": "100 ساعة",
554
+ "maintenance_cost": 500,
555
+ "operator_required": True,
556
+ "description": "هزاز خرسانة لدمك الخرسانة",
557
+ "image_url": "https://example.com/vibrator.jpg"
558
+ },
559
+ {
560
+ "id": "EQ-026",
561
+ "name": "ماكينة تسوية الخرسانة",
562
+ "category": "معدات الخرسانة",
563
+ "subcategory": "معدات تشطيب",
564
+ "brand": "سومرو",
565
+ "model": "S-840",
566
+ "capacity": "840 م2/ساعة",
567
+ "production_rate": "840 م2/ساعة",
568
+ "hourly_cost": 100,
569
+ "daily_cost": 800,
570
+ "weekly_cost": 4800,
571
+ "monthly_cost": 19200,
572
+ "fuel_consumption": "5 لتر/ساعة",
573
+ "maintenance_period": "150 ساعة",
574
+ "maintenance_cost": 1500,
575
+ "operator_required": True,
576
+ "description": "ماكينة تسوية الخرسانة (هليكوبتر)",
577
+ "image_url": "https://example.com/powertrowel.jpg"
578
+ }
579
+ ])
580
+
581
+ # 5. معدات الطرق
582
+ equipment_data.extend([
583
+ {
584
+ "id": "EQ-027",
585
+ "name": "فرادة أسفلت كبيرة",
586
+ "category": "معدات الطرق",
587
+ "subcategory": "فرادات",
588
+ "brand": "فوجيلي",
589
+ "model": "Super 2100-3i",
590
+ "capacity": "1100 طن/ساعة",
591
+ "production_rate": "1100 طن/ساعة",
592
+ "hourly_cost": 600,
593
+ "daily_cost": 4800,
594
+ "weekly_cost": 28800,
595
+ "monthly_cost": 115200,
596
+ "fuel_consumption": "45 لتر/ساعة",
597
+ "maintenance_period": "250 ساعة",
598
+ "maintenance_cost": 8000,
599
+ "operator_required": True,
600
+ "description": "فرادة أسفلت كبيرة للطرق السريعة",
601
+ "image_url": "https://example.com/paver.jpg"
602
+ },
603
+ {
604
+ "id": "EQ-028",
605
+ "name": "فرادة أسفلت متوسطة",
606
+ "category": "معدات الطرق",
607
+ "subcategory": "فرادات",
608
+ "brand": "فوجيلي",
609
+ "model": "Super 1800-3i",
610
+ "capacity": "700 طن/ساعة",
611
+ "production_rate": "700 طن/ساعة",
612
+ "hourly_cost": 450,
613
+ "daily_cost": 3600,
614
+ "weekly_cost": 21600,
615
+ "monthly_cost": 86400,
616
+ "fuel_consumption": "35 لتر/ساعة",
617
+ "maintenance_period": "250 ساعة",
618
+ "maintenance_cost": 6000,
619
+ "operator_required": True,
620
+ "description": "فرادة أسفلت متوسطة للطرق العامة",
621
+ "image_url": "https://example.com/paver2.jpg"
622
+ },
623
+ {
624
+ "id": "EQ-029",
625
+ "name": "مدحلة أسفلت ثقيلة",
626
+ "category": "معدات الطرق",
627
+ "subcategory": "مداحل",
628
+ "brand": "بوماج",
629
+ "model": "BW 203",
630
+ "capacity": "غير محدد",
631
+ "production_rate": "3000 م2/ساعة",
632
+ "hourly_cost": 250,
633
+ "daily_cost": 2000,
634
+ "weekly_cost": 12000,
635
+ "monthly_cost": 48000,
636
+ "fuel_consumption": "20 لتر/ساعة",
637
+ "maintenance_period": "250 ساعة",
638
+ "maintenance_cost": 3500,
639
+ "operator_required": True,
640
+ "description": "مدحلة أسفلت ثقيلة للطرق",
641
+ "image_url": "https://example.com/roller.jpg"
642
+ },
643
+ {
644
+ "id": "EQ-030",
645
+ "name": "مدحلة أسفلت مطاطية",
646
+ "category": "معدات الطرق",
647
+ "subcategory": "مداحل",
648
+ "brand": "بوماج",
649
+ "model": "BW 27 RH",
650
+ "capacity": "غير محدد",
651
+ "production_rate": "3500 م2/ساعة",
652
+ "hourly_cost": 200,
653
+ "daily_cost": 1600,
654
+ "weekly_cost": 9600,
655
+ "monthly_cost": 38400,
656
+ "fuel_consumption": "18 لتر/ساعة",
657
+ "maintenance_period": "250 ساعة",
658
+ "maintenance_cost": 3000,
659
+ "operator_required": True,
660
+ "description": "مدحلة أسفلت مطاطية للطرق",
661
+ "image_url": "https://example.com/rubberroller.jpg"
662
+ },
663
+ {
664
+ "id": "EQ-031",
665
+ "name": "قاشطة أسفلت",
666
+ "category": "معدات الطرق",
667
+ "subcategory": "قاشطات",
668
+ "brand": "ويرتجن",
669
+ "model": "W 210",
670
+ "capacity": "غير محدد",
671
+ "production_rate": "800 م2/ساعة",
672
+ "hourly_cost": 500,
673
+ "daily_cost": 4000,
674
+ "weekly_cost": 24000,
675
+ "monthly_cost": 96000,
676
+ "fuel_consumption": "40 لتر/ساعة",
677
+ "maintenance_period": "250 ساعة",
678
+ "maintenance_cost": 7000,
679
+ "operator_required": True,
680
+ "description": "قاشطة أسفلت لإزالة طبقات الأسفلت القديمة",
681
+ "image_url": "https://example.com/milling.jpg"
682
+ },
683
+ {
684
+ "id": "EQ-032",
685
+ "name": "شاحنة رش البيتومين",
686
+ "category": "معدات الطرق",
687
+ "subcategory": "معدات رش",
688
+ "brand": "روزنباور",
689
+ "model": "S12000",
690
+ "capacity": "12000 لتر",
691
+ "production_rate": "15000 م2/ساعة",
692
+ "hourly_cost": 200,
693
+ "daily_cost": 1600,
694
+ "weekly_cost": 9600,
695
+ "monthly_cost": 38400,
696
+ "fuel_consumption": "15 لتر/ساعة",
697
+ "maintenance_period": "250 ساعة",
698
+ "maintenance_cost": 3000,
699
+ "operator_required": True,
700
+ "description": "شاحنة رش البيتومين للطرق",
701
+ "image_url": "https://example.com/bitumensprayer.jpg"
702
+ }
703
+ ])
704
+
705
+ # 6. معدات الصرف الصحي
706
+ equipment_data.extend([
707
+ {
708
+ "id": "EQ-033",
709
+ "name": "حفارة خنادق كبيرة",
710
+ "category": "معدات الصرف الصحي",
711
+ "subcategory": "حفارات خنادق",
712
+ "brand": "فيرمير",
713
+ "model": "T1255III",
714
+ "capacity": "غير محدد",
715
+ "production_rate": "300 م/ساعة",
716
+ "hourly_cost": 400,
717
+ "daily_cost": 3200,
718
+ "weekly_cost": 19200,
719
+ "monthly_cost": 76800,
720
+ "fuel_consumption": "35 لتر/ساعة",
721
+ "maintenance_period": "250 ساعة",
722
+ "maintenance_cost": 6000,
723
+ "operator_required": True,
724
+ "description": "حفارة خنادق كبيرة لمشاريع الصرف الصحي",
725
+ "image_url": "https://example.com/trencher.jpg"
726
+ },
727
+ {
728
+ "id": "EQ-034",
729
+ "name": "ماكينة دفع أنابيب",
730
+ "category": "معدات الصرف الصحي",
731
+ "subcategory": "معدات دفع",
732
+ "brand": "هيرينكنيشت",
733
+ "model": "HK-500",
734
+ "capacity": "غير محدد",
735
+ "production_rate": "20 م/ساعة",
736
+ "hourly_cost": 600,
737
+ "daily_cost": 4800,
738
+ "weekly_cost": 28800,
739
+ "monthly_cost": 115200,
740
+ "fuel_consumption": "40 كيلوواط/ساعة",
741
+ "maintenance_period": "300 ساعة",
742
+ "maintenance_cost": 8000,
743
+ "operator_required": True,
744
+ "description": "ماكينة دفع أنابيب بدون حفر مفتوح",
745
+ "image_url": "https://example.com/pipejacking.jpg"
746
+ },
747
+ {
748
+ "id": "EQ-035",
749
+ "name": "سيارة شفط وتنظيف مجاري",
750
+ "category": "معدات الصرف الصحي",
751
+ "subcategory": "معدات تنظيف",
752
+ "brand": "كايزر",
753
+ "model": "AquaStar",
754
+ "capacity": "12000 لتر",
755
+ "production_rate": "غير محدد",
756
+ "hourly_cost": 300,
757
+ "daily_cost": 2400,
758
+ "weekly_cost": 14400,
759
+ "monthly_cost": 57600,
760
+ "fuel_consumption": "20 لتر/ساعة",
761
+ "maintenance_period": "250 ساعة",
762
+ "maintenance_cost": 4000,
763
+ "operator_required": True,
764
+ "description": "سيارة شفط وتنظيف مجاري بضغط عالي",
765
+ "image_url": "https://example.com/sewercleaner.jpg"
766
+ },
767
+ {
768
+ "id": "EQ-036",
769
+ "name": "كاميرا فحص مجاري",
770
+ "category": "معدات الصرف الصحي",
771
+ "subcategory": "معدات فحص",
772
+ "brand": "إيبوس",
773
+ "model": "ROVION",
774
+ "capacity": "غير محدد",
775
+ "production_rate": "غير محدد",
776
+ "hourly_cost": 150,
777
+ "daily_cost": 1200,
778
+ "weekly_cost": 7200,
779
+ "monthly_cost": 28800,
780
+ "fuel_consumption": "غير محدد",
781
+ "maintenance_period": "200 ساعة",
782
+ "maintenance_cost": 2000,
783
+ "operator_required": True,
784
+ "description": "كاميرا فحص مجاري للتفتيش والصيانة",
785
+ "image_url": "https://example.com/sewercamera.jpg"
786
+ },
787
+ {
788
+ "id": "EQ-037",
789
+ "name": "مضخة مياه غاطسة كبيرة",
790
+ "category": "معدات الصرف الصحي",
791
+ "subcategory": "مضخات",
792
+ "brand": "جرندفوس",
793
+ "model": "S2",
794
+ "capacity": "400 م3/ساعة",
795
+ "production_rate": "400 م3/ساعة",
796
+ "hourly_cost": 100,
797
+ "daily_cost": 800,
798
+ "weekly_cost": 4800,
799
+ "monthly_cost": 19200,
800
+ "fuel_consumption": "15 كيلوواط/ساعة",
801
+ "maintenance_period": "500 ساعة",
802
+ "maintenance_cost": 2500,
803
+ "operator_required": False,
804
+ "description": "مضخة مياه غاطسة كبيرة لمشاريع الصرف الصحي",
805
+ "image_url": "https://example.com/submersiblepump.jpg"
806
+ }
807
+ ])
808
+
809
+ # 7. معدات السيول والكباري
810
+ equipment_data.extend([
811
+ {
812
+ "id": "EQ-038",
813
+ "name": "معدات دق الخوازيق",
814
+ "category": "معدات السيول والكباري",
815
+ "subcategory": "معدات خوازيق",
816
+ "brand": "بوير",
817
+ "model": "BG 28",
818
+ "capacity": "غير محدد",
819
+ "production_rate": "10 خازوق/يوم",
820
+ "hourly_cost": 800,
821
+ "daily_cost": 6400,
822
+ "weekly_cost": 38400,
823
+ "monthly_cost": 153600,
824
+ "fuel_consumption": "60 لتر/ساعة",
825
+ "maintenance_period": "250 ساعة",
826
+ "maintenance_cost": 10000,
827
+ "operator_required": True,
828
+ "description": "معدات دق الخوازيق للكباري والأساسات العميقة",
829
+ "image_url": "https://example.com/piling.jpg"
830
+ },
831
+ {
832
+ "id": "EQ-039",
833
+ "name": "رافعة جسرية",
834
+ "category": "معدات السيول والكباري",
835
+ "subcategory": "رافعات",
836
+ "brand": "ليبهر",
837
+ "model": "LG 1750",
838
+ "capacity": "750 طن",
839
+ "production_rate": "غير محدد",
840
+ "hourly_cost": 1200,
841
+ "daily_cost": 9600,
842
+ "weekly_cost": 57600,
843
+ "monthly_cost": 230400,
844
+ "fuel_consumption": "80 لتر/ساعة",
845
+ "maintenance_period": "250 ساعة",
846
+ "maintenance_cost": 15000,
847
+ "operator_required": True,
848
+ "description": "رافعة جسرية لتركيب عناصر الكباري الثقيلة",
849
+ "image_url": "https://example.com/bridgecrane.jpg"
850
+ },
851
+ {
852
+ "id": "EQ-040",
853
+ "name": "معدات شد الكابلات",
854
+ "category": "معدات السيول والكباري",
855
+ "subcategory": "معدات شد",
856
+ "brand": "فرايسينت",
857
+ "model": "C500",
858
+ "capacity": "500 طن",
859
+ "production_rate": "غير محدد",
860
+ "hourly_cost": 300,
861
+ "daily_cost": 2400,
862
+ "weekly_cost": 14400,
863
+ "monthly_cost": 57600,
864
+ "fuel_consumption": "غير محدد",
865
+ "maintenance_period": "300 ساعة",
866
+ "maintenance_cost": 5000,
867
+ "operator_required": True,
868
+ "description": "معدات شد الكابلات للكباري المعلقة",
869
+ "image_url": "https://example.com/stressing.jpg"
870
+ },
871
+ {
872
+ "id": "EQ-041",
873
+ "name": "معدات حفر الأنفاق",
874
+ "category": "معدات السيول والكباري",
875
+ "subcategory": "معدات أنفاق",
876
+ "brand": "هيرينكنيشت",
877
+ "model": "S-500",
878
+ "capacity": "غير محدد",
879
+ "production_rate": "15 م/يوم",
880
+ "hourly_cost": 1500,
881
+ "daily_cost": 12000,
882
+ "weekly_cost": 72000,
883
+ "monthly_cost": 288000,
884
+ "fuel_consumption": "100 كيلوواط/ساعة",
885
+ "maintenance_period": "500 ساعة",
886
+ "maintenance_cost": 20000,
887
+ "operator_required": True,
888
+ "description": "معدات حفر الأنفاق للمشاريع الكبيرة",
889
+ "image_url": "https://example.com/tbm.jpg"
890
+ },
891
+ {
892
+ "id": "EQ-042",
893
+ "name": "سدود مؤقتة",
894
+ "category": "معدات السيول والكباري",
895
+ "subcategory": "معدات سيول",
896
+ "brand": "أكواباريير",
897
+ "model": "K-100",
898
+ "capacity": "غير محدد",
899
+ "production_rate": "100 م/يوم",
900
+ "hourly_cost": 200,
901
+ "daily_cost": 1600,
902
+ "weekly_cost": 9600,
903
+ "monthly_cost": 38400,
904
+ "fuel_consumption": "غير محدد",
905
+ "maintenance_period": "غير محدد",
906
+ "maintenance_cost": 2000,
907
+ "operator_required": True,
908
+ "description": "سدود مؤقتة للحماية من السيول",
909
+ "image_url": "https://example.com/cofferdam.jpg"
910
+ }
911
+ ])
912
+
913
+ # 8. معدات الضغط والتثبيت
914
+ equipment_data.extend([
915
+ {
916
+ "id": "EQ-043",
917
+ "name": "مدحلة تربة ثقيلة",
918
+ "category": "معدات الضغط والتثبيت",
919
+ "subcategory": "مداحل",
920
+ "brand": "بوماج",
921
+ "model": "BW 226",
922
+ "capacity": "غير محدد",
923
+ "production_rate": "3000 م2/ساعة",
924
+ "hourly_cost": 250,
925
+ "daily_cost": 2000,
926
+ "weekly_cost": 12000,
927
+ "monthly_cost": 48000,
928
+ "fuel_consumption": "20 لتر/ساعة",
929
+ "maintenance_period": "250 ساعة",
930
+ "maintenance_cost": 3500,
931
+ "operator_required": True,
932
+ "description": "مدحلة تربة ثقيلة للمشاريع الكبيرة",
933
+ "image_url": "https://example.com/soilroller.jpg"
934
+ },
935
+ {
936
+ "id": "EQ-044",
937
+ "name": "دكاكة قفازة",
938
+ "category": "معدات الضغط والتثبيت",
939
+ "subcategory": "دكاكات",
940
+ "brand": "واكر نيوسن",
941
+ "model": "BS 60-4",
942
+ "capacity": "غير محدد",
943
+ "production_rate": "150 م2/ساعة",
944
+ "hourly_cost": 50,
945
+ "daily_cost": 400,
946
+ "weekly_cost": 2400,
947
+ "monthly_cost": 9600,
948
+ "fuel_consumption": "2 لتر/ساعة",
949
+ "maintenance_period": "100 ساعة",
950
+ "maintenance_cost": 800,
951
+ "operator_required": True,
952
+ "description": "دكاكة قفازة للمساحات الضيقة",
953
+ "image_url": "https://example.com/rammer.jpg"
954
+ },
955
+ {
956
+ "id": "EQ-045",
957
+ "name": "دكاكة هزازة",
958
+ "category": "معدات الضغط والتثبيت",
959
+ "subcategory": "دكاكات",
960
+ "brand": "واكر نيوسن",
961
+ "model": "DPU 6555",
962
+ "capacity": "غير محدد",
963
+ "production_rate": "500 م2/ساعة",
964
+ "hourly_cost": 80,
965
+ "daily_cost": 640,
966
+ "weekly_cost": 3840,
967
+ "monthly_cost": 15360,
968
+ "fuel_consumption": "3 لتر/ساعة",
969
+ "maintenance_period": "100 ساعة",
970
+ "maintenance_cost": 1000,
971
+ "operator_required": True,
972
+ "description": "دكاكة هزازة للمساحات المتوسطة",
973
+ "image_url": "https://example.com/platecompactor.jpg"
974
+ },
975
+ {
976
+ "id": "EQ-046",
977
+ "name": "معدات تثبيت التربة",
978
+ "category": "معدات الضغط والتثبيت",
979
+ "subcategory": "معدات تثبيت",
980
+ "brand": "ويرتجن",
981
+ "model": "WR 250",
982
+ "capacity": "غير محدد",
983
+ "production_rate": "5000 م2/يوم",
984
+ "hourly_cost": 500,
985
+ "daily_cost": 4000,
986
+ "weekly_cost": 24000,
987
+ "monthly_cost": 96000,
988
+ "fuel_consumption": "40 لتر/ساعة",
989
+ "maintenance_period": "250 ساعة",
990
+ "maintenance_cost": 7000,
991
+ "operator_required": True,
992
+ "description": "معدات تثبيت التربة بالإسمنت أو الجير",
993
+ "image_url": "https://example.com/soilstabilizer.jpg"
994
+ }
995
+ ])
996
+
997
+ # 9. معدات التوليد والطاقة
998
+ equipment_data.extend([
999
+ {
1000
+ "id": "EQ-047",
1001
+ "name": "مولد كهرباء كبير",
1002
+ "category": "معدات التوليد والطاقة",
1003
+ "subcategory": "مولدات",
1004
+ "brand": "كاتربي��ر",
1005
+ "model": "C15",
1006
+ "capacity": "500 كيلوواط",
1007
+ "production_rate": "500 كيلوواط",
1008
+ "hourly_cost": 300,
1009
+ "daily_cost": 2400,
1010
+ "weekly_cost": 14400,
1011
+ "monthly_cost": 57600,
1012
+ "fuel_consumption": "80 لتر/ساعة",
1013
+ "maintenance_period": "500 ساعة",
1014
+ "maintenance_cost": 5000,
1015
+ "operator_required": False,
1016
+ "description": "مولد كهرباء كبير للمشاريع الكبيرة",
1017
+ "image_url": "https://example.com/generator.jpg"
1018
+ },
1019
+ {
1020
+ "id": "EQ-048",
1021
+ "name": "مولد كهرباء متوسط",
1022
+ "category": "معدات التوليد والطاقة",
1023
+ "subcategory": "مولدات",
1024
+ "brand": "كاتربيلر",
1025
+ "model": "C9",
1026
+ "capacity": "250 كيلوواط",
1027
+ "production_rate": "250 كيلوواط",
1028
+ "hourly_cost": 200,
1029
+ "daily_cost": 1600,
1030
+ "weekly_cost": 9600,
1031
+ "monthly_cost": 38400,
1032
+ "fuel_consumption": "45 لتر/ساعة",
1033
+ "maintenance_period": "500 ساعة",
1034
+ "maintenance_cost": 3500,
1035
+ "operator_required": False,
1036
+ "description": "مولد كهرباء متوسط للمشاريع المتوسطة",
1037
+ "image_url": "https://example.com/generator2.jpg"
1038
+ },
1039
+ {
1040
+ "id": "EQ-049",
1041
+ "name": "ضاغط هواء كبير",
1042
+ "category": "معدات التوليد والطاقة",
1043
+ "subcategory": "ضواغط",
1044
+ "brand": "أطلس كوبكو",
1045
+ "model": "XRVS 1000",
1046
+ "capacity": "1000 قدم مكعب/دقيقة",
1047
+ "production_rate": "1000 قدم مكعب/دقيقة",
1048
+ "hourly_cost": 200,
1049
+ "daily_cost": 1600,
1050
+ "weekly_cost": 9600,
1051
+ "monthly_cost": 38400,
1052
+ "fuel_consumption": "30 لتر/ساعة",
1053
+ "maintenance_period": "500 ساعة",
1054
+ "maintenance_cost": 3000,
1055
+ "operator_required": False,
1056
+ "description": "ضاغط هواء كبير للمشاريع الكبيرة",
1057
+ "image_url": "https://example.com/compressor.jpg"
1058
+ },
1059
+ {
1060
+ "id": "EQ-050",
1061
+ "name": "ضاغط هواء متوسط",
1062
+ "category": "معدات التوليد والطاقة",
1063
+ "subcategory": "ضواغط",
1064
+ "brand": "أطلس كوبكو",
1065
+ "model": "XRVS 500",
1066
+ "capacity": "500 قدم مكعب/دقيقة",
1067
+ "production_rate": "500 قدم مكعب/دقيقة",
1068
+ "hourly_cost": 150,
1069
+ "daily_cost": 1200,
1070
+ "weekly_cost": 7200,
1071
+ "monthly_cost": 28800,
1072
+ "fuel_consumption": "20 لتر/ساعة",
1073
+ "maintenance_period": "500 ساعة",
1074
+ "maintenance_cost": 2500,
1075
+ "operator_required": False,
1076
+ "description": "ضاغط هواء متوسط للمشاريع المتوسطة",
1077
+ "image_url": "https://example.com/compressor2.jpg"
1078
+ }
1079
+ ])
1080
+
1081
+ # 10. معدات القياس والمساحة
1082
+ equipment_data.extend([
1083
+ {
1084
+ "id": "EQ-051",
1085
+ "name": "محطة رصد متكاملة",
1086
+ "category": "معدات القياس والمساحة",
1087
+ "subcategory": "محطات رصد",
1088
+ "brand": "ليكا",
1089
+ "model": "TS16",
1090
+ "capacity": "غير محدد",
1091
+ "production_rate": "غير محدد",
1092
+ "hourly_cost": 100,
1093
+ "daily_cost": 800,
1094
+ "weekly_cost": 4800,
1095
+ "monthly_cost": 19200,
1096
+ "fuel_consumption": "غير محدد",
1097
+ "maintenance_period": "1000 ساعة",
1098
+ "maintenance_cost": 2000,
1099
+ "operator_required": True,
1100
+ "description": "محطة رصد متكاملة للمساحة الدقيقة",
1101
+ "image_url": "https://example.com/totalstation.jpg"
1102
+ },
1103
+ {
1104
+ "id": "EQ-052",
1105
+ "name": "جهاز GPS مساحي",
1106
+ "category": "معدات القياس والمساحة",
1107
+ "subcategory": "أجهزة GPS",
1108
+ "brand": "ترمبل",
1109
+ "model": "R10",
1110
+ "capacity": "غير محدد",
1111
+ "production_rate": "غير محدد",
1112
+ "hourly_cost": 80,
1113
+ "daily_cost": 640,
1114
+ "weekly_cost": 3840,
1115
+ "monthly_cost": 15360,
1116
+ "fuel_consumption": "غير محدد",
1117
+ "maintenance_period": "1000 ساعة",
1118
+ "maintenance_cost": 1500,
1119
+ "operator_required": True,
1120
+ "description": "جهاز GPS مساحي دقيق",
1121
+ "image_url": "https://example.com/gps.jpg"
1122
+ },
1123
+ {
1124
+ "id": "EQ-053",
1125
+ "name": "جهاز مسح ليزري ثلاثي الأبعاد",
1126
+ "category": "معدات القياس والمساحة",
1127
+ "subcategory": "أجهزة مسح",
1128
+ "brand": "ليكا",
1129
+ "model": "RTC360",
1130
+ "capacity": "غير محدد",
1131
+ "production_rate": "غير محدد",
1132
+ "hourly_cost": 150,
1133
+ "daily_cost": 1200,
1134
+ "weekly_cost": 7200,
1135
+ "monthly_cost": 28800,
1136
+ "fuel_consumption": "غير محدد",
1137
+ "maintenance_period": "1000 ساعة",
1138
+ "maintenance_cost": 3000,
1139
+ "operator_required": True,
1140
+ "description": "جهاز مسح ليزري ثلاثي الأبعاد للمشاريع المعقدة",
1141
+ "image_url": "https://example.com/laserscanner.jpg"
1142
+ },
1143
+ {
1144
+ "id": "EQ-054",
1145
+ "name": "طائرة بدون طيار للمسح",
1146
+ "category": "معدات القياس والمساحة",
1147
+ "subcategory": "طائرات مسح",
1148
+ "brand": "دي جي آي",
1149
+ "model": "Phantom 4 RTK",
1150
+ "capacity": "غير محدد",
1151
+ "production_rate": "غير محدد",
1152
+ "hourly_cost": 100,
1153
+ "daily_cost": 800,
1154
+ "weekly_cost": 4800,
1155
+ "monthly_cost": 19200,
1156
+ "fuel_consumption": "غير محدد",
1157
+ "maintenance_period": "500 ساعة",
1158
+ "maintenance_cost": 1000,
1159
+ "operator_required": True,
1160
+ "description": "طائرة بدون طيار للمسح الجوي والتصوير",
1161
+ "image_url": "https://example.com/drone.jpg"
1162
+ }
1163
+ ])
1164
+
1165
+ # تخزين البيانات في حالة الجلسة
1166
+ st.session_state.equipment_catalog = pd.DataFrame(equipment_data)
1167
+
1168
+ def render(self):
1169
+ """عرض واجهة كتالوج المعدات"""
1170
+
1171
+ st.markdown("## كتالوج المعدات")
1172
+
1173
+ # إنشاء تبويبات لعرض الكتالوج
1174
+ tabs = st.tabs([
1175
+ "عرض الكتالوج",
1176
+ "إضافة معدة",
1177
+ "تحليل التكاليف",
1178
+ "استيراد/تصدير"
1179
+ ])
1180
+
1181
+ with tabs[0]:
1182
+ self._render_catalog_view_tab()
1183
+
1184
+ with tabs[1]:
1185
+ self._render_add_equipment_tab()
1186
+
1187
+ with tabs[2]:
1188
+ self._render_cost_analysis_tab()
1189
+
1190
+ with tabs[3]:
1191
+ self._render_import_export_tab()
1192
+
1193
+ def _render_catalog_view_tab(self):
1194
+ """عرض تبويب عرض الكتالوج"""
1195
+
1196
+ st.markdown("### عرض كتالوج المعدات")
1197
+
1198
+ # استخراج البيانات
1199
+ equipment_df = st.session_state.equipment_catalog
1200
+
1201
+ # إنشاء فلاتر للعرض
1202
+ col1, col2 = st.columns(2)
1203
+
1204
+ with col1:
1205
+ # فلتر حسب الفئة
1206
+ categories = ["الكل"] + sorted(equipment_df["category"].unique().tolist())
1207
+ selected_category = st.selectbox("اختر فئة المعدات", categories)
1208
+
1209
+ with col2:
1210
+ # فلتر حسب الفئة الفرعية
1211
+ if selected_category != "الكل":
1212
+ subcategories = ["الكل"] + sorted(equipment_df[equipment_df["category"] == selected_category]["subcategory"].unique().tolist())
1213
+ else:
1214
+ subcategories = ["الكل"] + sorted(equipment_df["subcategory"].unique().tolist())
1215
+
1216
+ selected_subcategory = st.selectbox("اختر الفئة الفرعية", subcategories)
1217
+
1218
+ # تطبيق الفلاتر
1219
+ filtered_df = equipment_df.copy()
1220
+
1221
+ if selected_category != "الكل":
1222
+ filtered_df = filtered_df[filtered_df["category"] == selected_category]
1223
+
1224
+ if selected_subcategory != "الكل":
1225
+ filtered_df = filtered_df[filtered_df["subcategory"] == selected_subcategory]
1226
+
1227
+ # عرض البيانات
1228
+ if not filtered_df.empty:
1229
+ # عرض عدد النتائج
1230
+ st.info(f"تم العثور على {len(filtered_df)} معدة")
1231
+
1232
+ # عرض المعدات في شكل بطاقات
1233
+ for i, (_, equipment) in enumerate(filtered_df.iterrows()):
1234
+ col1, col2 = st.columns([1, 2])
1235
+
1236
+ with col1:
1237
+ # عرض صورة المعدة (استخدام صورة افتراضية)
1238
+ st.image("https://via.placeholder.com/150", caption=equipment["name"])
1239
+
1240
+ with col2:
1241
+ # عرض معلومات المعدة
1242
+ st.markdown(f"**{equipment['name']}** (الكود: {equipment['id']})")
1243
+ st.markdown(f"الفئة: {equipment['category']} - {equipment['subcategory']}")
1244
+ st.markdown(f"الماركة: {equipment['brand']} | الموديل: {equipment['model']}")
1245
+ st.markdown(f"السعة: {equipment['capacity']} | معدل الإنتاج: {equipment['production_rate']}")
1246
+
1247
+ # عرض التكاليف
1248
+ cost_col1, cost_col2, cost_col3, cost_col4 = st.columns(4)
1249
+ with cost_col1:
1250
+ st.metric("بالساعة", f"{equipment['hourly_cost']} ريال")
1251
+ with cost_col2:
1252
+ st.metric("باليوم", f"{equipment['daily_cost']} ريال")
1253
+ with cost_col3:
1254
+ st.metric("بالأسبوع", f"{equipment['weekly_cost']} ريال")
1255
+ with cost_col4:
1256
+ st.metric("بالشهر", f"{equipment['monthly_cost']} ريال")
1257
+
1258
+ # إضافة زر لعرض التفاصيل
1259
+ if st.button(f"عرض التفاصيل الكاملة", key=f"details_{equipment['id']}"):
1260
+ st.session_state.selected_equipment = equipment['id']
1261
+ self._show_equipment_details(equipment)
1262
+
1263
+ st.markdown("---")
1264
+ else:
1265
+ st.warning("لا توجد معدات تطابق معايير البحث")
1266
+
1267
+ def _show_equipment_details(self, equipment):
1268
+ """عرض تفاصيل المعدة"""
1269
+
1270
+ st.markdown(f"## تفاصيل المعدة: {equipment['name']}")
1271
+
1272
+ col1, col2 = st.columns([1, 2])
1273
+
1274
+ with col1:
1275
+ # عرض صورة المعدة (استخدام صورة افتراضية)
1276
+ st.image("https://via.placeholder.com/300", caption=equipment["name"])
1277
+
1278
+ with col2:
1279
+ # عرض المعلومات الأساسية
1280
+ st.markdown("### المعلومات الأساسية")
1281
+ st.markdown(f"**الكود:** {equipment['id']}")
1282
+ st.markdown(f"**الفئة:** {equipment['category']} - {equipment['subcategory']}")
1283
+ st.markdown(f"**الماركة:** {equipment['brand']}")
1284
+ st.markdown(f"**الموديل:** {equipment['model']}")
1285
+ st.markdown(f"**السعة:** {equipment['capacity']}")
1286
+ st.markdown(f"**معدل الإنتاج:** {equipment['production_rate']}")
1287
+ st.markdown(f"**الوصف:** {equipment['description']}")
1288
+
1289
+ # عرض معلومات التكلفة
1290
+ st.markdown("### معلومات التكلفة")
1291
+ cost_col1, cost_col2, cost_col3, cost_col4 = st.columns(4)
1292
+ with cost_col1:
1293
+ st.metric("التكلفة بالساعة", f"{equipment['hourly_cost']} ريال")
1294
+ with cost_col2:
1295
+ st.metric("التكلفة باليوم", f"{equipment['daily_cost']} ريال")
1296
+ with cost_col3:
1297
+ st.metric("التكلفة بالأسبوع", f"{equipment['weekly_cost']} ريال")
1298
+ with cost_col4:
1299
+ st.metric("التكلفة بالشهر", f"{equipment['monthly_cost']} ريال")
1300
+
1301
+ # عرض معلومات التشغيل والصيانة
1302
+ st.markdown("### معلومات التشغيل والصيانة")
1303
+ maint_col1, maint_col2, maint_col3 = st.columns(3)
1304
+ with maint_col1:
1305
+ st.metric("استهلاك الوقود", f"{equipment['fuel_consumption']}")
1306
+ with maint_col2:
1307
+ st.metric("فترة الصيانة", f"{equipment['maintenance_period']}")
1308
+ with maint_col3:
1309
+ st.metric("تكلفة الصيانة", f"{equipment['maintenance_cost']} ريال")
1310
+
1311
+ st.markdown(f"**يتطلب مشغل:** {'نعم' if equipment['operator_required'] else 'لا'}")
1312
+
1313
+ # إضافة زر للتعديل
1314
+ if st.button("تعديل بيانات المعدة"):
1315
+ st.session_state.edit_equipment = equipment['id']
1316
+ # هنا يمكن إضافة منطق التعديل
1317
+
1318
+ def _render_add_equipment_tab(self):
1319
+ """عرض تبويب إضافة معدة"""
1320
+
1321
+ st.markdown("### إضافة معدة جديدة")
1322
+
1323
+ # استخراج البيانات
1324
+ equipment_df = st.session_state.equipment_catalog
1325
+
1326
+ # إنشاء نموذج إضافة معدة
1327
+ with st.form("add_equipment_form"):
1328
+ st.markdown("#### المعلومات الأساسية")
1329
+
1330
+ # الصف الأول
1331
+ col1, col2 = st.columns(2)
1332
+ with col1:
1333
+ equipment_id = st.text_input("كود المعدة", value=f"EQ-{len(equipment_df) + 1:03d}")
1334
+ equipment_name = st.text_input("اسم المعدة", placeholder="مثال: حفارة هيدروليكية متوسطة")
1335
+
1336
+ with col2:
1337
+ # استخراج الفئات والفئات الفرعية الموجودة
1338
+ categories = sorted(equipment_df["category"].unique().tolist())
1339
+ equipment_category = st.selectbox("فئة المعدة", categories)
1340
+
1341
+ # استخراج الفئات الفرعية بناءً على الفئة المختارة
1342
+ subcategories = sorted(equipment_df[equipment_df["category"] == equipment_category]["subcategory"].unique().tolist())
1343
+ equipment_subcategory = st.selectbox("الفئة الفرعية", subcategories)
1344
+
1345
+ # الصف الثاني
1346
+ col1, col2 = st.columns(2)
1347
+ with col1:
1348
+ equipment_brand = st.text_input("الماركة", placeholder="مثال: كاتربيلر")
1349
+ equipment_model = st.text_input("الموديل", placeholder="مثال: CAT 320")
1350
+
1351
+ with col2:
1352
+ equipment_capacity = st.text_input("السعة", placeholder="مثال: 1.5 م3")
1353
+ equipment_production_rate = st.text_input("معدل الإنتاج", placeholder="مثال: 100 م3/ساعة")
1354
+
1355
+ st.markdown("#### معلومات التكلفة")
1356
+
1357
+ # الصف الثالث
1358
+ col1, col2, col3, col4 = st.columns(4)
1359
+ with col1:
1360
+ equipment_hourly_cost = st.number_input("التكلفة بالساعة (ريال)", min_value=0, step=10)
1361
+ with col2:
1362
+ equipment_daily_cost = st.number_input("التكلفة باليوم (ريال)", min_value=0, step=100)
1363
+ with col3:
1364
+ equipment_weekly_cost = st.number_input("التكلفة بالأسبوع (ريال)", min_value=0, step=500)
1365
+ with col4:
1366
+ equipment_monthly_cost = st.number_input("التكلفة بالشهر (ريال)", min_value=0, step=1000)
1367
+
1368
+ st.markdown("#### معلومات التشغيل والصيانة")
1369
+
1370
+ # الصف الرابع
1371
+ col1, col2, col3 = st.columns(3)
1372
+ with col1:
1373
+ equipment_fuel_consumption = st.text_input("استهلاك الوقود", placeholder="مثال: 25 لتر/ساعة")
1374
+ with col2:
1375
+ equipment_maintenance_period = st.text_input("فترة الصيانة", placeholder="مثال: 250 ساعة")
1376
+ with col3:
1377
+ equipment_maintenance_cost = st.number_input("تكلفة الصيانة (ريال)", min_value=0, step=500)
1378
+
1379
+ # الصف الخامس
1380
+ col1, col2 = st.columns(2)
1381
+ with col1:
1382
+ equipment_operator_required = st.checkbox("يتطلب مشغل")
1383
+ with col2:
1384
+ equipment_image_url = st.text_input("رابط الصورة", placeholder="مثال: https://example.com/image.jpg")
1385
+
1386
+ # وصف المعدة
1387
+ equipment_description = st.text_area("وصف المعدة", placeholder="أدخل وصفاً تفصيلياً للمعدة")
1388
+
1389
+ # زر الإضافة
1390
+ submit_button = st.form_submit_button("إضافة المعدة")
1391
+
1392
+ if submit_button:
1393
+ # التحقق من البيانات
1394
+ if not equipment_name or not equipment_category or not equipment_subcategory:
1395
+ st.error("يرجى إدخال المعلومات الأساسية للمعدة")
1396
+ else:
1397
+ # إنشاء معدة جديدة
1398
+ new_equipment = {
1399
+ "id": equipment_id,
1400
+ "name": equipment_name,
1401
+ "category": equipment_category,
1402
+ "subcategory": equipment_subcategory,
1403
+ "brand": equipment_brand,
1404
+ "model": equipment_model,
1405
+ "capacity": equipment_capacity,
1406
+ "production_rate": equipment_production_rate,
1407
+ "hourly_cost": equipment_hourly_cost,
1408
+ "daily_cost": equipment_daily_cost,
1409
+ "weekly_cost": equipment_weekly_cost,
1410
+ "monthly_cost": equipment_monthly_cost,
1411
+ "fuel_consumption": equipment_fuel_consumption,
1412
+ "maintenance_period": equipment_maintenance_period,
1413
+ "maintenance_cost": equipment_maintenance_cost,
1414
+ "operator_required": equipment_operator_required,
1415
+ "description": equipment_description,
1416
+ "image_url": equipment_image_url if equipment_image_url else "https://via.placeholder.com/150"
1417
+ }
1418
+
1419
+ # إضافة المعدة إلى الكتالوج
1420
+ st.session_state.equipment_catalog = pd.concat([
1421
+ st.session_state.equipment_catalog,
1422
+ pd.DataFrame([new_equipment])
1423
+ ], ignore_index=True)
1424
+
1425
+ st.success(f"تمت إضافة المعدة {equipment_name} بنجاح!")
1426
+
1427
+ def _render_cost_analysis_tab(self):
1428
+ """عرض تبويب تحليل التكاليف"""
1429
+
1430
+ st.markdown("### تحليل تكاليف المعدات")
1431
+
1432
+ # استخراج البيانات
1433
+ equipment_df = st.session_state.equipment_catalog
1434
+
1435
+ # تحليل متوسط التكاليف حسب الفئة
1436
+ st.markdown("#### متوسط التكاليف حسب الفئة")
1437
+
1438
+ # حساب متوسط التكاليف لكل فئة
1439
+ category_costs = equipment_df.groupby("category").agg({
1440
+ "hourly_cost": "mean",
1441
+ "daily_cost": "mean",
1442
+ "weekly_cost": "mean",
1443
+ "monthly_cost": "mean"
1444
+ }).reset_index()
1445
+
1446
+ # تغيير أسماء الأعمدة
1447
+ category_costs.columns = ["الفئة", "متوسط التكلفة بالساعة", "متوسط التكلفة باليوم", "متوسط التكلفة بالأسبوع", "متوسط التكلفة بالشهر"]
1448
+
1449
+ # عرض الجدول
1450
+ st.dataframe(category_costs, use_container_width=True)
1451
+
1452
+ # إنشاء رسم بياني للمقارنة
1453
+ st.markdown("#### مقارنة متوسط التكاليف اليومية حسب الفئة")
1454
+
1455
+ fig = px.bar(
1456
+ category_costs,
1457
+ x="الفئة",
1458
+ y="متوسط التكلفة باليوم",
1459
+ title="متوسط التكاليف اليومية حسب فئة المعدات",
1460
+ color="الفئة",
1461
+ text_auto=True
1462
+ )
1463
+
1464
+ st.plotly_chart(fig, use_container_width=True)
1465
+
1466
+ # تحليل توزيع المعدات حسب الفئة
1467
+ st.markdown("#### توزيع المعدات حسب الفئة")
1468
+
1469
+ # حساب عدد المعدات في كل فئة
1470
+ category_counts = equipment_df["category"].value_counts().reset_index()
1471
+ category_counts.columns = ["الفئة", "عدد المعدات"]
1472
+
1473
+ # إنشاء رسم بياني دائري
1474
+ fig = px.pie(
1475
+ category_counts,
1476
+ values="عدد المعدات",
1477
+ names="الفئة",
1478
+ title="توزيع المعدات حسب الفئة",
1479
+ color="الفئة"
1480
+ )
1481
+
1482
+ st.plotly_chart(fig, use_container_width=True)
1483
+
1484
+ # حاسبة تكاليف المشروع
1485
+ st.markdown("#### حاسبة تكاليف المشروع")
1486
+
1487
+ with st.form("project_cost_calculator"):
1488
+ st.markdown("أدخل المعدات المطلوبة للمشروع")
1489
+
1490
+ # اختيار المعدات
1491
+ selected_equipment = st.multiselect(
1492
+ "اختر المعدات",
1493
+ options=equipment_df["name"].tolist(),
1494
+ format_func=lambda x: f"{x} ({equipment_df[equipment_df['name'] == x]['id'].iloc[0]})"
1495
+ )
1496
+
1497
+ # اختيار مدة المشروع
1498
+ project_duration = st.number_input("مدة المشروع (بالأيام)", min_value=1, value=30)
1499
+
1500
+ # زر الحساب
1501
+ calculate_button = st.form_submit_button("حساب التكاليف")
1502
+
1503
+ if calculate_button:
1504
+ if not selected_equipment:
1505
+ st.error("يرجى اختيار معدة واحدة على الأقل")
1506
+ else:
1507
+ # حساب التكاليف
1508
+ project_costs = []
1509
+
1510
+ for equipment_name in selected_equipment:
1511
+ equipment = equipment_df[equipment_df["name"] == equipment_name].iloc[0]
1512
+
1513
+ # حساب التكلفة بناءً على المدة
1514
+ if project_duration <= 1:
1515
+ # تكلفة يوم واحد
1516
+ cost = equipment["daily_cost"]
1517
+ cost_type = "يومية"
1518
+ elif project_duration <= 7:
1519
+ # تكلفة أسبوع
1520
+ cost = equipment["weekly_cost"]
1521
+ cost_type = "أسبوعية"
1522
+ else:
1523
+ # تكلفة شهر أو أكثر
1524
+ months = project_duration / 30
1525
+ cost = equipment["monthly_cost"] * months
1526
+ cost_type = "شهرية"
1527
+
1528
+ project_costs.append({
1529
+ "المعدة": equipment_name,
1530
+ "الكود": equipment["id"],
1531
+ "نوع التكلفة": cost_type,
1532
+ "التكلفة الإجمالية": cost
1533
+ })
1534
+
1535
+ # عرض النتائج
1536
+ project_costs_df = pd.DataFrame(project_costs)
1537
+ st.dataframe(project_costs_df, use_container_width=True)
1538
+
1539
+ # حساب إجمالي التكاليف
1540
+ total_cost = project_costs_df["التكلفة الإجمالية"].sum()
1541
+ st.metric("إجمالي تكاليف المعدات للمشروع", f"{total_cost:,.2f} ريال")
1542
+
1543
+ def _render_import_export_tab(self):
1544
+ """عرض تبويب استيراد/تصدير"""
1545
+
1546
+ st.markdown("### استيراد وتصدير بيانات المعدات")
1547
+
1548
+ # استيراد البيانات
1549
+ st.markdown("#### استيراد البيانات")
1550
+
1551
+ uploaded_file = st.file_uploader("اختر ملف Excel لاستيراد بيانات المعدات", type=["xlsx", "xls"])
1552
+
1553
+ if uploaded_file is not None:
1554
+ try:
1555
+ # قراءة الملف
1556
+ imported_df = pd.read_excel(uploaded_file)
1557
+
1558
+ # عرض البيانات المستوردة
1559
+ st.dataframe(imported_df, use_container_width=True)
1560
+
1561
+ # زر الاستيراد
1562
+ if st.button("استيراد البيانات"):
1563
+ # التحقق من وجود الأعمدة المطلوبة
1564
+ required_columns = ["id", "name", "category", "subcategory"]
1565
+
1566
+ if all(col in imported_df.columns for col in required_columns):
1567
+ # دمج البيانات المستوردة مع البيانات الحالية
1568
+ st.session_state.equipment_catalog = pd.concat([
1569
+ st.session_state.equipment_catalog,
1570
+ imported_df
1571
+ ], ignore_index=True).drop_duplicates(subset=["id"])
1572
+
1573
+ st.success(f"تم استيراد {len(imported_df)} معدة بنجاح!")
1574
+ else:
1575
+ st.error("الملف المستورد لا يحتوي على الأعمدة المطلوبة")
1576
+
1577
+ except Exception as e:
1578
+ st.error(f"حدث خطأ أثناء استيراد الملف: {str(e)}")
1579
+
1580
+ # تصدير البيانات
1581
+ st.markdown("#### تصدير البيانات")
1582
+
1583
+ # اختيار تنسيق التصدير
1584
+ export_format = st.radio("اختر تنسيق التصدير", ["Excel", "CSV", "JSON"], horizontal=True)
1585
+
1586
+ if st.button("تصدير البيانات"):
1587
+ # استخراج البيانات
1588
+ equipment_df = st.session_state.equipment_catalog
1589
+
1590
+ # تصدير البيانات حسب التنسيق المختار
1591
+ if export_format == "Excel":
1592
+ # تصدير إلى Excel
1593
+ output = io.BytesIO()
1594
+ with pd.ExcelWriter(output, engine="openpyxl") as writer:
1595
+ equipment_df.to_excel(writer, index=False, sheet_name="Equipment")
1596
+
1597
+ # تحميل الملف
1598
+ st.download_button(
1599
+ label="تنزيل ملف Excel",
1600
+ data=output.getvalue(),
1601
+ file_name="equipment_catalog.xlsx",
1602
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1603
+ )
1604
+
1605
+ elif export_format == "CSV":
1606
+ # تصدير إلى CSV
1607
+ csv_data = equipment_df.to_csv(index=False)
1608
+
1609
+ # تحميل الملف
1610
+ st.download_button(
1611
+ label="تنزيل ملف CSV",
1612
+ data=csv_data,
1613
+ file_name="equipment_catalog.csv",
1614
+ mime="text/csv"
1615
+ )
1616
+
1617
+ else: # JSON
1618
+ # تصدير إلى JSON
1619
+ json_data = equipment_df.to_json(orient="records", force_ascii=False)
1620
+
1621
+ # تحميل الملف
1622
+ st.download_button(
1623
+ label="تنزيل ملف JSON",
1624
+ data=json_data,
1625
+ file_name="equipment_catalog.json",
1626
+ mime="application/json"
1627
+ )
1628
+
1629
+ def get_equipment_by_id(self, equipment_id):
1630
+ """الحصول على معدة بواسطة الكود"""
1631
+
1632
+ equipment_df = st.session_state.equipment_catalog
1633
+ equipment = equipment_df[equipment_df["id"] == equipment_id]
1634
+
1635
+ if not equipment.empty:
1636
+ return equipment.iloc[0].to_dict()
1637
+
1638
+ return None
1639
+
1640
+ def get_equipment_by_category(self, category):
1641
+ """الحصول على المعدات حسب الفئة"""
1642
+
1643
+ equipment_df = st.session_state.equipment_catalog
1644
+ equipment = equipment_df[equipment_df["category"] == category]
1645
+
1646
+ if not equipment.empty:
1647
+ return equipment.to_dict(orient="records")
1648
+
1649
+ return []
1650
+
1651
+ def calculate_equipment_cost(self, equipment_id, duration_days):
1652
+ """حساب تكلفة المعدة بناءً على المدة"""
1653
+
1654
+ equipment = self.get_equipment_by_id(equipment_id)
1655
+
1656
+ if equipment:
1657
+ # حساب التكلفة بناءً على المدة
1658
+ if duration_days <= 1:
1659
+ # تكلفة يوم واحد
1660
+ return equipment["daily_cost"]
1661
+ elif duration_days <= 7:
1662
+ # تكلفة أسبوع
1663
+ return equipment["weekly_cost"]
1664
+ else:
1665
+ # تكلفة شهر أو أكثر
1666
+ months = duration_days / 30
1667
+ return equipment["monthly_cost"] * months
1668
+
1669
+ return 0
pricing_system/modules/catalogs/labor_catalog.py ADDED
@@ -0,0 +1,1527 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ كتالوج العمالة - وحدة إدارة العمالة والمهندسين
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import plotly.express as px
9
+ import os
10
+ import json
11
+ from datetime import datetime
12
+ import io
13
+
14
+ class LaborCatalog:
15
+ """كتالوج العمالة"""
16
+
17
+ def __init__(self):
18
+ """تهيئة كتالوج العمالة"""
19
+
20
+ # تهيئة حالة الجلسة لكتالوج العمالة
21
+ if 'labor_catalog' not in st.session_state:
22
+ # إنشاء بيانات افتراضية للعمالة
23
+ self._initialize_labor_catalog()
24
+
25
+ def _initialize_labor_catalog(self):
26
+ """تهيئة بيانات كتالوج العمالة"""
27
+
28
+ # تعريف فئات العمالة
29
+ labor_categories = [
30
+ "مهندسين",
31
+ "فنيين",
32
+ "عمال مهرة",
33
+ "عمال عاديين",
34
+ "سائقين",
35
+ "مشغلي معدات",
36
+ "إداريين"
37
+ ]
38
+
39
+ # إنشاء قائمة العمالة
40
+ labor_data = []
41
+
42
+ # 1. مهندسين
43
+ labor_data.extend([
44
+ {
45
+ "id": "ENG-001",
46
+ "name": "مهندس مدني (خبرة 15+ سنة)",
47
+ "category": "مهندسين",
48
+ "subcategory": "مدني",
49
+ "hourly_rate": 150,
50
+ "daily_rate": 1200,
51
+ "weekly_rate": 6000,
52
+ "monthly_rate": 25000,
53
+ "nationality": "سعودي",
54
+ "availability": "متاح",
55
+ "skills": "إدارة مشاريع، تصميم إنشائي، إشراف على التنفيذ",
56
+ "certifications": "عضوية الهيئة السعودية للمهندسين، PMP",
57
+ "description": "مهندس مدني ذو خبرة 15+ سنة في إدارة وتنفيذ مشاريع البنية التحتية والطرق والجسور"
58
+ },
59
+ {
60
+ "id": "ENG-002",
61
+ "name": "مهندس مدني (خبرة 10-15 سنة)",
62
+ "category": "مهندسين",
63
+ "subcategory": "مدني",
64
+ "hourly_rate": 120,
65
+ "daily_rate": 960,
66
+ "weekly_rate": 4800,
67
+ "monthly_rate": 20000,
68
+ "nationality": "سعودي",
69
+ "availability": "متاح",
70
+ "skills": "إدارة مشاريع، تصميم إنشائي، إشراف على التنفيذ",
71
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
72
+ "description": "مهندس مدني ذو خبرة 10-15 سنة في إدارة وتنفيذ مشاريع البنية التحتية والطرق والجسور"
73
+ },
74
+ {
75
+ "id": "ENG-003",
76
+ "name": "مهندس مدني (خبرة 5-10 سنوات)",
77
+ "category": "مهندسين",
78
+ "subcategory": "مدني",
79
+ "hourly_rate": 100,
80
+ "daily_rate": 800,
81
+ "weekly_rate": 4000,
82
+ "monthly_rate": 16000,
83
+ "nationality": "سعودي",
84
+ "availability": "متاح",
85
+ "skills": "تصميم إنشائي، إشراف على التنفيذ، حساب كميات",
86
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
87
+ "description": "مهندس مدني ذو خبرة 5-10 سنوات في تنفيذ مشاريع البنية التحتية والطرق والجسور"
88
+ },
89
+ {
90
+ "id": "ENG-004",
91
+ "name": "مهندس مدني (خبرة 1-5 سنوات)",
92
+ "category": "مهندسين",
93
+ "subcategory": "مدني",
94
+ "hourly_rate": 80,
95
+ "daily_rate": 640,
96
+ "weekly_rate": 3200,
97
+ "monthly_rate": 13000,
98
+ "nationality": "سعودي",
99
+ "availability": "متاح",
100
+ "skills": "إشراف على التنفيذ، حساب كميات، رسم هندسي",
101
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
102
+ "description": "مهندس مدني حديث الخبرة في تنفيذ مشاريع البنية التحتية والطرق والجسور"
103
+ },
104
+ {
105
+ "id": "ENG-005",
106
+ "name": "مهندس ميكانيكي (خبرة 10+ سنة)",
107
+ "category": "مهندسين",
108
+ "subcategory": "ميكانيكي",
109
+ "hourly_rate": 130,
110
+ "daily_rate": 1040,
111
+ "weekly_rate": 5200,
112
+ "monthly_rate": 22000,
113
+ "nationality": "سعودي",
114
+ "availability": "متاح",
115
+ "skills": "تصميم أنظمة ميكانيكية، إدارة صيانة المعدات، إشراف على التنفيذ",
116
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
117
+ "description": "مهندس ميكانيكي ذو خبرة 10+ سنة في تصميم وتنفيذ الأنظمة الميكانيكية وصيانة المعدات"
118
+ },
119
+ {
120
+ "id": "ENG-006",
121
+ "name": "مهندس ميكانيكي (خبرة 5-10 سنوات)",
122
+ "category": "مهندسين",
123
+ "subcategory": "ميكانيكي",
124
+ "hourly_rate": 100,
125
+ "daily_rate": 800,
126
+ "weekly_rate": 4000,
127
+ "monthly_rate": 16000,
128
+ "nationality": "سعودي",
129
+ "availability": "متاح",
130
+ "skills": "تصميم أنظمة ميكانيكية، صيانة المعدات، إشراف على التنفيذ",
131
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
132
+ "description": "مهندس ميكانيكي ذو خبرة 5-10 سنوات في تنفيذ الأنظمة الميكانيكية وصيانة المعدات"
133
+ },
134
+ {
135
+ "id": "ENG-007",
136
+ "name": "مهندس كهربائي (خبرة 10+ سنة)",
137
+ "category": "مهندسين",
138
+ "subcategory": "كهربائي",
139
+ "hourly_rate": 130,
140
+ "daily_rate": 1040,
141
+ "weekly_rate": 5200,
142
+ "monthly_rate": 22000,
143
+ "nationality": "سعودي",
144
+ "availability": "متاح",
145
+ "skills": "تصميم أنظمة كهربائية، إدارة مشاريع كهربائية، إشراف على التنفيذ",
146
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
147
+ "description": "مهندس كهربائي ذو خبرة 10+ سنة في تصميم وتنفيذ الأنظمة الكهربائية للمشاريع الكبرى"
148
+ },
149
+ {
150
+ "id": "ENG-008",
151
+ "name": "مهندس كهربائي (خبرة 5-10 سنوات)",
152
+ "category": "مهندسين",
153
+ "subcategory": "كهربائي",
154
+ "hourly_rate": 100,
155
+ "daily_rate": 800,
156
+ "weekly_rate": 4000,
157
+ "monthly_rate": 16000,
158
+ "nationality": "سعودي",
159
+ "availability": "متاح",
160
+ "skills": "تصميم أنظمة كهربائية، إشراف على التنفيذ، اختبار وتشغيل",
161
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
162
+ "description": "مهندس كهربائي ذو خبرة 5-10 سنوات في تنفيذ الأنظمة الكهربائية للمشاريع"
163
+ },
164
+ {
165
+ "id": "ENG-009",
166
+ "name": "مهندس مساحة (خبرة 10+ سنة)",
167
+ "category": "مهندسين",
168
+ "subcategory": "مساحة",
169
+ "hourly_rate": 120,
170
+ "daily_rate": 960,
171
+ "weekly_rate": 4800,
172
+ "monthly_rate": 20000,
173
+ "nationality": "سعودي",
174
+ "availability": "متاح",
175
+ "skills": "مسح طبوغرافي، نظم معلومات جغرافية، حساب كميات",
176
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
177
+ "description": "مهندس مساحة ذو خبرة 10+ سنة في المسح الطبوغرافي ونظم المعلومات الجغرافية"
178
+ },
179
+ {
180
+ "id": "ENG-010",
181
+ "name": "مهندس مساحة (خبرة 5-10 سنوات)",
182
+ "category": "مهندسين",
183
+ "subcategory": "مساحة",
184
+ "hourly_rate": 90,
185
+ "daily_rate": 720,
186
+ "weekly_rate": 3600,
187
+ "monthly_rate": 15000,
188
+ "nationality": "سعودي",
189
+ "availability": "متاح",
190
+ "skills": "مسح طبوغرافي، نظم معلومات جغرافية، حساب كميات",
191
+ "certifications": "عضوية الهيئة السعودية للمهندسين",
192
+ "description": "مهندس مساحة ذو خبرة 5-10 سنوات في المسح الطبوغرافي ونظم المعلومات الجغرافية"
193
+ }
194
+ ])
195
+
196
+ # 2. فنيين
197
+ labor_data.extend([
198
+ {
199
+ "id": "TECH-001",
200
+ "name": "فني مدني (خبرة 10+ سنة)",
201
+ "category": "فنيين",
202
+ "subcategory": "مدني",
203
+ "hourly_rate": 60,
204
+ "daily_rate": 480,
205
+ "weekly_rate": 2400,
206
+ "monthly_rate": 9500,
207
+ "nationality": "سعودي",
208
+ "availability": "متاح",
209
+ "skills": "قراءة مخططات، إشراف على التنفيذ، فحص جودة",
210
+ "certifications": "دبلوم فني",
211
+ "description": "فني مدني ذو خبرة 10+ سنة في الإشراف على تنفيذ الأعمال المدنية"
212
+ },
213
+ {
214
+ "id": "TECH-002",
215
+ "name": "فني مدني (خبرة 5-10 سنوات)",
216
+ "category": "فنيين",
217
+ "subcategory": "مدني",
218
+ "hourly_rate": 50,
219
+ "daily_rate": 400,
220
+ "weekly_rate": 2000,
221
+ "monthly_rate": 8000,
222
+ "nationality": "سعودي",
223
+ "availability": "متاح",
224
+ "skills": "قراءة مخططات، إشراف على التنفيذ، فحص جودة",
225
+ "certifications": "دبلوم فني",
226
+ "description": "فني مدني ذو خبرة 5-10 سنوات في الإشراف على تنفيذ الأعمال المدنية"
227
+ },
228
+ {
229
+ "id": "TECH-003",
230
+ "name": "فني مساحة (خبرة 10+ سنة)",
231
+ "category": "فنيين",
232
+ "subcategory": "مساحة",
233
+ "hourly_rate": 55,
234
+ "daily_rate": 440,
235
+ "weekly_rate": 2200,
236
+ "monthly_rate": 9000,
237
+ "nationality": "سعودي",
238
+ "availability": "متاح",
239
+ "skills": "استخدام أجهزة المساحة، قراءة مخططات، حساب كميات",
240
+ "certifications": "دبلوم فني",
241
+ "description": "فني مساحة ذو خبرة 10+ سنة في أعمال المساحة وحساب الكميات"
242
+ },
243
+ {
244
+ "id": "TECH-004",
245
+ "name": "فني مساحة (خبرة 5-10 سنوات)",
246
+ "category": "فنيين",
247
+ "subcategory": "مساحة",
248
+ "hourly_rate": 45,
249
+ "daily_rate": 360,
250
+ "weekly_rate": 1800,
251
+ "monthly_rate": 7500,
252
+ "nationality": "سعودي",
253
+ "availability": "متاح",
254
+ "skills": "استخدام أجهزة المساحة، قراءة مخططات، حساب كميات",
255
+ "certifications": "دبلوم فني",
256
+ "description": "فني مساحة ذو خبرة 5-10 سنوات في أعمال المساحة وحساب الكميات"
257
+ },
258
+ {
259
+ "id": "TECH-005",
260
+ "name": "فني كهربائي (خبرة 10+ سنة)",
261
+ "category": "فنيين",
262
+ "subcategory": "كهربائي",
263
+ "hourly_rate": 55,
264
+ "daily_rate": 440,
265
+ "weekly_rate": 2200,
266
+ "monthly_rate": 9000,
267
+ "nationality": "سعودي",
268
+ "availability": "متاح",
269
+ "skills": "تمديدات كهربائية، قراءة مخططات، صيانة",
270
+ "certifications": "دبلوم فني",
271
+ "description": "فني كهربائي ذو خبرة 10+ سنة في التمديدات الكهربائية والصيانة"
272
+ },
273
+ {
274
+ "id": "TECH-006",
275
+ "name": "فني كهربائي (خبرة 5-10 سنوات)",
276
+ "category": "فنيين",
277
+ "subcategory": "كهربائي",
278
+ "hourly_rate": 45,
279
+ "daily_rate": 360,
280
+ "weekly_rate": 1800,
281
+ "monthly_rate": 7500,
282
+ "nationality": "سعودي",
283
+ "availability": "متاح",
284
+ "skills": "تمديدات كهربائية، قراءة مخططات، صيانة",
285
+ "certifications": "دبلوم فني",
286
+ "description": "فني كهربائي ذو خبرة 5-10 سنوات في التمديدات الكهربائية والصيانة"
287
+ },
288
+ {
289
+ "id": "TECH-007",
290
+ "name": "فني ميكانيكي (خبرة 10+ سنة)",
291
+ "category": "فنيين",
292
+ "subcategory": "ميكانيكي",
293
+ "hourly_rate": 55,
294
+ "daily_rate": 440,
295
+ "weekly_rate": 2200,
296
+ "monthly_rate": 9000,
297
+ "nationality": "سعودي",
298
+ "availability": "متاح",
299
+ "skills": "صيانة معدات، تركيب أنظمة ميكانيكية، قراءة مخططات",
300
+ "certifications": "دبلوم فني",
301
+ "description": "فني ميكانيكي ذو خبرة 10+ سنة في صيانة المعدات وتركيب الأنظمة الميكانيكية"
302
+ },
303
+ {
304
+ "id": "TECH-008",
305
+ "name": "فني ميكانيكي (خبرة 5-10 سنوات)",
306
+ "category": "فنيين",
307
+ "subcategory": "ميكانيكي",
308
+ "hourly_rate": 45,
309
+ "daily_rate": 360,
310
+ "weekly_rate": 1800,
311
+ "monthly_rate": 7500,
312
+ "nationality": "سعودي",
313
+ "availability": "متاح",
314
+ "skills": "صيانة معدات، تركيب أنظمة ميكانيكية، قراءة مخططات",
315
+ "certifications": "دبلوم فني",
316
+ "description": "فني ميكانيكي ذو خبرة 5-10 سنوات في صيانة المعدات وتركيب الأنظمة الميكانيكية"
317
+ },
318
+ {
319
+ "id": "TECH-009",
320
+ "name": "فني سباكة (خبرة 10+ سنة)",
321
+ "category": "فنيين",
322
+ "subcategory": "سباكة",
323
+ "hourly_rate": 50,
324
+ "daily_rate": 400,
325
+ "weekly_rate": 2000,
326
+ "monthly_rate": 8000,
327
+ "nationality": "مقيم",
328
+ "availability": "متاح",
329
+ "skills": "تمديدات صحية، تركيب أنظمة صرف، قراءة مخططات",
330
+ "certifications": "شهادة مهنية",
331
+ "description": "فني سباكة ذو خبرة 10+ سنة في التمديدات الصحية وأنظمة الصرف"
332
+ },
333
+ {
334
+ "id": "TECH-010",
335
+ "name": "فني سباكة (خبرة 5-10 سنوات)",
336
+ "category": "فنيين",
337
+ "subcategory": "سباكة",
338
+ "hourly_rate": 40,
339
+ "daily_rate": 320,
340
+ "weekly_rate": 1600,
341
+ "monthly_rate": 6500,
342
+ "nationality": "مقيم",
343
+ "availability": "متاح",
344
+ "skills": "تمديدات صحية، تركيب أنظمة صرف، قراءة مخططات",
345
+ "certifications": "شهادة مهنية",
346
+ "description": "فني سباكة ذو خبرة 5-10 سنوات في التمديدات الصحية وأنظمة الصرف"
347
+ }
348
+ ])
349
+
350
+ # 3. عمال مهرة
351
+ labor_data.extend([
352
+ {
353
+ "id": "SKILL-001",
354
+ "name": "حداد مسلح (خبرة 10+ سنة)",
355
+ "category": "عمال مهرة",
356
+ "subcategory": "حداد",
357
+ "hourly_rate": 40,
358
+ "daily_rate": 320,
359
+ "weekly_rate": 1600,
360
+ "monthly_rate": 6500,
361
+ "nationality": "مقيم",
362
+ "availability": "متاح",
363
+ "skills": "تجهيز وتركيب حديد التسليح، قراءة مخططات",
364
+ "certifications": "شهادة مهنية",
365
+ "description": "حداد مسلح ذو خبرة 10+ سنة في تجهيز وتركيب حديد التسليح للمنشآت الخرسانية"
366
+ },
367
+ {
368
+ "id": "SKILL-002",
369
+ "name": "حداد مسلح (خبرة 5-10 سنوات)",
370
+ "category": "عمال مهرة",
371
+ "subcategory": "حداد",
372
+ "hourly_rate": 35,
373
+ "daily_rate": 280,
374
+ "weekly_rate": 1400,
375
+ "monthly_rate": 5500,
376
+ "nationality": "مقيم",
377
+ "availability": "متاح",
378
+ "skills": "تجهيز وتركيب حديد التسليح، قراءة مخططات",
379
+ "certifications": "شهادة مهنية",
380
+ "description": "حداد مسلح ذو خبرة 5-10 سنوات في تجهيز وتركيب حديد التسليح للمنشآت الخرسانية"
381
+ },
382
+ {
383
+ "id": "SKILL-003",
384
+ "name": "نجار مسلح (خبرة 10+ سنة)",
385
+ "category": "عمال مهرة",
386
+ "subcategory": "نجار",
387
+ "hourly_rate": 40,
388
+ "daily_rate": 320,
389
+ "weekly_rate": 1600,
390
+ "monthly_rate": 6500,
391
+ "nationality": "مقيم",
392
+ "availability": "متاح",
393
+ "skills": "تجهيز وتركيب الشدات الخشبية، قراءة مخططات",
394
+ "certifications": "شهادة مهنية",
395
+ "description": "نجار مسلح ذو خبرة 10+ سنة في تجهيز وتركيب الشدات الخشبية للمنشآت الخرسانية"
396
+ },
397
+ {
398
+ "id": "SKILL-004",
399
+ "name": "نجار مسلح (خبرة 5-10 سنوات)",
400
+ "category": "عمال مهرة",
401
+ "subcategory": "نجار",
402
+ "hourly_rate": 35,
403
+ "daily_rate": 280,
404
+ "weekly_rate": 1400,
405
+ "monthly_rate": 5500,
406
+ "nationality": "مقيم",
407
+ "availability": "متاح",
408
+ "skills": "تجهيز وتركيب الشدات الخشبية، قراءة مخططات",
409
+ "certifications": "شهادة مهنية",
410
+ "description": "نجار مسلح ذو خبرة 5-10 سنوات في تجهيز وتركيب الشدات الخشبية للمنشآت الخرسانية"
411
+ },
412
+ {
413
+ "id": "SKILL-005",
414
+ "name": "بناء (خبرة 10+ سنة)",
415
+ "category": "عمال مهرة",
416
+ "subcategory": "بناء",
417
+ "hourly_rate": 35,
418
+ "daily_rate": 280,
419
+ "weekly_rate": 1400,
420
+ "monthly_rate": 5500,
421
+ "nationality": "مقيم",
422
+ "availability": "متاح",
423
+ "skills": "بناء طابوق، بناء حجر، تشطيبات",
424
+ "certifications": "شهادة مهنية",
425
+ "description": "بناء ذو خبرة 10+ سنة في أعمال البناء بالطابوق والحجر والتشطيبات"
426
+ },
427
+ {
428
+ "id": "SKILL-006",
429
+ "name": "بناء (خبرة 5-10 سنوات)",
430
+ "category": "عمال مهرة",
431
+ "subcategory": "بناء",
432
+ "hourly_rate": 30,
433
+ "daily_rate": 240,
434
+ "weekly_rate": 1200,
435
+ "monthly_rate": 4800,
436
+ "nationality": "مقيم",
437
+ "availability": "متاح",
438
+ "skills": "بناء طابوق، بناء حجر، تشطيبات",
439
+ "certifications": "شهادة مهنية",
440
+ "description": "بناء ذو خبرة 5-10 سنوات في أعمال البناء بالطابوق والحجر والتشطيبات"
441
+ },
442
+ {
443
+ "id": "SKILL-007",
444
+ "name": "لحام (خبرة 10+ سنة)",
445
+ "category": "عمال مهرة",
446
+ "subcategory": "لحام",
447
+ "hourly_rate": 40,
448
+ "daily_rate": 320,
449
+ "weekly_rate": 1600,
450
+ "monthly_rate": 6500,
451
+ "nationality": "مقيم",
452
+ "availability": "متاح",
453
+ "skills": "لحام كهربائي، لحام أرجون، قراءة مخططات",
454
+ "certifications": "شهادة مهنية",
455
+ "description": "لحام ذو خبرة 10+ سنة في أعمال اللحام الكهربائي والأرجون"
456
+ },
457
+ {
458
+ "id": "SKILL-008",
459
+ "name": "لحام (خبرة 5-10 سنوات)",
460
+ "category": "عمال مهرة",
461
+ "subcategory": "لحام",
462
+ "hourly_rate": 35,
463
+ "daily_rate": 280,
464
+ "weekly_rate": 1400,
465
+ "monthly_rate": 5500,
466
+ "nationality": "مقيم",
467
+ "availability": "متاح",
468
+ "skills": "لحام كهربائي، لحام أرجون، قراءة مخططات",
469
+ "certifications": "شهادة مهنية",
470
+ "description": "لحام ذو خبرة 5-10 سنوات في أعمال اللحام الكهربائي والأرجون"
471
+ },
472
+ {
473
+ "id": "SKILL-009",
474
+ "name": "كهربائي (خبرة 10+ سنة)",
475
+ "category": "عمال مهرة",
476
+ "subcategory": "كهربائي",
477
+ "hourly_rate": 35,
478
+ "daily_rate": 280,
479
+ "weekly_rate": 1400,
480
+ "monthly_rate": 5500,
481
+ "nationality": "مقيم",
482
+ "availability": "متاح",
483
+ "skills": "تمديدات كهربائية، تركيب لوحات، صيانة",
484
+ "certifications": "شهادة مهنية",
485
+ "description": "كهربائي ذو خبرة 10+ سنة في التمديدات الكهربائية وتركيب اللوحات والصيانة"
486
+ },
487
+ {
488
+ "id": "SKILL-010",
489
+ "name": "كهربائي (خبرة 5-10 سنوات)",
490
+ "category": "عمال مهرة",
491
+ "subcategory": "كهربائي",
492
+ "hourly_rate": 30,
493
+ "daily_rate": 240,
494
+ "weekly_rate": 1200,
495
+ "monthly_rate": 4800,
496
+ "nationality": "مقيم",
497
+ "availability": "متاح",
498
+ "skills": "تمديدات كهربائية، تركيب لوحات، صيانة",
499
+ "certifications": "شهادة مهنية",
500
+ "description": "كهربائي ذو خبرة 5-10 سنوات في التمديدات الكهربائية وتركيب اللوحات والصيانة"
501
+ }
502
+ ])
503
+
504
+ # 4. عمال عاديين
505
+ labor_data.extend([
506
+ {
507
+ "id": "LABOR-001",
508
+ "name": "عامل عادي (خبرة 5+ سنة)",
509
+ "category": "عمال عاديين",
510
+ "subcategory": "عامل",
511
+ "hourly_rate": 20,
512
+ "daily_rate": 160,
513
+ "weekly_rate": 800,
514
+ "monthly_rate": 3200,
515
+ "nationality": "مقيم",
516
+ "availability": "متاح",
517
+ "skills": "أعمال يدوية، مناولة مواد، تنظيف",
518
+ "certifications": "لا يوجد",
519
+ "description": "عامل عادي ذو خبرة 5+ سنة في الأعمال اليدوية ومناولة المواد والتنظيف"
520
+ },
521
+ {
522
+ "id": "LABOR-002",
523
+ "name": "عامل عادي (خبرة 1-5 سنوات)",
524
+ "category": "عمال عاديين",
525
+ "subcategory": "عامل",
526
+ "hourly_rate": 15,
527
+ "daily_rate": 120,
528
+ "weekly_rate": 600,
529
+ "monthly_rate": 2400,
530
+ "nationality": "مقيم",
531
+ "availability": "متاح",
532
+ "skills": "أعمال يدوية، مناولة مواد، تنظيف",
533
+ "certifications": "لا يوجد",
534
+ "description": "عامل عادي ذو خبرة 1-5 سنوات في الأعمال اليدوية ومناولة المواد والتنظيف"
535
+ },
536
+ {
537
+ "id": "LABOR-003",
538
+ "name": "عامل نظافة (خبرة 3+ سنة)",
539
+ "category": "عمال عاديين",
540
+ "subcategory": "نظافة",
541
+ "hourly_rate": 15,
542
+ "daily_rate": 120,
543
+ "weekly_rate": 600,
544
+ "monthly_rate": 2400,
545
+ "nationality": "مقيم",
546
+ "availability": "متاح",
547
+ "skills": "تنظيف، ترتيب، إزالة مخلفات",
548
+ "certifications": "لا يوجد",
549
+ "description": "عامل نظافة ذو خبرة 3+ سنة في أعمال التنظيف والترتيب وإزالة المخلفات"
550
+ },
551
+ {
552
+ "id": "LABOR-004",
553
+ "name": "عامل نظافة (خبرة 1-3 سنوات)",
554
+ "category": "عمال عاديين",
555
+ "subcategory": "نظافة",
556
+ "hourly_rate": 12,
557
+ "daily_rate": 96,
558
+ "weekly_rate": 480,
559
+ "monthly_rate": 1900,
560
+ "nationality": "مقيم",
561
+ "availability": "متاح",
562
+ "skills": "تنظيف، ترتيب، إزالة مخلفات",
563
+ "certifications": "لا يوجد",
564
+ "description": "عامل نظافة ذو خبرة 1-3 سنوات في أعمال التنظيف والترتيب وإزالة المخلفات"
565
+ },
566
+ {
567
+ "id": "LABOR-005",
568
+ "name": "عامل مساعد (خبرة 3+ سنة)",
569
+ "category": "عمال عاديين",
570
+ "subcategory": "مساعد",
571
+ "hourly_rate": 18,
572
+ "daily_rate": 144,
573
+ "weekly_rate": 720,
574
+ "monthly_rate": 2900,
575
+ "nationality": "مقيم",
576
+ "availability": "متاح",
577
+ "skills": "مساعدة في أعمال البناء، مناولة مواد، تنظيف",
578
+ "certifications": "لا يوجد",
579
+ "description": "عامل مساعد ذو خبرة 3+ سنة في مساعدة العمال المهرة ومناولة المواد"
580
+ },
581
+ {
582
+ "id": "LABOR-006",
583
+ "name": "عامل مساعد (خبرة 1-3 سنوات)",
584
+ "category": "عمال عاديين",
585
+ "subcategory": "مساعد",
586
+ "hourly_rate": 15,
587
+ "daily_rate": 120,
588
+ "weekly_rate": 600,
589
+ "monthly_rate": 2400,
590
+ "nationality": "مقيم",
591
+ "availability": "متاح",
592
+ "skills": "مساعدة في أعمال البناء، مناولة مواد، تنظيف",
593
+ "certifications": "لا يوجد",
594
+ "description": "عامل مساعد ذو خبرة 1-3 سنوات في مساعدة العمال المهرة ومناولة المواد"
595
+ }
596
+ ])
597
+
598
+ # 5. سائقين
599
+ labor_data.extend([
600
+ {
601
+ "id": "DRIVER-001",
602
+ "name": "سائق شاحنة ثقيلة (خبرة 10+ سنة)",
603
+ "category": "سائقين",
604
+ "subcategory": "شاحنة ثقيلة",
605
+ "hourly_rate": 35,
606
+ "daily_rate": 280,
607
+ "weekly_rate": 1400,
608
+ "monthly_rate": 5500,
609
+ "nationality": "سعودي",
610
+ "availability": "متاح",
611
+ "skills": "قيادة شاحنات ثقيلة، صيانة أولية، سجل نظيف",
612
+ "certifications": "رخصة قيادة شاحنات ثقيلة",
613
+ "description": "سائق شاحنة ثقيلة ذو خبرة 10+ سنة في قيادة الشاحنات الثقيلة ونقل المواد"
614
+ },
615
+ {
616
+ "id": "DRIVER-002",
617
+ "name": "سائق شاحنة ثقيلة (خبرة 5-10 سنوات)",
618
+ "category": "سائقين",
619
+ "subcategory": "شاحنة ثقيلة",
620
+ "hourly_rate": 30,
621
+ "daily_rate": 240,
622
+ "weekly_rate": 1200,
623
+ "monthly_rate": 4800,
624
+ "nationality": "سعودي",
625
+ "availability": "متاح",
626
+ "skills": "قيادة شاحنات ثقيلة، صيانة أولية، سجل نظيف",
627
+ "certifications": "رخصة قيادة شاحنات ثقيلة",
628
+ "description": "سائق شاحنة ثقيلة ذو خبرة 5-10 سنوات في قيادة الشاحنات الثقيلة ونقل المواد"
629
+ },
630
+ {
631
+ "id": "DRIVER-003",
632
+ "name": "سائق شاحنة خلاطة خرسانة (خبرة 10+ سنة)",
633
+ "category": "سائقين",
634
+ "subcategory": "خلاطة خرسانة",
635
+ "hourly_rate": 40,
636
+ "daily_rate": 320,
637
+ "weekly_rate": 1600,
638
+ "monthly_rate": 6500,
639
+ "nationality": "سعودي",
640
+ "availability": "متاح",
641
+ "skills": "قيادة خلاطات الخرسانة، صيانة أولية، سجل نظيف",
642
+ "certifications": "رخصة قيادة شاحنات ثقيلة",
643
+ "description": "سائق شاحنة خلاطة خرسانة ذو خبرة 10+ سنة في قيادة خلاطات الخرسانة وصب الخرسانة"
644
+ },
645
+ {
646
+ "id": "DRIVER-004",
647
+ "name": "سائق شاحنة خلاطة خرسانة (خبرة 5-10 سنوات)",
648
+ "category": "سائقين",
649
+ "subcategory": "خلاطة خرسانة",
650
+ "hourly_rate": 35,
651
+ "daily_rate": 280,
652
+ "weekly_rate": 1400,
653
+ "monthly_rate": 5500,
654
+ "nationality": "سعودي",
655
+ "availability": "متاح",
656
+ "skills": "قيادة خلاطات الخرسانة، صيانة أولية، سجل نظيف",
657
+ "certifications": "رخصة قيادة شاحنات ثقيلة",
658
+ "description": "سائق شاحنة خلاطة خرسانة ذو خبرة 5-10 سنوات في قيادة خلاطات الخرسانة وصب الخرسانة"
659
+ },
660
+ {
661
+ "id": "DRIVER-005",
662
+ "name": "سائق شاحنة نقل مياه (خبرة 5+ سنة)",
663
+ "category": "سائقين",
664
+ "subcategory": "نقل مياه",
665
+ "hourly_rate": 30,
666
+ "daily_rate": 240,
667
+ "weekly_rate": 1200,
668
+ "monthly_rate": 4800,
669
+ "nationality": "سعودي",
670
+ "availability": "متاح",
671
+ "skills": "قيادة شاحنات نقل المياه، صيانة أولية، سجل نظيف",
672
+ "certifications": "رخصة قيادة شاحنات ثقيلة",
673
+ "description": "سائق شاحنة نقل مياه ذو خبرة 5+ سنة في قيادة شاحنات نقل المياه وتوزيع المياه"
674
+ },
675
+ {
676
+ "id": "DRIVER-006",
677
+ "name": "سائق سيارة نقل خفيف (خبرة 5+ سنة)",
678
+ "category": "سائقين",
679
+ "subcategory": "نقل خفيف",
680
+ "hourly_rate": 25,
681
+ "daily_rate": 200,
682
+ "weekly_rate": 1000,
683
+ "monthly_rate": 4000,
684
+ "nationality": "سعودي",
685
+ "availability": "متاح",
686
+ "skills": "قيادة سيارات النقل الخفيف، صيانة أولية، سجل نظيف",
687
+ "certifications": "رخصة قيادة",
688
+ "description": "سائق سيارة نقل خفيف ذو خبرة 5+ سنة في قيادة سيارات النقل الخفيف ونقل المواد والعمال"
689
+ }
690
+ ])
691
+
692
+ # 6. مشغلي معدات
693
+ labor_data.extend([
694
+ {
695
+ "id": "OPER-001",
696
+ "name": "مشغل حفارة (خبرة 10+ سنة)",
697
+ "category": "مشغلي معدات",
698
+ "subcategory": "حفارة",
699
+ "hourly_rate": 45,
700
+ "daily_rate": 360,
701
+ "weekly_rate": 1800,
702
+ "monthly_rate": 7500,
703
+ "nationality": "سعودي",
704
+ "availability": "متاح",
705
+ "skills": "تشغيل حفارات، صيانة أولية، حفر دقيق",
706
+ "certifications": "رخصة تشغيل معدات ثقيلة",
707
+ "description": "مشغل حفارة ذو خبرة 10+ سنة في تشغيل الحفارات وأعمال الحفر الدقيق"
708
+ },
709
+ {
710
+ "id": "OPER-002",
711
+ "name": "مشغل حفارة (خبرة 5-10 سنوات)",
712
+ "category": "مشغلي معدات",
713
+ "subcategory": "حفارة",
714
+ "hourly_rate": 40,
715
+ "daily_rate": 320,
716
+ "weekly_rate": 1600,
717
+ "monthly_rate": 6500,
718
+ "nationality": "سعودي",
719
+ "availability": "متاح",
720
+ "skills": "تشغيل حفارات، صيانة أولية، حفر دقيق",
721
+ "certifications": "رخصة تشغيل معدات ثقيلة",
722
+ "description": "مشغل حفارة ذو خبرة 5-10 سنوات في تشغيل الحفارات وأعمال الحفر الدقيق"
723
+ },
724
+ {
725
+ "id": "OPER-003",
726
+ "name": "مشغل لودر (خبرة 10+ سنة)",
727
+ "category": "مشغلي معدات",
728
+ "subcategory": "لودر",
729
+ "hourly_rate": 40,
730
+ "daily_rate": 320,
731
+ "weekly_rate": 1600,
732
+ "monthly_rate": 6500,
733
+ "nationality": "سعودي",
734
+ "availability": "متاح",
735
+ "skills": "تشغيل لودر، صيانة أولية، تحميل دقيق",
736
+ "certifications": "رخصة تشغيل معدات ثقيلة",
737
+ "description": "مشغل لودر ذو خبرة 10+ سنة في تشغيل اللودر وأعمال التحميل الدقيق"
738
+ },
739
+ {
740
+ "id": "OPER-004",
741
+ "name": "مشغل لودر (خبرة 5-10 سنوات)",
742
+ "category": "مشغلي معدات",
743
+ "subcategory": "لودر",
744
+ "hourly_rate": 35,
745
+ "daily_rate": 280,
746
+ "weekly_rate": 1400,
747
+ "monthly_rate": 5500,
748
+ "nationality": "سعودي",
749
+ "availability": "متاح",
750
+ "skills": "تشغيل لودر، صيانة أولية، تحميل دقيق",
751
+ "certifications": "رخصة تشغيل معدات ثقيلة",
752
+ "description": "مشغل لودر ذو خبرة 5-10 سنوات في تشغيل اللودر وأعمال التحميل الدقيق"
753
+ },
754
+ {
755
+ "id": "OPER-005",
756
+ "name": "مشغل بلدوزر (خبرة 10+ سنة)",
757
+ "category": "مشغلي معدات",
758
+ "subcategory": "بلدوزر",
759
+ "hourly_rate": 45,
760
+ "daily_rate": 360,
761
+ "weekly_rate": 1800,
762
+ "monthly_rate": 7500,
763
+ "nationality": "سعودي",
764
+ "availability": "متاح",
765
+ "skills": "تشغيل بلدوزر، صيانة أولية، تسوية دقيقة",
766
+ "certifications": "رخصة تشغيل معدات ثقيلة",
767
+ "description": "مشغل بلدوزر ذو خبرة 10+ سنة في تشغيل البلدوزر وأعمال التسوية الدقيقة"
768
+ },
769
+ {
770
+ "id": "OPER-006",
771
+ "name": "مشغل بلدوزر (خبرة 5-10 سنوات)",
772
+ "category": "مشغلي معدات",
773
+ "subcategory": "بلدوزر",
774
+ "hourly_rate": 40,
775
+ "daily_rate": 320,
776
+ "weekly_rate": 1600,
777
+ "monthly_rate": 6500,
778
+ "nationality": "سعودي",
779
+ "availability": "متاح",
780
+ "skills": "تشغيل بلدوزر، صيانة أولية، تسوية دقيقة",
781
+ "certifications": "رخصة تشغيل معدات ثقيلة",
782
+ "description": "مشغل بلدوزر ذو خبرة 5-10 سنوات في تشغيل البلدوزر وأعمال التسوية الدقيقة"
783
+ },
784
+ {
785
+ "id": "OPER-007",
786
+ "name": "مشغل جريدر (خبرة 10+ سنة)",
787
+ "category": "مشغلي معدات",
788
+ "subcategory": "جريدر",
789
+ "hourly_rate": 45,
790
+ "daily_rate": 360,
791
+ "weekly_rate": 1800,
792
+ "monthly_rate": 7500,
793
+ "nationality": "سعودي",
794
+ "availability": "متاح",
795
+ "skills": "تشغيل جريدر، صيانة أولية، تسوية دقيقة",
796
+ "certifications": "رخصة تشغيل معدات ثقيلة",
797
+ "description": "مشغل جريدر ذو خبرة 10+ سنة في تشغيل الجريدر وأعمال التسوية الدقيقة للطرق"
798
+ },
799
+ {
800
+ "id": "OPER-008",
801
+ "name": "مشغل جريدر (خبرة 5-10 سنوات)",
802
+ "category": "مشغلي معدات",
803
+ "subcategory": "جريدر",
804
+ "hourly_rate": 40,
805
+ "daily_rate": 320,
806
+ "weekly_rate": 1600,
807
+ "monthly_rate": 6500,
808
+ "nationality": "سعودي",
809
+ "availability": "متاح",
810
+ "skills": "تشغيل جريدر، صيانة أولية، تسوية دقيقة",
811
+ "certifications": "رخصة تشغيل معدات ثقيلة",
812
+ "description": "مشغل جريدر ذو خبرة 5-10 سنوات في تشغيل الجريدر وأعمال التسوية الدقيقة للطرق"
813
+ },
814
+ {
815
+ "id": "OPER-009",
816
+ "name": "مشغل رافعة (خبرة 10+ سنة)",
817
+ "category": "مشغلي معدات",
818
+ "subcategory": "رافعة",
819
+ "hourly_rate": 50,
820
+ "daily_rate": 400,
821
+ "weekly_rate": 2000,
822
+ "monthly_rate": 8000,
823
+ "nationality": "سعودي",
824
+ "availability": "متاح",
825
+ "skills": "تشغيل رافعات، صيانة أولية، رفع دقيق",
826
+ "certifications": "رخصة تشغيل رافعات",
827
+ "description": "مشغل رافعة ذو خبرة 10+ سنة في تشغيل الرافعات وأعمال الرفع الدقيق"
828
+ },
829
+ {
830
+ "id": "OPER-010",
831
+ "name": "مشغل رافعة (خبرة 5-10 سنوات)",
832
+ "category": "مشغلي معدات",
833
+ "subcategory": "رافعة",
834
+ "hourly_rate": 45,
835
+ "daily_rate": 360,
836
+ "weekly_rate": 1800,
837
+ "monthly_rate": 7000,
838
+ "nationality": "سعودي",
839
+ "availability": "متاح",
840
+ "skills": "تشغيل رافعات، صيانة أولية، رفع دقيق",
841
+ "certifications": "رخصة تشغيل رافعات",
842
+ "description": "مشغل رافعة ذو خبرة 5-10 سنوات في تشغيل الرافعات وأعمال الرفع الدقيق"
843
+ }
844
+ ])
845
+
846
+ # 7. إداريين
847
+ labor_data.extend([
848
+ {
849
+ "id": "ADMIN-001",
850
+ "name": "مدير مشروع (خبرة 15+ سنة)",
851
+ "category": "إداريين",
852
+ "subcategory": "مدير مشروع",
853
+ "hourly_rate": 200,
854
+ "daily_rate": 1600,
855
+ "weekly_rate": 8000,
856
+ "monthly_rate": 32000,
857
+ "nationality": "سعودي",
858
+ "availability": "متاح",
859
+ "skills": "إدارة مشاريع، تخطيط، متابعة، إعداد تقارير",
860
+ "certifications": "PMP، عضوية الهيئة السعودية للمهندسين",
861
+ "description": "مدير مشروع ذو خبرة 15+ سنة في إدارة مشاريع البنية التحتية والطرق والجسور"
862
+ },
863
+ {
864
+ "id": "ADMIN-002",
865
+ "name": "مدير مشروع (خبرة 10-15 سنة)",
866
+ "category": "إداريين",
867
+ "subcategory": "مدير مشروع",
868
+ "hourly_rate": 150,
869
+ "daily_rate": 1200,
870
+ "weekly_rate": 6000,
871
+ "monthly_rate": 25000,
872
+ "nationality": "سعودي",
873
+ "availability": "متاح",
874
+ "skills": "إدارة مشاريع، تخطيط، متابعة، إعداد تقارير",
875
+ "certifications": "PMP، عضوية الهيئة السعودية للمهندسين",
876
+ "description": "مدير مشروع ذو خبرة 10-15 سنة في إدارة مشاريع البنية التحتية والطرق والجسور"
877
+ },
878
+ {
879
+ "id": "ADMIN-003",
880
+ "name": "مهندس تخطيط (خبرة 10+ سنة)",
881
+ "category": "إداريين",
882
+ "subcategory": "تخطيط",
883
+ "hourly_rate": 120,
884
+ "daily_rate": 960,
885
+ "weekly_rate": 4800,
886
+ "monthly_rate": 20000,
887
+ "nationality": "سعودي",
888
+ "availability": "متاح",
889
+ "skills": "تخطيط مشاريع، جدولة، متابعة، إعداد تقارير",
890
+ "certifications": "PMP، Primavera P6، MS Project",
891
+ "description": "مهندس تخطيط ذو خبرة 10+ سنة في تخطيط وجدولة ومتابعة مشاريع البنية التحتية"
892
+ },
893
+ {
894
+ "id": "ADMIN-004",
895
+ "name": "مهندس تخطيط (خبرة 5-10 سنوات)",
896
+ "category": "إداريين",
897
+ "subcategory": "تخطيط",
898
+ "hourly_rate": 100,
899
+ "daily_rate": 800,
900
+ "weekly_rate": 4000,
901
+ "monthly_rate": 16000,
902
+ "nationality": "سعودي",
903
+ "availability": "متاح",
904
+ "skills": "تخطيط مشاريع، جدولة، متابعة، إعداد تقارير",
905
+ "certifications": "Primavera P6، MS Project",
906
+ "description": "مهندس تخطيط ذو خبرة 5-10 سنوات في تخطيط وجدولة ومتابعة مشاريع البنية التحتية"
907
+ },
908
+ {
909
+ "id": "ADMIN-005",
910
+ "name": "مهندس مراقبة جودة (خبرة 10+ سنة)",
911
+ "category": "إداريين",
912
+ "subcategory": "مراقبة جودة",
913
+ "hourly_rate": 120,
914
+ "daily_rate": 960,
915
+ "weekly_rate": 4800,
916
+ "monthly_rate": 20000,
917
+ "nationality": "سعودي",
918
+ "availability": "متاح",
919
+ "skills": "مراقبة جودة، اختبارات، إعداد تقارير، تطبيق معايير",
920
+ "certifications": "ISO 9001، عضوية الهيئة السعودية للمهندسين",
921
+ "description": "مهندس مراقبة جودة ذو خبرة 10+ سنة في مراقبة جودة مشاريع البنية التحتية"
922
+ },
923
+ {
924
+ "id": "ADMIN-006",
925
+ "name": "مهندس مراقبة جودة (خبرة 5-10 سنوات)",
926
+ "category": "إداريين",
927
+ "subcategory": "مراقبة جودة",
928
+ "hourly_rate": 100,
929
+ "daily_rate": 800,
930
+ "weekly_rate": 4000,
931
+ "monthly_rate": 16000,
932
+ "nationality": "سعودي",
933
+ "availability": "متاح",
934
+ "skills": "مراقبة جودة، اختبارات، إعداد تقارير، تطبيق معايير",
935
+ "certifications": "ISO 9001، عضوية الهيئة السعودية للمهندسين",
936
+ "description": "مهندس مراقبة جودة ذو خبرة 5-10 سنوات في مراقبة جودة مشاريع البنية التحتية"
937
+ },
938
+ {
939
+ "id": "ADMIN-007",
940
+ "name": "مهندس سلامة (خبرة 10+ سنة)",
941
+ "category": "إداريين",
942
+ "subcategory": "سلامة",
943
+ "hourly_rate": 120,
944
+ "daily_rate": 960,
945
+ "weekly_rate": 4800,
946
+ "monthly_rate": 20000,
947
+ "nationality": "سعودي",
948
+ "availability": "متاح",
949
+ "skills": "إدارة السلامة، تدريب، تفتيش، إعداد تقارير",
950
+ "certifications": "NEBOSH، OSHA",
951
+ "description": "مهندس سلامة ذو خبرة 10+ سنة في إدارة السلامة في مشاريع البنية التحتية"
952
+ },
953
+ {
954
+ "id": "ADMIN-008",
955
+ "name": "مهندس سلامة (خبرة 5-10 سنوات)",
956
+ "category": "إداريين",
957
+ "subcategory": "سلامة",
958
+ "hourly_rate": 100,
959
+ "daily_rate": 800,
960
+ "weekly_rate": 4000,
961
+ "monthly_rate": 16000,
962
+ "nationality": "سعودي",
963
+ "availability": "متاح",
964
+ "skills": "إدارة السلامة، تدريب، تفتيش، إعداد تقارير",
965
+ "certifications": "NEBOSH، OSHA",
966
+ "description": "مهندس سلامة ذو خبرة 5-10 سنوات في إدارة السلامة في مشاريع البنية التحتية"
967
+ },
968
+ {
969
+ "id": "ADMIN-009",
970
+ "name": "محاسب مشاريع (خبرة 10+ سنة)",
971
+ "category": "إداريين",
972
+ "subcategory": "محاسبة",
973
+ "hourly_rate": 100,
974
+ "daily_rate": 800,
975
+ "weekly_rate": 4000,
976
+ "monthly_rate": 16000,
977
+ "nationality": "سع��دي",
978
+ "availability": "متاح",
979
+ "skills": "محاسبة مشاريع، إعداد تقارير مالية، متابعة مصروفات",
980
+ "certifications": "SOCPA",
981
+ "description": "محاسب مشاريع ذو خبرة 10+ سنة في محاسبة مشاريع البنية التحتية"
982
+ },
983
+ {
984
+ "id": "ADMIN-010",
985
+ "name": "محاسب مشاريع (خبرة 5-10 سنوات)",
986
+ "category": "إداريين",
987
+ "subcategory": "محاسبة",
988
+ "hourly_rate": 80,
989
+ "daily_rate": 640,
990
+ "weekly_rate": 3200,
991
+ "monthly_rate": 13000,
992
+ "nationality": "سعودي",
993
+ "availability": "متاح",
994
+ "skills": "محاسبة مشاريع، إعداد تقارير مالية، متابعة مصروفات",
995
+ "certifications": "SOCPA",
996
+ "description": "محاسب مشاريع ذو خبرة 5-10 سنوات في محاسبة مشاريع البنية التحتية"
997
+ }
998
+ ])
999
+
1000
+ # تخزين البيانات في حالة الجلسة
1001
+ st.session_state.labor_catalog = pd.DataFrame(labor_data)
1002
+
1003
+ def render(self):
1004
+ """عرض واجهة كتالوج العمالة"""
1005
+
1006
+ st.markdown("## كتالوج العمالة والمهندسين")
1007
+
1008
+ # إنشاء تبويبات لعرض الكتالوج
1009
+ tabs = st.tabs([
1010
+ "عرض الكتالوج",
1011
+ "إضافة عامل",
1012
+ "تحليل الأسعار",
1013
+ "استيراد/تصدير"
1014
+ ])
1015
+
1016
+ with tabs[0]:
1017
+ self._render_catalog_view_tab()
1018
+
1019
+ with tabs[1]:
1020
+ self._render_add_labor_tab()
1021
+
1022
+ with tabs[2]:
1023
+ self._render_price_analysis_tab()
1024
+
1025
+ with tabs[3]:
1026
+ self._render_import_export_tab()
1027
+
1028
+ def _render_catalog_view_tab(self):
1029
+ """عرض تبويب عرض الكتالوج"""
1030
+
1031
+ st.markdown("### عرض كتالوج العمالة والمهندسين")
1032
+
1033
+ # استخراج البيانات
1034
+ labor_df = st.session_state.labor_catalog
1035
+
1036
+ # إنشاء فلاتر للعرض
1037
+ col1, col2, col3 = st.columns(3)
1038
+
1039
+ with col1:
1040
+ # فلتر حسب الفئة
1041
+ categories = ["الكل"] + sorted(labor_df["category"].unique().tolist())
1042
+ selected_category = st.selectbox("اختر فئة العمالة", categories)
1043
+
1044
+ with col2:
1045
+ # فلتر حسب الفئة الفرعية
1046
+ if selected_category != "الكل":
1047
+ subcategories = ["الكل"] + sorted(labor_df[labor_df["category"] == selected_category]["subcategory"].unique().tolist())
1048
+ else:
1049
+ subcategories = ["الكل"] + sorted(labor_df["subcategory"].unique().tolist())
1050
+
1051
+ selected_subcategory = st.selectbox("اختر التخصص", subcategories)
1052
+
1053
+ with col3:
1054
+ # فلتر حسب الجنسية
1055
+ nationalities = ["الكل"] + sorted(labor_df["nationality"].unique().tolist())
1056
+ selected_nationality = st.selectbox("اختر الجنسية", nationalities)
1057
+
1058
+ # تطبيق الفلاتر
1059
+ filtered_df = labor_df.copy()
1060
+
1061
+ if selected_category != "الكل":
1062
+ filtered_df = filtered_df[filtered_df["category"] == selected_category]
1063
+
1064
+ if selected_subcategory != "الكل":
1065
+ filtered_df = filtered_df[filtered_df["subcategory"] == selected_subcategory]
1066
+
1067
+ if selected_nationality != "الكل":
1068
+ filtered_df = filtered_df[filtered_df["nationality"] == selected_nationality]
1069
+
1070
+ # عرض البيانات
1071
+ if not filtered_df.empty:
1072
+ # عرض عدد النتائج
1073
+ st.info(f"تم العثور على {len(filtered_df)} عامل/مهندس")
1074
+
1075
+ # عرض العمالة في شكل بطاقات
1076
+ for i, (_, labor) in enumerate(filtered_df.iterrows()):
1077
+ col1, col2 = st.columns([1, 3])
1078
+
1079
+ with col1:
1080
+ # عرض صورة العامل (استخدام صورة افتراضية)
1081
+ st.image("https://via.placeholder.com/150", caption=labor["name"])
1082
+
1083
+ with col2:
1084
+ # عرض معلومات العامل
1085
+ st.markdown(f"**{labor['name']}** (الكود: {labor['id']})")
1086
+ st.markdown(f"الفئة: {labor['category']} - {labor['subcategory']}")
1087
+ st.markdown(f"الجنسية: {labor['nationality']} | الحالة: {labor['availability']}")
1088
+ st.markdown(f"الأسعار: {labor['hourly_rate']} ريال/ساعة | {labor['daily_rate']} ريال/يوم | {labor['monthly_rate']} ريال/شهر")
1089
+ st.markdown(f"المهارات: {labor['skills']}")
1090
+
1091
+ # إضافة زر لعرض التفاصيل
1092
+ if st.button(f"عرض التفاصيل الكاملة", key=f"details_{labor['id']}"):
1093
+ st.session_state.selected_labor = labor['id']
1094
+ self._show_labor_details(labor)
1095
+
1096
+ st.markdown("---")
1097
+ else:
1098
+ st.warning("لا توجد عمالة تطابق معايير البحث")
1099
+
1100
+ def _show_labor_details(self, labor):
1101
+ """عرض تفاصيل العامل"""
1102
+
1103
+ st.markdown(f"## تفاصيل العامل/المهندس: {labor['name']}")
1104
+
1105
+ col1, col2 = st.columns([1, 2])
1106
+
1107
+ with col1:
1108
+ # عرض صورة العامل (استخدام صورة افتراضية)
1109
+ st.image("https://via.placeholder.com/300", caption=labor["name"])
1110
+
1111
+ with col2:
1112
+ # عرض المعلومات الأساسية
1113
+ st.markdown("### المعلومات الأساسية")
1114
+ st.markdown(f"**الكود:** {labor['id']}")
1115
+ st.markdown(f"**الفئة:** {labor['category']} - {labor['subcategory']}")
1116
+ st.markdown(f"**الجنسية:** {labor['nationality']}")
1117
+ st.markdown(f"**الحالة:** {labor['availability']}")
1118
+ st.markdown(f"**المهارات:** {labor['skills']}")
1119
+ st.markdown(f"**الشهادات:** {labor['certifications']}")
1120
+ st.markdown(f"**الوصف:** {labor['description']}")
1121
+
1122
+ # عرض معلومات الأسعار
1123
+ st.markdown("### معلومات الأسعار")
1124
+
1125
+ price_data = {
1126
+ "وحدة الزمن": ["ساعة", "يوم", "أسبوع", "شهر"],
1127
+ "السعر (ريال)": [
1128
+ labor['hourly_rate'],
1129
+ labor['daily_rate'],
1130
+ labor['weekly_rate'],
1131
+ labor['monthly_rate']
1132
+ ]
1133
+ }
1134
+
1135
+ price_df = pd.DataFrame(price_data)
1136
+ st.dataframe(price_df, use_container_width=True)
1137
+
1138
+ # إضافة زر للتعديل
1139
+ if st.button("تعديل بيانات العامل"):
1140
+ st.session_state.edit_labor = labor['id']
1141
+ # هنا يمكن إضافة منطق التعديل
1142
+
1143
+ def _render_add_labor_tab(self):
1144
+ """عرض تبويب إضافة عامل"""
1145
+
1146
+ st.markdown("### إضافة عامل/مهندس جديد")
1147
+
1148
+ # استخراج البيانات
1149
+ labor_df = st.session_state.labor_catalog
1150
+
1151
+ # إنشاء نموذج إضافة عامل
1152
+ with st.form("add_labor_form"):
1153
+ st.markdown("#### المعلومات الأساسية")
1154
+
1155
+ # الصف الأول
1156
+ col1, col2 = st.columns(2)
1157
+ with col1:
1158
+ labor_id = st.text_input("كود العامل", value=f"LABOR-{len(labor_df) + 1:03d}")
1159
+ labor_name = st.text_input("اسم العامل", placeholder="مثال: مهندس مدني (خبرة 10+ سنة)")
1160
+
1161
+ with col2:
1162
+ # استخراج الفئات والفئات الفرعية الموجودة
1163
+ categories = sorted(labor_df["category"].unique().tolist())
1164
+ labor_category = st.selectbox("فئة العامل", categories)
1165
+
1166
+ # استخراج الفئات الفرعية بناءً على الفئة المختارة
1167
+ subcategories = sorted(labor_df[labor_df["category"] == labor_category]["subcategory"].unique().tolist())
1168
+ labor_subcategory = st.selectbox("التخصص", subcategories)
1169
+
1170
+ # الصف الثاني
1171
+ col1, col2 = st.columns(2)
1172
+ with col1:
1173
+ labor_nationality = st.selectbox("الجنسية", ["سعودي", "مقيم"])
1174
+ with col2:
1175
+ labor_availability = st.selectbox("الحالة", ["متاح", "غير متاح"])
1176
+
1177
+ # الصف الثالث - الأسعار
1178
+ st.markdown("#### معلومات الأسعار")
1179
+
1180
+ col1, col2, col3, col4 = st.columns(4)
1181
+ with col1:
1182
+ labor_hourly_rate = st.number_input("السعر بالساعة (ريال)", min_value=0, step=5)
1183
+ with col2:
1184
+ labor_daily_rate = st.number_input("السعر باليوم (ريال)", min_value=0, step=40)
1185
+ with col3:
1186
+ labor_weekly_rate = st.number_input("السعر بالأسبوع (ريال)", min_value=0, step=200)
1187
+ with col4:
1188
+ labor_monthly_rate = st.number_input("السعر بالشهر (ريال)", min_value=0, step=1000)
1189
+
1190
+ # المهارات والشه��دات
1191
+ st.markdown("#### المهارات والشهادات")
1192
+
1193
+ labor_skills = st.text_area("المهارات", placeholder="مثال: إدارة مشاريع، تصميم إنشائي، إشراف على التنفيذ")
1194
+ labor_certifications = st.text_input("الشهادات", placeholder="مثال: عضوية الهيئة السعودية للمهندسين، PMP")
1195
+
1196
+ # وصف العامل
1197
+ labor_description = st.text_area("وصف العامل", placeholder="أدخل وصفاً تفصيلياً للعامل/المهندس")
1198
+
1199
+ # زر الإضافة
1200
+ submit_button = st.form_submit_button("إضافة العامل")
1201
+
1202
+ if submit_button:
1203
+ # التحقق من البيانات
1204
+ if not labor_name or not labor_category or not labor_subcategory:
1205
+ st.error("يرجى إدخال المعلومات الأساسية للعامل")
1206
+ else:
1207
+ # إنشاء عامل جديد
1208
+ new_labor = {
1209
+ "id": labor_id,
1210
+ "name": labor_name,
1211
+ "category": labor_category,
1212
+ "subcategory": labor_subcategory,
1213
+ "hourly_rate": labor_hourly_rate,
1214
+ "daily_rate": labor_daily_rate,
1215
+ "weekly_rate": labor_weekly_rate,
1216
+ "monthly_rate": labor_monthly_rate,
1217
+ "nationality": labor_nationality,
1218
+ "availability": labor_availability,
1219
+ "skills": labor_skills,
1220
+ "certifications": labor_certifications,
1221
+ "description": labor_description
1222
+ }
1223
+
1224
+ # إضافة العامل إلى الكتالوج
1225
+ st.session_state.labor_catalog = pd.concat([
1226
+ st.session_state.labor_catalog,
1227
+ pd.DataFrame([new_labor])
1228
+ ], ignore_index=True)
1229
+
1230
+ st.success(f"تمت إضافة العامل {labor_name} بنجاح!")
1231
+
1232
+ def _render_price_analysis_tab(self):
1233
+ """عرض تبويب تحليل الأسعار"""
1234
+
1235
+ st.markdown("### تحليل أسعار العمالة والمهندسين")
1236
+
1237
+ # استخراج البيانات
1238
+ labor_df = st.session_state.labor_catalog
1239
+
1240
+ # تحليل متوسط الأسعار حسب الفئة
1241
+ st.markdown("#### متوسط الأسعار حسب الفئة")
1242
+
1243
+ # حساب متوسط الأسعار لكل فئة
1244
+ category_prices = labor_df.groupby("category").agg({
1245
+ "hourly_rate": "mean",
1246
+ "daily_rate": "mean",
1247
+ "monthly_rate": "mean"
1248
+ }).reset_index()
1249
+
1250
+ # تغيير أسماء الأعمدة
1251
+ category_prices.columns = ["الفئة", "متوسط السعر بالساعة", "متوسط السعر باليوم", "متوسط السعر بالشهر"]
1252
+
1253
+ # عرض الجدول
1254
+ st.dataframe(category_prices, use_container_width=True)
1255
+
1256
+ # إنشاء رسم بياني للمقارنة
1257
+ st.markdown("#### مقارنة متوسط الأسعار الشهرية حسب الفئة")
1258
+
1259
+ fig = px.bar(
1260
+ category_prices,
1261
+ x="الفئة",
1262
+ y="متوسط السعر بالشهر",
1263
+ title="متوسط أسعار العمالة والمهندسين الشهرية حسب الفئة",
1264
+ color="الفئة",
1265
+ text_auto=True
1266
+ )
1267
+
1268
+ st.plotly_chart(fig, use_container_width=True)
1269
+
1270
+ # تحليل توزيع العمالة حسب الجنسية
1271
+ st.markdown("#### توزيع العمالة حسب الجنسية")
1272
+
1273
+ # حساب عدد العمالة حسب الجنسية
1274
+ nationality_counts = labor_df["nationality"].value_counts().reset_index()
1275
+ nationality_counts.columns = ["الجنسية", "عدد العمالة"]
1276
+
1277
+ # إنشاء رسم بياني دائري
1278
+ fig = px.pie(
1279
+ nationality_counts,
1280
+ values="عدد العمالة",
1281
+ names="الجنسية",
1282
+ title="توزيع العمالة حسب الجنسية",
1283
+ color="الجنسية"
1284
+ )
1285
+
1286
+ st.plotly_chart(fig, use_container_width=True)
1287
+
1288
+ # تحليل متوسط الأسعار حسب التخصص
1289
+ st.markdown("#### متوسط الأسعار حسب التخصص")
1290
+
1291
+ # اختيار الفئة للتحليل
1292
+ selected_category_for_analysis = st.selectbox(
1293
+ "اختر الفئة للتحليل",
1294
+ sorted(labor_df["category"].unique().tolist())
1295
+ )
1296
+
1297
+ # حساب متوسط الأسعار لكل تخصص ضمن الفئة المختارة
1298
+ subcategory_prices = labor_df[labor_df["category"] == selected_category_for_analysis].groupby("subcategory").agg({
1299
+ "hourly_rate": "mean",
1300
+ "daily_rate": "mean",
1301
+ "monthly_rate": "mean"
1302
+ }).reset_index()
1303
+
1304
+ # تغيير أسماء الأعمدة
1305
+ subcategory_prices.columns = ["التخصص", "متوسط السعر بالساعة", "متوسط السعر باليوم", "متوسط السعر بالشهر"]
1306
+
1307
+ # عرض الجدول
1308
+ st.dataframe(subcategory_prices, use_container_width=True)
1309
+
1310
+ # إنشاء رسم بياني للمقارنة
1311
+ fig = px.bar(
1312
+ subcategory_prices,
1313
+ x="التخصص",
1314
+ y="متوسط السعر بالشهر",
1315
+ title=f"متوسط أسعار {selected_category_for_analysis} الشهرية حسب التخصص",
1316
+ color="التخصص",
1317
+ text_auto=True
1318
+ )
1319
+
1320
+ st.plotly_chart(fig, use_container_width=True)
1321
+
1322
+ # حاسبة تكاليف العمالة للمشروع
1323
+ st.markdown("#### حاسبة تكاليف العمالة للمشروع")
1324
+
1325
+ with st.form("project_labor_calculator"):
1326
+ st.markdown("أدخل العمالة المطلوبة للمشروع")
1327
+
1328
+ # اختيار العمالة
1329
+ selected_labor = st.multiselect(
1330
+ "اختر العمالة",
1331
+ options=labor_df["name"].tolist(),
1332
+ format_func=lambda x: f"{x} ({labor_df[labor_df['name'] == x]['id'].iloc[0]})"
1333
+ )
1334
+
1335
+ # اختيار وحدة الزمن
1336
+ time_unit = st.radio("اختر وحدة الزمن", ["ساعة", "يوم", "أسبوع", "شهر"], horizontal=True)
1337
+
1338
+ # إنشاء حقول إدخال الكميات
1339
+ quantities = {}
1340
+
1341
+ if selected_labor:
1342
+ st.markdown("أدخل المدة المطلوبة")
1343
+
1344
+ for labor_name in selected_labor:
1345
+ quantities[labor_name] = st.number_input(
1346
+ f"{labor_name}",
1347
+ min_value=0,
1348
+ step=1,
1349
+ key=f"qty_{labor_df[labor_df['name'] == labor_name]['id'].iloc[0]}"
1350
+ )
1351
+
1352
+ # زر الحساب
1353
+ calculate_button = st.form_submit_button("حساب التكاليف")
1354
+
1355
+ if calculate_button:
1356
+ if not selected_labor:
1357
+ st.error("يرجى اختيار عامل واحد على الأقل")
1358
+ else:
1359
+ # حساب التكاليف
1360
+ project_costs = []
1361
+
1362
+ for labor_name in selected_labor:
1363
+ labor = labor_df[labor_df["name"] == labor_name].iloc[0]
1364
+ quantity = quantities[labor_name]
1365
+
1366
+ if quantity > 0:
1367
+ # تحديد السعر بناءً على وحدة الزمن
1368
+ if time_unit == "ساعة":
1369
+ rate = labor["hourly_rate"]
1370
+ rate_name = "السعر بالساعة"
1371
+ elif time_unit == "يوم":
1372
+ rate = labor["daily_rate"]
1373
+ rate_name = "السعر باليوم"
1374
+ elif time_unit == "أسبوع":
1375
+ rate = labor["weekly_rate"]
1376
+ rate_name = "السعر بالأسبوع"
1377
+ else: # شهر
1378
+ rate = labor["monthly_rate"]
1379
+ rate_name = "السعر بالشهر"
1380
+
1381
+ cost = rate * quantity
1382
+
1383
+ project_costs.append({
1384
+ "العامل": labor_name,
1385
+ "الكود": labor["id"],
1386
+ "الفئة": labor["category"],
1387
+ "التخصص": labor["subcategory"],
1388
+ rate_name: rate,
1389
+ f"عدد {time_unit}ات": quantity,
1390
+ "التكلفة الإجمالية": cost
1391
+ })
1392
+
1393
+ if project_costs:
1394
+ # عرض النتائج
1395
+ project_costs_df = pd.DataFrame(project_costs)
1396
+ st.dataframe(project_costs_df, use_container_width=True)
1397
+
1398
+ # حساب إجمالي التكاليف
1399
+ total_cost = project_costs_df["التكلفة الإجمالية"].sum()
1400
+ st.metric("إجمالي تكاليف العمالة للمشروع", f"{total_cost:,.2f} ريال")
1401
+ else:
1402
+ st.warning("يرجى إدخال مدة أكبر من صفر")
1403
+
1404
+ def _render_import_export_tab(self):
1405
+ """عرض تبويب استيراد/تصدير"""
1406
+
1407
+ st.markdown("### استيراد وتصدير بيانات العمالة والمهندسين")
1408
+
1409
+ # استيراد البيانات
1410
+ st.markdown("#### استيراد البيانات")
1411
+
1412
+ uploaded_file = st.file_uploader("اختر ملف Excel لاستيراد بيانات العمالة", type=["xlsx", "xls"])
1413
+
1414
+ if uploaded_file is not None:
1415
+ try:
1416
+ # قراءة الملف
1417
+ imported_df = pd.read_excel(uploaded_file)
1418
+
1419
+ # عرض البيانات المستوردة
1420
+ st.dataframe(imported_df, use_container_width=True)
1421
+
1422
+ # زر الاستيراد
1423
+ if st.button("استيراد البيانات"):
1424
+ # التحقق من وجود الأعمدة المطلوبة
1425
+ required_columns = ["id", "name", "category", "subcategory", "hourly_rate", "daily_rate", "weekly_rate", "monthly_rate"]
1426
+
1427
+ if all(col in imported_df.columns for col in required_columns):
1428
+ # دمج البيانات المستوردة مع البيانات الحالية
1429
+ st.session_state.labor_catalog = pd.concat([
1430
+ st.session_state.labor_catalog,
1431
+ imported_df
1432
+ ], ignore_index=True).drop_duplicates(subset=["id"])
1433
+
1434
+ st.success(f"تم استيراد {len(imported_df)} عامل/مهندس بنجاح!")
1435
+ else:
1436
+ st.error("الملف المستورد لا يحتوي على الأعمدة المطلوبة")
1437
+
1438
+ except Exception as e:
1439
+ st.error(f"حدث خطأ أثناء استيراد الملف: {str(e)}")
1440
+
1441
+ # تصدير البيانات
1442
+ st.markdown("#### تصدير البيانات")
1443
+
1444
+ # اختيار تنسيق التصدير
1445
+ export_format = st.radio("اختر تنسيق التصدير", ["Excel", "CSV", "JSON"], horizontal=True)
1446
+
1447
+ if st.button("تصدير البيانات"):
1448
+ # استخراج البيانات
1449
+ labor_df = st.session_state.labor_catalog
1450
+
1451
+ # تصدير البيانات حسب التنسيق المختار
1452
+ if export_format == "Excel":
1453
+ # تصدير إلى Excel
1454
+ output = io.BytesIO()
1455
+ with pd.ExcelWriter(output, engine="openpyxl") as writer:
1456
+ labor_df.to_excel(writer, index=False, sheet_name="Labor")
1457
+
1458
+ # تحميل الملف
1459
+ st.download_button(
1460
+ label="تنزيل ملف Excel",
1461
+ data=output.getvalue(),
1462
+ file_name="labor_catalog.xlsx",
1463
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1464
+ )
1465
+
1466
+ elif export_format == "CSV":
1467
+ # تصدير إلى CSV
1468
+ csv_data = labor_df.to_csv(index=False)
1469
+
1470
+ # تحميل الملف
1471
+ st.download_button(
1472
+ label="تنزيل ملف CSV",
1473
+ data=csv_data,
1474
+ file_name="labor_catalog.csv",
1475
+ mime="text/csv"
1476
+ )
1477
+
1478
+ else: # JSON
1479
+ # تصدير إلى JSON
1480
+ json_data = labor_df.to_json(orient="records", force_ascii=False)
1481
+
1482
+ # تحميل الملف
1483
+ st.download_button(
1484
+ label="تنزيل ملف JSON",
1485
+ data=json_data,
1486
+ file_name="labor_catalog.json",
1487
+ mime="application/json"
1488
+ )
1489
+
1490
+ def get_labor_by_id(self, labor_id):
1491
+ """الحصول على عامل بواسطة الكود"""
1492
+
1493
+ labor_df = st.session_state.labor_catalog
1494
+ labor = labor_df[labor_df["id"] == labor_id]
1495
+
1496
+ if not labor.empty:
1497
+ return labor.iloc[0].to_dict()
1498
+
1499
+ return None
1500
+
1501
+ def get_labor_by_category(self, category):
1502
+ """الحصول على العمالة حسب الفئة"""
1503
+
1504
+ labor_df = st.session_state.labor_catalog
1505
+ labor = labor_df[labor_df["category"] == category]
1506
+
1507
+ if not labor.empty:
1508
+ return labor.to_dict(orient="records")
1509
+
1510
+ return []
1511
+
1512
+ def calculate_labor_cost(self, labor_id, quantity, time_unit="day"):
1513
+ """حساب تكلفة العامل بناءً على الكمية ووحدة الزمن"""
1514
+
1515
+ labor = self.get_labor_by_id(labor_id)
1516
+
1517
+ if labor:
1518
+ if time_unit == "hour":
1519
+ return labor["hourly_rate"] * quantity
1520
+ elif time_unit == "day":
1521
+ return labor["daily_rate"] * quantity
1522
+ elif time_unit == "week":
1523
+ return labor["weekly_rate"] * quantity
1524
+ elif time_unit == "month":
1525
+ return labor["monthly_rate"] * quantity
1526
+
1527
+ return 0
pricing_system/modules/catalogs/materials_catalog.py ADDED
@@ -0,0 +1,1930 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ كتالوج المواد - وحدة إدارة مواد المقاولات
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import plotly.express as px
9
+ import os
10
+ import json
11
+ from datetime import datetime
12
+ import io
13
+
14
+ class MaterialsCatalog:
15
+ """كتالوج المواد"""
16
+
17
+ def __init__(self):
18
+ """تهيئة كتالوج المواد"""
19
+
20
+ # تهيئة حالة الجلسة لكتالوج المواد
21
+ if 'materials_catalog' not in st.session_state:
22
+ # إنشاء بيانات افتراضية للمواد
23
+ self._initialize_materials_catalog()
24
+
25
+ def _initialize_materials_catalog(self):
26
+ """تهيئة بيانات كتالوج المواد"""
27
+
28
+ # تعريف فئات المواد
29
+ material_categories = [
30
+ "مواد الخرسانة",
31
+ "مواد البناء",
32
+ "مواد الطرق",
33
+ "مواد الصرف الصحي",
34
+ "مواد العزل",
35
+ "مواد التشطيبات",
36
+ "مواد كهربائية",
37
+ "مواد ميكانيكية",
38
+ "مواد الري والزراعة",
39
+ "مواد متنوعة"
40
+ ]
41
+
42
+ # إنشاء قائمة المواد
43
+ materials_data = []
44
+
45
+ # 1. مواد الخرسانة
46
+ materials_data.extend([
47
+ {
48
+ "id": "MAT-001",
49
+ "name": "أسمنت بورتلاندي عادي",
50
+ "category": "مواد الخرسانة",
51
+ "subcategory": "أسمنت",
52
+ "unit": "طن",
53
+ "price": 600,
54
+ "supplier": "شركة أسمنت اليمامة",
55
+ "origin": "محلي",
56
+ "lead_time": 2,
57
+ "min_order": 10,
58
+ "description": "أسمنت بورتلاندي عادي مطابق للمواصفات السعودية",
59
+ "image_url": "https://example.com/cement.jpg"
60
+ },
61
+ {
62
+ "id": "MAT-002",
63
+ "name": "أسمنت مقاوم للكبريتات",
64
+ "category": "مواد الخرسانة",
65
+ "subcategory": "أسمنت",
66
+ "unit": "طن",
67
+ "price": 650,
68
+ "supplier": "شركة أسمنت ينبع",
69
+ "origin": "محلي",
70
+ "lead_time": 2,
71
+ "min_order": 10,
72
+ "description": "أسمنت مقاوم للكبريتات للمناطق ذات التربة الكبريتية",
73
+ "image_url": "https://example.com/srcement.jpg"
74
+ },
75
+ {
76
+ "id": "MAT-003",
77
+ "name": "رمل خشن",
78
+ "category": "مواد الخرسانة",
79
+ "subcategory": "ركام",
80
+ "unit": "م3",
81
+ "price": 80,
82
+ "supplier": "كسارات الرياض",
83
+ "origin": "محلي",
84
+ "lead_time": 1,
85
+ "min_order": 20,
86
+ "description": "رمل خشن للخرسانة مطابق للمواصفات",
87
+ "image_url": "https://example.com/sand.jpg"
88
+ },
89
+ {
90
+ "id": "MAT-004",
91
+ "name": "بحص مقاس 3/4 بوصة",
92
+ "category": "مواد الخرسانة",
93
+ "subcategory": "ركام",
94
+ "unit": "م3",
95
+ "price": 120,
96
+ "supplier": "كسارات الرياض",
97
+ "origin": "محلي",
98
+ "lead_time": 1,
99
+ "min_order": 20,
100
+ "description": "بحص مقاس 3/4 بوصة للخرسانة مطابق للمواصفات",
101
+ "image_url": "https://example.com/gravel.jpg"
102
+ },
103
+ {
104
+ "id": "MAT-005",
105
+ "name": "بحص مقاس 3/8 بوصة",
106
+ "category": "مواد الخرسانة",
107
+ "subcategory": "ركام",
108
+ "unit": "م3",
109
+ "price": 130,
110
+ "supplier": "كسارات الرياض",
111
+ "origin": "محلي",
112
+ "lead_time": 1,
113
+ "min_order": 20,
114
+ "description": "بحص مقاس 3/8 بوصة للخرسانة مطابق للمواصفات",
115
+ "image_url": "https://example.com/gravel2.jpg"
116
+ },
117
+ {
118
+ "id": "MAT-006",
119
+ "name": "ماء",
120
+ "category": "مواد الخرسانة",
121
+ "subcategory": "سوائل",
122
+ "unit": "م3",
123
+ "price": 5,
124
+ "supplier": "متعدد",
125
+ "origin": "محلي",
126
+ "lead_time": 1,
127
+ "min_order": 10,
128
+ "description": "ماء صالح للخلط في الخرسانة",
129
+ "image_url": "https://example.com/water.jpg"
130
+ },
131
+ {
132
+ "id": "MAT-007",
133
+ "name": "إضافة ملدنة للخرسانة",
134
+ "category": "مواد الخرسانة",
135
+ "subcategory": "إضافات",
136
+ "unit": "لتر",
137
+ "price": 15,
138
+ "supplier": "سيكا",
139
+ "origin": "مستورد",
140
+ "lead_time": 7,
141
+ "min_order": 200,
142
+ "description": "إضافة ملدنة لتحسين قابلية تشغيل الخرسانة",
143
+ "image_url": "https://example.com/plasticizer.jpg"
144
+ },
145
+ {
146
+ "id": "MAT-008",
147
+ "name": "إضافة مؤخرة للشك",
148
+ "category": "مواد الخرسانة",
149
+ "subcategory": "إضافات",
150
+ "unit": "لتر",
151
+ "price": 18,
152
+ "supplier": "سيكا",
153
+ "origin": "مستورد",
154
+ "lead_time": 7,
155
+ "min_order": 200,
156
+ "description": "إضافة مؤخرة للشك للصب في الأجواء الحارة",
157
+ "image_url": "https://example.com/retarder.jpg"
158
+ },
159
+ {
160
+ "id": "MAT-009",
161
+ "name": "إضافة معجلة للشك",
162
+ "category": "مواد الخرسانة",
163
+ "subcategory": "إضافات",
164
+ "unit": "لتر",
165
+ "price": 20,
166
+ "supplier": "سيكا",
167
+ "origin": "مستورد",
168
+ "lead_time": 7,
169
+ "min_order": 200,
170
+ "description": "إضافة معجلة للشك للصب في الأجواء الباردة",
171
+ "image_url": "https://example.com/accelerator.jpg"
172
+ },
173
+ {
174
+ "id": "MAT-010",
175
+ "name": "ألياف بوليبروبلين",
176
+ "category": "مواد الخرسانة",
177
+ "subcategory": "إضافات",
178
+ "unit": "كجم",
179
+ "price": 35,
180
+ "supplier": "سيكا",
181
+ "origin": "مستورد",
182
+ "lead_time": 7,
183
+ "min_order": 100,
184
+ "description": "ألياف بوليبروبلين لتقليل التشققات في الخرسانة",
185
+ "image_url": "https://example.com/fibers.jpg"
186
+ }
187
+ ])
188
+
189
+ # 2. مواد البناء
190
+ materials_data.extend([
191
+ {
192
+ "id": "MAT-011",
193
+ "name": "طابوق أسمنتي مقاس 20×20×40 سم",
194
+ "category": "مواد البناء",
195
+ "subcategory": "طابوق",
196
+ "unit": "قطعة",
197
+ "price": 3.5,
198
+ "supplier": "مصنع الرياض للطابوق",
199
+ "origin": "محلي",
200
+ "lead_time": 3,
201
+ "min_order": 1000,
202
+ "description": "طابوق أسمنتي مقاس 20×20×40 سم للجدران الخارجية",
203
+ "image_url": "https://example.com/block.jpg"
204
+ },
205
+ {
206
+ "id": "MAT-012",
207
+ "name": "طابوق أسمنتي مقاس 15×20×40 سم",
208
+ "category": "مواد البناء",
209
+ "subcategory": "طابوق",
210
+ "unit": "قطعة",
211
+ "price": 3,
212
+ "supplier": "مصنع الرياض للطابوق",
213
+ "origin": "محلي",
214
+ "lead_time": 3,
215
+ "min_order": 1000,
216
+ "description": "طابوق أسمنتي مقاس 15×20×40 سم للجدران الداخلية",
217
+ "image_url": "https://example.com/block2.jpg"
218
+ },
219
+ {
220
+ "id": "MAT-013",
221
+ "name": "طابوق أسمنتي مقاس 10×20×40 سم",
222
+ "category": "مواد البناء",
223
+ "subcategory": "طابوق",
224
+ "unit": "قطعة",
225
+ "price": 2.5,
226
+ "supplier": "مصنع الرياض للطابوق",
227
+ "origin": "محلي",
228
+ "lead_time": 3,
229
+ "min_order": 1000,
230
+ "description": "طابوق أسمنتي مقاس 10×20×40 سم للجدران الداخلية",
231
+ "image_url": "https://example.com/block3.jpg"
232
+ },
233
+ {
234
+ "id": "MAT-014",
235
+ "name": "حديد تسليح قطر 8 مم",
236
+ "category": "مواد البناء",
237
+ "subcategory": "حديد تسليح",
238
+ "unit": "طن",
239
+ "price": 3200,
240
+ "supplier": "شركة حديد الراجحي",
241
+ "origin": "محلي",
242
+ "lead_time": 5,
243
+ "min_order": 5,
244
+ "description": "حديد تسليح قطر 8 مم مطابق للمواصفات السعودية",
245
+ "image_url": "https://example.com/rebar8.jpg"
246
+ },
247
+ {
248
+ "id": "MAT-015",
249
+ "name": "حديد تسليح قطر 10 مم",
250
+ "category": "مواد البناء",
251
+ "subcategory": "حديد تسليح",
252
+ "unit": "طن",
253
+ "price": 3150,
254
+ "supplier": "شركة حديد الراجحي",
255
+ "origin": "محلي",
256
+ "lead_time": 5,
257
+ "min_order": 5,
258
+ "description": "حديد تسليح قطر 10 مم مطابق للمواصفات السعودية",
259
+ "image_url": "https://example.com/rebar10.jpg"
260
+ },
261
+ {
262
+ "id": "MAT-016",
263
+ "name": "حديد تسليح قطر 12 مم",
264
+ "category": "مواد البناء",
265
+ "subcategory": "حديد تسليح",
266
+ "unit": "طن",
267
+ "price": 3100,
268
+ "supplier": "شركة حديد الراجحي",
269
+ "origin": "محلي",
270
+ "lead_time": 5,
271
+ "min_order": 5,
272
+ "description": "حديد تسليح قطر 12 مم مطابق للمواصفات السعودية",
273
+ "image_url": "https://example.com/rebar12.jpg"
274
+ },
275
+ {
276
+ "id": "MAT-017",
277
+ "name": "حديد تسليح قطر 16 مم",
278
+ "category": "مواد البناء",
279
+ "subcategory": "حديد تسليح",
280
+ "unit": "طن",
281
+ "price": 3050,
282
+ "supplier": "شركة حديد الراجحي",
283
+ "origin": "محلي",
284
+ "lead_time": 5,
285
+ "min_order": 5,
286
+ "description": "حديد تسليح قطر 16 مم مطابق للمواصفات السعودية",
287
+ "image_url": "https://example.com/rebar16.jpg"
288
+ },
289
+ {
290
+ "id": "MAT-018",
291
+ "name": "حديد تسليح قطر 20 مم",
292
+ "category": "مواد البناء",
293
+ "subcategory": "حديد تسليح",
294
+ "unit": "طن",
295
+ "price": 3000,
296
+ "supplier": "شركة حديد الراجحي",
297
+ "origin": "محلي",
298
+ "lead_time": 5,
299
+ "min_order": 5,
300
+ "description": "حديد تسليح قطر 20 مم مطابق للمواصفات السعودية",
301
+ "image_url": "https://example.com/rebar20.jpg"
302
+ },
303
+ {
304
+ "id": "MAT-019",
305
+ "name": "حديد تسليح قطر 25 مم",
306
+ "category": "مواد البناء",
307
+ "subcategory": "حديد تسليح",
308
+ "unit": "طن",
309
+ "price": 2950,
310
+ "supplier": "شركة حديد الراجحي",
311
+ "origin": "محلي",
312
+ "lead_time": 5,
313
+ "min_order": 5,
314
+ "description": "حديد تسليح قطر 25 مم مطابق للمواصفات السعودية",
315
+ "image_url": "https://example.com/rebar25.jpg"
316
+ },
317
+ {
318
+ "id": "MAT-020",
319
+ "name": "شبك حديد ملحوم 6 مم",
320
+ "category": "مواد البناء",
321
+ "subcategory": "حديد تسليح",
322
+ "unit": "م2",
323
+ "price": 25,
324
+ "supplier": "شركة حديد الراجحي",
325
+ "origin": "محلي",
326
+ "lead_time": 5,
327
+ "min_order": 100,
328
+ "description": "شبك حديد ملحوم 6 مم للأرضيات والأسقف",
329
+ "image_url": "https://example.com/wiremesh.jpg"
330
+ }
331
+ ])
332
+
333
+ # 3. مواد الطرق
334
+ materials_data.extend([
335
+ {
336
+ "id": "MAT-021",
337
+ "name": "بيس كورس",
338
+ "category": "مواد الطرق",
339
+ "subcategory": "طبقات أساس",
340
+ "unit": "م3",
341
+ "price": 70,
342
+ "supplier": "كسارات الرياض",
343
+ "origin": "محلي",
344
+ "lead_time": 2,
345
+ "min_order": 50,
346
+ "description": "مادة بيس كورس لطبقات الأساس في الطرق",
347
+ "image_url": "https://example.com/basecourse.jpg"
348
+ },
349
+ {
350
+ "id": "MAT-022",
351
+ "name": "ساب بيس",
352
+ "category": "مواد الطرق",
353
+ "subcategory": "طبقات أساس",
354
+ "unit": "م3",
355
+ "price": 60,
356
+ "supplier": "كسارات الرياض",
357
+ "origin": "محلي",
358
+ "lead_time": 2,
359
+ "min_order": 50,
360
+ "description": "مادة ساب بيس لطبقات ما تحت الأساس في الطرق",
361
+ "image_url": "https://example.com/subbase.jpg"
362
+ },
363
+ {
364
+ "id": "MAT-023",
365
+ "name": "بيتومين MC-70",
366
+ "category": "مواد الطرق",
367
+ "subcategory": "بيتومين",
368
+ "unit": "لتر",
369
+ "price": 3.5,
370
+ "supplier": "أرامكو",
371
+ "origin": "محلي",
372
+ "lead_time": 7,
373
+ "min_order": 10000,
374
+ "description": "بيتومين MC-70 للطبقة التأسيسية",
375
+ "image_url": "https://example.com/bitumen1.jpg"
376
+ },
377
+ {
378
+ "id": "MAT-024",
379
+ "name": "بيتومين RC-250",
380
+ "category": "مواد الطرق",
381
+ "subcategory": "بيتومين",
382
+ "unit": "لتر",
383
+ "price": 3.8,
384
+ "supplier": "أرامكو",
385
+ "origin": "محلي",
386
+ "lead_time": 7,
387
+ "min_order": 10000,
388
+ "description": "بيتومين RC-250 للطبقة اللاصقة",
389
+ "image_url": "https://example.com/bitumen2.jpg"
390
+ },
391
+ {
392
+ "id": "MAT-025",
393
+ "name": "خلطة إسفلتية ساخنة",
394
+ "category": "مواد الطرق",
395
+ "subcategory": "إسفلت",
396
+ "unit": "طن",
397
+ "price": 250,
398
+ "supplier": "مصنع الإسفلت المركزي",
399
+ "origin": "محلي",
400
+ "lead_time": 1,
401
+ "min_order": 20,
402
+ "description": "خلطة إسفلتية ساخنة لطبقات الرصف",
403
+ "image_url": "https://example.com/asphalt.jpg"
404
+ },
405
+ {
406
+ "id": "MAT-026",
407
+ "name": "بردورات خرسانية",
408
+ "category": "مواد الطرق",
409
+ "subcategory": "بردورات",
410
+ "unit": "متر طولي",
411
+ "price": 35,
412
+ "supplier": "مصنع الخرسانة الجاهزة",
413
+ "origin": "محلي",
414
+ "lead_time": 3,
415
+ "min_order": 100,
416
+ "description": "بردورات خرسانية مقاس 15×30×50 سم",
417
+ "image_url": "https://example.com/curb.jpg"
418
+ },
419
+ {
420
+ "id": "MAT-027",
421
+ "name": "انترلوك خرساني",
422
+ "category": "مواد الطرق",
423
+ "subcategory": "رصف",
424
+ "unit": "م2",
425
+ "price": 45,
426
+ "supplier": "مصنع الخرسانة الجاهزة",
427
+ "origin": "محلي",
428
+ "lead_time": 3,
429
+ "min_order": 100,
430
+ "description": "انترلوك خرساني سماكة 8 سم للأرصفة",
431
+ "image_url": "https://example.com/interlock.jpg"
432
+ },
433
+ {
434
+ "id": "MAT-028",
435
+ "name": "دهان خطوط طرق أبيض",
436
+ "category": "مواد الطرق",
437
+ "subcategory": "دهانات",
438
+ "unit": "لتر",
439
+ "price": 25,
440
+ "supplier": "شركة الدهانات السعودية",
441
+ "origin": "محلي",
442
+ "lead_time": 5,
443
+ "min_order": 200,
444
+ "description": "دهان خطوط طرق أبيض عاكس",
445
+ "image_url": "https://example.com/roadpaint.jpg"
446
+ },
447
+ {
448
+ "id": "MAT-029",
449
+ "name": "دهان خطوط طرق أصفر",
450
+ "category": "مواد الطرق",
451
+ "subcategory": "دهانات",
452
+ "unit": "لتر",
453
+ "price": 25,
454
+ "supplier": "شركة الدهانات السعودية",
455
+ "origin": "محلي",
456
+ "lead_time": 5,
457
+ "min_order": 200,
458
+ "description": "دهان خطوط طرق أصفر عاكس",
459
+ "image_url": "https://example.com/roadpaint2.jpg"
460
+ },
461
+ {
462
+ "id": "MAT-030",
463
+ "name": "حبيبات زجاجية عاكسة",
464
+ "category": "مواد الطرق",
465
+ "subcategory": "دهانات",
466
+ "unit": "كجم",
467
+ "price": 15,
468
+ "supplier": "شركة الدهانات السعودية",
469
+ "origin": "مستورد",
470
+ "lead_time": 10,
471
+ "min_order": 100,
472
+ "description": "حبيبات زجاجية عاك��ة لدهانات الطرق",
473
+ "image_url": "https://example.com/reflective.jpg"
474
+ }
475
+ ])
476
+
477
+ # 4. مواد الصرف الصحي
478
+ materials_data.extend([
479
+ {
480
+ "id": "MAT-031",
481
+ "name": "أنابيب PVC قطر 110 مم",
482
+ "category": "مواد الصرف الصحي",
483
+ "subcategory": "أنابيب",
484
+ "unit": "متر طولي",
485
+ "price": 35,
486
+ "supplier": "الشركة السعودية للأنابيب",
487
+ "origin": "محلي",
488
+ "lead_time": 5,
489
+ "min_order": 100,
490
+ "description": "أنابيب PVC قطر 110 مم للصرف الصحي الداخلي",
491
+ "image_url": "https://example.com/pvcpipe.jpg"
492
+ },
493
+ {
494
+ "id": "MAT-032",
495
+ "name": "أنابيب PVC قطر 160 مم",
496
+ "category": "مواد الصرف الصحي",
497
+ "subcategory": "أنابيب",
498
+ "unit": "متر طولي",
499
+ "price": 55,
500
+ "supplier": "الشركة السعودية للأنابيب",
501
+ "origin": "محلي",
502
+ "lead_time": 5,
503
+ "min_order": 100,
504
+ "description": "أنابيب PVC قطر 160 مم للصرف الصحي الخارجي",
505
+ "image_url": "https://example.com/pvcpipe2.jpg"
506
+ },
507
+ {
508
+ "id": "MAT-033",
509
+ "name": "أنابيب UPVC قطر 200 مم",
510
+ "category": "مواد الصرف الصحي",
511
+ "subcategory": "أنابيب",
512
+ "unit": "متر طولي",
513
+ "price": 80,
514
+ "supplier": "الشركة السعودية للأنابيب",
515
+ "origin": "محلي",
516
+ "lead_time": 5,
517
+ "min_order": 50,
518
+ "description": "أنابيب UPVC قطر 200 مم للصرف الصحي الرئيسي",
519
+ "image_url": "https://example.com/upvcpipe.jpg"
520
+ },
521
+ {
522
+ "id": "MAT-034",
523
+ "name": "أنابيب UPVC قطر 315 مم",
524
+ "category": "مواد الصرف الصحي",
525
+ "subcategory": "أنابيب",
526
+ "unit": "متر طولي",
527
+ "price": 150,
528
+ "supplier": "الشركة السعودية للأنابيب",
529
+ "origin": "محلي",
530
+ "lead_time": 7,
531
+ "min_order": 50,
532
+ "description": "أنابيب UPVC قطر 315 مم للصرف الصحي الرئيسي",
533
+ "image_url": "https://example.com/upvcpipe2.jpg"
534
+ },
535
+ {
536
+ "id": "MAT-035",
537
+ "name": "أنابيب خرسانية مسلحة قطر 600 مم",
538
+ "category": "مواد الصرف الصحي",
539
+ "subcategory": "أنابيب",
540
+ "unit": "متر طولي",
541
+ "price": 450,
542
+ "supplier": "مصنع الأنابيب الخرسانية",
543
+ "origin": "محلي",
544
+ "lead_time": 10,
545
+ "min_order": 20,
546
+ "description": "أنابيب خرسانية مسلحة قطر 600 مم للصرف الصحي الرئيسي",
547
+ "image_url": "https://example.com/concretepipe.jpg"
548
+ },
549
+ {
550
+ "id": "MAT-036",
551
+ "name": "أنابيب خرسانية مسلحة قطر 1000 مم",
552
+ "category": "مواد الصرف الصحي",
553
+ "subcategory": "أنابيب",
554
+ "unit": "متر طولي",
555
+ "price": 850,
556
+ "supplier": "مصنع الأنابيب الخرسانية",
557
+ "origin": "محلي",
558
+ "lead_time": 14,
559
+ "min_order": 10,
560
+ "description": "أنابيب خرسانية مسلحة قطر 1000 مم للصرف الصحي الرئيسي",
561
+ "image_url": "https://example.com/concretepipe2.jpg"
562
+ },
563
+ {
564
+ "id": "MAT-037",
565
+ "name": "غرفة تفتيش خرسانية مسبقة الصب 80×80 سم",
566
+ "category": "مواد الصرف الصحي",
567
+ "subcategory": "غرف تفتيش",
568
+ "unit": "قطعة",
569
+ "price": 650,
570
+ "supplier": "مصنع الخرسانة الجاهزة",
571
+ "origin": "محلي",
572
+ "lead_time": 7,
573
+ "min_order": 5,
574
+ "description": "غرفة تفتيش خرسانية مسبقة الصب 80×80 سم",
575
+ "image_url": "https://example.com/manhole.jpg"
576
+ },
577
+ {
578
+ "id": "MAT-038",
579
+ "name": "غطاء غرفة تفتيش حديد ز��ر ثقيل",
580
+ "category": "مواد الصرف الصحي",
581
+ "subcategory": "غرف تفتيش",
582
+ "unit": "قطعة",
583
+ "price": 450,
584
+ "supplier": "مصنع المسبوكات الحديدية",
585
+ "origin": "محلي",
586
+ "lead_time": 7,
587
+ "min_order": 10,
588
+ "description": "غطاء غرفة تفتيش حديد زهر ثقيل للطرق",
589
+ "image_url": "https://example.com/manholecoverheavy.jpg"
590
+ },
591
+ {
592
+ "id": "MAT-039",
593
+ "name": "غطاء غرفة تفتيش حديد زهر خفيف",
594
+ "category": "مواد الصرف الصحي",
595
+ "subcategory": "غرف تفتيش",
596
+ "unit": "قطعة",
597
+ "price": 300,
598
+ "supplier": "مصنع المسبوكات الحديدية",
599
+ "origin": "محلي",
600
+ "lead_time": 7,
601
+ "min_order": 10,
602
+ "description": "غطاء غرفة تفتيش حديد زهر خفيف للأرصفة",
603
+ "image_url": "https://example.com/manholecoverlight.jpg"
604
+ },
605
+ {
606
+ "id": "MAT-040",
607
+ "name": "مصافي مطر حديد زهر",
608
+ "category": "مواد الصرف الصحي",
609
+ "subcategory": "مصافي",
610
+ "unit": "قطعة",
611
+ "price": 350,
612
+ "supplier": "مصنع المسبوكات الحديدية",
613
+ "origin": "محلي",
614
+ "lead_time": 7,
615
+ "min_order": 10,
616
+ "description": "مصافي مطر حديد زهر للطرق",
617
+ "image_url": "https://example.com/gully.jpg"
618
+ }
619
+ ])
620
+
621
+ # 5. مواد العزل
622
+ materials_data.extend([
623
+ {
624
+ "id": "MAT-041",
625
+ "name": "رولات عزل مائي بيتوميني 4 مم",
626
+ "category": "مواد العزل",
627
+ "subcategory": "عزل مائي",
628
+ "unit": "م2",
629
+ "price": 25,
630
+ "supplier": "شركة العزل السعودية",
631
+ "origin": "محلي",
632
+ "lead_time": 5,
633
+ "min_order": 200,
634
+ "description": "رولات عزل مائي بيتوميني 4 مم للأسطح",
635
+ "image_url": "https://example.com/waterproofing.jpg"
636
+ },
637
+ {
638
+ "id": "MAT-042",
639
+ "name": "دهان عزل مائي أكريليك",
640
+ "category": "مواد العزل",
641
+ "subcategory": "عزل مائي",
642
+ "unit": "لتر",
643
+ "price": 18,
644
+ "supplier": "شركة العزل السعودية",
645
+ "origin": "محلي",
646
+ "lead_time": 3,
647
+ "min_order": 100,
648
+ "description": "دهان عزل مائي أكريليك للأسطح",
649
+ "image_url": "https://example.com/waterproofingpaint.jpg"
650
+ },
651
+ {
652
+ "id": "MAT-043",
653
+ "name": "مادة عزل مائي بوليمرية",
654
+ "category": "مواد العزل",
655
+ "subcategory": "عزل مائي",
656
+ "unit": "كجم",
657
+ "price": 35,
658
+ "supplier": "سيكا",
659
+ "origin": "مستورد",
660
+ "lead_time": 7,
661
+ "min_order": 50,
662
+ "description": "مادة عزل مائي بوليمرية للحمامات والمطابخ",
663
+ "image_url": "https://example.com/polymerwaterproofing.jpg"
664
+ },
665
+ {
666
+ "id": "MAT-044",
667
+ "name": "ألواح بوليسترين للعزل الحراري 5 سم",
668
+ "category": "مواد العزل",
669
+ "subcategory": "عزل حراري",
670
+ "unit": "م2",
671
+ "price": 20,
672
+ "supplier": "شركة العزل السعودية",
673
+ "origin": "محلي",
674
+ "lead_time": 5,
675
+ "min_order": 100,
676
+ "description": "ألواح بوليسترين للعزل الحراري سماكة 5 سم",
677
+ "image_url": "https://example.com/polystyrene.jpg"
678
+ },
679
+ {
680
+ "id": "MAT-045",
681
+ "name": "ألواح صوف صخري 5 سم",
682
+ "category": "مواد العزل",
683
+ "subcategory": "عزل حراري",
684
+ "unit": "م2",
685
+ "price": 35,
686
+ "supplier": "شركة العزل السعودية",
687
+ "origin": "مستورد",
688
+ "lead_time": 10,
689
+ "min_order": 100,
690
+ "description": "ألواح صوف صخري للعزل الحراري والصوتي سماكة 5 سم",
691
+ "image_url": "https://example.com/rockwool.jpg"
692
+ },
693
+ {
694
+ "id": "MAT-046",
695
+ "name": "ألواح عزل صوتي 2 سم",
696
+ "category": "مواد العزل",
697
+ "subcategory": "عزل صوتي",
698
+ "unit": "م2",
699
+ "price": 45,
700
+ "supplier": "شركة العزل السعودية",
701
+ "origin": "مستورد",
702
+ "lead_time": 10,
703
+ "min_order": 50,
704
+ "description": "ألواح عزل صوتي سماكة 2 سم",
705
+ "image_url": "https://example.com/acousticinsulation.jpg"
706
+ },
707
+ {
708
+ "id": "MAT-047",
709
+ "name": "شريط عزل مطاطي",
710
+ "category": "مواد العزل",
711
+ "subcategory": "عزل مائي",
712
+ "unit": "متر طولي",
713
+ "price": 15,
714
+ "supplier": "سيكا",
715
+ "origin": "مستورد",
716
+ "lead_time": 7,
717
+ "min_order": 100,
718
+ "description": "شريط عزل مطاطي للفواصل الإنشائية",
719
+ "image_url": "https://example.com/rubberstrip.jpg"
720
+ },
721
+ {
722
+ "id": "MAT-048",
723
+ "name": "مادة حشو فواصل بوليوريثان",
724
+ "category": "مواد العزل",
725
+ "subcategory": "عزل مائي",
726
+ "unit": "لتر",
727
+ "price": 40,
728
+ "supplier": "سيكا",
729
+ "origin": "مستورد",
730
+ "lead_time": 7,
731
+ "min_order": 20,
732
+ "description": "مادة حشو فواصل بوليوريثان للفواصل الإنشائية",
733
+ "image_url": "https://example.com/sealant.jpg"
734
+ }
735
+ ])
736
+
737
+ # 6. مواد التشطيبات
738
+ materials_data.extend([
739
+ {
740
+ "id": "MAT-049",
741
+ "name": "بلاط سيراميك للأرضيات 60×60 سم",
742
+ "category": "مواد التشطيبات",
743
+ "subcategory": "بلاط",
744
+ "unit": "م2",
745
+ "price": 65,
746
+ "supplier": "شركة السيراميك السعودية",
747
+ "origin": "محلي",
748
+ "lead_time": 7,
749
+ "min_order": 100,
750
+ "description": "بلاط سيراميك للأرضيات مقاس 60×60 سم",
751
+ "image_url": "https://example.com/ceramictile.jpg"
752
+ },
753
+ {
754
+ "id": "MAT-050",
755
+ "name": "بلاط بورسلين للأرضيات 60×60 سم",
756
+ "category": "مواد التشطيبات",
757
+ "subcategory": "بلاط",
758
+ "unit": "م2",
759
+ "price": 85,
760
+ "supplier": "شركة السيراميك السعودية",
761
+ "origin": "محلي",
762
+ "lead_time": 7,
763
+ "min_order": 100,
764
+ "description": "بلاط بورسلين للأرضيات مقاس 60×60 سم",
765
+ "image_url": "https://example.com/porcelaintile.jpg"
766
+ },
767
+ {
768
+ "id": "MAT-051",
769
+ "name": "بلاط سيراميك للجدران 30×60 سم",
770
+ "category": "مواد التشطيبات",
771
+ "subcategory": "بلاط",
772
+ "unit": "م2",
773
+ "price": 60,
774
+ "supplier": "شركة السيراميك السعودية",
775
+ "origin": "محلي",
776
+ "lead_time": 7,
777
+ "min_order": 100,
778
+ "description": "بلاط سيراميك للجدران مقاس 30×60 سم",
779
+ "image_url": "https://example.com/walltile.jpg"
780
+ },
781
+ {
782
+ "id": "MAT-052",
783
+ "name": "رخام أبيض كرارة",
784
+ "category": "مواد التشطيبات",
785
+ "subcategory": "رخام",
786
+ "unit": "م2",
787
+ "price": 350,
788
+ "supplier": "شركة الرخام السعودية",
789
+ "origin": "مستورد",
790
+ "lead_time": 14,
791
+ "min_order": 20,
792
+ "description": "رخام أبيض كرارة للأرضيات سماكة 2 سم",
793
+ "image_url": "https://example.com/marble.jpg"
794
+ },
795
+ {
796
+ "id": "MAT-053",
797
+ "name": "جرانيت أسود",
798
+ "category": "مواد التشطيبات",
799
+ "subcategory": "جرانيت",
800
+ "unit": "م2",
801
+ "price": 450,
802
+ "supplier": "شركة الرخام السعودية",
803
+ "origin": "مستورد",
804
+ "lead_time": 14,
805
+ "min_order": 20,
806
+ "description": "جرانيت أسود للأرضيات سماكة 2 سم",
807
+ "image_url": "https://example.com/granite.jpg"
808
+ },
809
+ {
810
+ "id": "MAT-054",
811
+ "name": "دهان أساس للجدران الداخلية",
812
+ "category": "مواد التشطيبات",
813
+ "subcategory": "دهانات",
814
+ "unit": "لتر",
815
+ "price": 15,
816
+ "supplier": "شركة الدهانات السعودية",
817
+ "origin": "محلي",
818
+ "lead_time": 3,
819
+ "min_order": 100,
820
+ "description": "دهان أساس للجدران الداخلية",
821
+ "image_url": "https://example.com/primer.jpg"
822
+ },
823
+ {
824
+ "id": "MAT-055",
825
+ "name": "دهان بلاستيك للجدران الداخلية",
826
+ "category": "مواد التشطيبات",
827
+ "subcategory": "دهانات",
828
+ "unit": "لتر",
829
+ "price": 25,
830
+ "supplier": "شركة الدهانات السعودية",
831
+ "origin": "محلي",
832
+ "lead_time": 3,
833
+ "min_order": 100,
834
+ "description": "دهان بلاستيك للجدران الداخلية",
835
+ "image_url": "https://example.com/paint.jpg"
836
+ },
837
+ {
838
+ "id": "MAT-056",
839
+ "name": "دهان خارجي مقاوم للعوامل الجوية",
840
+ "category": "مواد التشطيبات",
841
+ "subcategory": "دهانات",
842
+ "unit": "لتر",
843
+ "price": 35,
844
+ "supplier": "شركة الدهانات السعودية",
845
+ "origin": "محلي",
846
+ "lead_time": 3,
847
+ "min_order": 100,
848
+ "description": "دهان خارجي مقاوم للعوامل الجوية",
849
+ "image_url": "https://example.com/exteriorpaint.jpg"
850
+ },
851
+ {
852
+ "id": "MAT-057",
853
+ "name": "ألواح جبس 12 مم",
854
+ "category": "مواد التشطيبات",
855
+ "subcategory": "جبس",
856
+ "unit": "م2",
857
+ "price": 18,
858
+ "supplier": "شركة الجبس السعودية",
859
+ "origin": "محلي",
860
+ "lead_time": 5,
861
+ "min_order": 100,
862
+ "description": "ألواح جبس سماكة 12 مم للأسقف والجدران",
863
+ "image_url": "https://example.com/gypsum.jpg"
864
+ },
865
+ {
866
+ "id": "MAT-058",
867
+ "name": "ألواح جبس مقاومة للرطوبة 12 مم",
868
+ "category": "مواد التشطيبات",
869
+ "subcategory": "جبس",
870
+ "unit": "م2",
871
+ "price": 25,
872
+ "supplier": "شركة الجبس السعودية",
873
+ "origin": "محلي",
874
+ "lead_time": 5,
875
+ "min_order": 100,
876
+ "description": "ألواح جبس مقاومة للرطوبة سماكة 12 مم للحمامات والمطابخ",
877
+ "image_url": "https://example.com/moistureresistantgypsum.jpg"
878
+ }
879
+ ])
880
+
881
+ # 7. مواد كهربائية
882
+ materials_data.extend([
883
+ {
884
+ "id": "MAT-059",
885
+ "name": "كابل نحاس 1×10 مم2",
886
+ "category": "مواد كهربائية",
887
+ "subcategory": "كابلات",
888
+ "unit": "متر طولي",
889
+ "price": 15,
890
+ "supplier": "الشركة السعودية للكابلات",
891
+ "origin": "محلي",
892
+ "lead_time": 5,
893
+ "min_order": 100,
894
+ "description": "كابل نحاس 1×10 مم2 للتمديدات الكهربائية",
895
+ "image_url": "https://example.com/coppercable.jpg"
896
+ },
897
+ {
898
+ "id": "MAT-060",
899
+ "name": "كابل نحاس 1×6 مم2",
900
+ "category": "مواد كهربائية",
901
+ "subcategory": "كابلات",
902
+ "unit": "متر طولي",
903
+ "price": 10,
904
+ "supplier": "الشركة السعودية للكابلات",
905
+ "origin": "محلي",
906
+ "lead_time": 5,
907
+ "min_order": 100,
908
+ "description": "كابل نحاس 1×6 مم2 للتمديدات الكهربائية",
909
+ "image_url": "https://example.com/coppercable2.jpg"
910
+ },
911
+ {
912
+ "id": "MAT-061",
913
+ "name": "كابل نحاس 1×2.5 مم2",
914
+ "category": "مواد كهربائية",
915
+ "subcategory": "كابلات",
916
+ "unit": "متر طولي",
917
+ "price": 5,
918
+ "supplier": "الشركة السعودية للكابلات",
919
+ "origin": "محلي",
920
+ "lead_time": 5,
921
+ "min_order": 100,
922
+ "description": "كابل نحاس 1×2.5 مم2 للتمديدات الكهربائية",
923
+ "image_url": "https://example.com/coppercable3.jpg"
924
+ },
925
+ {
926
+ "id": "MAT-062",
927
+ "name": "كابل نحاس 1×1.5 مم2",
928
+ "category": "مواد كهربائية",
929
+ "subcategory": "كابلات",
930
+ "unit": "متر طولي",
931
+ "price": 3,
932
+ "supplier": "الشركة السعودية للكابلات",
933
+ "origin": "محلي",
934
+ "lead_time": 5,
935
+ "min_order": 100,
936
+ "description": "كابل نحاس 1×1.5 مم2 للتمديدات الكهربائية",
937
+ "image_url": "https://example.com/coppercable4.jpg"
938
+ },
939
+ {
940
+ "id": "MAT-063",
941
+ "name": "أنابيب بلاستيكية للتمديدات الكهربائية 20 مم",
942
+ "category": "مواد كهربائية",
943
+ "subcategory": "أنابيب",
944
+ "unit": "متر طولي",
945
+ "price": 3,
946
+ "supplier": "الشركة السعودية للأنابيب",
947
+ "origin": "محلي",
948
+ "lead_time": 3,
949
+ "min_order": 100,
950
+ "description": "أنابيب بلاستيكية للتمديدات الكهربائية قطر 20 مم",
951
+ "image_url": "https://example.com/conduit.jpg"
952
+ },
953
+ {
954
+ "id": "MAT-064",
955
+ "name": "أنابيب بلاستيكية للتمديدات الكهربائية 25 مم",
956
+ "category": "مواد كهربائية",
957
+ "subcategory": "أنابيب",
958
+ "unit": "متر طولي",
959
+ "price": 4,
960
+ "supplier": "الشركة السعودية للأنابيب",
961
+ "origin": "محلي",
962
+ "lead_time": 3,
963
+ "min_order": 100,
964
+ "description": "أنابيب بلاستيكية للتمديدات الكهربائية قطر 25 مم",
965
+ "image_url": "https://example.com/conduit2.jpg"
966
+ },
967
+ {
968
+ "id": "MAT-065",
969
+ "name": "علب كهربائية بلاستيكية",
970
+ "category": "مواد كهربائية",
971
+ "subcategory": "علب",
972
+ "unit": "قطعة",
973
+ "price": 2,
974
+ "supplier": "الشركة السعودية للأنابيب",
975
+ "origin": "محلي",
976
+ "lead_time": 3,
977
+ "min_order": 100,
978
+ "description": "علب كهربائية بلاستيكية للمفاتيح والبرايز",
979
+ "image_url": "https://example.com/electricalbox.jpg"
980
+ },
981
+ {
982
+ "id": "MAT-066",
983
+ "name": "مفتاح إنارة مفرد",
984
+ "category": "مواد كهربائية",
985
+ "subcategory": "مفاتيح",
986
+ "unit": "قطعة",
987
+ "price": 15,
988
+ "supplier": "شركة الأدوات الكهربائية",
989
+ "origin": "محلي",
990
+ "lead_time": 3,
991
+ "min_order": 50,
992
+ "description": "مفتاح إنارة مفرد",
993
+ "image_url": "https://example.com/switch.jpg"
994
+ },
995
+ {
996
+ "id": "MAT-067",
997
+ "name": "مفتاح إنارة ثنائي",
998
+ "category": "مواد كهربائية",
999
+ "subcategory": "مفاتيح",
1000
+ "unit": "قطعة",
1001
+ "price": 20,
1002
+ "supplier": "شركة الأدوات الكهربائية",
1003
+ "origin": "محلي",
1004
+ "lead_time": 3,
1005
+ "min_order": 50,
1006
+ "description": "مفتاح إنارة ثنائي",
1007
+ "image_url": "https://example.com/switch2.jpg"
1008
+ },
1009
+ {
1010
+ "id": "MAT-068",
1011
+ "name": "بريزة كهربائية",
1012
+ "category": "مواد كهربائية",
1013
+ "subcategory": "برايز",
1014
+ "unit": "قطعة",
1015
+ "price": 15,
1016
+ "supplier": "شركة الأدوات الكهربائية",
1017
+ "origin": "محلي",
1018
+ "lead_time": 3,
1019
+ "min_order": 50,
1020
+ "description": "بريزة كهربائية",
1021
+ "image_url": "https://example.com/socket.jpg"
1022
+ }
1023
+ ])
1024
+
1025
+ # 8. مواد ميكانيكية
1026
+ materials_data.extend([
1027
+ {
1028
+ "id": "MAT-069",
1029
+ "name": "أنابيب مياه PPR قطر 20 مم",
1030
+ "category": "مواد ميكانيكية",
1031
+ "subcategory": "أنابيب مياه",
1032
+ "unit": "متر طولي",
1033
+ "price": 8,
1034
+ "supplier": "الشركة السعودية للأنابيب",
1035
+ "origin": "محلي",
1036
+ "lead_time": 3,
1037
+ "min_order": 100,
1038
+ "description": "أنابيب مياه PPR قطر 20 مم",
1039
+ "image_url": "https://example.com/pprpipe.jpg"
1040
+ },
1041
+ {
1042
+ "id": "MAT-070",
1043
+ "name": "أنابيب مياه PPR قطر 25 مم",
1044
+ "category": "مواد ميكانيكية",
1045
+ "subcategory": "أنابيب مياه",
1046
+ "unit": "متر طولي",
1047
+ "price": 12,
1048
+ "supplier": "الشركة السعودية للأنابيب",
1049
+ "origin": "محلي",
1050
+ "lead_time": 3,
1051
+ "min_order": 100,
1052
+ "description": "أنابيب مياه PPR قطر 25 مم",
1053
+ "image_url": "https://example.com/pprpipe2.jpg"
1054
+ },
1055
+ {
1056
+ "id": "MAT-071",
1057
+ "name": "أنابيب مياه PPR قطر 32 مم",
1058
+ "category": "مواد ميكانيكية",
1059
+ "subcategory": "أنابيب مياه",
1060
+ "unit": "متر طولي",
1061
+ "price": 18,
1062
+ "supplier": "الشركة السعودية للأنابيب",
1063
+ "origin": "محلي",
1064
+ "lead_time": 3,
1065
+ "min_order": 100,
1066
+ "description": "أنابيب مياه PPR قطر 32 مم",
1067
+ "image_url": "https://example.com/pprpipe3.jpg"
1068
+ },
1069
+ {
1070
+ "id": "MAT-072",
1071
+ "name": "أنابيب حديد مجلفن قطر 2 بوصة",
1072
+ "category": "مواد ميكانيكية",
1073
+ "subcategory": "أنابيب حريق",
1074
+ "unit": "متر طولي",
1075
+ "price": 65,
1076
+ "supplier": "الشركة السعودية للأنابيب",
1077
+ "origin": "محلي",
1078
+ "lead_time": 5,
1079
+ "min_order": 50,
1080
+ "description": "أنابيب حديد مجلفن قطر 2 بوصة لأنظمة مكافحة الحريق",
1081
+ "image_url": "https://example.com/galvanizedpipe.jpg"
1082
+ },
1083
+ {
1084
+ "id": "MAT-073",
1085
+ "name": "أنابيب حديد مجلفن قطر 4 بوصة",
1086
+ "category": "مواد ميكانيكية",
1087
+ "subcategory": "أنابيب حريق",
1088
+ "unit": "متر طولي",
1089
+ "price": 120,
1090
+ "supplier": "الشركة السعودية للأنابيب",
1091
+ "origin": "محلي",
1092
+ "lead_time": 5,
1093
+ "min_order": 50,
1094
+ "description": "أنابيب حديد مجلفن قطر 4 بوصة لأنظمة مكافحة الحريق",
1095
+ "image_url": "https://example.com/galvanizedpipe2.jpg"
1096
+ },
1097
+ {
1098
+ "id": "MAT-074",
1099
+ "name": "أنابيب نحاس قطر 1/2 بوصة",
1100
+ "category": "مواد ميكانيكية",
1101
+ "subcategory": "أنابيب تكييف",
1102
+ "unit": "متر طولي",
1103
+ "price": 45,
1104
+ "supplier": "الشركة السعودية للأنابيب",
1105
+ "origin": "مستورد",
1106
+ "lead_time": 10,
1107
+ "min_order": 50,
1108
+ "description": "أنابيب نحاس قطر 1/2 بوصة لأنظمة التكييف",
1109
+ "image_url": "https://example.com/copperpipe.jpg"
1110
+ },
1111
+ {
1112
+ "id": "MAT-075",
1113
+ "name": "أنابيب نحاس قطر 3/4 بوصة",
1114
+ "category": "مواد ميكانيكية",
1115
+ "subcategory": "أنابيب تكييف",
1116
+ "unit": "متر طولي",
1117
+ "price": 65,
1118
+ "supplier": "الشركة السعودية للأنابيب",
1119
+ "origin": "مستورد",
1120
+ "lead_time": 10,
1121
+ "min_order": 50,
1122
+ "description": "أنابيب نحاس قطر 3/4 بوصة لأنظمة التكييف",
1123
+ "image_url": "https://example.com/copperpipe2.jpg"
1124
+ },
1125
+ {
1126
+ "id": "MAT-076",
1127
+ "name": "مضخة مياه 1 حصان",
1128
+ "category": "مواد ميكانيكية",
1129
+ "subcategory": "مضخات",
1130
+ "unit": "قطعة",
1131
+ "price": 850,
1132
+ "supplier": "شركة المضخات السعودية",
1133
+ "origin": "مستورد",
1134
+ "lead_time": 10,
1135
+ "min_order": 2,
1136
+ "description": "مضخة مياه 1 حصان",
1137
+ "image_url": "https://example.com/waterpump.jpg"
1138
+ },
1139
+ {
1140
+ "id": "MAT-077",
1141
+ "name": "مضخة مياه 2 حصان",
1142
+ "category": "مواد ميكانيكية",
1143
+ "subcategory": "مضخات",
1144
+ "unit": "قطعة",
1145
+ "price": 1200,
1146
+ "supplier": "شركة المضخات السعودية",
1147
+ "origin": "مستورد",
1148
+ "lead_time": 10,
1149
+ "min_order": 2,
1150
+ "description": "مضخة مياه 2 حصان",
1151
+ "image_url": "https://example.com/waterpump2.jpg"
1152
+ },
1153
+ {
1154
+ "id": "MAT-078",
1155
+ "name": "خزان مياه بلاستيكي 1000 لتر",
1156
+ "category": "مواد ميكانيكية",
1157
+ "subcategory": "خزانات",
1158
+ "unit": "قطعة",
1159
+ "price": 650,
1160
+ "supplier": "شركة الخزانات السعودية",
1161
+ "origin": "محلي",
1162
+ "lead_time": 5,
1163
+ "min_order": 2,
1164
+ "description": "خزان مياه بلاستيكي سعة 1000 لتر",
1165
+ "image_url": "https://example.com/watertank.jpg"
1166
+ }
1167
+ ])
1168
+
1169
+ # 9. مواد الري والزراعة
1170
+ materials_data.extend([
1171
+ {
1172
+ "id": "MAT-079",
1173
+ "name": "أنابيب بولي إيثيلين قطر 32 مم",
1174
+ "category": "مواد الري والزراعة",
1175
+ "subcategory": "أنابيب ري",
1176
+ "unit": "متر طولي",
1177
+ "price": 8,
1178
+ "supplier": "الشركة السعودية للأنابيب",
1179
+ "origin": "محلي",
1180
+ "lead_time": 3,
1181
+ "min_order": 100,
1182
+ "description": "أنابيب بولي إيثيلين قطر 32 مم لأنظمة الري",
1183
+ "image_url": "https://example.com/pepipe.jpg"
1184
+ },
1185
+ {
1186
+ "id": "MAT-080",
1187
+ "name": "أنابيب بولي إيثيلين قطر 63 مم",
1188
+ "category": "مواد الري والزراعة",
1189
+ "subcategory": "أنابيب ري",
1190
+ "unit": "متر طولي",
1191
+ "price": 15,
1192
+ "supplier": "الشركة السعودية للأنابيب",
1193
+ "origin": "محلي",
1194
+ "lead_time": 3,
1195
+ "min_order": 100,
1196
+ "description": "أنابيب بولي إيثيلين قطر 63 مم لأنظمة الري",
1197
+ "image_url": "https://example.com/pepipe2.jpg"
1198
+ },
1199
+ {
1200
+ "id": "MAT-081",
1201
+ "name": "نقاطات ري 4 لتر/ساعة",
1202
+ "category": "مواد الري والزراعة",
1203
+ "subcategory": "نقاطات",
1204
+ "unit": "قطعة",
1205
+ "price": 1,
1206
+ "supplier": "شركة أنظمة الري",
1207
+ "origin": "محلي",
1208
+ "lead_time": 3,
1209
+ "min_order": 1000,
1210
+ "description": "نقاطات ري 4 لتر/ساعة",
1211
+ "image_url": "https://example.com/dripper.jpg"
1212
+ },
1213
+ {
1214
+ "id": "MAT-082",
1215
+ "name": "نقاطات ري 8 لتر/ساعة",
1216
+ "category": "مواد الري والزراعة",
1217
+ "subcategory": "نقاطات",
1218
+ "unit": "قطعة",
1219
+ "price": 1.2,
1220
+ "supplier": "شركة أنظمة الري",
1221
+ "origin": "محلي",
1222
+ "lead_time": 3,
1223
+ "min_order": 1000,
1224
+ "description": "نقاطات ري 8 لتر/ساعة",
1225
+ "image_url": "https://example.com/dripper2.jpg"
1226
+ },
1227
+ {
1228
+ "id": "MAT-083",
1229
+ "name": "رشاشات ري دوارة",
1230
+ "category": "مواد الري والزراعة",
1231
+ "subcategory": "رشاشات",
1232
+ "unit": "قطعة",
1233
+ "price": 25,
1234
+ "supplier": "شركة أنظمة الري",
1235
+ "origin": "مستورد",
1236
+ "lead_time": 7,
1237
+ "min_order": 100,
1238
+ "description": "رشاشات ري دوارة للمسطحات الخضراء",
1239
+ "image_url": "https://example.com/sprinkler.jpg"
1240
+ },
1241
+ {
1242
+ "id": "MAT-084",
1243
+ "name": "محابس بلاستيكية قطر 32 مم",
1244
+ "category": "مواد الري والزراعة",
1245
+ "subcategory": "محابس",
1246
+ "unit": "قطعة",
1247
+ "price": 15,
1248
+ "supplier": "شركة أنظمة الري",
1249
+ "origin": "محلي",
1250
+ "lead_time": 3,
1251
+ "min_order": 50,
1252
+ "description": "محابس بلاستيكية قطر 32 مم لأنظمة الري",
1253
+ "image_url": "https://example.com/valve.jpg"
1254
+ },
1255
+ {
1256
+ "id": "MAT-085",
1257
+ "name": "محابس بلاستيكية قطر 63 مم",
1258
+ "category": "مواد الري والزراعة",
1259
+ "subcategory": "محابس",
1260
+ "unit": "قطعة",
1261
+ "price": 35,
1262
+ "supplier": "شركة أنظمة الري",
1263
+ "origin": "محلي",
1264
+ "lead_time": 3,
1265
+ "min_order": 50,
1266
+ "description": "محابس بلاستيكية قطر 63 مم لأنظمة الري",
1267
+ "image_url": "https://example.com/valve2.jpg"
1268
+ },
1269
+ {
1270
+ "id": "MAT-086",
1271
+ "name": "وحدة تحكم ري 6 محطات",
1272
+ "category": "مواد الري والزراعة",
1273
+ "subcategory": "وحدات تحكم",
1274
+ "unit": "قطعة",
1275
+ "price": 450,
1276
+ "supplier": "شركة أنظمة الري",
1277
+ "origin": "مستورد",
1278
+ "lead_time": 10,
1279
+ "min_order": 5,
1280
+ "description": "وحدة تحكم ري 6 محطات",
1281
+ "image_url": "https://example.com/controller.jpg"
1282
+ },
1283
+ {
1284
+ "id": "MAT-087",
1285
+ "name": "وحدة تحكم ري 12 محطة",
1286
+ "category": "مواد الري والزراعة",
1287
+ "subcategory": "وحدات تحكم",
1288
+ "unit": "قطعة",
1289
+ "price": 750,
1290
+ "supplier": "شركة أنظمة الري",
1291
+ "origin": "مستورد",
1292
+ "lead_time": 10,
1293
+ "min_order": 5,
1294
+ "description": "وحدة تحكم ري 12 محطة",
1295
+ "image_url": "https://example.com/controller2.jpg"
1296
+ },
1297
+ {
1298
+ "id": "MAT-088",
1299
+ "name": "تربة زراعية",
1300
+ "category": "مواد الري والزراعة",
1301
+ "subcategory": "تربة",
1302
+ "unit": "م3",
1303
+ "price": 120,
1304
+ "supplier": "شركة المواد الزراعية",
1305
+ "origin": "محلي",
1306
+ "lead_time": 2,
1307
+ "min_order": 10,
1308
+ "description": "تربة زراعية للزراعة",
1309
+ "image_url": "https://example.com/soil.jpg"
1310
+ }
1311
+ ])
1312
+
1313
+ # 10. مواد متنوعة
1314
+ materials_data.extend([
1315
+ {
1316
+ "id": "MAT-089",
1317
+ "name": "أسلاك تربيط حديد التسليح",
1318
+ "category": "مواد متنوعة",
1319
+ "subcategory": "أسلاك",
1320
+ "unit": "كجم",
1321
+ "price": 12,
1322
+ "supplier": "شركة حديد الراجحي",
1323
+ "origin": "محلي",
1324
+ "lead_time": 3,
1325
+ "min_order": 50,
1326
+ "description": "أسلاك تربيط حديد التسليح",
1327
+ "image_url": "https://example.com/bindingwire.jpg"
1328
+ },
1329
+ {
1330
+ "id": "MAT-090",
1331
+ "name": "مسامير متنوعة",
1332
+ "category": "مواد متنوعة",
1333
+ "subcategory": "مسامير",
1334
+ "unit": "كجم",
1335
+ "price": 15,
1336
+ "supplier": "شركة الأدوات",
1337
+ "origin": "محلي",
1338
+ "lead_time": 3,
1339
+ "min_order": 20,
1340
+ "description": "مسامير متنوعة للأعمال الإنشائية",
1341
+ "image_url": "https://example.com/nails.jpg"
1342
+ },
1343
+ {
1344
+ "id": "MAT-091",
1345
+ "name": "شدات معدنية",
1346
+ "category": "مواد متنوعة",
1347
+ "subcategory": "شدات",
1348
+ "unit": "م2",
1349
+ "price": 120,
1350
+ "supplier": "شركة معدات البناء",
1351
+ "origin": "محلي",
1352
+ "lead_time": 7,
1353
+ "min_order": 50,
1354
+ "description": "شدات معدنية للأعمال الخرسانية",
1355
+ "image_url": "https://example.com/formwork.jpg"
1356
+ },
1357
+ {
1358
+ "id": "MAT-092",
1359
+ "name": "سقالات معدنية",
1360
+ "category": "مواد متنوعة",
1361
+ "subcategory": "سقالات",
1362
+ "unit": "م2",
1363
+ "price": 85,
1364
+ "supplier": "شركة معدات البناء",
1365
+ "origin": "محلي",
1366
+ "lead_time": 7,
1367
+ "min_order": 50,
1368
+ "description": "سقالات معدنية للأعمال الإنشائية",
1369
+ "image_url": "https://example.com/scaffold.jpg"
1370
+ },
1371
+ {
1372
+ "id": "MAT-093",
1373
+ "name": "خشب أبلكاش 18 مم",
1374
+ "category": "مواد متنوعة",
1375
+ "subcategory": "خشب",
1376
+ "unit": "م2",
1377
+ "price": 65,
1378
+ "supplier": "شركة الأخشاب",
1379
+ "origin": "مستورد",
1380
+ "lead_time": 7,
1381
+ "min_order": 20,
1382
+ "description": "خشب أبلكاش سماكة 18 مم للشدات الخشبية",
1383
+ "image_url": "https://example.com/plywood.jpg"
1384
+ },
1385
+ {
1386
+ "id": "MAT-094",
1387
+ "name": "عوارض خشبية 10×10 سم",
1388
+ "category": "مواد متنوعة",
1389
+ "subcategory": "خشب",
1390
+ "unit": "متر طولي",
1391
+ "price": 25,
1392
+ "supplier": "شركة الأخشاب",
1393
+ "origin": "مستورد",
1394
+ "lead_time": 7,
1395
+ "min_order": 50,
1396
+ "description": "عوارض خشبية 10×10 سم للشدات الخشبية",
1397
+ "image_url": "https://example.com/timber.jpg"
1398
+ },
1399
+ {
1400
+ "id": "MAT-095",
1401
+ "name": "مواد لاصقة للبلاط",
1402
+ "category": "مواد متنوعة",
1403
+ "subcategory": "مواد لاصقة",
1404
+ "unit": "كجم",
1405
+ "price": 2.5,
1406
+ "supplier": "شركة مواد البناء",
1407
+ "origin": "محلي",
1408
+ "lead_time": 3,
1409
+ "min_order": 500,
1410
+ "description": "مواد لاصقة للبلاط",
1411
+ "image_url": "https://example.com/tileadhesive.jpg"
1412
+ },
1413
+ {
1414
+ "id": "MAT-096",
1415
+ "name": "روبة للبلاط",
1416
+ "category": "مواد متنوعة",
1417
+ "subcategory": "مواد لاصقة",
1418
+ "unit": "كجم",
1419
+ "price": 3,
1420
+ "supplier": "شركة مواد البناء",
1421
+ "origin": "محلي",
1422
+ "lead_time": 3,
1423
+ "min_order": 200,
1424
+ "description": "روبة للبلاط",
1425
+ "image_url": "https://example.com/grout.jpg"
1426
+ },
1427
+ {
1428
+ "id": "MAT-097",
1429
+ "name": "مواد معالجة الخرسانة",
1430
+ "category": "مواد متنوعة",
1431
+ "subcategory": "مواد معالجة",
1432
+ "unit": "لتر",
1433
+ "price": 12,
1434
+ "supplier": "سيكا",
1435
+ "origin": "مستورد",
1436
+ "lead_time": 7,
1437
+ "min_order": 100,
1438
+ "description": "مواد معالجة الخرسانة",
1439
+ "image_url": "https://example.com/curing.jpg"
1440
+ },
1441
+ {
1442
+ "id": "MAT-098",
1443
+ "name": "مواد إصلاح الخرسانة",
1444
+ "category": "مواد متنوعة",
1445
+ "subcategory": "مواد معالجة",
1446
+ "unit": "كجم",
1447
+ "price": 18,
1448
+ "supplier": "سيكا",
1449
+ "origin": "مستورد",
1450
+ "lead_time": 7,
1451
+ "min_order": 50,
1452
+ "description": "مواد إصلاح الخرسانة",
1453
+ "image_url": "https://example.com/repair.jpg"
1454
+ }
1455
+ ])
1456
+
1457
+ # تخزين البيانات في حالة الجلسة
1458
+ st.session_state.materials_catalog = pd.DataFrame(materials_data)
1459
+
1460
+ def render(self):
1461
+ """عرض واجهة كتالوج المواد"""
1462
+
1463
+ st.markdown("## كتالوج المواد")
1464
+
1465
+ # إنشاء تبويبات لعرض الكتالوج
1466
+ tabs = st.tabs([
1467
+ "عرض الكتالوج",
1468
+ "إضافة مادة",
1469
+ "تحليل الأسعار",
1470
+ "استيراد/تصدير"
1471
+ ])
1472
+
1473
+ with tabs[0]:
1474
+ self._render_catalog_view_tab()
1475
+
1476
+ with tabs[1]:
1477
+ self._render_add_material_tab()
1478
+
1479
+ with tabs[2]:
1480
+ self._render_price_analysis_tab()
1481
+
1482
+ with tabs[3]:
1483
+ self._render_import_export_tab()
1484
+
1485
+ def _render_catalog_view_tab(self):
1486
+ """عرض تبويب عرض الكتالوج"""
1487
+
1488
+ st.markdown("### عرض كتالوج المواد")
1489
+
1490
+ # استخر��ج البيانات
1491
+ materials_df = st.session_state.materials_catalog
1492
+
1493
+ # إنشاء فلاتر للعرض
1494
+ col1, col2, col3 = st.columns(3)
1495
+
1496
+ with col1:
1497
+ # فلتر حسب الفئة
1498
+ categories = ["الكل"] + sorted(materials_df["category"].unique().tolist())
1499
+ selected_category = st.selectbox("اختر فئة المواد", categories)
1500
+
1501
+ with col2:
1502
+ # فلتر حسب الفئة الفرعية
1503
+ if selected_category != "الكل":
1504
+ subcategories = ["الكل"] + sorted(materials_df[materials_df["category"] == selected_category]["subcategory"].unique().tolist())
1505
+ else:
1506
+ subcategories = ["الكل"] + sorted(materials_df["subcategory"].unique().tolist())
1507
+
1508
+ selected_subcategory = st.selectbox("اختر الفئة الفرعية", subcategories)
1509
+
1510
+ with col3:
1511
+ # فلتر حسب المورد
1512
+ suppliers = ["الكل"] + sorted(materials_df["supplier"].unique().tolist())
1513
+ selected_supplier = st.selectbox("اختر المورد", suppliers)
1514
+
1515
+ # تطبيق الفلاتر
1516
+ filtered_df = materials_df.copy()
1517
+
1518
+ if selected_category != "الكل":
1519
+ filtered_df = filtered_df[filtered_df["category"] == selected_category]
1520
+
1521
+ if selected_subcategory != "الكل":
1522
+ filtered_df = filtered_df[filtered_df["subcategory"] == selected_subcategory]
1523
+
1524
+ if selected_supplier != "الكل":
1525
+ filtered_df = filtered_df[filtered_df["supplier"] == selected_supplier]
1526
+
1527
+ # عرض البيانات
1528
+ if not filtered_df.empty:
1529
+ # عرض عدد النتائج
1530
+ st.info(f"تم العثور على {len(filtered_df)} مادة")
1531
+
1532
+ # عرض المواد في شكل بطاقات
1533
+ for i, (_, material) in enumerate(filtered_df.iterrows()):
1534
+ col1, col2 = st.columns([1, 2])
1535
+
1536
+ with col1:
1537
+ # عرض صورة المادة (استخدام صورة افتراضية)
1538
+ st.image("https://via.placeholder.com/150", caption=material["name"])
1539
+
1540
+ with col2:
1541
+ # عرض معلومات المادة
1542
+ st.markdown(f"**{material['name']}** (الكود: {material['id']})")
1543
+ st.markdown(f"الفئة: {material['category']} - {material['subcategory']}")
1544
+ st.markdown(f"الوحدة: {material['unit']} | السعر: {material['price']} ريال/{material['unit']}")
1545
+ st.markdown(f"المورد: {material['supplier']} | المنشأ: {material['origin']}")
1546
+ st.markdown(f"مدة التوريد: {material['lead_time']} يوم | الحد الأدنى للطلب: {material['min_order']} {material['unit']}")
1547
+
1548
+ # إضافة زر لعرض التفاصيل
1549
+ if st.button(f"عرض التفاصيل الكاملة", key=f"details_{material['id']}"):
1550
+ st.session_state.selected_material = material['id']
1551
+ self._show_material_details(material)
1552
+
1553
+ st.markdown("---")
1554
+ else:
1555
+ st.warning("لا توجد مواد تطابق معايير البحث")
1556
+
1557
+ def _show_material_details(self, material):
1558
+ """عرض تفاصيل المادة"""
1559
+
1560
+ st.markdown(f"## تفاصيل المادة: {material['name']}")
1561
+
1562
+ col1, col2 = st.columns([1, 2])
1563
+
1564
+ with col1:
1565
+ # عرض صورة المادة (استخدام صورة افتراضية)
1566
+ st.image("https://via.placeholder.com/300", caption=material["name"])
1567
+
1568
+ with col2:
1569
+ # عرض المعلومات الأساسية
1570
+ st.markdown("### المعلومات الأساسية")
1571
+ st.markdown(f"**الكود:** {material['id']}")
1572
+ st.markdown(f"**الفئة:** {material['category']} - {material['subcategory']}")
1573
+ st.markdown(f"**الوحدة:** {material['unit']}")
1574
+ st.markdown(f"**السعر:** {material['price']} ريال/{material['unit']}")
1575
+ st.markdown(f"**المورد:** {material['supplier']}")
1576
+ st.markdown(f"**المنشأ:** {material['origin']}")
1577
+ st.markdown(f"**مدة التوريد:** {material['lead_time']} يوم")
1578
+ st.markdown(f"**الحد الأدنى للطلب:** {material['min_order']} {material['unit']}")
1579
+ st.markdown(f"**الوصف:** {material['description']}")
1580
+
1581
+ # إضافة زر للتعديل
1582
+ if st.button("تعديل بيانات المادة"):
1583
+ st.session_state.edit_material = material['id']
1584
+ # هنا يمكن إضافة منطق التعديل
1585
+
1586
+ def _render_add_material_tab(self):
1587
+ """عرض تبويب إضافة مادة"""
1588
+
1589
+ st.markdown("### إضافة مادة جديدة")
1590
+
1591
+ # استخراج البيانات
1592
+ materials_df = st.session_state.materials_catalog
1593
+
1594
+ # إنشاء نموذج إضافة مادة
1595
+ with st.form("add_material_form"):
1596
+ st.markdown("#### المعلومات الأساسية")
1597
+
1598
+ # الصف الأول
1599
+ col1, col2 = st.columns(2)
1600
+ with col1:
1601
+ material_id = st.text_input("كود المادة", value=f"MAT-{len(materials_df) + 1:03d}")
1602
+ material_name = st.text_input("اسم المادة", placeholder="مثال: أسمنت بورتلاندي عادي")
1603
+
1604
+ with col2:
1605
+ # استخراج الفئات والفئات الفرعية الموجودة
1606
+ categories = sorted(materials_df["category"].unique().tolist())
1607
+ material_category = st.selectbox("فئة المادة", categories)
1608
+
1609
+ # استخراج الفئات الفرعية بناءً على الفئة المختارة
1610
+ subcategories = sorted(materials_df[materials_df["category"] == material_category]["subcategory"].unique().tolist())
1611
+ material_subcategory = st.selectbox("الفئة الفرعية", subcategories)
1612
+
1613
+ # الصف الثاني
1614
+ col1, col2, col3 = st.columns(3)
1615
+ with col1:
1616
+ material_unit = st.text_input("وحدة القياس", placeholder="مثال: م3، طن، قطعة")
1617
+ with col2:
1618
+ material_price = st.number_input("السعر (ريال)", min_value=0.0, step=0.5)
1619
+ with col3:
1620
+ material_min_order = st.number_input("الحد الأدنى للطلب", min_value=1, step=1)
1621
+
1622
+ # الصف الثالث
1623
+ col1, col2, col3 = st.columns(3)
1624
+ with col1:
1625
+ material_supplier = st.text_input("المورد", placeholder="مثال: شركة الإسمنت السعودية")
1626
+ with col2:
1627
+ material_origin = st.selectbox("المنشأ", ["محلي", "مستورد"])
1628
+ with col3:
1629
+ material_lead_time = st.number_input("مدة التوريد (يوم)", min_value=1, step=1)
1630
+
1631
+ # وصف المادة
1632
+ material_description = st.text_area("وصف المادة", placeholder="أدخل وصفاً تفصيلياً للمادة")
1633
+
1634
+ # رابط الصورة
1635
+ material_image_url = st.text_input("رابط الصورة", placeholder="مثال: https://example.com/image.jpg")
1636
+
1637
+ # زر الإضافة
1638
+ submit_button = st.form_submit_button("إضافة المادة")
1639
+
1640
+ if submit_button:
1641
+ # التحقق من البيانات
1642
+ if not material_name or not material_category or not material_subcategory or not material_unit:
1643
+ st.error("يرجى إدخال المعلومات الأساسية للمادة")
1644
+ else:
1645
+ # إنشاء مادة جديدة
1646
+ new_material = {
1647
+ "id": material_id,
1648
+ "name": material_name,
1649
+ "category": material_category,
1650
+ "subcategory": material_subcategory,
1651
+ "unit": material_unit,
1652
+ "price": material_price,
1653
+ "supplier": material_supplier,
1654
+ "origin": material_origin,
1655
+ "lead_time": material_lead_time,
1656
+ "min_order": material_min_order,
1657
+ "description": material_description,
1658
+ "image_url": material_image_url if material_image_url else "https://via.placeholder.com/150"
1659
+ }
1660
+
1661
+ # إضافة المادة إلى الكتالوج
1662
+ st.session_state.materials_catalog = pd.concat([
1663
+ st.session_state.materials_catalog,
1664
+ pd.DataFrame([new_material])
1665
+ ], ignore_index=True)
1666
+
1667
+ st.success(f"تمت إضافة المادة {material_name} بنجاح!")
1668
+
1669
+ def _render_price_analysis_tab(self):
1670
+ """عرض تبويب تحليل الأسعار"""
1671
+
1672
+ st.markdown("### تحليل أسعار المواد")
1673
+
1674
+ # استخراج البيانات
1675
+ materials_df = st.session_state.materials_catalog
1676
+
1677
+ # تحليل متوسط الأسعار حسب الفئة
1678
+ st.markdown("#### متوسط الأسعار حسب الفئة")
1679
+
1680
+ # حساب متوسط الأسعار لكل فئة
1681
+ category_prices = materials_df.groupby("category").agg({
1682
+ "price": "mean"
1683
+ }).reset_index()
1684
+
1685
+ # تغيير أسماء الأعمدة
1686
+ category_prices.columns = ["الفئة", "متوسط السعر"]
1687
+
1688
+ # عرض الجدول
1689
+ st.dataframe(category_prices, use_container_width=True)
1690
+
1691
+ # إنشاء رسم بياني للمقارنة
1692
+ st.markdown("#### مقارنة متوسط الأسعار حسب الفئة")
1693
+
1694
+ fig = px.bar(
1695
+ category_prices,
1696
+ x="الفئة",
1697
+ y="متوسط السعر",
1698
+ title="متوسط أسعار المواد حسب الفئة",
1699
+ color="الفئة",
1700
+ text_auto=True
1701
+ )
1702
+
1703
+ st.plotly_chart(fig, use_container_width=True)
1704
+
1705
+ # تحليل توزيع المواد حسب المنشأ
1706
+ st.markdown("#### توزيع المواد حسب المنشأ")
1707
+
1708
+ # حساب عدد المواد حسب المنشأ
1709
+ origin_counts = materials_df["origin"].value_counts().reset_index()
1710
+ origin_counts.columns = ["المنشأ", "عدد المواد"]
1711
+
1712
+ # إنشاء رسم بياني دائري
1713
+ fig = px.pie(
1714
+ origin_counts,
1715
+ values="عدد المواد",
1716
+ names="المنشأ",
1717
+ title="توزيع المواد حسب المنشأ",
1718
+ color="المنشأ"
1719
+ )
1720
+
1721
+ st.plotly_chart(fig, use_container_width=True)
1722
+
1723
+ # تحليل مدة التوريد
1724
+ st.markdown("#### تحليل مدة التوريد")
1725
+
1726
+ # حساب متوسط مدة التوريد حسب المنشأ
1727
+ lead_time_by_origin = materials_df.groupby("origin").agg({
1728
+ "lead_time": "mean"
1729
+ }).reset_index()
1730
+
1731
+ # تغيير أسماء الأعمدة
1732
+ lead_time_by_origin.columns = ["المنشأ", "متوسط مدة التوريد (يوم)"]
1733
+
1734
+ # عرض الجدول
1735
+ st.dataframe(lead_time_by_origin, use_container_width=True)
1736
+
1737
+ # إنشاء رسم بياني للمقارنة
1738
+ fig = px.bar(
1739
+ lead_time_by_origin,
1740
+ x="المنشأ",
1741
+ y="متوسط مدة التوريد (يوم)",
1742
+ title="متوسط مدة التوريد حسب المنشأ",
1743
+ color="المنشأ",
1744
+ text_auto=True
1745
+ )
1746
+
1747
+ st.plotly_chart(fig, use_container_width=True)
1748
+
1749
+ # حاسبة تكاليف المشروع
1750
+ st.markdown("#### حاسبة تكاليف المشروع")
1751
+
1752
+ with st.form("project_cost_calculator"):
1753
+ st.markdown("أدخل المواد المطلوبة للمشروع")
1754
+
1755
+ # اختيار المواد
1756
+ selected_materials = st.multiselect(
1757
+ "اختر المواد",
1758
+ options=materials_df["name"].tolist(),
1759
+ format_func=lambda x: f"{x} ({materials_df[materials_df['name'] == x]['id'].iloc[0]})"
1760
+ )
1761
+
1762
+ # إنشاء حقول إدخال الكميات
1763
+ quantities = {}
1764
+
1765
+ if selected_materials:
1766
+ st.markdown("أدخل الكميات المطلوبة")
1767
+
1768
+ for material_name in selected_materials:
1769
+ material = materials_df[materials_df["name"] == material_name].iloc[0]
1770
+ quantities[material_name] = st.number_input(
1771
+ f"{material_name} ({material['unit']})",
1772
+ min_value=0.0,
1773
+ step=0.5,
1774
+ key=f"qty_{material['id']}"
1775
+ )
1776
+
1777
+ # زر الحساب
1778
+ calculate_button = st.form_submit_button("حساب التكاليف")
1779
+
1780
+ if calculate_button:
1781
+ if not selected_materials:
1782
+ st.error("يرجى اختيار مادة واحدة على الأقل")
1783
+ else:
1784
+ # حساب التكاليف
1785
+ project_costs = []
1786
+
1787
+ for material_name in selected_materials:
1788
+ material = materials_df[materials_df["name"] == material_name].iloc[0]
1789
+ quantity = quantities[material_name]
1790
+
1791
+ if quantity > 0:
1792
+ cost = material["price"] * quantity
1793
+
1794
+ project_costs.append({
1795
+ "المادة": material_name,
1796
+ "الكود": material["id"],
1797
+ "الوحدة": material["unit"],
1798
+ "الكمية": quantity,
1799
+ "سعر الوحدة": material["price"],
1800
+ "التكلفة الإجمالية": cost
1801
+ })
1802
+
1803
+ if project_costs:
1804
+ # عرض النتائج
1805
+ project_costs_df = pd.DataFrame(project_costs)
1806
+ st.dataframe(project_costs_df, use_container_width=True)
1807
+
1808
+ # حساب إجمالي التكاليف
1809
+ total_cost = project_costs_df["التكلفة الإجمالية"].sum()
1810
+ st.metric("إجمالي تكاليف المواد للمشروع", f"{total_cost:,.2f} ريال")
1811
+ else:
1812
+ st.warning("يرجى إدخال كميات أكبر من صفر")
1813
+
1814
+ def _render_import_export_tab(self):
1815
+ """عرض تبويب استيراد/تصدير"""
1816
+
1817
+ st.markdown("### استيراد وتصدير بيانات المواد")
1818
+
1819
+ # استيراد البيانات
1820
+ st.markdown("#### استيراد البيانات")
1821
+
1822
+ uploaded_file = st.file_uploader("اختر ملف Excel لاستيراد بيانات المواد", type=["xlsx", "xls"])
1823
+
1824
+ if uploaded_file is not None:
1825
+ try:
1826
+ # قراءة الملف
1827
+ imported_df = pd.read_excel(uploaded_file)
1828
+
1829
+ # عرض البيانات المستوردة
1830
+ st.dataframe(imported_df, use_container_width=True)
1831
+
1832
+ # زر الاستيراد
1833
+ if st.button("استيراد البيانات"):
1834
+ # التحقق من وجود الأعمدة المطلوبة
1835
+ required_columns = ["id", "name", "category", "subcategory", "unit", "price"]
1836
+
1837
+ if all(col in imported_df.columns for col in required_columns):
1838
+ # دمج البيانات المستوردة مع البيانات الحالية
1839
+ st.session_state.materials_catalog = pd.concat([
1840
+ st.session_state.materials_catalog,
1841
+ imported_df
1842
+ ], ignore_index=True).drop_duplicates(subset=["id"])
1843
+
1844
+ st.success(f"تم استيراد {len(imported_df)} مادة بنجاح!")
1845
+ else:
1846
+ st.error("الملف المستورد لا يحتوي على الأعمدة المطلوبة")
1847
+
1848
+ except Exception as e:
1849
+ st.error(f"حدث خطأ أثناء استيراد الملف: {str(e)}")
1850
+
1851
+ # تصدير البيانات
1852
+ st.markdown("#### تصدير البيانات")
1853
+
1854
+ # اختيار تنسيق التصدير
1855
+ export_format = st.radio("اختر تنسيق التصدير", ["Excel", "CSV", "JSON"], horizontal=True)
1856
+
1857
+ if st.button("تصدير البيانات"):
1858
+ # استخراج البيانات
1859
+ materials_df = st.session_state.materials_catalog
1860
+
1861
+ # تصدير البيانات حسب التنسيق المختار
1862
+ if export_format == "Excel":
1863
+ # تصدير إلى Excel
1864
+ output = io.BytesIO()
1865
+ with pd.ExcelWriter(output, engine="openpyxl") as writer:
1866
+ materials_df.to_excel(writer, index=False, sheet_name="Materials")
1867
+
1868
+ # تحميل الملف
1869
+ st.download_button(
1870
+ label="تنزيل ملف Excel",
1871
+ data=output.getvalue(),
1872
+ file_name="materials_catalog.xlsx",
1873
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1874
+ )
1875
+
1876
+ elif export_format == "CSV":
1877
+ # تصدير إلى CSV
1878
+ csv_data = materials_df.to_csv(index=False)
1879
+
1880
+ # تحميل الملف
1881
+ st.download_button(
1882
+ label="تنزيل ملف CSV",
1883
+ data=csv_data,
1884
+ file_name="materials_catalog.csv",
1885
+ mime="text/csv"
1886
+ )
1887
+
1888
+ else: # JSON
1889
+ # تصدير إلى JSON
1890
+ json_data = materials_df.to_json(orient="records", force_ascii=False)
1891
+
1892
+ # تحميل الملف
1893
+ st.download_button(
1894
+ label="تنزيل ملف JSON",
1895
+ data=json_data,
1896
+ file_name="materials_catalog.json",
1897
+ mime="application/json"
1898
+ )
1899
+
1900
+ def get_material_by_id(self, material_id):
1901
+ """الحصول على مادة بواسطة الكود"""
1902
+
1903
+ materials_df = st.session_state.materials_catalog
1904
+ material = materials_df[materials_df["id"] == material_id]
1905
+
1906
+ if not material.empty:
1907
+ return material.iloc[0].to_dict()
1908
+
1909
+ return None
1910
+
1911
+ def get_materials_by_category(self, category):
1912
+ """الحصول على المواد حسب الفئة"""
1913
+
1914
+ materials_df = st.session_state.materials_catalog
1915
+ materials = materials_df[materials_df["category"] == category]
1916
+
1917
+ if not materials.empty:
1918
+ return materials.to_dict(orient="records")
1919
+
1920
+ return []
1921
+
1922
+ def calculate_material_cost(self, material_id, quantity):
1923
+ """حساب تكلفة المادة بناءً على الكمية"""
1924
+
1925
+ material = self.get_material_by_id(material_id)
1926
+
1927
+ if material:
1928
+ return material["price"] * quantity
1929
+
1930
+ return 0
pricing_system/modules/catalogs/subcontractors_catalog.py ADDED
@@ -0,0 +1,1159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ كتالوج مقاولي الباطن - وحدة إدارة مقاولي الباطن
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import plotly.express as px
9
+ import os
10
+ import json
11
+ from datetime import datetime
12
+ import io
13
+
14
+ class SubcontractorsCatalog:
15
+ """كتالوج مقاولي الباطن"""
16
+
17
+ def __init__(self):
18
+ """تهيئة كتالوج مقاولي الباطن"""
19
+
20
+ # تهيئة حالة الجلسة لكتالوج مقاولي الباطن
21
+ if 'subcontractors_catalog' not in st.session_state:
22
+ # إنشاء بيانات افتراضية لمقاولي الباطن
23
+ self._initialize_subcontractors_catalog()
24
+
25
+ def _initialize_subcontractors_catalog(self):
26
+ """تهيئة بيانات كتالوج مقاولي الباطن"""
27
+
28
+ # تعريف فئات مقاولي الباطن
29
+ subcontractor_categories = [
30
+ "أعمال الكهرباء",
31
+ "أعمال ITC",
32
+ "أعمال CCTV",
33
+ "أنظمة التحكم في الوصول",
34
+ "شبكات الري",
35
+ "أعمال الصرف الصحي",
36
+ "أعمال الطرق",
37
+ "أعمال الجسور",
38
+ "أعمال الحفر والردم",
39
+ "أعمال الخرسانة"
40
+ ]
41
+
42
+ # إنشاء قائمة مقاولي الباطن
43
+ subcontractors_data = []
44
+
45
+ # 1. أعمال الكهرباء
46
+ subcontractors_data.extend([
47
+ {
48
+ "id": "ELEC-001",
49
+ "name": "شركة الأنظمة الكهربائية المتكاملة",
50
+ "category": "أعمال الكهرباء",
51
+ "subcategory": "تمديدات كهربائية",
52
+ "contact_person": "م. خالد العتيبي",
53
+ "phone": "0555123456",
54
+ "email": "[email protected]",
55
+ "address": "الرياض - حي العليا",
56
+ "classification": "درجة أولى",
57
+ "experience_years": 15,
58
+ "completed_projects": 120,
59
+ "ongoing_projects": 8,
60
+ "min_project_value": 500000,
61
+ "max_project_value": 50000000,
62
+ "specialties": "تمديدات كهربائية، محطات توزيع، لوحات توزيع، أنظمة إنارة",
63
+ "rating": 4.8,
64
+ "description": "شركة متخصصة في تنفيذ أعمال التمديدات الكهربائية ومحطات التوزيع واللوحات الكهربائية وأنظمة الإنارة للمشاريع الكبرى"
65
+ },
66
+ {
67
+ "id": "ELEC-002",
68
+ "name": "مؤسسة النور للمقاولات الكهربائية",
69
+ "category": "أعمال الكهرباء",
70
+ "subcategory": "إنارة طرق",
71
+ "contact_person": "م. سعد القحطاني",
72
+ "phone": "0555234567",
73
+ "email": "[email protected]",
74
+ "address": "جدة - حي الروضة",
75
+ "classification": "درجة ثانية",
76
+ "experience_years": 10,
77
+ "completed_projects": 75,
78
+ "ongoing_projects": 5,
79
+ "min_project_value": 200000,
80
+ "max_project_value": 20000000,
81
+ "specialties": "إنارة طرق، إنارة ميادين، إنارة حدائق، أنظمة تحكم",
82
+ "rating": 4.5,
83
+ "description": "مؤسسة متخصصة في تنفيذ أعمال إنارة الطرق والميادين والحدائق وأنظمة التحكم في الإنارة"
84
+ },
85
+ {
86
+ "id": "ELEC-003",
87
+ "name": "شركة الطاقة للمقاولات الكهربائية",
88
+ "category": "أعمال الكهرباء",
89
+ "subcategory": "محطات كهربائية",
90
+ "contact_person": "م. فهد الشمري",
91
+ "phone": "0555345678",
92
+ "email": "[email protected]",
93
+ "address": "الدمام - حي الفيصلية",
94
+ "classification": "درجة أولى",
95
+ "experience_years": 18,
96
+ "completed_projects": 150,
97
+ "ongoing_projects": 10,
98
+ "min_project_value": 1000000,
99
+ "max_project_value": 100000000,
100
+ "specialties": "محطات توزيع، محطات تحويل، خطوط نقل، أنظمة حماية",
101
+ "rating": 4.9,
102
+ "description": "شركة متخصصة في تنفيذ أعمال محطات التوزيع والتحويل وخطوط النقل وأنظمة الحماية الكهربائية"
103
+ }
104
+ ])
105
+
106
+ # 2. أعمال ITC
107
+ subcontractors_data.extend([
108
+ {
109
+ "id": "ITC-001",
110
+ "name": "شركة التقنية المتكاملة للاتصالات",
111
+ "category": "أعمال ITC",
112
+ "subcategory": "شبكات اتصالات",
113
+ "contact_person": "م. محمد العمري",
114
+ "phone": "0555456789",
115
+ "email": "[email protected]",
116
+ "address": "الرياض - حي الملز",
117
+ "classification": "درجة أولى",
118
+ "experience_years": 12,
119
+ "completed_projects": 90,
120
+ "ongoing_projects": 7,
121
+ "min_project_value": 500000,
122
+ "max_project_value": 40000000,
123
+ "specialties": "شبكات اتصالات، أنظمة مراقبة، أنظمة صوتية، شبكات ألياف ضوئية",
124
+ "rating": 4.7,
125
+ "description": "شركة متخصصة في تنفيذ أعمال شبكات الاتصالات وأنظمة المراقبة والأنظمة الصوتية وشبكات الألياف الضوئية"
126
+ },
127
+ {
128
+ "id": "ITC-002",
129
+ "name": "مؤسسة الاتصالات المتقدمة",
130
+ "category": "أعمال ITC",
131
+ "subcategory": "أنظمة معلومات",
132
+ "contact_person": "م. عبدالله الزهراني",
133
+ "phone": "0555567890",
134
+ "email": "[email protected]",
135
+ "address": "جدة - حي السلامة",
136
+ "classification": "درجة ثانية",
137
+ "experience_years": 8,
138
+ "completed_projects": 60,
139
+ "ongoing_projects": 4,
140
+ "min_project_value": 200000,
141
+ "max_project_value": 15000000,
142
+ "specialties": "أنظمة معلومات، شبكات داخلية، أنظمة تخزين، أنظمة حماية",
143
+ "rating": 4.4,
144
+ "description": "مؤسسة متخصصة في تنفيذ أعمال أنظمة المعلومات والشبكات الداخلية وأنظمة التخزين وأنظمة الحماية"
145
+ },
146
+ {
147
+ "id": "ITC-003",
148
+ "name": "شركة البيانات للاتصالات وتقنية المعلومات",
149
+ "category": "أعمال ITC",
150
+ "subcategory": "بنية تحتية للاتصالات",
151
+ "contact_person": "م. سلطان المالكي",
152
+ "phone": "0555678901",
153
+ "email": "[email protected]",
154
+ "address": "الدمام - حي الشاطئ",
155
+ "classification": "درجة أولى",
156
+ "experience_years": 15,
157
+ "completed_projects": 110,
158
+ "ongoing_projects": 9,
159
+ "min_project_value": 800000,
160
+ "max_project_value": 60000000,
161
+ "specialties": "بنية تحتية للاتصالات، أبراج اتصالات، محطات إرسال، شبكات ألياف ضوئية",
162
+ "rating": 4.8,
163
+ "description": "شركة متخصصة في تنفيذ أعمال البنية التحتية للاتصالات وأبراج الاتصالات ومحطات الإرسال وشبكات الألياف الضوئية"
164
+ }
165
+ ])
166
+
167
+ # 3. أعمال CCTV
168
+ subcontractors_data.extend([
169
+ {
170
+ "id": "CCTV-001",
171
+ "name": "شركة الأمان لأنظمة المراقبة",
172
+ "category": "أعمال CCTV",
173
+ "subcategory": "أنظمة مراقبة",
174
+ "contact_person": "م. ناصر الحربي",
175
+ "phone": "0555789012",
176
+ "email": "[email protected]",
177
+ "address": "الرياض - حي النزهة",
178
+ "classification": "درجة ثانية",
179
+ "experience_years": 10,
180
+ "completed_projects": 85,
181
+ "ongoing_projects": 6,
182
+ "min_project_value": 100000,
183
+ "max_project_value": 10000000,
184
+ "specialties": "أنظمة مراقبة، كاميرات أمنية، أنظمة تسجيل، أنظمة تحكم",
185
+ "rating": 4.6,
186
+ "description": "شركة متخصصة في تنفيذ أعمال أنظمة المراقبة والكاميرات الأمنية وأنظمة التسجيل وأنظمة التحكم"
187
+ },
188
+ {
189
+ "id": "CCTV-002",
190
+ "name": "مؤسسة الحماية للأنظمة الأمنية",
191
+ "category": "أعمال CCTV",
192
+ "subcategory": "أنظمة أمنية",
193
+ "contact_person": "م. سعيد الغامدي",
194
+ "phone": "0555890123",
195
+ "email": "[email protected]",
196
+ "address": "جدة - حي الصفا",
197
+ "classification": "درجة ثالثة",
198
+ "experience_years": 7,
199
+ "completed_projects": 50,
200
+ "ongoing_projects": 3,
201
+ "min_project_value": 50000,
202
+ "max_project_value": 5000000,
203
+ "specialties": "أنظمة أمنية، كاميرات مراقبة، أنظمة إنذار، أنظمة دخول",
204
+ "rating": 4.3,
205
+ "description": "مؤسسة متخصصة في تنفيذ أعمال الأنظمة الأمنية وكاميرات المراقبة وأنظمة الإنذار وأنظمة الدخول"
206
+ },
207
+ {
208
+ "id": "CCTV-003",
209
+ "name": "شركة المراقبة الذكية",
210
+ "category": "أعمال CCTV",
211
+ "subcategory": "أنظمة مراقبة ذكية",
212
+ "contact_person": "م. عمر السعدي",
213
+ "phone": "0555901234",
214
+ "email": "[email protected]",
215
+ "address": "الدمام - حي الخليج",
216
+ "classification": "درجة أولى",
217
+ "experience_years": 12,
218
+ "completed_projects": 95,
219
+ "ongoing_projects": 8,
220
+ "min_project_value": 300000,
221
+ "max_project_value": 25000000,
222
+ "specialties": "أنظمة مراقبة ذكية، تحليل فيديو، تعرف على الوجوه، تتبع حركة",
223
+ "rating": 4.8,
224
+ "description": "شركة متخصصة في تنفيذ أعمال أنظمة المراقبة الذكية وتحليل الفيديو والتعرف على الوجوه وتتبع الحركة"
225
+ }
226
+ ])
227
+
228
+ # 4. أنظمة التحكم في الوصول
229
+ subcontractors_data.extend([
230
+ {
231
+ "id": "ACCESS-001",
232
+ "name": "شركة التحكم الأمني",
233
+ "category": "أنظمة التحكم في الوصول",
234
+ "subcategory": "أنظمة تحكم",
235
+ "contact_person": "م. فيصل العنزي",
236
+ "phone": "0556012345",
237
+ "email": "[email protected]",
238
+ "address": "الرياض - حي الورود",
239
+ "classification": "درجة ثانية",
240
+ "experience_years": 9,
241
+ "completed_projects": 70,
242
+ "ongoing_projects": 5,
243
+ "min_project_value": 100000,
244
+ "max_project_value": 8000000,
245
+ "specialties": "أنظمة تحكم في الدخول، بصمات، بطاقات ذكية، أقفال إلكترونية",
246
+ "rating": 4.5,
247
+ "description": "شركة متخصصة في تنفيذ أعمال أنظمة التحكم في الدخول والبصمات والبطاقات الذكية والأقفال الإلكترونية"
248
+ },
249
+ {
250
+ "id": "ACCESS-002",
251
+ "name": "مؤسسة الوصول الآمن",
252
+ "category": "أنظمة التحكم في الوصول",
253
+ "subcategory": "أنظمة أمنية",
254
+ "contact_person": "م. ماجد الدوسري",
255
+ "phone": "0556123456",
256
+ "email": "[email protected]",
257
+ "address": "جدة - حي المروة",
258
+ "classification": "درجة ثالثة",
259
+ "experience_years": 6,
260
+ "completed_projects": 40,
261
+ "ongoing_projects": 3,
262
+ "min_project_value": 50000,
263
+ "max_project_value": 3000000,
264
+ "specialties": "أنظمة أمنية، بوابات إلكترونية، حواجز آلية، كاميرات تعرف",
265
+ "rating": 4.2,
266
+ "description": "مؤسسة متخصصة في تنفيذ أعمال الأنظمة الأمنية والبوابات الإلكترونية والحواجز الآلية وكاميرات التعرف"
267
+ },
268
+ {
269
+ "id": "ACCESS-003",
270
+ "name": "شركة الأمن الذكي",
271
+ "category": "أنظمة التحكم في الوصول",
272
+ "subcategory": "أنظمة متكاملة",
273
+ "contact_person": "م. طارق الشهري",
274
+ "phone": "0556234567",
275
+ "email": "[email protected]",
276
+ "address": "الدمام - حي النور",
277
+ "classification": "درجة أولى",
278
+ "experience_years": 14,
279
+ "completed_projects": 100,
280
+ "ongoing_projects": 7,
281
+ "min_project_value": 500000,
282
+ "max_project_value": 20000000,
283
+ "specialties": "أنظمة متكاملة، تحكم مركزي، مراقبة ذكية، تحليل سلوك",
284
+ "rating": 4.7,
285
+ "description": "شركة متخصصة في تنفيذ أعمال الأنظمة المتكاملة والتحكم المركزي والمراقبة الذكية وتحليل السلوك"
286
+ }
287
+ ])
288
+
289
+ # 5. شبكات الري
290
+ subcontractors_data.extend([
291
+ {
292
+ "id": "IRR-001",
293
+ "name": "شركة الواحة لأنظمة الري",
294
+ "category": "شبكات الري",
295
+ "subcategory": "أنظمة ري",
296
+ "contact_person": "م. سامي المطيري",
297
+ "phone": "0556345678",
298
+ "email": "[email protected]",
299
+ "address": "الرياض - حي الياسمين",
300
+ "classification": "درجة ثانية",
301
+ "experience_years": 11,
302
+ "completed_projects": 80,
303
+ "ongoing_projects": 6,
304
+ "min_project_value": 200000,
305
+ "max_project_value": 15000000,
306
+ "specialties": "أنظمة ري، شبكات مياه، مضخات، أنظمة تحكم",
307
+ "rating": 4.6,
308
+ "description": "شركة متخصصة في تنفيذ أعمال أنظمة الري وشبكات المياه والمضخات وأنظمة التحكم"
309
+ },
310
+ {
311
+ "id": "IRR-002",
312
+ "name": "مؤسسة الخضراء للري والزراعة",
313
+ "category": "شبكات الري",
314
+ "subcategory": "ري زراعي",
315
+ "contact_person": "م. عبدالرحمن الحربي",
316
+ "phone": "0556456789",
317
+ "email": "[email protected]",
318
+ "address": "جدة - حي الفيحاء",
319
+ "classification": "درجة ثالثة",
320
+ "experience_years": 8,
321
+ "completed_projects": 55,
322
+ "ongoing_projects": 4,
323
+ "min_project_value": 100000,
324
+ "max_project_value": 5000000,
325
+ "specialties": "ري زراعي، ري بالتنقيط، ري بالرش، أنظمة تسميد",
326
+ "rating": 4.4,
327
+ "description": "مؤسسة متخصصة في تنفيذ أعمال الري الزراعي والري بالتنقيط والري بالرش وأنظمة التسميد"
328
+ },
329
+ {
330
+ "id": "IRR-003",
331
+ "name": "شركة المياه الذكية",
332
+ "category": "شبكات الري",
333
+ "subcategory": "أنظمة ري ذكية",
334
+ "contact_person": "م. خالد السبيعي",
335
+ "phone": "0556567890",
336
+ "email": "[email protected]",
337
+ "address": "الدمام - حي الفردوس",
338
+ "classification": "درجة أولى",
339
+ "experience_years": 13,
340
+ "completed_projects": 90,
341
+ "ongoing_projects": 7,
342
+ "min_project_value": 500000,
343
+ "max_project_value": 25000000,
344
+ "specialties": "أنظمة ري ذكية، تحكم عن بعد، استشعار رطوبة، توفير مياه",
345
+ "rating": 4.8,
346
+ "description": "شركة متخصصة في تنفيذ أعمال أنظمة الري الذكية والتحكم عن بعد واستشعار الرطوبة وتوفير المياه"
347
+ }
348
+ ])
349
+
350
+ # 6. أعمال الصرف الصحي
351
+ subcontractors_data.extend([
352
+ {
353
+ "id": "SEW-001",
354
+ "name": "شركة البنية التحتية للصرف الصحي",
355
+ "category": "أعمال الصرف الصحي",
356
+ "subcategory": "شبكات صرف",
357
+ "contact_person": "م. عبدالعزيز الشمري",
358
+ "phone": "0556678901",
359
+ "email": "[email protected]",
360
+ "address": "الرياض - حي الملقا",
361
+ "classification": "درجة أولى",
362
+ "experience_years": 16,
363
+ "completed_projects": 120,
364
+ "ongoing_projects": 9,
365
+ "min_project_value": 1000000,
366
+ "max_project_value": 80000000,
367
+ "specialties": "شبكات صرف، محطات ضخ، محطات معالجة، خطوط رئيسية",
368
+ "rating": 4.9,
369
+ "description": "شركة متخصصة في تنفيذ أعمال شبكات الصرف ومحطات الضخ ومحطات المعالجة والخطوط الرئيسية"
370
+ },
371
+ {
372
+ "id": "SEW-002",
373
+ "name": "مؤسسة الصرف المتكاملة",
374
+ "category": "أعمال الصرف الصحي",
375
+ "subcategory": "صرف داخلي",
376
+ "contact_person": "م. فهد العتيبي",
377
+ "phone": "0556789012",
378
+ "email": "[email protected]",
379
+ "address": "جدة - حي الحمراء",
380
+ "classification": "درجة ثانية",
381
+ "experience_years": 9,
382
+ "completed_projects": 65,
383
+ "ongoing_projects": 5,
384
+ "min_project_value": 300000,
385
+ "max_project_value": 20000000,
386
+ "specialties": "صرف داخلي، تمديدات صحية، غرف تفتيش، خزانات تحليل",
387
+ "rating": 4.5,
388
+ "description": "مؤسسة متخصصة في تنفيذ أعمال الصرف الداخلي والتمديدات الصحية وغرف التفتيش وخزانات التحليل"
389
+ },
390
+ {
391
+ "id": "SEW-003",
392
+ "name": "شركة معالجة المياه",
393
+ "category": "أعمال الصرف الصحي",
394
+ "subcategory": "محطات معالجة",
395
+ "contact_person": "م. سلطان القحطاني",
396
+ "phone": "0556890123",
397
+ "email": "[email protected]",
398
+ "address": "الدمام - حي الأنوار",
399
+ "classification": "درجة أولى",
400
+ "experience_years": 18,
401
+ "completed_projects": 130,
402
+ "ongoing_projects": 10,
403
+ "min_project_value": 2000000,
404
+ "max_project_value": 100000000,
405
+ "specialties": "محطات معالجة، تنقية مياه، إعادة تدوير، أنظمة تحكم",
406
+ "rating": 4.9,
407
+ "description": "شركة متخصصة في تنفيذ أعمال محطات المعالجة وتنقية المياه وإعادة التدوير وأنظمة التحكم"
408
+ }
409
+ ])
410
+
411
+ # 7. أعمال الطرق
412
+ subcontractors_data.extend([
413
+ {
414
+ "id": "ROAD-001",
415
+ "name": "شركة الطرق الحديثة",
416
+ "category": "أعمال الطرق",
417
+ "subcategory": "إنشاء طرق",
418
+ "contact_person": "م. محمد الحارثي",
419
+ "phone": "0556901234",
420
+ "email": "[email protected]",
421
+ "address": "الرياض - حي النخيل",
422
+ "classification": "درجة أولى",
423
+ "experience_years": 20,
424
+ "completed_projects": 150,
425
+ "ongoing_projects": 12,
426
+ "min_project_value": 5000000,
427
+ "max_project_value": 200000000,
428
+ "specialties": "إنشاء طرق، تقاطعات، أنفاق، جسور صغيرة",
429
+ "rating": 4.9,
430
+ "description": "شركة متخصصة في تنفيذ أعمال إنشاء الطرق والتقاطعات والأنفاق والجسور الصغيرة"
431
+ },
432
+ {
433
+ "id": "ROAD-002",
434
+ "name": "مؤسسة الطرق السريعة",
435
+ "category": "أعمال الطرق",
436
+ "subcategory": "رصف طرق",
437
+ "contact_person": "م. سعد الغامدي",
438
+ "phone": "0557012345",
439
+ "email": "[email protected]",
440
+ "address": "جدة - حي الشرفية",
441
+ "classification": "درجة ثانية",
442
+ "experience_years": 12,
443
+ "completed_projects": 85,
444
+ "ongoing_projects": 7,
445
+ "min_project_value": 1000000,
446
+ "max_project_value": 50000000,
447
+ "specialties": "رصف طرق، سفلتة، خلطات إسفلتية، علامات مرورية",
448
+ "rating": 4.6,
449
+ "description": "مؤسسة متخصصة في تنفيذ أعمال رصف الطرق والسفلتة والخلطات الإسفلتية والعلامات المرورية"
450
+ },
451
+ {
452
+ "id": "ROAD-003",
453
+ "name": "شركة البنية التحتية للطرق",
454
+ "category": "أعمال الطرق",
455
+ "subcategory": "بنية تحتية",
456
+ "contact_person": "م. عبدالله المالكي",
457
+ "phone": "0557123456",
458
+ "email": "[email protected]",
459
+ "address": "الدمام - حي الفيصلية",
460
+ "classification": "درجة أولى",
461
+ "experience_years": 17,
462
+ "completed_projects": 120,
463
+ "ongoing_projects": 10,
464
+ "min_project_value": 3000000,
465
+ "max_project_value": 150000000,
466
+ "specialties": "بنية تحتية، تصريف مياه، قنوات، عبارات",
467
+ "rating": 4.8,
468
+ "description": "شركة متخصصة في تنفيذ أعمال البنية التحتية للطرق وتصريف المياه والقنوات والعبارات"
469
+ }
470
+ ])
471
+
472
+ # 8. أعمال الجسور
473
+ subcontractors_data.extend([
474
+ {
475
+ "id": "BRIDGE-001",
476
+ "name": "شركة الجسور العالمية",
477
+ "category": "أعمال الجسور",
478
+ "subcategory": "إنشاء جسور",
479
+ "contact_person": "م. فيصل الدوسري",
480
+ "phone": "0557234567",
481
+ "email": "[email protected]",
482
+ "address": "الرياض - حي الصحافة",
483
+ "classification": "درجة أولى",
484
+ "experience_years": 22,
485
+ "completed_projects": 80,
486
+ "ongoing_projects": 8,
487
+ "min_project_value": 10000000,
488
+ "max_project_value": 500000000,
489
+ "specialties": "إنشاء جسور، جسور معلقة، جسور خرسانية، جسور معدنية",
490
+ "rating": 4.9,
491
+ "description": "شركة متخصصة في تنفيذ أعمال إنشاء الجسور والجسور المعلقة والجسور الخرسانية والجسور المعدنية"
492
+ },
493
+ {
494
+ "id": "BRIDGE-002",
495
+ "name": "مؤسسة الجسور الحديثة",
496
+ "category": "أعمال الجسور",
497
+ "subcategory": "صيانة جسور",
498
+ "contact_person": "م. خالد العمري",
499
+ "phone": "0557345678",
500
+ "email": "[email protected]",
501
+ "address": "جدة - حي البوادي",
502
+ "classification": "درجة ثانية",
503
+ "experience_years": 14,
504
+ "completed_projects": 60,
505
+ "ongoing_projects": 5,
506
+ "min_project_value": 2000000,
507
+ "max_project_value": 100000000,
508
+ "specialties": "صيانة جسور، ترميم، تقوية، توسعة",
509
+ "rating": 4.7,
510
+ "description": "مؤسسة متخصصة في تنفيذ أعمال صيانة الجسور والترميم والتقوية والتوسعة"
511
+ },
512
+ {
513
+ "id": "BRIDGE-003",
514
+ "name": "شركة الإنشاءات المتخصصة",
515
+ "category": "أعمال الجسور",
516
+ "subcategory": "جسور معقدة",
517
+ "contact_person": "م. سلطان الشهري",
518
+ "phone": "0557456789",
519
+ "email": "[email protected]",
520
+ "address": "الدمام - حي الروضة",
521
+ "classification": "درجة أولى",
522
+ "experience_years": 25,
523
+ "completed_projects": 90,
524
+ "ongoing_projects": 7,
525
+ "min_project_value": 20000000,
526
+ "max_project_value": 800000000,
527
+ "specialties": "جسور معقدة، تقاطعات متعددة المستويات، أنفاق، منشآت خاصة",
528
+ "rating": 5.0,
529
+ "description": "شركة متخصصة في تنفيذ أعمال الجسور المعقدة والتقاطعات متعددة المستويات والأنفاق والمنشآت الخاصة"
530
+ }
531
+ ])
532
+
533
+ # 9. أعمال الحفر والردم
534
+ subcontractors_data.extend([
535
+ {
536
+ "id": "EXCAV-001",
537
+ "name": "شركة الحفريات الكبرى",
538
+ "category": "أعمال الحفر والردم",
539
+ "subcategory": "حفر كبير",
540
+ "contact_person": "م. ناصر العتيبي",
541
+ "phone": "0557567890",
542
+ "email": "[email protected]",
543
+ "address": "الرياض - حي العزيزية",
544
+ "classification": "درجة أولى",
545
+ "experience_years": 18,
546
+ "completed_projects": 130,
547
+ "ongoing_projects": 10,
548
+ "min_project_value": 3000000,
549
+ "max_project_value": 150000000,
550
+ "specialties": "حفر كبير، نقل مواد، تسوية، تثبيت تربة",
551
+ "rating": 4.8,
552
+ "description": "شركة متخصصة في تنفيذ أعمال الحفر الكبير ونقل المواد والتسوية وتثبيت التربة"
553
+ },
554
+ {
555
+ "id": "EXCAV-002",
556
+ "name": "مؤسسة الحفر والردم",
557
+ "category": "أعمال الحفر والردم",
558
+ "subcategory": "حفر وردم",
559
+ "contact_person": "م. سعيد القحطاني",
560
+ "phone": "0557678901",
561
+ "email": "[email protected]",
562
+ "address": "جدة - حي السلامة",
563
+ "classification": "درجة ثانية",
564
+ "experience_years": 12,
565
+ "completed_projects": 90,
566
+ "ongoing_projects": 6,
567
+ "min_project_value": 500000,
568
+ "max_project_value": 30000000,
569
+ "specialties": "حفر وردم، تسوية مواقع، دك تربة، تصريف مياه",
570
+ "rating": 4.6,
571
+ "description": "مؤسسة متخصصة في تنفيذ أعمال الحفر والردم وتسوية المواقع ودك التربة وتصريف المياه"
572
+ },
573
+ {
574
+ "id": "EXCAV-003",
575
+ "name": "شركة التربة المتخصصة",
576
+ "category": "أعمال الحفر والردم",
577
+ "subcategory": "معالجة تربة",
578
+ "contact_person": "م. فهد الشمري",
579
+ "phone": "0557789012",
580
+ "email": "[email protected]",
581
+ "address": "الدمام - حي النزهة",
582
+ "classification": "درجة أولى",
583
+ "experience_years": 15,
584
+ "completed_projects": 100,
585
+ "ongoing_projects": 8,
586
+ "min_project_value": 2000000,
587
+ "max_project_value": 100000000,
588
+ "specialties": "معالجة تربة، تثبيت، تحسين خواص، فحوصات",
589
+ "rating": 4.8,
590
+ "description": "شركة متخصصة في تنفيذ أعمال معالجة التربة والتثبيت وتحسين الخواص والفحوصات"
591
+ }
592
+ ])
593
+
594
+ # 10. أعمال الخرسانة
595
+ subcontractors_data.extend([
596
+ {
597
+ "id": "CONC-001",
598
+ "name": "شركة الخرسانة المتميزة",
599
+ "category": "أعمال الخرسانة",
600
+ "subcategory": "خرسانة جاهزة",
601
+ "contact_person": "م. عبدالرحمن الشهري",
602
+ "phone": "0557890123",
603
+ "email": "[email protected]",
604
+ "address": "الرياض - حي الربيع",
605
+ "classification": "درجة أولى",
606
+ "experience_years": 20,
607
+ "completed_projects": 200,
608
+ "ongoing_projects": 15,
609
+ "min_project_value": 2000000,
610
+ "max_project_value": 200000000,
611
+ "specialties": "خرسانة جاهزة، خرسانة خاصة، خرسانة مسلحة، خرسانة سابقة الإجهاد",
612
+ "rating": 4.9,
613
+ "description": "شركة متخصصة في تنفيذ أعمال الخرسانة الجاهزة والخرسانة الخاصة والخرسانة المسلحة والخرسانة سابقة الإجهاد"
614
+ },
615
+ {
616
+ "id": "CONC-002",
617
+ "name": "مؤسسة الهياكل الخرسانية",
618
+ "category": "أعمال الخرسانة",
619
+ "subcategory": "هياكل خرسانية",
620
+ "contact_person": "م. ماجد العنزي",
621
+ "phone": "0557901234",
622
+ "email": "[email protected]",
623
+ "address": "جدة - حي الروضة",
624
+ "classification": "درجة ثانية",
625
+ "experience_years": 15,
626
+ "completed_projects": 120,
627
+ "ongoing_projects": 8,
628
+ "min_project_value": 1000000,
629
+ "max_project_value": 80000000,
630
+ "specialties": "هياكل خرسانية، أعمدة، أسقف، جدران",
631
+ "rating": 4.7,
632
+ "description": "مؤسسة متخصصة في تنفيذ أعمال الهياكل الخرسانية والأعمدة والأسقف والجدران"
633
+ },
634
+ {
635
+ "id": "CONC-003",
636
+ "name": "شركة الخرسانة المتخصصة",
637
+ "category": "أعمال الخرسانة",
638
+ "subcategory": "خرسانة خاصة",
639
+ "contact_person": "م. خالد المالكي",
640
+ "phone": "0558012345",
641
+ "email": "[email protected]",
642
+ "address": "الدمام - حي الشاطئ",
643
+ "classification": "درجة أولى",
644
+ "experience_years": 18,
645
+ "completed_projects": 150,
646
+ "ongoing_projects": 12,
647
+ "min_project_value": 3000000,
648
+ "max_project_value": 250000000,
649
+ "specialties": "خرسانة خاصة، خرسانة عالية المقاومة، خرسانة مقاومة للكيماويات، خرسانة ذاتية الدمك",
650
+ "rating": 4.9,
651
+ "description": "شركة متخصصة في تنفيذ أعمال الخرسانة الخاصة والخرسانة عالية المقاومة والخرسانة المقاومة للكيماويات والخرسانة ذاتية الدمك"
652
+ }
653
+ ])
654
+
655
+ # تخزين البيانات في حالة الجلسة
656
+ st.session_state.subcontractors_catalog = pd.DataFrame(subcontractors_data)
657
+
658
+ def render(self):
659
+ """عرض واجهة كتالوج مقاولي الباطن"""
660
+
661
+ st.markdown("## كتالوج مقاولي الباطن")
662
+
663
+ # إنشاء تبويبات لعرض الكتالوج
664
+ tabs = st.tabs([
665
+ "عرض الكتالوج",
666
+ "إضافة مقاول",
667
+ "تحليل المقاولين",
668
+ "استيراد/تصدير"
669
+ ])
670
+
671
+ with tabs[0]:
672
+ self._render_catalog_view_tab()
673
+
674
+ with tabs[1]:
675
+ self._render_add_subcontractor_tab()
676
+
677
+ with tabs[2]:
678
+ self._render_analysis_tab()
679
+
680
+ with tabs[3]:
681
+ self._render_import_export_tab()
682
+
683
+ def _render_catalog_view_tab(self):
684
+ """عرض تبويب عرض الكتالوج"""
685
+
686
+ st.markdown("### عرض كتالوج مقاولي الباطن")
687
+
688
+ # استخراج البيانات
689
+ subcontractors_df = st.session_state.subcontractors_catalog
690
+
691
+ # إنشاء فلاتر للعرض
692
+ col1, col2, col3 = st.columns(3)
693
+
694
+ with col1:
695
+ # فلتر حسب الفئة
696
+ categories = ["الكل"] + sorted(subcontractors_df["category"].unique().tolist())
697
+ selected_category = st.selectbox("اختر فئة المقاول", categories)
698
+
699
+ with col2:
700
+ # فلتر حسب الفئة الفرعية
701
+ if selected_category != "الكل":
702
+ subcategories = ["الكل"] + sorted(subcontractors_df[subcontractors_df["category"] == selected_category]["subcategory"].unique().tolist())
703
+ else:
704
+ subcategories = ["الكل"] + sorted(subcontractors_df["subcategory"].unique().tolist())
705
+
706
+ selected_subcategory = st.selectbox("اختر التخصص", subcategories)
707
+
708
+ with col3:
709
+ # فلتر حسب التصنيف
710
+ classifications = ["الكل"] + sorted(subcontractors_df["classification"].unique().tolist())
711
+ selected_classification = st.selectbox("اختر التصنيف", classifications)
712
+
713
+ # تطبيق الفلاتر
714
+ filtered_df = subcontractors_df.copy()
715
+
716
+ if selected_category != "الكل":
717
+ filtered_df = filtered_df[filtered_df["category"] == selected_category]
718
+
719
+ if selected_subcategory != "الكل":
720
+ filtered_df = filtered_df[filtered_df["subcategory"] == selected_subcategory]
721
+
722
+ if selected_classification != "الكل":
723
+ filtered_df = filtered_df[filtered_df["classification"] == selected_classification]
724
+
725
+ # عرض البيانات
726
+ if not filtered_df.empty:
727
+ # عرض عدد النتائج
728
+ st.info(f"تم العثور على {len(filtered_df)} مقاول باطن")
729
+
730
+ # عرض المقاولين في شكل بطاقات
731
+ for i, (_, subcontractor) in enumerate(filtered_df.iterrows()):
732
+ col1, col2 = st.columns([1, 3])
733
+
734
+ with col1:
735
+ # عرض صورة المقاول (استخدام صورة افتراضية)
736
+ st.image("https://via.placeholder.com/150", caption=subcontractor["name"])
737
+
738
+ with col2:
739
+ # عرض معلومات المقاول
740
+ st.markdown(f"**{subcontractor['name']}** (الكود: {subcontractor['id']})")
741
+ st.markdown(f"الفئة: {subcontractor['category']} - {subcontractor['subcategory']}")
742
+ st.markdown(f"التصنيف: {subcontractor['classification']} | الخبرة: {subcontractor['experience_years']} سنة")
743
+ st.markdown(f"التقييم: {'⭐' * int(subcontractor['rating'])} ({subcontractor['rating']})")
744
+ st.markdown(f"المشاريع المنجزة: {subcontractor['completed_projects']} | المشاريع الجارية: {subcontractor['ongoing_projects']}")
745
+
746
+ # إضافة زر لعرض التفاصيل
747
+ if st.button(f"عرض التفاصيل الكاملة", key=f"details_{subcontractor['id']}"):
748
+ st.session_state.selected_subcontractor = subcontractor['id']
749
+ self._show_subcontractor_details(subcontractor)
750
+
751
+ st.markdown("---")
752
+ else:
753
+ st.warning("لا يوجد مقاولين يطابقون معايير البحث")
754
+
755
+ def _show_subcontractor_details(self, subcontractor):
756
+ """عرض تف��صيل المقاول"""
757
+
758
+ st.markdown(f"## تفاصيل مقاول الباطن: {subcontractor['name']}")
759
+
760
+ col1, col2 = st.columns([1, 2])
761
+
762
+ with col1:
763
+ # عرض صورة المقاول (استخدام صورة افتراضية)
764
+ st.image("https://via.placeholder.com/300", caption=subcontractor["name"])
765
+
766
+ with col2:
767
+ # عرض المعلومات الأساسية
768
+ st.markdown("### المعلومات الأساسية")
769
+ st.markdown(f"**الكود:** {subcontractor['id']}")
770
+ st.markdown(f"**الفئة:** {subcontractor['category']} - {subcontractor['subcategory']}")
771
+ st.markdown(f"**التصنيف:** {subcontractor['classification']}")
772
+ st.markdown(f"**سنوات الخبرة:** {subcontractor['experience_years']} سنة")
773
+ st.markdown(f"**التقييم:** {'⭐' * int(subcontractor['rating'])} ({subcontractor['rating']})")
774
+ st.markdown(f"**التخصصات:** {subcontractor['specialties']}")
775
+ st.markdown(f"**الوصف:** {subcontractor['description']}")
776
+
777
+ # عرض معلومات الاتصال
778
+ st.markdown("### معلومات الاتصال")
779
+
780
+ contact_col1, contact_col2 = st.columns(2)
781
+
782
+ with contact_col1:
783
+ st.markdown(f"**الشخص المسؤول:** {subcontractor['contact_person']}")
784
+ st.markdown(f"**الهاتف:** {subcontractor['phone']}")
785
+
786
+ with contact_col2:
787
+ st.markdown(f"**البريد الإلكتروني:** {subcontractor['email']}")
788
+ st.markdown(f"**العنوان:** {subcontractor['address']}")
789
+
790
+ # عرض معلومات المشاريع
791
+ st.markdown("### معلومات المشاريع")
792
+
793
+ projects_col1, projects_col2 = st.columns(2)
794
+
795
+ with projects_col1:
796
+ st.markdown(f"**المشاريع المنجزة:** {subcontractor['completed_projects']}")
797
+ st.markdown(f"**المشاريع الجارية:** {subcontractor['ongoing_projects']}")
798
+
799
+ with projects_col2:
800
+ st.markdown(f"**الحد الأدنى لقيمة المشروع:** {subcontractor['min_project_value']:,} ريال")
801
+ st.markdown(f"**الحد الأقصى لقيمة المشروع:** {subcontractor['max_project_value']:,} ريال")
802
+
803
+ # إضافة زر للتعديل
804
+ if st.button("تعديل بيانات المقاول"):
805
+ st.session_state.edit_subcontractor = subcontractor['id']
806
+ # هنا يمكن إضافة منطق التعديل
807
+
808
+ def _render_add_subcontractor_tab(self):
809
+ """عرض تبويب إضافة مقاول"""
810
+
811
+ st.markdown("### إضافة مقاول باطن جديد")
812
+
813
+ # استخراج البيانات
814
+ subcontractors_df = st.session_state.subcontractors_catalog
815
+
816
+ # إنشاء نموذج إضافة مقاول
817
+ with st.form("add_subcontractor_form"):
818
+ st.markdown("#### المعلومات الأساسية")
819
+
820
+ # الصف الأول
821
+ col1, col2 = st.columns(2)
822
+ with col1:
823
+ subcontractor_id = st.text_input("كود المقاول", value=f"SUB-{len(subcontractors_df) + 1:03d}")
824
+ subcontractor_name = st.text_input("اسم المقاول", placeholder="مثال: شركة الأنظمة الكهربائية المتكاملة")
825
+
826
+ with col2:
827
+ # استخراج الفئات والفئات الفرعية الموجودة
828
+ categories = sorted(subcontractors_df["category"].unique().tolist())
829
+ subcontractor_category = st.selectbox("فئة المقاول", categories)
830
+
831
+ # استخراج الفئات الفرعية بناءً على الفئة المختارة
832
+ subcategories = sorted(subcontractors_df[subcontractors_df["category"] == subcontractor_category]["subcategory"].unique().tolist())
833
+ subcontractor_subcategory = st.selectbox("التخصص", subcategories)
834
+
835
+ # الصف الثاني
836
+ col1, col2 = st.columns(2)
837
+ with col1:
838
+ subcontractor_classification = st.selectbox("التصنيف", ["درجة أولى", "درجة ثانية", "درجة ثالثة", "درجة رابعة", "درجة خامسة"])
839
+ with col2:
840
+ subcontractor_experience_years = st.number_input("سنوات الخبرة", min_value=1, max_value=50, step=1)
841
+
842
+ # معلومات الاتصال
843
+ st.markdown("#### معلومات الاتصال")
844
+
845
+ col1, col2 = st.columns(2)
846
+ with col1:
847
+ subcontractor_contact_person = st.text_input("الشخص المسؤول", placeholder="مثال: م. خالد العتيبي")
848
+ subcontractor_phone = st.text_input("الهاتف", placeholder="مثال: 0555123456")
849
+
850
+ with col2:
851
+ subcontractor_email = st.text_input("البريد الإلكتروني", placeholder="مثال: [email protected]")
852
+ subcontractor_address = st.text_input("العنوان", placeholder="مثال: الرياض - حي العليا")
853
+
854
+ # معلومات المشاريع
855
+ st.markdown("#### معلومات المشاريع")
856
+
857
+ col1, col2 = st.columns(2)
858
+ with col1:
859
+ subcontractor_completed_projects = st.number_input("عدد المشاريع المنجزة", min_value=0, step=1)
860
+ subcontractor_ongoing_projects = st.number_input("عدد المشاريع الجارية", min_value=0, step=1)
861
+
862
+ with col2:
863
+ subcontractor_min_project_value = st.number_input("الحد الأدنى لقيمة المشروع (ريال)", min_value=0, step=100000)
864
+ subcontractor_max_project_value = st.number_input("الحد الأقصى لقيمة المشروع (ريال)", min_value=0, step=1000000)
865
+
866
+ # معلومات إضافية
867
+ st.markdown("#### معلومات إضافية")
868
+
869
+ subcontractor_specialties = st.text_area("التخصصات", placeholder="مثال: تمديدات كهربائية، محطات توزيع، لوحات توزيع، أنظمة إنارة")
870
+ subcontractor_rating = st.slider("التقييم", min_value=1.0, max_value=5.0, step=0.1, value=4.0)
871
+ subcontractor_description = st.text_area("وصف المقاول", placeholder="أدخل وصفاً تفصيلياً للمقاول")
872
+
873
+ # زر الإضافة
874
+ submit_button = st.form_submit_button("إضافة المقاول")
875
+
876
+ if submit_button:
877
+ # التحقق من البيانات
878
+ if not subcontractor_name or not subcontractor_category or not subcontractor_subcategory:
879
+ st.error("يرجى إدخال المعلومات الأساسية للمقاول")
880
+ else:
881
+ # إنشاء مقاول جديد
882
+ new_subcontractor = {
883
+ "id": subcontractor_id,
884
+ "name": subcontractor_name,
885
+ "category": subcontractor_category,
886
+ "subcategory": subcontractor_subcategory,
887
+ "contact_person": subcontractor_contact_person,
888
+ "phone": subcontractor_phone,
889
+ "email": subcontractor_email,
890
+ "address": subcontractor_address,
891
+ "classification": subcontractor_classification,
892
+ "experience_years": subcontractor_experience_years,
893
+ "completed_projects": subcontractor_completed_projects,
894
+ "ongoing_projects": subcontractor_ongoing_projects,
895
+ "min_project_value": subcontractor_min_project_value,
896
+ "max_project_value": subcontractor_max_project_value,
897
+ "specialties": subcontractor_specialties,
898
+ "rating": subcontractor_rating,
899
+ "description": subcontractor_description
900
+ }
901
+
902
+ # إضافة المقاول إلى الكتالوج
903
+ st.session_state.subcontractors_catalog = pd.concat([
904
+ st.session_state.subcontractors_catalog,
905
+ pd.DataFrame([new_subcontractor])
906
+ ], ignore_index=True)
907
+
908
+ st.success(f"تمت إضافة المقاول {subcontractor_name} بنجاح!")
909
+
910
+ def _render_analysis_tab(self):
911
+ """عرض تبويب تحليل المقاولين"""
912
+
913
+ st.markdown("### تحليل مقاولي الباطن")
914
+
915
+ # استخراج البيانات
916
+ subcontractors_df = st.session_state.subcontractors_catalog
917
+
918
+ # تحليل توزيع المقاولين حسب الفئة
919
+ st.markdown("#### توزيع المقاولين حسب الفئة")
920
+
921
+ # حساب عدد المقاولين لكل فئة
922
+ category_counts = subcontractors_df["category"].value_counts().reset_index()
923
+ category_counts.columns = ["الفئة", "عدد المقاولين"]
924
+
925
+ # عرض الجدول
926
+ st.dataframe(category_counts, use_container_width=True)
927
+
928
+ # إنشاء رسم بياني للمقارنة
929
+ fig = px.bar(
930
+ category_counts,
931
+ x="الفئة",
932
+ y="عدد المقاولين",
933
+ title="توزيع مقاولي الباطن حسب الفئة",
934
+ color="الفئة",
935
+ text_auto=True
936
+ )
937
+
938
+ st.plotly_chart(fig, use_container_width=True)
939
+
940
+ # تحليل توزيع المقاولين حسب التصنيف
941
+ st.markdown("#### توزيع المقاولين حسب التصنيف")
942
+
943
+ # حساب عدد المقاولين لكل تصنيف
944
+ classification_counts = subcontractors_df["classification"].value_counts().reset_index()
945
+ classification_counts.columns = ["التصنيف", "عدد المقاولين"]
946
+
947
+ # إنشاء رسم بياني دائري
948
+ fig = px.pie(
949
+ classification_counts,
950
+ values="عدد المقاولين",
951
+ names="التصنيف",
952
+ title="توزيع مقاولي الباطن حسب التصنيف",
953
+ color="التصنيف"
954
+ )
955
+
956
+ st.plotly_chart(fig, use_container_width=True)
957
+
958
+ # تحليل متوسط التقييم حسب الفئة
959
+ st.markdown("#### متوسط التقييم حسب الفئة")
960
+
961
+ # حساب متوسط التقييم لكل فئة
962
+ category_ratings = subcontractors_df.groupby("category").agg({
963
+ "rating": "mean"
964
+ }).reset_index()
965
+
966
+ # تغيير أسماء الأعمدة
967
+ category_ratings.columns = ["الفئة", "متوسط التقييم"]
968
+
969
+ # عرض الجدول
970
+ st.dataframe(category_ratings, use_container_width=True)
971
+
972
+ # إنشاء رسم بياني للمقارنة
973
+ fig = px.bar(
974
+ category_ratings,
975
+ x="الفئة",
976
+ y="متوسط التقييم",
977
+ title="متوسط تقييم مقاولي الباطن حسب الفئة",
978
+ color="الفئة",
979
+ text_auto=True
980
+ )
981
+
982
+ st.plotly_chart(fig, use_container_width=True)
983
+
984
+ # تحليل متوسط سنوات الخبرة حسب التصنيف
985
+ st.markdown("#### متوسط سنوات الخبرة حسب التصنيف")
986
+
987
+ # حساب متوسط سنوات الخبرة لكل تصنيف
988
+ classification_experience = subcontractors_df.groupby("classification").agg({
989
+ "experience_years": "mean"
990
+ }).reset_index()
991
+
992
+ # تغيير أسماء الأعمدة
993
+ classification_experience.columns = ["التصنيف", "متوسط سنوات الخبرة"]
994
+
995
+ # عرض الجدول
996
+ st.dataframe(classification_experience, use_container_width=True)
997
+
998
+ # إنشاء رسم بياني للمقارنة
999
+ fig = px.bar(
1000
+ classification_experience,
1001
+ x="التصنيف",
1002
+ y="متوسط سنوات الخبرة",
1003
+ title="متوسط سنوات خبرة مقاولي الباطن حسب التصنيف",
1004
+ color="التصنيف",
1005
+ text_auto=True
1006
+ )
1007
+
1008
+ st.plotly_chart(fig, use_container_width=True)
1009
+
1010
+ # تحليل المقاولين الأعلى تقييماً
1011
+ st.markdown("#### المقاولين الأعلى تقييماً")
1012
+
1013
+ # استخراج المقاولين الأعلى تقييماً
1014
+ top_rated = subcontractors_df.sort_values(by="rating", ascending=False).head(10)
1015
+
1016
+ # إنشاء جدول للعرض
1017
+ top_rated_display = top_rated[["name", "category", "classification", "rating", "experience_years"]].copy()
1018
+ top_rated_display.columns = ["اسم المقاول", "الفئة", "التصنيف", "التقييم", "سنوات الخبرة"]
1019
+
1020
+ # عرض الجدول
1021
+ st.dataframe(top_rated_display, use_container_width=True)
1022
+
1023
+ # تحليل المقاولين الأكثر خبرة
1024
+ st.markdown("#### المقاولين الأكثر خبرة")
1025
+
1026
+ # استخراج المقاولين الأكثر خبرة
1027
+ most_experienced = subcontractors_df.sort_values(by="experience_years", ascending=False).head(10)
1028
+
1029
+ # إنشاء جدول للعرض
1030
+ most_experienced_display = most_experienced[["name", "category", "classification", "experience_years", "rating"]].copy()
1031
+ most_experienced_display.columns = ["اسم المقاول", "الفئة", "التصنيف", "سنوات الخبرة", "التقييم"]
1032
+
1033
+ # عرض الجدول
1034
+ st.dataframe(most_experienced_display, use_container_width=True)
1035
+
1036
+ def _render_import_export_tab(self):
1037
+ """عرض تبويب استيراد/تصدير"""
1038
+
1039
+ st.markdown("### استيراد وتصدير بيانات مقاولي الباطن")
1040
+
1041
+ # استيراد البيانات
1042
+ st.markdown("#### استيراد البيانات")
1043
+
1044
+ uploaded_file = st.file_uploader("اختر ملف Excel لاستيراد بيانات مقاولي الباطن", type=["xlsx", "xls"])
1045
+
1046
+ if uploaded_file is not None:
1047
+ try:
1048
+ # قراءة الملف
1049
+ imported_df = pd.read_excel(uploaded_file)
1050
+
1051
+ # عرض البيانات المستوردة
1052
+ st.dataframe(imported_df, use_container_width=True)
1053
+
1054
+ # زر الاستيراد
1055
+ if st.button("استيراد البيانات"):
1056
+ # التحقق من وجود الأعمدة المطلوبة
1057
+ required_columns = ["id", "name", "category", "subcategory", "classification"]
1058
+
1059
+ if all(col in imported_df.columns for col in required_columns):
1060
+ # دمج البيانات المستوردة مع البيانات الحالية
1061
+ st.session_state.subcontractors_catalog = pd.concat([
1062
+ st.session_state.subcontractors_catalog,
1063
+ imported_df
1064
+ ], ignore_index=True).drop_duplicates(subset=["id"])
1065
+
1066
+ st.success(f"تم استيراد {len(imported_df)} مقاول باطن بنجاح!")
1067
+ else:
1068
+ st.error("الملف المستورد لا يحتوي على الأعمدة المطلوبة")
1069
+
1070
+ except Exception as e:
1071
+ st.error(f"حدث خطأ أثناء استيراد الملف: {str(e)}")
1072
+
1073
+ # تصدير البيانات
1074
+ st.markdown("#### تصدير البيانات")
1075
+
1076
+ # اختيار تنسيق التصدير
1077
+ export_format = st.radio("اختر تنسيق التصدير", ["Excel", "CSV", "JSON"], horizontal=True)
1078
+
1079
+ if st.button("تصدير البيانات"):
1080
+ # استخراج البيانات
1081
+ subcontractors_df = st.session_state.subcontractors_catalog
1082
+
1083
+ # تصدير البيانات حسب التنسيق المختار
1084
+ if export_format == "Excel":
1085
+ # تصدير إلى Excel
1086
+ output = io.BytesIO()
1087
+ with pd.ExcelWriter(output, engine="openpyxl") as writer:
1088
+ subcontractors_df.to_excel(writer, index=False, sheet_name="Subcontractors")
1089
+
1090
+ # تحميل الملف
1091
+ st.download_button(
1092
+ label="تنزيل ملف Excel",
1093
+ data=output.getvalue(),
1094
+ file_name="subcontractors_catalog.xlsx",
1095
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1096
+ )
1097
+
1098
+ elif export_format == "CSV":
1099
+ # تصدير إلى CSV
1100
+ csv_data = subcontractors_df.to_csv(index=False)
1101
+
1102
+ # تحميل الملف
1103
+ st.download_button(
1104
+ label="تنزيل ملف CSV",
1105
+ data=csv_data,
1106
+ file_name="subcontractors_catalog.csv",
1107
+ mime="text/csv"
1108
+ )
1109
+
1110
+ else: # JSON
1111
+ # تصدير إلى JSON
1112
+ json_data = subcontractors_df.to_json(orient="records", force_ascii=False)
1113
+
1114
+ # تحميل الملف
1115
+ st.download_button(
1116
+ label="تنزيل ملف JSON",
1117
+ data=json_data,
1118
+ file_name="subcontractors_catalog.json",
1119
+ mime="application/json"
1120
+ )
1121
+
1122
+ def get_subcontractor_by_id(self, subcontractor_id):
1123
+ """الحصول على مقاول بواسطة الكود"""
1124
+
1125
+ subcontractors_df = st.session_state.subcontractors_catalog
1126
+ subcontractor = subcontractors_df[subcontractors_df["id"] == subcontractor_id]
1127
+
1128
+ if not subcontractor.empty:
1129
+ return subcontractor.iloc[0].to_dict()
1130
+
1131
+ return None
1132
+
1133
+ def get_subcontractors_by_category(self, category):
1134
+ """الحصول على المقاولين حسب الفئة"""
1135
+
1136
+ subcontractors_df = st.session_state.subcontractors_catalog
1137
+ subcontractors = subcontractors_df[subcontractors_df["category"] == category]
1138
+
1139
+ if not subcontractors.empty:
1140
+ return subcontractors.to_dict(orient="records")
1141
+
1142
+ return []
1143
+
1144
+ def get_top_rated_subcontractors(self, category=None, limit=5):
1145
+ """الحصول على المقاولين الأعلى تقييماً"""
1146
+
1147
+ subcontractors_df = st.session_state.subcontractors_catalog
1148
+
1149
+ if category:
1150
+ filtered_df = subcontractors_df[subcontractors_df["category"] == category]
1151
+ else:
1152
+ filtered_df = subcontractors_df
1153
+
1154
+ top_rated = filtered_df.sort_values(by="rating", ascending=False).head(limit)
1155
+
1156
+ if not top_rated.empty:
1157
+ return top_rated.to_dict(orient="records")
1158
+
1159
+ return []
pricing_system/modules/indirect_support/indirect_support_management.py ADDED
@@ -0,0 +1,1532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وحدة إدارة الإدارات المساندة - إدارة تكاليف الإدارات المساندة
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import plotly.express as px
9
+ import plotly.graph_objects as go
10
+ import os
11
+ import json
12
+ from datetime import datetime
13
+ import io
14
+
15
+ class IndirectSupportManagement:
16
+ """فئة إدارة الإدارات المساندة"""
17
+
18
+ def __init__(self):
19
+ """تهيئة وحدة إدارة الإدارات المساندة"""
20
+
21
+ # تهيئة حالة الجلسة لإدارة الإدارات المساندة
22
+ if 'indirect_support' not in st.session_state:
23
+ self._initialize_indirect_support()
24
+
25
+ def _initialize_indirect_support(self):
26
+ """تهيئة بيانات إدارة الإدارات المساندة"""
27
+
28
+ # إنشاء بيانات افتراضية للإدارات المساندة
29
+ departments = [
30
+ {
31
+ "id": "ADM-001",
32
+ "name": "الإدارة العليا",
33
+ "category": "إدارية",
34
+ "annual_cost": 2400000,
35
+ "staff_count": 5,
36
+ "allocation_method": "نسبة من قيمة المشروع",
37
+ "allocation_percentage": 0.02,
38
+ "description": "تشمل الرئيس التنفيذي والمدراء التنفيذيين"
39
+ },
40
+ {
41
+ "id": "ADM-002",
42
+ "name": "إدارة الموارد البشرية",
43
+ "category": "إدارية",
44
+ "annual_cost": 1200000,
45
+ "staff_count": 8,
46
+ "allocation_method": "نسبة من قيمة المشروع",
47
+ "allocation_percentage": 0.01,
48
+ "description": "مسؤولة عن التوظيف والتدريب وشؤون الموظفين"
49
+ },
50
+ {
51
+ "id": "ADM-003",
52
+ "name": "الإدارة المالية",
53
+ "category": "إدارية",
54
+ "annual_cost": 1500000,
55
+ "staff_count": 10,
56
+ "allocation_method": "نسبة من قيمة المشروع",
57
+ "allocation_percentage": 0.015,
58
+ "description": "مسؤولة عن المحاسبة والميزانية والتقارير المالية"
59
+ },
60
+ {
61
+ "id": "ADM-004",
62
+ "name": "إدارة تقنية المعلومات",
63
+ "category": "إدارية",
64
+ "annual_cost": 1800000,
65
+ "staff_count": 12,
66
+ "allocation_method": "تكلفة ثابتة",
67
+ "allocation_percentage": 0,
68
+ "fixed_cost": 50000,
69
+ "description": "مسؤولة عن البنية التحتية التقنية والدعم الفني"
70
+ },
71
+ {
72
+ "id": "TEC-001",
73
+ "name": "إدارة الجودة",
74
+ "category": "فنية",
75
+ "annual_cost": 1600000,
76
+ "staff_count": 15,
77
+ "allocation_method": "نسبة من قيمة المشروع",
78
+ "allocation_percentage": 0.02,
79
+ "description": "مسؤولة عن ضمان الجودة ومراقبة الجودة"
80
+ },
81
+ {
82
+ "id": "TEC-002",
83
+ "name": "إدارة السلامة",
84
+ "category": "فنية",
85
+ "annual_cost": 1400000,
86
+ "staff_count": 12,
87
+ "allocation_method": "نسبة من قيمة المشروع",
88
+ "allocation_percentage": 0.015,
89
+ "description": "مسؤولة عن السلامة المهنية وسلامة الموقع"
90
+ },
91
+ {
92
+ "id": "TEC-003",
93
+ "name": "إدارة المشتريات",
94
+ "category": "فنية",
95
+ "annual_cost": 2000000,
96
+ "staff_count": 18,
97
+ "allocation_method": "نسبة من قيمة المشروع",
98
+ "allocation_percentage": 0.025,
99
+ "description": "مسؤولة عن المشتريات والتعاقدات"
100
+ },
101
+ {
102
+ "id": "TEC-004",
103
+ "name": "إدارة المستودعات",
104
+ "category": "فنية",
105
+ "annual_cost": 1200000,
106
+ "staff_count": 20,
107
+ "allocation_method": "تكلفة ثابتة",
108
+ "allocation_percentage": 0,
109
+ "fixed_cost": 100000,
110
+ "description": "مسؤولة عن إدارة المخزون والمستودعات"
111
+ },
112
+ {
113
+ "id": "SUP-001",
114
+ "name": "إدارة النقل",
115
+ "category": "مساندة",
116
+ "annual_cost": 2500000,
117
+ "staff_count": 25,
118
+ "allocation_method": "تكلفة ثابتة",
119
+ "allocation_percentage": 0,
120
+ "fixed_cost": 200000,
121
+ "description": "مسؤولة عن نقل المواد والمعدات والعمالة"
122
+ },
123
+ {
124
+ "id": "SUP-002",
125
+ "name": "إدارة الصيانة",
126
+ "category": "مساندة",
127
+ "annual_cost": 1800000,
128
+ "staff_count": 22,
129
+ "allocation_method": "تكلفة ثابتة",
130
+ "allocation_percentage": 0,
131
+ "fixed_cost": 150000,
132
+ "description": "مسؤولة عن صيانة المعدات والآليات"
133
+ },
134
+ {
135
+ "id": "SUP-003",
136
+ "name": "إدارة الأمن",
137
+ "category": "مساندة",
138
+ "annual_cost": 1000000,
139
+ "staff_count": 30,
140
+ "allocation_method": "تكلفة ثابتة",
141
+ "allocation_percentage": 0,
142
+ "fixed_cost": 80000,
143
+ "description": "مسؤولة عن أمن المواقع والمنشآت"
144
+ },
145
+ {
146
+ "id": "SUP-004",
147
+ "name": "إدارة الخدمات العامة",
148
+ "category": "مساندة",
149
+ "annual_cost": 800000,
150
+ "staff_count": 15,
151
+ "allocation_method": "تكلفة ثابتة",
152
+ "allocation_percentage": 0,
153
+ "fixed_cost": 50000,
154
+ "description": "مسؤولة عن الخدمات العامة والضيافة"
155
+ }
156
+ ]
157
+
158
+ # إنشاء بيانات افتراضية للمشاريع
159
+ projects = [
160
+ {
161
+ "id": "PRJ-001",
162
+ "name": "مشروع تطوير طريق الملك عبدالله",
163
+ "value": 50000000,
164
+ "duration": 24,
165
+ "start_date": "2025-01-01",
166
+ "end_date": "2026-12-31",
167
+ "status": "جاري",
168
+ "location": "الرياض",
169
+ "client": "وزارة النقل",
170
+ "description": "مشروع تطوير وتوسعة طريق الملك عبدالله بطول 15 كم"
171
+ },
172
+ {
173
+ "id": "PRJ-002",
174
+ "name": "مشروع إنشاء شبكة صرف صحي",
175
+ "value": 30000000,
176
+ "duration": 18,
177
+ "start_date": "2025-03-01",
178
+ "end_date": "2026-08-31",
179
+ "status": "جاري",
180
+ "location": "جدة",
181
+ "client": "شركة المياه الوطنية",
182
+ "description": "مشروع إنشاء شبكة صرف صحي في حي النزهة بجدة"
183
+ },
184
+ {
185
+ "id": "PRJ-003",
186
+ "name": "مشروع إنشاء جسر تقاطع طريق الملك فهد",
187
+ "value": 80000000,
188
+ "duration": 30,
189
+ "start_date": "2025-02-01",
190
+ "end_date": "2027-07-31",
191
+ "status": "جاري",
192
+ "location": "الدمام",
193
+ "client": "أمانة المنطقة الشرقية",
194
+ "description": "مشروع إنشاء جسر علوي عند تقاطع طريق الملك فهد مع طريق الأمير محمد بن فهد"
195
+ }
196
+ ]
197
+
198
+ # إنشاء بيانات افتراضية لتخصيص الإدارات المساندة للمشاريع
199
+ allocations = []
200
+
201
+ for project in projects:
202
+ for department in departments:
203
+ if department["allocation_method"] == "نسبة من قيمة المشروع":
204
+ allocation_amount = project["value"] * department["allocation_percentage"]
205
+ else: # تكلفة ثابتة
206
+ allocation_amount = department.get("fixed_cost", 0)
207
+
208
+ allocations.append({
209
+ "project_id": project["id"],
210
+ "department_id": department["id"],
211
+ "allocation_amount": allocation_amount,
212
+ "allocation_method": department["allocation_method"],
213
+ "allocation_percentage": department["allocation_percentage"] if department["allocation_method"] == "نسبة من قيمة المشروع" else 0,
214
+ "fixed_cost": department.get("fixed_cost", 0) if department["allocation_method"] == "تكلفة ثابتة" else 0,
215
+ "notes": ""
216
+ })
217
+
218
+ # تخزين البيانات في حالة الجلسة
219
+ st.session_state.indirect_support = {
220
+ "departments": pd.DataFrame(departments),
221
+ "projects": pd.DataFrame(projects),
222
+ "allocations": pd.DataFrame(allocations)
223
+ }
224
+
225
+ def render(self):
226
+ """عرض واجهة إدارة الإدارات المساندة"""
227
+
228
+ st.markdown("## إدارة الإدارات المساندة")
229
+
230
+ # إنشاء تبويبات لعرض إدارة الإدارات المساندة
231
+ tabs = st.tabs([
232
+ "الإدارات المساندة",
233
+ "المشاريع",
234
+ "تخصيص التكاليف",
235
+ "التقارير"
236
+ ])
237
+
238
+ with tabs[0]:
239
+ self._render_departments_tab()
240
+
241
+ with tabs[1]:
242
+ self._render_projects_tab()
243
+
244
+ with tabs[2]:
245
+ self._render_allocations_tab()
246
+
247
+ with tabs[3]:
248
+ self._render_reports_tab()
249
+
250
+ def _render_departments_tab(self):
251
+ """عرض تبويب الإدارات المساندة"""
252
+
253
+ st.markdown("### الإدارات المساندة")
254
+
255
+ # استخراج البيانات
256
+ departments = st.session_state.indirect_support["departments"]
257
+
258
+ # إنشاء فلاتر للعرض
259
+ col1, col2 = st.columns(2)
260
+
261
+ with col1:
262
+ # فلتر حسب الفئة
263
+ categories = ["الكل"] + sorted(departments["category"].unique().tolist())
264
+ selected_category = st.selectbox("اختر فئة الإدارة", categories, key="departments_category")
265
+
266
+ with col2:
267
+ # فلتر حسب طريقة التخصيص
268
+ allocation_methods = ["الكل"] + sorted(departments["allocation_method"].unique().tolist())
269
+ selected_allocation_method = st.selectbox("اختر طريقة التخصيص", allocation_methods, key="departments_allocation_method")
270
+
271
+ # تطبيق الفلاتر
272
+ filtered_df = departments.copy()
273
+
274
+ if selected_category != "الكل":
275
+ filtered_df = filtered_df[filtered_df["category"] == selected_category]
276
+
277
+ if selected_allocation_method != "الكل":
278
+ filtered_df = filtered_df[filtered_df["allocation_method"] == selected_allocation_method]
279
+
280
+ # عرض البيانات
281
+ if not filtered_df.empty:
282
+ # عرض عدد النتائج
283
+ st.info(f"تم العثور على {len(filtered_df)} إدارة")
284
+
285
+ # إنشاء جدول للعرض
286
+ display_df = filtered_df[["id", "name", "category", "annual_cost", "staff_count", "allocation_method"]].copy()
287
+ display_df.columns = ["الكود", "الاسم", "الفئة", "التكلفة السنوية", "عدد الموظفين", "طريقة التخصيص"]
288
+
289
+ # عرض الجدول
290
+ st.dataframe(display_df, use_container_width=True)
291
+
292
+ # إضافة إدارة جديدة
293
+ st.markdown("### إضافة إدارة جديدة")
294
+
295
+ with st.form("add_department_form"):
296
+ # الصف الأول
297
+ col1, col2 = st.columns(2)
298
+
299
+ with col1:
300
+ department_id = st.text_input("كود الإدارة", value=f"DEP-{len(departments) + 1:03d}")
301
+ department_name = st.text_input("اسم الإدارة", placeholder="مثال: إدارة الموارد البشرية")
302
+
303
+ with col2:
304
+ department_category = st.selectbox("فئة الإدارة", ["إدارية", "فنية", "مساندة"])
305
+ department_staff_count = st.number_input("عدد الموظفين", min_value=1, step=1)
306
+
307
+ # الصف الثاني
308
+ col1, col2 = st.columns(2)
309
+
310
+ with col1:
311
+ department_annual_cost = st.number_input("التكلفة السنوية (ريال)", min_value=0, step=100000)
312
+
313
+ with col2:
314
+ department_allocation_method = st.selectbox("طريقة التخصيص", ["نسبة من قيمة المشروع", "تكلفة ثابتة"])
315
+
316
+ # الصف الثالث
317
+ if department_allocation_method == "نسبة من قيمة المشروع":
318
+ department_allocation_percentage = st.slider("نسبة التخصيص", min_value=0.0, max_value=0.1, value=0.01, step=0.001, format="%g%%") * 100
319
+ department_fixed_cost = 0
320
+ else: # تكلفة ثابتة
321
+ department_fixed_cost = st.number_input("التكلفة الثابتة (ريال)", min_value=0, step=10000)
322
+ department_allocation_percentage = 0
323
+
324
+ # الصف الرابع
325
+ department_description = st.text_area("وصف الإدارة", placeholder="أدخل وصفاً تفصيلياً للإدارة")
326
+
327
+ # زر الإضافة
328
+ submit_button = st.form_submit_button("إضافة الإدارة")
329
+
330
+ if submit_button:
331
+ # التحقق من البيانات
332
+ if not department_name or not department_category:
333
+ st.error("يرجى إدخال المعلومات الأساسية للإدارة")
334
+ else:
335
+ # إنشاء إدارة جديدة
336
+ new_department = {
337
+ "id": department_id,
338
+ "name": department_name,
339
+ "category": department_category,
340
+ "annual_cost": department_annual_cost,
341
+ "staff_count": department_staff_count,
342
+ "allocation_method": department_allocation_method,
343
+ "allocation_percentage": department_allocation_percentage / 100 if department_allocation_method == "نسبة من قيمة المشروع" else 0,
344
+ "fixed_cost": department_fixed_cost if department_allocation_method == "تكلفة ثابتة" else 0,
345
+ "description": department_description
346
+ }
347
+
348
+ # إضافة الإدارة إلى الجدول
349
+ st.session_state.indirect_support["departments"] = pd.concat([
350
+ st.session_state.indirect_support["departments"],
351
+ pd.DataFrame([new_department])
352
+ ], ignore_index=True)
353
+
354
+ # إضافة تخصيصات للإدارة الجديدة
355
+ projects = st.session_state.indirect_support["projects"]
356
+ allocations = st.session_state.indirect_support["allocations"]
357
+
358
+ new_allocations = []
359
+
360
+ for _, project in projects.iterrows():
361
+ if department_allocation_method == "نسبة من قيمة المشروع":
362
+ allocation_amount = project["value"] * (department_allocation_percentage / 100)
363
+ else: # تكلفة ثابتة
364
+ allocation_amount = department_fixed_cost
365
+
366
+ new_allocations.append({
367
+ "project_id": project["id"],
368
+ "department_id": department_id,
369
+ "allocation_amount": allocation_amount,
370
+ "allocation_method": department_allocation_method,
371
+ "allocation_percentage": department_allocation_percentage / 100 if department_allocation_method == "نسبة من قيمة المشروع" else 0,
372
+ "fixed_cost": department_fixed_cost if department_allocation_method == "تكلفة ثابتة" else 0,
373
+ "notes": ""
374
+ })
375
+
376
+ # إضافة التخصيصات الجديدة
377
+ st.session_state.indirect_support["allocations"] = pd.concat([
378
+ allocations,
379
+ pd.DataFrame(new_allocations)
380
+ ], ignore_index=True)
381
+
382
+ st.success(f"تمت إضافة الإدارة {department_name} بنجاح!")
383
+
384
+ # إعادة تحميل الصفحة
385
+ st.experimental_rerun()
386
+
387
+ # عرض تفاصيل الإدارات
388
+ st.markdown("### تفاصيل الإدارات")
389
+
390
+ selected_department_id = st.selectbox("اختر إدارة لعرض التفاصيل", filtered_df["id"].tolist(), key="department_details")
391
+
392
+ # استخراج الإدارة المختارة
393
+ selected_department = filtered_df[filtered_df["id"] == selected_department_id].iloc[0]
394
+
395
+ # عرض تفاصيل الإدارة
396
+ st.markdown(f"**الإدارة:** {selected_department['name']} ({selected_department['id']})")
397
+ st.markdown(f"**الفئة:** {selected_department['category']}")
398
+ st.markdown(f"**التكلفة السنوية:** {selected_department['annual_cost']:,} ريال")
399
+ st.markdown(f"**عدد الموظفين:** {selected_department['staff_count']} موظف")
400
+ st.markdown(f"**طريقة التخصيص:** {selected_department['allocation_method']}")
401
+
402
+ if selected_department["allocation_method"] == "نسبة من قيمة المشروع":
403
+ st.markdown(f"**نسبة التخصيص:** {selected_department['allocation_percentage'] * 100:.2f}%")
404
+ else: # تكلفة ثابتة
405
+ st.markdown(f"**التكلفة الثابتة:** {selected_department.get('fixed_cost', 0):,} ريال")
406
+
407
+ if "description" in selected_department and selected_department["description"]:
408
+ st.markdown(f"**الوصف:** {selected_department['description']}")
409
+
410
+ # عرض تخصيصات الإدارة
411
+ st.markdown("#### تخصيصات الإدارة للمشاريع")
412
+
413
+ # استخراج تخصيصات الإدارة
414
+ allocations = st.session_state.indirect_support["allocations"]
415
+ projects = st.session_state.indirect_support["projects"]
416
+
417
+ department_allocations = allocations[allocations["department_id"] == selected_department_id]
418
+
419
+ if not department_allocations.empty:
420
+ # دمج بيانات المشاريع
421
+ merged_allocations = pd.merge(
422
+ department_allocations,
423
+ projects[["id", "name", "value"]],
424
+ left_on="project_id",
425
+ right_on="id",
426
+ suffixes=("", "_project")
427
+ )
428
+
429
+ # إنشاء جدول للعرض
430
+ display_allocations = merged_allocations[["id_project", "name", "value", "allocation_amount"]].copy()
431
+ display_allocations.columns = ["كود المشروع", "اسم المشروع", "قيمة المشروع", "مبلغ التخصيص"]
432
+
433
+ # عرض الجدول
434
+ st.dataframe(display_allocations, use_container_width=True)
435
+
436
+ # إنشاء رسم بياني للتخصيصات
437
+ fig = px.bar(
438
+ display_allocations,
439
+ x="اسم المشروع",
440
+ y="مبلغ التخصيص",
441
+ title=f"تخصيصات إدارة {selected_department['name']} للمشاريع",
442
+ color="اسم المشروع",
443
+ text_auto=True
444
+ )
445
+
446
+ st.plotly_chart(fig, use_container_width=True)
447
+ else:
448
+ st.info("لا يوجد تخصيصات لهذه الإدارة")
449
+ else:
450
+ st.warning("لا يوجد إدارات تطابق معايير البحث")
451
+
452
+ def _render_projects_tab(self):
453
+ """عرض تبويب المشاريع"""
454
+
455
+ st.markdown("### المشاريع")
456
+
457
+ # استخراج البيانات
458
+ projects = st.session_state.indirect_support["projects"]
459
+
460
+ # إنشاء فلاتر للعرض
461
+ col1, col2 = st.columns(2)
462
+
463
+ with col1:
464
+ # فلتر حسب الحالة
465
+ statuses = ["الكل"] + sorted(projects["status"].unique().tolist())
466
+ selected_status = st.selectbox("اختر حالة المشروع", statuses, key="projects_status")
467
+
468
+ with col2:
469
+ # فلتر حسب الموقع
470
+ locations = ["الكل"] + sorted(projects["location"].unique().tolist())
471
+ selected_location = st.selectbox("اختر موقع المشروع", locations, key="projects_location")
472
+
473
+ # تطبيق الفلاتر
474
+ filtered_df = projects.copy()
475
+
476
+ if selected_status != "الكل":
477
+ filtered_df = filtered_df[filtered_df["status"] == selected_status]
478
+
479
+ if selected_location != "الكل":
480
+ filtered_df = filtered_df[filtered_df["location"] == selected_location]
481
+
482
+ # عرض البيانات
483
+ if not filtered_df.empty:
484
+ # عرض عدد النتائج
485
+ st.info(f"تم العثور على {len(filtered_df)} مشروع")
486
+
487
+ # إنشاء جدول للعرض
488
+ display_df = filtered_df[["id", "name", "value", "duration", "status", "location", "client"]].copy()
489
+ display_df.columns = ["الكود", "الاسم", "القيمة", "المدة (شهر)", "الحالة", "الموقع", "العميل"]
490
+
491
+ # عرض الجدول
492
+ st.dataframe(display_df, use_container_width=True)
493
+
494
+ # إضافة مشروع جديد
495
+ st.markdown("### إضافة مشروع جديد")
496
+
497
+ with st.form("add_project_form"):
498
+ # الصف الأول
499
+ col1, col2 = st.columns(2)
500
+
501
+ with col1:
502
+ project_id = st.text_input("كود المشروع", value=f"PRJ-{len(projects) + 1:03d}")
503
+ project_name = st.text_input("اسم المشروع", placeholder="مثال: مشروع تطوير طريق الملك عبدالله")
504
+
505
+ with col2:
506
+ project_value = st.number_input("قيمة المشروع (ريال)", min_value=0, step=1000000)
507
+ project_duration = st.number_input("مدة المشروع (شهر)", min_value=1, step=1)
508
+
509
+ # الصف الثاني
510
+ col1, col2 = st.columns(2)
511
+
512
+ with col1:
513
+ project_start_date = st.date_input("تاريخ البدء")
514
+ project_status = st.selectbox("حالة المشروع", ["جاري", "مكتمل", "متوقف", "ملغي"])
515
+
516
+ with col2:
517
+ project_end_date = st.date_input("تاريخ الانتهاء")
518
+ project_location = st.text_input("موقع المشروع", placeholder="مثال: الرياض")
519
+
520
+ # الصف الثالث
521
+ project_client = st.text_input("العميل", placeholder="مثال: وزارة النقل")
522
+ project_description = st.text_area("وصف المشروع", placeholder="أدخل وصفاً تفصيلياً للمشروع")
523
+
524
+ # زر الإضافة
525
+ submit_button = st.form_submit_button("إضافة المشروع")
526
+
527
+ if submit_button:
528
+ # التحقق من البيانات
529
+ if not project_name or not project_value or not project_location or not project_client:
530
+ st.error("يرجى إدخال المعلومات الأساسية للمشروع")
531
+ else:
532
+ # إنشاء مشروع جديد
533
+ new_project = {
534
+ "id": project_id,
535
+ "name": project_name,
536
+ "value": project_value,
537
+ "duration": project_duration,
538
+ "start_date": project_start_date.strftime("%Y-%m-%d"),
539
+ "end_date": project_end_date.strftime("%Y-%m-%d"),
540
+ "status": project_status,
541
+ "location": project_location,
542
+ "client": project_client,
543
+ "description": project_description
544
+ }
545
+
546
+ # إضافة المشروع إلى الجدول
547
+ st.session_state.indirect_support["projects"] = pd.concat([
548
+ st.session_state.indirect_support["projects"],
549
+ pd.DataFrame([new_project])
550
+ ], ignore_index=True)
551
+
552
+ # إضافة تخصيصات للمشروع الجديد
553
+ departments = st.session_state.indirect_support["departments"]
554
+ allocations = st.session_state.indirect_support["allocations"]
555
+
556
+ new_allocations = []
557
+
558
+ for _, department in departments.iterrows():
559
+ if department["allocation_method"] == "نسبة من قيمة المشروع":
560
+ allocation_amount = project_value * department["allocation_percentage"]
561
+ else: # تكلفة ثابتة
562
+ allocation_amount = department.get("fixed_cost", 0)
563
+
564
+ new_allocations.append({
565
+ "project_id": project_id,
566
+ "department_id": department["id"],
567
+ "allocation_amount": allocation_amount,
568
+ "allocation_method": department["allocation_method"],
569
+ "allocation_percentage": department["allocation_percentage"] if department["allocation_method"] == "نسبة من قيمة المشروع" else 0,
570
+ "fixed_cost": department.get("fixed_cost", 0) if department["allocation_method"] == "تكلفة ثابتة" else 0,
571
+ "notes": ""
572
+ })
573
+
574
+ # إضافة التخصيصات الجديدة
575
+ st.session_state.indirect_support["allocations"] = pd.concat([
576
+ allocations,
577
+ pd.DataFrame(new_allocations)
578
+ ], ignore_index=True)
579
+
580
+ st.success(f"تمت إضافة المشروع {project_name} بنجاح!")
581
+
582
+ # إعادة تحميل الصفحة
583
+ st.experimental_rerun()
584
+
585
+ # عرض تفاصيل المشاريع
586
+ st.markdown("### تفاصيل المشاريع")
587
+
588
+ selected_project_id = st.selectbox("اختر مشروع لعرض التفاصيل", filtered_df["id"].tolist(), key="project_details")
589
+
590
+ # استخراج المشروع المختا��
591
+ selected_project = filtered_df[filtered_df["id"] == selected_project_id].iloc[0]
592
+
593
+ # عرض تفاصيل المشروع
594
+ st.markdown(f"**المشروع:** {selected_project['name']} ({selected_project['id']})")
595
+ st.markdown(f"**القيمة:** {selected_project['value']:,} ريال")
596
+ st.markdown(f"**المدة:** {selected_project['duration']} شهر")
597
+ st.markdown(f"**تاريخ البدء:** {selected_project['start_date']}")
598
+ st.markdown(f"**تاريخ الانتهاء:** {selected_project['end_date']}")
599
+ st.markdown(f"**الحالة:** {selected_project['status']}")
600
+ st.markdown(f"**الموقع:** {selected_project['location']}")
601
+ st.markdown(f"**العميل:** {selected_project['client']}")
602
+
603
+ if "description" in selected_project and selected_project["description"]:
604
+ st.markdown(f"**الوصف:** {selected_project['description']}")
605
+
606
+ # عرض تخصيصات المشروع
607
+ st.markdown("#### تخصيصات الإدارات المساندة للمشروع")
608
+
609
+ # استخراج تخصيصات المشروع
610
+ allocations = st.session_state.indirect_support["allocations"]
611
+ departments = st.session_state.indirect_support["departments"]
612
+
613
+ project_allocations = allocations[allocations["project_id"] == selected_project_id]
614
+
615
+ if not project_allocations.empty:
616
+ # دمج بيانات الإدارات
617
+ merged_allocations = pd.merge(
618
+ project_allocations,
619
+ departments[["id", "name", "category"]],
620
+ left_on="department_id",
621
+ right_on="id",
622
+ suffixes=("", "_department")
623
+ )
624
+
625
+ # إنشاء جدول للعرض
626
+ display_allocations = merged_allocations[["id_department", "name", "category", "allocation_amount", "allocation_method"]].copy()
627
+ display_allocations.columns = ["كود الإدارة", "اسم الإدارة", "الفئة", "مبلغ التخصيص", "طريقة التخصيص"]
628
+
629
+ # عرض الجدول
630
+ st.dataframe(display_allocations, use_container_width=True)
631
+
632
+ # حساب إجمالي التخصيصات
633
+ total_allocation = display_allocations["مبلغ التخصيص"].sum()
634
+
635
+ st.markdown(f"**إجمالي تخصيصات الإدارات المساندة:** {total_allocation:,} ريال")
636
+ st.markdown(f"**نسبة التخصيصات من قيمة المشروع:** {(total_allocation / selected_project['value']) * 100:.2f}%")
637
+
638
+ # إنشاء رسم بياني للتخصيصات
639
+ fig = px.bar(
640
+ display_allocations,
641
+ x="اسم الإدارة",
642
+ y="مبلغ التخصيص",
643
+ title=f"تخصيصات الإدارات المساندة لمشروع {selected_project['name']}",
644
+ color="الفئة",
645
+ text_auto=True
646
+ )
647
+
648
+ st.plotly_chart(fig, use_container_width=True)
649
+
650
+ # إنشاء رسم بياني دائري للتخصيصات حسب الفئة
651
+ category_allocations = display_allocations.groupby("الفئة")["مبلغ التخصيص"].sum().reset_index()
652
+
653
+ fig = px.pie(
654
+ category_allocations,
655
+ values="مبلغ التخصيص",
656
+ names="الفئة",
657
+ title=f"توزيع تخصيصات الإدارات المساندة حسب الفئة لمشروع {selected_project['name']}",
658
+ color="الفئة"
659
+ )
660
+
661
+ st.plotly_chart(fig, use_container_width=True)
662
+ else:
663
+ st.info("لا يوجد تخصيصات لهذا المشروع")
664
+ else:
665
+ st.warning("لا يوجد مشاريع تطابق معايير البحث")
666
+
667
+ def _render_allocations_tab(self):
668
+ """عرض تبويب تخصيص التكاليف"""
669
+
670
+ st.markdown("### تخصيص تكاليف الإدارات المساندة")
671
+
672
+ # استخراج البيانات
673
+ allocations = st.session_state.indirect_support["allocations"]
674
+ departments = st.session_state.indirect_support["departments"]
675
+ projects = st.session_state.indirect_support["projects"]
676
+
677
+ # دمج البيانات
678
+ merged_allocations = pd.merge(
679
+ allocations,
680
+ departments[["id", "name", "category"]],
681
+ left_on="department_id",
682
+ right_on="id",
683
+ suffixes=("", "_department")
684
+ )
685
+
686
+ merged_allocations = pd.merge(
687
+ merged_allocations,
688
+ projects[["id", "name", "value", "status"]],
689
+ left_on="project_id",
690
+ right_on="id",
691
+ suffixes=("_department", "_project")
692
+ )
693
+
694
+ # إنشاء فلاتر للعرض
695
+ col1, col2, col3 = st.columns(3)
696
+
697
+ with col1:
698
+ # فلتر حسب المشروع
699
+ project_options = ["الكل"] + sorted(projects["name"].unique().tolist())
700
+ selected_project = st.selectbox("اختر المشروع", project_options, key="allocations_project")
701
+
702
+ with col2:
703
+ # فلتر حسب فئة الإدارة
704
+ department_categories = ["الكل"] + sorted(departments["category"].unique().tolist())
705
+ selected_department_category = st.selectbox("اختر فئة الإدارة", department_categories, key="allocations_department_category")
706
+
707
+ with col3:
708
+ # فلتر حسب طريقة التخصيص
709
+ allocation_methods = ["الكل"] + sorted(allocations["allocation_method"].unique().tolist())
710
+ selected_allocation_method = st.selectbox("اختر طريقة التخصيص", allocation_methods, key="allocations_method")
711
+
712
+ # تطبيق الفلاتر
713
+ filtered_df = merged_allocations.copy()
714
+
715
+ if selected_project != "الكل":
716
+ filtered_df = filtered_df[filtered_df["name_project"] == selected_project]
717
+
718
+ if selected_department_category != "الكل":
719
+ filtered_df = filtered_df[filtered_df["category"] == selected_department_category]
720
+
721
+ if selected_allocation_method != "الكل":
722
+ filtered_df = filtered_df[filtered_df["allocation_method"] == selected_allocation_method]
723
+
724
+ # عرض البيانات
725
+ if not filtered_df.empty:
726
+ # عرض عدد النتائج
727
+ st.info(f"تم العثور على {len(filtered_df)} تخصيص")
728
+
729
+ # إنشاء جدول للعرض
730
+ display_df = filtered_df[["id_project", "name_project", "id_department", "name_department", "category", "allocation_method", "allocation_amount"]].copy()
731
+ display_df.columns = ["كود المشروع", "اسم المشروع", "كود الإدارة", "اسم الإدارة", "فئة الإدارة", "طريقة التخصيص", "مبلغ التخصيص"]
732
+
733
+ # عرض الجدول
734
+ st.dataframe(display_df, use_container_width=True)
735
+
736
+ # تعديل التخصيصات
737
+ st.markdown("### تعديل التخصيصات")
738
+
739
+ # اختيار مشروع وإدارة لتعديل التخصيص
740
+ col1, col2 = st.columns(2)
741
+
742
+ with col1:
743
+ edit_project_id = st.selectbox("اختر المشروع", projects["id"].tolist(), key="edit_allocation_project")
744
+
745
+ with col2:
746
+ edit_department_id = st.selectbox("اختر الإدارة", departments["id"].tolist(), key="edit_allocation_department")
747
+
748
+ # استخراج التخصيص المختار
749
+ selected_allocation = allocations[
750
+ (allocations["project_id"] == edit_project_id) &
751
+ (allocations["department_id"] == edit_department_id)
752
+ ]
753
+
754
+ if not selected_allocation.empty:
755
+ selected_allocation = selected_allocation.iloc[0]
756
+
757
+ # استخراج بيانات المشروع والإدارة
758
+ selected_project = projects[projects["id"] == edit_project_id].iloc[0]
759
+ selected_department = departments[departments["id"] == edit_department_id].iloc[0]
760
+
761
+ st.markdown(f"**المشروع:** {selected_project['name']} ({selected_project['id']})")
762
+ st.markdown(f"**الإدارة:** {selected_department['name']} ({selected_department['id']})")
763
+ st.markdown(f"**طريقة التخصيص الحالية:** {selected_allocation['allocation_method']}")
764
+ st.markdown(f"**مبلغ التخصيص الحالي:** {selected_allocation['allocation_amount']:,} ريال")
765
+
766
+ # نموذج تعديل التخصيص
767
+ with st.form("edit_allocation_form"):
768
+ # اختيار طريقة التخصيص
769
+ allocation_method = st.selectbox(
770
+ "طريقة التخصيص",
771
+ ["نسبة من قيمة المشروع", "تكلفة ثابتة"],
772
+ index=0 if selected_allocation["allocation_method"] == "نسبة من قيمة المشروع" else 1,
773
+ key="edit_allocation_method"
774
+ )
775
+
776
+ # إدخال قي��ة التخصيص
777
+ if allocation_method == "نسبة من قيمة المشروع":
778
+ allocation_percentage = st.slider(
779
+ "نسبة التخصيص",
780
+ min_value=0.0,
781
+ max_value=0.1,
782
+ value=float(selected_allocation["allocation_percentage"]),
783
+ step=0.001,
784
+ format="%g%%",
785
+ key="edit_allocation_percentage"
786
+ ) * 100
787
+
788
+ # حساب مبلغ التخصيص
789
+ allocation_amount = selected_project["value"] * (allocation_percentage / 100)
790
+
791
+ st.markdown(f"**مبلغ التخصيص المحسوب:** {allocation_amount:,} ريال")
792
+
793
+ fixed_cost = 0
794
+ else: # تكلفة ثابتة
795
+ fixed_cost = st.number_input(
796
+ "التكلفة الثابتة (ريال)",
797
+ min_value=0,
798
+ value=int(selected_allocation["fixed_cost"]),
799
+ step=10000,
800
+ key="edit_allocation_fixed_cost"
801
+ )
802
+
803
+ allocation_amount = fixed_cost
804
+ allocation_percentage = 0
805
+
806
+ # ملاحظات
807
+ notes = st.text_area(
808
+ "ملاحظات",
809
+ value=selected_allocation["notes"] if "notes" in selected_allocation else "",
810
+ key="edit_allocation_notes"
811
+ )
812
+
813
+ # زر الحفظ
814
+ submit_button = st.form_submit_button("حفظ التعديلات")
815
+
816
+ if submit_button:
817
+ # تحديث التخصيص
818
+ allocation_index = allocations[
819
+ (allocations["project_id"] == edit_project_id) &
820
+ (allocations["department_id"] == edit_department_id)
821
+ ].index[0]
822
+
823
+ allocations.at[allocation_index, "allocation_method"] = allocation_method
824
+ allocations.at[allocation_index, "allocation_percentage"] = allocation_percentage / 100 if allocation_method == "نسبة من قيمة المشروع" else 0
825
+ allocations.at[allocation_index, "fixed_cost"] = fixed_cost if allocation_method == "تكلفة ثابتة" else 0
826
+ allocations.at[allocation_index, "allocation_amount"] = allocation_amount
827
+ allocations.at[allocation_index, "notes"] = notes
828
+
829
+ # تحديث حالة الجلسة
830
+ st.session_state.indirect_support["allocations"] = allocations
831
+
832
+ st.success("تم تحديث التخصيص بنجاح!")
833
+
834
+ # إعادة تحميل الصفحة
835
+ st.experimental_rerun()
836
+ else:
837
+ st.warning("لم يتم العثور على تخصيص للمشروع والإدارة المختارين")
838
+
839
+ # عرض ملخص التخصيصات
840
+ st.markdown("### ملخص التخصيصات")
841
+
842
+ # حساب إجمالي التخصيصات لكل مشروع
843
+ project_allocations = filtered_df.groupby("name_project")["allocation_amount"].sum().reset_index()
844
+ project_allocations.columns = ["المشروع", "إجمالي التخصيصات"]
845
+
846
+ # عرض الجدول
847
+ st.dataframe(project_allocations, use_container_width=True)
848
+
849
+ # إنشاء رسم بياني للتخصيصات حسب المشروع
850
+ fig = px.bar(
851
+ project_allocations,
852
+ x="المشروع",
853
+ y="إجمالي التخصيصات",
854
+ title="إجمالي تخصيصات الإدارات المساندة حسب المشروع",
855
+ color="المشروع",
856
+ text_auto=True
857
+ )
858
+
859
+ st.plotly_chart(fig, use_container_width=True)
860
+
861
+ # حساب إجمالي التخصيصات لكل فئة إدارة
862
+ category_allocations = filtered_df.groupby("category")["allocation_amount"].sum().reset_index()
863
+ category_allocations.columns = ["فئة الإدارة", "إجمالي التخصيصات"]
864
+
865
+ # عرض الجدول
866
+ st.dataframe(category_allocations, use_container_width=True)
867
+
868
+ # إنشاء رسم بياني للتخصيصات حسب فئة الإدارة
869
+ fig = px.pie(
870
+ category_allocations,
871
+ values="إجمالي التخصيصات",
872
+ names="فئة الإدارة",
873
+ title="توزيع تخصيصات الإدارات المساندة حسب الفئة",
874
+ color="فئة الإدارة"
875
+ )
876
+
877
+ st.plotly_chart(fig, use_container_width=True)
878
+ else:
879
+ st.warning("لا يوجد تخصيصات تطابق معايير البحث")
880
+
881
+ def _render_reports_tab(self):
882
+ """عرض تبويب التقارير"""
883
+
884
+ st.markdown("### تقارير الإدارات المساندة")
885
+
886
+ # استخراج البيانات
887
+ allocations = st.session_state.indirect_support["allocations"]
888
+ departments = st.session_state.indirect_support["departments"]
889
+ projects = st.session_state.indirect_support["projects"]
890
+
891
+ # دمج البيانات
892
+ merged_allocations = pd.merge(
893
+ allocations,
894
+ departments[["id", "name", "category", "annual_cost", "staff_count"]],
895
+ left_on="department_id",
896
+ right_on="id",
897
+ suffixes=("", "_department")
898
+ )
899
+
900
+ merged_allocations = pd.merge(
901
+ merged_allocations,
902
+ projects[["id", "name", "value", "status", "duration"]],
903
+ left_on="project_id",
904
+ right_on="id",
905
+ suffixes=("_department", "_project")
906
+ )
907
+
908
+ # اختيار نوع التقرير
909
+ report_type = st.selectbox(
910
+ "اختر نوع التقرير",
911
+ [
912
+ "تقرير تكاليف الإدارات المساندة",
913
+ "تقرير تخصيصات المشاريع",
914
+ "تقرير مقارنة التكاليف",
915
+ "تقرير تحليل الكفاءة"
916
+ ],
917
+ key="report_type"
918
+ )
919
+
920
+ if report_type == "تقرير تكاليف الإدارات المساندة":
921
+ self._render_departments_cost_report(departments, merged_allocations)
922
+ elif report_type == "تقرير تخصيصات المشاريع":
923
+ self._render_projects_allocation_report(projects, merged_allocations)
924
+ elif report_type == "تقرير مقارنة التكاليف":
925
+ self._render_cost_comparison_report(departments, projects, merged_allocations)
926
+ elif report_type == "تقرير تحليل الكفاءة":
927
+ self._render_efficiency_analysis_report(departments, projects, merged_allocations)
928
+
929
+ def _render_departments_cost_report(self, departments, merged_allocations):
930
+ """عرض تقرير تكاليف الإدارات المساندة"""
931
+
932
+ st.markdown("#### تقرير تكاليف الإدارات المساندة")
933
+
934
+ # حساب إجمالي التكاليف السنوية
935
+ total_annual_cost = departments["annual_cost"].sum()
936
+ total_staff_count = departments["staff_count"].sum()
937
+
938
+ # عرض ملخص التكاليف
939
+ st.markdown(f"**إجمالي التكاليف السنوية للإدارات المساندة:** {total_annual_cost:,} ريال")
940
+ st.markdown(f"**إجمالي عدد الموظفين:** {total_staff_count} موظف")
941
+ st.markdown(f"**متوسط تكلفة الموظف السنوية:** {total_annual_cost / total_staff_count:,.2f} ريال")
942
+
943
+ # حساب التكاليف حسب الفئة
944
+ category_costs = departments.groupby("category").agg({
945
+ "annual_cost": "sum",
946
+ "staff_count": "sum"
947
+ }).reset_index()
948
+
949
+ category_costs["نسبة التكلفة"] = category_costs["annual_cost"] / total_annual_cost * 100
950
+ category_costs["متوسط تكلفة الموظف"] = category_costs["annual_cost"] / category_costs["staff_count"]
951
+
952
+ # تغيير أسماء الأعمدة
953
+ category_costs.columns = ["الفئة", "التكلفة السنوية", "عدد الموظفين", "نسبة التكلفة", "متوسط تكلفة الموظف"]
954
+
955
+ # عرض الجدول
956
+ st.dataframe(category_costs, use_container_width=True)
957
+
958
+ # إنشاء رسم بياني للتكاليف حسب الفئة
959
+ fig = px.pie(
960
+ category_costs,
961
+ values="التكلفة السنوية",
962
+ names="الفئة",
963
+ title="توزيع تكاليف الإدارات المساندة حسب الفئة",
964
+ color="الفئة"
965
+ )
966
+
967
+ st.plotly_chart(fig, use_container_width=True)
968
+
969
+ # إنشاء رسم بياني لمتوسط تكلفة الموظف حسب الفئة
970
+ fig = px.bar(
971
+ category_costs,
972
+ x="الفئة",
973
+ y="متوسط تكلفة الموظف",
974
+ title="متوسط تكلفة الموظف السنوية حسب فئة الإدارة",
975
+ color="الفئة",
976
+ text_auto=True
977
+ )
978
+
979
+ st.plotly_chart(fig, use_container_width=True)
980
+
981
+ # حساب إجمالي التخصيصات لكل إدارة
982
+ department_allocations = merged_allocations.groupby("name_department").agg({
983
+ "allocation_amount": "sum",
984
+ "annual_cost": "first",
985
+ "category": "first"
986
+ }).reset_index()
987
+
988
+ department_allocations["نسبة التغطية"] = department_allocations["allocation_amount"] / department_allocations["annual_cost"] * 100
989
+
990
+ # تغيير أسماء الأعمدة
991
+ department_allocations.columns = ["الإدارة", "إجمالي التخصيصات", "التكلفة السنوية", "الفئة", "نسبة التغطية"]
992
+
993
+ # ترتيب البيانات حسب نسبة التغطية
994
+ department_allocations = department_allocations.sort_values(by="نسبة التغطية", ascending=False)
995
+
996
+ # عرض الجدول
997
+ st.dataframe(department_allocations, use_container_width=True)
998
+
999
+ # إنشاء رسم بياني لنسبة التغطية
1000
+ fig = px.bar(
1001
+ department_allocations,
1002
+ x="الإدارة",
1003
+ y="نسبة التغطية",
1004
+ title="نسبة تغطية تكاليف الإدارات المساندة من خلال التخصيصات",
1005
+ color="الفئة",
1006
+ text_auto=True
1007
+ )
1008
+
1009
+ # إضافة خط أفقي عند 100%
1010
+ fig.add_shape(
1011
+ type="line",
1012
+ x0=-0.5,
1013
+ y0=100,
1014
+ x1=len(department_allocations) - 0.5,
1015
+ y1=100,
1016
+ line=dict(
1017
+ color="red",
1018
+ width=2,
1019
+ dash="dash"
1020
+ )
1021
+ )
1022
+
1023
+ st.plotly_chart(fig, use_container_width=True)
1024
+
1025
+ # حساب إجمالي التغطية
1026
+ total_allocations = department_allocations["إجمالي التخصيصات"].sum()
1027
+ total_coverage = total_allocations / total_annual_cost * 100
1028
+
1029
+ st.markdown(f"**إجمالي التخصيصات:** {total_allocations:,} ريال")
1030
+ st.markdown(f"**نسبة التغطية الإجمالية:** {total_coverage:.2f}%")
1031
+
1032
+ if total_coverage < 100:
1033
+ st.warning(f"هناك عجز في تغطية تكاليف الإدارات المساندة بنسبة {100 - total_coverage:.2f}%")
1034
+ elif total_coverage > 100:
1035
+ st.success(f"هناك فائض في تغطية تكاليف الإدارات المساندة بنسبة {total_coverage - 100:.2f}%")
1036
+ else:
1037
+ st.success("تمت تغطية تكاليف الإدارات المساندة بالكامل")
1038
+
1039
+ def _render_projects_allocation_report(self, projects, merged_allocations):
1040
+ """عرض تقرير تخصيصات المشاريع"""
1041
+
1042
+ st.markdown("#### تقرير تخصيصات المشاريع")
1043
+
1044
+ # حساب إجمالي قيم المشاريع
1045
+ total_projects_value = projects["value"].sum()
1046
+
1047
+ # عرض ملخص المشاريع
1048
+ st.markdown(f"**عدد المشاريع:** {len(projects)}")
1049
+ st.markdown(f"**إجمالي قيم المشاريع:** {total_projects_value:,} ريال")
1050
+
1051
+ # حساب إجمالي التخصيصات لكل مشروع
1052
+ project_allocations = merged_allocations.groupby("name_project").agg({
1053
+ "allocation_amount": "sum",
1054
+ "value": "first",
1055
+ "status": "first",
1056
+ "duration": "first"
1057
+ }).reset_index()
1058
+
1059
+ project_allocations["نسبة التخصيص"] = project_allocations["allocation_amount"] / project_allocations["value"] * 100
1060
+ project_allocations["تكلفة التخصيص الشهرية"] = project_allocations["allocation_amount"] / project_allocations["duration"]
1061
+
1062
+ # تغيير أسماء الأعمدة
1063
+ project_allocations.columns = ["المشروع", "إجمالي التخصيصات", "قيمة المشروع", "الحالة", "المدة (شهر)", "نسبة التخصيص", "تكلفة التخصيص الشهرية"]
1064
+
1065
+ # ترتيب البيانات حسب نسبة التخصيص
1066
+ project_allocations = project_allocations.sort_values(by="نسبة التخصيص", ascending=False)
1067
+
1068
+ # عرض الجدول
1069
+ st.dataframe(project_allocations, use_container_width=True)
1070
+
1071
+ # إنشاء رسم بياني لنسبة التخصيص
1072
+ fig = px.bar(
1073
+ project_allocations,
1074
+ x="المشروع",
1075
+ y="نسبة التخصيص",
1076
+ title="نسبة تخصيص الإدارات المساندة من قيمة المشروع",
1077
+ color="الحالة",
1078
+ text_auto=True
1079
+ )
1080
+
1081
+ st.plotly_chart(fig, use_container_width=True)
1082
+
1083
+ # إنشاء رسم بياني للتكلفة الشهرية
1084
+ fig = px.bar(
1085
+ project_allocations,
1086
+ x="المشروع",
1087
+ y="تكلفة التخصيص الشهرية",
1088
+ title="تكلفة تخصيص الإدارات المساندة الشهرية لكل مشروع",
1089
+ color="الحالة",
1090
+ text_auto=True
1091
+ )
1092
+
1093
+ st.plotly_chart(fig, use_container_width=True)
1094
+
1095
+ # حساب توزيع التخصيصات حسب فئة الإدارة لكل مشروع
1096
+ category_allocations = merged_allocations.groupby(["name_project", "category"]).agg({
1097
+ "allocation_amount": "sum"
1098
+ }).reset_index()
1099
+
1100
+ # تغيير أسماء الأعمدة
1101
+ category_allocations.columns = ["المشروع", "فئة الإدارة", "مبلغ التخصيص"]
1102
+
1103
+ # إنشاء رسم بياني للتخصيصات حسب فئة الإدارة لكل مشروع
1104
+ fig = px.bar(
1105
+ category_allocations,
1106
+ x="المشروع",
1107
+ y="مبلغ التخصيص",
1108
+ color="فئة الإدارة",
1109
+ title="توزيع تخصيصات الإدارات المساندة حسب الفئة لكل مشروع",
1110
+ barmode="stack",
1111
+ text_auto=True
1112
+ )
1113
+
1114
+ st.plotly_chart(fig, use_container_width=True)
1115
+
1116
+ # حساب متوسط نسبة التخصيص
1117
+ avg_allocation_percentage = project_allocations["نسبة التخصيص"].mean()
1118
+
1119
+ st.markdown(f"**متوسط نسبة تخصيص الإدارات المساندة من قيمة المشروع:** {avg_allocation_percentage:.2f}%")
1120
+
1121
+ # تحليل العلاقة بين قيمة المشروع ونسبة التخصيص
1122
+ fig = px.scatter(
1123
+ project_allocations,
1124
+ x="قيمة المشروع",
1125
+ y="نسبة التخصيص",
1126
+ title="العلاقة بين قيمة المشروع ونسبة تخصيص الإدارات المساندة",
1127
+ color="الحالة",
1128
+ size="المدة (شهر)",
1129
+ hover_data=["المشروع"],
1130
+ trendline="ols"
1131
+ )
1132
+
1133
+ st.plotly_chart(fig, use_container_width=True)
1134
+
1135
+ # تحليل العلاقة بين مدة المشروع وتكلفة التخصيص الشهرية
1136
+ fig = px.scatter(
1137
+ project_allocations,
1138
+ x="المدة (شهر)",
1139
+ y="تكلفة التخصيص الشهرية",
1140
+ title="العلاقة بين مدة المشروع وتكلفة تخصيص الإدارات المساندة الشهرية",
1141
+ color="الحالة",
1142
+ size="قيمة المشروع",
1143
+ hover_data=["المشروع"],
1144
+ trendline="ols"
1145
+ )
1146
+
1147
+ st.plotly_chart(fig, use_container_width=True)
1148
+
1149
+ def _render_cost_comparison_report(self, departments, projects, merged_allocations):
1150
+ """عرض تقرير مقارنة التكاليف"""
1151
+
1152
+ st.markdown("#### تقرير مقارنة التكاليف")
1153
+
1154
+ # حساب إجمالي التكاليف السنوية
1155
+ total_annual_cost = departments["annual_cost"].sum()
1156
+
1157
+ # حساب إجمالي قيم المشاريع
1158
+ total_projects_value = projects["value"].sum()
1159
+
1160
+ # حساب إجمالي التخصيصات
1161
+ total_allocations = merged_allocations["allocation_amount"].sum()
1162
+
1163
+ # عرض ملخص التكاليف
1164
+ st.markdown(f"**إجمالي التكاليف السنوية للإدارات المساندة:** {total_annual_cost:,} ريال")
1165
+ st.markdown(f"**إجمالي قيم المشاريع:** {total_projects_value:,} ريال")
1166
+ st.markdown(f"**إجمالي التخصيصات:** {total_allocations:,} ريال")
1167
+ st.markdown(f"**نسبة التكاليف السنوية من إجمالي قيم المشاريع:** {(total_annual_cost / total_projects_value) * 100:.2f}%")
1168
+ st.markdown(f"**نسبة التخصيصات من إجمالي قيم المشاريع:** {(total_allocations / total_projects_value) * 100:.2f}%")
1169
+ st.markdown(f"**نسبة تغطية التكاليف السنوية من خلال التخصيصات:** {(total_allocations / total_annual_cost) * 100:.2f}%")
1170
+
1171
+ # إنشاء بيانات للرسم البياني
1172
+ comparison_data = pd.DataFrame({
1173
+ "البند": ["التكاليف السنوية", "التخصيصات"],
1174
+ "القيمة": [total_annual_cost, total_allocations]
1175
+ })
1176
+
1177
+ # إنشاء رسم بياني للمقارنة
1178
+ fig = px.bar(
1179
+ comparison_data,
1180
+ x="البند",
1181
+ y="القيمة",
1182
+ title="مقارنة بين التكاليف السنوية والتخصيصات",
1183
+ color="البند",
1184
+ text_auto=True
1185
+ )
1186
+
1187
+ st.plotly_chart(fig, use_container_width=True)
1188
+
1189
+ # حساب التكاليف والتخصيصات حسب فئة الإدارة
1190
+ category_costs = departments.groupby("category").agg({
1191
+ "annual_cost": "sum"
1192
+ }).reset_index()
1193
+
1194
+ category_allocations = merged_allocations.groupby("category").agg({
1195
+ "allocation_amount": "sum"
1196
+ }).reset_index()
1197
+
1198
+ # دمج البيانات
1199
+ category_comparison = pd.merge(
1200
+ category_costs,
1201
+ category_allocations,
1202
+ on="category",
1203
+ how="left"
1204
+ )
1205
+
1206
+ category_comparison["نسبة التغطية"] = category_comparison["allocation_amount"] / category_comparison["annual_cost"] * 100
1207
+
1208
+ # تغيير أسماء الأعمدة
1209
+ category_comparison.columns = ["الفئة", "التكلفة السنوية", "التخصيصات", "نسبة التغطية"]
1210
+
1211
+ # عرض الجدول
1212
+ st.dataframe(category_comparison, use_container_width=True)
1213
+
1214
+ # إنشاء رسم بياني للمقارنة حسب الفئة
1215
+ category_comparison_data = []
1216
+
1217
+ for _, row in category_comparison.iterrows():
1218
+ category_comparison_data.extend([
1219
+ {"الفئة": row["الفئة"], "البند": "التكلفة السنوية", "القيمة": row["التكلفة السنوية"]},
1220
+ {"الفئة": row["الفئة"], "البند": "التخصيصات", "القيمة": row["التخصيصات"]}
1221
+ ])
1222
+
1223
+ category_comparison_df = pd.DataFrame(category_comparison_data)
1224
+
1225
+ fig = px.bar(
1226
+ category_comparison_df,
1227
+ x="الفئة",
1228
+ y="القيمة",
1229
+ color="البند",
1230
+ title="مقارنة بين التكاليف السنوية والتخصيصات حسب فئة الإدارة",
1231
+ barmode="group",
1232
+ text_auto=True
1233
+ )
1234
+
1235
+ st.plotly_chart(fig, use_container_width=True)
1236
+
1237
+ # إنشاء رسم بياني لنسبة التغطية
1238
+ fig = px.bar(
1239
+ category_comparison,
1240
+ x="الفئة",
1241
+ y="نسبة التغطية",
1242
+ title="نسبة تغطية التكاليف السنوية من خلال التخصيصات حسب فئة الإدارة",
1243
+ color="الفئة",
1244
+ text_auto=True
1245
+ )
1246
+
1247
+ # إضافة خط أفقي عند 100%
1248
+ fig.add_shape(
1249
+ type="line",
1250
+ x0=-0.5,
1251
+ y0=100,
1252
+ x1=len(category_comparison) - 0.5,
1253
+ y1=100,
1254
+ line=dict(
1255
+ color="red",
1256
+ width=2,
1257
+ dash="dash"
1258
+ )
1259
+ )
1260
+
1261
+ st.plotly_chart(fig, use_container_width=True)
1262
+
1263
+ # تحليل تأثير التخصيصات على تكلفة المشاريع
1264
+ project_allocations = merged_allocations.groupby("name_project").agg({
1265
+ "allocation_amount": "sum",
1266
+ "value": "first"
1267
+ }).reset_index()
1268
+
1269
+ project_allocations["نسبة التخصيص"] = project_allocations["allocation_amount"] / project_allocations["value"] * 100
1270
+
1271
+ # تغيير أسماء الأعمدة
1272
+ project_allocations.columns = ["المشروع", "التخصيصات", "قيمة المشروع", "نسبة التخصيص"]
1273
+
1274
+ # ترتيب البيانات حسب نسبة التخصيص
1275
+ project_allocations = project_allocations.sort_values(by="نسبة التخصيص", ascending=False)
1276
+
1277
+ # عرض الجدول
1278
+ st.dataframe(project_allocations, use_container_width=True)
1279
+
1280
+ # إنشاء رسم بياني لتأثير التخصيصات على تكلفة المشاريع
1281
+ fig = px.bar(
1282
+ project_allocations,
1283
+ x="المشروع",
1284
+ y=["قيمة المشروع", "التخصيصات"],
1285
+ title="تأثير تخصيصات الإدارات المساندة على تكلفة المشاريع",
1286
+ barmode="stack",
1287
+ text_auto=True
1288
+ )
1289
+
1290
+ st.plotly_chart(fig, use_container_width=True)
1291
+
1292
+ def _render_efficiency_analysis_report(self, departments, projects, merged_allocations):
1293
+ """عرض تقرير تحليل الكفاءة"""
1294
+
1295
+ st.markdown("#### تقرير تحليل الكفاءة")
1296
+
1297
+ # حساب مؤشرات الكفاءة للإدارات
1298
+ department_efficiency = departments.copy()
1299
+
1300
+ # حساب إجمالي التخصيصات لكل إدارة
1301
+ department_allocations = merged_allocations.groupby("department_id").agg({
1302
+ "allocation_amount": "sum"
1303
+ }).reset_index()
1304
+
1305
+ # دمج البيانات
1306
+ department_efficiency = pd.merge(
1307
+ department_efficiency,
1308
+ department_allocations,
1309
+ left_on="id",
1310
+ right_on="department_id",
1311
+ how="left"
1312
+ )
1313
+
1314
+ # حساب مؤشرات الكفاءة
1315
+ department_efficiency["allocation_amount"] = department_efficiency["allocation_amount"].fillna(0)
1316
+ department_efficiency["نسبة التغطية"] = department_efficiency["allocation_amount"] / department_efficiency["annual_cost"] * 100
1317
+ department_efficiency["تكلفة الموظف السنوية"] = department_efficiency["annual_cost"] / department_efficiency["staff_count"]
1318
+ department_efficiency["تخصيص الموظف"] = department_efficiency["allocation_amount"] / department_efficiency["staff_count"]
1319
+ department_efficiency["مؤشر الكفاءة"] = department_efficiency["تخصيص الموظف"] / department_efficiency["تكلفة الموظف السنوية"] * 100
1320
+
1321
+ # تغيير أسماء الأعمدة
1322
+ efficiency_display = department_efficiency[["name", "category", "annual_cost", "staff_count", "allocation_amount", "نسبة التغطية", "تكلفة الموظف السنوية", "تخصيص الموظف", "مؤشر الكفاءة"]].copy()
1323
+ efficiency_display.columns = ["الإدارة", "الفئة", "التكلفة السنوية", "عدد الموظفين", "التخصيصات", "نسبة التغطية", "تكلفة الموظف السنوية", "تخصيص الموظف", "مؤشر الكفاءة"]
1324
+
1325
+ # ترتيب البيانات حسب مؤشر الكفاءة
1326
+ efficiency_display = efficiency_display.sort_values(by="مؤشر الكفاءة", ascending=False)
1327
+
1328
+ # عرض الجدول
1329
+ st.dataframe(efficiency_display, use_container_width=True)
1330
+
1331
+ # إنشاء رسم بياني لمؤشر الكفاءة
1332
+ fig = px.bar(
1333
+ efficiency_display,
1334
+ x="الإدارة",
1335
+ y="مؤشر الكفاءة",
1336
+ title="مؤشر كفاءة الإدارات المساندة",
1337
+ color="الفئة",
1338
+ text_auto=True
1339
+ )
1340
+
1341
+ # إضافة خط أفقي عند 100%
1342
+ fig.add_shape(
1343
+ type="line",
1344
+ x0=-0.5,
1345
+ y0=100,
1346
+ x1=len(efficiency_display) - 0.5,
1347
+ y1=100,
1348
+ line=dict(
1349
+ color="red",
1350
+ width=2,
1351
+ dash="dash"
1352
+ )
1353
+ )
1354
+
1355
+ st.plotly_chart(fig, use_container_width=True)
1356
+
1357
+ # تحليل العلاقة بين عدد الموظفين ومؤشر الكفاءة
1358
+ fig = px.scatter(
1359
+ efficiency_display,
1360
+ x="عدد الموظفين",
1361
+ y="مؤشر الكفاءة",
1362
+ title="العلاقة بين عدد الموظفين ومؤشر الكفاءة",
1363
+ color="الفئة",
1364
+ size="التكلفة السنوية",
1365
+ hover_data=["الإدارة"],
1366
+ trendline="ols"
1367
+ )
1368
+
1369
+ st.plotly_chart(fig, use_container_width=True)
1370
+
1371
+ # تحليل العلاقة بين تكلفة الموظف السنوية ومؤشر الكفاءة
1372
+ fig = px.scatter(
1373
+ efficiency_display,
1374
+ x="تكلفة الموظف السنوية",
1375
+ y="مؤشر الكفاءة",
1376
+ title="العلاقة بين تكلفة الموظف السنوية ومؤشر الكفاءة",
1377
+ color="الفئة",
1378
+ size="عدد الموظفين",
1379
+ hover_data=["الإدارة"],
1380
+ trendline="ols"
1381
+ )
1382
+
1383
+ st.plotly_chart(fig, use_container_width=True)
1384
+
1385
+ # تحليل كفاءة الإدارات حسب الفئة
1386
+ category_efficiency = efficiency_display.groupby("الفئة").agg({
1387
+ "التكلفة السنوية": "sum",
1388
+ "عدد الموظفين": "sum",
1389
+ "التخصيصات": "sum",
1390
+ "مؤشر الكفاءة": "mean"
1391
+ }).reset_index()
1392
+
1393
+ category_efficiency["نسبة التغطية"] = category_efficiency["التخصيصات"] / category_efficiency["التكلفة السنوية"] * 100
1394
+ category_efficiency["تكلفة الموظف السنوية"] = category_efficiency["التكلفة السنوية"] / category_efficiency["عدد الموظفين"]
1395
+
1396
+ # عرض الجدول
1397
+ st.dataframe(category_efficiency, use_container_width=True)
1398
+
1399
+ # إنشاء رسم بياني لمؤشر الكفاءة حسب الفئة
1400
+ fig = px.bar(
1401
+ category_efficiency,
1402
+ x="الفئة",
1403
+ y="مؤشر الكفاءة",
1404
+ title="متوسط مؤشر كفاءة الإدارات المساندة حسب الفئة",
1405
+ color="الفئة",
1406
+ text_auto=True
1407
+ )
1408
+
1409
+ # إضافة خط أفقي عند 100%
1410
+ fig.add_shape(
1411
+ type="line",
1412
+ x0=-0.5,
1413
+ y0=100,
1414
+ x1=len(category_efficiency) - 0.5,
1415
+ y1=100,
1416
+ line=dict(
1417
+ color="red",
1418
+ width=2,
1419
+ dash="dash"
1420
+ )
1421
+ )
1422
+
1423
+ st.plotly_chart(fig, use_container_width=True)
1424
+
1425
+ # توصيات لتحسين الكفاءة
1426
+ st.markdown("#### توصيات لتحسين الكفاءة")
1427
+
1428
+ # تحديد الإدارات ذات الكفاءة المنخفضة
1429
+ low_efficiency_departments = efficiency_display[efficiency_display["مؤشر الكفاءة"] < 80].sort_values(by="مؤشر الكفاءة")
1430
+
1431
+ if not low_efficiency_departments.empty:
1432
+ st.markdown("##### الإدارات ذات الكفاءة المنخفضة")
1433
+
1434
+ for _, row in low_efficiency_departments.iterrows():
1435
+ st.markdown(f"**{row['الإدارة']} (مؤشر الكفاءة: {row['مؤشر الكفاءة']:.2f}%):**")
1436
+
1437
+ if row["نسبة التغطية"] < 80:
1438
+ st.markdown("- زيادة التخصيصات للإدارة من خلال مراجعة طريقة التخصيص")
1439
+
1440
+ if row["تكلفة الموظف السنوية"] > category_efficiency[category_efficiency["الفئة"] == row["الفئة"]]["تكلفة الموظف السنوية"].values[0]:
1441
+ st.markdown("- مراجعة تكلفة الموظفين في الإدارة")
1442
+
1443
+ st.markdown("- تحسين إنتاجية الإدارة من خلال تطوير العمليات وأتمتة الأعمال")
1444
+ st.markdown("- مراجعة عدد الموظفين في الإدارة")
1445
+
1446
+ st.markdown("---")
1447
+ else:
1448
+ st.success("جميع الإدارات تتمتع بمستوى كفاءة جيد (أكثر من 80%)")
1449
+
1450
+ # تحديد الإدارات ذات الكفاءة العالية
1451
+ high_efficiency_departments = efficiency_display[efficiency_display["مؤشر الكفاءة"] > 120].sort_values(by="مؤشر الكفاءة", ascending=False)
1452
+
1453
+ if not high_efficiency_departments.empty:
1454
+ st.markdown("##### الإدارات ذات الكفاءة العالية")
1455
+
1456
+ for _, row in high_efficiency_departments.iterrows():
1457
+ st.markdown(f"**{row['الإدارة']} (مؤشر الكفاءة: {row['مؤشر الكفاءة']:.2f}%):**")
1458
+
1459
+ if row["نسبة التغطية"] > 120:
1460
+ st.markdown("- مراجعة طريقة التخصيص للتأكد من عدم المبالغة في التخصيصات")
1461
+
1462
+ st.markdown("- دراسة أسباب ارتفاع الكفاءة والاستفادة منها في تطوير الإدارات الأخرى")
1463
+ st.markdown("- تقييم جودة الخدمات المقدمة للتأكد من عدم تأثرها بارتفاع الكفاءة")
1464
+
1465
+ st.markdown("---")
1466
+
1467
+ # توصيات عامة
1468
+ st.markdown("##### توصيات عامة لتحسين كفاءة الإدارات المساندة")
1469
+
1470
+ st.markdown("1. مراجعة طرق تخصيص تكاليف الإدارات المساندة للمشاريع")
1471
+ st.markdown("2. تطوير نظام لقياس أداء الإدارات المساندة")
1472
+ st.markdown("3. تحسين عمليات الإدارات المساندة من خلال أتمتة الأعمال")
1473
+ st.markdown("4. تطوير برامج تدريبية لرفع كفاءة الموظفين")
1474
+ st.markdown("5. مراجعة الهيكل التنظيمي للإدارات المساندة")
1475
+ st.markdown("6. تطبيق مبادئ الإدارة الرشيقة (Lean Management) في الإدارات المساندة")
1476
+ st.markdown("7. تحسين التنسيق بين الإدارات المساندة والمشاريع")
1477
+
1478
+ def calculate_project_indirect_cost(self, project_id):
1479
+ """حساب تكلفة الإدارات المساندة لمشروع معين"""
1480
+
1481
+ # استخراج البيانات
1482
+ allocations = st.session_state.indirect_support["allocations"]
1483
+
1484
+ # حساب إجمالي التخصيصات للمشروع
1485
+ project_allocations = allocations[allocations["project_id"] == project_id]
1486
+
1487
+ if not project_allocations.empty:
1488
+ return project_allocations["allocation_amount"].sum()
1489
+
1490
+ return 0
1491
+
1492
+ def calculate_department_allocations(self, department_id):
1493
+ """حساب تخصيصات إدارة معينة"""
1494
+
1495
+ # استخراج البيانات
1496
+ allocations = st.session_state.indirect_support["allocations"]
1497
+
1498
+ # حساب إجمالي التخصيصات للإدارة
1499
+ department_allocations = allocations[allocations["department_id"] == department_id]
1500
+
1501
+ if not department_allocations.empty:
1502
+ return department_allocations["allocation_amount"].sum()
1503
+
1504
+ return 0
1505
+
1506
+ def get_department_by_id(self, department_id):
1507
+ """الحصول على إدارة بواسطة الكود"""
1508
+
1509
+ # استخراج البيانات
1510
+ departments = st.session_state.indirect_support["departments"]
1511
+
1512
+ # البحث عن الإدارة
1513
+ department = departments[departments["id"] == department_id]
1514
+
1515
+ if not department.empty:
1516
+ return department.iloc[0].to_dict()
1517
+
1518
+ return None
1519
+
1520
+ def get_project_by_id(self, project_id):
1521
+ """الحصول على مشروع بواسطة الكود"""
1522
+
1523
+ # استخراج البيانات
1524
+ projects = st.session_state.indirect_support["projects"]
1525
+
1526
+ # البحث عن المشروع
1527
+ project = projects[projects["id"] == project_id]
1528
+
1529
+ if not project.empty:
1530
+ return project.iloc[0].to_dict()
1531
+
1532
+ return None
pricing_system/modules/pricing_strategies/pricing_strategies.py ADDED
@@ -0,0 +1,1505 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وحدة استراتيجيات التسعير المتقدمة - تنفيذ استراتيجيات متعددة للتسعير
3
+ """
4
+
5
+ import streamlit as st
6
+ import pandas as pd
7
+ import numpy as np
8
+ import plotly.express as px
9
+ import plotly.graph_objects as go
10
+ import os
11
+ import json
12
+ from datetime import datetime
13
+ import io
14
+
15
+ class PricingStrategies:
16
+ """فئة استراتيجيات التسعير المتقدمة"""
17
+
18
+ def __init__(self):
19
+ """تهيئة وحدة استراتيجيات التسعير"""
20
+
21
+ # تهيئة حالة الجلسة لاستراتيجيات التسعير
22
+ if 'pricing_strategies' not in st.session_state:
23
+ self._initialize_pricing_strategies()
24
+
25
+ def _initialize_pricing_strategies(self):
26
+ """تهيئة بيانات استراتيجيات التسعير"""
27
+
28
+ # إنشاء بيانات افتراضية لاستراتيجيات التسعير
29
+ strategies = [
30
+ {
31
+ "id": "STD-001",
32
+ "name": "التسعير القياسي",
33
+ "description": "استراتيجية التسعير القياسية تعتمد على تحديد سعر كل بند بناءً على تكلفته الفعلية مضافاً إليها نسبة ربح ثابتة",
34
+ "profit_margin": 15,
35
+ "risk_factor": 5,
36
+ "overhead_percentage": 10,
37
+ "is_active": True,
38
+ "parameters": {
39
+ "apply_uniform_margin": True,
40
+ "margin_variation": 0
41
+ }
42
+ },
43
+ {
44
+ "id": "BAL-001",
45
+ "name": "التسعير المتزن",
46
+ "description": "استراتيجية التسعير المتزن تعتمد على توزيع هامش الربح بشكل متوازن على جميع بنود المشروع مع مراعاة المخاطر",
47
+ "profit_margin": 18,
48
+ "risk_factor": 8,
49
+ "overhead_percentage": 12,
50
+ "is_active": True,
51
+ "parameters": {
52
+ "balance_factor": 0.8,
53
+ "risk_weight": 1.2
54
+ }
55
+ },
56
+ {
57
+ "id": "UNB-001",
58
+ "name": "التسعير غير المتزن",
59
+ "description": "استراتيجية التسعير غير المتزن تعتمد على زيادة أسعار البنود المبكرة في المشروع وتخفيض أسعار البنود المتأخرة",
60
+ "profit_margin": 20,
61
+ "risk_factor": 10,
62
+ "overhead_percentage": 15,
63
+ "is_active": True,
64
+ "parameters": {
65
+ "front_loading_factor": 1.3,
66
+ "back_loading_factor": 0.7,
67
+ "threshold_percentage": 30
68
+ }
69
+ },
70
+ {
71
+ "id": "PRF-001",
72
+ "name": "التسعير الموجه للربحية",
73
+ "description": "استراتيجية التسعير الموجه للربحية تعتمد على زيادة أسعار البنود ذات التكلفة المنخفضة والكميات الكبيرة",
74
+ "profit_margin": 25,
75
+ "risk_factor": 12,
76
+ "overhead_percentage": 18,
77
+ "is_active": True,
78
+ "parameters": {
79
+ "high_volume_factor": 1.4,
80
+ "low_cost_factor": 1.5,
81
+ "volume_threshold": 70,
82
+ "cost_threshold": 30
83
+ }
84
+ },
85
+ {
86
+ "id": "BDL-001",
87
+ "name": "التسعير بالتجميع",
88
+ "description": "استراتيجية التسعير بالتجميع تعتمد على تجميع البنود المتشابهة وتسعيرها كمجموعة واحدة",
89
+ "profit_margin": 17,
90
+ "risk_factor": 7,
91
+ "overhead_percentage": 12,
92
+ "is_active": True,
93
+ "parameters": {
94
+ "bundling_threshold": 0.8,
95
+ "similarity_measure": "category",
96
+ "min_bundle_size": 3
97
+ }
98
+ },
99
+ {
100
+ "id": "LOC-001",
101
+ "name": "التسعير بالمحتوى المحلي",
102
+ "description": "استراتيجية التسعير بالمحتوى المحلي تعتمد على زيادة نسبة المحتوى المحلي في المشروع لتحقيق متطلبات الجهات المالكة",
103
+ "profit_margin": 15,
104
+ "risk_factor": 6,
105
+ "overhead_percentage": 10,
106
+ "is_active": True,
107
+ "parameters": {
108
+ "local_content_target": 70,
109
+ "local_content_premium": 1.2,
110
+ "imported_content_discount": 0.9
111
+ }
112
+ }
113
+ ]
114
+
115
+ # إنشاء بيانات افتراضية للمشاريع
116
+ projects = [
117
+ {
118
+ "id": "PRJ-001",
119
+ "name": "مشروع تطوير طريق الملك عبدالله",
120
+ "value": 50000000,
121
+ "duration": 24,
122
+ "start_date": "2025-01-01",
123
+ "end_date": "2026-12-31",
124
+ "status": "جاري",
125
+ "location": "الرياض",
126
+ "client": "وزارة النقل",
127
+ "description": "مشروع تطوير وتوسعة طريق الملك عبدالله بطول 15 كم"
128
+ },
129
+ {
130
+ "id": "PRJ-002",
131
+ "name": "مشروع إنشاء شبكة صرف صحي",
132
+ "value": 30000000,
133
+ "duration": 18,
134
+ "start_date": "2025-03-01",
135
+ "end_date": "2026-08-31",
136
+ "status": "جاري",
137
+ "location": "جدة",
138
+ "client": "شركة المياه الوطنية",
139
+ "description": "مشروع إنشاء شبكة صرف صحي في حي النزهة بجدة"
140
+ },
141
+ {
142
+ "id": "PRJ-003",
143
+ "name": "مشروع إنشاء جسر تقاطع طريق الملك فهد",
144
+ "value": 80000000,
145
+ "duration": 30,
146
+ "start_date": "2025-02-01",
147
+ "end_date": "2027-07-31",
148
+ "status": "جاري",
149
+ "location": "الدمام",
150
+ "client": "أمانة المنطقة الشرقية",
151
+ "description": "مشروع إنشاء جسر علوي عند تقاطع طريق الملك فهد مع طريق الأمير محمد بن فهد"
152
+ }
153
+ ]
154
+
155
+ # إنشاء بيانات افتراضية لتطبيق الاستراتيجيات على المشاريع
156
+ strategy_applications = []
157
+
158
+ for project in projects:
159
+ # اختيار استراتيجية افتراضية لكل مشروع
160
+ if project["id"] == "PRJ-001":
161
+ strategy_id = "BAL-001" # التسعير المتزن
162
+ elif project["id"] == "PRJ-002":
163
+ strategy_id = "UNB-001" # التسعير غير المتزن
164
+ else:
165
+ strategy_id = "LOC-001" # التسعير بالمحتوى المحلي
166
+
167
+ strategy_applications.append({
168
+ "project_id": project["id"],
169
+ "strategy_id": strategy_id,
170
+ "application_date": datetime.now().strftime("%Y-%m-%d"),
171
+ "applied_by": "مدير التسعير",
172
+ "status": "مطبق",
173
+ "notes": "تم تطبيق الاستراتيجية بنجاح",
174
+ "results": {
175
+ "original_value": project["value"],
176
+ "new_value": project["value"] * 1.1, # زيادة افتراضية بنسبة 10%
177
+ "profit_margin": 18,
178
+ "local_content_percentage": 65,
179
+ "risk_assessment": "متوسط"
180
+ }
181
+ })
182
+
183
+ # إنشاء بيانات افتراضية لمقارنة الاستراتيجيات
184
+ strategy_comparisons = []
185
+
186
+ for project in projects:
187
+ comparison = {
188
+ "project_id": project["id"],
189
+ "comparison_date": datetime.now().strftime("%Y-%m-%d"),
190
+ "compared_by": "مدير التسعير",
191
+ "strategies": {}
192
+ }
193
+
194
+ # إضافة نتائج لكل استراتيجية
195
+ base_value = project["value"]
196
+
197
+ for strategy in strategies:
198
+ # حساب قيمة افتراضية مختلفة لكل استراتيجية
199
+ if strategy["id"] == "STD-001": # التسعير القياسي
200
+ value_factor = 1.0
201
+ profit_margin = 15
202
+ local_content = 60
203
+ elif strategy["id"] == "BAL-001": # التسعير المتزن
204
+ value_factor = 1.05
205
+ profit_margin = 18
206
+ local_content = 62
207
+ elif strategy["id"] == "UNB-001": # التسعير غير المتزن
208
+ value_factor = 1.08
209
+ profit_margin = 20
210
+ local_content = 58
211
+ elif strategy["id"] == "PRF-001": # التسعير الموجه للربحية
212
+ value_factor = 1.15
213
+ profit_margin = 25
214
+ local_content = 55
215
+ elif strategy["id"] == "BDL-001": # التسعير بالتجميع
216
+ value_factor = 1.03
217
+ profit_margin = 17
218
+ local_content = 63
219
+ else: # التسعير بالمحتوى المحلي
220
+ value_factor = 1.1
221
+ profit_margin = 15
222
+ local_content = 70
223
+
224
+ comparison["strategies"][strategy["id"]] = {
225
+ "value": base_value * value_factor,
226
+ "profit_margin": profit_margin,
227
+ "local_content_percentage": local_content,
228
+ "risk_assessment": "متوسط" if profit_margin < 20 else "مرتفع",
229
+ "cash_flow_impact": "إيجابي" if strategy["id"] in ["UNB-001", "PRF-001"] else "محايد"
230
+ }
231
+
232
+ strategy_comparisons.append(comparison)
233
+
234
+ # تخزين البيانات في حالة الجلسة
235
+ st.session_state.pricing_strategies = {
236
+ "strategies": pd.DataFrame(strategies),
237
+ "projects": pd.DataFrame(projects),
238
+ "strategy_applications": pd.DataFrame(strategy_applications),
239
+ "strategy_comparisons": strategy_comparisons
240
+ }
241
+
242
+ def render(self):
243
+ """عرض واجهة استراتيجيات التسعير"""
244
+
245
+ st.markdown("## استراتيجيات التسعير المتقدمة")
246
+
247
+ # إنشاء تبويبات لعرض استراتيجيات التسعير
248
+ tabs = st.tabs([
249
+ "الاستراتيجيات",
250
+ "تطبيق الاستراتيجيات",
251
+ "مقارنة الاستراتيجيات",
252
+ "تحليل المحتوى المحلي"
253
+ ])
254
+
255
+ with tabs[0]:
256
+ self._render_strategies_tab()
257
+
258
+ with tabs[1]:
259
+ self._render_apply_strategies_tab()
260
+
261
+ with tabs[2]:
262
+ self._render_compare_strategies_tab()
263
+
264
+ with tabs[3]:
265
+ self._render_local_content_tab()
266
+
267
+ def _render_strategies_tab(self):
268
+ """عرض تبويب الاستراتيجيات"""
269
+
270
+ st.markdown("### استراتيجيات التسعير")
271
+
272
+ # استخراج البيانات
273
+ strategies = st.session_state.pricing_strategies["strategies"]
274
+
275
+ # عرض الاستراتيجيات
276
+ if not strategies.empty:
277
+ # إنشاء جدول للعرض
278
+ display_df = strategies[["id", "name", "profit_margin", "risk_factor", "overhead_percentage", "is_active"]].copy()
279
+ display_df.columns = ["الكود", "الاسم", "هامش الربح (%)", "عامل المخاطرة (%)", "نسبة المصاريف العمومية (%)", "نشط"]
280
+
281
+ # عرض الجدول
282
+ st.dataframe(display_df, use_container_width=True)
283
+
284
+ # إضافة استراتيجية جديدة
285
+ st.markdown("### إضافة استراتيجية جديدة")
286
+
287
+ with st.form("add_strategy_form"):
288
+ # الصف الأول
289
+ col1, col2 = st.columns(2)
290
+
291
+ with col1:
292
+ strategy_id = st.text_input("كود الاستراتيجية", value=f"STR-{len(strategies) + 1:03d}")
293
+ strategy_name = st.text_input("اسم الاستراتيجية", placeholder="مثال: استراتيجية التسعير التنافسي")
294
+
295
+ with col2:
296
+ strategy_profit_margin = st.slider("هامش الربح (%)", min_value=5, max_value=40, value=15, step=1)
297
+ strategy_risk_factor = st.slider("عامل المخاطرة (%)", min_value=0, max_value=20, value=5, step=1)
298
+
299
+ # الصف الثاني
300
+ col1, col2 = st.columns(2)
301
+
302
+ with col1:
303
+ strategy_overhead = st.slider("نسبة المصاريف العمومية (%)", min_value=5, max_value=30, value=10, step=1)
304
+ strategy_is_active = st.checkbox("نشط", value=True)
305
+
306
+ with col2:
307
+ strategy_type = st.selectbox(
308
+ "نوع الاستراتيجية",
309
+ [
310
+ "التسعير القياسي",
311
+ "التسعير المتزن",
312
+ "التسعير غير المتزن",
313
+ "التسعير الموجه للربحية",
314
+ "التسعير بالتجميع",
315
+ "التسعير بالمحتوى المحلي",
316
+ "أخرى"
317
+ ]
318
+ )
319
+
320
+ # الصف الثالث
321
+ strategy_description = st.text_area("وصف الاستراتيجية", placeholder="أدخل وصفاً تفصيلياً للاستراتيجية")
322
+
323
+ # معلمات الاستراتيجية
324
+ st.markdown("#### معلمات الاستراتيجية")
325
+
326
+ # إظهار معلمات مختلفة حسب نوع الاستراتيجية
327
+ parameters = {}
328
+
329
+ if strategy_type == "التسعير القياسي":
330
+ col1, col2 = st.columns(2)
331
+
332
+ with col1:
333
+ apply_uniform_margin = st.checkbox("تطبيق هامش ربح موحد", value=True)
334
+
335
+ with col2:
336
+ margin_variation = st.slider("تباين الهامش (%)", min_value=0, max_value=20, value=0, step=1)
337
+
338
+ parameters = {
339
+ "apply_uniform_margin": apply_uniform_margin,
340
+ "margin_variation": margin_variation
341
+ }
342
+
343
+ elif strategy_type == "التسعير المتزن":
344
+ col1, col2 = st.columns(2)
345
+
346
+ with col1:
347
+ balance_factor = st.slider("معامل التوازن", min_value=0.1, max_value=1.0, value=0.8, step=0.1)
348
+
349
+ with col2:
350
+ risk_weight = st.slider("وزن المخاطرة", min_value=0.5, max_value=2.0, value=1.2, step=0.1)
351
+
352
+ parameters = {
353
+ "balance_factor": balance_factor,
354
+ "risk_weight": risk_weight
355
+ }
356
+
357
+ elif strategy_type == "التسعير غير المتزن":
358
+ col1, col2 = st.columns(2)
359
+
360
+ with col1:
361
+ front_loading_factor = st.slider("معامل التحميل الأمامي", min_value=1.0, max_value=2.0, value=1.3, step=0.1)
362
+ threshold_percentage = st.slider("نسبة الحد الفاصل (%)", min_value=10, max_value=50, value=30, step=5)
363
+
364
+ with col2:
365
+ back_loading_factor = st.slider("معامل التحميل الخلفي", min_value=0.5, max_value=1.0, value=0.7, step=0.1)
366
+
367
+ parameters = {
368
+ "front_loading_factor": front_loading_factor,
369
+ "back_loading_factor": back_loading_factor,
370
+ "threshold_percentage": threshold_percentage
371
+ }
372
+
373
+ elif strategy_type == "التسعير الموجه للربحية":
374
+ col1, col2 = st.columns(2)
375
+
376
+ with col1:
377
+ high_volume_factor = st.slider("معامل الكميات الكبيرة", min_value=1.0, max_value=2.0, value=1.4, step=0.1)
378
+ volume_threshold = st.slider("حد الكميات الكبيرة (%)", min_value=50, max_value=90, value=70, step=5)
379
+
380
+ with col2:
381
+ low_cost_factor = st.slider("معامل التكلفة المنخفضة", min_value=1.0, max_value=2.0, value=1.5, step=0.1)
382
+ cost_threshold = st.slider("حد التكلفة المنخفضة (%)", min_value=10, max_value=50, value=30, step=5)
383
+
384
+ parameters = {
385
+ "high_volume_factor": high_volume_factor,
386
+ "low_cost_factor": low_cost_factor,
387
+ "volume_threshold": volume_threshold,
388
+ "cost_threshold": cost_threshold
389
+ }
390
+
391
+ elif strategy_type == "التسعير بالتجميع":
392
+ col1, col2 = st.columns(2)
393
+
394
+ with col1:
395
+ bundling_threshold = st.slider("حد التجميع", min_value=0.5, max_value=1.0, value=0.8, step=0.1)
396
+ min_bundle_size = st.slider("الحد الأدنى لحجم المجموعة", min_value=2, max_value=10, value=3, step=1)
397
+
398
+ with col2:
399
+ similarity_measure = st.selectbox(
400
+ "مقياس التشابه",
401
+ ["category", "subcategory", "custom"],
402
+ format_func=lambda x: "الفئة" if x == "category" else "الفئة الفرعية" if x == "subcategory" else "مخصص"
403
+ )
404
+
405
+ parameters = {
406
+ "bundling_threshold": bundling_threshold,
407
+ "similarity_measure": similarity_measure,
408
+ "min_bundle_size": min_bundle_size
409
+ }
410
+
411
+ elif strategy_type == "التسعير بالمحتوى المحلي":
412
+ col1, col2 = st.columns(2)
413
+
414
+ with col1:
415
+ local_content_target = st.slider("نسبة المحتوى المحلي المستهدفة (%)", min_value=30, max_value=100, value=70, step=5)
416
+ local_content_premium = st.slider("علاوة المحتوى المحلي", min_value=1.0, max_value=1.5, value=1.2, step=0.1)
417
+
418
+ with col2:
419
+ imported_content_discount = st.slider("خصم المحتوى المستورد", min_value=0.5, max_value=1.0, value=0.9, step=0.1)
420
+
421
+ parameters = {
422
+ "local_content_target": local_content_target,
423
+ "local_content_premium": local_content_premium,
424
+ "imported_content_discount": imported_content_discount
425
+ }
426
+
427
+ # زر الإضافة
428
+ submit_button = st.form_submit_button("إضافة الاستراتيجية")
429
+
430
+ if submit_button:
431
+ # التحقق من البيانات
432
+ if not strategy_name or not strategy_description:
433
+ st.error("يرجى إدخال المعلومات الأساسية للاستراتيجية")
434
+ else:
435
+ # إنشاء استراتيجية جديدة
436
+ new_strategy = {
437
+ "id": strategy_id,
438
+ "name": strategy_name,
439
+ "description": strategy_description,
440
+ "profit_margin": strategy_profit_margin,
441
+ "risk_factor": strategy_risk_factor,
442
+ "overhead_percentage": strategy_overhead,
443
+ "is_active": strategy_is_active,
444
+ "parameters": parameters
445
+ }
446
+
447
+ # إضافة الاستراتيجية إلى الجدول
448
+ st.session_state.pricing_strategies["strategies"] = pd.concat([
449
+ st.session_state.pricing_strategies["strategies"],
450
+ pd.DataFrame([new_strategy])
451
+ ], ignore_index=True)
452
+
453
+ st.success(f"تمت إضافة الاستراتيجية {strategy_name} بنجاح!")
454
+
455
+ # إعادة تحميل الصفحة
456
+ st.experimental_rerun()
457
+
458
+ # عرض تفاصيل الاستراتيجيات
459
+ st.markdown("### تفاصيل الاستراتيجيات")
460
+
461
+ selected_strategy_id = st.selectbox("اختر استراتيجية لعرض التفاصيل", strategies["id"].tolist(), key="strategy_details")
462
+
463
+ # استخراج الاستراتيجية المختارة
464
+ selected_strategy = strategies[strategies["id"] == selected_strategy_id].iloc[0]
465
+
466
+ # عرض تفاصيل الاستراتيجية
467
+ st.markdown(f"**الاستراتيجية:** {selected_strategy['name']} ({selected_strategy['id']})")
468
+ st.markdown(f"**الوصف:** {selected_strategy['description']}")
469
+ st.markdown(f"**هامش الربح:** {selected_strategy['profit_margin']}%")
470
+ st.markdown(f"**عامل المخاطرة:** {selected_strategy['risk_factor']}%")
471
+ st.markdown(f"**نسبة المصاريف العمومية:** {selected_strategy['overhead_percentage']}%")
472
+ st.markdown(f"**الحالة:** {'نشط' if selected_strategy['is_active'] else 'غير نشط'}")
473
+
474
+ # عرض معلمات الاستراتيجية
475
+ st.markdown("#### معلمات الاستراتيجية")
476
+
477
+ parameters = selected_strategy["parameters"]
478
+
479
+ if isinstance(parameters, dict):
480
+ for key, value in parameters.items():
481
+ # تنسيق اسم المعلمة
482
+ formatted_key = key.replace("_", " ").title()
483
+
484
+ # عرض المعلمة
485
+ st.markdown(f"**{formatted_key}:** {value}")
486
+
487
+ # تعديل الاستراتيجية
488
+ st.markdown("#### تعديل الاستراتيجية")
489
+
490
+ with st.form("edit_strategy_form"):
491
+ # الصف الأول
492
+ col1, col2 = st.columns(2)
493
+
494
+ with col1:
495
+ edit_strategy_name = st.text_input("اسم الاستراتيجية", value=selected_strategy["name"], key="edit_name")
496
+
497
+ with col2:
498
+ edit_strategy_profit_margin = st.slider("هامش الربح (%)", min_value=5, max_value=40, value=int(selected_strategy["profit_margin"]), step=1, key="edit_profit")
499
+ edit_strategy_risk_factor = st.slider("عامل المخاطرة (%)", min_value=0, max_value=20, value=int(selected_strategy["risk_factor"]), step=1, key="edit_risk")
500
+
501
+ # الصف الثاني
502
+ col1, col2 = st.columns(2)
503
+
504
+ with col1:
505
+ edit_strategy_overhead = st.slider("نسبة المصاريف العمومية (%)", min_value=5, max_value=30, value=int(selected_strategy["overhead_percentage"]), step=1, key="edit_overhead")
506
+ edit_strategy_is_active = st.checkbox("نشط", value=bool(selected_strategy["is_active"]), key="edit_active")
507
+
508
+ # الصف الثالث
509
+ edit_strategy_description = st.text_area("وصف الاستراتيجية", value=selected_strategy["description"], key="edit_description")
510
+
511
+ # زر الحفظ
512
+ submit_button = st.form_submit_button("حفظ التعديلات")
513
+
514
+ if submit_button:
515
+ # تحديث الاستراتيجية
516
+ strategy_index = strategies[strategies["id"] == selected_strategy_id].index[0]
517
+
518
+ strategies.at[strategy_index, "name"] = edit_strategy_name
519
+ strategies.at[strategy_index, "description"] = edit_strategy_description
520
+ strategies.at[strategy_index, "profit_margin"] = edit_strategy_profit_margin
521
+ strategies.at[strategy_index, "risk_factor"] = edit_strategy_risk_factor
522
+ strategies.at[strategy_index, "overhead_percentage"] = edit_strategy_overhead
523
+ strategies.at[strategy_index, "is_active"] = edit_strategy_is_active
524
+
525
+ # تحديث حالة الجلسة
526
+ st.session_state.pricing_strategies["strategies"] = strategies
527
+
528
+ st.success("تم تحديث الاستراتيجية بنجاح!")
529
+
530
+ # إعادة تحميل الصفحة
531
+ st.experimental_rerun()
532
+ else:
533
+ st.warning("لا يوجد استراتيجيات تسعير")
534
+
535
+ def _render_apply_strategies_tab(self):
536
+ """عرض تبويب تطبيق الاستراتيجيات"""
537
+
538
+ st.markdown("### تطبيق استراتيجيات التسعير")
539
+
540
+ # استخراج البيانات
541
+ strategies = st.session_state.pricing_strategies["strategies"]
542
+ projects = st.session_state.pricing_strategies["projects"]
543
+ strategy_applications = st.session_state.pricing_strategies["strategy_applications"]
544
+
545
+ # عرض التطبيقات الحالية
546
+ if not strategy_applications.empty:
547
+ # دمج البيانات
548
+ merged_applications = pd.merge(
549
+ strategy_applications,
550
+ projects[["id", "name", "value"]],
551
+ left_on="project_id",
552
+ right_on="id",
553
+ suffixes=("", "_project")
554
+ )
555
+
556
+ merged_applications = pd.merge(
557
+ merged_applications,
558
+ strategies[["id", "name"]],
559
+ left_on="strategy_id",
560
+ right_on="id",
561
+ suffixes=("_project", "_strategy")
562
+ )
563
+
564
+ # إنشاء جدول للعرض
565
+ display_df = merged_applications[["project_id", "name_project", "strategy_id", "name_strategy", "application_date", "status"]].copy()
566
+ display_df.columns = ["كود المشروع", "اسم المشروع", "كود الاستراتيجية", "اسم الاستراتيجية", "تاريخ التطبيق", "الحالة"]
567
+
568
+ # عرض الجدول
569
+ st.dataframe(display_df, use_container_width=True)
570
+
571
+ # تطبيق استراتيجية جديدة
572
+ st.markdown("### تطبيق استراتيجية جديدة")
573
+
574
+ with st.form("apply_strategy_form"):
575
+ # الصف الأول
576
+ col1, col2 = st.columns(2)
577
+
578
+ with col1:
579
+ project_id = st.selectbox("اختر المشروع", projects["id"].tolist(), format_func=lambda x: f"{x} - {projects[projects['id'] == x]['name'].values[0]}")
580
+
581
+ with col2:
582
+ active_strategies = strategies[strategies["is_active"] == True]
583
+ strategy_id = st.selectbox("اختر الاستراتيجية", active_strategies["id"].tolist(), format_func=lambda x: f"{x} - {strategies[strategies['id'] == x]['name'].values[0]}")
584
+
585
+ # الصف الثاني
586
+ col1, col2 = st.columns(2)
587
+
588
+ with col1:
589
+ application_date = st.date_input("تاريخ التطبيق", value=datetime.now())
590
+ applied_by = st.text_input("تم التطبيق بواسطة", value="مدير التسعير")
591
+
592
+ with col2:
593
+ status = st.selectbox("الحالة", ["مطبق", "قيد التطبيق", "معلق"])
594
+
595
+ # الصف الثالث
596
+ notes = st.text_area("ملاحظات", placeholder="أدخل ملاحظات حول تطبيق الاستراتيجية")
597
+
598
+ # زر التطبيق
599
+ submit_button = st.form_submit_button("تطبيق الاستراتيجية")
600
+
601
+ if submit_button:
602
+ # التحقق من البيانات
603
+ if not project_id or not strategy_id:
604
+ st.error("يرجى اختيار المشروع والاستراتيجية")
605
+ else:
606
+ # التحقق من عدم وجود تطبيق سابق للاستراتيجية على المشروع
607
+ existing_application = strategy_applications[
608
+ (strategy_applications["project_id"] == project_id) &
609
+ (strategy_applications["strategy_id"] == strategy_id)
610
+ ]
611
+
612
+ if not existing_application.empty:
613
+ st.warning("تم تطبيق هذه الاستراتيجية على هذا المشروع مسبقاً")
614
+ else:
615
+ # استخراج بيانات المشروع والاستراتيجية
616
+ project = projects[projects["id"] == project_id].iloc[0]
617
+ strategy = strategies[strategies["id"] == strategy_id].iloc[0]
618
+
619
+ # حساب نتائج تطبيق الاستراتيجية
620
+ original_value = project["value"]
621
+
622
+ # حساب القيمة الجديدة بناءً على نوع الاستراتيجية
623
+ if "STD" in strategy_id: # التسعير القياسي
624
+ new_value = original_value * (1 + strategy["profit_margin"] / 100)
625
+ profit_margin = strategy["profit_margin"]
626
+ local_content = 60
627
+ elif "BAL" in strategy_id: # التسعير المتزن
628
+ new_value = original_value * (1 + (strategy["profit_margin"] + strategy["risk_factor"]) / 100)
629
+ profit_margin = strategy["profit_margin"]
630
+ local_content = 62
631
+ elif "UNB" in strategy_id: # التسعير غير المتزن
632
+ new_value = original_value * (1 + (strategy["profit_margin"] + strategy["risk_factor"]) / 100)
633
+ profit_margin = strategy["profit_margin"]
634
+ local_content = 58
635
+ elif "PRF" in strategy_id: # التسعير الموجه للربحية
636
+ new_value = original_value * (1 + (strategy["profit_margin"] + strategy["risk_factor"] / 2) / 100)
637
+ profit_margin = strategy["profit_margin"]
638
+ local_content = 55
639
+ elif "BDL" in strategy_id: # التسعير بالتجميع
640
+ new_value = original_value * (1 + strategy["profit_margin"] / 100)
641
+ profit_margin = strategy["profit_margin"]
642
+ local_content = 63
643
+ else: # التسعير بالمحتوى المحلي
644
+ new_value = original_value * (1 + (strategy["profit_margin"] - 2) / 100)
645
+ profit_margin = strategy["profit_margin"] - 2
646
+ local_content = 70
647
+
648
+ # تحديد تقييم المخاطر
649
+ risk_assessment = "منخفض" if strategy["risk_factor"] < 8 else "متوسط" if strategy["risk_factor"] < 15 else "مرتفع"
650
+
651
+ # إنشاء تطبيق جديد
652
+ new_application = {
653
+ "project_id": project_id,
654
+ "strategy_id": strategy_id,
655
+ "application_date": application_date.strftime("%Y-%m-%d"),
656
+ "applied_by": applied_by,
657
+ "status": status,
658
+ "notes": notes,
659
+ "results": {
660
+ "original_value": original_value,
661
+ "new_value": new_value,
662
+ "profit_margin": profit_margin,
663
+ "local_content_percentage": local_content,
664
+ "risk_assessment": risk_assessment
665
+ }
666
+ }
667
+
668
+ # إضافة التطبيق إلى الجدول
669
+ st.session_state.pricing_strategies["strategy_applications"] = pd.concat([
670
+ st.session_state.pricing_strategies["strategy_applications"],
671
+ pd.DataFrame([new_application])
672
+ ], ignore_index=True)
673
+
674
+ st.success(f"تم تطبيق استراتيجية {strategy['name']} على مشروع {project['name']} بنجاح!")
675
+
676
+ # إعادة تحميل الصفحة
677
+ st.experimental_rerun()
678
+
679
+ # عرض نتائج التطبيق
680
+ if not strategy_applications.empty:
681
+ st.markdown("### نتائج تطبيق الاستراتيجيات")
682
+
683
+ # اختيار مشروع لعرض النتائج
684
+ selected_project_id = st.selectbox("اختر المشروع", strategy_applications["project_id"].unique().tolist(), key="results_project", format_func=lambda x: f"{x} - {projects[projects['id'] == x]['name'].values[0]}")
685
+
686
+ # استخراج تطبيقات المشروع المختار
687
+ project_applications = strategy_applications[strategy_applications["project_id"] == selected_project_id]
688
+
689
+ if not project_applications.empty:
690
+ # استخراج المشروع
691
+ project = projects[projects["id"] == selected_project_id].iloc[0]
692
+
693
+ st.markdown(f"**المشروع:** {project['name']} ({project['id']})")
694
+ st.markdown(f"**القيمة الأصلية:** {project['value']:,} ريال")
695
+
696
+ # عرض نتائج كل تطبيق
697
+ for _, application in project_applications.iterrows():
698
+ # استخراج الاستراتيجية
699
+ strategy = strategies[strategies["id"] == application["strategy_id"]].iloc[0]
700
+
701
+ st.markdown(f"#### استراتيجية {strategy['name']}")
702
+
703
+ # استخراج النتائج
704
+ results = application["results"]
705
+
706
+ if isinstance(results, dict):
707
+ col1, col2, col3 = st.columns(3)
708
+
709
+ with col1:
710
+ st.metric("القيمة الجديدة", f"{results['new_value']:,} ريال", f"{((results['new_value'] / results['original_value']) - 1) * 100:.1f}%")
711
+
712
+ with col2:
713
+ st.metric("هامش الربح", f"{results['profit_margin']}%")
714
+
715
+ with col3:
716
+ st.metric("نسبة المحتوى المحلي", f"{results['local_content_percentage']}%")
717
+
718
+ st.markdown(f"**تقييم المخاطر:** {results['risk_assessment']}")
719
+
720
+ if "notes" in application and application["notes"]:
721
+ st.markdown(f"**ملاحظات:** {application['notes']}")
722
+
723
+ st.markdown("---")
724
+
725
+ # مقارنة نتائج التطبيقات
726
+ if len(project_applications) > 1:
727
+ st.markdown("#### مقارنة نتائج تطبيق الاستراتيجيات")
728
+
729
+ # إنشاء بيانات للمقارنة
730
+ comparison_data = []
731
+
732
+ for _, application in project_applications.iterrows():
733
+ # استخراج الاستراتيجية
734
+ strategy = strategies[strategies["id"] == application["strategy_id"]].iloc[0]
735
+
736
+ # استخراج النتائج
737
+ results = application["results"]
738
+
739
+ if isinstance(results, dict):
740
+ comparison_data.append({
741
+ "الاستراتيجية": strategy["name"],
742
+ "القيمة الجديدة": results["new_value"],
743
+ "هامش الربح": results["profit_margin"],
744
+ "نسبة المحتوى المحلي": results["local_content_percentage"],
745
+ "تقييم المخاطر": results["risk_assessment"]
746
+ })
747
+
748
+ comparison_df = pd.DataFrame(comparison_data)
749
+
750
+ # عرض الجدول
751
+ st.dataframe(comparison_df, use_container_width=True)
752
+
753
+ # إنشاء رسم بياني للمقارنة
754
+ fig = px.bar(
755
+ comparison_df,
756
+ x="��لاستراتيجية",
757
+ y="القيمة الجديدة",
758
+ title=f"مقارنة القيمة الجديدة للمشروع حسب الاستراتيجية",
759
+ color="الاستراتيجية",
760
+ text_auto=True
761
+ )
762
+
763
+ # إضافة خط أفقي عند القيمة الأصلية
764
+ fig.add_shape(
765
+ type="line",
766
+ x0=-0.5,
767
+ y0=project["value"],
768
+ x1=len(comparison_df) - 0.5,
769
+ y1=project["value"],
770
+ line=dict(
771
+ color="red",
772
+ width=2,
773
+ dash="dash"
774
+ )
775
+ )
776
+
777
+ # إضافة تسمية للخط الأفقي
778
+ fig.add_annotation(
779
+ x=len(comparison_df) - 0.5,
780
+ y=project["value"],
781
+ text="القيمة الأصلية",
782
+ showarrow=False,
783
+ yshift=10
784
+ )
785
+
786
+ st.plotly_chart(fig, use_container_width=True)
787
+
788
+ # إنشاء رسم بياني للمقارنة بين هامش الربح ونسبة المحتوى المحلي
789
+ fig = px.scatter(
790
+ comparison_df,
791
+ x="هامش الربح",
792
+ y="نسبة المحتوى المحلي",
793
+ title=f"مقارنة هامش الربح ونسبة المحتوى المحلي حسب الاستراتيجية",
794
+ color="الاستراتيجية",
795
+ size="القيمة الجديدة",
796
+ hover_data=["تقييم المخاطر"]
797
+ )
798
+
799
+ st.plotly_chart(fig, use_container_width=True)
800
+ else:
801
+ st.info("لا يوجد تطبيقات لهذا المشروع")
802
+
803
+ def _render_compare_strategies_tab(self):
804
+ """عرض تبويب مقارنة الاستراتيجيات"""
805
+
806
+ st.markdown("### مقارنة استراتيجيات التسعير")
807
+
808
+ # استخراج البيانات
809
+ strategies = st.session_state.pricing_strategies["strategies"]
810
+ projects = st.session_state.pricing_strategies["projects"]
811
+ strategy_comparisons = st.session_state.pricing_strategies["strategy_comparisons"]
812
+
813
+ # اختيار مشروع للمقارنة
814
+ selected_project_id = st.selectbox("اختر المشروع", projects["id"].tolist(), format_func=lambda x: f"{x} - {projects[projects['id'] == x]['name'].values[0]}")
815
+
816
+ # استخراج المشروع
817
+ project = projects[projects["id"] == selected_project_id].iloc[0]
818
+
819
+ # البحث عن مقارنة موجودة
820
+ existing_comparison = None
821
+
822
+ for comparison in strategy_comparisons:
823
+ if comparison["project_id"] == selected_project_id:
824
+ existing_comparison = comparison
825
+ break
826
+
827
+ if existing_comparison:
828
+ st.markdown(f"**المشروع:** {project['name']} ({project['id']})")
829
+ st.markdown(f"**القيمة:** {project['value']:,} ريال")
830
+ st.markdown(f"**تاريخ المقارنة:** {existing_comparison['comparison_date']}")
831
+
832
+ # إنشاء بيانات للمقارنة
833
+ comparison_data = []
834
+
835
+ for strategy_id, results in existing_comparison["strategies"].items():
836
+ # استخراج الاستراتيجية
837
+ strategy = strategies[strategies["id"] == strategy_id].iloc[0]
838
+
839
+ comparison_data.append({
840
+ "الاستراتيجية": strategy["name"],
841
+ "القيمة": results["value"],
842
+ "هامش الربح": results["profit_margin"],
843
+ "نسبة المحتوى المحلي": results["local_content_percentage"],
844
+ "تقييم المخاطر": results["risk_assessment"],
845
+ "تأثير التدفق النقدي": results["cash_flow_impact"]
846
+ })
847
+
848
+ comparison_df = pd.DataFrame(comparison_data)
849
+
850
+ # عرض الجدول
851
+ st.dataframe(comparison_df, use_container_width=True)
852
+
853
+ # إنشاء رسم بياني للمقارنة
854
+ fig = px.bar(
855
+ comparison_df,
856
+ x="الاستراتيجية",
857
+ y="القيمة",
858
+ title=f"مقارنة قيمة المشروع حسب الاستراتيجية",
859
+ color="الاستراتيجية",
860
+ text_auto=True
861
+ )
862
+
863
+ # إضافة خط أفقي عند القيمة الأصلية
864
+ fig.add_shape(
865
+ type="line",
866
+ x0=-0.5,
867
+ y0=project["value"],
868
+ x1=len(comparison_df) - 0.5,
869
+ y1=project["value"],
870
+ line=dict(
871
+ color="red",
872
+ width=2,
873
+ dash="dash"
874
+ )
875
+ )
876
+
877
+ # إضافة تسمية للخط الأفقي
878
+ fig.add_annotation(
879
+ x=len(comparison_df) - 0.5,
880
+ y=project["value"],
881
+ text="القيمة الأصلية",
882
+ showarrow=False,
883
+ yshift=10
884
+ )
885
+
886
+ st.plotly_chart(fig, use_container_width=True)
887
+
888
+ # إنشاء رسم بياني للمقارنة بين هامش الربح ونسبة المحتوى المحلي
889
+ fig = px.scatter(
890
+ comparison_df,
891
+ x="هامش الربح",
892
+ y="نسبة المحتوى المحلي",
893
+ title=f"مقارنة هامش الربح ونسبة المحتوى المحلي حسب الاستراتيجية",
894
+ color="الاستراتيجية",
895
+ size="القيمة",
896
+ hover_data=["تقييم المخاطر", "تأثير التدفق النقدي"]
897
+ )
898
+
899
+ st.plotly_chart(fig, use_container_width=True)
900
+
901
+ # إنشاء رسم بياني للمقارنة بين تأثير التدفق النقدي وتقييم المخاطر
902
+ cash_flow_impact_map = {"إيجابي": 3, "محايد": 2, "سلبي": 1}
903
+ risk_assessment_map = {"منخفض": 1, "متوسط": 2, "مرتفع": 3}
904
+
905
+ comparison_df["تأثير التدفق النقدي (رقمي)"] = comparison_df["تأثير التدفق النقدي"].map(cash_flow_impact_map)
906
+ comparison_df["تقييم المخاطر (رقمي)"] = comparison_df["تقييم المخاطر"].map(risk_assessment_map)
907
+
908
+ fig = px.scatter(
909
+ comparison_df,
910
+ x="تقييم المخاطر (رقمي)",
911
+ y="تأثير التدفق النقدي (رقمي)",
912
+ title=f"مقارنة تأثير التدفق النقدي وتقييم المخاطر حسب الاستراتيجية",
913
+ color="الاستراتيجية",
914
+ size="القيمة",
915
+ hover_data=["هامش الربح", "نسبة المحتوى المحلي"],
916
+ labels={
917
+ "تقييم المخاطر (رقمي)": "تقييم المخاطر",
918
+ "تأثير التدفق النقدي (رقمي)": "تأثير التدفق النقدي"
919
+ }
920
+ )
921
+
922
+ # تعديل محاور الرسم البياني
923
+ fig.update_xaxes(
924
+ tickvals=[1, 2, 3],
925
+ ticktext=["منخفض", "متوسط", "مرتفع"]
926
+ )
927
+
928
+ fig.update_yaxes(
929
+ tickvals=[1, 2, 3],
930
+ ticktext=["سلبي", "محايد", "إيجابي"]
931
+ )
932
+
933
+ st.plotly_chart(fig, use_container_width=True)
934
+
935
+ # تحليل وتوصيات
936
+ st.markdown("### تحليل وتوصيات")
937
+
938
+ # تحديد أفضل استراتيجية حسب المعايير المختلفة
939
+ best_value_strategy = comparison_df.loc[comparison_df["القيمة"].idxmax()]
940
+ best_profit_strategy = comparison_df.loc[comparison_df["هامش الربح"].idxmax()]
941
+ best_local_content_strategy = comparison_df.loc[comparison_df["نسبة المحتوى المحلي"].idxmax()]
942
+ best_cash_flow_strategy = comparison_df.loc[comparison_df["تأثير التدفق النقدي (رقمي)"].idxmax()]
943
+
944
+ # تحديد الاستراتيجية المتوازنة
945
+ comparison_df["النقاط"] = (
946
+ comparison_df["القيمة"] / project["value"] * 25 +
947
+ comparison_df["هامش الربح"] * 2 +
948
+ comparison_df["نسبة المحتوى المحلي"] * 0.5 +
949
+ comparison_df["تأثير التدفق النقدي (رقمي)"] * 5 -
950
+ comparison_df["تقييم المخاطر (رقمي)"] * 5
951
+ )
952
+
953
+ balanced_strategy = comparison_df.loc[comparison_df["النقاط"].idxmax()]
954
+
955
+ st.markdown("#### أفضل الاستراتيجيات حسب المعايير المختلفة")
956
+
957
+ col1, col2 = st.columns(2)
958
+
959
+ with col1:
960
+ st.markdown(f"**أعلى قيمة:** {best_value_strategy['الاستراتيجية']} ({best_value_strategy['القيمة']:,} ريال)")
961
+ st.markdown(f"**أعلى هامش ربح:** {best_profit_strategy['الاستراتيجية']} ({best_profit_strategy['هامش الربح']}%)")
962
+
963
+ with col2:
964
+ st.markdown(f"**أعلى محتوى محلي:** {best_local_content_strategy['الاستراتيجية']} ({best_local_content_strategy['نسبة المحتوى المحلي']}%)")
965
+ st.markdown(f"**أفضل تدفق نقدي:** {best_cash_flow_strategy['الاستراتيجية']} ({best_cash_flow_strategy['تأثير التدفق النقدي']})")
966
+
967
+ st.markdown(f"**الاستراتيجية المتوازنة:** {balanced_strategy['الاستراتيجية']}")
968
+
969
+ # توصيات
970
+ st.markdown("#### التوصيات")
971
+
972
+ # تحديد التوصية بناءً على خصائص المشروع
973
+ if project["client"] == "وزارة النقل" or project["client"] == "أمانة المنطقة الشرقية":
974
+ # المشاريع الحكومية تتطلب محتوى محلي عالي
975
+ if best_local_content_strategy["نسبة المحتوى المحلي"] >= 65:
976
+ recommended_strategy = best_local_content_strategy["الاستراتيجية"]
977
+ recommendation_reason = "لتلبية متطلبات المحتوى المحلي للمشاريع الحكومية"
978
+ else:
979
+ recommended_strategy = balanced_strategy["الاستراتيجية"]
980
+ recommendation_reason = "لتحقيق توازن بين المعايير المختلفة مع مراعاة متطلبات المشاريع الحكومية"
981
+ elif "صرف صحي" in project["name"]:
982
+ # مشاريع الصرف الصحي تتطلب تدفق نقدي جيد
983
+ if best_cash_flow_strategy["تأثير التدفق النقدي"] == "إيجابي":
984
+ recommended_strategy = best_cash_flow_strategy["الاستراتيجية"]
985
+ recommendation_reason = "لضمان تدفق نقدي إيجابي في مشاريع الصرف الصحي التي تتطلب استثمارات أولية كبيرة"
986
+ else:
987
+ recommended_strategy = balanced_strategy["الاستراتيجية"]
988
+ recommendation_reason = "لتحقيق توازن بين المعايير المختلفة مع مراعاة متطلبات مشاريع الصرف الصحي"
989
+ elif project["value"] > 50000000:
990
+ # المشاريع الكبيرة تتطلب إدارة مخاطر جيدة
991
+ low_risk_strategies = comparison_df[comparison_df["تقييم المخاطر"] == "منخفض"]
992
+
993
+ if not low_risk_strategies.empty:
994
+ low_risk_balanced = low_risk_strategies.loc[low_risk_strategies["النقاط"].idxmax()]
995
+ recommended_strategy = low_risk_balanced["الاستراتيجية"]
996
+ recommendation_reason = "لتقليل المخاطر في المشاريع الكبيرة مع تحقيق توازن بين المعايير الأخرى"
997
+ else:
998
+ recommended_strategy = balanced_strategy["الاستراتيجية"]
999
+ recommendation_reason = "لتحقيق توازن بين المعايير المختلفة مع مراعاة حجم المشروع الكبير"
1000
+ else:
1001
+ # المشاريع الأخرى
1002
+ recommended_strategy = balanced_strategy["الاستراتيجية"]
1003
+ recommendation_reason = "لتحقيق توازن بين المعايير المختلفة"
1004
+
1005
+ st.markdown(f"**الاستراتيجية الموصى بها:** {recommended_strategy}")
1006
+ st.markdown(f"**سبب التوصية:** {recommendation_reason}")
1007
+
1008
+ # نصائح إضافية
1009
+ st.markdown("#### نصائح إضافية")
1010
+
1011
+ if "جسر" in project["name"] or "طريق" in project["name"]:
1012
+ st.markdown("- مراعاة تكاليف المواد الإنشائية وتقلبات أسعارها في السوق السعودي")
1013
+ st.markdown("- التأكد من توفر المعدات الثقيلة اللازمة للمشروع")
1014
+ st.markdown("- مراعاة تكاليف النقل للمواد الإنشائية")
1015
+
1016
+ if "صرف صحي" in project["name"]:
1017
+ st.markdown("- مراعاة تكاليف الحفر والردم")
1018
+ st.markdown("- التأكد من توفر المواسير والملحقات بالمواصفات المطلوبة")
1019
+ st.markdown("- مراعاة تكاليف الاختبارات والفحوصات")
1020
+
1021
+ if project["value"] > 50000000:
1022
+ st.markdown("- تقسيم المشروع إلى مراحل لتحسين التدفق النقدي")
1023
+ st.markdown("- مراعاة تكاليف الضمانات البنكية")
1024
+ st.markdown("- التأكد من توفر السيولة اللازمة للمشروع")
1025
+
1026
+ # إعادة المقارنة
1027
+ st.markdown("#### إعادة المقارنة")
1028
+
1029
+ if st.button("إعادة المقارنة"):
1030
+ # إنشاء مقارنة جديدة
1031
+ new_comparison = {
1032
+ "project_id": selected_project_id,
1033
+ "comparison_date": datetime.now().strftime("%Y-%m-%d"),
1034
+ "compared_by": "مدير التسعير",
1035
+ "strategies": {}
1036
+ }
1037
+
1038
+ # إضافة نتائج لكل استراتيجية
1039
+ base_value = project["value"]
1040
+
1041
+ for _, strategy in strategies.iterrows():
1042
+ # حساب قيمة افتراضية مختلفة لكل استراتيجية
1043
+ if strategy["id"] == "STD-001": # التسعير القياسي
1044
+ value_factor = 1.0
1045
+ profit_margin = 15
1046
+ local_content = 60
1047
+ cash_flow_impact = "محايد"
1048
+ elif strategy["id"] == "BAL-001": # التسعير المتزن
1049
+ value_factor = 1.05
1050
+ profit_margin = 18
1051
+ local_content = 62
1052
+ cash_flow_impact = "محايد"
1053
+ elif strategy["id"] == "UNB-001": # التسعير غير المتزن
1054
+ value_factor = 1.08
1055
+ profit_margin = 20
1056
+ local_content = 58
1057
+ cash_flow_impact = "إيجابي"
1058
+ elif strategy["id"] == "PRF-001": # التسعير الموجه للربحية
1059
+ value_factor = 1.15
1060
+ profit_margin = 25
1061
+ local_content = 55
1062
+ cash_flow_impact = "إيجابي"
1063
+ elif strategy["id"] == "BDL-001": # التسعير بالتجميع
1064
+ value_factor = 1.03
1065
+ profit_margin = 17
1066
+ local_content = 63
1067
+ cash_flow_impact = "محايد"
1068
+ else: # التسعير بالمحتوى المحلي
1069
+ value_factor = 1.1
1070
+ profit_margin = 15
1071
+ local_content = 70
1072
+ cash_flow_impact = "محايد"
1073
+
1074
+ # تحديد تقييم المخاطر
1075
+ risk_assessment = "منخفض" if strategy["risk_factor"] < 8 else "متوسط" if strategy["risk_factor"] < 15 else "مرتفع"
1076
+
1077
+ new_comparison["strategies"][strategy["id"]] = {
1078
+ "value": base_value * value_factor,
1079
+ "profit_margin": profit_margin,
1080
+ "local_content_percentage": local_content,
1081
+ "risk_assessment": risk_assessment,
1082
+ "cash_flow_impact": cash_flow_impact
1083
+ }
1084
+
1085
+ # تحديث المقارنة الموجودة
1086
+ for i, comparison in enumerate(strategy_comparisons):
1087
+ if comparison["project_id"] == selected_project_id:
1088
+ strategy_comparisons[i] = new_comparison
1089
+ break
1090
+
1091
+ st.success("تم إعادة المقارنة بنجاح!")
1092
+
1093
+ # إعادة تحميل الصفحة
1094
+ st.experimental_rerun()
1095
+ else:
1096
+ st.info("لا يوجد مقارنة لهذا المشروع")
1097
+
1098
+ # إنشاء مقارنة جديدة
1099
+ if st.button("إنشاء مقارنة جديدة"):
1100
+ # إنشاء مقارنة جديدة
1101
+ new_comparison = {
1102
+ "project_id": selected_project_id,
1103
+ "comparison_date": datetime.now().strftime("%Y-%m-%d"),
1104
+ "compared_by": "مدير التسعير",
1105
+ "strategies": {}
1106
+ }
1107
+
1108
+ # إضافة نتائج لكل استراتيجية
1109
+ base_value = project["value"]
1110
+
1111
+ for _, strategy in strategies.iterrows():
1112
+ # حساب قيمة افتراضية مختلفة لكل استراتيجية
1113
+ if strategy["id"] == "STD-001": # التسعير القياسي
1114
+ value_factor = 1.0
1115
+ profit_margin = 15
1116
+ local_content = 60
1117
+ cash_flow_impact = "محايد"
1118
+ elif strategy["id"] == "BAL-001": # التسعير المتزن
1119
+ value_factor = 1.05
1120
+ profit_margin = 18
1121
+ local_content = 62
1122
+ cash_flow_impact = "محايد"
1123
+ elif strategy["id"] == "UNB-001": # التسعير غير المتزن
1124
+ value_factor = 1.08
1125
+ profit_margin = 20
1126
+ local_content = 58
1127
+ cash_flow_impact = "إيجابي"
1128
+ elif strategy["id"] == "PRF-001": # التسعير الموجه للربحية
1129
+ value_factor = 1.15
1130
+ profit_margin = 25
1131
+ local_content = 55
1132
+ cash_flow_impact = "إيجابي"
1133
+ elif strategy["id"] == "BDL-001": # التسعير بالتجميع
1134
+ value_factor = 1.03
1135
+ profit_margin = 17
1136
+ local_content = 63
1137
+ cash_flow_impact = "محايد"
1138
+ else: # التسعير بالمحتوى المحلي
1139
+ value_factor = 1.1
1140
+ profit_margin = 15
1141
+ local_content = 70
1142
+ cash_flow_impact = "محايد"
1143
+
1144
+ # تحديد تقييم المخاطر
1145
+ risk_assessment = "منخفض" if strategy["risk_factor"] < 8 else "متوسط" if strategy["risk_factor"] < 15 else "مرتفع"
1146
+
1147
+ new_comparison["strategies"][strategy["id"]] = {
1148
+ "value": base_value * value_factor,
1149
+ "profit_margin": profit_margin,
1150
+ "local_content_percentage": local_content,
1151
+ "risk_assessment": risk_assessment,
1152
+ "cash_flow_impact": cash_flow_impact
1153
+ }
1154
+
1155
+ # إضافة المقارنة الجديدة
1156
+ st.session_state.pricing_strategies["strategy_comparisons"].append(new_comparison)
1157
+
1158
+ st.success("تم إنشاء المقارنة بنجاح!")
1159
+
1160
+ # إعادة تحميل الصفحة
1161
+ st.experimental_rerun()
1162
+
1163
+ def _render_local_content_tab(self):
1164
+ """عرض تبويب تحليل المحتوى المحلي"""
1165
+
1166
+ st.markdown("### تحليل المحتوى المحلي")
1167
+
1168
+ # استخراج البيانات
1169
+ strategies = st.session_state.pricing_strategies["strategies"]
1170
+ projects = st.session_state.pricing_strategies["projects"]
1171
+ strategy_applications = st.session_state.pricing_strategies["strategy_applications"]
1172
+
1173
+ # عرض معلومات عن المحتوى المحلي
1174
+ st.markdown("""
1175
+ المحتوى المحلي هو نسبة المواد والخدمات والعمالة المحلية المستخدمة في المشروع. يعتبر المحتوى المحلي من المتطلبات الهامة في المشاريع الحكومية في المملكة العربية السعودية، وذلك تماشياً مع رؤية المملكة 2030 وبرنامج تعزيز القيمة المضافة المحلية (اكتفاء).
1176
+
1177
+ يتم حساب نسبة المحتوى المحلي بناءً على:
1178
+ - نسبة المواد المحلية المستخدمة في المشروع
1179
+ - نسبة المعدات المحلية المستخدمة في المشروع
1180
+ - نسبة العمالة المحلية المستخدمة في المشروع
1181
+ - نسبة المقاولين من الباطن المحليين المشاركين في المشروع
1182
+ """)
1183
+
1184
+ # إنشاء نموذج لحساب المحتوى المحلي
1185
+ st.markdown("#### حساب المحتوى المحلي")
1186
+
1187
+ with st.form("calculate_local_content_form"):
1188
+ # الصف الأول
1189
+ col1, col2 = st.columns(2)
1190
+
1191
+ with col1:
1192
+ project_id = st.selectbox("اختر المشروع", projects["id"].tolist(), format_func=lambda x: f"{x} - {projects[projects['id'] == x]['name'].values[0]}")
1193
+ materials_percentage = st.slider("نسبة المواد من إجمالي التكلفة (%)", min_value=0, max_value=100, value=50, step=5)
1194
+
1195
+ with col2:
1196
+ local_materials_percentage = st.slider("نسبة المواد المحلية (%)", min_value=0, max_value=100, value=60, step=5)
1197
+ equipment_percentage = st.slider("نسبة المعدات من إجمالي التكلفة (%)", min_value=0, max_value=100, value=20, step=5)
1198
+
1199
+ # الصف الثاني
1200
+ col1, col2 = st.columns(2)
1201
+
1202
+ with col1:
1203
+ local_equipment_percentage = st.slider("نسبة المعدات المحلية (%)", min_value=0, max_value=100, value=40, step=5)
1204
+ labor_percentage = st.slider("نسبة العمالة من إجمالي التكلفة (%)", min_value=0, max_value=100, value=20, step=5)
1205
+
1206
+ with col2:
1207
+ local_labor_percentage = st.slider("نسبة العمالة المحلية (%)", min_value=0, max_value=100, value=30, step=5)
1208
+ subcontractors_percentage = st.slider("نسبة المقاولين من الباطن من إجمالي التكلفة (%)", min_value=0, max_value=100, value=10, step=5)
1209
+
1210
+ # الصف الثالث
1211
+ local_subcontractors_percentage = st.slider("نسبة المقاولين من الباطن المحليين (%)", min_value=0, max_value=100, value=70, step=5)
1212
+
1213
+ # التحقق من إجمالي النسب
1214
+ total_percentage = materials_percentage + equipment_percentage + labor_percentage + subcontractors_percentage
1215
+
1216
+ if total_percentage != 100:
1217
+ st.warning(f"إجمالي النسب يجب أن يكون 100%، الإجمالي الحالي: {total_percentage}%")
1218
+
1219
+ # زر الحساب
1220
+ submit_button = st.form_submit_button("حساب المحتوى المحلي")
1221
+
1222
+ if submit_button:
1223
+ # حساب المحتوى المحلي
1224
+ local_content = (
1225
+ materials_percentage * local_materials_percentage / 100 +
1226
+ equipment_percentage * local_equipment_percentage / 100 +
1227
+ labor_percentage * local_labor_percentage / 100 +
1228
+ subcontractors_percentage * local_subcontractors_percentage / 100
1229
+ )
1230
+
1231
+ # عرض النتيجة
1232
+ st.success(f"نسبة المحتوى المحلي: {local_content:.2f}%")
1233
+
1234
+ # تحليل النتيجة
1235
+ if local_content >= 70:
1236
+ st.success("تحقق هذه النسبة متطلبات المحتوى المحلي للمشاريع الحكومية")
1237
+ elif local_content >= 50:
1238
+ st.warning("تحقق هذه النسبة الحد الأدنى من متطلبات المحتوى المحلي، ولكن يفضل زيادتها")
1239
+ else:
1240
+ st.error("لا تحقق هذه النسبة متطلبات المحتوى المحلي للمشاريع الحكومية")
1241
+
1242
+ # توصيات لزيادة المحتوى المحلي
1243
+ st.markdown("#### توصيات لزيادة المحتوى المحلي")
1244
+
1245
+ recommendations = []
1246
+
1247
+ if local_materials_percentage < 70:
1248
+ recommendations.append("- زيادة نسبة المواد المحلية المستخدمة في المشروع")
1249
+
1250
+ if local_equipment_percentage < 50:
1251
+ recommendations.append("- زيادة نسبة المعدات المحلية المستخدمة في المشروع")
1252
+
1253
+ if local_labor_percentage < 40:
1254
+ recommendations.append("- زيادة نسبة العمالة المحلية المستخدمة في المشروع")
1255
+
1256
+ if local_subcontractors_percentage < 80:
1257
+ recommendations.append("- زيادة نسبة المقاولين من الباطن المحليين المشاركين في المشروع")
1258
+
1259
+ if recommendations:
1260
+ for recommendation in recommendations:
1261
+ st.markdown(recommendation)
1262
+ else:
1263
+ st.success("لا توجد توصيات إضافية، نسبة المحتوى المحلي جيدة")
1264
+
1265
+ # إنشاء رسم بياني للمحتوى المحلي
1266
+ local_content_data = pd.DataFrame({
1267
+ "المكون": ["المواد", "المعدات", "العمالة", "المقاولين من الباطن"],
1268
+ "النسبة من إجمالي التكلفة": [materials_percentage, equipment_percentage, labor_percentage, subcontractors_percentage],
1269
+ "نسبة المحتوى المحلي": [local_materials_percentage, local_equipment_percentage, local_labor_percentage, local_subcontractors_percentage],
1270
+ "المساهمة في المحتوى المحلي": [
1271
+ materials_percentage * local_materials_percentage / 100,
1272
+ equipment_percentage * local_equipment_percentage / 100,
1273
+ labor_percentage * local_labor_percentage / 100,
1274
+ subcontractors_percentage * local_subcontractors_percentage / 100
1275
+ ]
1276
+ })
1277
+
1278
+ # عرض الجدول
1279
+ st.dataframe(local_content_data, use_container_width=True)
1280
+
1281
+ # إنشاء رسم بياني للمساهمة في المحتوى المحلي
1282
+ fig = px.bar(
1283
+ local_content_data,
1284
+ x="المكون",
1285
+ y="المساهمة في المحتوى المحلي",
1286
+ title="مساهمة كل مكون في المحتوى المحلي",
1287
+ color="المكون",
1288
+ text_auto=True
1289
+ )
1290
+
1291
+ st.plotly_chart(fig, use_container_width=True)
1292
+
1293
+ # إنشاء رسم بياني للمقارنة بين النسبة من إجمالي التكلفة ونسبة المحتوى المحلي
1294
+ fig = px.bar(
1295
+ local_content_data,
1296
+ x="المكون",
1297
+ y=["النسبة من إجمالي التكلفة", "نسبة المحتوى المحلي"],
1298
+ title="مقارنة بين النسبة من إجمالي التكلفة ونسبة المحتوى المحلي",
1299
+ barmode="group",
1300
+ text_auto=True
1301
+ )
1302
+
1303
+ st.plotly_chart(fig, use_container_width=True)
1304
+
1305
+ # عرض معلومات عن استراتيجية التسعير بالمحتوى المحلي
1306
+ st.markdown("#### استراتيجية التسعير بالمحتوى المحلي")
1307
+
1308
+ st.markdown("""
1309
+ استراتيجية التسعير بالمحتوى المحلي تهدف إلى زيادة نسبة المحتوى المحلي في المشروع لتحقيق متطلبات الجهات المالكة. تعتمد هذه الاستراتيجية على:
1310
+
1311
+ 1. **تحديد نسبة المحتوى المحلي المستهدفة**: تحديد النسبة المستهدفة للمحتوى المحلي في المشروع بناءً على متطلبات الجهة المالكة.
1312
+
1313
+ 2. **تحديد علاوة المحتوى المحلي**: زيادة أسعار البنود التي تستخدم مواد ومعدات وعمالة محلية لتعويض التكلفة الإضافية.
1314
+
1315
+ 3. **تحديد خصم المحتوى المستورد**: تخفيض أسعار البنود التي تستخدم مواد ومعدات وعمالة مستوردة لتحقيق التوازن في السعر الإجمالي.
1316
+
1317
+ 4. **تحليل تأثير المحتوى المحلي على التكلفة**: دراسة تأثير زيادة المحتوى المحلي على التكلفة الإجمالية للمشروع.
1318
+
1319
+ 5. **تحليل تأثير المحتوى المحلي على الجدول الزمني**: دراسة تأثير زيادة المحتوى المحلي على الجدول الزمني للمشروع.
1320
+ """)
1321
+
1322
+ # عرض مزايا وعيوب استراتيجية التسعير بالمحتوى المحلي
1323
+ col1, col2 = st.columns(2)
1324
+
1325
+ with col1:
1326
+ st.markdown("##### مزايا استراتيجية التسعير بالمحتوى المحلي")
1327
+
1328
+ st.markdown("""
1329
+ - تلبية متطلبات الجهات المالكة للمحتوى المحلي
1330
+ - زيادة فرص الفوز بالمناقصات الحكومية
1331
+ - المساهمة في تحقيق رؤية المملكة 2030
1332
+ - تقليل مخاطر تقلبات أسعار الصرف
1333
+ - تقليل مخاطر التأخير في التوريد
1334
+ """)
1335
+
1336
+ with col2:
1337
+ st.markdown("##### عيوب استراتيجية التسعير بالمحتوى المحلي")
1338
+
1339
+ st.markdown("""
1340
+ - زيادة التكلفة الإجمالية للمشروع
1341
+ - صعوبة توفير بعض المواد والمعدات محلياً
1342
+ - صعوبة توفير العمالة المحلية المؤهلة
1343
+ - تأثير محتمل على جودة المشروع
1344
+ - تأثير محتمل على الجدول الزمني للمشروع
1345
+ """)
1346
+
1347
+ # عرض متطلبات المحتوى المحلي في المشاريع الحكومية
1348
+ st.markdown("#### متطلبات المحتوى المحلي في المشاريع الحكومية")
1349
+
1350
+ st.markdown("""
1351
+ تختلف متطلبات المحتوى المحلي في المشاريع الحكومية حسب نوع المشروع والجهة المالكة. بشكل عام، تتراوح نسبة المحتوى المحلي المطلوبة بين 30% و 70% حسب نوع المشروع.
1352
+
1353
+ بعض الجهات الحكومية تطبق نظام النقاط في تقييم العروض، حيث يتم منح نقاط إضافية للعروض التي تحقق نسبة محتوى محلي أعلى.
1354
+
1355
+ يمكن الاطلاع على متطلبات المحتوى المحلي لكل جهة حكومية من خلال موقع هيئة المحتوى المحلي والمشتريات الحكومية.
1356
+ """)
1357
+
1358
+ # عرض مصادر المواد والمعدات المحلية
1359
+ st.markdown("#### مصادر المواد والمعدات المحلية")
1360
+
1361
+ st.markdown("""
1362
+ يمكن الحصول على المواد والمعدات المحلية من خلال:
1363
+
1364
+ 1. **المصانع المحلية**: مثل مصانع الحديد والإسمنت والخرسانة الجاهزة والأنابيب وغيرها.
1365
+
1366
+ 2. **الموردين المحليين**: مثل موردي المعدات والآليات والأدوات وغيرها.
1367
+
1368
+ 3. **الوكلاء المحليين**: مثل وكلاء الشركات العالمية في المملكة.
1369
+
1370
+ 4. **المقاولين من الباطن المحليين**: مثل مقاولي الحفر والردم والتشطيبات وغيرها.
1371
+
1372
+ يمكن الاطلاع على قائمة المصانع والموردين المحليين من خلال موقع هيئة المحتوى المحلي والمشتريات الحكومية.
1373
+ """)
1374
+
1375
+ def apply_strategy_to_project(self, project_id, strategy_id):
1376
+ """تطبيق استراتيجية على مشروع"""
1377
+
1378
+ # استخراج البيانات
1379
+ strategies = st.session_state.pricing_strategies["strategies"]
1380
+ projects = st.session_state.pricing_strategies["projects"]
1381
+ strategy_applications = st.session_state.pricing_strategies["strategy_applications"]
1382
+
1383
+ # التحقق من وجود المشروع والاستراتيجية
1384
+ project = projects[projects["id"] == project_id]
1385
+ strategy = strategies[strategies["id"] == strategy_id]
1386
+
1387
+ if project.empty or strategy.empty:
1388
+ return False, "المشروع أو الاستراتيجية غير موجودة"
1389
+
1390
+ # التحقق من عدم وجود تطبيق سابق للاستراتيجية على المشروع
1391
+ existing_application = strategy_applications[
1392
+ (strategy_applications["project_id"] == project_id) &
1393
+ (strategy_applications["strategy_id"] == strategy_id)
1394
+ ]
1395
+
1396
+ if not existing_application.empty:
1397
+ return False, "تم تطبيق هذه الاستراتيجية على هذا المشروع مسبقاً"
1398
+
1399
+ # استخراج بيانات المشروع والاستراتيجية
1400
+ project = project.iloc[0]
1401
+ strategy = strategy.iloc[0]
1402
+
1403
+ # حساب نتائج تطبيق الاستراتيجية
1404
+ original_value = project["value"]
1405
+
1406
+ # حساب القيمة الجديدة بناءً على نوع الاستراتيجية
1407
+ if "STD" in strategy_id: # التسعير القياسي
1408
+ new_value = original_value * (1 + strategy["profit_margin"] / 100)
1409
+ profit_margin = strategy["profit_margin"]
1410
+ local_content = 60
1411
+ elif "BAL" in strategy_id: # التسعير المتزن
1412
+ new_value = original_value * (1 + (strategy["profit_margin"] + strategy["risk_factor"]) / 100)
1413
+ profit_margin = strategy["profit_margin"]
1414
+ local_content = 62
1415
+ elif "UNB" in strategy_id: # التسعير غير المتزن
1416
+ new_value = original_value * (1 + (strategy["profit_margin"] + strategy["risk_factor"]) / 100)
1417
+ profit_margin = strategy["profit_margin"]
1418
+ local_content = 58
1419
+ elif "PRF" in strategy_id: # التسعير الموجه للربحية
1420
+ new_value = original_value * (1 + (strategy["profit_margin"] + strategy["risk_factor"] / 2) / 100)
1421
+ profit_margin = strategy["profit_margin"]
1422
+ local_content = 55
1423
+ elif "BDL" in strategy_id: # التسعير بالتجميع
1424
+ new_value = original_value * (1 + strategy["profit_margin"] / 100)
1425
+ profit_margin = strategy["profit_margin"]
1426
+ local_content = 63
1427
+ else: # التسعير بالمحتوى المحلي
1428
+ new_value = original_value * (1 + (strategy["profit_margin"] - 2) / 100)
1429
+ profit_margin = strategy["profit_margin"] - 2
1430
+ local_content = 70
1431
+
1432
+ # تحديد تقييم المخاطر
1433
+ risk_assessment = "منخفض" if strategy["risk_factor"] < 8 else "متوسط" if strategy["risk_factor"] < 15 else "مرتفع"
1434
+
1435
+ # إنشاء تطبيق جديد
1436
+ new_application = {
1437
+ "project_id": project_id,
1438
+ "strategy_id": strategy_id,
1439
+ "application_date": datetime.now().strftime("%Y-%m-%d"),
1440
+ "applied_by": "مدير التسعير",
1441
+ "status": "مطبق",
1442
+ "notes": "تم تطبيق الاستراتيجية بنجاح",
1443
+ "results": {
1444
+ "original_value": original_value,
1445
+ "new_value": new_value,
1446
+ "profit_margin": profit_margin,
1447
+ "local_content_percentage": local_content,
1448
+ "risk_assessment": risk_assessment
1449
+ }
1450
+ }
1451
+
1452
+ # إضافة التطبيق إلى الجدول
1453
+ st.session_state.pricing_strategies["strategy_applications"] = pd.concat([
1454
+ st.session_state.pricing_strategies["strategy_applications"],
1455
+ pd.DataFrame([new_application])
1456
+ ], ignore_index=True)
1457
+
1458
+ return True, f"تم تطبيق استراتيجية {strategy['name']} على مشروع {project['name']} بنجاح!"
1459
+
1460
+ def get_strategy_by_id(self, strategy_id):
1461
+ """الحصول على استراتيجية بواسطة الكود"""
1462
+
1463
+ # استخراج البيانات
1464
+ strategies = st.session_state.pricing_strategies["strategies"]
1465
+
1466
+ # البحث عن الاستراتيجية
1467
+ strategy = strategies[strategies["id"] == strategy_id]
1468
+
1469
+ if not strategy.empty:
1470
+ return strategy.iloc[0].to_dict()
1471
+
1472
+ return None
1473
+
1474
+ def get_project_by_id(self, project_id):
1475
+ """الحصول على مشروع بواسطة الكود"""
1476
+
1477
+ # استخراج البيانات
1478
+ projects = st.session_state.pricing_strategies["projects"]
1479
+
1480
+ # البحث عن المشروع
1481
+ project = projects[projects["id"] == project_id]
1482
+
1483
+ if not project.empty:
1484
+ return project.iloc[0].to_dict()
1485
+
1486
+ return None
1487
+
1488
+ def calculate_local_content(self, materials_percentage, local_materials_percentage, equipment_percentage, local_equipment_percentage, labor_percentage, local_labor_percentage, subcontractors_percentage, local_subcontractors_percentage):
1489
+ """حساب نسبة المحتوى المحلي"""
1490
+
1491
+ # التحقق من إجمالي النسب
1492
+ total_percentage = materials_percentage + equipment_percentage + labor_percentage + subcontractors_percentage
1493
+
1494
+ if total_percentage != 100:
1495
+ return False, f"إجمالي النسب يجب أن يكون 100%، الإجمالي الحالي: {total_percentage}%"
1496
+
1497
+ # حساب المحتوى المحلي
1498
+ local_content = (
1499
+ materials_percentage * local_materials_percentage / 100 +
1500
+ equipment_percentage * local_equipment_percentage / 100 +
1501
+ labor_percentage * local_labor_percentage / 100 +
1502
+ subcontractors_percentage * local_subcontractors_percentage / 100
1503
+ )
1504
+
1505
+ return True, local_content
pricing_system/static/css/unified_style.css ADDED
@@ -0,0 +1,423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * نظام تحليل المناقصات - الأنماط الموحدة
3
+ * Tender Analysis System - Unified Styles
4
+ */
5
+
6
+ /* الألوان الأساسية */
7
+ :root {
8
+ --primary-color: #1e88e5;
9
+ --secondary-color: #26a69a;
10
+ --accent-color: #ff9800;
11
+ --background-color: #f5f5f5;
12
+ --card-color: #ffffff;
13
+ --text-color: #333333;
14
+ --text-light-color: #757575;
15
+ --border-color: #e0e0e0;
16
+ --success-color: #4caf50;
17
+ --warning-color: #ff9800;
18
+ --error-color: #f44336;
19
+ --info-color: #2196f3;
20
+ }
21
+
22
+ /* تنسيق الخط والاتجاه */
23
+ body {
24
+ font-family: 'Cairo', 'Tajawal', sans-serif;
25
+ direction: rtl;
26
+ text-align: right;
27
+ background-color: var(--background-color);
28
+ color: var(--text-color);
29
+ }
30
+
31
+ /* العناوين */
32
+ h1, h2, h3, h4, h5, h6 {
33
+ font-family: 'Cairo', 'Tajawal', sans-serif;
34
+ font-weight: 700;
35
+ color: var(--primary-color);
36
+ margin-bottom: 1rem;
37
+ }
38
+
39
+ h1 {
40
+ font-size: 2.2rem;
41
+ border-bottom: 2px solid var(--primary-color);
42
+ padding-bottom: 0.5rem;
43
+ }
44
+
45
+ h2 {
46
+ font-size: 1.8rem;
47
+ color: var(--secondary-color);
48
+ }
49
+
50
+ h3 {
51
+ font-size: 1.5rem;
52
+ }
53
+
54
+ /* البطاقات */
55
+ .card {
56
+ background-color: var(--card-color);
57
+ border-radius: 8px;
58
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
59
+ padding: 1.5rem;
60
+ margin-bottom: 1.5rem;
61
+ }
62
+
63
+ .card-header {
64
+ border-bottom: 1px solid var(--border-color);
65
+ padding-bottom: 1rem;
66
+ margin-bottom: 1rem;
67
+ display: flex;
68
+ justify-content: space-between;
69
+ align-items: center;
70
+ }
71
+
72
+ .card-title {
73
+ font-size: 1.3rem;
74
+ font-weight: 600;
75
+ color: var(--primary-color);
76
+ margin: 0;
77
+ }
78
+
79
+ /* الأزرار */
80
+ .stButton > button {
81
+ background-color: var(--primary-color);
82
+ color: white;
83
+ border-radius: 4px;
84
+ border: none;
85
+ padding: 0.5rem 1rem;
86
+ font-family: 'Cairo', 'Tajawal', sans-serif;
87
+ font-weight: 600;
88
+ transition: all 0.3s ease;
89
+ }
90
+
91
+ .stButton > button:hover {
92
+ background-color: #1976d2;
93
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
94
+ }
95
+
96
+ .secondary-button > button {
97
+ background-color: var(--secondary-color);
98
+ }
99
+
100
+ .secondary-button > button:hover {
101
+ background-color: #00897b;
102
+ }
103
+
104
+ .accent-button > button {
105
+ background-color: var(--accent-color);
106
+ }
107
+
108
+ .accent-button > button:hover {
109
+ background-color: #fb8c00;
110
+ }
111
+
112
+ .danger-button > button {
113
+ background-color: var(--error-color);
114
+ }
115
+
116
+ .danger-button > button:hover {
117
+ background-color: #e53935;
118
+ }
119
+
120
+ /* حقول الإدخال */
121
+ .stTextInput > div > div > input {
122
+ border-radius: 4px;
123
+ border: 1px solid var(--border-color);
124
+ padding: 0.5rem;
125
+ font-family: 'Cairo', 'Tajawal', sans-serif;
126
+ }
127
+
128
+ .stTextInput > div > div > input:focus {
129
+ border-color: var(--primary-color);
130
+ box-shadow: 0 0 0 2px rgba(30, 136, 229, 0.2);
131
+ }
132
+
133
+ /* القوائم المنسدلة */
134
+ .stSelectbox > div > div > div {
135
+ border-radius: 4px;
136
+ border: 1px solid var(--border-color);
137
+ }
138
+
139
+ .stSelectbox > div > div > div:focus {
140
+ border-color: var(--primary-color);
141
+ box-shadow: 0 0 0 2px rgba(30, 136, 229, 0.2);
142
+ }
143
+
144
+ /* الجداول */
145
+ .stDataFrame {
146
+ border-radius: 8px;
147
+ overflow: hidden;
148
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
149
+ }
150
+
151
+ .stDataFrame > div {
152
+ border: none !important;
153
+ }
154
+
155
+ .stDataFrame th {
156
+ background-color: var(--primary-color);
157
+ color: white;
158
+ font-weight: 600;
159
+ padding: 0.75rem 1rem;
160
+ }
161
+
162
+ .stDataFrame td {
163
+ padding: 0.75rem 1rem;
164
+ border-bottom: 1px solid var(--border-color);
165
+ }
166
+
167
+ .stDataFrame tr:nth-child(even) {
168
+ background-color: rgba(0, 0, 0, 0.02);
169
+ }
170
+
171
+ /* الشريط الجانبي */
172
+ .sidebar .sidebar-content {
173
+ background-color: #263238;
174
+ color: white;
175
+ }
176
+
177
+ .sidebar .sidebar-content h1,
178
+ .sidebar .sidebar-content h2,
179
+ .sidebar .sidebar-content h3 {
180
+ color: white;
181
+ }
182
+
183
+ .sidebar .sidebar-content .stRadio > div {
184
+ background-color: #37474f;
185
+ border-radius: 8px;
186
+ padding: 0.5rem;
187
+ }
188
+
189
+ .sidebar .sidebar-content .stRadio > div > div > label {
190
+ color: white;
191
+ }
192
+
193
+ /* علامات التبويب */
194
+ .stTabs {
195
+ background-color: var(--card-color);
196
+ border-radius: 8px;
197
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
198
+ padding: 1rem;
199
+ }
200
+
201
+ .stTabs > div > div > div > div {
202
+ font-family: 'Cairo', 'Tajawal', sans-serif;
203
+ font-weight: 600;
204
+ }
205
+
206
+ .stTabs > div > div > div > div[data-baseweb="tab-highlight"] {
207
+ background-color: var(--primary-color);
208
+ }
209
+
210
+ /* الرسائل */
211
+ .stSuccess, .stInfo, .stWarning, .stError {
212
+ border-radius: 8px;
213
+ padding: 1rem;
214
+ margin-bottom: 1rem;
215
+ }
216
+
217
+ .stSuccess {
218
+ background-color: rgba(76, 175, 80, 0.1);
219
+ border-left: 4px solid var(--success-color);
220
+ }
221
+
222
+ .stInfo {
223
+ background-color: rgba(33, 150, 243, 0.1);
224
+ border-left: 4px solid var(--info-color);
225
+ }
226
+
227
+ .stWarning {
228
+ background-color: rgba(255, 152, 0, 0.1);
229
+ border-left: 4px solid var(--warning-color);
230
+ }
231
+
232
+ .stError {
233
+ background-color: rgba(244, 67, 54, 0.1);
234
+ border-left: 4px solid var(--error-color);
235
+ }
236
+
237
+ /* الرسوم البيانية */
238
+ .stPlotlyChart {
239
+ background-color: var(--card-color);
240
+ border-radius: 8px;
241
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
242
+ padding: 1rem;
243
+ margin-bottom: 1.5rem;
244
+ }
245
+
246
+ /* تخصيص للغة العربية */
247
+ [dir="rtl"] .stPlotlyChart {
248
+ direction: ltr;
249
+ }
250
+
251
+ /* الشعار */
252
+ .logo-container {
253
+ display: flex;
254
+ justify-content: center;
255
+ margin-bottom: 1.5rem;
256
+ }
257
+
258
+ .logo-container img {
259
+ max-width: 200px;
260
+ height: auto;
261
+ }
262
+
263
+ /* تنسيق الصفوف والأعمدة */
264
+ .row {
265
+ display: flex;
266
+ flex-wrap: wrap;
267
+ margin-right: -0.75rem;
268
+ margin-left: -0.75rem;
269
+ }
270
+
271
+ .col {
272
+ flex: 1 0 0%;
273
+ padding-right: 0.75rem;
274
+ padding-left: 0.75rem;
275
+ }
276
+
277
+ /* تنسيق البطاقات الإحصائية */
278
+ .stat-card {
279
+ background-color: var(--card-color);
280
+ border-radius: 8px;
281
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
282
+ padding: 1.5rem;
283
+ margin-bottom: 1.5rem;
284
+ text-align: center;
285
+ }
286
+
287
+ .stat-card-primary {
288
+ border-top: 4px solid var(--primary-color);
289
+ }
290
+
291
+ .stat-card-secondary {
292
+ border-top: 4px solid var(--secondary-color);
293
+ }
294
+
295
+ .stat-card-accent {
296
+ border-top: 4px solid var(--accent-color);
297
+ }
298
+
299
+ .stat-card-success {
300
+ border-top: 4px solid var(--success-color);
301
+ }
302
+
303
+ .stat-value {
304
+ font-size: 2.5rem;
305
+ font-weight: 700;
306
+ color: var(--text-color);
307
+ margin-bottom: 0.5rem;
308
+ }
309
+
310
+ .stat-label {
311
+ font-size: 1rem;
312
+ color: var(--text-light-color);
313
+ }
314
+
315
+ /* تنسيق النماذج */
316
+ .form-group {
317
+ margin-bottom: 1.5rem;
318
+ }
319
+
320
+ .form-label {
321
+ display: block;
322
+ margin-bottom: 0.5rem;
323
+ font-weight: 600;
324
+ }
325
+
326
+ /* تنسيق الشاشة الرئيسية */
327
+ .welcome-section {
328
+ text-align: center;
329
+ padding: 2rem 0;
330
+ }
331
+
332
+ .welcome-title {
333
+ font-size: 2.5rem;
334
+ color: var(--primary-color);
335
+ margin-bottom: 1rem;
336
+ }
337
+
338
+ .welcome-subtitle {
339
+ font-size: 1.2rem;
340
+ color: var(--text-light-color);
341
+ margin-bottom: 2rem;
342
+ }
343
+
344
+ /* تنسيق القائمة الرئيسية */
345
+ .main-menu {
346
+ display: flex;
347
+ flex-wrap: wrap;
348
+ justify-content: center;
349
+ gap: 1.5rem;
350
+ margin-bottom: 2rem;
351
+ }
352
+
353
+ .menu-item {
354
+ background-color: var(--card-color);
355
+ border-radius: 8px;
356
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
357
+ padding: 1.5rem;
358
+ text-align: center;
359
+ width: 200px;
360
+ transition: all 0.3s ease;
361
+ }
362
+
363
+ .menu-item:hover {
364
+ transform: translateY(-5px);
365
+ box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
366
+ }
367
+
368
+ .menu-icon {
369
+ font-size: 2.5rem;
370
+ color: var(--primary-color);
371
+ margin-bottom: 1rem;
372
+ }
373
+
374
+ .menu-title {
375
+ font-size: 1.2rem;
376
+ font-weight: 600;
377
+ color: var(--text-color);
378
+ }
379
+
380
+ /* تنسيق الشاشة الترحيبية */
381
+ .features-section {
382
+ margin-top: 3rem;
383
+ }
384
+
385
+ .feature-card {
386
+ background-color: var(--card-color);
387
+ border-radius: 8px;
388
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
389
+ padding: 1.5rem;
390
+ margin-bottom: 1.5rem;
391
+ display: flex;
392
+ align-items: flex-start;
393
+ }
394
+
395
+ .feature-icon {
396
+ font-size: 2rem;
397
+ color: var(--primary-color);
398
+ margin-left: 1.5rem;
399
+ }
400
+
401
+ .feature-content {
402
+ flex: 1;
403
+ }
404
+
405
+ .feature-title {
406
+ font-size: 1.3rem;
407
+ font-weight: 600;
408
+ color: var(--primary-color);
409
+ margin-bottom: 0.5rem;
410
+ }
411
+
412
+ .feature-description {
413
+ color: var(--text-light-color);
414
+ }
415
+
416
+ /* تنسيق الشاشة الترحيبية */
417
+ .footer {
418
+ text-align: center;
419
+ padding: 2rem 0;
420
+ margin-top: 3rem;
421
+ border-top: 1px solid var(--border-color);
422
+ color: var(--text-light-color);
423
+ }
pricing_system/tests/test_integrated_system.py ADDED
@@ -0,0 +1,531 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ import sys
3
+ import os
4
+ import pandas as pd
5
+ import numpy as np
6
+ import streamlit as st
7
+ from unittest.mock import patch, MagicMock
8
+
9
+ # إضافة مسار الوحدات
10
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11
+
12
+ # استيراد الوحدات للاختبار
13
+ from pricing_system.integration_framework import IntegrationFramework
14
+ from pricing_system.modules.catalogs.equipment_catalog import EquipmentCatalog
15
+ from pricing_system.modules.catalogs.materials_catalog import MaterialsCatalog
16
+ from pricing_system.modules.catalogs.labor_catalog import LaborCatalog
17
+ from pricing_system.modules.catalogs.subcontractors_catalog import SubcontractorsCatalog
18
+ from pricing_system.modules.analysis.smart_price_analysis import SmartPriceAnalysis
19
+ from pricing_system.modules.indirect_support.indirect_support_management import IndirectSupportManagement
20
+ from pricing_system.modules.pricing_strategies.pricing_strategies import PricingStrategies
21
+
22
+ class TestIntegratedSystem(unittest.TestCase):
23
+ """
24
+ اختبارات النظام المتكامل للتأكد من عمل جميع المكونات معًا بشكل صحيح
25
+ """
26
+
27
+ def setUp(self):
28
+ """
29
+ إعداد بيئة الاختبار
30
+ """
31
+ # تهيئة حالة الجلسة الوهمية
32
+ st.session_state = {}
33
+
34
+ # إنشاء مثيلات من الوحدات للاختبار
35
+ self.equipment_catalog = EquipmentCatalog()
36
+ self.materials_catalog = MaterialsCatalog()
37
+ self.labor_catalog = LaborCatalog()
38
+ self.subcontractors_catalog = SubcontractorsCatalog()
39
+ self.smart_price_analysis = SmartPriceAnalysis()
40
+ self.indirect_support = IndirectSupportManagement()
41
+ self.pricing_strategies = PricingStrategies()
42
+
43
+ # إنشاء مثيل من إطار التكامل
44
+ self.integration_framework = IntegrationFramework()
45
+
46
+ # إعداد بيانات اختبار المشروع
47
+ self.project_data = {
48
+ 'name': 'مشروع اختبار',
49
+ 'location': 'الرياض',
50
+ 'client': 'وزارة الإسكان',
51
+ 'start_date': '2025-01-01',
52
+ 'end_date': '2025-12-31',
53
+ 'budget': 10000000.0,
54
+ 'boq_items': [
55
+ {
56
+ 'item_code': 'B001',
57
+ 'description': 'حفر وردم',
58
+ 'unit': 'م3',
59
+ 'quantity': 1000.0,
60
+ 'unit_price': 50.0,
61
+ 'total_price': 50000.0
62
+ },
63
+ {
64
+ 'item_code': 'B002',
65
+ 'description': 'خرسانة مسلحة',
66
+ 'unit': 'م3',
67
+ 'quantity': 500.0,
68
+ 'unit_price': 1200.0,
69
+ 'total_price': 600000.0
70
+ },
71
+ {
72
+ 'item_code': 'B003',
73
+ 'description': 'أعمال تشطيبات',
74
+ 'unit': 'م2',
75
+ 'quantity': 2000.0,
76
+ 'unit_price': 300.0,
77
+ 'total_price': 600000.0
78
+ }
79
+ ],
80
+ 'resources': [],
81
+ 'pricing_strategy': 'standard',
82
+ 'indirect_costs': {},
83
+ 'profit_margin': 0.15,
84
+ 'local_content_target': 0.40
85
+ }
86
+
87
+ # إضافة بيانات اختبار للكتالوجات
88
+ self._add_test_data_to_catalogs()
89
+
90
+ def _add_test_data_to_catalogs(self):
91
+ """
92
+ إضافة بيانات اختبار إلى الكتالوجات
93
+ """
94
+ # إضافة معدات للاختبار
95
+ equipment_data = [
96
+ {
97
+ 'name': 'حفار',
98
+ 'type': 'حفار',
99
+ 'description': 'حفار هيدروليكي',
100
+ 'price_per_hour': 200.0,
101
+ 'price_per_day': 1600.0,
102
+ 'price_per_week': 8000.0,
103
+ 'price_per_month': 30000.0,
104
+ 'is_local': True
105
+ },
106
+ {
107
+ 'name': 'لودر',
108
+ 'type': 'لودر',
109
+ 'description': 'لودر متوسط الحجم',
110
+ 'price_per_hour': 150.0,
111
+ 'price_per_day': 1200.0,
112
+ 'price_per_week': 6000.0,
113
+ 'price_per_month': 22000.0,
114
+ 'is_local': False
115
+ },
116
+ {
117
+ 'name': 'شاحنة نقل',
118
+ 'type': 'شاحنة',
119
+ 'description': 'شاحنة نقل ثقيلة',
120
+ 'price_per_hour': 120.0,
121
+ 'price_per_day': 960.0,
122
+ 'price_per_week': 4800.0,
123
+ 'price_per_month': 18000.0,
124
+ 'is_local': True
125
+ }
126
+ ]
127
+
128
+ for equipment in equipment_data:
129
+ self.equipment_catalog.add_equipment(equipment['name'], equipment)
130
+
131
+ # إضافة مواد للاختبار
132
+ materials_data = [
133
+ {
134
+ 'name': 'اسمنت',
135
+ 'description': 'اسمنت بورتلاندي',
136
+ 'unit': 'طن',
137
+ 'price': 600.0,
138
+ 'is_local': True
139
+ },
140
+ {
141
+ 'name': 'حديد تسليح',
142
+ 'description': 'حديد تسليح قطر 16 مم',
143
+ 'unit': 'طن',
144
+ 'price': 3500.0,
145
+ 'is_local': True
146
+ },
147
+ {
148
+ 'name': 'رمل',
149
+ 'description': 'رمل ناعم للخرسانة',
150
+ 'unit': 'م3',
151
+ 'price': 80.0,
152
+ 'is_local': True
153
+ },
154
+ {
155
+ 'name': 'بلاط سيراميك',
156
+ 'description': 'بلاط سيراميك للأرضيات',
157
+ 'unit': 'م2',
158
+ 'price': 120.0,
159
+ 'is_local': False
160
+ }
161
+ ]
162
+
163
+ for material in materials_data:
164
+ self.materials_catalog.add_material(material['name'], material)
165
+
166
+ # إضافة عمالة للاختبار
167
+ labor_data = [
168
+ {
169
+ 'name': 'مهندس مدني',
170
+ 'type': 'مهندس مدني',
171
+ 'description': 'مهندس مدني خبرة 5 سنوات',
172
+ 'price_per_hour': 100.0,
173
+ 'price_per_day': 800.0,
174
+ 'price_per_week': 4000.0,
175
+ 'price_per_month': 15000.0,
176
+ 'is_local': True
177
+ },
178
+ {
179
+ 'name': 'عامل بناء',
180
+ 'type': 'عامل بناء',
181
+ 'description': 'عامل بناء ماهر',
182
+ 'price_per_hour': 20.0,
183
+ 'price_per_day': 160.0,
184
+ 'price_per_week': 800.0,
185
+ 'price_per_month': 3000.0,
186
+ 'is_local': False
187
+ },
188
+ {
189
+ 'name': 'فني كهرباء',
190
+ 'type': 'كهربائي',
191
+ 'description': 'فني كهرباء خبرة 3 سنوات',
192
+ 'price_per_hour': 30.0,
193
+ 'price_per_day': 240.0,
194
+ 'price_per_week': 1200.0,
195
+ 'price_per_month': 4500.0,
196
+ 'is_local': True
197
+ }
198
+ ]
199
+
200
+ for labor in labor_data:
201
+ self.labor_catalog.add_labor(labor['name'], labor)
202
+
203
+ # إضافة مقاولي باطن للاختبار
204
+ subcontractors_data = [
205
+ {
206
+ 'name': 'شركة الأعمال الكهربائية',
207
+ 'specialization': 'أعمال كهربائية',
208
+ 'description': 'شركة متخصصة في الأعمال الكهربائية',
209
+ 'contact_info': '[email protected]',
210
+ 'is_local': True
211
+ },
212
+ {
213
+ 'name': 'مؤسسة أنظمة التكييف',
214
+ 'specialization': 'أعمال تكييف',
215
+ 'description': 'مؤسسة متخصصة في أنظمة التكييف',
216
+ 'contact_info': '[email protected]',
217
+ 'is_local': True
218
+ },
219
+ {
220
+ 'name': 'شركة أنظمة المراقبة',
221
+ 'specialization': 'أعمال CCTV',
222
+ 'description': 'شركة متخصصة في أنظمة المراقبة والكاميرات',
223
+ 'contact_info': '[email protected]',
224
+ 'is_local': False
225
+ }
226
+ ]
227
+
228
+ for subcontractor in subcontractors_data:
229
+ self.subcontractors_catalog.add_subcontractor(subcontractor['name'], subcontractor)
230
+
231
+ # إضافة إدارات مساندة للاختبار
232
+ departments_data = [
233
+ {
234
+ 'name': 'إدارة المشاريع',
235
+ 'description': 'إدارة متابعة وتنفيذ المشاريع',
236
+ 'monthly_cost': 100000.0,
237
+ 'employees_count': 10,
238
+ 'allocation_percentage': 20.0
239
+ },
240
+ {
241
+ 'name': 'إدارة المشتريات',
242
+ 'description': 'إدارة المشتريات والتوريدات',
243
+ 'monthly_cost': 80000.0,
244
+ 'employees_count': 8,
245
+ 'allocation_percentage': 15.0
246
+ },
247
+ {
248
+ 'name': 'الإدارة المالية',
249
+ 'description': 'الإدارة المالية والمحاسبة',
250
+ 'monthly_cost': 70000.0,
251
+ 'employees_count': 7,
252
+ 'allocation_percentage': 10.0
253
+ }
254
+ ]
255
+
256
+ for department in departments_data:
257
+ self.indirect_support.add_department(department['name'], department)
258
+
259
+ def test_equipment_catalog_integration(self):
260
+ """
261
+ اختبار تكامل كتالوج المعدات
262
+ """
263
+ # التحقق من وجود المعدات في الكتالوج
264
+ equipment_list = self.equipment_catalog.get_equipment_list()
265
+ self.assertEqual(len(equipment_list), 3)
266
+ self.assertIn('حفار', equipment_list)
267
+ self.assertIn('لودر', equipment_list)
268
+ self.assertIn('شاحنة نقل', equipment_list)
269
+
270
+ # التحقق من تفاصيل المعدات
271
+ حفار_details = self.equipment_catalog.get_equipment_details('حفار')
272
+ self.assertEqual(حفار_details['price_per_day'], 1600.0)
273
+ self.assertTrue(حفار_details['is_local'])
274
+
275
+ لودر_details = self.equipment_catalog.get_equipment_details('لودر')
276
+ self.assertEqual(لودر_details['price_per_day'], 1200.0)
277
+ self.assertFalse(لودر_details['is_local'])
278
+
279
+ def test_materials_catalog_integration(self):
280
+ """
281
+ اختبار تكامل كتالوج المواد
282
+ """
283
+ # التحقق من وجود المواد في الكتالوج
284
+ materials_list = self.materials_catalog.get_materials_list()
285
+ self.assertEqual(len(materials_list), 4)
286
+ self.assertIn('اسمنت', materials_list)
287
+ self.assertIn('حديد تسليح', materials_list)
288
+ self.assertIn('رمل', materials_list)
289
+ self.assertIn('بلاط سيراميك', materials_list)
290
+
291
+ # التحقق من تفاصيل المواد
292
+ اسمنت_details = self.materials_catalog.get_material_details('اسمنت')
293
+ self.assertEqual(اسمنت_details['price'], 600.0)
294
+ self.assertEqual(اسمنت_details['unit'], 'طن')
295
+ self.assertTrue(اسمنت_details['is_local'])
296
+
297
+ بلاط_details = self.materials_catalog.get_material_details('بلاط سيراميك')
298
+ self.assertEqual(بلاط_details['price'], 120.0)
299
+ self.assertEqual(بلاط_details['unit'], 'م2')
300
+ self.assertFalse(بلاط_details['is_local'])
301
+
302
+ def test_labor_catalog_integration(self):
303
+ """
304
+ اختبار تكامل كتالوج العمالة
305
+ """
306
+ # التحقق من وجود العمالة في الكتالوج
307
+ labor_list = self.labor_catalog.get_labor_list()
308
+ self.assertEqual(len(labor_list), 3)
309
+ self.assertIn('مهندس مدني', labor_list)
310
+ self.assertIn('عامل بناء', labor_list)
311
+ self.assertIn('فني كهرباء', labor_list)
312
+
313
+ # التحقق من تفاصيل العمالة
314
+ مهندس_details = self.labor_catalog.get_labor_details('مهندس مدني')
315
+ self.assertEqual(مهندس_details['price_per_month'], 15000.0)
316
+ self.assertTrue(مهندس_details['is_local'])
317
+
318
+ عامل_details = self.labor_catalog.get_labor_details('عامل بناء')
319
+ self.assertEqual(عامل_details['price_per_day'], 160.0)
320
+ self.assertFalse(عامل_details['is_local'])
321
+
322
+ def test_subcontractors_catalog_integration(self):
323
+ """
324
+ اختبار تكامل كتالوج مقاولي الباطن
325
+ """
326
+ # التحقق من وجود مقاولي الباطن في الكتالوج
327
+ subcontractors_list = self.subcontractors_catalog.get_subcontractors_list()
328
+ self.assertEqual(len(subcontractors_list), 3)
329
+ self.assertIn('شركة الأعمال الكهربائية', subcontractors_list)
330
+ self.assertIn('مؤسسة أنظمة التكييف', subcontractors_list)
331
+ self.assertIn('شركة أنظمة المراقبة', subcontractors_list)
332
+
333
+ # التحقق من تفاصيل مقاولي الباطن
334
+ كهرباء_details = self.subcontractors_catalog.get_subcontractor_details('شركة الأعمال الكهربائية')
335
+ self.assertEqual(كهرباء_details['specialization'], 'أعمال كهربائية')
336
+ self.assertTrue(كهرباء_details['is_local'])
337
+
338
+ مراقبة_details = self.subcontractors_catalog.get_subcontractor_details('شركة أنظمة المراقبة')
339
+ self.assertEqual(مراقبة_details['specialization'], 'أعمال CCTV')
340
+ self.assertFalse(مراقبة_details['is_local'])
341
+
342
+ def test_indirect_support_integration(self):
343
+ """
344
+ اختبار تكامل إدارة الإدارات المساندة
345
+ """
346
+ # التحقق من وجود الإدارات المساندة
347
+ departments = self.indirect_support.get_departments()
348
+ self.assertEqual(len(departments), 3)
349
+ self.assertIn('إدارة المشاريع', departments)
350
+ self.assertIn('إدارة المشتريات', departments)
351
+ self.assertIn('الإدارة المالية', departments)
352
+
353
+ # التحقق من تفاصيل الإدارات المساندة
354
+ مشاريع_details = self.indirect_support.get_department_details('إدارة المشاريع')
355
+ self.assertEqual(مشاريع_details['monthly_cost'], 100000.0)
356
+ self.assertEqual(مشاريع_details['allocation_percentage'], 20.0)
357
+
358
+ # التحقق من حساب التكاليف الإجمالية
359
+ total_monthly_cost = self.indirect_support.get_total_monthly_cost()
360
+ self.assertEqual(total_monthly_cost, 250000.0)
361
+
362
+ total_allocated_cost = self.indirect_support.get_total_allocated_cost()
363
+ self.assertEqual(total_allocated_cost, 45000.0) # (100000*0.2 + 80000*0.15 + 70000*0.1)
364
+
365
+ def test_smart_price_analysis_integration(self):
366
+ """
367
+ اختبار تكامل التحليل الذكي للأسعار
368
+ """
369
+ # إضافة تحليل لبنود المشروع
370
+ for i, item in enumerate(self.project_data['boq_items']):
371
+ analysis = {
372
+ 'direct_cost': item['total_price'] * 0.7, # 70% من السعر الإجمالي
373
+ 'indirect_cost': item['total_price'] * 0.15, # 15% من السعر الإجمالي
374
+ 'profit_margin': item['total_price'] * 0.15, # 15% من السعر الإجمالي
375
+ 'total_price': item['total_price'],
376
+ 'materials': [
377
+ {
378
+ 'name': 'اسمنت',
379
+ 'quantity': 10,
380
+ 'unit': 'طن',
381
+ 'price': 600.0,
382
+ 'total': 6000.0,
383
+ 'is_local': True
384
+ },
385
+ {
386
+ 'name': 'حديد تسليح',
387
+ 'quantity': 5,
388
+ 'unit': 'طن',
389
+ 'price': 3500.0,
390
+ 'total': 17500.0,
391
+ 'is_local': True
392
+ }
393
+ ],
394
+ 'equipment': [
395
+ {
396
+ 'name': 'حفار',
397
+ 'duration': 5,
398
+ 'duration_unit': 'يوم',
399
+ 'price': 1600.0,
400
+ 'total': 8000.0,
401
+ 'is_local': True
402
+ }
403
+ ],
404
+ 'labor': [
405
+ {
406
+ 'name': 'عامل بناء',
407
+ 'duration': 20,
408
+ 'duration_unit': 'يوم',
409
+ 'price': 160.0,
410
+ 'total': 3200.0,
411
+ 'is_local': False
412
+ }
413
+ ],
414
+ 'materials_cost': 23500.0,
415
+ 'equipment_cost': 8000.0,
416
+ 'labor_cost': 3200.0,
417
+ 'subcontractors_cost': 0.0
418
+ }
419
+
420
+ self.smart_price_analysis.add_item_analysis(i, analysis)
421
+
422
+ # التحقق من تحليل البنود
423
+ all_analyses = self.smart_price_analysis.get_all_items_analysis()
424
+ self.assertEqual(len(all_analyses), 3)
425
+
426
+ # التحقق من تحليل التكاليف الإجمالية للمشروع
427
+ cost_analysis = self.smart_price_analysis.get_project_cost_analysis()
428
+ self.assertEqual(cost_analysis['total_materials_cost'], 23500.0 * 3)
429
+ self.assertEqual(cost_analysis['total_equipment_cost'], 8000.0 * 3)
430
+ self.assertEqual(cost_analysis['total_labor_cost'], 3200.0 * 3)
431
+
432
+ # التحقق من المواد الأكثر تكلفة
433
+ top_materials = self.smart_price_analysis.get_top_materials(limit=2)
434
+ self.assertEqual(len(top_materials), 2)
435
+ self.assertEqual(top_materials[0]['name'], 'حديد تسليح')
436
+ self.assertEqual(top_materials[0]['total_cost'], 17500.0 * 3)
437
+
438
+ def test_pricing_strategies_integration(self):
439
+ """
440
+ اختبار تكامل استراتيجيات التسعير
441
+ """
442
+ # تطبيق استراتيجية التسعير القياسي
443
+ result = self.pricing_strategies.apply_strategy(
444
+ 'standard',
445
+ self.project_data,
446
+ self.smart_price_analysis
447
+ )
448
+
449
+ self.assertTrue(result['success'])
450
+ self.assertEqual(len(result['items_result']), 3)
451
+
452
+ # التحقق من نتائج تطبيق الاستراتيجية
453
+ total_cost = sum(item['cost'] for item in result['items_result'])
454
+ total_price = sum(item['price'] for item in result['items_result'])
455
+ profit_margin = total_price - total_cost
456
+
457
+ self.assertEqual(result['total_cost'], total_cost)
458
+ self.assertEqual(result['total_price'], total_price)
459
+ self.assertEqual(result['profit_margin'], profit_margin)
460
+
461
+ # مقارنة استراتيجيات التسعير
462
+ comparison_result = self.pricing_strategies.compare_strategies(
463
+ self.project_data,
464
+ self.smart_price_analysis
465
+ )
466
+
467
+ self.assertTrue(comparison_result['success'])
468
+ self.assertEqual(len(comparison_result['strategies_result']), 6) # 6 استراتيجيات
469
+
470
+ def test_local_content_analysis_integration(self):
471
+ """
472
+ اختبار تكامل تحليل المحتوى المحلي
473
+ """
474
+ # تحليل المحتوى المحلي
475
+ local_content_analysis = self.pricing_strategies.analyze_local_content(
476
+ self.project_data,
477
+ self.smart_price_analysis
478
+ )
479
+
480
+ self.assertTrue(local_content_analysis['success'])
481
+
482
+ # التحقق من نتائج تحليل المحتوى المحلي
483
+ self.assertIn('local_content_percentage', local_content_analysis)
484
+ self.assertIn('resources_local_content', local_content_analysis)
485
+
486
+ # التحقق من تحليل المحتوى المحلي حسب نوع الموارد
487
+ resources_local_content = local_content_analysis['resources_local_content']
488
+ self.assertIn('materials', resources_local_content)
489
+ self.assertIn('equipment', resources_local_content)
490
+ self.assertIn('labor', resources_local_content)
491
+
492
+ # التحقق من التوصيات
493
+ if local_content_analysis['local_content_percentage'] < self.project_data['local_content_target']:
494
+ self.assertIn('recommendations', local_content_analysis)
495
+ self.assertTrue(len(local_content_analysis['recommendations']) > 0)
496
+
497
+ def test_integration_framework(self):
498
+ """
499
+ اختبار إطار التكامل
500
+ """
501
+ # تهيئة حالة الجلسة
502
+ st.session_state = {}
503
+
504
+ # إنشاء مثيلات وهمية من PricingApp و ResourcesApp
505
+ pricing_app_mock = MagicMock()
506
+ pricing_app_mock.tabs = ["جدول الكميات", "تحليل التكاليف", "سيناريوهات التسعير", "التحليل التنافسي", "التقارير"]
507
+ pricing_app_mock._render_bill_of_quantities_tab = MagicMock()
508
+ pricing_app_mock._render_cost_analysis_tab = MagicMock()
509
+ pricing_app_mock._render_pricing_scenarios_tab = MagicMock()
510
+ pricing_app_mock._render_competitive_analysis_tab = MagicMock()
511
+ pricing_app_mock._render_reports_tab = MagicMock()
512
+
513
+ resources_app_mock = MagicMock()
514
+
515
+ # ربط إطار التكامل مع التطبيقات الوهمية
516
+ self.integration_framework.connect_pricing_app(pricing_app_mock)
517
+ self.integration_framework.connect_resources_app(resources_app_mock)
518
+
519
+ # التحقق من إضافة علامات التبويب الجديدة
520
+ self.assertEqual(len(pricing_app_mock.tabs), 8) # 5 علامات أصلية + 3 جديدة
521
+ self.assertIn("كتالوجات الموارد", pricing_app_mock.tabs)
522
+ self.assertIn("الإدارات المساندة", pricing_app_mock.tabs)
523
+ self.assertIn("المحتوى المحلي", pricing_app_mock.tabs)
524
+
525
+ # التحقق من إضافة الدوال الجديدة
526
+ self.assertTrue(hasattr(pricing_app_mock, '_render_resource_catalogs_tab'))
527
+ self.assertTrue(hasattr(pricing_app_mock, '_render_indirect_support_tab'))
528
+ self.assertTrue(hasattr(pricing_app_mock, '_render_local_content_tab'))
529
+
530
+ if __name__ == '__main__':
531
+ unittest.main()