fix: resync room selections with travel data before step 2

This commit is contained in:
fromme committed 2026-09-29 10:59:13 +02:00
1 parent be0e15b3d4
commit d0e7ba017c
5 files changed
+708 -1

No files matched your search

@@ -13,6 +13,7 @@ use App\Form\BookingCreateStep2Type;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Form\Model\RoomSelectionDto;
use App\Model\RoomSelectionAdjustment;
use App\Service\BookingConfigurator;
use App\Service\BookingCreateContextFactory;
use App\Service\BookingSessionManager;
@@ -66,6 +67,12 @@ class Step2Controller extends AbstractBookingCreateController
// Enrich with fresh availability data
$this->travelDataService->enrichWithFreshAvailabilities($bookingCreateDto->travel);
// Resync the room selections with the refreshed travel before any participant count is derived
// from them, and send the customer back to step 1 when a room they picked is gone.
if (null !== $redirect = $this->guardAgainstUnavailableRoomSelection($request, $bookingCreateDto)) {
return $redirect;
}
// Ensure correct number of participants first, then prepopulate the applicant if needed.
if (null !== $redirect = $this->guardAgainstEmptyParticipantList($bookingCreateDto)) {
return $redirect;
@@ -120,6 +127,79 @@ class Step2Controller extends AbstractBookingCreateController
return $this->render('booking/create/step_2.html.twig', $templateData);
}
/**
* Sends the customer back to step 1 when the refreshed travel data no longer covers their rooms.
*
* Deriving the participant count from a room that has disappeared would silently fall back to a
* capacity of one and render too few participants, and a quantity the contingent no longer covers
* reaches the BusPro submit unchecked, because no later step re-reads room availability. The
* selections are resynced and saved before every exit, so step 1 and the neighbouring guard both
* see a collection that matches the rooms the template renders: leaving a stale entry behind would
* fail step 1's own validation on a field it never shows.
*/
private function guardAgainstUnavailableRoomSelection(Request $request, BookingDto $bookingCreateDto): ?RedirectResponse
{
$adjustments = $this->bookingService->reconcileRoomSelections($bookingCreateDto);
$this->bookingSessionService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
if ([] === $bookingCreateDto->travel->getAvailableRooms()) {
$this->handleApiError(
$this->logger,
'Travel has no bookable rooms left while a booking is in progress.',
$this->createRoomContext($bookingCreateDto, $adjustments),
'Für diese Reise sind leider keine Zimmer mehr verfügbar.'
);
return $this->redirectToRoute('app_booking_create_error');
}
if ([] === $adjustments) {
return null;
}
$hasDroppedRoom = false;
foreach ($adjustments as $adjustment) {
$hasDroppedRoom = $hasDroppedRoom || $adjustment->isDropped();
}
$this->handleApiError(
$this->logger,
'Room selection exceeds what the current travel data still offers.',
$this->createRoomContext($bookingCreateDto, $adjustments),
true === $hasDroppedRoom
? 'Dein gewähltes Zimmer ist leider nicht mehr verfügbar. Bitte wähle dein Zimmer erneut.'
: 'Für dein gewähltes Zimmer sind nicht mehr genügend Plätze verfügbar. Wir haben deine Auswahl angepasst, bitte überprüfe sie.'
);
return $this->redirectToRoute('app_booking_create_step_1');
}
/**
* @param list<RoomSelectionAdjustment> $adjustments
*
* @return array<string, mixed>
*/
private function createRoomContext(BookingDto $bookingCreateDto, array $adjustments): array
{
$rooms = [];
foreach ($adjustments as $adjustment) {
$rooms[] = [
'roomId' => $adjustment->roomId,
'label' => $adjustment->label,
'requestedQuantity' => $adjustment->requestedQuantity,
'bookableQuantity' => $adjustment->bookableQuantity,
];
}
return [
'travelId' => $bookingCreateDto->travel->id,
'travelCode' => $bookingCreateDto->travel->code,
'hotelId' => $bookingCreateDto->hotelId,
'rooms' => $rooms,
];
}
/**
* Rejects a room selection that resolves to no participants at all.
*
@@ -190,7 +270,14 @@ class Step2Controller extends AbstractBookingCreateController
$rooms = $travelData->getAvailableRooms();
foreach ($roomSelections as $roomSelection) {
$room = $rooms[$roomSelection->id];
$room = $rooms[$roomSelection->id] ?? null;
if (null === $room) {
// Unreachable from index(), which reconciles the selections first. Kept so a future
// caller cannot silently fall back to a capacity of one and book too few people.
continue;
}
$participantsCount += ($room->minPax ?? 1) * $roomSelection->quantity;
}