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); $filename = 'Rechnung-' . ($invoice['rechnungs_nr'] ?? 'Entwurf') . '.pdf'; $mpdf->Output($filename, 'D'); } 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 = 'Logo'; } } $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 .= ' ' . nl2br($h($line['bezeichnung'])) . ' '; } else { $posRows .= ' ' . $h($line['position']) . ' ' . nl2br($h($line['bezeichnung'])) . ' ' . $fmtNum($line['menge']) . ' ' . $h($line['einheit']) . ' ' . $h(number_format((float)$line['ust_satz'], 0)) . ' % ' . $fmt($line['einzelpreis']) . ' ' . $fmt($line['betrag_netto']) . ' '; } } $payDays = $inv['payment_days'] ?? 14; // Empfänger-Land nur wenn nicht DE $land = ($inv['land'] ?? 'DE') !== 'DE' ? '
' . $h($inv['land']) : ''; return <<
{$logoHtml}
{$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'])}
{$posRows}
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 << {$h($settings['firma'] ?? '')}
Inh. {$h($settings['inhaber'] ?? '')}
{$h($settings['strasse'] ?? '')}
{$h($settings['plz'] ?? '')} {$h($settings['ort'] ?? '')}
USt-ID: {$h($settings['ust_id'] ?? '')} Tel: {$h($settings['telefon'] ?? '')}
E-Mail: {$h($settings['email'] ?? '')}
Web: {$h($settings['website'] ?? '')} {$h($settings['bank_name'] ?? '')}
IBAN: {$h($settings['iban'] ?? '')}
BIC: {$h($settings['bic'] ?? '')} Seite {PAGENO} von {nbpg} HTML; } private function buildCss(): string { return <<