*/ final readonly class ReceptionExportProvider implements ProviderInterface { private const FARM_NAME = 'FERME SCEA LES NAUDS'; private const HEADER_FILL = 'FFCCECFF'; /** * Largeurs de colonnes (A à V). */ private const COLUMN_WIDTHS = [ 'A' => 12.0, 'B' => 11.0, 'C' => 7.0, 'D' => 14.0, 'E' => 12.0, 'F' => 22.0, 'G' => 30.0, 'H' => 30.0, 'I' => 18.0, 'J' => 8.0, 'K' => 18.0, 'L' => 14.0, 'M' => 12.0, 'N' => 16.0, 'O' => 22.0, 'P' => 11.0, 'Q' => 11.0, 'R' => 11.0, 'S' => 22.0, 'T' => 26.0, 'U' => 9.0, 'V' => 26.0, ]; public function __construct( private ReceptionRepository $receptionRepository, private LoggerInterface $logger, ) {} public function provide(Operation $operation, array $uriVariables = [], array $context = []): Response { $receptions = $this->receptionRepository->findValidatedForExport(); $spreadsheet = $this->buildSpreadsheet($receptions); $body = $this->renderXlsx($spreadsheet); $filename = sprintf('receptions_%s.xlsx', new DateTimeImmutable()->format('Y-m-d')); $response = new Response($body); $response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); $response->headers->set('Content-Disposition', sprintf('attachment; filename="%s"', $filename)); $response->headers->set('Content-Length', (string) strlen($body)); return $response; } /** * @param list $receptions */ private function buildSpreadsheet(array $receptions): Spreadsheet { $spreadsheet = new Spreadsheet(); $spreadsheet->getDefaultStyle()->getFont()->setName('Aptos Narrow')->setSize(11); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('Receptions'); $pageSetup = $sheet->getPageSetup(); $pageSetup->setPaperSize(PageSetup::PAPERSIZE_A4); $pageSetup->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); $pageSetup->setFitToWidth(1); $pageSetup->setFitToHeight(0); $pageSetup->setRowsToRepeatAtTopByStartAndEnd(1, 2); $sheet->getPageMargins()->setTop(0.4)->setBottom(0.4)->setLeft(0.3)->setRight(0.3); // Ligne 1 : titre + date $sheet->setCellValue('A1', sprintf('%s — RÉCEPTIONS TERMINÉES', self::FARM_NAME)); $sheet->mergeCells('A1:U1'); $sheet->getStyle('A1:U1')->applyFromArray([ 'font' => [ 'name' => 'Arial Black', 'size' => 16, 'bold' => true, ], 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_LEFT, 'vertical' => Alignment::VERTICAL_CENTER, ], ]); $sheet->setCellValue('V1', ExcelDate::PHPToExcel(new DateTimeImmutable())); $sheet->getStyle('V1')->getNumberFormat()->setFormatCode('dd/mm/yyyy'); $sheet->getStyle('V1')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT)->setVertical(Alignment::VERTICAL_CENTER); $sheet->getStyle('V1')->getFont()->setSize(12)->setBold(true); $sheet->getRowDimension(1)->setRowHeight(26.0); $sheet->getStyle('A1:V1')->getBorders()->getBottom()->setBorderStyle(Border::BORDER_THICK); // Ligne 2 : en-têtes $headers = [ 'A' => 'N° identification', 'B' => 'Date', 'C' => 'Heure', 'D' => 'Type réception', 'E' => 'Utilisateur', 'F' => 'Fournisseur', 'G' => 'Adresse fournisseur', 'H' => 'Adresse réception', 'I' => 'Transporteur', 'J' => 'Code trans.', 'K' => 'Chauffeur', 'L' => 'Camion', 'M' => 'Plaque', 'N' => 'Type marchandise', 'O' => 'Détail marchandise', 'P' => 'Brut (kg)', 'Q' => 'Tare (kg)', 'R' => 'Net (kg)', 'S' => 'Détail bovins', 'T' => 'Bovins par type', 'U' => 'Total bovins', 'V' => 'Granulés / bâtiments', ]; foreach ($headers as $col => $value) { $sheet->setCellValue($col.'2', $value); } $sheet->getRowDimension(2)->setRowHeight(32.0); $sheet->getStyle('A2:V2')->applyFromArray([ 'font' => ['bold' => true], 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true, ], 'fill' => [ 'fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => self::HEADER_FILL], ], ]); foreach (self::COLUMN_WIDTHS as $col => $width) { $sheet->getColumnDimension($col)->setWidth($width); } // Données $row = 3; foreach ($receptions as $reception) { try { $this->writeReceptionRow($sheet, $row, $reception); } catch (Throwable $e) { $this->logger->warning('Export réceptions : ligne ignorée suite à une erreur.', [ 'receptionId' => $reception->getId(), 'identificationNumber' => $reception->getIdentificationNumber(), 'row' => $row, 'exception' => $e, ]); } ++$row; } $lastRow = $row - 1; if ($lastRow >= 2) { $sheet->getStyle('A2:V'.$lastRow)->getBorders()->applyFromArray([ 'allBorders' => ['borderStyle' => Border::BORDER_THIN], 'outline' => ['borderStyle' => Border::BORDER_MEDIUM], ]); } $sheet->freezePane('A3'); return $spreadsheet; } private function writeReceptionRow(Worksheet $sheet, int $row, Reception $reception): void { $sheet->setCellValue('A'.$row, $reception->getIdentificationNumber() ?? ''); $date = $reception->getReceptionDate(); if (null !== $date) { $excelDate = $this->safePhpToExcel($date); if (null !== $excelDate) { $sheet->setCellValue('B'.$row, $excelDate); $sheet->getStyle('B'.$row)->getNumberFormat()->setFormatCode('dd/mm/yyyy'); } $sheet->setCellValue('C'.$row, $date->format('H:i')); } $sheet->setCellValue('D'.$row, $reception->getReceptionType()?->getLabel() ?? ''); $sheet->setCellValue('E'.$row, $reception->getUser()?->getUsername() ?? ''); $supplier = $reception->getSupplier(); $sheet->setCellValue('F'.$row, $supplier?->getName() ?? ''); $sheet->setCellValue('G'.$row, $this->formatAddresses($supplier?->getAddresses())); $sheet->setCellValue('H'.$row, $reception->getAddress()?->getFullAddress() ?? ''); $carrier = $reception->getCarrier(); $sheet->setCellValue('I'.$row, $carrier?->getName() ?? ''); $sheet->setCellValue('J'.$row, $carrier?->getCode() ?? ''); $sheet->setCellValue('K'.$row, $reception->getDriver()?->getName() ?? ''); $sheet->setCellValue('L'.$row, $reception->getTruck()?->getName() ?? ''); $sheet->setCellValue('M'.$row, $reception->getLicensePlate() ?? ''); $sheet->setCellValue('N'.$row, $reception->getMerchandiseType()?->getLabel() ?? ''); $sheet->setCellValue('O'.$row, $reception->getMerchandiseDetail() ?? ''); $gross = $this->extractWeight($reception->getWeights(), 'gross'); $tare = $this->extractWeight($reception->getWeights(), 'tare'); if (null !== $gross) { $sheet->setCellValue('P'.$row, $gross); } if (null !== $tare) { $sheet->setCellValue('Q'.$row, $tare); } if (null !== $gross && null !== $tare) { $sheet->setCellValue('R'.$row, $gross - $tare); } $sheet->getStyle('P'.$row.':R'.$row)->getNumberFormat()->setFormatCode('#,##0'); $sheet->setCellValue('S'.$row, $reception->getBovineDetail() ?? ''); [$bovinesText, $bovinesTotal] = $this->formatBovineTypes($reception); $sheet->setCellValue('T'.$row, $bovinesText); if (null !== $bovinesTotal) { $sheet->setCellValue('U'.$row, $bovinesTotal); } $sheet->setCellValue('V'.$row, $this->formatPelletBuildings($reception)); // Alignements $sheet->getStyle('A'.$row.':C'.$row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle('J'.$row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle('P'.$row.':R'.$row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); $sheet->getStyle('U'.$row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle('G'.$row.':H'.$row)->getAlignment()->setWrapText(true); $sheet->getStyle('O'.$row)->getAlignment()->setWrapText(true); $sheet->getStyle('S'.$row.':V'.$row)->getAlignment()->setWrapText(true); } /** * @return array{0: string, 1: ?int} [texte concaténé, total] */ private function formatBovineTypes(Reception $reception): array { $parts = []; $total = 0; $found = false; foreach ($reception->getBovinesTypes() as $rb) { $label = $rb->getBovineType()?->getLabel(); $qty = $rb->getQuantity(); if (null === $label && null === $qty) { continue; } $parts[] = sprintf('%s : %d', $label ?? '—', $qty ?? 0); $total += $qty ?? 0; $found = true; } return [implode(', ', $parts), $found ? $total : null]; } private function formatPelletBuildings(Reception $reception): string { $parts = []; foreach ($reception->getPelletBuildings() as $pb) { $pellet = $pb->getPelletType()?->getLabel(); $building = $pb->getBuilding()?->getLabel() ?? $pb->getBuilding()?->getCode(); if (null === $pellet && null === $building) { continue; } $parts[] = sprintf('%s (%s)', $pellet ?? '—', $building ?? '—'); } return implode(', ', $parts); } /** * @param null|iterable
$addresses */ private function formatAddresses(?iterable $addresses): string { if (null === $addresses) { return ''; } $parts = []; foreach ($addresses as $address) { $full = $address->getFullAddress(); if ('' !== $full) { $parts[] = $full; } } return implode(' ; ', $parts); } /** * @param iterable $weights */ private function extractWeight(iterable $weights, string $type): ?int { foreach ($weights as $weight) { if ($weight->getType() === $type) { return $weight->getWeight(); } } return null; } private function safePhpToExcel(?DateTimeImmutable $date): ?float { if (null === $date) { return null; } try { $value = ExcelDate::PHPToExcel($date); } catch (Throwable) { return null; } return is_float($value) ? $value : null; } private function renderXlsx(Spreadsheet $spreadsheet): string { $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); ob_start(); $writer->save('php://output'); $body = ob_get_clean(); return false !== $body ? $body : ''; } }