Spaces:
Paused
Paused
| 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 | |