feat: booking details pdf

This commit is contained in:
Björn Fromme
2026-08-18 14:26:24 +02:00
parent d3666c4618
commit 2d7a02341d
19 changed files with 1054 additions and 100 deletions
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Buchung {{ booking.id }} {{ booking.groupName }}</title>
<style>
@page { size: A4 portrait; margin: 16mm 15mm; }
body {
font-family: 'DejaVu Sans', sans-serif;
font-size: 11px;
line-height: 1.4;
color: #111827;
margin: 0;
}
/* The included price breakdown emits a bare <h2>; without these rules it would
fall back to the browser defaults instead of the email layout's typography. */
h1 { font-size: 16px; margin: 0 0 2px; }
h2 { font-size: 12px; margin: 14px 0 4px; padding-bottom: 2px; border-bottom: 1px solid #d1d5db; }
table { width: 100%; border-collapse: collapse; }
td { vertical-align: top; }
.header { width: 100%; margin-bottom: 14px; }
.header td { vertical-align: middle; }
.header .meta { text-align: right; font-size: 10px; color: #6b7280; }
.columns { table-layout: fixed; }
.columns > tr > td { width: 50%; padding-right: 12px; }
.data th { width: 42%; text-align: left; font-weight: normal; color: #6b7280; padding: 2px 8px 2px 0; }
.data td { padding: 2px 0; }
/* The shared breakdown partial is sized for emails and carries its metrics as
inline styles, so scaling it down for print needs !important. */
.prices table { font-size: 11px !important; }
.prices td { padding: 2px 0 !important; }
.prices span { font-size: 9px !important; }
.footer { margin-top: 18px; font-size: 9px; color: #9ca3af; }
</style>
</head>
<body>
<table class="header">
<tr>
<td><img src="{{ logoSrc }}" alt="" style="height: 34px;"></td>
{#- No booking number: the local id has no counterpart in BusPro. -#}
<td class="meta">
{% if booking.confirmedAt %}Bestätigt am {{ booking.confirmedAt|date('d.m.Y') }}{% endif %}
</td>
</tr>
</table>
<h1>{{ booking.type.label }} · {{ booking.groupName }}</h1>
<table class="columns">
<tr>
<td>
<h2>Kontakt</h2>
<table class="data">
<tr><th>Gruppenname</th><td>{{ booking.groupName }}</td></tr>
<tr><th>Anrede</th><td>{{ booking.salutation|default('') }}</td></tr>
<tr><th>Name</th><td>{{ booking.firstName }} {{ booking.lastName }}</td></tr>
<tr><th>E-Mail</th><td>{{ booking.email|default('') }}</td></tr>
<tr><th>Telefon</th><td>{{ booking.phone|default('') }}</td></tr>
<tr>
<th>Adresse</th>
<td>
{% if booking.street %}
{{ booking.street }}<br>{{ booking.zip }} {{ booking.city }}
{% else %}
{% endif %}
</td>
</tr>
</table>
</td>
<td>
<h2>Reisedaten</h2>
<table class="data">
<tr><th>Gruppenhaus</th><td>{{ booking.accommodation.name }}</td></tr>
<tr><th>Anreise</th><td>{{ booking.dateFrom|date('d.m.Y') }}</td></tr>
<tr><th>Abreise</th><td>{{ booking.dateTo|date('d.m.Y') }}</td></tr>
<tr><th>Nächte</th><td>{{ booking.nights }}</td></tr>
<tr><th>Anzahl Personen</th><td>{{ booking.paxCount }}</td></tr>
{% if booking.minorsCount > 0 %}
<tr><th>davon Kinder (03 J.)</th><td>{{ booking.minorsCount }}</td></tr>
{% endif %}
{% if booking.childrenCount > 0 %}
<tr><th>davon Kinder (4{{ booking.accommodation.maxAdolescentAge }} J.)</th><td>{{ booking.childrenCount }}</td></tr>
{% endif %}
<tr><th>Art</th><td>{{ booking.type.label }}</td></tr>
</table>
</td>
</tr>
</table>
{% if booking.boardServiceLabel or booking.additionalServices|length > 0 %}
<h2>Gebuchte Leistungen</h2>
<table class="data">
{% if booking.boardServiceLabel %}
<tr><th>Verpflegung</th><td>{{ booking.boardServiceLabel }}</td></tr>
{% endif %}
{% for service in booking.additionalServices %}
<tr><th>{{ loop.first ? 'Zusatzleistungen' : '' }}</th><td>{{ service.label }}</td></tr>
{% endfor %}
</table>
{% endif %}
<div class="prices">
{% include 'email/_price_breakdown.html.twig' %}
</div>
{% if booking.remarks %}
<h2>Bemerkungen</h2>
<p style="margin: 0;">{{ booking.remarks|nl2br }}</p>
{% endif %}
<p class="footer">Erstellt am {{ 'now'|date('d.m.Y') }}</p>
</body>
</html>