From 6a39a66e7314395cc5238894936a470fed92c589 Mon Sep 17 00:00:00 2001 From: Kowalski Date: Sat, 31 Oct 2026 09:41:08 +0100 Subject: [PATCH] GUI + Tisk faktur --- app/Model/Faktura/FakturaPdf.php | 74 ++------------- app/Model/Faktura/PdfSestava.php | 95 +++++++++++++++++++ app/Model/Faktura/SeznamFakturPdf.php | 129 ++++++++++++++++++++++++++ app/Model/Faktura/seznam.latte | 68 ++++++++++++++ app/UI/@layout.latte | 49 ++-------- app/UI/Faktura/FakturaPresenter.php | 27 ++++++ config/services.neon | 4 +- 7 files changed, 335 insertions(+), 111 deletions(-) create mode 100644 app/Model/Faktura/PdfSestava.php create mode 100644 app/Model/Faktura/SeznamFakturPdf.php create mode 100644 app/Model/Faktura/seznam.latte diff --git a/app/Model/Faktura/FakturaPdf.php b/app/Model/Faktura/FakturaPdf.php index 240f4cb..2dfc048 100644 --- a/app/Model/Faktura/FakturaPdf.php +++ b/app/Model/Faktura/FakturaPdf.php @@ -4,13 +4,6 @@ declare(strict_types=1); namespace App\Model\Faktura; -use Mpdf\Config\ConfigVariables; -use Mpdf\Config\FontVariables; -use Mpdf\Mpdf; -use Mpdf\Output\Destination; -use Nette\Bridges\ApplicationLatte\LatteFactory; -use Nette\Utils\FileSystem; - /** * Sestava faktury podle rptFaktura3 (PDF, tisk). */ @@ -22,8 +15,7 @@ final class FakturaPdf ]; public function __construct( - private LatteFactory $latteFactory, - private string $tempDir, + private PdfSestava $pdfSestava, ) { } @@ -33,18 +25,13 @@ final class FakturaPdf public function render(object $faktura, array $polozky, bool $autoPrint = false): string { $view = $this->view($faktura, $polozky); - $html = $this->latteFactory->create()->renderToString($this->templateFile(), $view); - $mpdf = $this->createMpdf(); - $mpdf->SetHTMLFooter($this->footer((string) $view['vytisteno'])); - $mpdf->SetTitle('Faktura ' . (string) ($faktura->CISLO ?? '')); - $mpdf->SetAuthor('Kufřík'); - if ($autoPrint) { - $mpdf->SetJS('this.print();'); - } - $mpdf->WriteHTML($html); - - return $mpdf->Output('', Destination::STRING_RETURN); + return $this->pdfSestava->render( + $this->templateFile(), + $view, + 'Faktura ' . (string) ($faktura->CISLO ?? ''), + $autoPrint, + ); } public static function fileName(string $cislo): string @@ -105,56 +92,9 @@ final class FakturaPdf 'polozky' => $radky, 'celkem' => 'Celkem: ' . self::formatMoney($celkem), 'cinnosti' => $cinnosti === '' ? [] : (preg_split("/\R/u", $cinnosti) ?: []), - 'vytisteno' => (new \DateTimeImmutable('today'))->format('d.m.Y'), ]; } - private function createMpdf(): Mpdf - { - $tempDir = $this->tempDir . DIRECTORY_SEPARATOR . 'mpdf'; - FileSystem::createDir($tempDir); - - $fontDir = dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'fonts'; - $config = (new ConfigVariables())->getDefaults(); - $fontData = (new FontVariables())->getDefaults(); - - return new Mpdf([ - 'mode' => 'utf-8', - 'format' => 'A4', - 'margin_left' => 18, - 'margin_right' => 16, - 'margin_top' => 16, - 'margin_bottom' => 18, - 'margin_footer' => 8, - 'default_font' => 'sourcesans3', - 'default_font_size' => 9, - 'tempDir' => $tempDir, - 'shrink_tables_to_fit' => 0, - 'fontDir' => array_merge($config['fontDir'], [$fontDir]), - 'fontdata' => $fontData['fontdata'] + [ - 'sourcesans3' => [ - 'R' => 'SourceSans3-Regular.ttf', - 'B' => 'SourceSans3-Bold.ttf', - 'I' => 'SourceSans3-It.ttf', - 'BI' => 'SourceSans3-BoldIt.ttf', - ], - ], - ]); - } - - private function footer(string $vytisteno): string - { - $datum = htmlspecialchars($vytisteno, ENT_QUOTES, 'UTF-8'); - - return ' - - - - - -
Vytvořeno aplikací KufříkVytištěno dne: ' . $datum . '.Strana {PAGENO}/{nbpg}
'; - } - private function templateFile(): string { return __DIR__ . DIRECTORY_SEPARATOR . 'faktura.latte'; diff --git a/app/Model/Faktura/PdfSestava.php b/app/Model/Faktura/PdfSestava.php new file mode 100644 index 0000000..3dde983 --- /dev/null +++ b/app/Model/Faktura/PdfSestava.php @@ -0,0 +1,95 @@ + $params + */ + public function render( + string $templateFile, + array $params, + string $title, + bool $autoPrint = false, + string $orientation = 'P', + ): string { + $html = $this->latteFactory->create()->renderToString($templateFile, $params); + + $mpdf = $this->create($orientation === 'L'); + $mpdf->SetHTMLFooter($this->footer()); + $mpdf->SetTitle($title); + $mpdf->SetAuthor('Kufřík'); + if ($autoPrint) { + $mpdf->SetJS('this.print();'); + } + $mpdf->WriteHTML($html); + + return $mpdf->Output('', Destination::STRING_RETURN); + } + + private function create(bool $landscape): Mpdf + { + $tempDir = $this->tempDir . DIRECTORY_SEPARATOR . 'mpdf'; + FileSystem::createDir($tempDir); + + $fontDir = dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'fonts'; + $config = (new ConfigVariables())->getDefaults(); + $fontData = (new FontVariables())->getDefaults(); + + return new Mpdf([ + 'mode' => 'utf-8', + 'format' => 'A4', + 'orientation' => $landscape ? 'L' : 'P', + 'margin_left' => $landscape ? 14 : 18, + 'margin_right' => $landscape ? 14 : 16, + 'margin_top' => $landscape ? 14 : 16, + 'margin_bottom' => $landscape ? 16 : 18, + 'margin_footer' => 8, + 'default_font' => 'sourcesans3', + 'default_font_size' => 9, + 'tempDir' => $tempDir, + 'shrink_tables_to_fit' => 0, + 'fontDir' => array_merge($config['fontDir'], [$fontDir]), + 'fontdata' => $fontData['fontdata'] + [ + 'sourcesans3' => [ + 'R' => 'SourceSans3-Regular.ttf', + 'B' => 'SourceSans3-Bold.ttf', + 'I' => 'SourceSans3-It.ttf', + 'BI' => 'SourceSans3-BoldIt.ttf', + ], + ], + ]); + } + + private function footer(): string + { + $datum = htmlspecialchars((new \DateTimeImmutable('today'))->format('d.m.Y'), ENT_QUOTES, 'UTF-8'); + + return ' + + + + + +
Vytvořeno aplikací KufříkVytištěno dne: ' . $datum . '.Strana {PAGENO}/{nbpg}
'; + } +} diff --git a/app/Model/Faktura/SeznamFakturPdf.php b/app/Model/Faktura/SeznamFakturPdf.php new file mode 100644 index 0000000..191c05c --- /dev/null +++ b/app/Model/Faktura/SeznamFakturPdf.php @@ -0,0 +1,129 @@ + 'leden', + 2 => 'únor', + 3 => 'březen', + 4 => 'duben', + 5 => 'květen', + 6 => 'červen', + 7 => 'červenec', + 8 => 'srpen', + 9 => 'září', + 10 => 'říjen', + 11 => 'listopad', + 12 => 'prosinec', + ]; + + public function __construct( + private PdfSestava $pdfSestava, + ) { + } + + /** + * @param array $faktury + */ + public function render(array $faktury, int $rok, string $mesic, bool $autoPrint = false): string + { + $mesicCislo = (int) $mesic; + $obdobi = ($mesicCislo >= 1 && $mesicCislo <= 12) + ? self::MESICE[$mesicCislo] . ' ' . $rok + : 'rok ' . $rok; + + $radky = []; + $celkem = 0.0; + $zaplaceno = 0.0; + $dnes = new \DateTimeImmutable('today'); + + foreach ($faktury as $faktura) { + $castka = (float) ($faktura->CASTKA_CELKEM ?? 0); + $uhrazeno = (float) ($faktura->ZAPLACENO ?? 0); + $celkem += $castka; + $zaplaceno += $uhrazeno; + + $stav = (string) ($faktura->STAV_UHRADY ?? FakturaFacade::stavZCastek($castka, $uhrazeno)); + $splatnost = self::asDate($faktura->DATUM_SPLATNOSTI ?? null); + $poSplatnosti = $stav !== FakturaFacade::STAV_ZAPLACENO + && $splatnost !== null + && $splatnost->format('Y-m-d') < $dnes->format('Y-m-d'); + + $radky[] = [ + 'cislo' => (string) ($faktura->CISLO ?? ''), + 'odberatel' => trim((string) ($faktura->O_PRAJM ?? '')) !== '' ? (string) $faktura->O_PRAJM : '—', + 'vystaveno' => self::formatDate(self::asDate($faktura->DATUM_VYSTAVENI ?? null)), + 'splatnost' => self::formatDate($splatnost), + 'poSplatnosti' => $poSplatnosti, + 'castka' => self::formatMoney($castka), + 'zaplaceno' => self::formatMoney($uhrazeno), + 'stav' => match ($stav) { + FakturaFacade::STAV_ZAPLACENO => 'Zaplaceno', + FakturaFacade::STAV_CASTECNE => 'Částečně', + default => 'Neuhrazeno', + }, + 'sudy' => count($radky) % 2 === 1, + ]; + } + + return $this->pdfSestava->render( + __DIR__ . DIRECTORY_SEPARATOR . 'seznam.latte', + [ + 'obdobi' => $obdobi, + 'radky' => $radky, + 'pocet' => count($radky), + 'celkem' => self::formatMoney($celkem), + 'zaplaceno' => self::formatMoney($zaplaceno), + 'zbyva' => self::formatMoney(max(0, round($celkem - $zaplaceno, 2))), + ], + 'Seznam faktur — ' . $obdobi, + $autoPrint, + 'L', + ); + } + + public static function fileName(int $rok, string $mesic): string + { + $mesicCislo = (int) $mesic; + $suffix = ($mesicCislo >= 1 && $mesicCislo <= 12) + ? $rok . '_' . sprintf('%02d', $mesicCislo) + : (string) $rok; + + return 'Seznam_faktur_' . $suffix . '.pdf'; + } + + private static function asDate(mixed $value): ?\DateTimeImmutable + { + if ($value instanceof \DateTimeImmutable) { + return $value; + } + if ($value instanceof \DateTimeInterface) { + return \DateTimeImmutable::createFromInterface($value); + } + if (!is_string($value) || trim($value) === '') { + return null; + } + $timestamp = strtotime($value); + + return $timestamp ? (new \DateTimeImmutable())->setTimestamp($timestamp) : null; + } + + private static function formatDate(?\DateTimeInterface $value): string + { + return $value ? $value->format('d.m.Y') : ''; + } + + private static function formatMoney(float $value): string + { + return number_format($value, 2, ',', ' ') . ' Kč'; + } +} diff --git a/app/Model/Faktura/seznam.latte b/app/Model/Faktura/seznam.latte new file mode 100644 index 0000000..589ca60 --- /dev/null +++ b/app/Model/Faktura/seznam.latte @@ -0,0 +1,68 @@ + + + + + + + + +

Seznam vystavených faktur

+

Období: {$obdobi}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
ČísloOdběratelVystavenoSplatnostČástkaZaplacenoStav
V tomto období nejsou žádné faktury.
{$radek['cislo']}{$radek['odberatel']}{$radek['vystaveno']}{$radek['splatnost']}{$radek['castka']}{$radek['zaplaceno']}{$radek['stav']}
+ + + + + + + + +
Počet faktur: {$pocet}Celkem: {$celkem}Zaplaceno: {$zaplaceno}Zbývá uhradit: {$zbyva}
+ + + diff --git a/app/UI/@layout.latte b/app/UI/@layout.latte index c848b56..f6f7b7c 100644 --- a/app/UI/@layout.latte +++ b/app/UI/@layout.latte @@ -63,13 +63,6 @@ - - - {if $user->isInRole('admin')}