feat: split nights off an accommodation booking as separate periods
addresses #869f6z36v
This commit is contained in:
1 parent
972839286c
commit
7001bc0cf2
35 files changed
+2221
-25
No files matched your search
@@ -41,6 +41,48 @@
|
||||
{{ form_row(form.minorsCount) }}
|
||||
{{ form_row(form.childrenCount) }}
|
||||
|
||||
{# Saved by their own modals, which reprice the booking on the spot — see SplitPeriodController. #}
|
||||
<div class="lg:col-span-2">
|
||||
<h3 class="font-bold pb-1">Zeiträume</h3>
|
||||
<p class="text-xs text-gray-500 pb-2">
|
||||
Anreise und Abreise umfassen den ganzen Aufenthalt. Abgeteilte Zeiträume, etwa für ein
|
||||
Vorausteam, werden mit eigenen Positionen berechnet; Personen und Leistungen oben gelten
|
||||
nur für den Zeitraum der Gruppe.
|
||||
</p>
|
||||
<table class="w-full text-sm">
|
||||
{% if booking.leadingPeriod %}
|
||||
{% set p = booking.leadingPeriod %}
|
||||
{{ block('period_row') }}
|
||||
{% endif %}
|
||||
<tr class="border-t border-gray-200">
|
||||
<td class="py-1 whitespace-nowrap">{{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }}</td>
|
||||
<td class="py-1 whitespace-nowrap">{{ booking.mainNights }} {{ booking.mainNights != 1 ? 'Nächte' : 'Nacht' }}</td>
|
||||
<td class="py-1" colspan="2">Gruppe</td>
|
||||
<td class="py-1 text-right">
|
||||
{# One period per side, so only the free sides are offered. #}
|
||||
{% if booking.leadingPeriod is null or booking.trailingPeriod is null %}
|
||||
<twig:dropdown>
|
||||
{% if booking.leadingPeriod is null %}
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_split', back_params | merge({ position: 'leading' })) }}">
|
||||
am Anfang abteilen
|
||||
</twig:dropdown:hxbutton>
|
||||
{% endif %}
|
||||
{% if booking.trailingPeriod is null %}
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_split', back_params | merge({ position: 'trailing' })) }}">
|
||||
am Ende abteilen
|
||||
</twig:dropdown:hxbutton>
|
||||
{% endif %}
|
||||
</twig:dropdown>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if booking.trailingPeriod %}
|
||||
{% set p = booking.trailingPeriod %}
|
||||
{{ block('period_row') }}
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if form.boardService is defined or form.selectedAdditionalServices is defined %}
|
||||
<h2 class="lg:col-span-2 text-base font-bold pt-2">Leistungen</h2>
|
||||
{% if form.boardService is defined %}
|
||||
@@ -98,3 +140,37 @@
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block period_row %}
|
||||
{% set period_params = { id: p.id } %}
|
||||
{% if app.request.query.get('r') %}
|
||||
{% set period_params = period_params | merge({ r: app.request.query.get('r') }) %}
|
||||
{% endif %}
|
||||
<tr class="border-t border-gray-200 align-top">
|
||||
<td class="py-1 whitespace-nowrap">{{ p.dateFrom | date('d.m.Y') }} – {{ p.dateTo | date('d.m.Y') }}</td>
|
||||
<td class="py-1 whitespace-nowrap">{{ p.nights }} {{ p.nights != 1 ? 'Nächte' : 'Nacht' }}</td>
|
||||
<td class="py-1">
|
||||
{% if p.label %}
|
||||
<div class="font-medium">{{ p.label }}</div>
|
||||
{% endif %}
|
||||
{% for line in p.lines %}
|
||||
<div>{{ line.label }}</div>
|
||||
{% else %}
|
||||
<div class="text-gray-500">keine Positionen</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="py-1 text-right whitespace-nowrap">
|
||||
{{ (p.total / 100) | format_currency(booking.pricingCurrency ?? booking.accommodation.currency) }}
|
||||
</td>
|
||||
<td class="py-1 text-right">
|
||||
<twig:dropdown>
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_edit', period_params) }}">
|
||||
bearbeiten
|
||||
</twig:dropdown:hxbutton>
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_remove', period_params) }}" warning>
|
||||
entfernen
|
||||
</twig:dropdown:hxbutton>
|
||||
</twig:dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,57 @@
|
||||
{% extends 'htmx_modal_admin.html.twig' %}
|
||||
{% form_theme form 'forms_admin.html.twig' %}
|
||||
|
||||
{% macro line_row(line) %}
|
||||
<div class="flex items-start gap-2" data-form-collection-target="field">
|
||||
<div class="flex-1">{{ form_row(line.label) }}</div>
|
||||
<div class="w-40">{{ form_row(line.amount) }}</div>
|
||||
<button type="button"
|
||||
class="mt-9 text-gray-500 hover:text-red-500"
|
||||
data-action="form-collection#removeItem"
|
||||
title="Position entfernen">
|
||||
{{ icon('close', 'w-5 h-5') }}
|
||||
</button>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
{% import _self as period %}
|
||||
<div class="text-xl font-bold pb-4">
|
||||
{{ title }}
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 pb-4">
|
||||
Die Nächte werden vom Zeitraum der Gruppe abgezogen, der neu berechnet wird. Die Positionen
|
||||
kommen zum Gesamtpreis hinzu und fallen nur unter den Rabatt auf den Gesamtpreis.
|
||||
</p>
|
||||
{{ form_start(form) }}
|
||||
{{ form_errors(form) }}
|
||||
<div class="pb-4">
|
||||
{{ form_row(form.label) }}
|
||||
</div>
|
||||
<div class="w-40 pb-4">
|
||||
{{ form_row(form.nights) }}
|
||||
</div>
|
||||
{% set prototype %}{{ period.line_row(form.lines.vars.prototype) }}{% endset %}
|
||||
<div {{ stimulus_controller('form-collection', { prototype: prototype|json_encode }) }}>
|
||||
<div class="font-bold pb-2">Positionen</div>
|
||||
{{ form_errors(form.lines) }}
|
||||
<div class="space-y-2" data-form-collection-target="fields">
|
||||
{% for line in form.lines %}
|
||||
{{ period.line_row(line) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="button"
|
||||
class="button button--secondary button--small mt-2"
|
||||
data-action="form-collection#addItem">
|
||||
Position hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
{% do form.lines.setRendered %}
|
||||
<div class="flex justify-end pt-8">
|
||||
<button type="submit" class="button button--primary button--small">
|
||||
speichern
|
||||
</button>
|
||||
</div>
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,13 @@
|
||||
{% extends 'htmx_confirmation_modal.html.twig' %}
|
||||
|
||||
{% block title %}Zeitraum entfernen{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
Möchtest du den Zeitraum {{ period.dateFrom | date('d.m.Y') }} – {{ period.dateTo | date('d.m.Y') }}
|
||||
mit allen Positionen entfernen? Die Nächte gehen an den Zeitraum der Gruppe zurück, und der
|
||||
Preis wird neu berechnet.
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block button_confirm %}entfernen{% endblock %}
|
||||
@@ -43,6 +43,13 @@
|
||||
<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 %}
|
||||
@@ -89,6 +96,17 @@
|
||||
<h2 class="text-lg font-bold mb-4">Preise</h2>
|
||||
{% set currency = booking.pricingCurrency ?? priceBreakdown.currency %}
|
||||
<table class="w-full text-sm">
|
||||
{% for period in priceBreakdown.periods | filter(p => p.position == 'leading') %}
|
||||
{{ block('period_rows') }}
|
||||
{% 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">
|
||||
@@ -139,6 +157,9 @@
|
||||
<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') %}
|
||||
{{ block('period_rows') }}
|
||||
{% endfor %}
|
||||
<tr class="border-t border-gray-200 font-semibold">
|
||||
<td class="pt-2">
|
||||
Gesamtpreis
|
||||
@@ -304,3 +325,20 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{# One split-off period of the breakdown: its range as a heading, then its lines. #}
|
||||
{% block period_rows %}
|
||||
<tr class="border-t border-gray-200 font-medium">
|
||||
<td class="pt-2 pb-1" colspan="2">
|
||||
{% if period.label ?? null %}{{ period.label }} · {% endif %}
|
||||
{{ period.dateFrom | date('d.m.Y') }} – {{ period.dateTo | date('d.m.Y') }},
|
||||
{{ period.nights }} {{ period.nights != 1 ? 'Nächte' : 'Nacht' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% for line in period.lines %}
|
||||
<tr>
|
||||
<td class="py-1">{{ line.label }}</td>
|
||||
<td class="py-1 text-right whitespace-nowrap">{{ (line.amount / 100) | format_currency(currency) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
@@ -11,6 +11,17 @@
|
||||
<h2>Preise</h2>
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 14px;">
|
||||
{% for period in priceBreakdown.periods|filter(p => p.position == 'leading') %}
|
||||
{% include 'email/_price_breakdown_period.html.twig' %}
|
||||
{% endfor %}
|
||||
{% if priceBreakdown.periods is not empty %}
|
||||
<tr>
|
||||
<td colspan="2" style="{{ cell }} border-top: 1px solid #d1d5db; font-weight: bold;">
|
||||
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 style="{{ cell }}">
|
||||
@@ -64,6 +75,9 @@
|
||||
<td style="{{ amount }}">{{ (priceBreakdown.runningCosts / 100)|format_currency(currency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% for period in priceBreakdown.periods|filter(p => p.position == 'trailing') %}
|
||||
{% include 'email/_price_breakdown_period.html.twig' %}
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td style="{{ cell }} border-top: 1px solid #d1d5db; font-weight: bold;">
|
||||
Gesamtpreis
|
||||
@@ -103,3 +117,4 @@
|
||||
{% endif %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{#- One split-off period of _price_breakdown.html.twig: its range as a heading, then its lines.
|
||||
Context: period, cell, amount, currency. -#}
|
||||
<tr>
|
||||
<td colspan="2" style="{{ cell }} border-top: 1px solid #d1d5db; font-weight: bold;">
|
||||
{% if period.label ?? null %}{{ period.label }} · {% endif %}
|
||||
{{ period.dateFrom|date('d.m.Y') }} – {{ period.dateTo|date('d.m.Y') }},
|
||||
{{ period.nights }} {{ period.nights != 1 ? 'Nächte' : 'Nacht' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% for line in period.lines %}
|
||||
<tr>
|
||||
<td style="{{ cell }}">{{ line.label }}</td>
|
||||
<td style="{{ amount }}">{{ (line.amount / 100)|format_currency(currency) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -19,6 +19,12 @@
|
||||
<td><strong>Nächte</strong></td>
|
||||
<td>{{ booking.nights }}</td>
|
||||
</tr>
|
||||
{% if booking.periods is not empty %}
|
||||
<tr>
|
||||
<td><strong>Zeitraum Gruppe</strong></td>
|
||||
<td>{{ booking.mainDateFrom|date('d.m.Y') }} – {{ booking.mainDateTo|date('d.m.Y') }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><strong>Anzahl Personen</strong></td>
|
||||
<td>{{ booking.paxCount }}</td>
|
||||
|
||||
@@ -67,6 +67,16 @@
|
||||
{{ booking.nights }} {{ booking.nights != 1 ? 'Nächte' : 'Nacht' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% if ctx.priceBreakdown.periods is not empty %}
|
||||
<tr>
|
||||
<td class="p-2">Zeitraum Gruppe</td>
|
||||
<td class="p-2 text-right">
|
||||
{{ booking.mainDateFrom|date('d.m.Y') }} – {{ booking.mainDateTo|date('d.m.Y') }},
|
||||
<br>
|
||||
{{ ctx.priceBreakdown.nights }} {{ ctx.priceBreakdown.nights != 1 ? 'Nächte' : 'Nacht' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="p-2">
|
||||
Anzahl Personen
|
||||
@@ -88,6 +98,14 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% for period in ctx.priceBreakdown.periods|filter(p => p.position == 'leading') %}
|
||||
{% include 'groups/offer/_summary_period.html.twig' %}
|
||||
{% endfor %}
|
||||
{% if ctx.priceBreakdown.periods is not empty %}
|
||||
<tr class="border-t border-primary-bg font-semibold">
|
||||
<td class="p-2" colspan="2">Gruppe</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if ctx.priceBreakdown.basePrice > 0 %}
|
||||
<tr>
|
||||
<td class="p-2">
|
||||
@@ -141,6 +159,9 @@
|
||||
<td class="p-2 text-right whitespace-nowrap">{{ (ctx.priceBreakdown.runningCosts / 100)|format_currency(currency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% for period in ctx.priceBreakdown.periods|filter(p => p.position == 'trailing') %}
|
||||
{% include 'groups/offer/_summary_period.html.twig' %}
|
||||
{% endfor %}
|
||||
<tr class="border-t border-primary-bg font-semibold text-base">
|
||||
<td class="p-2">
|
||||
Gesamtpreis
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{#- One split-off period of the offer summary: its range as a heading, then its lines.
|
||||
Context: period, currency. -#}
|
||||
<tr class="border-t border-primary-bg font-semibold">
|
||||
<td class="p-2" colspan="2">
|
||||
{% if period.label ?? null %}{{ period.label }} · {% endif %}
|
||||
{{ period.dateFrom|date('d.m.Y') }} – {{ period.dateTo|date('d.m.Y') }},
|
||||
{{ period.nights }} {{ period.nights != 1 ? 'Nächte' : 'Nacht' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% for line in period.lines %}
|
||||
<tr>
|
||||
<td class="p-2">{{ line.label }}</td>
|
||||
<td class="p-2 text-right whitespace-nowrap">{{ (line.amount / 100)|format_currency(currency) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -83,6 +83,9 @@
|
||||
<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>
|
||||
{% if booking.periods is not empty %}
|
||||
<tr><th>Zeitraum Gruppe</th><td>{{ booking.mainDateFrom|date('d.m.Y') }} – {{ booking.mainDateTo|date('d.m.Y') }}</td></tr>
|
||||
{% endif %}
|
||||
<tr><th>Anzahl Personen</th><td>{{ booking.paxCount }}</td></tr>
|
||||
{% if booking.minorsCount > 0 %}
|
||||
<tr><th>davon Kinder (0–3 J.)</th><td>{{ booking.minorsCount }}</td></tr>
|
||||
|
||||
Reference in new issue
Block a user