EGYADMIN commited on
Commit
b58abff
·
verified ·
1 Parent(s): be8fe8e

Upload 4 files

Browse files
modules/pricing/data_storage.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from datetime import datetime
4
+
5
+ class DataStorage:
6
+ """فئة لإدارة تخزين واسترجاع البيانات"""
7
+
8
+ def __init__(self, data_dir="./data"):
9
+ """تهيئة مدير التخزين"""
10
+ self.data_dir = data_dir
11
+
12
+ # إنشاء مجلد البيانات إذا لم يكن موجوداً
13
+ if not os.path.exists(data_dir):
14
+ os.makedirs(data_dir)
15
+
16
+ # مسارات الملفات
17
+ self.projects_file = os.path.join(data_dir, "projects.json")
18
+ self.boq_items_file = os.path.join(data_dir, "boq_items.json")
19
+ self.pricing_history_file = os.path.join(data_dir, "pricing_history.json")
20
+ self.risks_file = os.path.join(data_dir, "risks.json")
21
+
22
+ # تهيئة الملفات إذا لم تكن موجودة
23
+ self._initialize_files()
24
+
25
+ def _initialize_files(self):
26
+ """تهيئة ملفات البيانات إذا لم تكن موجودة"""
27
+ files = [
28
+ self.projects_file,
29
+ self.boq_items_file,
30
+ self.pricing_history_file,
31
+ self.risks_file
32
+ ]
33
+
34
+ for file in files:
35
+ if not os.path.exists(file):
36
+ with open(file, 'w', encoding='utf-8') as f:
37
+ json.dump([], f, ensure_ascii=False, indent=4)
38
+
39
+ # وظائف إدارة المشاريع
40
+ def load_projects(self):
41
+ """تحميل بيانات المشاريع"""
42
+ with open(self.projects_file, 'r', encoding='utf-8') as f:
43
+ return json.load(f)
44
+
45
+ def save_projects(self, projects):
46
+ """حفظ بيانات المشاريع"""
47
+ with open(self.projects_file, 'w', encoding='utf-8') as f:
48
+ json.dump(projects, f, ensure_ascii=False, indent=4)
49
+
50
+ def add_project(self, project):
51
+ """إضافة مشروع جديد"""
52
+ projects = self.load_projects()
53
+
54
+ # إضافة طابع زمني للإنشاء والتحديث
55
+ project['created_at'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
56
+ project['updated_at'] = project['created_at']
57
+
58
+ projects.append(project)
59
+ self.save_projects(projects)
60
+ return project
61
+
62
+ def update_project(self, project_id, updated_data):
63
+ """تحديث بيانات مشروع"""
64
+ projects = self.load_projects()
65
+
66
+ for i, project in enumerate(projects):
67
+ if project['id'] == project_id:
68
+ # تحديث البيانات مع الحفاظ على البيانات الأصلية
69
+ projects[i].update(updated_data)
70
+ # تحديث طابع زمني التحديث
71
+ projects[i]['updated_at'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
72
+ self.save_projects(projects)
73
+ return projects[i]
74
+
75
+ return None # المشروع غير موجود
76
+
77
+ def delete_project(self, project_id):
78
+ """حذف مشروع"""
79
+ projects = self.load_projects()
80
+ items = self.load_boq_items()
81
+ risks = self.load_risks()
82
+
83
+ # حذف المشروع
84
+ projects = [p for p in projects if p['id'] != project_id]
85
+ self.save_projects(projects)
86
+
87
+ # حذف بنود المشروع
88
+ items = [item for item in items if item['project_id'] != project_id]
89
+ self.save_boq_items(items)
90
+
91
+ # حذف مخاطر المشروع
92
+ risks = [risk for risk in risks if risk['project_id'] != project_id]
93
+ self.save_risks(risks)
94
+
95
+ return True
96
+
97
+ # وظائف إدارة بنود جداول الكميات
98
+ def load_boq_items(self):
99
+ """تحميل بنود جداول الكميات"""
100
+ with open(self.boq_items_file, 'r', encoding='utf-8') as f:
101
+ return json.load(f)
102
+
103
+ def save_boq_items(self, items):
104
+ """حفظ بنود جداول الكميات"""
105
+ with open(self.boq_items_file, 'w', encoding='utf-8') as f:
106
+ json.dump(items, f, ensure_ascii=False, indent=4)
107
+
108
+ def add_boq_item(self, item):
109
+ """إضافة بند جديد"""
110
+ items = self.load_boq_items()
111
+
112
+ # إضافة طابع زمني للإنشاء والتحديث
113
+ item['created_at'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
114
+ item['updated_at'] = item['created_at']
115
+
116
+ items.append(item)
117
+ self.save_boq_items(items)
118
+ return item
119
+
120
+ def update_boq_item(self, item_id, updated_data):
121
+ """تحديث بيانات بند"""
122
+ items = self.load_boq_items()
123
+
124
+ for i, item in enumerate(items):
125
+ if item['id'] == item_id:
126
+ # تحديث البيانات مع الحفاظ على البيانات الأصلية
127
+ items[i].update(updated_data)
128
+ # تحديث طابع زمني التحديث
129
+ items[i]['updated_at'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
130
+ self.save_boq_items(items)
131
+ return items[i]
132
+
133
+ return None # البند غير موجود
134
+
135
+ def delete_boq_item(self, item_id):
136
+ """حذف بند"""
137
+ items = self.load_boq_items()
138
+
139
+ # حذف البند
140
+ items = [item for item in items if item['id'] != item_id]
141
+ self.save_boq_items(items)
142
+
143
+ return True
144
+
145
+ def get_project_items(self, project_id):
146
+ """الحصول على بنود مشروع معين"""
147
+ items = self.load_boq_items()
148
+ return [item for item in items if item['project_id'] == project_id]
149
+
150
+ # وظائف إدارة سجل التسعير
151
+ def load_pricing_history(self):
152
+ """تحميل سجل التسعير"""
153
+ with open(self.pricing_history_file, 'r', encoding='utf-8') as f:
154
+ return json.load(f)
155
+
156
+ def save_pricing_history(self, history):
157
+ """حفظ سجل التسعير"""
158
+ with open(self.pricing_history_file, 'w', encoding='utf-8') as f:
159
+ json.dump(history, f, ensure_ascii=False, indent=4)
160
+
161
+ def save_pricing(self, pricing_data):
162
+ """حفظ تسعير جديد"""
163
+ history = self.load_pricing_history()
164
+
165
+ # إضافة طابع زمني
166
+ pricing_data['saved_at'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
167
+
168
+ history.append(pricing_data)
169
+ self.save_pricing_history(history)
170
+ return pricing_data
171
+
172
+ def get_project_pricing_history(self, project_id):
173
+ """الحصول على سجل تسعير مشروع معين"""
174
+ history = self.load_pricing_history()
175
+ return [entry for entry in history if entry['project_id'] == project_id]
176
+
177
+ # وظائف إدارة المخاطر
178
+ def load_risks(self):
179
+ """تحميل بيانات المخاطر"""
180
+ with open(self.risks_file, 'r', encoding='utf-8') as f:
181
+ return json.load(f)
182
+
183
+ def save_risks(self, risks):
184
+ """حفظ بيانات المخاطر"""
185
+ with open(self.risks_file, 'w', encoding='utf-8') as f:
186
+ json.dump(risks, f, ensure_ascii=False, indent=4)
187
+
188
+ def add_risk(self, risk):
189
+ """إضافة مخاطرة جديدة"""
190
+ risks = self.load_risks()
191
+
192
+ # إضافة طابع زمني للإنشاء والتحديث
193
+ risk['created_at'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
194
+ risk['updated_at'] = risk['created_at']
195
+
196
+ risks.append(risk)
197
+ self.save_risks(risks)
198
+ return risk
199
+
200
+ def update_risk(self, risk_id, updated_data):
201
+ """تحديث بيانات مخاطرة"""
202
+ risks = self.load_risks()
203
+
204
+ for i, risk in enumerate(risks):
205
+ if risk['id'] == risk_id:
206
+ # تحديث البيانات مع الحفاظ على البيانات الأصلية
207
+ risks[i].update(updated_data)
208
+ # تحديث طابع زمني التحديث
209
+ risks[i]['updated_at'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
210
+ self.save_risks(risks)
211
+ return risks[i]
212
+
213
+ return None # المخاطرة غير موجودة
214
+
215
+ def delete_risk(self, risk_id):
216
+ """حذف مخاطرة"""
217
+ risks = self.load_risks()
218
+
219
+ # حذف المخاطرة
220
+ risks = [r for r in risks if r['id'] != risk_id]
221
+ self.save_risks(risks)
222
+
223
+ return True
224
+
225
+ def get_project_risks(self, project_id):
226
+ """الحصول على مخاطر مشروع معين"""
227
+ risks = self.load_risks()
228
+ return [risk for risk in risks if risk['project_id'] == project_id]
229
+
230
+ # وظائف النسخ الاحتياطي
231
+ def backup_data(self, backup_dir="./backup"):
232
+ """إنشاء نسخة احتياطية من البيانات"""
233
+ if not os.path.exists(backup_dir):
234
+ os.makedirs(backup_dir)
235
+
236
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
237
+
238
+ # نسخ ملفات البيانات
239
+ files = {
240
+ "projects.json": self.projects_file,
241
+ "boq_items.json": self.boq_items_file,
242
+ "pricing_history.json": self.pricing_history_file,
243
+ "risks.json": self.risks_file
244
+ }
245
+
246
+ for filename, src_file in files.items():
247
+ if os.path.exists(src_file):
248
+ backup_file = os.path.join(backup_dir, f"{filename.split('.')[0]}_{timestamp}.json")
249
+ with open(src_file, 'r', encoding='utf-8') as src:
250
+ with open(backup_file, 'w', encoding='utf-8') as dst:
251
+ dst.write(src.read())
252
+
253
+ return timestamp
modules/pricing/export_utils.py ADDED
@@ -0,0 +1,547 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import os
3
+ import pandas as pd
4
+ from datetime import datetime
5
+
6
+ def export_to_excel(project_items, project_info, filename):
7
+ """تصدير بيانات المشروع إلى ملف Excel"""
8
+ import openpyxl
9
+ from openpyxl.styles import Font, Alignment, Border, Side
10
+
11
+ # إنشاء مصنف Excel جديد
12
+ wb = openpyxl.Workbook()
13
+
14
+ # إنشاء ورقة معلومات المشروع
15
+ project_sheet = wb.active
16
+ project_sheet.title = "معلومات المشروع"
17
+
18
+ # إضافة معلومات المشروع
19
+ project_sheet['A1'] = "معلومات المشروع"
20
+ project_sheet['A1'].font = Font(bold=True, size=14)
21
+
22
+ headers = ["البند", "القيمة"]
23
+ project_sheet.append(headers)
24
+
25
+ project_data = [
26
+ ["اسم المشروع", project_info['name']],
27
+ ["العميل", project_info['client']],
28
+ ["الموقع", project_info.get('location', '-')],
29
+ ["رقم المناقصة", project_info.get('tender_number', '-')],
30
+ ["القيمة التقديرية", f"{project_info['estimated_value']:,.2f} ريال"],
31
+ ["الموعد النهائي", project_info['deadline']],
32
+ ["مدة العقد", project_info.get('contract_duration', '-')],
33
+ ["نوع التسعير", project_info['pricing_type']]
34
+ ]
35
+
36
+ for row in project_data:
37
+ project_sheet.append(row)
38
+
39
+ # تنسيق الجدول
40
+ for col in range(1, 3):
41
+ for row in range(2, len(project_data) + 3):
42
+ project_sheet.cell(row=row, column=col).border = Border(
43
+ left=Side(style='thin'),
44
+ right=Side(style='thin'),
45
+ top=Side(style='thin'),
46
+ bottom=Side(style='thin')
47
+ )
48
+
49
+ # إنشاء ورقة جدول الكميات
50
+ boq_sheet = wb.create_sheet(title="جدول الكميات")
51
+
52
+ # إضافة عناوين الأعمدة
53
+ headers = ["الكود", "الوصف", "الوحدة", "الكمية", "سعر الوحدة (ريال)", "السعر الإجمالي (ريال)", "نوع المورد"]
54
+ boq_sheet.append(headers)
55
+
56
+ # إضافة بيانات البنود
57
+ for item in project_items:
58
+ row_data = [
59
+ item['code'],
60
+ item['description'],
61
+ item['unit'],
62
+ item['quantity'],
63
+ item['unit_price'],
64
+ item['total_price'],
65
+ item.get('resource_type', '-')
66
+ ]
67
+ boq_sheet.append(row_data)
68
+
69
+ # تنسيق الجدول
70
+ for col in range(1, 8):
71
+ for row in range(1, len(project_items) + 2):
72
+ boq_sheet.cell(row=row, column=col).border = Border(
73
+ left=Side(style='thin'),
74
+ right=Side(style='thin'),
75
+ top=Side(style='thin'),
76
+ bottom=Side(style='thin')
77
+ )
78
+
79
+ # حفظ الملف
80
+ wb.save(filename)
81
+ return filename
82
+
83
+ def export_to_pdf(project_items, project_info, filename):
84
+ """تصدير بيانات المشروع إلى ملف PDF"""
85
+ from reportlab.lib.pagesizes import A4
86
+ from reportlab.lib import colors
87
+ from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
88
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
89
+ from reportlab.pdfbase import pdfmetrics
90
+ from reportlab.pdfbase.ttfonts import TTFont
91
+
92
+ try:
93
+ import arabic_reshaper
94
+ from bidi.algorithm import get_display
95
+ ARABIC_SUPPORT = True
96
+ except ImportError:
97
+ ARABIC_SUPPORT = False
98
+
99
+ # تسجيل الخط العربي
100
+ font_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fonts", "arabic_font.ttf")
101
+ if os.path.exists(font_path):
102
+ try:
103
+ pdfmetrics.registerFont(TTFont('Arabic', font_path))
104
+ except:
105
+ # استخدام خط افتراضي إذا فشل تسجيل الخط العربي
106
+ pass
107
+
108
+ # إنشاء أنماط النص
109
+ styles = getSampleStyleSheet()
110
+ styles.add(ParagraphStyle(name='Arabic', fontName='Helvetica', alignment=1)) # محاذاة للوسط
111
+
112
+ # إنشاء مستند PDF
113
+ doc = SimpleDocTemplate(filename, pagesize=A4, rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=30)
114
+ elements = []
115
+
116
+ # إضافة عنوان المستند
117
+ title_text = f"تقرير المشروع: {project_info['name']}"
118
+ if ARABIC_SUPPORT:
119
+ title_text = get_display(arabic_reshaper.reshape(title_text))
120
+ title = Paragraph(title_text, styles['Arabic'])
121
+ elements.append(title)
122
+ elements.append(Spacer(1, 20))
123
+
124
+ # إضافة معلومات المشروع
125
+ project_data = [
126
+ ["البند", "القيمة"],
127
+ ["اسم المشروع", project_info['name']],
128
+ ["العميل", project_info['client']],
129
+ ["الموقع", project_info.get('location', '-')],
130
+ ["رقم المناقصة", project_info.get('tender_number', '-')],
131
+ ["القيمة التقديرية", f"{project_info['estimated_value']:,.2f} ريال"],
132
+ ["الموعد النهائي", project_info['deadline']],
133
+ ["مدة العقد", project_info.get('contract_duration', '-')],
134
+ ["نوع التسعير", project_info['pricing_type']]
135
+ ]
136
+
137
+ # تحويل النص العربي
138
+ if ARABIC_SUPPORT:
139
+ for i in range(len(project_data)):
140
+ for j in range(len(project_data[i])):
141
+ if isinstance(project_data[i][j], str):
142
+ project_data[i][j] = get_display(arabic_reshaper.reshape(project_data[i][j]))
143
+
144
+ # إنشاء جدول معلومات المشروع
145
+ project_table = Table(project_data)
146
+ project_table.setStyle(TableStyle([
147
+ ('BACKGROUND', (0, 0), (1, 0), colors.grey),
148
+ ('TEXTCOLOR', (0, 0), (1, 0), colors.whitesmoke),
149
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
150
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
151
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
152
+ ('GRID', (0, 0), (-1, -1), 1, colors.black)
153
+ ]))
154
+
155
+ elements.append(project_table)
156
+ elements.append(Spacer(1, 30))
157
+
158
+ # إضافة عنوان جدول الكميات
159
+ boq_title_text = "جدول الكميات"
160
+ if ARABIC_SUPPORT:
161
+ boq_title_text = get_display(arabic_reshaper.reshape(boq_title_text))
162
+ boq_title = Paragraph(boq_title_text, styles['Arabic'])
163
+ elements.append(boq_title)
164
+ elements.append(Spacer(1, 10))
165
+
166
+ # إعداد بيانات جدول الكميات
167
+ boq_data = [["الكود", "الوصف", "الوحدة", "الكمية", "سعر الوحدة", "السعر الإجمالي"]]
168
+
169
+ for item in project_items:
170
+ row = [
171
+ item['code'],
172
+ item['description'],
173
+ item['unit'],
174
+ str(item['quantity']),
175
+ f"{item['unit_price']:,.2f}",
176
+ f"{item['total_price']:,.2f}"
177
+ ]
178
+ # تحويل النص العربي
179
+ if ARABIC_SUPPORT:
180
+ for i in range(len(row)):
181
+ if isinstance(row[i], str):
182
+ row[i] = get_display(arabic_reshaper.reshape(row[i]))
183
+ boq_data.append(row)
184
+
185
+ # إضافة الإجمالي
186
+ total_cost = sum(item['total_price'] for item in project_items)
187
+ total_row = ["", "", "", "", "الإجمالي", f"{total_cost:,.2f}"]
188
+ if ARABIC_SUPPORT:
189
+ total_row[4] = get_display(arabic_reshaper.reshape(total_row[4]))
190
+ total_row[5] = get_display(arabic_reshaper.reshape(total_row[5]))
191
+ boq_data.append(total_row)
192
+
193
+ # إنشاء جدول الكميات
194
+ boq_table = Table(boq_data)
195
+ boq_table.setStyle(TableStyle([
196
+ ('BACKGROUND', (0, 0), (-1, 0), colors.grey),
197
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
198
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
199
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
200
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
201
+ ('GRID', (0, 0), (-1, -1), 1, colors.black),
202
+ ('BACKGROUND', (0, -1), (-1, -1), colors.lightgrey)
203
+ ]))
204
+
205
+ elements.append(boq_table)
206
+
207
+ # بناء المستند
208
+ doc.build(elements)
209
+ return filename
210
+
211
+ def export_local_content_report(project_items, project_info, filename):
212
+ """تصدير تقرير المحتوى المحلي إلى ملف Excel"""
213
+ import openpyxl
214
+ from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
215
+ from openpyxl.utils import get_column_letter
216
+
217
+ # إنشاء مصنف Excel جديد
218
+ wb = openpyxl.Workbook()
219
+
220
+ # إنشاء ورقة ملخص المحتوى المحلي
221
+ summary_sheet = wb.active
222
+ summary_sheet.title = "ملخص المحتوى المحلي"
223
+
224
+ # إضافة عنوان التقرير
225
+ summary_sheet['A1'] = f"تقرير المحتوى المحلي - {project_info['name']}"
226
+ summary_sheet['A1'].font = Font(bold=True, size=14)
227
+ summary_sheet.merge_cells('A1:G1')
228
+ summary_sheet['A1'].alignment = Alignment(horizontal='center')
229
+
230
+ # إضافة معلومات المشروع
231
+ summary_sheet['A3'] = "معلومات المشروع"
232
+ summary_sheet['A3'].font = Font(bold=True)
233
+
234
+ project_data = [
235
+ ["اسم المشروع", project_info['name']],
236
+ ["العميل", project_info['client']],
237
+ ["رقم المناقصة", project_info.get('tender_number', '-')],
238
+ ["النسبة المستهدفة للمحتوى المحلي", f"{project_info.get('local_content_target', 40)}%"]
239
+ ]
240
+
241
+ for i, row in enumerate(project_data):
242
+ summary_sheet[f'A{i+4}'] = row[0]
243
+ summary_sheet[f'B{i+4}'] = row[1]
244
+
245
+ # إضافة ملخص المحتوى المحلي
246
+ summary_sheet['A9'] = "ملخص المحتوى المحلي"
247
+ summary_sheet['A9'].font = Font(bold=True)
248
+
249
+ # الحصول على ملخص المحتوى المحلي
250
+ summary = project_info.get('local_content_summary', {
251
+ 'total_percentage': 0,
252
+ 'by_category': {
253
+ 'materials': 0,
254
+ 'labor': 0,
255
+ 'services': 0,
256
+ 'equipment': 0
257
+ }
258
+ })
259
+
260
+ summary_data = [
261
+ ["النسبة الإجمالية للمحتوى المحلي", f"{summary.get('total_percentage', 0):.1f}%"],
262
+ ["نسبة المواد المحلية", f"{summary.get('by_category', {}).get('materials', 0):.1f}%"],
263
+ ["نسبة العمالة المحلية", f"{summary.get('by_category', {}).get('labor', 0):.1f}%"],
264
+ ["نسبة الخدمات المحلية", f"{summary.get('by_category', {}).get('services', 0):.1f}%"],
265
+ ["نسبة المعدات المحلية", f"{summary.get('by_category', {}).get('equipment', 0):.1f}%"]
266
+ ]
267
+
268
+ for i, row in enumerate(summary_data):
269
+ summary_sheet[f'A{i+10}'] = row[0]
270
+ summary_sheet[f'B{i+10}'] = row[1]
271
+
272
+ # تنسيق الخلايا
273
+ for col in range(1, 3):
274
+ for row in range(4, 15):
275
+ cell = summary_sheet.cell(row=row, column=col)
276
+ cell.border = Border(
277
+ left=Side(style='thin'),
278
+ right=Side(style='thin'),
279
+ top=Side(style='thin'),
280
+ bottom=Side(style='thin')
281
+ )
282
+
283
+ # إضافة تقييم الامتثال
284
+ target_percentage = project_info.get('local_content_target', 40)
285
+ total_percentage = summary.get('total_percentage', 0)
286
+
287
+ summary_sheet['A16'] = "تقييم الامتثال"
288
+ summary_sheet['A16'].font = Font(bold=True)
289
+
290
+ if total_percentage >= target_percentage:
291
+ compliance_text = f"المشروع يحقق متطلبات المحتوى المحلي المستهدفة ({target_percentage}%)."
292
+ compliance_fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid")
293
+ else:
294
+ compliance_text = f"المشروع لا يحقق متطلبات المحتوى المحلي المستهدفة ({target_percentage}%)."
295
+ compliance_fill = PatternFill(start_color="FFCCCC", end_color="FFCCCC", fill_type="solid")
296
+
297
+ summary_sheet['A17'] = compliance_text
298
+ summary_sheet['A17'].fill = compliance_fill
299
+ summary_sheet.merge_cells('A17:G17')
300
+
301
+ # إنشاء ورقة تفاصيل البنود
302
+ details_sheet = wb.create_sheet(title="تفاصيل البنود")
303
+
304
+ # إضافة عناوين الأعمدة
305
+ headers = [
306
+ "الكود", "الوصف", "نوع المورد", "مورد محلي",
307
+ "نسبة المحتوى المحلي (%)", "السعر الإجمالي (ريال)",
308
+ "المساهمة في المحتوى المحلي (ريال)"
309
+ ]
310
+
311
+ for i, header in enumerate(headers, 1):
312
+ details_sheet.cell(row=1, column=i).value = header
313
+ details_sheet.cell(row=1, column=i).font = Font(bold=True)
314
+ details_sheet.cell(row=1, column=i).fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
315
+
316
+ # إضافة بيانات البنود
317
+ for i, item in enumerate(project_items, 2):
318
+ details_sheet.cell(row=i, column=1).value = item['code']
319
+ details_sheet.cell(row=i, column=2).value = item['description']
320
+ details_sheet.cell(row=i, column=3).value = item.get('resource_type', '-')
321
+ details_sheet.cell(row=i, column=4).value = "نعم" if item.get('is_local_supplier', False) else "لا"
322
+ details_sheet.cell(row=i, column=5).value = item.get('local_content_percentage', 0)
323
+ details_sheet.cell(row=i, column=6).value = item['total_price']
324
+ details_sheet.cell(row=i, column=7).value = item['total_price'] * item.get('local_content_percentage', 0) / 100
325
+
326
+ # تنسيق الأعمدة
327
+ for col in range(1, 8):
328
+ column_letter = get_column_letter(col)
329
+ details_sheet.column_dimensions[column_letter].width = 15
330
+
331
+ details_sheet.column_dimensions['B'].width = 30 # عمود الوصف أوسع
332
+
333
+ # تنسيق الخلايا
334
+ for col in range(1, 8):
335
+ for row in range(1, len(project_items) + 2):
336
+ cell = details_sheet.cell(row=row, column=col)
337
+ cell.border = Border(
338
+ left=Side(style='thin'),
339
+ right=Side(style='thin'),
340
+ top=Side(style='thin'),
341
+ bottom=Side(style='thin')
342
+ )
343
+
344
+ # حفظ الملف
345
+ wb.save(filename)
346
+ return filename
347
+
348
+ def export_risk_report(risks, project_info, total_cost, filename):
349
+ """تصدير تقرير المخاطر إلى ملف Excel"""
350
+ import openpyxl
351
+ from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
352
+ from openpyxl.utils import get_column_letter
353
+
354
+ # إنشاء مصنف Excel جديد
355
+ wb = openpyxl.Workbook()
356
+
357
+ # إنشاء ورقة ملخص المخاطر
358
+ summary_sheet = wb.active
359
+ summary_sheet.title = "ملخص المخاطر"
360
+
361
+ # إضافة عنوان التقرير
362
+ summary_sheet['A1'] = f"تقرير ��لمخاطر - {project_info['name']}"
363
+ summary_sheet['A1'].font = Font(bold=True, size=14)
364
+ summary_sheet.merge_cells('A1:G1')
365
+ summary_sheet['A1'].alignment = Alignment(horizontal='center')
366
+
367
+ # إضافة معلومات المشروع
368
+ summary_sheet['A3'] = "معلومات المشروع"
369
+ summary_sheet['A3'].font = Font(bold=True)
370
+
371
+ project_data = [
372
+ ["اسم المشروع", project_info['name']],
373
+ ["العميل", project_info['client']],
374
+ ["رقم المناقصة", project_info.get('tender_number', '-')],
375
+ ["التكلفة الإجمالية", f"{total_cost:,.2f} ريال"]
376
+ ]
377
+
378
+ for i, row in enumerate(project_data):
379
+ summary_sheet[f'A{i+4}'] = row[0]
380
+ summary_sheet[f'B{i+4}'] = row[1]
381
+
382
+ # إضافة ملخص المخاطر
383
+ summary_sheet['A9'] = "ملخص المخاطر"
384
+ summary_sheet['A9'].font = Font(bold=True)
385
+
386
+ # الحصول على ملخص المخاطر
387
+ risk_summary = project_info.get('risk_summary', {
388
+ 'total_risks': len(risks),
389
+ 'high_risks': sum(1 for risk in risks if risk['risk_score'] >= 15),
390
+ 'medium_risks': sum(1 for risk in risks if 8 <= risk['risk_score'] < 15),
391
+ 'low_risks': sum(1 for risk in risks if risk['risk_score'] < 8),
392
+ 'total_cost_impact': sum(risk['cost_impact'] for risk in risks),
393
+ 'total_schedule_impact': sum(risk['schedule_impact'] for risk in risks),
394
+ 'risk_contingency': sum(risk['probability'] / 5 * risk['cost_impact'] for risk in risks),
395
+ 'risk_contingency_percentage': (sum(risk['probability'] / 5 * risk['cost_impact'] for risk in risks) / total_cost * 100) if total_cost > 0 else 0
396
+ })
397
+
398
+ summary_data = [
399
+ ["إجمالي عدد المخاطر", risk_summary.get('total_risks', 0)],
400
+ ["المخاطر العالية", risk_summary.get('high_risks', 0)],
401
+ ["المخاطر المتوسطة", risk_summary.get('medium_risks', 0)],
402
+ ["المخاطر المنخفضة", risk_summary.get('low_risks', 0)],
403
+ ["إجمالي التأثير المالي", f"{risk_summary.get('total_cost_impact', 0):,.2f} ريال"],
404
+ ["إجمالي التأثير على الجدول الزمني", f"{risk_summary.get('total_schedule_impact', 0)} يوم"],
405
+ ["احتياطي المخاطر المقترح", f"{risk_summary.get('risk_contingency', 0):,.2f} ريال"],
406
+ ["نسبة احتياطي المخاطر", f"{risk_summary.get('risk_contingency_percentage', 0):.1f}%"]
407
+ ]
408
+
409
+ for i, row in enumerate(summary_data):
410
+ summary_sheet[f'A{i+10}'] = row[0]
411
+ summary_sheet[f'B{i+10}'] = row[1]
412
+
413
+ # تنسيق الخلايا
414
+ for col in range(1, 3):
415
+ for row in range(4, 18):
416
+ cell = summary_sheet.cell(row=row, column=col)
417
+ cell.border = Border(
418
+ left=Side(style='thin'),
419
+ right=Side(style='thin'),
420
+ top=Side(style='thin'),
421
+ bottom=Side(style='thin')
422
+ )
423
+
424
+ # إنشاء ورقة قائمة المخاطر
425
+ risks_sheet = wb.create_sheet(title="قائمة المخاطر")
426
+
427
+ # إضافة عناوين الأعمدة
428
+ headers = [
429
+ "معرف", "اسم المخاطرة", "الفئة", "الاحتمالية", "التأثير",
430
+ "درجة المخاطرة", "التأثير المالي", "التأثير على الجدول", "الحالة"
431
+ ]
432
+
433
+ for i, header in enumerate(headers, 1):
434
+ risks_sheet.cell(row=1, column=i).value = header
435
+ risks_sheet.cell(row=1, column=i).font = Font(bold=True)
436
+ risks_sheet.cell(row=1, column=i).fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
437
+
438
+ # ترتيب المخاطر حسب درجة المخاطرة
439
+ sorted_risks = sorted(risks, key=lambda x: x['risk_score'], reverse=True)
440
+
441
+ # إضافة بيانات المخاطر
442
+ for i, risk in enumerate(sorted_risks, 2):
443
+ risks_sheet.cell(row=i, column=1).value = risk['id']
444
+ risks_sheet.cell(row=i, column=2).value = risk['name']
445
+ risks_sheet.cell(row=i, column=3).value = risk['category']
446
+ risks_sheet.cell(row=i, column=4).value = risk['probability']
447
+ risks_sheet.cell(row=i, column=5).value = risk['impact']
448
+ risks_sheet.cell(row=i, column=6).value = risk['risk_score']
449
+ risks_sheet.cell(row=i, column=7).value = risk['cost_impact']
450
+ risks_sheet.cell(row=i, column=8).value = risk['schedule_impact']
451
+ risks_sheet.cell(row=i, column=9).value = risk['status']
452
+
453
+ # تلوين الصف حسب درجة المخاطرة
454
+ risk_score = risk['risk_score']
455
+ if risk_score >= 15:
456
+ fill_color = "FFCCCC" # أحمر فاتح للمخاطر العالية
457
+ elif risk_score >= 8:
458
+ fill_color = "FFEEBB" # أصفر للمخاطر المتوسطة
459
+ else:
460
+ fill_color = "CCFFCC" # أخضر فاتح للمخاطر المنخفضة
461
+
462
+ for col in range(1, 10):
463
+ risks_sheet.cell(row=i, column=col).fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type="solid")
464
+
465
+ # تنسيق الأعمدة
466
+ for col in range(1, 10):
467
+ column_letter = get_column_letter(col)
468
+ risks_sheet.column_dimensions[column_letter].width = 15
469
+
470
+ risks_sheet.column_dimensions['B'].width = 30 # عمود اسم المخاطرة أوسع
471
+
472
+ # تنسيق الخلايا
473
+ for col in range(1, 10):
474
+ for row in range(1, len(risks) + 2):
475
+ cell = risks_sheet.cell(row=row, column=col)
476
+ cell.border = Border(
477
+ left=Side(style='thin'),
478
+ right=Side(style='thin'),
479
+ top=Side(style='thin'),
480
+ bottom=Side(style='thin')
481
+ )
482
+
483
+ # إنشاء ورقة تفاصيل المخاطر
484
+ details_sheet = wb.create_sheet(title="تفاصيل المخاطر")
485
+
486
+ # إضافة عناوين الأعمدة
487
+ details_headers = [
488
+ "معرف", "اسم المخاطرة", "الوصف", "خطة التخفيف", "خطة الطوارئ"
489
+ ]
490
+
491
+ for i, header in enumerate(details_headers, 1):
492
+ details_sheet.cell(row=1, column=i).value = header
493
+ details_sheet.cell(row=1, column=i).font = Font(bold=True)
494
+ details_sheet.cell(row=1, column=i).fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
495
+
496
+ # إضافة تفاصيل المخاطر
497
+ for i, risk in enumerate(sorted_risks, 2):
498
+ details_sheet.cell(row=i, column=1).value = risk['id']
499
+ details_sheet.cell(row=i, column=2).value = risk['name']
500
+ details_sheet.cell(row=i, column=3).value = risk.get('description', '')
501
+ details_sheet.cell(row=i, column=4).value = risk.get('mitigation_plan', '')
502
+ details_sheet.cell(row=i, column=5).value = risk.get('contingency_plan', '')
503
+
504
+ # تنسيق الأعمدة
505
+ for col in range(1, 6):
506
+ column_letter = get_column_letter(col)
507
+ details_sheet.column_dimensions[column_letter].width = 20
508
+
509
+ details_sheet.column_dimensions['C'].width = 40 # عمود الوصف أوسع
510
+ details_sheet.column_dimensions['D'].width = 40 # عمود خطة التخفيف أوسع
511
+ details_sheet.column_dimensions['E'].width = 40 # عمود خطة الطوارئ أوسع
512
+
513
+ # تنسيق الخلايا
514
+ for col in range(1, 6):
515
+ for row in range(1, len(risks) + 2):
516
+ cell = details_sheet.cell(row=row, column=col)
517
+ cell.border = Border(
518
+ left=Side(style='thin'),
519
+ right=Side(style='thin'),
520
+ top=Side(style='thin'),
521
+ bottom=Side(style='thin')
522
+ )
523
+ if col >= 3: # تنسيق النص في الأعمدة الطويلة
524
+ cell.alignment = Alignment(wrap_text=True)
525
+
526
+ # حفظ الملف
527
+ wb.save(filename)
528
+ return filename
529
+
530
+ def get_download_link(file_path, link_text, file_type):
531
+ """إنشاء رابط تنزيل للملف"""
532
+ with open(file_path, "rb") as f:
533
+ file_bytes = f.read()
534
+
535
+ b64 = base64.b64encode(file_bytes).decode()
536
+
537
+ if file_type == "csv":
538
+ mime_type = "text/csv"
539
+ elif file_type == "excel":
540
+ mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
541
+ elif file_type == "pdf":
542
+ mime_type = "application/pdf"
543
+ else:
544
+ mime_type = "application/octet-stream"
545
+
546
+ href = f'<a href="data:{mime_type};base64,{b64}" download="{os.path.basename(file_path)}">{link_text}</a>'
547
+ return href
modules/pricing/fonts/README.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ # تم إنشاء ملف خط عربي بديل
modules/pricing/fonts/arabic_font.ttf ADDED
File without changes