Kufrik/app/Model/Faktura/FakturaPdf.php

198 lines
5.6 KiB
PHP

<?php
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).
*/
final class FakturaPdf
{
private const FORMY_UHRADY = [
1 => 'převodem',
2 => 'hotově',
];
public function __construct(
private LatteFactory $latteFactory,
private string $tempDir,
) {
}
/**
* @param array<int, object> $polozky
*/
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);
}
public static function fileName(string $cislo): string
{
$safe = preg_replace('/[^\p{L}\p{N}._-]+/u', '_', trim($cislo)) ?? '';
$safe = trim($safe, '._');
return 'Faktura_' . ($safe !== '' ? $safe : 'doklad') . '.pdf';
}
/**
* @param array<int, object> $polozky
* @return array<string, mixed>
*/
private function view(object $faktura, array $polozky): array
{
$radky = [];
$celkem = 0.0;
foreach ($polozky as $polozka) {
$cena = (float) ($polozka->CENA ?? 0);
$celkem += $cena;
$radky[] = [
'nazev' => (string) ($polozka->NAZEV ?? ''),
'pocet' => self::formatQty($polozka->POCET ?? 0),
'jednotka' => (string) ($polozka->JEDNOTKA ?? ''),
'cenaJednotka' => self::formatMoney($polozka->CENA_JEDNOTKA ?? 0),
'sleva' => self::formatQty($polozka->SLEVA ?? 0) . ' %',
'cena' => self::formatMoney($cena),
];
}
$cinnosti = trim((string) ($faktura->CINNOSTI ?? ''));
return [
'cislo' => (string) ($faktura->CISLO ?? ''),
'dodavatel' => [
'nazev' => (string) ($faktura->D_PRAJM ?? ''),
'ulice' => (string) ($faktura->D_ULICE ?? ''),
'psc' => (string) ($faktura->D_PSC ?? ''),
'mesto' => (string) ($faktura->D_MESTO ?? ''),
'telefon' => (string) ($faktura->D_TELEFON ?? ''),
'email' => (string) ($faktura->D_EMAIL ?? ''),
'web' => (string) ($faktura->D_WEB ?? ''),
'ic' => (string) ($faktura->D_IC ?? ''),
'ucet' => (string) ($faktura->D_BANKA ?? ''),
],
'odberatel' => [
'nazev' => (string) ($faktura->O_PRAJM ?? ''),
'ulice' => (string) ($faktura->O_ULICE ?? ''),
'psc' => (string) ($faktura->O_PSC ?? ''),
'mesto' => (string) ($faktura->O_MESTO ?? ''),
'ic' => (string) ($faktura->O_IC ?? ''),
'dic' => self::formatDic($faktura->O_DIC ?? null),
],
'datumVystaveni' => self::formatDate($faktura->DATUM_VYSTAVENI ?? null),
'datumSplatnosti' => self::formatDate($faktura->DATUM_SPLATNOSTI ?? null),
'formaUhrady' => self::FORMY_UHRADY[(int) ($faktura->FORMA_UHRADY ?? 0)] ?? '',
'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 '<table width="100%" style="font-size: 8pt; color: #444; border-top: 0.2mm solid #ccc;">
<tr>
<td width="33%">Vytvořeno aplikací Kufřík</td>
<td width="34%" align="center">Vytištěno dne: ' . $datum . '.</td>
<td width="33%" align="right">Strana {PAGENO}/{nbpg}</td>
</tr>
</table>';
}
private function templateFile(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'faktura.latte';
}
private static function formatDate(mixed $value): string
{
if ($value instanceof \DateTimeInterface) {
return $value->format('d.m.Y');
}
if (!is_string($value) || trim($value) === '') {
return '';
}
$timestamp = strtotime($value);
return $timestamp ? date('d.m.Y', $timestamp) : $value;
}
private static function formatMoney(mixed $value): string
{
return number_format((float) $value, 2, ',', ' ') . ' Kč';
}
private static function formatQty(mixed $value): string
{
$formatted = number_format((float) $value, 2, ',', ' ');
return rtrim(rtrim($formatted, '0'), ',');
}
private static function formatDic(mixed $value): string
{
$dic = trim((string) $value);
if ($dic === '' || preg_match('/^[A-Za-z]{2}/', $dic)) {
return $dic;
}
return 'CZ' . $dic;
}
}