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 = '
';
}
$html = '
Rechnung
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']).'
| Pos |
Beschreibung |
Menge |
Einzelpreis |
Gesamt |
';
// --- ITEMS LOOP (wie gehabt) ---
$pos = 1;
$sum = 0;
foreach ($items as $row) {
$line = (int)$row['line_total_eur'];
$sum += $line;
$html .= '
| '.h($row['pos'] ?? $pos).' |
'.h($row['description']).' |
'.h($row['qty']).' |
'.h(eur($row['unit_price_eur'])).' |
'.h(eur($line)).' |
';
$pos++;
}
// --- CLOSE TABLE + SUMMARY + PAYMENT + FOOTER ---
$html .= '
'.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);
}