Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

274 Zeilen
9.7KB

  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Services;
  4. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  5. use PhpOffice\PhpSpreadsheet\Style\Fill;
  6. use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  7. use Dompdf\Dompdf;
  8. use Dompdf\Options;
  9. class ExportService
  10. {
  11. /**
  12. * Build a ZIP archive containing XLSX + PDF for each territory.
  13. *
  14. * @param list<array<string,mixed>> $territories
  15. * @param list<array<string,mixed>> $allHouseholds All household rows (with territory_id)
  16. * @return string Raw ZIP binary content
  17. */
  18. public function buildZip(array $territories, array $allHouseholds): string
  19. {
  20. $byTerritory = [];
  21. foreach ($allHouseholds as $h) {
  22. $tid = (int) $h['territory_id'];
  23. $byTerritory[$tid][] = $h;
  24. }
  25. $zipFile = tempnam(sys_get_temp_dir(), 'territory_export_');
  26. $zip = new \ZipArchive();
  27. $zip->open($zipFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
  28. foreach ($territories as $territory) {
  29. $tid = (int) $territory['id'];
  30. $households = $byTerritory[$tid] ?? [];
  31. $slug = $this->slug($territory['name']);
  32. $xlsx = $this->buildXlsx($territory, $households);
  33. $zip->addFromString("{$slug}.xlsx", $xlsx);
  34. $pdf = $this->buildPdf($territory, $households);
  35. $zip->addFromString("{$slug}.pdf", $pdf);
  36. }
  37. $zip->close();
  38. $content = (string) file_get_contents($zipFile);
  39. unlink($zipFile);
  40. return $content;
  41. }
  42. /** @param list<array<string,mixed>> $households */
  43. private function buildXlsx(array $territory, array $households): string
  44. {
  45. $spreadsheet = new Spreadsheet();
  46. $spreadsheet->removeSheetByIndex(0);
  47. $byStreet = $this->groupByStreet($households);
  48. if (empty($byStreet)) {
  49. $sheet = $spreadsheet->createSheet();
  50. $sheet->setTitle('No Data');
  51. $sheet->setCellValue('A1', 'No households found for this territory.');
  52. }
  53. foreach ($byStreet as $streetName => $streetHouseholds) {
  54. $sheet = $spreadsheet->createSheet();
  55. $sheet->setTitle(substr((string) $streetName, 0, 31));
  56. [$even, $odd] = $this->splitEvenOdd($streetHouseholds);
  57. $headerStyle = [
  58. 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
  59. 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '1D7A6D']],
  60. 'borders' => ['bottom' => ['borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN]],
  61. ];
  62. $evenHeaders = ['#', 'Address (Even)', 'Bus.', 'DNC', 'DNC Date'];
  63. $oddHeaders = ['#', 'Address (Odd)', 'Bus.', 'DNC', 'DNC Date'];
  64. foreach ($evenHeaders as $col => $header) {
  65. $cell = $this->colLetter($col + 1) . '1';
  66. $sheet->setCellValue($cell, $header);
  67. $sheet->getStyle($cell)->applyFromArray($headerStyle);
  68. }
  69. $sheet->setCellValue('G1', '');
  70. foreach ($oddHeaders as $col => $header) {
  71. $cell = $this->colLetter($col + 8) . '1';
  72. $sheet->setCellValue($cell, $header);
  73. $sheet->getStyle($cell)->applyFromArray($headerStyle);
  74. }
  75. $row = 2;
  76. foreach ($even as $h) {
  77. $sheet->setCellValue("A{$row}", $h['street_number'] ?? '');
  78. $sheet->setCellValue("B{$row}", $h['address'] ?? '');
  79. $sheet->setCellValue("C{$row}", $h['is_business'] ? 'Yes' : '');
  80. $sheet->setCellValue("D{$row}", $h['do_not_call'] ? 'Yes' : '');
  81. $sheet->setCellValue("E{$row}", $h['do_not_call_date'] ?? '');
  82. $row++;
  83. }
  84. $row = 2;
  85. foreach ($odd as $h) {
  86. $sheet->setCellValue("H{$row}", $h['street_number'] ?? '');
  87. $sheet->setCellValue("I{$row}", $h['address'] ?? '');
  88. $sheet->setCellValue("J{$row}", $h['is_business'] ? 'Yes' : '');
  89. $sheet->setCellValue("K{$row}", $h['do_not_call'] ? 'Yes' : '');
  90. $sheet->setCellValue("L{$row}", $h['do_not_call_date'] ?? '');
  91. $row++;
  92. }
  93. foreach (range('A', 'L') as $col) {
  94. $sheet->getColumnDimension($col)->setAutoSize(true);
  95. }
  96. }
  97. $writer = new Xlsx($spreadsheet);
  98. $tmpFile = tempnam(sys_get_temp_dir(), 'territory_xlsx_');
  99. $writer->save($tmpFile);
  100. $content = (string) file_get_contents($tmpFile);
  101. unlink($tmpFile);
  102. return $content;
  103. }
  104. /** @param list<array<string,mixed>> $households */
  105. private function buildPdf(array $territory, array $households): string
  106. {
  107. $options = new Options();
  108. $options->set('defaultFont', 'DejaVu Sans');
  109. $options->set('isRemoteEnabled', false);
  110. $dompdf = new Dompdf($options);
  111. $dompdf->loadHtml($this->buildPdfHtml($territory, $households));
  112. $dompdf->setPaper('A4', 'landscape');
  113. $dompdf->render();
  114. return (string) $dompdf->output();
  115. }
  116. /** @param list<array<string,mixed>> $households */
  117. private function buildPdfHtml(array $territory, array $households): string
  118. {
  119. $title = htmlspecialchars($territory['name'], ENT_QUOTES, 'UTF-8');
  120. $byStreet = $this->groupByStreet($households);
  121. $html = '<!DOCTYPE html><html><head><meta charset="UTF-8">
  122. <style>
  123. body { font-family: DejaVu Sans, sans-serif; font-size: 9pt; margin: 10mm; }
  124. h1 { font-size: 14pt; margin-bottom: 4px; }
  125. h2 { font-size: 11pt; margin: 12px 0 4px; border-bottom: 1px solid #555; padding-bottom: 2px; }
  126. table { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
  127. th { background: #1d7a6d; color: #fff; padding: 4px 6px; font-size: 8pt; text-align: left; }
  128. td { padding: 3px 6px; border-bottom: 1px solid #ddd; font-size: 8pt; vertical-align: top; }
  129. .dnc { color: #c0392b; font-weight: bold; }
  130. .sep { width: 6px; }
  131. </style>
  132. </head><body>';
  133. $html .= "<h1>Territory: {$title}</h1>";
  134. if (empty($byStreet)) {
  135. $html .= '<p>No households found.</p>';
  136. }
  137. foreach ($byStreet as $streetName => $streetHouseholds) {
  138. $escapedStreet = htmlspecialchars((string) $streetName, ENT_QUOTES, 'UTF-8');
  139. [$even, $odd] = $this->splitEvenOdd($streetHouseholds);
  140. $html .= "<h2>{$escapedStreet}</h2>";
  141. $html .= '<table><tr>
  142. <th>#</th><th>Address (Even)</th><th>DNC</th>
  143. <th class="sep"></th>
  144. <th>#</th><th>Address (Odd)</th><th>DNC</th>
  145. </tr>';
  146. $max = max(count($even), count($odd), 1);
  147. $evenVals = array_values($even);
  148. $oddVals = array_values($odd);
  149. for ($i = 0; $i < $max; $i++) {
  150. $html .= '<tr>';
  151. if (isset($evenVals[$i])) {
  152. $h = $evenVals[$i];
  153. $addr = htmlspecialchars($h['address'] ?? '', ENT_QUOTES, 'UTF-8');
  154. $dnc = $h['do_not_call'] ? '<span class="dnc">DNC</span>' : '';
  155. $html .= "<td>{$h['street_number']}</td><td>{$addr}</td><td>{$dnc}</td>";
  156. } else {
  157. $html .= '<td></td><td></td><td></td>';
  158. }
  159. $html .= '<td class="sep"></td>';
  160. if (isset($oddVals[$i])) {
  161. $h = $oddVals[$i];
  162. $addr = htmlspecialchars($h['address'] ?? '', ENT_QUOTES, 'UTF-8');
  163. $dnc = $h['do_not_call'] ? '<span class="dnc">DNC</span>' : '';
  164. $html .= "<td>{$h['street_number']}</td><td>{$addr}</td><td>{$dnc}</td>";
  165. } else {
  166. $html .= '<td></td><td></td><td></td>';
  167. }
  168. $html .= '</tr>';
  169. }
  170. $html .= '</table>';
  171. }
  172. $html .= '</body></html>';
  173. return $html;
  174. }
  175. /**
  176. * @param list<array<string,mixed>> $households
  177. * @return array<string, list<array<string,mixed>>>
  178. */
  179. private function groupByStreet(array $households): array
  180. {
  181. $byStreet = [];
  182. foreach ($households as $h) {
  183. $street = trim((string) ($h['street_name'] ?? ''));
  184. if ($street === '') {
  185. $street = 'Unknown Street';
  186. }
  187. $byStreet[$street][] = $h;
  188. }
  189. ksort($byStreet);
  190. return $byStreet;
  191. }
  192. /**
  193. * @param list<array<string,mixed>> $households
  194. * @return array{list<array<string,mixed>>, list<array<string,mixed>>}
  195. */
  196. private function splitEvenOdd(array $households): array
  197. {
  198. $even = [];
  199. $odd = [];
  200. foreach ($households as $h) {
  201. $num = (int) ($h['street_number'] ?? 0);
  202. if ($num % 2 === 0) {
  203. $even[] = $h;
  204. } else {
  205. $odd[] = $h;
  206. }
  207. }
  208. usort($even, fn($a, $b) => (int) ($a['street_number'] ?? 0) <=> (int) ($b['street_number'] ?? 0));
  209. usort($odd, fn($a, $b) => (int) ($a['street_number'] ?? 0) <=> (int) ($b['street_number'] ?? 0));
  210. return [$even, $odd];
  211. }
  212. private function colLetter(int $n): string
  213. {
  214. $letter = '';
  215. while ($n > 0) {
  216. $n--;
  217. $letter = chr(65 + ($n % 26)) . $letter;
  218. $n = (int) ($n / 26);
  219. }
  220. return $letter;
  221. }
  222. private function slug(string $name): string
  223. {
  224. $slug = strtolower(preg_replace('/[^a-zA-Z0-9]+/', '_', $name) ?? $name);
  225. return trim($slug, '_') ?: 'territory';
  226. }
  227. }

Powered by TurnKey Linux.