diff --git a/src/Controller/Booking/CreateStep2Controller.php b/src/Controller/Booking/CreateStep2Controller.php index 0430bf1..d186bcc 100644 --- a/src/Controller/Booking/CreateStep2Controller.php +++ b/src/Controller/Booking/CreateStep2Controller.php @@ -46,6 +46,10 @@ class CreateStep2Controller extends AbstractController // Ensure correct number of participants $this->ensureCorrectNumberOfParticipants($bookingCreateDto); $participantsCount = $this->getParticipantsCount($bookingCreateDto); + + // Pre-select mandatory services for participants with birth dates + $this->bookingService->preselectMandatoryServices($bookingCreateDto); + $this->bookingService->saveBookingCreateDto($request, $bookingCreateDto); $form = $this->createForm(BookingCreateStep2Type::class, $bookingCreateDto, [ @@ -94,6 +98,16 @@ class CreateStep2Controller extends AbstractController ]); $form->handleRequest($request); + + // Pre-select mandatory services after form processing but before pricing calculation + $this->bookingService->preselectMandatoryServices($bookingCreateDto); + + // Recreate the form with the updated DTO that includes mandatory services + $form = $this->createForm(BookingCreateStep2Type::class, $bookingCreateDto, [ + 'attr' => ['novalidate' => 'novalidate'], + 'validation_groups' => false, + ]); + $this->bookingService->saveBookingCreateDto($request, $bookingCreateDto); $summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto); diff --git a/src/Form/Service/ParticipantFieldOptionsProvider.php b/src/Form/Service/ParticipantFieldOptionsProvider.php index 090975e..1e68e4a 100644 --- a/src/Form/Service/ParticipantFieldOptionsProvider.php +++ b/src/Form/Service/ParticipantFieldOptionsProvider.php @@ -134,9 +134,8 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider $attributes = []; - // Pre-select and make readonly for mandatory services + // Make readonly for mandatory services (pre-selection handled by service layer) if (true === $service->mandatory) { - $attributes['checked'] = true; $attributes['readonly'] = true; $attributes['class'] = 'text-pink'; $attributes['title'] = 'Diese Leistung ist nicht abwählbar'; diff --git a/src/Service/BookingPriceCalculatorService.php b/src/Service/BookingPriceCalculatorService.php index 309d709..0f6a942 100644 --- a/src/Service/BookingPriceCalculatorService.php +++ b/src/Service/BookingPriceCalculatorService.php @@ -286,6 +286,12 @@ class BookingPriceCalculatorService } $subType = $serviceData['subType'] ?? 'other'; + + // Normalize rental subtypes to avoid duplicate sections + if (true === in_array($subType, Constants::TOKEN_RENTALS, true)) { + $subType = 'rentals'; // Normalize all rental subtypes to a single key + } + $isDiscount = $serviceData['totalPrice'] < 0; // Create separate buckets for positive costs and discounts @@ -332,9 +338,10 @@ class BookingPriceCalculatorService Constants::TOKEN_ADDITIONAL => 'Zusatzleistungen', Constants::TOKEN_BOARD => 'Verpflegung', 'transportation' => 'Beförderung', + 'rentals' => 'Leihmaterial', // Normalized rental subtype ]; - // Handle rentals array + // Handle rentals array (keep for backward compatibility with non-normalized subtypes) if (true === in_array($subType, Constants::TOKEN_RENTALS, true)) { return 'Leihmaterial'; } diff --git a/src/Service/BookingService.php b/src/Service/BookingService.php index f51b8c9..a988f09 100644 --- a/src/Service/BookingService.php +++ b/src/Service/BookingService.php @@ -266,4 +266,58 @@ class BookingService return $oldSnapshot !== $newSnapshot; } + + /** + * Pre-selects mandatory services for all participants in the booking DTO. + * + * This method ensures that mandatory services are selected before form rendering + * and pricing calculations, resolving timing issues where mandatory services + * were only selected during form rendering via choice_attr callbacks. + * + * @param BookingCreateDto $bookingDto The booking DTO to update with mandatory services + */ + public function preselectMandatoryServices(BookingCreateDto $bookingDto): void + { + $additionalServices = $bookingDto->travel->getAdditionalServicesBySubTypes(\App\BusProNet\Constants::TOKEN_ADDITIONAL); + $mandatoryServices = array_filter($additionalServices, fn ($service) => true === $service->mandatory); + + // Pre-select mandatory services for each participant + foreach ($bookingDto->participants as $participant) { + if (null === $participant->dateOfBirth) { + continue; // Skip participants without age information + } + + // Get age-appropriate mandatory services for this participant + $ageAppropriateServices = array_filter($mandatoryServices, function ($service) use ($bookingDto, $participant) { + // Use the age evaluator service to check if service is appropriate for participant age + if (null === $service->ageFrom && null === $service->ageTo) { + return true; // No age restrictions + } + + $age = $participant->dateOfBirth->diff($bookingDto->travel->dateFrom)->y; + + if (null !== $service->ageFrom && $age < $service->ageFrom) { + return false; + } + + if (null !== $service->ageTo && $age > $service->ageTo) { + return false; + } + + return true; + }); + + // Add mandatory services to current selections + $currentSelections = $participant->additionalServices ?? []; + $currentServiceIds = array_map(fn ($service) => $service->id, $currentSelections); + + foreach ($ageAppropriateServices as $mandatoryService) { + if (false === in_array($mandatoryService->id, $currentServiceIds, true)) { + $currentSelections[] = $mandatoryService; + } + } + + $participant->additionalServices = $currentSelections; + } + } } diff --git a/templates/booking/create_step_2.html.twig b/templates/booking/create_step_2.html.twig index fc5c4b3..46651f2 100644 --- a/templates/booking/create_step_2.html.twig +++ b/templates/booking/create_step_2.html.twig @@ -58,6 +58,24 @@ {{ form_row(participant.remarksRoom) }} {% endif %} + + {# Services hint - shown when date of birth is missing #} + {% set participantData = participant.vars.data %} + {% set hasDateOfBirth = participantData and participantData.dateOfBirth %} + + {% if not hasDateOfBirth %} +
+
+ + + +

+ Zusatzleistungen sind erst nach Angabe des Geburtsdatums buchbar +

+
+
+ {% endif %} +
{% if participant.skiPass is defined %} {{ form_row(participant.skiPass, {