From 8a495346d673d6ef579c593821ab9384dd6883a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 1 Oct 2025 18:52:50 +0200 Subject: [PATCH] wip: check eligibility based on ski pass availability --- .../Condition/BookingEligibilityCondition.php | 74 ++++ src/Form/Service/CreateFieldStateProvider.php | 63 +++- .../ParticipantFieldOptionsProvider.php | 1 + src/Service/BookingPriceCalculatorService.php | 24 +- src/Service/BookingService.php | 14 +- src/Service/ParticipantEligibilityService.php | 117 ++++++ src/Twig/AppExtension.php | 1 + src/Twig/AppRuntime.php | 8 + templates/booking/create_step_2.html.twig | 353 +++++++++--------- 9 files changed, 471 insertions(+), 184 deletions(-) create mode 100644 src/Form/Service/Condition/BookingEligibilityCondition.php create mode 100644 src/Service/ParticipantEligibilityService.php diff --git a/src/Form/Service/Condition/BookingEligibilityCondition.php b/src/Form/Service/Condition/BookingEligibilityCondition.php new file mode 100644 index 0000000..df2e22f --- /dev/null +++ b/src/Form/Service/Condition/BookingEligibilityCondition.php @@ -0,0 +1,74 @@ + $formData Current form data for condition evaluation + * + * @return bool True if participant is ineligible (no skipasses available for their age) + */ + public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool + { + // Invert the eligibility check since conditions typically evaluate to TRUE for "hide" + // isParticipantEligible() returns TRUE when eligible, we need TRUE when INELIGIBLE + return !$this->participantEligibilityService->isParticipantEligible($bookingDto, $participantIndex); + } + + /** + * Returns field names that this condition depends on. + * + * This condition depends on the dateOfBirth field since eligibility + * is determined by age-based skipass availability. + * + * @return string[] Array containing field names this condition depends on + */ + public function getDependentFields(): array + { + return ['dateOfBirth']; + } + + /** + * Returns a human-readable description of this condition. + * + * @return string Description of the booking eligibility condition + */ + public function getDescription(): string + { + return 'Participant has no available skipasses for their age'; + } +} \ No newline at end of file diff --git a/src/Form/Service/CreateFieldStateProvider.php b/src/Form/Service/CreateFieldStateProvider.php index 7e10056..2ab887f 100644 --- a/src/Form/Service/CreateFieldStateProvider.php +++ b/src/Form/Service/CreateFieldStateProvider.php @@ -6,6 +6,7 @@ namespace App\Form\Service; use App\BusProNet\Utility\DirectionMapper; use App\Form\Service\Abstract\AbstractFieldStateProvider; +use App\Form\Service\Condition\BookingEligibilityCondition; use App\Form\Service\Condition\BulkInsuranceBookingCondition; use App\Form\Service\Condition\CompositeCondition; use App\Form\Service\Condition\DateOfBirthProvidedCondition; @@ -14,6 +15,7 @@ use App\Form\Service\Condition\RentalSelectionCondition; use App\Form\Service\Condition\RoomSelectionCondition; use App\Form\Service\Condition\ServiceSubTypeCondition; use App\Form\Service\Condition\SkiPassSelectionCondition; +use App\Service\ParticipantEligibilityService; /** * Field state provider for the booking create workflow. @@ -27,9 +29,15 @@ use App\Form\Service\Condition\SkiPassSelectionCondition; * - Age-dependent service fields are hidden until birth date is provided * - Transportation pickup fields are hidden by default, shown only when transportation type is BUS * - Parking field is hidden by default, shown only when outbound transportation is PKW + * - All service fields are hidden for ineligible participants (no skipasses available for their age) */ class CreateFieldStateProvider extends AbstractFieldStateProvider { + public function __construct( + private readonly ParticipantEligibilityService $participantEligibilityService + ) { + parent::__construct(); + } /** * Registers field state conditions for the create workflow. * @@ -56,6 +64,9 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider */ protected function registerFieldStateConditions(): void { + // Booking eligibility condition - hide all service fields when no skipasses available for participant's age + $bookingEligibilityCondition = new BookingEligibilityCondition($this->participantEligibilityService); + $rentalCondition = new RentalSelectionCondition(); $skiPassCondition = new SkiPassSelectionCondition(); @@ -67,25 +78,43 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider // Hide age-dependent fields when no date of birth is provided $dateOfBirthProvidedCondition = new DateOfBirthProvidedCondition(); - // Age-dependent service fields are hidden until birth date is provided + // Age-dependent service fields are hidden until birth date is provided OR when participant is ineligible $this->fieldStateConditions['courses'] = [ - 'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition), + 'hidden' => CompositeCondition::or( + CompositeCondition::not($dateOfBirthProvidedCondition), + $bookingEligibilityCondition + ), ]; $this->fieldStateConditions['additionalServices'] = [ - 'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition), + 'hidden' => CompositeCondition::or( + CompositeCondition::not($dateOfBirthProvidedCondition), + $bookingEligibilityCondition + ), ]; - // Show rentals only when both date of birth is provided AND skipass is selected + // Skipass field - hidden when participant is ineligible OR when no date of birth + $this->fieldStateConditions['skiPass'] = [ + 'hidden' => CompositeCondition::or( + CompositeCondition::not($dateOfBirthProvidedCondition), + $bookingEligibilityCondition + ), + ]; + + // Show rentals only when both date of birth is provided AND skipass is selected AND participant is eligible $this->fieldStateConditions['rentals'] = [ 'hidden' => CompositeCondition::or( CompositeCondition::not($dateOfBirthProvidedCondition), - CompositeCondition::not($skiPassCondition) + CompositeCondition::not($skiPassCondition), + $bookingEligibilityCondition ), ]; $this->fieldStateConditions['board'] = [ - 'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition), + 'hidden' => CompositeCondition::or( + CompositeCondition::not($dateOfBirthProvidedCondition), + $bookingEligibilityCondition + ), ]; // Bulk insurance booking conditions @@ -114,11 +143,12 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider ), ]; - // Hide insurance field until date of birth is provided OR when bulk insurance booking is active for dependent participants + // Hide insurance field until date of birth is provided OR when bulk insurance booking is active OR when participant is ineligible $this->fieldStateConditions['insurance'] = [ 'hidden' => CompositeCondition::or( CompositeCondition::not($dateOfBirthProvidedCondition), - $bulkInsuranceBookingCondition + $bulkInsuranceBookingCondition, + $bookingEligibilityCondition ), ]; @@ -131,6 +161,23 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider ]; // Transportation-related field conditions + // All transportation fields require date of birth and participant eligibility + + // Outbound transportation - hidden until date of birth provided AND participant is eligible + $this->fieldStateConditions['transportationOutbound'] = [ + 'hidden' => CompositeCondition::or( + CompositeCondition::not($dateOfBirthProvidedCondition), + $bookingEligibilityCondition + ), + ]; + + // Inbound transportation - hidden until date of birth provided AND participant is eligible + $this->fieldStateConditions['transportationInbound'] = [ + 'hidden' => CompositeCondition::or( + CompositeCondition::not($dateOfBirthProvidedCondition), + $bookingEligibilityCondition + ), + ]; // Show outbound pickup only when transportation is BUS (hidden by default) $this->fieldStateConditions['pickupOutbound'] = [ diff --git a/src/Form/Service/ParticipantFieldOptionsProvider.php b/src/Form/Service/ParticipantFieldOptionsProvider.php index 19a1155..273ec4f 100644 --- a/src/Form/Service/ParticipantFieldOptionsProvider.php +++ b/src/Form/Service/ParticipantFieldOptionsProvider.php @@ -685,6 +685,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider }); } + /** * Generates label for rental insurance checkbox including pricing information. */ diff --git a/src/Service/BookingPriceCalculatorService.php b/src/Service/BookingPriceCalculatorService.php index cdfab25..560b0f5 100644 --- a/src/Service/BookingPriceCalculatorService.php +++ b/src/Service/BookingPriceCalculatorService.php @@ -21,6 +21,10 @@ use App\Form\Model\ParticipantDto; */ class BookingPriceCalculatorService { + public function __construct( + private readonly ParticipantEligibilityService $participantEligibilityService + ) { + } /** * Calculates comprehensive pricing breakdown for a booking. * @@ -89,6 +93,8 @@ class BookingPriceCalculatorService /** * Calculates pricing for all selected services across all participants, grouped by subtype. * + * Only includes services from eligible participants (those with available skipasses for their age). + * * @param BookingDtoInterface $bookingDto The booking data containing participants and their service selections * * @return array Array of service groups with each group containing services of the same subtype @@ -101,10 +107,15 @@ class BookingPriceCalculatorService return []; } - // Aggregate service selections across all participants + // Aggregate service selections across all eligible participants $serviceAggregation = []; - foreach ($participants as $participant) { + foreach ($participants as $participantIndex => $participant) { + // Skip ineligible participants (no skipasses available for their age) + if (false === $this->participantEligibilityService->isParticipantEligible($bookingDto, $participantIndex)) { + continue; + } + $this->aggregateParticipantServices($participant, $serviceAggregation); } @@ -285,6 +296,8 @@ class BookingPriceCalculatorService * - Zustieg: Sum of all pickup prices and base transportation costs (positive and negative) * - Parkplatz: Sum of all parking service prices * + * Only includes transportation costs from eligible participants. + * * Note: Transportation discounts will be handled generically by groupServicesBySubtype as "Beförderung - Rabatt" * * @param BookingDtoInterface $bookingDto The booking data containing participants @@ -301,7 +314,12 @@ class BookingPriceCalculatorService $pickupParticipants = 0; $parkingParticipants = 0; - foreach ($participants as $participant) { + foreach ($participants as $participantIndex => $participant) { + // Skip ineligible participants (no skipasses available for their age) + if (false === $this->participantEligibilityService->isParticipantEligible($bookingDto, $participantIndex)) { + continue; + } + $participantPickupCost = 0.0; $participantParkingCost = 0.0; diff --git a/src/Service/BookingService.php b/src/Service/BookingService.php index 13ddf2b..be3aeaf 100644 --- a/src/Service/BookingService.php +++ b/src/Service/BookingService.php @@ -19,6 +19,7 @@ class BookingService public function __construct( private readonly TravelDataService $travelDataService, private readonly BookingPriceCalculatorService $priceCalculator, + private readonly ParticipantEligibilityService $participantEligibilityService, ) { } @@ -149,7 +150,7 @@ class BookingService * Converts a Room model into a RoomSelectionDto with the specified quantity * selection. Used during booking initialization to create selectable room options. * - * @param Room $room The room model to convert + * @param Room $room The room model to convert * @param array $roomsIdsAndQuantities Array of room ID to quantity mappings * * @return RoomSelectionDto The room selection DTO @@ -329,6 +330,10 @@ class BookingService * and pricing calculations, resolving timing issues where mandatory services * were only selected during form rendering via choice_attr callbacks. * + * Mandatory services are only preselected for eligible participants - those who + * have at least one skipass available for their age. Ineligible participants are + * skipped to prevent their mandatory services from being included in pricing. + * * @param BookingCreateDto $bookingDto The booking DTO to update with mandatory services */ public function preselectMandatoryServices(BookingCreateDto $bookingDto): void @@ -337,11 +342,16 @@ class BookingService $mandatoryServices = array_filter($additionalServices, fn ($service) => true === $service->mandatory); // Pre-select mandatory services for each participant - foreach ($bookingDto->participants as $participant) { + foreach ($bookingDto->participants as $participantIndex => $participant) { if (null === $participant->dateOfBirth) { continue; // Skip participants without age information } + // Skip ineligible participants (no skipasses available for their age) + if (false === $this->participantEligibilityService->isParticipantEligible($bookingDto, $participantIndex)) { + continue; + } + // Get age-appropriate mandatory services for this participant $ageAppropriateServices = array_filter($mandatoryServices, function ($service) use ($bookingDto, $participant) { if (null === $service->ageFrom && null === $service->ageTo) { diff --git a/src/Service/ParticipantEligibilityService.php b/src/Service/ParticipantEligibilityService.php new file mode 100644 index 0000000..e334dac --- /dev/null +++ b/src/Service/ParticipantEligibilityService.php @@ -0,0 +1,117 @@ +getParticipant($participantIndex); + + if (null === $participant || null === $participant->dateOfBirth) { + return false; + } + + // Create cache key based on participant's birth date, travel date, and index + $cacheKey = sprintf( + 'participant_eligibility_%s_%s_%d', + $participant->dateOfBirth->format('Y-m-d'), + $bookingDto->travel->dateFrom->format('Y-m-d'), + $participantIndex + ); + + return Blink::global()->once($cacheKey, function () use ($bookingDto, $participantIndex) { + $allSkiPasses = $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_SKI_PASS, true, true); + $availableSkiPasses = array_filter( + $allSkiPasses, + fn (Service $service) => $this->isSkiPassAvailableForParticipant($service, $bookingDto, $participantIndex) + ); + + return !empty($availableSkiPasses); + }); + } + + /** + * Checks if a skipass service is available for the given participant based on age constraints. + */ + private function isSkiPassAvailableForParticipant(Service $service, BookingDtoInterface $bookingDto, int $participantIndex): bool + { + $participant = $bookingDto->getParticipant($participantIndex); + + if (null === $participant || null === $participant->dateOfBirth) { + return false; + } + + // No age constraints = available to all + if (null === $service->ageConstraintType) { + return true; + } + + $travelStartDate = CarbonImmutable::instance($bookingDto->travel->dateFrom); + $birthDate = CarbonImmutable::instance($participant->dateOfBirth); + $ageAtTravelStart = $travelStartDate->diffInYears($birthDate); + $birthYear = (int) $birthDate->format('Y'); + + $constraintType = $service->ageConstraintType ?? 'absolute_age'; + + if ('absolute_age' === $constraintType || 'mixed' === $constraintType) { + $minAge = $service->ageFrom; + $maxAge = $service->ageTo; + + if (null !== $minAge && $ageAtTravelStart < $minAge) { + return false; + } + + if (null !== $maxAge && $ageAtTravelStart > $maxAge) { + return false; + } + } + + if ('birth_year' === $constraintType || 'mixed' === $constraintType) { + $minBirthYear = $service->birthYearFrom; + $maxBirthYear = $service->birthYearTo; + + if (null !== $minBirthYear && $birthYear < $minBirthYear) { + return false; + } + + if (null !== $maxBirthYear && $birthYear > $maxBirthYear) { + return false; + } + } + + return true; + } +} diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index 8ab5f84..9ebfcbe 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -25,6 +25,7 @@ class AppExtension extends AbstractExtension { return [ new TwigFunction('icon', [AppRuntime::class, 'renderIcon'], ['needs_environment' => true, 'is_safe' => ['html']]), + new TwigFunction('is_participant_eligible', [AppRuntime::class, 'isParticipantEligible']), ]; } } diff --git a/src/Twig/AppRuntime.php b/src/Twig/AppRuntime.php index 188c23a..be8b713 100644 --- a/src/Twig/AppRuntime.php +++ b/src/Twig/AppRuntime.php @@ -3,6 +3,8 @@ namespace App\Twig; use App\BusProNet\DataProvider\CountryDataProvider; +use App\Form\Model\BookingDtoInterface; +use App\Service\ParticipantEligibilityService; use Twig\Environment; use Twig\Extension\RuntimeExtensionInterface; use Twig\Extra\Intl\IntlExtension; @@ -12,6 +14,7 @@ class AppRuntime implements RuntimeExtensionInterface public function __construct( private readonly IntlExtension $intlExtension, private readonly CountryDataProvider $countryDataProvider, + private readonly ParticipantEligibilityService $participantEligibilityService, ) { } @@ -93,4 +96,9 @@ class AppRuntime implements RuntimeExtensionInterface return $this->countryDataProvider->get($nationality)?->nationality; } + + public function isParticipantEligible(BookingDtoInterface $bookingDto, int $participantIndex): bool + { + return $this->participantEligibilityService->isParticipantEligible($bookingDto, $participantIndex); + } } diff --git a/templates/booking/create_step_2.html.twig b/templates/booking/create_step_2.html.twig index 0dc36e5..a339998 100644 --- a/templates/booking/create_step_2.html.twig +++ b/templates/booking/create_step_2.html.twig @@ -63,12 +63,15 @@
{% for participant in form.participants %} {% set participantDataValid = participant.vars.valid %} + {% set participantData = participant.vars.data %} + {% set hasDateOfBirth = participantData and participantData.dateOfBirth %} + {% set isEligible = hasDateOfBirth and is_participant_eligible(bookingCreateDto, loop.index0) %}
Teilnehmer:in {{ loop.index }} - {% if participantPrices is defined and participantPrices[loop.index0] is defined and participantPrices[loop.index0] > 0.0 %} + {% if isEligible and participantPrices is defined and participantPrices[loop.index0] is defined and participantPrices[loop.index0] > 0.0 %} €{{ participantPrices[loop.index0]|number_format(2, ',', '.') }} @@ -118,10 +121,6 @@ {% endif %}
- {# Services hint - shown when date of birth is missing #} - {% set participantData = participant.vars.data %} - {% set hasDateOfBirth = participantData and participantData.dateOfBirth %} - {% if not hasDateOfBirth %}
@@ -133,175 +132,187 @@

- {% endif %} - -
- {{ _self.service_field(participant, 'skiPass', 'Skipass', { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} - - {{ _self.service_field(participant, 'courses', 'Kurse', { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} - - {{ _self.service_field(participant, 'additionalServices', 'Zusatzleistungen', { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} - - {{ _self.service_field(participant, 'rentals', 'Leihmaterial', { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} - - {{ _self.checkbox_field(participant, 'rentalInsurance', 'Leihmaterial-Versicherung', { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} - - {{ _self.service_field(participant, 'board', 'Verpflegung', { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} - - {# Insurance field OR assigned insurance display for dependent participants #} - {% set participantData = participant.vars.data %} - {% set showBulkInsurance = loop.index > 1 and form.vars.data.participants[0].bulkInsuranceBooking %} - - {% if showBulkInsurance %} -
- Reiseversicherung -
- {% if participantData.insurance %} - {{ participantData.insurance.label }} - {% if participantData.insurance.price and participantData.insurance.price > 0 %} - (€{{ participantData.insurance.price|number_format(2, ',', '.') }}) - {% endif %} - – wie Anmelder - {% else %} - wie Anmelder - {% endif %} -
-
- {% else %} -
- Reiseversicherung - - {# Bulk insurance booking checkbox (applicant only) #} - {% if participant.bulkInsuranceBooking is defined %} - {{ form_row(participant.bulkInsuranceBooking) }} - {% endif %} - - {% if participant.insurance is defined %} - {{ form_row(participant.insurance, { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - }, - 'label': false - }) }} - {% else %} -
Nicht wählbar
- {% endif %} -
- {% endif %} -
- - {# Transportation Services Section #} -
-

Anreise

-
-
- {% if participant.transportationOutbound is defined %} - {{ form_row(participant.transportationOutbound, { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} - {% endif %} - {% if participant.pickupOutbound is defined %} -
- {{ form_row(participant.pickupOutbound, { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} -
- {% endif %} - {% if participant.parking is defined %} -
- {{ form_row(participant.parking, { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} -
- {% endif %} - {% if participant.licensePlate is defined %} -
- {{ form_row(participant.licensePlate, { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} -
- {% endif %} - -
-
- {% if participant.transportationInbound is defined %} - {{ form_row(participant.transportationInbound, { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} - {% endif %} - {% if participant.pickupInbound is defined %} -
- {{ form_row(participant.pickupInbound, { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path('app_booking_create_step_2_refresh'), - 'hx-swap': 'none' - } - }) }} -
- {% endif %} + {% elseif not isEligible %} +
+
+ + + +

+ Buchung wegen des Alters von Teilnehmer:in {{ loop.index }} nicht möglich +

+ {% endif %} -
+ {% if isEligible %} +
+ {{ _self.service_field(participant, 'skiPass', 'Skipass', { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} + + {{ _self.service_field(participant, 'courses', 'Kurse', { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} + + {{ _self.service_field(participant, 'additionalServices', 'Zusatzleistungen', { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} + + {{ _self.service_field(participant, 'rentals', 'Leihmaterial', { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} + + {{ _self.checkbox_field(participant, 'rentalInsurance', 'Leihmaterial-Versicherung', { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} + + {{ _self.service_field(participant, 'board', 'Verpflegung', { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} + + {# Insurance field OR assigned insurance display for dependent participants #} + {% set participantData = participant.vars.data %} + {% set showBulkInsurance = loop.index > 1 and form.vars.data.participants[0].bulkInsuranceBooking %} + + {% if showBulkInsurance %} +
+ Reiseversicherung +
+ {% if participantData.insurance %} + {{ participantData.insurance.label }} + {% if participantData.insurance.price and participantData.insurance.price > 0 %} + (€{{ participantData.insurance.price|number_format(2, ',', '.') }}) + {% endif %} + – wie Anmelder + {% else %} + wie Anmelder + {% endif %} +
+
+ {% else %} +
+ Reiseversicherung + + {# Bulk insurance booking checkbox (applicant only) #} + {% if participant.bulkInsuranceBooking is defined %} + {{ form_row(participant.bulkInsuranceBooking) }} + {% endif %} + + {% if participant.insurance is defined %} + {{ form_row(participant.insurance, { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + }, + 'label': false + }) }} + {% else %} +
Nicht wählbar
+ {% endif %} +
+ {% endif %} +
+ + {# Transportation Services Section #} +
+

Anreise

+
+
+ {% if participant.transportationOutbound is defined %} + {{ form_row(participant.transportationOutbound, { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} + {% endif %} + {% if participant.pickupOutbound is defined %} +
+ {{ form_row(participant.pickupOutbound, { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} +
+ {% endif %} + {% if participant.parking is defined %} +
+ {{ form_row(participant.parking, { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} +
+ {% endif %} + {% if participant.licensePlate is defined %} +
+ {{ form_row(participant.licensePlate, { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} +
+ {% endif %} + +
+
+ {% if participant.transportationInbound is defined %} + {{ form_row(participant.transportationInbound, { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} + {% endif %} + {% if participant.pickupInbound is defined %} +
+ {{ form_row(participant.pickupInbound, { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path('app_booking_create_step_2_refresh'), + 'hx-swap': 'none' + } + }) }} +
+ {% endif %} +
+
+
+ {% endif %}