getOrCreateBookingCreateDto($this->bookingService, $request); if ($result instanceof Response) { return $result; } $bookingCreateDto = $result; // Enrich with fresh availability data $this->enrichWithFreshAvailabilities($bookingCreateDto); // Validate step access if ($redirect = $this->validateStepAccess($bookingCreateDto, 2)) { return $redirect; } // 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, [ 'attr' => ['novalidate' => 'novalidate'], 'validation_groups' => ['booking_create_step_2'], ]); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $bookingCreateDto->currentStep = 3; $this->bookingService->saveBookingCreateDto($request, $bookingCreateDto); return $this->redirectToRoute('app_booking_create_step_3'); } $summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto); $roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto); $availableRooms = $bookingCreateDto->travel->getAvailableRooms(); $groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms); $participantPrices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingCreateDto); return $this->render('booking/create_step_2.html.twig', [ 'bookingCreateDto' => $bookingCreateDto, 'participantsCount' => $participantsCount, 'pricingData' => $summary['pricing'], 'assignmentCounts' => $roomAssignmentCounts, 'participantPrices' => $participantPrices, 'form' => $form->createView(), 'groupedSelectedRooms' => $groupedSelectedRooms, ]); } /** * Handles HTMX requests for dynamic form updates when room selections change by submitting the form * without validation and returning a freshly rendered instance. */ #[Route('/bookings/create/participants/refresh', name: 'app_booking_create_step_2_refresh', methods: ['POST'])] public function refreshParticipantForm(Request $request): Response { $result = $this->getOrCreateBookingCreateDtoForHtmx($this->bookingService, $request); if ($result instanceof Response) { return $result; } $bookingCreateDto = $result; // Enrich with fresh availability data $this->enrichWithFreshAvailabilities($bookingCreateDto); $participantsCount = $this->getParticipantsCount($bookingCreateDto); // Process form data without validation to capture current state $form = $this->createForm(BookingCreateStep2Type::class, $bookingCreateDto, [ 'attr' => ['novalidate' => 'novalidate'], 'validation_groups' => false, ]); $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); $roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto); $availableRooms = $bookingCreateDto->travel->getAvailableRooms(); $groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms); $participantPrices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingCreateDto); // The DTO is now updated with the latest selection and submitted data has been cleaned. // We can now render the blocks with the fresh data. return $this->htmxOobResponse( 'booking/create_step_2.html.twig', ['participants_form', 'booking_summary'], [ 'form' => $form->createView(), 'bookingCreateDto' => $bookingCreateDto, 'participantsCount' => $participantsCount, 'pricingData' => $summary['pricing'], 'assignmentCounts' => $roomAssignmentCounts, 'participantPrices' => $participantPrices, 'groupedSelectedRooms' => $groupedSelectedRooms, ] ); } /** * Calculates the total number of participants based on room selections. * * @param BookingCreateDto $bookingCreateDto The booking DTO containing room selections * * @return int Total number of participants required */ private function getParticipantsCount(BookingCreateDto $bookingCreateDto): int { return $this ->bookingService ->getParticipantsCount($bookingCreateDto->roomSelections, $bookingCreateDto->travel); } /** * Ensures the booking DTO has the correct number of participant objects. * * Creates or reuses participant DTOs to match the required participant count * based on room selections. Preserves existing participant data when possible * and assigns proper index values. * * @param BookingCreateDto $bookingCreateDto The booking DTO to update */ private function ensureCorrectNumberOfParticipants(BookingCreateDto $bookingCreateDto): void { $participantsCount = $this->getParticipantsCount($bookingCreateDto); $participants = $bookingCreateDto->participants; $bookingCreateDto->participants = []; for ($i = 0; $i < $participantsCount; ++$i) { $participant = $participants[$i] ?? new ParticipantDto(); $participant->index = $i; $bookingCreateDto->participants[$i] = $participant; } } /** * Enriches travel data with cached availability information from BusProNet API. * * Fetches availability data with short-term caching and patches the travel object * to ensure service availability is reasonably up-to-date while reducing API calls. * This is essential for accurate pricing and service selection during the booking process. * * @param BookingCreateDto $bookingCreateDto The booking DTO containing travel data to enrich */ private function enrichWithFreshAvailabilities(BookingCreateDto $bookingCreateDto): void { $dateId = $bookingCreateDto->travel->id; $availabilities = $this->travelDataService->getAvailabilityDataCached($dateId); if (null !== $availabilities) { $this->travelDataService->patchAvailabilities($bookingCreateDto->travel, $availabilities); } } }