*/ private array $items = []; private int $lastDevisPage = 0; private ?\Twig\Environment $twig = null; public function __construct( private readonly KernelInterface $kernel, private readonly Devis $devis, ?\Twig\Environment $twig = null, ) { $this->twig = $twig; parent::__construct(); $items = []; foreach ($this->devis->getLines() as $line) { $items[$line->getPos()] = [ 'title' => $line->getTitle(), 'content' => $line->getDescription() ?? '', 'priceHt' => (float) $line->getPriceHt(), ]; } ksort($items); $this->items = $items; $this->SetTitle($this->enc('Devis N° '.$this->devis->getOrderNumber()->getNumOrder())); } public function Header(): void { // Sur les pages CGV importees (au-dela de la derniere page devis), pas de header if ($this->lastDevisPage > 0 && $this->PageNo() > $this->lastDevisPage) { return; } $this->SetFont('Arial', '', 10); $logo = $this->kernel->getProjectDir().'/public/logo.jpg'; if (file_exists($logo)) { $this->Image($logo, 10, 8, 53); } $formatter = new \IntlDateFormatter( 'fr_FR', \IntlDateFormatter::FULL, \IntlDateFormatter::NONE, 'Europe/Paris', \IntlDateFormatter::GREGORIAN ); $numDevisText = $this->enc('DEVIS N° '.$this->devis->getOrderNumber()->getNumOrder()); $dateText = $this->enc('Emis a Beautor, le '.$formatter->format($this->devis->getCreatedAt())); $this->Text(15, 80, $numDevisText); $this->Text(15, 85, $dateText); $this->SetFont('Arial', 'B', 12); $y = 60; $customer = $this->devis->getCustomer(); if (null !== $customer) { $name = $customer->getRaisonSociale() ?: $customer->getFullName(); $this->Text(110, $y, $this->enc($name)); if ($address = $customer->getAddress()) { $y += 5; $this->Text(110, $y, $this->enc($address)); } if ($address2 = $customer->getAddress2()) { $y += 5; $this->Text(110, $y, $this->enc($address2)); } $y += 5; $cityLine = ($customer->getZipCode() ?? '').' '.($customer->getCity() ?? ''); $this->Text(110, $y, $this->enc(trim($cityLine))); } $this->body(); } private function body(): void { $this->SetFont('Arial', 'B', 10); $this->SetXY(145, 100); $this->Cell(40, 5, $this->enc('PRIX HT'), 0, 0, 'C'); $this->Line(145, 110, 145, 220); $this->Line(185, 110, 185, 220); $this->Line(0, 100, 5, 100); $this->Line(0, 200, 5, 200); } public function generate(): void { $this->AliasNbPages(); $this->AddPage(); $this->SetFont('Arial', '', 12); $startY = 110; $this->SetY($startY); $contentBottomLimit = 220; foreach ($this->items as $item) { if ($this->GetY() + 30 > $contentBottomLimit) { $this->AddPage(); $this->body(); $this->SetY($startY); } $currentY = $this->GetY(); // Titre $this->SetX(20); $this->SetFont('Arial', 'B', 11); $this->Cell(95, 10, $this->enc($item['title']), 0, 0); // Prix HT $this->SetFont('Arial', 'B', 11); $this->SetXY(142, $currentY); $this->Cell(39, 8, number_format($item['priceHt'], 2, ',', ' ').' '.EURO, 0, 1, 'R'); // Description $this->SetFont('Arial', '', 11); $this->SetX(30); if ('' !== $item['content']) { $this->MultiCell(90, 5, $this->enc($item['content']), 0, 'L'); } $this->Ln(5); } $this->displaySummary(); $this->displaySign(); $this->appendCgv(); } /** * Genere les CGV depuis le template Twig via Dompdf puis les importe via FPDI. */ private function displaySign(): void { $this->SetAutoPageBreak(false); $this->SetXY(15, $this->GetPageHeight() - 45); $this->SetFont('Arial', '', 10); $this->SetTextColor(0, 0, 0); $this->Cell(30, 10, '{{Sign;type=signature;role=First Party}}', 0, 0, 'L'); } private function appendCgv(): void { if (null === $this->twig) { return; } try { $html = $this->twig->render('pdf/cgv.html.twig'); $dompdf = new \Dompdf\Dompdf(); $dompdf->loadHtml($html); $dompdf->setPaper('A4'); $dompdf->render(); $tmpCgv = tempnam(sys_get_temp_dir(), 'cgv_').'.pdf'; file_put_contents($tmpCgv, $dompdf->output()); $this->lastDevisPage = $this->PageNo(); $pageCount = $this->setSourceFile($tmpCgv); for ($i = 1; $i <= $pageCount; ++$i) { $tpl = $this->importPage($i); $size = $this->getTemplateSize($tpl); $this->AddPage($size['orientation'] ?? 'P', [$size['width'], $size['height']]); $this->useTemplate($tpl); // Sur la DERNIERE page CGV : champ signature DocuSeal if ($i === $pageCount) { $this->SetAutoPageBreak(false); $this->SetXY(15, $this->GetPageHeight() - 35); $this->SetFont('Arial', '', 10); $this->SetTextColor(0, 0, 0); $this->Cell(60, 20, '{{SignCGV;type=signature;role=First Party}}', 0, 0, 'L'); } } @unlink($tmpCgv); } catch (\Throwable) { } } private function displaySummary(): void { $totalHt = (float) $this->devis->getTotalHt(); $totalTva = (float) $this->devis->getTotalTva(); $totalTtc = (float) $this->devis->getTotalTtc(); $tvaEnabled = $totalTva > 0.01; $this->SetY($tvaEnabled ? -60 : -50); $this->SetFont('Arial', 'B', 12); $this->SetX(105); $this->Cell(40, 10, $this->enc('Total HT :'), 0, 0, 'R'); $this->Cell(40, 10, number_format($totalHt, 2, ',', ' ').' '.EURO, 0, 1, 'R'); if ($tvaEnabled) { $this->SetFont('Arial', '', 12); $this->SetX(105); $this->Cell(40, 10, $this->enc('TVA (20%) :'), 0, 0, 'R'); $this->Cell(40, 10, number_format($totalTva, 2, ',', ' ').' '.EURO, 0, 1, 'R'); $this->SetFont('Arial', 'B', 12); $this->SetX(105); $this->Cell(40, 10, $this->enc('Total TTC :'), 0, 0, 'R'); $this->Cell(40, 10, number_format($totalTtc, 2, ',', ' ').' '.EURO, 0, 1, 'R'); } else { $this->SetFont('Arial', '', 8); $this->SetX(105); $this->Cell(80, 5, $this->enc('TVA non applicable - art. 293 B du CGI'), 0, 1, 'R'); } } public function Footer(): void { // Sur les pages CGV importees (au-dela de la derniere page devis), pas de footer if ($this->lastDevisPage > 0 && $this->PageNo() > $this->lastDevisPage) { return; } $this->SetY(-28); $this->SetDrawColor(253, 140, 4); $this->Line(15, $this->GetY(), 195, $this->GetY()); $this->Ln(4); $this->SetFont('Arial', '', 8); $this->SetTextColor(0, 0, 0); $this->Cell(190, 4, $this->enc('42 rue de Saint-Quentin - 02800 BEAUTOR - Tél: 06 79 34 88 02'), 0, 1, 'C'); $this->Cell(190, 4, $this->enc('e-mail : contact@e-cosplay.fr - www.e-cosplay.fr'), 0, 1, 'C'); $this->Cell(190, 4, $this->enc('Association E-Cosplay - N°SIRET 943 121 517 00011 - CODE APE 9329Z - RNA W022006988'), 0, 1, 'C'); // Numero de page avec alias {nb} $this->SetFont('Arial', 'I', 7); $this->SetTextColor(150, 150, 150); $this->Cell(190, 4, $this->enc('Page ').$this->PageNo().' / {nb}', 0, 0, 'C'); } /** * Encode une chaine UTF-8 en Windows-1252 (requis par FPDF). */ private function enc(string $text): string { return mb_convert_encoding($text, 'Windows-1252', 'UTF-8'); } }