false, 'error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ], JSON_UNESCAPED_UNICODE); exit; }); set_error_handler(function($severity, $message, $file, $line){ throw new ErrorException($message, 0, $severity, $file, $line); }); require_once "../config/config.php"; global $pdo; $config = require __DIR__ . '/../config/invoice.php'; require_once __DIR__ . '/../vendor/autoload.php'; use Dompdf\Dompdf; use Dompdf\Options; function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function eur($n){ return number_format((int)$n, 0, ',', '.') . " €"; } function imgToDataUri(string $path): string { if (!file_exists($path)) { throw new RuntimeException("Logo nicht gefunden: $path"); } if (!is_readable($path)) { throw new RuntimeException("Logo nicht lesbar (Rechte): $path"); } $data = file_get_contents($path); if ($data === false) { throw new RuntimeException("Logo konnte nicht gelesen werden: $path"); } $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $mime = match($ext) { 'png' => 'image/png', 'jpg', 'jpeg' => 'image/jpeg', 'svg' => 'image/svg+xml', default => throw new RuntimeException("Logo-Format nicht unterstützt: .$ext (bitte png/jpg/svg)") }; return "data:$mime;base64," . base64_encode($data); } try { $id = (int)($_GET['id'] ?? 0); if ($id <= 0) throw new RuntimeException('id fehlt'); // Load invoice + items $st = $pdo->prepare("SELECT * FROM invoices WHERE id=:id"); $st->execute([':id'=>$id]); $inv = $st->fetch(PDO::FETCH_ASSOC); if (!$inv) throw new RuntimeException('Rechnung nicht gefunden'); $it = $pdo->prepare("SELECT * FROM invoice_items WHERE invoice_id=:id ORDER BY pos ASC, id ASC"); $it->execute([':id'=>$id]); $items = $it->fetchAll(PDO::FETCH_ASSOC); // Ensure pdf dir $pdfDir = $config['pdf_dir']; if (!is_dir($pdfDir)) { if (!mkdir($pdfDir, 0775, true)) throw new RuntimeException('pdf_dir kann nicht erstellt werden'); } $issuer = $config['issuer']; $pay = $config['payment']; $branding = $config['branding'] ?? []; $accent = $branding['accent'] ?? '#111'; $logoData = imgToDataUri($branding['logo_path'] ?? null); $logoHtml = ''; if ($logoData) { $logoHtml = 'ANJUMA Logo'; } $html = '
'.h($issuer['name']).'
'.h($issuer['street']).' • '.h($issuer['zip']).' '.h($issuer['city']).'
'.$logoHtml.'
Rechnung
Rechnungsnummer'.h($inv['invoice_no']).'
Rechnungsdatum'.h(date('d.m.Y', strtotime($inv['invoice_date']))).'
Leistungsdatum'.h(date('d.m.Y', strtotime($inv['service_date']))).'
Fällig am'.h(date('d.m.Y', strtotime($inv['due_date']))).'
Rechnung an
'.h($inv['customer_name']).'
'.($inv['customer_street'] ? h($inv['customer_street']).'
' : '').' '.h($inv['customer_zip']).' '.h($inv['customer_city']).'
'.h($inv['customer_country']).'
Aussteller
'.h($issuer['name']).'
'.h($issuer['street']).'
'.h($issuer['zip']).' '.h($issuer['city']).'
'.h($issuer['country']).'

Steuernr.: '.h($issuer['tax_no']).'
E-Mail: '.h($issuer['email']).'
Tel.: '.h($issuer['phone']).'
'; // --- ITEMS LOOP (wie gehabt) --- $pos = 1; $sum = 0; foreach ($items as $row) { $line = (int)$row['line_total_eur']; $sum += $line; $html .= ' '; $pos++; } // --- CLOSE TABLE + SUMMARY + PAYMENT + FOOTER --- $html .= '
Pos Beschreibung Menge Einzelpreis Gesamt
'.h($row['pos'] ?? $pos).' '.h($row['description']).' '.h($row['qty']).' '.h(eur($row['unit_price_eur'])).' '.h(eur($line)).'
'.h($config['kleinunternehmer_text']).' '.(!empty($inv['notes']) ? '

'.nl2br(h($inv['notes'])) : '').'
Zwischensumme '.h(eur($sum)).'
Umsatzsteuer 0 €
Gesamtbetrag '.h(eur($sum)).'
Zahlungsinformationen
Bitte überweisen Sie den Betrag innerhalb von '.h($pay['pay_within_days']).' Tagen.
IBAN '.h($pay['iban']).'
BIC '.h($pay['bic']).'
Bank '.h($pay['bank']).'
'; // Dompdf options $options = new Options(); $options->set('isRemoteEnabled', false); $options->set('defaultFont', 'DejaVu Sans'); $dompdf = new Dompdf($options); $dompdf->loadHtml($html, 'UTF-8'); $dompdf->setPaper('A4', 'portrait'); $dompdf->render(); $pdfOutput = $dompdf->output(); // Save file $filename = $inv['invoice_no'] . '.pdf'; $fullPath = rtrim($pdfDir, '/').'/'.$filename; $relativePath = 'storage/invoices/' . $filename; if (file_put_contents($fullPath, $pdfOutput) === false) { throw new RuntimeException('PDF konnte nicht gespeichert werden: ' . $fullPath); } // Store pdf_path in DB (relative is nicer) $pdo->prepare("UPDATE invoices SET pdf_path=:p WHERE id=:id") ->execute([':p'=>$relativePath, ':id'=>$id]); // Output download header('Content-Type: application/pdf'); header('Content-Disposition: inline; filename="'.basename($filename).'"'); echo $pdfOutput; } catch (Throwable $e) { http_response_code(400); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok'=>false,'error'=>$e->getMessage()], JSON_UNESCAPED_UNICODE); }