Files
myep/templates/booking/create/step_4.html.twig
T
Björn Fromme 776b9d0912 feat: proper determination of agency initiated bookings
For booking that are agency initiated (groups) different rules apply
concerning mutability and requirement of personal data and whether the
first participant is the applicant or not. This commit adds logic to
evaluate the agency id assigned to the booking data to decide.
2026-01-08 12:54:59 +01:00

571 lines
35 KiB
Twig

{% extends 'layout_booking.html.twig' %}
{% block title %}Bitte bestätige deine Buchung{% endblock %}
{% block content %}
{{ form_start(form, {
'attr': {
'class': 'flex-1 flex flex-col min-h-0',
'novalidate': 'novalidate'
}
}) }}
{# Pagination - mobile only #}
<div class="lg:hidden">
{% include 'booking/_pagination.html.twig' with { 'current_step': 4 } %}
</div>
<div class="flex-1 flex flex-col lg:grid lg:grid-cols-3 min-h-0 relative">
{# Sidebar with travel information #}
<div class="hidden lg:block order-2 lg:flex-1 lg:min-h-0 bg-white lg:border-l lg:border-primary-bg">
{# 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">
<div class="flex items-center justify-between pb-2">
<span class="block uppercase font-semibold">Gesamtpreis</span>
<span class="font-semibold">{{ summaryData.payableAmount|format_currency('EUR') }}</span>
</div>
<div class="flex items-center justify-between">
<span class="block uppercase font-semibold">Buchungsübersicht</span>
</div>
</div>
{# Travel Information #}
<div class="overflow-y-auto px-4 lg:px-8 py-8">
<div class="pb-4">
<table>
<tr>
<th class="text-left align-top border-r-2 border-gray-300 pr-2">
Reise
</th>
<td class="pl-2">
{{ bookingCreateDto.travel.label }}
</td>
</tr>
<tr>
<th class="text-left align-top border-r-2 border-gray-300 pr-2">
Zeitraum
</th>
<td class="pl-2">
{{ bookingCreateDto.travel.dateFrom|date('d.m.Y') }} - {{ bookingCreateDto.travel.dateTo|date('d.m.Y') }}
</td>
</tr>
<tr>
<th class="text-left align-top border-r-2 border-gray-300 pr-2">
Unterkunft
</th>
<td class="pl-2">
{{ bookingCreateDto.travel.hotel.name }}
</td>
</tr>
<tr>
<th class="text-left align-top border-r-2 border-gray-300 pr-2">
Teilnehmer
</th>
<td class="pl-2">
{{ summaryData.participantCount }}
</td>
</tr>
</table>
</div>
{% include 'booking/_summary_hotel.html.twig' %}
</div>
</div>
<div id="form-wrapper"
class="flex-1 order-1 lg:col-span-2 bg-white flex flex-col min-h-0">
{# Flash messages - must be inside form-wrapper for HTMX swap to display them #}
{% include '_partials/_flashes.html.twig' %}
{# Pagination - desktop only #}
<div class="hidden lg:block shrink-0">
{% include 'booking/_pagination.html.twig' with { 'current_step': 4 } %}
</div>
<div class="flex-1 overflow-y-auto px-4 lg:px-8 py-8">
<h2 class="pb-4">
Bitte bestätige deine Buchung
</h2>
{# Validation errors #}
{% from '_partials/_validation_errors.html.twig' import validation_alert %}
{{ validation_alert(form, 'Bitte bestätige deine Angaben und stimme den AGB zu', false) }}
{# Travel Information - mobile only #}
<div class="lg:hidden pb-4">
<table class="mb-4">
<tr>
<th class="text-left align-top border-r-2 border-gray-300 pr-2">
Reise
</th>
<td class="pl-2">
{{ bookingCreateDto.travel.label }}
</td>
</tr>
<tr>
<th class="text-left align-top border-r-2 border-gray-300 pr-2">
Zeitraum
</th>
<td class="pl-2">
{{ bookingCreateDto.travel.dateFrom|date('d.m.Y') }} - {{ bookingCreateDto.travel.dateTo|date('d.m.Y') }}
</td>
</tr>
<tr>
<th class="text-left align-top border-r-2 border-gray-300 pr-2">
Unterkunft
</th>
<td class="pl-2">
{{ bookingCreateDto.travel.hotel.name }}
</td>
</tr>
<tr>
<th class="text-left align-top border-r-2 border-gray-300 pr-2">
Teilnehmer
</th>
<td class="pl-2">
{{ summaryData.participantCount }}
</td>
</tr>
</table>
{% include 'booking/_summary_hotel.html.twig' %}
</div>
{# Detailed Pricing Breakdown #}
<h3 class="text-xl font-semibold mb-4">
Preisübersicht
</h3>
{# Rooms Section #}
{% if summaryData.pricingData.rooms is not empty %}
<div class="border border-primary-bg mb-4">
<div class="p-2 bg-primary-bg uppercase font-semibold">
Unterkunft
</div>
<table class="w-full table-fixed">
{% for roomPricing in summaryData.pricingData.rooms %}
<tr>
<td class="p-2 align-top">
<div>{{ roomPricing.quantity }}x {{ roomPricing.label }}</div>
{% if summaryData.assignmentCounts[roomPricing.roomId] is defined %}
<div class="text-sm text-gray-600">{{ summaryData.assignmentCounts[roomPricing.roomId] }} Person(en) belegt</div>
{% endif %}
</td>
<td class="p-2 align-top w-24 text-right whitespace-nowrap">
{{ roomPricing.totalPrice|format_currency('EUR') }}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{# Services Section #}
{% if summaryData.pricingData.services is not empty %}
<div class="border border-primary-bg mb-4">
<div class="p-2 bg-primary-bg uppercase font-semibold">
Leistungen
</div>
{% for serviceGroup in summaryData.pricingData.services %}
<div class="p-2 text-primary-dark/70 uppercase font-semibold">
{{ serviceGroup.groupName }}
</div>
<table class="w-full table-fixed">
{% for servicePricing in serviceGroup.services %}
<tr>
<td class="px-2 pb-2 align-top">
{{ servicePricing.participantCount }}x {{ servicePricing.label }}
</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{{ servicePricing.totalPrice|format_currency('EUR') }}
</td>
</tr>
{% endfor %}
</table>
{% endfor %}
</div>
{% endif %}
{# Participants Summary #}
<h3 class="text-xl font-semibold mb-4">
Teilnehmer
</h3>
{% for participant in bookingCreateDto.participants %}
<div class="border border-primary-bg mb-4">
<div class="p-2 bg-primary-bg uppercase font-semibold flex justify-between items-center">
<span>
{{ participant.firstName }} {{ participant.lastName }}
{% if loop.first and not bookingCreateDto.isInternalAgencyBooking() %}<span class="text-sm font-normal">(Anmelder:in)</span>{% endif %}
</span>
{% if participantPrices is defined and participantPrices[loop.index0] is defined %}
<span>{{ participantPrices[loop.index0]|format_currency('EUR') }}</span>
{% endif %}
</div>
{# Personal Data - responsive grid #}
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2 p-2 text-sm">
{% if participant.dateOfBirth %}
<div>
<span class="text-gray-600 font-semibold">Geburtsdatum:</span>
{{ participant.dateOfBirth|date('d.m.Y') }}
</div>
{% endif %}
{% if participant.gender %}
<div>
<span class="text-gray-600 font-semibold">Geschlecht:</span>
{{ participant.gender == 'M' ? 'Männlich' : 'Weiblich' }}
</div>
{% endif %}
{% if participant.email %}
<div>
<span class="text-gray-600 font-semibold">E-Mail:</span>
{{ participant.email }}
</div>
{% endif %}
{% if participant.mobile %}
<div>
<span class="text-gray-600 font-semibold">Telefon:</span>
{{ participant.mobile }}
</div>
{% endif %}
{% if participant.nationality %}
<div>
<span class="text-gray-600 font-semibold">Nationalität:</span>
{{ participant.nationality }}
</div>
{% endif %}
{% if participant.address and (participant.address.street or participant.address.city) %}
<div>
<span class="text-gray-600 font-semibold">Adresse:</span>
{% if participant.address.street %}{{ participant.address.street }}, {% endif %}
{% if participant.address.postCode or participant.address.city %}
{{ participant.address.postCode }} {{ participant.address.city }}
{% endif %}
{% if participant.address.country %}, {{ participant.address.country }}{% endif %}
</div>
{% endif %}
</div>
{# Unterkunft Section #}
{% set assignedRoom = bookingCreateDto.travel.getRoomById(participant.assignedRoomId) %}
{% if assignedRoom or participant.remarksRoom %}
<div class="px-2 py-1 text-primary-dark/70 text-sm uppercase font-semibold">
Unterkunft
</div>
<table class="w-full table-fixed text-sm">
{% if assignedRoom %}
<tr>
<td class="px-2 pb-2 align-top">{{ assignedRoom.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if assignedRoom.price is not null and assignedRoom.price != 0 %}
{{ assignedRoom.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endif %}
{% if participant.remarksRoom %}
<tr>
<td class="px-2 pb-2 align-top text-gray-600" colspan="2">Wunsch: {{ participant.remarksRoom }}</td>
</tr>
{% endif %}
</table>
{% endif %}
{# An-/Abreise Section #}
{% if participant.transportationOutbound or participant.transportationInbound or participant.pickup or participant.parking or participant.licensePlate %}
<div class="px-2 py-1 text-primary-dark/70 text-sm uppercase font-semibold">
An-/Abreise
</div>
<table class="w-full table-fixed text-sm">
{% if participant.transportationOutbound %}
<tr>
<td class="px-2 pb-2 align-top">Hinfahrt: {{ participant.transportationOutbound.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if participant.transportationOutbound.price is not null and participant.transportationOutbound.price != 0 %}
{{ participant.transportationOutbound.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endif %}
{% if participant.transportationInbound %}
<tr>
<td class="px-2 pb-2 align-top">Rückfahrt: {{ participant.transportationInbound.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if participant.transportationInbound.price is not null and participant.transportationInbound.price != 0 %}
{{ participant.transportationInbound.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endif %}
{% if participant.pickup %}
<tr>
<td class="px-2 pb-2 align-top">Zu-/Ausstieg: {{ participant.pickup.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if participant.pickup.price is not null and participant.pickup.price != 0 %}
{{ participant.pickup.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endif %}
{% if participant.parking %}
<tr>
<td class="px-2 pb-2 align-top">Parkplatz</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if participant.parkingService is not null and participant.parkingService.price is not null and participant.parkingService.price != 0 %}
{{ participant.parkingService.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endif %}
{% if participant.licensePlate %}
<tr>
<td class="px-2 pb-2 align-top text-gray-600" colspan="2">Kennzeichen: {{ participant.licensePlate }}</td>
</tr>
{% endif %}
</table>
{% endif %}
{# Verpflegung Section #}
{% if participant.board is not empty %}
<div class="px-2 py-1 text-primary-dark/70 text-sm uppercase font-semibold">
Verpflegung
</div>
<table class="w-full table-fixed text-sm">
{% for boardItem in participant.board %}
<tr>
<td class="px-2 pb-2 align-top">{{ boardItem.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if boardItem.price is not null and boardItem.price != 0 %}
{{ boardItem.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# Skipass Section #}
{% if participant.skiPass %}
<div class="px-2 py-1 text-primary-dark/70 text-sm uppercase font-semibold">
Skipass
</div>
<table class="w-full table-fixed text-sm">
<tr>
<td class="px-2 pb-2 align-top">{{ participant.skiPass.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if participant.skiPass.price is not null and participant.skiPass.price != 0 %}
{{ participant.skiPass.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
</table>
{% endif %}
{# Leihmaterial Section #}
{% if participant.rentals is not empty or (participant.rentalInsuranceSelected and participant.rentalInsurance) %}
<div class="px-2 py-1 text-primary-dark/70 text-sm uppercase font-semibold">
Leihmaterial
</div>
<table class="w-full table-fixed text-sm">
{% for rental in participant.rentals %}
<tr>
<td class="px-2 pb-2 align-top">{{ rental.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if rental.price is not null and rental.price != 0 %}
{{ rental.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endfor %}
{% if participant.rentalInsuranceSelected and participant.rentalInsurance %}
<tr>
<td class="px-2 pb-2 align-top">{{ participant.rentalInsurance.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if participant.rentalInsurance.price is not null and participant.rentalInsurance.price != 0 %}
{{ participant.rentalInsurance.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endif %}
{% if participant.rentals is not empty and (participant.height or participant.weight or participant.shoeSize) %}
<tr>
<td class="px-2 pb-2 align-top text-gray-600" colspan="2">
{% if participant.height %}Größe: {{ participant.height }} cm{% endif %}
{% if participant.weight %}{% if participant.height %}, {% endif %}Gewicht: {{ participant.weight }} kg{% endif %}
{% if participant.shoeSize %}{% if participant.height or participant.weight %}, {% endif %}Schuhgröße: {{ participant.shoeSize }}{% endif %}
</td>
</tr>
{% endif %}
</table>
{% endif %}
{# Kurse Section #}
{% if participant.courses is not empty %}
<div class="px-2 py-1 text-primary-dark/70 text-sm uppercase font-semibold">
Kurse
</div>
<table class="w-full table-fixed text-sm">
{% for course in participant.courses %}
<tr>
<td class="px-2 pb-2 align-top">{{ course.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if course.price is not null and course.price != 0 %}
{{ course.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# Zusatzleistungen Section #}
{% if participant.additionalServices is not empty %}
<div class="px-2 py-1 text-primary-dark/70 text-sm uppercase font-semibold">
Zusatzleistungen
</div>
<table class="w-full table-fixed text-sm">
{% for service in participant.additionalServices %}
<tr>
<td class="px-2 pb-2 align-top">{{ service.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if service.price is not null and service.price != 0 %}
{{ service.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{# Reiseversicherung Section #}
{% if participant.insurance %}
<div class="px-2 py-1 text-primary-dark/70 text-sm uppercase font-semibold">
Reiseversicherung
</div>
<table class="w-full table-fixed text-sm">
<tr>
<td class="px-2 pb-2 align-top">{{ participant.insurance.label }}</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{% if participant.insurance.price is not null and participant.insurance.price != 0 %}
{{ participant.insurance.price|format_currency('EUR') }}
{% endif %}
</td>
</tr>
</table>
{% endif %}
</div>
{% endfor %}
{# Grand Total #}
{% if summaryData.pricingData.grandTotal is defined and summaryData.pricingData.grandTotal > 0 %}
{% set acceptedVouchers = bookingCreateDto.getAcceptedVouchers() %}
{% if acceptedVouchers and acceptedVouchers.hasVouchers() %}
<div class="border border-primary-bg mb-4">
<div class="p-2 bg-primary-bg uppercase font-semibold">
Kostenübersicht
</div>
<table class="w-full table-fixed">
<tr>
<td class="px-2 pb-2 align-top">
Gesamtpreis
</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
{{ summaryData.pricingData.grandTotal|format_currency('EUR') }}
</td>
</tr>
{% for voucher in acceptedVouchers.vouchers %}
<tr>
<td class="px-2 pb-2 align-top">
{% if voucher.promotional %}
Aktionsgutschein ({{ voucher.code }})
{% elseif voucher.goodwill %}
Kulanzgutschein ({{ voucher.code }})
{% elseif voucher.purchase %}
Kaufgutschein ({{ voucher.code }})
{% endif %}
</td>
<td class="px-2 pb-2 align-top w-24 text-right whitespace-nowrap">
- {{ voucher.amount|format_currency('EUR') }}
</td>
</tr>
{% endfor %}
</table>
</div>
<div class="flex items-center justify-between px-2 pb-4">
<span class="block uppercase font-semibold">Zu zahlen</span>
<span class="font-semibold">{{ summaryData.payableAmount|format_currency('EUR') }}</span>
</div>
{% else %}
<div class="flex items-center justify-between pb-4">
<span class="block uppercase font-semibold">Gesamtpreis</span>
<span class="font-semibold">{{ summaryData.pricingData.grandTotal|format_currency('EUR') }}</span>
</div>
{% endif %}
{% endif %}
{# Payment Summary #}
<div class="border border-primary-bg mb-4">
<div class="p-2 bg-primary-bg uppercase font-semibold">
Zahlungsart
</div>
<table class="w-full table-fixed text-sm">
<tr>
<td class="p-2 align-top">
{% if bookingCreateDto.paymentMethod == constant('App\\BusProNet\\Constants::PAYMENT_METHOD_DEBIT') %}
Lastschrift (Einzugsermächtigungsverfahren)
{% else %}
Überweisung
{% endif %}
</td>
</tr>
{% if bookingCreateDto.paymentMethod == constant('App\\BusProNet\\Constants::PAYMENT_METHOD_DEBIT') and bookingCreateDto.bankAccount %}
<tr>
<td class="p-2 align-top">
<span class="text-gray-600 font-semibold">IBAN:</span>
<span class="font-mono">{{ bookingCreateDto.bankAccount.iban }}</span>
</td>
</tr>
<tr>
<td class="p-2 align-top">
<span class="text-gray-600 font-semibold">Kontoinhaber:</span>
{{ bookingCreateDto.bankAccount.accountHolder }}
</td>
</tr>
{% endif %}
</table>
</div>
{# Confirmation Checkboxes #}
<div class="border border-primary-bg">
<div class="p-2 bg-primary-bg uppercase font-semibold">
Bestätigung
</div>
<div class="p-4">
{{ form_row(form.confirmationAccepted, {
'label_attr': {'class': 'text-base font-semibold'}
}) }}
{{ form_row(form.termsAccepted, {
'label_attr': {'class': 'text-base font-semibold'}
}) }}
</div>
</div>
</div>
</div>
</div>
<div class="shrink-0 bg-primary-dark px-4 lg:px-8 py-2 lg:py-4 z-20">
<div class="flex justify-between" hx-disinherit="*">
<button type="button"
hx-get="{{ path('app_booking_cancel') }}"
hx-target="body"
hx-swap="beforeend"
class="inline-flex items-center justify-center bg-gray-200 rounded-md w-10 h-10">
<svg class="w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><line x1="200" y1="56" x2="56" y2="200" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="200" y1="200" x2="56" y2="56" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
</button>
<button type="submit" class="button button--primary" {{ qa_attribute('btn-submit') }}>
{% if bookingCreateDto.bookingStatus == 'A' %}
Anfragen
{% else %}
Verbindlich buchen
{% endif %}
</button>
</div>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
{% endblock %}