feat: groups price calculator admin crud, booking/offer flow and api

This commit is contained in:
Björn Fromme
2026-08-03 15:25:10 +02:00
parent eabed8295a
commit 9d2aa11fdb
258 changed files with 16231 additions and 582 deletions
+163
View File
@@ -0,0 +1,163 @@
{% block booking_summary %}
<div class="bg-white h-full flex flex-col lg:border-l lg:border-primary-bg"
id="booking-summary"
{% if htmx_oob_swap ?? false %}hx-swap-oob="outerHTML"{% endif %}>
{# Header #}
<div class="shrink-0 px-4 lg:px-8 py-4 lg:flex lg:flex-col lg:justify-center shadow-md relative z-10">
{% if ctx.priceBreakdown is not null %}
<div class="flex items-center justify-between pb-2">
<span class="block uppercase font-semibold">Gesamtpreis</span>
<span class="font-semibold">{{ (ctx.priceBreakdown.total / 100)|format_currency(ctx.accommodation.currency) }}</span>
</div>
{% endif %}
<div class="flex items-center justify-between">
<span class="block uppercase font-semibold">Übersicht</span>
<button type="button"
class="lg:hidden button button--small bg-primary-light"
data-action="toggle#toggle">
<svg class="w-4 h-4 text-white transition-transform"
data-toggle-target="icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256">
<rect width="256" height="256" fill="none"/>
<polyline points="208 96 128 176 48 96" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
</svg>
</button>
</div>
</div>
{# Content #}
<div class="hidden lg:block flex-1 min-h-0 bg-white shadow-lg lg:shadow-none overflow-y-auto px-4 lg:px-8 py-8"
data-toggle-target="content">
{# Accommodation info #}
{% set hasImage = ctx.hotelCmsData is not null
and ctx.hotelCmsData.images is not null
and ctx.hotelCmsData.images.resized is defined
and ctx.hotelCmsData.images.resized.l is defined
and ctx.hotelCmsData.images.resized.l[0] is defined %}
<div class="border border-primary-bg mb-4">
<div class="p-2 bg-primary-bg uppercase font-semibold">Unterkunft</div>
<div class="grid grid-cols-3 gap-x-4 p-2">
{% if hasImage %}
<img src="{{ ctx.hotelCmsData.images.resized.l[0].url }}"
alt="{{ ctx.hotelCmsData.images.resized.l[0].alt ?? ctx.accommodation.name }}"
class="block w-full h-auto">
{% endif %}
<div class="{{ hasImage ? 'col-span-2' : 'col-span-3' }}">
<span class="block font-semibold">{{ ctx.accommodation.name }}</span>
{% if ctx.hotelCmsData is not null and ctx.hotelCmsData.address is not null %}
<div class="text-sm text-gray-500 mt-1">{{ ctx.hotelCmsData.address|nl2br }}</div>
{% endif %}
</div>
</div>
</div>
{# Price breakdown #}
{% if ctx.priceBreakdown is not null %}
<div class="border border-primary-bg mt-4">
<div class="p-2 bg-primary-bg uppercase font-semibold">Preise</div>
<table class="w-full table-fixed text-sm">
<tr>
<td class="p-2">
Zeitraum
</td>
<td class="p-2 text-right">
{% if dto.dateFrom %}
{{ dto.dateFrom|date('d.m.Y') }} {{ dto.dateTo|date('d.m.Y') }},
<br>
{{ dto.nights }} {{ dto.nights != 1 ? 'Nächte' : 'Nacht' }}
{% else %}
<span class="italic">noch nicht gewählt</span>
{% endif %}
</td>
</tr>
<tr>
<td class="p-2">
Anzahl Personen
</td>
<td class="p-2 text-right">
{{ dto.paxCount }}
</td>
</tr>
{% if dto.minorsCount > 0 %}
<tr>
<td class="p-2">davon Kinder (03 J.)</td>
<td class="p-2 text-right">{{ dto.minorsCount }}</td>
</tr>
{% endif %}
{% if dto.childrenCount > 0 %}
<tr>
<td class="p-2">davon Kinder (4{{ ctx.accommodation.maxAdolescentAge }} J.)</td>
<td class="p-2 text-right">{{ dto.childrenCount }}</td>
</tr>
{% endif %}
{% if ctx.priceBreakdown.basePrice > 0 %}
<tr>
<td class="p-2">
Basispreis
<p class="text-xs text-gray-500">für {{ ctx.priceBreakdown.includedPax }} Pers.</p>
</td>
<td class="p-2 text-right whitespace-nowrap">{{ (ctx.priceBreakdown.basePrice / 100)|format_currency(ctx.accommodation.currency) }}</td>
</tr>
{% endif %}
{% if ctx.priceBreakdown.additionalPersonsPrice > 0 %}
<tr>
<td class="p-2">
Aufpreis Personenzahl
<p class="text-xs text-gray-500">für {{ ctx.priceBreakdown.effectivePax - ctx.priceBreakdown.includedPax }} Pers.</p>
</td>
<td class="p-2 text-right whitespace-nowrap">{{ (ctx.priceBreakdown.additionalPersonsPrice / 100)|format_currency(ctx.accommodation.currency) }}</td>
</tr>
{% endif %}
{% if ctx.priceBreakdown.shortTermSurcharge > 0 %}
<tr>
<td class="p-2">Aufpreis Kurzzeit</td>
<td class="p-2 text-right whitespace-nowrap">{{ (ctx.priceBreakdown.shortTermSurcharge / 100)|format_currency(ctx.accommodation.currency) }}</td>
</tr>
{% endif %}
{% if ctx.priceBreakdown.boardPrice > 0 %}
<tr>
<td class="p-2">Verpflegung</td>
<td class="p-2 text-right whitespace-nowrap">{{ (ctx.priceBreakdown.boardPrice / 100)|format_currency(ctx.accommodation.currency) }}</td>
</tr>
{% endif %}
{% if ctx.priceBreakdown.undersubscriptionSurcharge > 0 %}
<tr>
<td class="p-2">
Verpflegungs-Aufschlag
<p class="text-xs text-gray-500">für Gruppen unter {{ ctx.priceBreakdown.undersubscriptionThreshold }} Pers.</p>
</td>
<td class="p-2 text-right whitespace-nowrap">{{ (ctx.priceBreakdown.undersubscriptionSurcharge / 100)|format_currency(ctx.accommodation.currency) }}</td>
</tr>
{% endif %}
{% for service in ctx.priceBreakdown.serviceDetails %}
{% if service.price > 0 %}
<tr>
<td class="p-2">{{ service.label }}</td>
<td class="p-2 text-right whitespace-nowrap">{{ (service.price / 100)|format_currency(ctx.accommodation.currency) }}</td>
</tr>
{% endif %}
{% endfor %}
{% if ctx.priceBreakdown.runningCosts > 0 %}
<tr>
<td class="p-2">Strom- und Abfallgebühren</td>
<td class="p-2 text-right whitespace-nowrap">{{ (ctx.priceBreakdown.runningCosts / 100)|format_currency(ctx.accommodation.currency) }}</td>
</tr>
{% endif %}
<tr class="border-t border-primary-bg font-semibold text-base">
<td class="p-2">
Gesamtpreis
<p class="text-xs font-bold">zzgl. ortsabhängiger Gebühren</p>
</td>
<td class="p-2 text-right whitespace-nowrap">
{{ (ctx.priceBreakdown.total / 100)|format_currency(ctx.accommodation.currency) }}
</td>
</tr>
</table>
</div>
{% endif %}
</div>
</div>
{% endblock %}