Steuerkram/src/Services/PdfEuerService.php
2026-03-25 00:19:33 +01:00

124 lines
4.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Steuerkram\Services;
use Mpdf\Mpdf;
class PdfEuerService
{
public function render(array $euer, array $settings): void
{
$html = $this->buildHtml($euer, $settings);
$mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'margin_top' => 20,
'margin_bottom' => 20,
'margin_left' => 20,
'margin_right' => 20,
]);
$mpdf->SetTitle('EÜR ' . $euer['year']);
$mpdf->SetAuthor($settings['firma'] ?? '');
$mpdf->WriteHTML($this->buildCss(), \Mpdf\HTMLParserMode::HEADER_CSS);
$mpdf->WriteHTML($html, \Mpdf\HTMLParserMode::HTML_BODY);
$mpdf->Output('EÜR-' . $euer['year'] . '.pdf', 'D');
}
private function buildHtml(array $euer, array $settings): string
{
$h = fn($v) => htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
$fmt = fn($v) => number_format((float)$v, 2, ',', '.') . ' €';
$firma = $h($settings['firma'] ?? '');
$year = $euer['year'];
// Build table rows
$rows = '';
$maxRows = max(count($euer['einnahmen']), count($euer['ausgaben']));
for ($i = 0; $i < $maxRows; $i++) {
$e = $euer['einnahmen'][$i] ?? null;
$a = $euer['ausgaben'][$i] ?? null;
$rows .= '<tr>';
$rows .= $e
? '<td class="konto-nr">' . $h($e['kontonummer']) . '</td><td class="konto-bez">' . $h($e['bezeichnung']) . '</td><td class="konto-val">' . $fmt($e['netto']) . '</td>'
: '<td colspan="3">&nbsp;</td>';
$rows .= '<td class="sep"></td>';
$rows .= $a
? '<td class="konto-nr">' . $h($a['kontonummer']) . '</td><td class="konto-bez">' . $h($a['bezeichnung']) . '</td><td class="konto-val">' . $fmt($a['netto']) . '</td>'
: '<td colspan="3">&nbsp;</td>';
$rows .= '</tr>';
}
$gewinnLabel = $euer['gewinn'] >= 0 ? 'Überschuss (Gewinn)' : 'Fehlbetrag (Verlust)';
$gewinnClass = $euer['gewinn'] >= 0 ? 'positive' : 'negative';
return <<<HTML
<h1>Einnahmen-Überschuss-Rechnung {$year}</h1>
<p class="subtitle">{$firma} · Geschäftsjahr {$year}</p>
<table class="euer-table">
<thead>
<tr>
<th colspan="3">Betriebseinnahmen</th>
<th class="sep"></th>
<th colspan="3">Betriebsausgaben</th>
</tr>
<tr class="sub-header">
<th>Kto.</th><th>Bezeichnung</th><th>Betrag</th>
<th class="sep"></th>
<th>Kto.</th><th>Bezeichnung</th><th>Betrag</th>
</tr>
</thead>
<tbody>
{$rows}
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="2"><strong>Summe Einnahmen</strong></td>
<td class="konto-val"><strong>{$fmt($euer['total_einnahmen'])}</strong></td>
<td class="sep"></td>
<td colspan="2"><strong>Summe Ausgaben</strong></td>
<td class="konto-val"><strong>{$fmt($euer['total_ausgaben'])}</strong></td>
</tr>
<tr class="gewinn-row {$gewinnClass}">
<td colspan="7" class="gewinn-cell">
<strong>{$gewinnLabel}:</strong>
<span class="gewinn-val">{$fmt($euer['gewinn'])}</span>
</td>
</tr>
</tfoot>
</table>
<p class="generated-note">Erstellt am {$h(date('d.m.Y H:i'))} · Steuerkram</p>
HTML;
}
private function buildCss(): string
{
return <<<CSS
body { font-family: DejaVu Sans, sans-serif; font-size: 9pt; }
h1 { font-size: 14pt; margin-bottom: 2mm; }
.subtitle { font-size: 9pt; color: #555; margin-bottom: 8mm; }
.euer-table { width: 100%; border-collapse: collapse; font-size: 8.5pt; }
.euer-table thead th { background: #1e293b; color: #fff; padding: 2mm; text-align: left; }
.euer-table .sub-header th { background: #334155; color: #e2e8f0; font-size: 7.5pt; }
.euer-table tbody tr:nth-child(even) { background: #f8f8f8; }
.euer-table tbody td { padding: 1.5mm 2mm; border-bottom: 0.2pt solid #e0e0e0; }
.konto-nr { width: 12mm; color: #555; }
.konto-bez { }
.konto-val { width: 24mm; text-align: right; font-family: DejaVu Sans Mono, monospace; }
.sep { width: 3mm; background: #fff; }
.total-row td { padding: 2mm; background: #e8ecf0; border-top: 1pt solid #555; font-size: 9pt; }
.gewinn-row td { padding: 3mm; }
.gewinn-row.positive td { background: #dcfce7; }
.gewinn-row.negative td { background: #fee2e2; }
.gewinn-cell { text-align: right; font-size: 11pt; }
.gewinn-val { margin-left: 4mm; }
.generated-note { font-size: 7pt; color: #888; margin-top: 8mm; }
CSS;
}
}