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řík |
- Vytiš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řík |
+ Vytiš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}
+
+
+
+
+
+ | Číslo |
+ Odběratel |
+ Vystaveno |
+ Splatnost |
+ Částka |
+ Zaplaceno |
+ Stav |
+
+
+
+
+ | 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 @@
-
-
-
- Kufřík
-
-
-
{if $user->isInRole('admin')}
@@ -120,6 +113,10 @@
-
-
-
{if $user->isInRole('admin')}
diff --git a/app/UI/Faktura/FakturaPresenter.php b/app/UI/Faktura/FakturaPresenter.php
index dd932b7..407f74d 100644
--- a/app/UI/Faktura/FakturaPresenter.php
+++ b/app/UI/Faktura/FakturaPresenter.php
@@ -3,6 +3,7 @@
namespace App\UI\Faktura;
use App\Model\Faktura\FakturaPdf;
+use App\Model\Faktura\SeznamFakturPdf;
use App\UI\BasePresenter;
use App\Model\Facade\FakturaFacade;
use Nette\Application\Attributes\Persistent;
@@ -22,6 +23,7 @@ class FakturaPresenter extends BasePresenter
public function __construct(
private FakturaFacade $fakturaFacade,
private FakturaPdf $fakturaPdf,
+ private SeznamFakturPdf $seznamFakturPdf,
) {
parent::__construct();
}
@@ -37,6 +39,31 @@ class FakturaPresenter extends BasePresenter
$fileName = FakturaPdf::fileName((string) $faktura->CISLO);
$disposition = $tisk ? 'inline' : 'attachment';
+ $this->sendPdf($pdf, $fileName, $disposition);
+ }
+
+ /**
+ * Tisk seznamu faktur podle filtru rok / měsíc.
+ */
+ public function actionSeznam(): void
+ {
+ $rok = $this->rok ?? (int) date('Y');
+ $mesic = $this->normalizeMesic($this->mesic);
+
+ $this->fakturaFacade->reconcileUhrady();
+ $faktury = $this->fakturaFacade->getFiltered($rok, $mesic);
+ foreach ($faktury as $faktura) {
+ $celkem = (float) ($faktura->CASTKA_CELKEM ?? 0);
+ $zaplaceno = (float) ($faktura->ZAPLACENO ?? 0);
+ $faktura->STAV_UHRADY = FakturaFacade::stavZCastek($celkem, $zaplaceno);
+ }
+
+ $pdf = $this->seznamFakturPdf->render($faktury, $rok, $mesic, autoPrint: true);
+ $this->sendPdf($pdf, SeznamFakturPdf::fileName($rok, $mesic), 'inline');
+ }
+
+ private function sendPdf(string $pdf, string $fileName, string $disposition): void
+ {
$this->sendResponse(new CallbackResponse(function ($httpRequest, $httpResponse) use ($pdf, $fileName, $disposition): void {
$httpResponse->setContentType('application/pdf');
$httpResponse->setHeader(
diff --git a/config/services.neon b/config/services.neon
index 2320c5c..71c9630 100644
--- a/config/services.neon
+++ b/config/services.neon
@@ -18,7 +18,9 @@ services:
- App\Model\Sdf\SdfCompactExporter(%sdfExportScript%, %sdfPassword%)
- App\Model\Sdf\SdfKufrikImporter
- - App\Model\Faktura\FakturaPdf(tempDir: %tempDir%)
+ - App\Model\Faktura\PdfSestava(tempDir: %tempDir%)
+ - App\Model\Faktura\FakturaPdf
+ - App\Model\Faktura\SeznamFakturPdf
# 1. Factory pro klientskou databázi
kufrikConnectionFactory: