198 lines
5.6 KiB
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;
|
|
}
|
|
}
|