340 lines
19 KiB
Twig
340 lines
19 KiB
Twig
{% extends 'layout_admin.html.twig' %}
|
||
|
||
{% block content %}
|
||
{% set edit_params = { 'id': booking.id } %}
|
||
{% if app.request.query.get('r') %}
|
||
{% set edit_params = edit_params | merge({ 'r': app.request.query.get('r') }) %}
|
||
{% endif %}
|
||
|
||
<twig:page:heading>
|
||
{{ booking.recordLabel }} · {% include 'admin/accommodation_booking/_status_label.html.twig' %}
|
||
{{ booking.groupName }}
|
||
</twig:page:heading>
|
||
<p class="text-sm text-gray-500 mb-6">{{ booking.accommodation.name }}</p>
|
||
|
||
{# Same arrangement as the edit form: what was booked in the main column, status, the next
|
||
step and the actions in a sidebar that stays in view on wide screens. #}
|
||
<div class="xl:grid xl:grid-cols-3 xl:gap-6 xl:items-start space-y-6 xl:space-y-0">
|
||
<div class="xl:col-span-2 space-y-6">
|
||
<twig:form:section title="Aufenthalt">
|
||
<dl class="grid grid-cols-2 gap-y-2 text-sm">
|
||
<dt class="font-medium">Gruppenhaus</dt>
|
||
<dd>{{ booking.accommodation.name }}</dd>
|
||
<dt class="font-medium">Anreise</dt>
|
||
<dd>{{ booking.dateFrom | date('d.m.Y') }}</dd>
|
||
<dt class="font-medium">Abreise</dt>
|
||
<dd>{{ booking.dateTo | date('d.m.Y') }}</dd>
|
||
<dt class="font-medium">Nächte</dt>
|
||
<dd>{{ booking.nights }}</dd>
|
||
{% if booking.periods is not empty %}
|
||
<dt class="font-medium">Zeitraum Gruppe</dt>
|
||
<dd>
|
||
{{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }},
|
||
{{ booking.mainNights }} {{ booking.mainNights != 1 ? 'Nächte' : 'Nacht' }}
|
||
</dd>
|
||
{% endif %}
|
||
<dt class="font-medium">Anzahl Personen</dt>
|
||
<dd>{{ booking.paxCount }}</dd>
|
||
{% if booking.minorsCount > 0 %}
|
||
<dt class="font-medium">davon Kinder (0–3 Jahre)</dt>
|
||
<dd>{{ booking.minorsCount }}</dd>
|
||
{% endif %}
|
||
{% if booking.childrenCount > 0 %}
|
||
<dt class="font-medium">davon Kinder (4–{{ booking.accommodation.maxAdolescentAge }} Jahre)</dt>
|
||
<dd>{{ booking.childrenCount }}</dd>
|
||
{% endif %}
|
||
</dl>
|
||
</twig:form:section>
|
||
|
||
{% if booking.boardServiceLabel or booking.additionalServices | length > 0 %}
|
||
<twig:form:section title="Leistungen">
|
||
<dl class="grid grid-cols-2 gap-y-2 text-sm">
|
||
{% if booking.boardServiceLabel %}
|
||
<dt class="font-medium">Verpflegung</dt>
|
||
<dd>{{ booking.boardServiceLabel }}</dd>
|
||
{% endif %}
|
||
{% for service in booking.additionalServices %}
|
||
<dt class="font-medium">{{ loop.first ? 'Zusatzleistungen' : '' }}</dt>
|
||
<dd>{{ service.label }}</dd>
|
||
{% endfor %}
|
||
</dl>
|
||
</twig:form:section>
|
||
{% endif %}
|
||
|
||
{% if priceBreakdown is not null %}
|
||
{% set currency = booking.pricingCurrency ?? priceBreakdown.currency %}
|
||
<twig:form:section title="Preise">
|
||
<table class="w-full text-sm">
|
||
{% for period in priceBreakdown.periods | filter(p => p.position == 'leading') %}
|
||
{% include 'admin/accommodation_booking/_price_period.html.twig' %}
|
||
{% endfor %}
|
||
{% if priceBreakdown.periods is not empty %}
|
||
<tr class="border-t border-gray-200 font-medium">
|
||
<td class="pt-2 pb-1" colspan="2">
|
||
Gruppe · {{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }},
|
||
{{ priceBreakdown.nights }} {{ priceBreakdown.nights != 1 ? 'Nächte' : 'Nacht' }}
|
||
</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% if priceBreakdown.basePrice > 0 %}
|
||
<tr>
|
||
<td class="py-1">
|
||
Basispreis
|
||
<span class="text-xs text-gray-500">für {{ priceBreakdown.includedPax }} Pers.</span>
|
||
</td>
|
||
<td class="py-1 text-right whitespace-nowrap">{{ (priceBreakdown.basePrice / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% if priceBreakdown.additionalPersonsPrice > 0 %}
|
||
<tr>
|
||
<td class="py-1">
|
||
Aufpreis Personenzahl
|
||
<span class="text-xs text-gray-500">für {{ priceBreakdown.effectivePax - priceBreakdown.includedPax }} Pers.</span>
|
||
</td>
|
||
<td class="py-1 text-right whitespace-nowrap">{{ (priceBreakdown.additionalPersonsPrice / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% if priceBreakdown.shortTermSurcharge > 0 %}
|
||
<tr>
|
||
<td class="py-1">Aufpreis Kurzzeit</td>
|
||
<td class="py-1 text-right whitespace-nowrap">{{ (priceBreakdown.shortTermSurcharge / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% if priceBreakdown.boardPrice > 0 %}
|
||
<tr>
|
||
<td class="py-1">Verpflegung</td>
|
||
<td class="py-1 text-right whitespace-nowrap">{{ (priceBreakdown.boardPrice / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% if priceBreakdown.undersubscriptionSurcharge > 0 %}
|
||
<tr>
|
||
<td class="py-1">Verpflegungs-Aufschlag für Gruppen unter {{ priceBreakdown.undersubscriptionThreshold }} Personen</td>
|
||
<td class="py-1 text-right whitespace-nowrap">{{ (priceBreakdown.undersubscriptionSurcharge / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% for service in priceBreakdown.serviceDetails %}
|
||
{% if service.price > 0 %}
|
||
<tr>
|
||
<td class="py-1">{{ service.label }}</td>
|
||
<td class="py-1 text-right whitespace-nowrap">{{ (service.price / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% if priceBreakdown.runningCosts > 0 %}
|
||
<tr>
|
||
<td class="py-1">Strom- und Abfallgebühren</td>
|
||
<td class="py-1 text-right whitespace-nowrap">{{ (priceBreakdown.runningCosts / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% for period in priceBreakdown.periods | filter(p => p.position == 'trailing') %}
|
||
{% include 'admin/accommodation_booking/_price_period.html.twig' %}
|
||
{% endfor %}
|
||
<tr class="border-t border-gray-200 font-semibold">
|
||
<td class="pt-2">
|
||
Gesamtpreis
|
||
<span class="block text-xs font-normal text-gray-500">zzgl. ortsabhängiger Gebühren</span>
|
||
</td>
|
||
<td class="pt-2 text-right whitespace-nowrap">{{ (priceBreakdown.total / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% for discount in priceBreakdown.discounts %}
|
||
<tr class="text-green-700">
|
||
<td class="pt-1">{{ discount.label }} {{ discount.percent }} %</td>
|
||
<td class="pt-1 text-right whitespace-nowrap">– {{ (discount.amount / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% if priceBreakdown.discountSubtotal is not null %}
|
||
<tr class="border-t border-gray-200">
|
||
<td class="pt-2">Zwischensumme</td>
|
||
<td class="pt-2 text-right whitespace-nowrap">{{ (priceBreakdown.discountSubtotal / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% if priceBreakdown.totalDiscountDetails is not null %}
|
||
<tr class="text-green-700">
|
||
<td class="pt-1">{{ priceBreakdown.totalDiscountDetails.label }}</td>
|
||
<td class="pt-1 text-right whitespace-nowrap">– {{ (priceBreakdown.totalDiscountDetails.amount / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% if priceBreakdown.discounts is not empty or priceBreakdown.totalDiscountDetails is not null %}
|
||
<tr class="border-t border-gray-200 font-bold text-green-700">
|
||
<td class="pt-2">Gesamtpreis nach Rabatt</td>
|
||
<td class="pt-2 text-right whitespace-nowrap">{{ (priceBreakdown.discountedTotal / 100) | format_currency(currency) }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
</table>
|
||
</twig:form:section>
|
||
{% endif %}
|
||
|
||
<twig:form:section title="Kontakt">
|
||
<dl class="grid grid-cols-2 gap-y-2 text-sm">
|
||
<dt class="font-medium">Gruppenname</dt>
|
||
<dd>{{ booking.groupName }}</dd>
|
||
<dt class="font-medium">Anrede</dt>
|
||
<dd>{{ booking.salutation | default('–') }}</dd>
|
||
<dt class="font-medium">Name</dt>
|
||
<dd>{{ booking.firstName }} {{ booking.lastName }}</dd>
|
||
<dt class="font-medium">E-Mail</dt>
|
||
<dd>{{ booking.email }}</dd>
|
||
<dt class="font-medium">Telefon</dt>
|
||
<dd>{{ booking.phone | default('–') }}</dd>
|
||
<dt class="font-medium">Adresse</dt>
|
||
<dd>
|
||
{% if booking.street %}
|
||
{{ booking.street }}<br>
|
||
{{ booking.zip }} {{ booking.city }}
|
||
{% else %}
|
||
–
|
||
{% endif %}
|
||
</dd>
|
||
</dl>
|
||
</twig:form:section>
|
||
|
||
{% if booking.remarks %}
|
||
<twig:form:section title="Bemerkungen">
|
||
<p class="text-sm">{{ booking.remarks | nl2br }}</p>
|
||
</twig:form:section>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<aside class="space-y-6 xl:sticky xl:top-8">
|
||
<twig:form:section title="Status">
|
||
<dl class="grid grid-cols-2 gap-y-1 text-sm">
|
||
<dt class="text-gray-500">Status</dt>
|
||
<dd>{% include 'admin/accommodation_booking/_status_label.html.twig' %}</dd>
|
||
<dt class="text-gray-500">Herkunft</dt>
|
||
<dd>{{ booking.origin.label }}</dd>
|
||
{% if booking.managedBy is not null %}
|
||
<dt class="text-gray-500">Bearbeiter:in</dt>
|
||
<dd>{{ booking.managedBy.displayName }}</dd>
|
||
{% endif %}
|
||
{% if booking.acceptedAt is not null %}
|
||
<dt class="text-gray-500">Angenommen</dt>
|
||
<dd>{{ booking.acceptedAt | date('d.m.Y, H:i') }}</dd>
|
||
{% endif %}
|
||
{% if booking.confirmedAt is not null %}
|
||
<dt class="text-gray-500">Bestätigt</dt>
|
||
<dd>{{ booking.confirmedAt | date('d.m.Y, H:i') }}</dd>
|
||
{% endif %}
|
||
<dt class="text-gray-500">Eingegangen</dt>
|
||
<dd>{{ booking.createdAt | date('d.m.Y, H:i') }}</dd>
|
||
{% if booking.updatedAt is not null and booking.updatedAt != booking.createdAt %}
|
||
<dt class="text-gray-500">Aktualisiert</dt>
|
||
<dd>{{ booking.updatedAt | date('d.m.Y, H:i') }}</dd>
|
||
{% endif %}
|
||
</dl>
|
||
</twig:form:section>
|
||
|
||
{# One pink button per page, for the next step; everything else is grey, and the one
|
||
destructive action is set apart below the others. #}
|
||
{% set has_next_step = booking.draft or booking.requested or booking.received %}
|
||
{% if booking.draft or booking.requested %}
|
||
<twig:form:section title="Nächster Schritt: Angebot">
|
||
<p class="text-sm text-gray-500 mb-3">
|
||
Das Angebot wurde dem Kunden noch nicht zugestellt. Beim Versand wird der Zugangslink
|
||
erzeugt und die Buchung wartet anschließend auf die Annahme durch den Kunden.
|
||
</p>
|
||
<div class="flex flex-col gap-2">
|
||
<button type="button"
|
||
class="button button--primary button--small w-full"
|
||
hx-get="{{ path('app_admin_accommodationbooking_send_offer', { id: booking.id }) }}"
|
||
hx-target="body"
|
||
hx-swap="beforeend">
|
||
Angebot per E-Mail senden
|
||
</button>
|
||
<button type="button"
|
||
class="button button--neutral button--small w-full"
|
||
hx-get="{{ path('app_admin_accommodationbooking_generate_access_link', { id: booking.id }) }}"
|
||
hx-target="body"
|
||
hx-swap="beforeend">
|
||
nur Link erzeugen
|
||
</button>
|
||
</div>
|
||
</twig:form:section>
|
||
{% elseif booking.received %}
|
||
<twig:form:section title="Nächster Schritt: Bestätigung">
|
||
<p class="text-sm text-gray-500 mb-3">
|
||
Die Buchung des Kunden ist eingegangen, aber es wurde noch keine Bestätigung verschickt.
|
||
</p>
|
||
<button type="button"
|
||
class="button button--primary button--small w-full"
|
||
hx-get="{{ path('app_admin_accommodationbooking_confirm', { id: booking.id }) }}"
|
||
hx-target="body"
|
||
hx-swap="beforeend">
|
||
Buchung per E-Mail bestätigen
|
||
</button>
|
||
</twig:form:section>
|
||
{% endif %}
|
||
|
||
<twig:form:section title="Zugangslink">
|
||
{% if not booking.customerAccessible %}
|
||
<p class="text-sm text-gray-500">
|
||
Ein Zugangslink wird beim Versand des Angebots erzeugt.
|
||
</p>
|
||
{% elseif accessLink %}
|
||
<div class="grid grid-cols-2 gap-2">
|
||
<button type="button"
|
||
class="button button--neutral button--small sm:text-xs w-full"
|
||
data-controller="clipboard"
|
||
data-clipboard-content-value="{{ accessLink }}"
|
||
data-clipboard-label-value="Kopiert!"
|
||
data-action="clipboard#copy">
|
||
{{ icon('copy', 'w-4 h-4') }} Link kopieren
|
||
</button>
|
||
<button type="button"
|
||
class="button button--neutral button--small sm:text-xs w-full"
|
||
hx-get="{{ path('app_admin_accommodationbooking_send_access_link', { id: booking.id }) }}"
|
||
hx-target="body"
|
||
hx-swap="beforeend">
|
||
Link senden
|
||
</button>
|
||
</div>
|
||
<p class="text-xs text-gray-500 mt-2">
|
||
Gültig bis {{ accessLinkExpiresAt | date('d.m.Y') }}
|
||
{% if accessLinkExpiresAt < date() %}
|
||
<span class="text-red-600 font-medium">(abgelaufen)</span>
|
||
{% endif %}
|
||
</p>
|
||
{% else %}
|
||
<p class="text-sm text-gray-500">
|
||
Der Zugangslink wird beim Versand des Angebots erzeugt.
|
||
</p>
|
||
{% endif %}
|
||
</twig:form:section>
|
||
|
||
<twig:form:section title="Aktionen">
|
||
<div class="flex flex-col gap-2">
|
||
<a href="{{ path('app_admin_accommodationbooking_edit', edit_params) }}"
|
||
class="button {{ has_next_step ? 'button--neutral' : 'button--primary' }} button--small w-full">
|
||
bearbeiten
|
||
</a>
|
||
<button type="button"
|
||
class="button button--neutral button--small w-full"
|
||
hx-get="{{ path('app_admin_accommodationbooking_duplicate', edit_params) }}"
|
||
hx-target="body"
|
||
hx-swap="beforeend">
|
||
duplizieren
|
||
</button>
|
||
<a href="{{ path('app_admin_accommodationbooking_pdf', { 'id': booking.id }) }}"
|
||
class="button button--neutral button--small w-full">
|
||
PDF herunterladen
|
||
</a>
|
||
</div>
|
||
{% if not booking.confirmed and not booking.discarded %}
|
||
<div class="mt-4 pt-3 border-t border-gray-200 text-center">
|
||
<button type="button"
|
||
class="text-sm font-medium text-red-600 hover:underline"
|
||
hx-get="{{ path('app_admin_accommodationbooking_discard', { id: booking.id }) }}"
|
||
hx-target="body"
|
||
hx-swap="beforeend">
|
||
{{ booking.recordLabel }} absagen
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
</twig:form:section>
|
||
|
||
<a href="{{ returnUrl }}" class="inline-block text-sm text-gray-500 hover:text-gray-900 hover:underline">
|
||
← zurück
|
||
</a>
|
||
</aside>
|
||
</div>
|
||
{% endblock %}
|