Update: 2026-05-15 14:23:28
This commit is contained in:
@@ -132,8 +132,72 @@ $totalFont = '065F46'; // Dark green
|
|||||||
$borderColor = 'E2E1F0'; // Light border
|
$borderColor = 'E2E1F0'; // Light border
|
||||||
$altRowBg = 'F8F7FD'; // Alternating row
|
$altRowBg = 'F8F7FD'; // Alternating row
|
||||||
|
|
||||||
// Process each invoice
|
// ══════════════════════════════════════════
|
||||||
$sheetIndex = 0;
|
// 1. SUMMARY SHEET (First Sheet)
|
||||||
|
// ══════════════════════════════════════════
|
||||||
|
|
||||||
|
$summarySheet = $spreadsheet->getActiveSheet();
|
||||||
|
$summarySheet->setTitle('الملخص الإجمالي');
|
||||||
|
$summarySheet->setRightToLeft(true);
|
||||||
|
|
||||||
|
// --- SUMMARY HEADER ---
|
||||||
|
$summarySheet->mergeCells("A1:J1");
|
||||||
|
$summarySheet->setCellValue("A1", 'مُـصَـادَق — ملخص الفواتير الإجمالي');
|
||||||
|
$summarySheet->getStyle("A1")->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'size' => 16, 'color' => ['argb' => 'FF' . $headerFont]],
|
||||||
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $headerBg]],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
|
||||||
|
]);
|
||||||
|
$summarySheet->getRowDimension(1)->setRowHeight(40);
|
||||||
|
|
||||||
|
// Summary Meta Info
|
||||||
|
$companyNameFilter = 'جميع الشركات';
|
||||||
|
if ($companyId) {
|
||||||
|
$cStmt = $db->prepare("SELECT name FROM companies WHERE id = ?");
|
||||||
|
$cStmt->execute([$companyId]);
|
||||||
|
$cName = $cStmt->fetchColumn();
|
||||||
|
if ($cName) $companyNameFilter = $dec($cName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$summarySheet->setCellValue("A3", 'الشركة:');
|
||||||
|
$summarySheet->setCellValue("B3", $companyNameFilter);
|
||||||
|
$summarySheet->setCellValue("D3", 'الفترة:');
|
||||||
|
$summarySheet->setCellValue("E3", ($dateFrom ?? '—') . ' إلى ' . ($dateTo ?? '—'));
|
||||||
|
$summarySheet->setCellValue("G3", 'عدد الفواتير:');
|
||||||
|
$summarySheet->setCellValue("H3", count($invoices));
|
||||||
|
|
||||||
|
$summarySheet->getStyle("A3:H3")->getFont()->setBold(true);
|
||||||
|
|
||||||
|
// --- SUMMARY TABLE HEADERS ---
|
||||||
|
$row = 5;
|
||||||
|
$summaryHeaders = ['#', 'رقم الفاتورة', 'المورّد', 'وصف البند', 'الكمية', 'سعر الوحدة', 'المجموع الجزئي', 'نسبة الضريبة', 'قيمة الضريبة', 'الصافي'];
|
||||||
|
$sumCols = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
|
||||||
|
|
||||||
|
foreach ($summaryHeaders as $i => $h) {
|
||||||
|
$summarySheet->setCellValue($sumCols[$i] . $row, $h);
|
||||||
|
}
|
||||||
|
|
||||||
|
$summarySheet->getStyle("A{$row}:J{$row}")->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'color' => ['argb' => 'FF' . $headerFont]],
|
||||||
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $headerBg]],
|
||||||
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Summary column widths
|
||||||
|
$summarySheet->getColumnDimension('B')->setWidth(18);
|
||||||
|
$summarySheet->getColumnDimension('C')->setWidth(25);
|
||||||
|
$summarySheet->getColumnDimension('D')->setWidth(35);
|
||||||
|
$summarySheet->getColumnDimension('G')->setWidth(14);
|
||||||
|
$summarySheet->getColumnDimension('I')->setWidth(14);
|
||||||
|
$summarySheet->getColumnDimension('J')->setWidth(16);
|
||||||
|
|
||||||
|
$row++;
|
||||||
|
$summaryStartRow = $row;
|
||||||
|
$globalLineCount = 0;
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════
|
||||||
|
// 2. INDIVIDUAL INVOICE SHEETS + POPULATE SUMMARY
|
||||||
|
// ══════════════════════════════════════════
|
||||||
|
|
||||||
foreach ($invoices as $invIdx => $inv) {
|
foreach ($invoices as $invIdx => $inv) {
|
||||||
// Fetch line items for this invoice
|
// Fetch line items for this invoice
|
||||||
@@ -141,15 +205,46 @@ foreach ($invoices as $invIdx => $inv) {
|
|||||||
$stmtLines->execute([$inv['id']]);
|
$stmtLines->execute([$inv['id']]);
|
||||||
$lines = $stmtLines->fetchAll();
|
$lines = $stmtLines->fetchAll();
|
||||||
|
|
||||||
// Create sheet (reuse first sheet for first invoice)
|
// --- Add to Summary Sheet ---
|
||||||
if ($sheetIndex === 0) {
|
if (!empty($lines)) {
|
||||||
$sheet = $spreadsheet->getActiveSheet();
|
foreach ($lines as $line) {
|
||||||
|
$globalLineCount++;
|
||||||
|
$summarySheet->setCellValue("A{$row}", $globalLineCount);
|
||||||
|
$summarySheet->setCellValue("B{$row}", $inv['invoice_number'] ?? '-');
|
||||||
|
$summarySheet->setCellValue("C{$row}", $dec($inv['supplier_name']));
|
||||||
|
$summarySheet->setCellValue("D{$row}", $line['description'] ?? 'بدون وصف');
|
||||||
|
$summarySheet->setCellValue("E{$row}", (float)$line['quantity']);
|
||||||
|
$summarySheet->setCellValue("F{$row}", (float)$line['unit_price']);
|
||||||
|
$summarySheet->setCellValue("G{$row}", "=E{$row}*F{$row}");
|
||||||
|
$summarySheet->setCellValue("H{$row}", (float)$line['tax_rate']);
|
||||||
|
$summarySheet->setCellValue("I{$row}", "=G{$row}*H{$row}");
|
||||||
|
$summarySheet->setCellValue("J{$row}", "=G{$row}+I{$row}");
|
||||||
|
|
||||||
|
if ($globalLineCount % 2 === 0) {
|
||||||
|
$summarySheet->getStyle("A{$row}:J{$row}")->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB('FFF8F7FD');
|
||||||
|
}
|
||||||
|
$row++;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$sheet = $spreadsheet->createSheet();
|
// Fallback if no line items
|
||||||
|
$globalLineCount++;
|
||||||
|
$summarySheet->setCellValue("A{$row}", $globalLineCount);
|
||||||
|
$summarySheet->setCellValue("B{$row}", $inv['invoice_number'] ?? '-');
|
||||||
|
$summarySheet->setCellValue("C{$row}", $dec($inv['supplier_name']));
|
||||||
|
$summarySheet->setCellValue("D{$row}", 'إجمالي الفاتورة');
|
||||||
|
$summarySheet->setCellValue("E{$row}", 1);
|
||||||
|
$summarySheet->setCellValue("F{$row}", (float)$inv['subtotal']);
|
||||||
|
$summarySheet->setCellValue("G{$row}", "=E{$row}*F{$row}");
|
||||||
|
$summarySheet->setCellValue("H{$row}", 0.16);
|
||||||
|
$summarySheet->setCellValue("I{$row}", "=G{$row}*H{$row}");
|
||||||
|
$summarySheet->setCellValue("J{$row}", "=G{$row}+I{$row}");
|
||||||
|
$row++;
|
||||||
}
|
}
|
||||||
|
|
||||||
$invoiceNum = $inv['invoice_number'] ?? ('INV-' . ($sheetIndex + 1));
|
// --- Create Individual Sheet ---
|
||||||
$sheetTitle = mb_substr(preg_replace('/[^a-zA-Z0-9\x{0600}-\x{06FF}\s\-]/u', '', $invoiceNum), 0, 31) ?: ('فاتورة ' . ($sheetIndex + 1));
|
$sheet = $spreadsheet->createSheet();
|
||||||
|
$invoiceNum = $inv['invoice_number'] ?? ('INV-' . ($invIdx + 1));
|
||||||
|
$sheetTitle = mb_substr(preg_replace('/[^a-zA-Z0-9\x{0600}-\x{06FF}\s\-]/u', '', $invoiceNum), 0, 31) ?: ('فاتورة ' . ($invIdx + 1));
|
||||||
$sheet->setTitle($sheetTitle);
|
$sheet->setTitle($sheetTitle);
|
||||||
$sheet->setRightToLeft(true);
|
$sheet->setRightToLeft(true);
|
||||||
|
|
||||||
@@ -164,20 +259,20 @@ foreach ($invoices as $invIdx => $inv) {
|
|||||||
$sheet->getColumnDimension('H')->setWidth(14); // Discount
|
$sheet->getColumnDimension('H')->setWidth(14); // Discount
|
||||||
$sheet->getColumnDimension('I')->setWidth(18); // Net Total (formula)
|
$sheet->getColumnDimension('I')->setWidth(18); // Net Total (formula)
|
||||||
|
|
||||||
$row = 1;
|
$invRow = 1;
|
||||||
|
|
||||||
// ── INVOICE HEADER ──────────────────────────
|
// ── INVOICE HEADER ──────────────────────────
|
||||||
$sheet->mergeCells("A{$row}:I{$row}");
|
$sheet->mergeCells("A{$invRow}:I{$invRow}");
|
||||||
$sheet->setCellValue("A{$row}", 'مُـصَـادَق — تقرير فاتورة مشتريات');
|
$sheet->setCellValue("A{$invRow}", 'مُـصَـادَق — تقرير فاتورة مشتريات');
|
||||||
$sheet->getStyle("A{$row}")->applyFromArray([
|
$sheet->getStyle("A{$invRow}")->applyFromArray([
|
||||||
'font' => ['bold' => true, 'size' => 16, 'color' => ['argb' => 'FF' . $headerFont]],
|
'font' => ['bold' => true, 'size' => 16, 'color' => ['argb' => 'FF' . $headerFont]],
|
||||||
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $headerBg]],
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $headerBg]],
|
||||||
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
|
||||||
]);
|
]);
|
||||||
$sheet->getRowDimension($row)->setRowHeight(40);
|
$sheet->getRowDimension($invRow)->setRowHeight(40);
|
||||||
$row++;
|
$invRow++;
|
||||||
|
|
||||||
// Invoice meta data (2 columns layout)
|
// Invoice meta data
|
||||||
$metaData = [
|
$metaData = [
|
||||||
['رقم الفاتورة', $inv['invoice_number'] ?? '-', 'اسم المورّد', $dec($inv['supplier_name'])],
|
['رقم الفاتورة', $inv['invoice_number'] ?? '-', 'اسم المورّد', $dec($inv['supplier_name'])],
|
||||||
['تاريخ الفاتورة', $inv['invoice_date'] ?? '-', 'الرقم الضريبي للمورّد', $dec($inv['supplier_tin'])],
|
['تاريخ الفاتورة', $inv['invoice_date'] ?? '-', 'الرقم الضريبي للمورّد', $dec($inv['supplier_tin'])],
|
||||||
@@ -192,154 +287,107 @@ foreach ($invoices as $invIdx => $inv) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($metaData as $meta) {
|
foreach ($metaData as $meta) {
|
||||||
$sheet->setCellValue("A{$row}", $meta[0]);
|
$sheet->setCellValue("A{$invRow}", $meta[0]);
|
||||||
$sheet->mergeCells("B{$row}:C{$row}");
|
$sheet->mergeCells("B{$invRow}:C{$invRow}");
|
||||||
$sheet->setCellValue("B{$row}", $meta[1]);
|
$sheet->setCellValue("B{$invRow}", $meta[1]);
|
||||||
$sheet->setCellValue("E{$row}", $meta[2]);
|
$sheet->setCellValue("E{$invRow}", $meta[2]);
|
||||||
$sheet->mergeCells("F{$row}:I{$row}");
|
$sheet->mergeCells("F{$invRow}:I{$invRow}");
|
||||||
$sheet->setCellValue("F{$row}", $meta[3]);
|
$sheet->setCellValue("F{$invRow}", $meta[3]);
|
||||||
|
|
||||||
// Style labels
|
$sheet->getStyle("A{$invRow}:C{$invRow}")->applyFromArray([
|
||||||
$sheet->getStyle("A{$row}:C{$row}")->applyFromArray([
|
|
||||||
'font' => ['bold' => true, 'size' => 11, 'color' => ['argb' => 'FF' . $subHeaderFont]],
|
'font' => ['bold' => true, 'size' => 11, 'color' => ['argb' => 'FF' . $subHeaderFont]],
|
||||||
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $subHeaderBg]],
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $subHeaderBg]],
|
||||||
]);
|
]);
|
||||||
$sheet->getStyle("E{$row}")->applyFromArray([
|
$sheet->getStyle("E{$invRow}")->applyFromArray([
|
||||||
'font' => ['bold' => true, 'size' => 11, 'color' => ['argb' => 'FF' . $subHeaderFont]],
|
'font' => ['bold' => true, 'size' => 11, 'color' => ['argb' => 'FF' . $subHeaderFont]],
|
||||||
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $subHeaderBg]],
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $subHeaderBg]],
|
||||||
]);
|
]);
|
||||||
$sheet->getRowDimension($row)->setRowHeight(24);
|
$sheet->getRowDimension($invRow)->setRowHeight(24);
|
||||||
$row++;
|
$invRow++;
|
||||||
}
|
}
|
||||||
$row++; // Empty spacer row
|
$invRow++;
|
||||||
|
|
||||||
// ── LINE ITEMS TABLE HEADER ─────────────────
|
// Items Header
|
||||||
$headers = ['#', 'وصف البند', 'الكمية', 'سعر الوحدة', 'المجموع الجزئي', 'نسبة الضريبة', 'قيمة الضريبة', 'الخصم', 'الصافي'];
|
$headers = ['#', 'وصف البند', 'الكمية', 'سعر الوحدة', 'المجموع الجزئي', 'نسبة الضريبة', 'قيمة الضريبة', 'الخصم', 'الصافي'];
|
||||||
$cols = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
|
$cols = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
|
||||||
$headerRow = $row;
|
foreach ($headers as $i => $h) $sheet->setCellValue($cols[$i] . $invRow, $h);
|
||||||
$itemsStartRow = $row + 1;
|
|
||||||
|
|
||||||
foreach ($headers as $i => $header) {
|
$sheet->getStyle("A{$invRow}:I{$invRow}")->applyFromArray([
|
||||||
$sheet->setCellValue($cols[$i] . $row, $header);
|
'font' => ['bold' => true, 'color' => ['argb' => 'FF' . $headerFont]],
|
||||||
}
|
|
||||||
|
|
||||||
$sheet->getStyle("A{$row}:I{$row}")->applyFromArray([
|
|
||||||
'font' => ['bold' => true, 'size' => 12, 'color' => ['argb' => 'FF' . $headerFont]],
|
|
||||||
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $headerBg]],
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $headerBg]],
|
||||||
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER],
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
'borders' => [
|
|
||||||
'allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['argb' => 'FF' . $headerBg]],
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
$sheet->getRowDimension($row)->setRowHeight(32);
|
$sheet->getRowDimension($invRow)->setRowHeight(32);
|
||||||
$row++;
|
$invRow++;
|
||||||
|
$itemsStart = $invRow;
|
||||||
// ── LINE ITEMS WITH FORMULAS ────────────────
|
|
||||||
$firstDataRow = $row;
|
|
||||||
|
|
||||||
if (!empty($lines)) {
|
if (!empty($lines)) {
|
||||||
foreach ($lines as $lineIdx => $line) {
|
foreach ($lines as $lIdx => $line) {
|
||||||
$lineNum = $lineIdx + 1;
|
$sheet->setCellValue("A{$invRow}", $lIdx + 1);
|
||||||
$quantity = is_numeric($line['quantity'] ?? null) ? (float)$line['quantity'] : 1;
|
$sheet->setCellValue("B{$invRow}", $line['description'] ?? 'بدون وصف');
|
||||||
$unitPrice = is_numeric($line['unit_price'] ?? null) ? (float)$line['unit_price'] : 0;
|
$sheet->setCellValue("C{$invRow}", (float)$line['quantity']);
|
||||||
$taxRate = is_numeric($line['tax_rate'] ?? null) ? (float)$line['tax_rate'] : 0.16;
|
$sheet->setCellValue("D{$invRow}", (float)$line['unit_price']);
|
||||||
$discount = is_numeric($line['discount_amount'] ?? null) ? (float)$line['discount_amount'] : 0;
|
$sheet->setCellValue("E{$invRow}", "=C{$invRow}*D{$invRow}");
|
||||||
|
$sheet->setCellValue("F{$invRow}", (float)$line['tax_rate']);
|
||||||
$sheet->setCellValue("A{$row}", $lineNum);
|
$sheet->getStyle("F{$invRow}")->getNumberFormat()->setFormatCode('0%');
|
||||||
$sheet->setCellValue("B{$row}", $line['description'] ?? 'بدون وصف');
|
$sheet->setCellValue("G{$invRow}", "=E{$invRow}*F{$invRow}");
|
||||||
$sheet->setCellValue("C{$row}", $quantity);
|
$sheet->setCellValue("H{$invRow}", (float)$line['discount_amount']);
|
||||||
$sheet->setCellValue("D{$row}", $unitPrice);
|
$sheet->setCellValue("I{$invRow}", "=E{$invRow}+G{$invRow}-H{$invRow}");
|
||||||
$sheet->setCellValue("E{$row}", "=C{$row}*D{$row}");
|
if ($lIdx % 2 === 1) $sheet->getStyle("A{$invRow}:I{$invRow}")->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB('FFF8F7FD');
|
||||||
$sheet->setCellValue("F{$row}", $taxRate);
|
foreach (['D','E','G','H','I'] as $c) $sheet->getStyle("{$c}{$invRow}")->getNumberFormat()->setFormatCode('#,##0.000');
|
||||||
$sheet->getStyle("F{$row}")->getNumberFormat()->setFormatCode('0%');
|
$invRow++;
|
||||||
$sheet->setCellValue("G{$row}", "=E{$row}*F{$row}");
|
|
||||||
$sheet->setCellValue("H{$row}", $discount);
|
|
||||||
$sheet->setCellValue("I{$row}", "=E{$row}+G{$row}-H{$row}");
|
|
||||||
|
|
||||||
if ($lineIdx % 2 === 1) {
|
|
||||||
$sheet->getStyle("A{$row}:I{$row}")->applyFromArray([
|
|
||||||
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $altRowBg]],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (['D', 'E', 'G', 'H', 'I'] as $col) {
|
|
||||||
$sheet->getStyle("{$col}{$row}")->getNumberFormat()->setFormatCode('#,##0.000');
|
|
||||||
}
|
|
||||||
$sheet->getStyle("C{$row}")->getNumberFormat()->setFormatCode('#,##0');
|
|
||||||
$sheet->getStyle("A{$row}:I{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
|
||||||
$sheet->getStyle("B{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
|
|
||||||
$sheet->getRowDimension($row)->setRowHeight(26);
|
|
||||||
$row++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$sheet->setCellValue("A{$row}", 1);
|
$sheet->setCellValue("A{$invRow}", 1);
|
||||||
$sheet->setCellValue("B{$row}", 'إجمالي الفاتورة (بدون تفاصيل بنود)');
|
$sheet->setCellValue("B{$invRow}", 'إجمالي الفاتورة');
|
||||||
$sheet->setCellValue("C{$row}", 1);
|
$sheet->setCellValue("C{$invRow}", 1);
|
||||||
$sheet->setCellValue("D{$row}", (float)($inv['subtotal'] ?? 0));
|
$sheet->setCellValue("D{$invRow}", (float)$inv['subtotal']);
|
||||||
$sheet->setCellValue("E{$row}", "=C{$row}*D{$row}");
|
$sheet->setCellValue("E{$invRow}", "=C{$invRow}*D{$invRow}");
|
||||||
$sheet->setCellValue("F{$row}", 0.16);
|
$sheet->setCellValue("F{$invRow}", 0.16);
|
||||||
$sheet->getStyle("F{$row}")->getNumberFormat()->setFormatCode('0%');
|
$sheet->getStyle("F{$invRow}")->getNumberFormat()->setFormatCode('0%');
|
||||||
$sheet->setCellValue("G{$row}", "=E{$row}*F{$row}");
|
$sheet->setCellValue("G{$invRow}", "=E{$invRow}*F{$invRow}");
|
||||||
$sheet->setCellValue("H{$row}", (float)($inv['discount_total'] ?? 0));
|
$sheet->setCellValue("H{$invRow}", (float)$inv['discount_total']);
|
||||||
$sheet->setCellValue("I{$row}", "=E{$row}+G{$row}-H{$row}");
|
$sheet->setCellValue("I{$invRow}", "=E{$invRow}+G{$invRow}-H{$invRow}");
|
||||||
foreach (['D', 'E', 'G', 'H', 'I'] as $col) {
|
foreach (['D','E','G','H','I'] as $c) $sheet->getStyle("{$c}{$invRow}")->getNumberFormat()->setFormatCode('#,##0.000');
|
||||||
$sheet->getStyle("{$col}{$row}")->getNumberFormat()->setFormatCode('#,##0.000');
|
$invRow++;
|
||||||
}
|
|
||||||
$sheet->getStyle("A{$row}:I{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
|
||||||
$sheet->getRowDimension($row)->setRowHeight(26);
|
|
||||||
$row++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$lastDataRow = $row - 1;
|
// Totals row for individual sheet
|
||||||
|
$lastItemRow = $invRow - 1;
|
||||||
// ── DATA AREA BORDERS ──
|
$sheet->mergeCells("A{$invRow}:B{$invRow}");
|
||||||
$sheet->getStyle("A{$itemsStartRow}:I" . ($row - 1))->applyFromArray([
|
$sheet->setCellValue("A{$invRow}", 'المجموع الكلي');
|
||||||
'borders' => [
|
$sheet->setCellValue("C{$invRow}", "=SUM(C{$itemsStart}:C{$lastItemRow})");
|
||||||
'allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['argb' => 'FF' . $borderColor]],
|
$sheet->setCellValue("E{$invRow}", "=SUM(E{$itemsStart}:E{$lastItemRow})");
|
||||||
]
|
$sheet->setCellValue("G{$invRow}", "=SUM(G{$itemsStart}:G{$lastItemRow})");
|
||||||
|
$sheet->setCellValue("H{$invRow}", "=SUM(H{$itemsStart}:H{$lastItemRow})");
|
||||||
|
$sheet->setCellValue("I{$invRow}", "=SUM(I{$itemsStart}:I{$lastItemRow})");
|
||||||
|
$sheet->getStyle("G{$invRow}:I{$invRow}")->applyFromArray([
|
||||||
|
'font' => ['bold' => true, 'color' => ['argb' => 'FF' . $totalFont]],
|
||||||
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $totalBg]],
|
||||||
]);
|
]);
|
||||||
|
foreach (['C','E','G','H','I'] as $c) $sheet->getStyle("{$c}{$invRow}")->getNumberFormat()->setFormatCode('#,##0.000');
|
||||||
|
$invRow += 2;
|
||||||
|
$sheet->setCellValue("A{$invRow}", 'تم إنشاء هذا التقرير تلقائياً من منصة مُصادَق — ' . date('Y-m-d H:i'));
|
||||||
|
}
|
||||||
|
|
||||||
// ── TOTALS ROW WITH SUM FORMULAS ────────────
|
// Final Summary Row with totals
|
||||||
$sheet->mergeCells("A{$row}:B{$row}");
|
$lastSummaryRow = $row - 1;
|
||||||
$sheet->setCellValue("A{$row}", 'المجموع الكلي');
|
$summarySheet->mergeCells("A{$row}:D{$row}");
|
||||||
$sheet->setCellValue("C{$row}", "=SUM(C{$firstDataRow}:C{$lastDataRow})");
|
$summarySheet->setCellValue("A{$row}", 'المجموع الكلي النهائي');
|
||||||
$sheet->setCellValue("D{$row}", '');
|
$summarySheet->setCellValue("G{$row}", "=SUM(G{$summaryStartRow}:G{$lastSummaryRow})");
|
||||||
$sheet->setCellValue("E{$row}", "=SUM(E{$firstDataRow}:E{$lastDataRow})");
|
$summarySheet->setCellValue("I{$row}", "=SUM(I{$summaryStartRow}:I{$lastSummaryRow})");
|
||||||
$sheet->setCellValue("F{$row}", '');
|
$summarySheet->setCellValue("J{$row}", "=SUM(J{$summaryStartRow}:J{$lastSummaryRow})");
|
||||||
$sheet->setCellValue("G{$row}", "=SUM(G{$firstDataRow}:G{$lastDataRow})");
|
|
||||||
$sheet->setCellValue("H{$row}", "=SUM(H{$firstDataRow}:H{$lastDataRow})");
|
|
||||||
$sheet->setCellValue("I{$row}", "=SUM(I{$firstDataRow}:I{$lastDataRow})");
|
|
||||||
|
|
||||||
$sheet->getStyle("G{$row}:I{$row}")->applyFromArray([
|
$summarySheet->getStyle("A{$row}:J{$row}")->applyFromArray([
|
||||||
'font' => ['bold' => true, 'size' => 13, 'color' => ['argb' => 'FF' . $totalFont]],
|
'font' => ['bold' => true, 'size' => 13, 'color' => ['argb' => 'FF' . $totalFont]],
|
||||||
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $totalBg]],
|
'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'FF' . $totalBg]],
|
||||||
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
|
||||||
'borders' => [
|
]);
|
||||||
'allBorders' => ['borderStyle' => Border::BORDER_MEDIUM, 'color' => ['argb' => 'FF059669']],
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
$sheet->getStyle("A{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
|
||||||
foreach (['C', 'E', 'G', 'H', 'I'] as $col) {
|
|
||||||
$sheet->getStyle("{$col}{$row}")->getNumberFormat()->setFormatCode('#,##0.000');
|
|
||||||
}
|
|
||||||
$sheet->getRowDimension($row)->setRowHeight(36);
|
|
||||||
|
|
||||||
$row += 2;
|
foreach (['F', 'G', 'I', 'J'] as $c) {
|
||||||
|
$summarySheet->getStyle("{$c}{$summaryStartRow}:{$c}{$row}")->getNumberFormat()->setFormatCode('#,##0.000');
|
||||||
// ── FOOTER ──
|
|
||||||
$sheet->mergeCells("A{$row}:I{$row}");
|
|
||||||
$sheet->setCellValue("A{$row}", 'تم إنشاء هذا التقرير تلقائياً من منصة مُصادَق — ' . date('Y-m-d H:i'));
|
|
||||||
$sheet->getStyle("A{$row}:I{$row}")->applyFromArray([
|
|
||||||
'font' => ['italic' => true, 'size' => 9, 'color' => ['argb' => 'FF8B82B0']],
|
|
||||||
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER]
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ── Print settings ──
|
|
||||||
$sheet->getPageSetup()->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE);
|
|
||||||
$sheet->getPageSetup()->setFitToWidth(1);
|
|
||||||
|
|
||||||
$sheetIndex++;
|
|
||||||
}
|
}
|
||||||
|
$summarySheet->getStyle("H{$summaryStartRow}:H{$row}")->getNumberFormat()->setFormatCode('0%');
|
||||||
|
|
||||||
// Set first sheet as active
|
// Set first sheet as active
|
||||||
$spreadsheet->setActiveSheetIndex(0);
|
$spreadsheet->setActiveSheetIndex(0);
|
||||||
|
|||||||
@@ -19,18 +19,18 @@ try {
|
|||||||
$tenantId = $decoded['tenant_id'];
|
$tenantId = $decoded['tenant_id'];
|
||||||
$userId = $decoded['user_id'];
|
$userId = $decoded['user_id'];
|
||||||
|
|
||||||
// --- QUOTA CHECK ---
|
$allowedRoles = ['super_admin', 'admin', 'accountant', 'employee'];
|
||||||
QuotaMiddleware::checkInvoiceQuota($tenantId);
|
|
||||||
// -------------------
|
|
||||||
|
|
||||||
$db = Database::getInstance();
|
|
||||||
|
|
||||||
$allowedRoles = ['admin', 'accountant', 'employee'];
|
|
||||||
if (!in_array($decoded['role'], $allowedRoles)) {
|
if (!in_array($decoded['role'], $allowedRoles)) {
|
||||||
json_error('غير مصرح لك برفع الفواتير', 403);
|
json_error('غير مصرح لك برفع الفواتير', 403);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- QUOTA CHECK (skip for super_admin ONLY) ---
|
||||||
|
if ($decoded['role'] !== 'super_admin') {
|
||||||
|
QuotaMiddleware::checkInvoiceQuota($tenantId);
|
||||||
|
}
|
||||||
|
// -------------------
|
||||||
|
|
||||||
// 2. Validate Request
|
// 2. Validate Request
|
||||||
// استخدام $_POST للتعامل الآمن مع multipart/form-data
|
// استخدام $_POST للتعامل الآمن مع multipart/form-data
|
||||||
$companyId = $_POST['company_id'] ?? null;
|
$companyId = $_POST['company_id'] ?? null;
|
||||||
@@ -197,8 +197,10 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$savedIds[] = $invoiceId;
|
$savedIds[] = $invoiceId;
|
||||||
|
if ($decoded['role'] !== 'super_admin') {
|
||||||
QuotaMiddleware::incrementInvoiceUsage($tenantId);
|
QuotaMiddleware::incrementInvoiceUsage($tenantId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$db->commit();
|
$db->commit();
|
||||||
|
|
||||||
|
|||||||
@@ -1391,7 +1391,7 @@
|
|||||||
<button x-show="page==='invoices'" @click="showBatchUploadModal = true" class="btn btn-navy">
|
<button x-show="page==='invoices'" @click="showBatchUploadModal = true" class="btn btn-navy">
|
||||||
<span>📁</span> استيراد مجمع (Batch)
|
<span>📁</span> استيراد مجمع (Batch)
|
||||||
</button>
|
</button>
|
||||||
<button x-show="page==='invoices'" @click="exportExcel()" class="btn btn-ghost btn-sm" style="border-color: var(--green-mid); color: var(--green);">
|
<button x-show="page==='invoices'" @click="showExportExcelModal = true" class="btn btn-ghost btn-sm" style="border-color: var(--green-mid); color: var(--green);">
|
||||||
<span>📥</span> تصدير Excel
|
<span>📥</span> تصدير Excel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -2789,6 +2789,48 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ── EXCEL EXPORT MODAL ──────────────────────────── -->
|
||||||
|
<div x-show="showExportExcelModal" class="modal-backdrop" x-cloak @click.self="showExportExcelModal = false">
|
||||||
|
<div class="modal-card" style="max-width:420px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<span>📥 تصدير فواتير Excel</span>
|
||||||
|
<button @click="showExportExcelModal = false" class="modal-close-btn" style="font-size:18px;">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="p-6" style="display:flex; flex-direction:column; gap:16px;">
|
||||||
|
<p class="text-sm text-gray-500">حدد المعايير المطلوبة لتصدير الفواتير إلى ملف Excel احترافي.</p>
|
||||||
|
|
||||||
|
<!-- Company Select -->
|
||||||
|
<div>
|
||||||
|
<label class="form-label">الشركة</label>
|
||||||
|
<select x-model="exportExcelForm.company_id" class="form-input">
|
||||||
|
<option value="">جميع الشركات</option>
|
||||||
|
<template x-for="c in companies" :key="c.id">
|
||||||
|
<option :value="c.id" x-text="c.name"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Date Range -->
|
||||||
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
||||||
|
<div>
|
||||||
|
<label class="form-label">من تاريخ</label>
|
||||||
|
<input type="date" x-model="exportExcelForm.date_from" class="form-input">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="form-label">إلى تاريخ</label>
|
||||||
|
<input type="date" x-model="exportExcelForm.date_to" class="form-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:10px;">
|
||||||
|
<button @click="exportExcelWithFilters()" class="btn btn-teal w-full justify-center">
|
||||||
|
📥 تصدير ملف Excel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ── PAYMENT RECEIPT MODAL ───────────────────────── -->
|
<!-- ── PAYMENT RECEIPT MODAL ───────────────────────── -->
|
||||||
<div x-show="showPaymentModal" x-cloak class="modal-backdrop">
|
<div x-show="showPaymentModal" x-cloak class="modal-backdrop">
|
||||||
<div class="modal-box" style="max-width:440px;">
|
<div class="modal-box" style="max-width:440px;">
|
||||||
@@ -2881,7 +2923,8 @@
|
|||||||
|
|
||||||
showAddUserModal: false, showAddCompanyModal: false, showConnectModal: false,
|
showAddUserModal: false, showAddCompanyModal: false, showConnectModal: false,
|
||||||
showUploadModal: false, showViewModal: false, showCompanyStatsModal: false,
|
showUploadModal: false, showViewModal: false, showCompanyStatsModal: false,
|
||||||
showExcelModal: false, showBatchUploadModal: false, showItemsModal: false,
|
showExcelModal: false, showExportExcelModal: false, showBatchUploadModal: false, showItemsModal: false,
|
||||||
|
exportExcelForm: { company_id: '', date_from: '', date_to: '' },
|
||||||
isUploadingBatch: false, batchProgress: { total: 0, current: 0 },
|
isUploadingBatch: false, batchProgress: { total: 0, current: 0 },
|
||||||
showAddTenantModal: false, showEditTenantModal: false, showTenantStatsModal: false,
|
showAddTenantModal: false, showEditTenantModal: false, showTenantStatsModal: false,
|
||||||
acknowledgedWarnings: false, isEditingInvoice: false,
|
acknowledgedWarnings: false, isEditingInvoice: false,
|
||||||
@@ -2978,6 +3021,20 @@
|
|||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
},
|
},
|
||||||
|
exportExcelWithFilters() {
|
||||||
|
let url = '/index.php?route=v1/invoices/export-excel';
|
||||||
|
if (this.exportExcelForm.company_id) url += '&company_id=' + encodeURIComponent(this.exportExcelForm.company_id);
|
||||||
|
if (this.exportExcelForm.date_from) url += '&date_from=' + encodeURIComponent(this.exportExcelForm.date_from);
|
||||||
|
if (this.exportExcelForm.date_to) url += '&date_to=' + encodeURIComponent(this.exportExcelForm.date_to);
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url + '&token=' + encodeURIComponent(this.token());
|
||||||
|
link.download = '';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
this.showExportExcelModal = false;
|
||||||
|
},
|
||||||
//
|
//
|
||||||
getQrSrc(inv) {
|
getQrSrc(inv) {
|
||||||
if (!inv) return '';
|
if (!inv) return '';
|
||||||
|
|||||||
Reference in New Issue
Block a user