Spaces:
Sleeping
Sleeping
import base64 | |
import os | |
import pandas as pd | |
from datetime import datetime | |
def export_to_excel(project_items, project_info, filename): | |
"""تصدير بيانات المشروع إلى ملف Excel""" | |
import openpyxl | |
from openpyxl.styles import Font, Alignment, Border, Side | |
# إنشاء مصنف Excel جديد | |
wb = openpyxl.Workbook() | |
# إنشاء ورقة معلومات المشروع | |
project_sheet = wb.active | |
project_sheet.title = "معلومات المشروع" | |
# إضافة معلومات المشروع | |
project_sheet['A1'] = "معلومات المشروع" | |
project_sheet['A1'].font = Font(bold=True, size=14) | |
headers = ["البند", "القيمة"] | |
project_sheet.append(headers) | |
project_data = [ | |
["اسم المشروع", project_info['name']], | |
["العميل", project_info['client']], | |
["الموقع", project_info.get('location', '-')], | |
["رقم المناقصة", project_info.get('tender_number', '-')], | |
["القيمة التقديرية", f"{project_info['estimated_value']:,.2f} ريال"], | |
["الموعد النهائي", project_info['deadline']], | |
["مدة العقد", project_info.get('contract_duration', '-')], | |
["نوع التسعير", project_info['pricing_type']] | |
] | |
for row in project_data: | |
project_sheet.append(row) | |
# تنسيق الجدول | |
for col in range(1, 3): | |
for row in range(2, len(project_data) + 3): | |
project_sheet.cell(row=row, column=col).border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
# إنشاء ورقة جدول الكميات | |
boq_sheet = wb.create_sheet(title="جدول الكميات") | |
# إضافة عناوين الأعمدة | |
headers = ["الكود", "الوصف", "الوحدة", "الكمية", "سعر الوحدة (ريال)", "السعر الإجمالي (ريال)", "نوع المورد"] | |
boq_sheet.append(headers) | |
# إضافة بيانات البنود | |
for item in project_items: | |
row_data = [ | |
item['code'], | |
item['description'], | |
item['unit'], | |
item['quantity'], | |
item['unit_price'], | |
item['total_price'], | |
item.get('resource_type', '-') | |
] | |
boq_sheet.append(row_data) | |
# تنسيق الجدول | |
for col in range(1, 8): | |
for row in range(1, len(project_items) + 2): | |
boq_sheet.cell(row=row, column=col).border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
# حفظ الملف | |
wb.save(filename) | |
return filename | |
def export_to_pdf(project_items, project_info, filename): | |
"""تصدير بيانات المشروع إلى ملف PDF""" | |
from reportlab.lib.pagesizes import A4 | |
from reportlab.lib import colors | |
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer | |
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
from reportlab.pdfbase import pdfmetrics | |
from reportlab.pdfbase.ttfonts import TTFont | |
try: | |
import arabic_reshaper | |
from bidi.algorithm import get_display | |
ARABIC_SUPPORT = True | |
except ImportError: | |
ARABIC_SUPPORT = False | |
# تسجيل الخط العربي | |
font_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fonts", "arabic_font.ttf") | |
if os.path.exists(font_path): | |
try: | |
pdfmetrics.registerFont(TTFont('Arabic', font_path)) | |
except: | |
# استخدام خط افتراضي إذا فشل تسجيل الخط العربي | |
pass | |
# إنشاء أنماط النص | |
styles = getSampleStyleSheet() | |
styles.add(ParagraphStyle(name='Arabic', fontName='Helvetica', alignment=1)) # محاذاة للوسط | |
# إنشاء مستند PDF | |
doc = SimpleDocTemplate(filename, pagesize=A4, rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=30) | |
elements = [] | |
# إضافة عنوان المستند | |
title_text = f"تقرير المشروع: {project_info['name']}" | |
if ARABIC_SUPPORT: | |
title_text = get_display(arabic_reshaper.reshape(title_text)) | |
title = Paragraph(title_text, styles['Arabic']) | |
elements.append(title) | |
elements.append(Spacer(1, 20)) | |
# إضافة معلومات المشروع | |
project_data = [ | |
["البند", "القيمة"], | |
["اسم المشروع", project_info['name']], | |
["العميل", project_info['client']], | |
["الموقع", project_info.get('location', '-')], | |
["رقم المناقصة", project_info.get('tender_number', '-')], | |
["القيمة التقديرية", f"{project_info['estimated_value']:,.2f} ريال"], | |
["الموعد النهائي", project_info['deadline']], | |
["مدة العقد", project_info.get('contract_duration', '-')], | |
["نوع التسعير", project_info['pricing_type']] | |
] | |
# تحويل النص العربي | |
if ARABIC_SUPPORT: | |
for i in range(len(project_data)): | |
for j in range(len(project_data[i])): | |
if isinstance(project_data[i][j], str): | |
project_data[i][j] = get_display(arabic_reshaper.reshape(project_data[i][j])) | |
# إنشاء جدول معلومات المشروع | |
project_table = Table(project_data) | |
project_table.setStyle(TableStyle([ | |
('BACKGROUND', (0, 0), (1, 0), colors.grey), | |
('TEXTCOLOR', (0, 0), (1, 0), colors.whitesmoke), | |
('ALIGN', (0, 0), (-1, -1), 'CENTER'), | |
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), | |
('BOTTOMPADDING', (0, 0), (-1, 0), 12), | |
('GRID', (0, 0), (-1, -1), 1, colors.black) | |
])) | |
elements.append(project_table) | |
elements.append(Spacer(1, 30)) | |
# إضافة عنوان جدول الكميات | |
boq_title_text = "جدول الكميات" | |
if ARABIC_SUPPORT: | |
boq_title_text = get_display(arabic_reshaper.reshape(boq_title_text)) | |
boq_title = Paragraph(boq_title_text, styles['Arabic']) | |
elements.append(boq_title) | |
elements.append(Spacer(1, 10)) | |
# إعداد بيانات جدول الكميات | |
boq_data = [["الكود", "الوصف", "الوحدة", "الكمية", "سعر الوحدة", "السعر الإجمالي"]] | |
for item in project_items: | |
row = [ | |
item['code'], | |
item['description'], | |
item['unit'], | |
str(item['quantity']), | |
f"{item['unit_price']:,.2f}", | |
f"{item['total_price']:,.2f}" | |
] | |
# تحويل النص العربي | |
if ARABIC_SUPPORT: | |
for i in range(len(row)): | |
if isinstance(row[i], str): | |
row[i] = get_display(arabic_reshaper.reshape(row[i])) | |
boq_data.append(row) | |
# إضافة الإجمالي | |
total_cost = sum(item['total_price'] for item in project_items) | |
total_row = ["", "", "", "", "الإجمالي", f"{total_cost:,.2f}"] | |
if ARABIC_SUPPORT: | |
total_row[4] = get_display(arabic_reshaper.reshape(total_row[4])) | |
total_row[5] = get_display(arabic_reshaper.reshape(total_row[5])) | |
boq_data.append(total_row) | |
# إنشاء جدول الكميات | |
boq_table = Table(boq_data) | |
boq_table.setStyle(TableStyle([ | |
('BACKGROUND', (0, 0), (-1, 0), colors.grey), | |
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), | |
('ALIGN', (0, 0), (-1, -1), 'CENTER'), | |
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), | |
('BOTTOMPADDING', (0, 0), (-1, 0), 12), | |
('GRID', (0, 0), (-1, -1), 1, colors.black), | |
('BACKGROUND', (0, -1), (-1, -1), colors.lightgrey) | |
])) | |
elements.append(boq_table) | |
# بناء المستند | |
doc.build(elements) | |
return filename | |
def export_local_content_report(project_items, project_info, filename): | |
"""تصدير تقرير المحتوى المحلي إلى ملف Excel""" | |
import openpyxl | |
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side | |
from openpyxl.utils import get_column_letter | |
# إنشاء مصنف Excel جديد | |
wb = openpyxl.Workbook() | |
# إنشاء ورقة ملخص المحتوى المحلي | |
summary_sheet = wb.active | |
summary_sheet.title = "ملخص المحتوى المحلي" | |
# إضافة عنوان التقرير | |
summary_sheet['A1'] = f"تقرير المحتوى المحلي - {project_info['name']}" | |
summary_sheet['A1'].font = Font(bold=True, size=14) | |
summary_sheet.merge_cells('A1:G1') | |
summary_sheet['A1'].alignment = Alignment(horizontal='center') | |
# إضافة معلومات المشروع | |
summary_sheet['A3'] = "معلومات المشروع" | |
summary_sheet['A3'].font = Font(bold=True) | |
project_data = [ | |
["اسم المشروع", project_info['name']], | |
["العميل", project_info['client']], | |
["رقم المناقصة", project_info.get('tender_number', '-')], | |
["النسبة المستهدفة للمحتوى المحلي", f"{project_info.get('local_content_target', 40)}%"] | |
] | |
for i, row in enumerate(project_data): | |
summary_sheet[f'A{i+4}'] = row[0] | |
summary_sheet[f'B{i+4}'] = row[1] | |
# إضافة ملخص المحتوى المحلي | |
summary_sheet['A9'] = "ملخص المحتوى المحلي" | |
summary_sheet['A9'].font = Font(bold=True) | |
# الحصول على ملخص المحتوى المحلي | |
summary = project_info.get('local_content_summary', { | |
'total_percentage': 0, | |
'by_category': { | |
'materials': 0, | |
'labor': 0, | |
'services': 0, | |
'equipment': 0 | |
} | |
}) | |
summary_data = [ | |
["النسبة الإجمالية للمحتوى المحلي", f"{summary.get('total_percentage', 0):.1f}%"], | |
["نسبة المواد المحلية", f"{summary.get('by_category', {}).get('materials', 0):.1f}%"], | |
["نسبة العمالة المحلية", f"{summary.get('by_category', {}).get('labor', 0):.1f}%"], | |
["نسبة الخدمات المحلية", f"{summary.get('by_category', {}).get('services', 0):.1f}%"], | |
["نسبة المعدات المحلية", f"{summary.get('by_category', {}).get('equipment', 0):.1f}%"] | |
] | |
for i, row in enumerate(summary_data): | |
summary_sheet[f'A{i+10}'] = row[0] | |
summary_sheet[f'B{i+10}'] = row[1] | |
# تنسيق الخلايا | |
for col in range(1, 3): | |
for row in range(4, 15): | |
cell = summary_sheet.cell(row=row, column=col) | |
cell.border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
# إضافة تقييم الامتثال | |
target_percentage = project_info.get('local_content_target', 40) | |
total_percentage = summary.get('total_percentage', 0) | |
summary_sheet['A16'] = "تقييم الامتثال" | |
summary_sheet['A16'].font = Font(bold=True) | |
if total_percentage >= target_percentage: | |
compliance_text = f"المشروع يحقق متطلبات المحتوى المحلي المستهدفة ({target_percentage}%)." | |
compliance_fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid") | |
else: | |
compliance_text = f"المشروع لا يحقق متطلبات المحتوى المحلي المستهدفة ({target_percentage}%)." | |
compliance_fill = PatternFill(start_color="FFCCCC", end_color="FFCCCC", fill_type="solid") | |
summary_sheet['A17'] = compliance_text | |
summary_sheet['A17'].fill = compliance_fill | |
summary_sheet.merge_cells('A17:G17') | |
# إنشاء ورقة تفاصيل البنود | |
details_sheet = wb.create_sheet(title="تفاصيل البنود") | |
# إضافة عناوين الأعمدة | |
headers = [ | |
"الكود", "الوصف", "نوع المورد", "مورد محلي", | |
"نسبة المحتوى المحلي (%)", "السعر الإجمالي (ريال)", | |
"المساهمة في المحتوى المحلي (ريال)" | |
] | |
for i, header in enumerate(headers, 1): | |
details_sheet.cell(row=1, column=i).value = header | |
details_sheet.cell(row=1, column=i).font = Font(bold=True) | |
details_sheet.cell(row=1, column=i).fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid") | |
# إضافة بيانات البنود | |
for i, item in enumerate(project_items, 2): | |
details_sheet.cell(row=i, column=1).value = item['code'] | |
details_sheet.cell(row=i, column=2).value = item['description'] | |
details_sheet.cell(row=i, column=3).value = item.get('resource_type', '-') | |
details_sheet.cell(row=i, column=4).value = "نعم" if item.get('is_local_supplier', False) else "لا" | |
details_sheet.cell(row=i, column=5).value = item.get('local_content_percentage', 0) | |
details_sheet.cell(row=i, column=6).value = item['total_price'] | |
details_sheet.cell(row=i, column=7).value = item['total_price'] * item.get('local_content_percentage', 0) / 100 | |
# تنسيق الأعمدة | |
for col in range(1, 8): | |
column_letter = get_column_letter(col) | |
details_sheet.column_dimensions[column_letter].width = 15 | |
details_sheet.column_dimensions['B'].width = 30 # عمود الوصف أوسع | |
# تنسيق الخلايا | |
for col in range(1, 8): | |
for row in range(1, len(project_items) + 2): | |
cell = details_sheet.cell(row=row, column=col) | |
cell.border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
# حفظ الملف | |
wb.save(filename) | |
return filename | |
def export_risk_report(risks, project_info, total_cost, filename): | |
"""تصدير تقرير المخاطر إلى ملف Excel""" | |
import openpyxl | |
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side | |
from openpyxl.utils import get_column_letter | |
# إنشاء مصنف Excel جديد | |
wb = openpyxl.Workbook() | |
# إنشاء ورقة ملخص المخاطر | |
summary_sheet = wb.active | |
summary_sheet.title = "ملخص المخاطر" | |
# إضافة عنوان التقرير | |
summary_sheet['A1'] = f"تقرير المخاطر - {project_info['name']}" | |
summary_sheet['A1'].font = Font(bold=True, size=14) | |
summary_sheet.merge_cells('A1:G1') | |
summary_sheet['A1'].alignment = Alignment(horizontal='center') | |
# إضافة معلومات المشروع | |
summary_sheet['A3'] = "معلومات المشروع" | |
summary_sheet['A3'].font = Font(bold=True) | |
project_data = [ | |
["اسم المشروع", project_info['name']], | |
["العميل", project_info['client']], | |
["رقم المناقصة", project_info.get('tender_number', '-')], | |
["التكلفة الإجمالية", f"{total_cost:,.2f} ريال"] | |
] | |
for i, row in enumerate(project_data): | |
summary_sheet[f'A{i+4}'] = row[0] | |
summary_sheet[f'B{i+4}'] = row[1] | |
# إضافة ملخص المخاطر | |
summary_sheet['A9'] = "ملخص المخاطر" | |
summary_sheet['A9'].font = Font(bold=True) | |
# الحصول على ملخص المخاطر | |
risk_summary = project_info.get('risk_summary', { | |
'total_risks': len(risks), | |
'high_risks': sum(1 for risk in risks if risk['risk_score'] >= 15), | |
'medium_risks': sum(1 for risk in risks if 8 <= risk['risk_score'] < 15), | |
'low_risks': sum(1 for risk in risks if risk['risk_score'] < 8), | |
'total_cost_impact': sum(risk['cost_impact'] for risk in risks), | |
'total_schedule_impact': sum(risk['schedule_impact'] for risk in risks), | |
'risk_contingency': sum(risk['probability'] / 5 * risk['cost_impact'] for risk in risks), | |
'risk_contingency_percentage': (sum(risk['probability'] / 5 * risk['cost_impact'] for risk in risks) / total_cost * 100) if total_cost > 0 else 0 | |
}) | |
summary_data = [ | |
["إجمالي عدد المخاطر", risk_summary.get('total_risks', 0)], | |
["المخاطر العالية", risk_summary.get('high_risks', 0)], | |
["المخاطر المتوسطة", risk_summary.get('medium_risks', 0)], | |
["المخاطر المنخفضة", risk_summary.get('low_risks', 0)], | |
["إجمالي التأثير المالي", f"{risk_summary.get('total_cost_impact', 0):,.2f} ريال"], | |
["إجمالي التأثير على الجدول الزمني", f"{risk_summary.get('total_schedule_impact', 0)} يوم"], | |
["احتياطي المخاطر المقترح", f"{risk_summary.get('risk_contingency', 0):,.2f} ريال"], | |
["نسبة احتياطي المخاطر", f"{risk_summary.get('risk_contingency_percentage', 0):.1f}%"] | |
] | |
for i, row in enumerate(summary_data): | |
summary_sheet[f'A{i+10}'] = row[0] | |
summary_sheet[f'B{i+10}'] = row[1] | |
# تنسيق الخلايا | |
for col in range(1, 3): | |
for row in range(4, 18): | |
cell = summary_sheet.cell(row=row, column=col) | |
cell.border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
# إنشاء ورقة قائمة المخاطر | |
risks_sheet = wb.create_sheet(title="قائمة المخاطر") | |
# إضافة عناوين الأعمدة | |
headers = [ | |
"معرف", "اسم المخاطرة", "الفئة", "الاحتمالية", "التأثير", | |
"درجة المخاطرة", "التأثير المالي", "التأثير على الجدول", "الحالة" | |
] | |
for i, header in enumerate(headers, 1): | |
risks_sheet.cell(row=1, column=i).value = header | |
risks_sheet.cell(row=1, column=i).font = Font(bold=True) | |
risks_sheet.cell(row=1, column=i).fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid") | |
# ترتيب المخاطر حسب درجة المخاطرة | |
sorted_risks = sorted(risks, key=lambda x: x['risk_score'], reverse=True) | |
# إضافة بيانات المخاطر | |
for i, risk in enumerate(sorted_risks, 2): | |
risks_sheet.cell(row=i, column=1).value = risk['id'] | |
risks_sheet.cell(row=i, column=2).value = risk['name'] | |
risks_sheet.cell(row=i, column=3).value = risk['category'] | |
risks_sheet.cell(row=i, column=4).value = risk['probability'] | |
risks_sheet.cell(row=i, column=5).value = risk['impact'] | |
risks_sheet.cell(row=i, column=6).value = risk['risk_score'] | |
risks_sheet.cell(row=i, column=7).value = risk['cost_impact'] | |
risks_sheet.cell(row=i, column=8).value = risk['schedule_impact'] | |
risks_sheet.cell(row=i, column=9).value = risk['status'] | |
# تلوين الصف حسب درجة المخاطرة | |
risk_score = risk['risk_score'] | |
if risk_score >= 15: | |
fill_color = "FFCCCC" # أحمر فاتح للمخاطر العالية | |
elif risk_score >= 8: | |
fill_color = "FFEEBB" # أصفر للمخاطر المتوسطة | |
else: | |
fill_color = "CCFFCC" # أخضر فاتح للمخاطر المنخفضة | |
for col in range(1, 10): | |
risks_sheet.cell(row=i, column=col).fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type="solid") | |
# تنسيق الأعمدة | |
for col in range(1, 10): | |
column_letter = get_column_letter(col) | |
risks_sheet.column_dimensions[column_letter].width = 15 | |
risks_sheet.column_dimensions['B'].width = 30 # عمود اسم المخاطرة أوسع | |
# تنسيق الخلايا | |
for col in range(1, 10): | |
for row in range(1, len(risks) + 2): | |
cell = risks_sheet.cell(row=row, column=col) | |
cell.border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
# إنشاء ورقة تفاصيل المخاطر | |
details_sheet = wb.create_sheet(title="تفاصيل المخاطر") | |
# إضافة عناوين الأعمدة | |
details_headers = [ | |
"معرف", "اسم المخاطرة", "الوصف", "خطة التخفيف", "خطة الطوارئ" | |
] | |
for i, header in enumerate(details_headers, 1): | |
details_sheet.cell(row=1, column=i).value = header | |
details_sheet.cell(row=1, column=i).font = Font(bold=True) | |
details_sheet.cell(row=1, column=i).fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid") | |
# إضافة تفاصيل المخاطر | |
for i, risk in enumerate(sorted_risks, 2): | |
details_sheet.cell(row=i, column=1).value = risk['id'] | |
details_sheet.cell(row=i, column=2).value = risk['name'] | |
details_sheet.cell(row=i, column=3).value = risk.get('description', '') | |
details_sheet.cell(row=i, column=4).value = risk.get('mitigation_plan', '') | |
details_sheet.cell(row=i, column=5).value = risk.get('contingency_plan', '') | |
# تنسيق الأعمدة | |
for col in range(1, 6): | |
column_letter = get_column_letter(col) | |
details_sheet.column_dimensions[column_letter].width = 20 | |
details_sheet.column_dimensions['C'].width = 40 # عمود الوصف أوسع | |
details_sheet.column_dimensions['D'].width = 40 # عمود خطة التخفيف أوسع | |
details_sheet.column_dimensions['E'].width = 40 # عمود خطة الطوارئ أوسع | |
# تنسيق الخلايا | |
for col in range(1, 6): | |
for row in range(1, len(risks) + 2): | |
cell = details_sheet.cell(row=row, column=col) | |
cell.border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
if col >= 3: # تنسيق النص في الأعمدة الطويلة | |
cell.alignment = Alignment(wrap_text=True) | |
# حفظ الملف | |
wb.save(filename) | |
return filename | |
def get_download_link(file_path, link_text, file_type): | |
"""إنشاء رابط تنزيل للملف""" | |
with open(file_path, "rb") as f: | |
file_bytes = f.read() | |
b64 = base64.b64encode(file_bytes).decode() | |
if file_type == "csv": | |
mime_type = "text/csv" | |
elif file_type == "excel": | |
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | |
elif file_type == "pdf": | |
mime_type = "application/pdf" | |
else: | |
mime_type = "application/octet-stream" | |
href = f'<a href="data:{mime_type};base64,{b64}" download="{os.path.basename(file_path)}">{link_text}</a>' | |
return href | |