generate($invoice, $lines, $settings);
}
public function render(array $invoice, array $lines, array $settings): void
{
$pdfContent = $this->generate($invoice, $lines, $settings);
$filename = 'Rechnung-' . ($invoice['rechnungs_nr'] ?? 'Entwurf') . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($pdfContent));
echo $pdfContent;
exit;
}
private function generate(array $invoice, array $lines, array $settings): string
{
$html = $this->buildHtml($invoice, $lines, $settings);
$fontDir = dirname(__DIR__) . '/fonts';
$mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'margin_top' => 15,
'margin_bottom' => 25,
'margin_left' => 25, // DIN 5008: 25mm links
'margin_right' => 15, // DIN 5008: 15mm rechts
'fontDir' => [$fontDir],
'fontdata' => [
'opensans' => [
'R' => 'OpenSans-Regular.ttf',
'B' => 'OpenSans-Bold.ttf',
'I' => 'OpenSans-Italic.ttf',
'BI' => 'OpenSans-BoldItalic.ttf',
],
],
'default_font' => 'opensans',
]);
$mpdf->SetTitle('Rechnung ' . ($invoice['rechnungs_nr'] ?? 'Entwurf'));
$mpdf->SetAuthor($settings['firma'] ?? '');
$mpdf->SetHTMLFooter($this->buildFooter($settings));
$mpdf->WriteHTML($this->buildCss(), \Mpdf\HTMLParserMode::HEADER_CSS);
$mpdf->WriteHTML($html, \Mpdf\HTMLParserMode::HTML_BODY);
$pdfContent = $mpdf->Output('', 'S');
// ZUGFeRD EN16931-XML einbetten (E-Rechnung)
$zugferd = new ZugferdService();
return $zugferd->attachToPdf($pdfContent, $invoice, $lines, $settings);
}
private function buildHtml(array $inv, array $lines, array $settings): string
{
$h = fn($v) => htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
$fmt = fn($v) => number_format((float)$v, 2, ',', '.') . ' €';
$fmtNum = fn($v) => number_format((float)$v, 1, ',', '.');
$fmtDate = fn($v) => $v ? date('d.m.Y', strtotime($v)) : '–';
// Kundenname
$customerName = !empty($inv['firma'])
? $inv['firma']
: trim(($inv['anrede'] ?? '') . ' ' . ($inv['vorname'] ?? '') . ' ' . $inv['nachname']);
// Logo
$logoHtml = '';
if (!empty($settings['logo_path'])) {
$logoPath = dirname(__DIR__, 2) . '/public/' . $settings['logo_path'];
if (file_exists($logoPath)) {
$maxW = max(50, (int)($settings['logo_max_width'] ?? 600));
$maxH = max(20, (int)($settings['logo_max_height'] ?? 100));
$logoHtml = '';
}
}
$logoPosition = $settings['logo_position'] ?? 'center';
$logoMarginSide = max(0, (int)($settings['logo_margin_side'] ?? 75));
$logoMarginTop = max(0, (int)($settings['logo_margin_top'] ?? 6));
if ($logoPosition === 'left') {
$logoWrapStyle = "text-align:left;margin-left:{$logoMarginSide}px;margin-top:{$logoMarginTop}px";
} elseif ($logoPosition === 'right') {
$logoWrapStyle = "text-align:right;margin-right:{$logoMarginSide}px;margin-top:{$logoMarginTop}px";
} else {
$logoWrapStyle = 'text-align:center';
}
$lz = $inv['leistungszeitraum'] ?? '';
// Fälligkeitsdatum
$fälligText = !empty($inv['faellig_datum'])
? ' Fällig am: ' . $fmtDate($inv['faellig_datum']) . ''
: '';
// Positionszeilen
$posRows = '';
foreach ($lines as $line) {
if (($line['type'] ?? 'position') === 'heading') {
$posRows .= '
|
{$h($settings['firma'] ?? '')} · {$h($settings['strasse'] ?? '')} · {$h($settings['plz'] ?? '')} {$h($settings['ort'] ?? '')}
{$h($customerName)}
{$h($inv['strasse'])} {$h($inv['plz'])} {$h($inv['ort'])}{$land} |
Rechnung |
| Betreff | Rechnungsnummer | Kundennummer | Leistungszeitraum | Datum |
|---|---|---|---|---|
| {$h($inv['betreff'])} | {$h($inv['rechnungs_nr'] ?? 'Entwurf')} | {$h($inv['kundennummer'])} | {$h($lz ?: '–')} | {$fmtDate($inv['rechnungs_datum'])} |
| Pos. | Bezeichnung | Menge | Einheit | USt. | Einzelpreis | Gesamt |
|---|
| Nettobetrag: | {$fmt($inv['betrag_netto'])} |
| zzgl. Umsatzsteuer: | {$fmt($inv['betrag_ust'])} |
| Rechnungsbetrag: | {$fmt($inv['betrag_brutto'])} |
Bitte überweisen Sie den Rechnungsbetrag innerhalb {$h($payDays)} Tagen auf das unten genannte Konto.{$fälligText}
HTML; } private function buildFooter(array $settings): string { $h = fn($v) => htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); return <<