diff --git a/src/Form/Model/BookingDto.php b/src/Form/Model/BookingDto.php index 2c52eafa..5a5e7bed 100644 --- a/src/Form/Model/BookingDto.php +++ b/src/Form/Model/BookingDto.php @@ -58,6 +58,15 @@ class BookingDto */ public string $bookingStatus = 'F'; + /** + * Booking status the travel itself calls for when the booking starts, before any room is + * selected and before the create-flow status rules run. Step 1 falls back to it whenever + * no on-request room is selected, so the status can move back from 'A'. + * + * Null in sessions started before it was introduced. + */ + public ?string $initialBookingStatus = null; + /** * Reference to booking entity (only populated in edit mode). */ diff --git a/src/Service/BookingConfigurator.php b/src/Service/BookingConfigurator.php index 32725eed..0c04a869 100644 --- a/src/Service/BookingConfigurator.php +++ b/src/Service/BookingConfigurator.php @@ -85,6 +85,7 @@ class BookingConfigurator ? $this->agencyLoader->loadById($agencyId)?->code : null; $bookingCreateDto->bookingStatus = $bookingStatus; + $bookingCreateDto->initialBookingStatus = $bookingStatus; $this->applyCreateBookingStatusRules($bookingCreateDto); $this->bookingSessionService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE); @@ -659,31 +660,43 @@ class BookingConfigurator /** * Updates booking status based on selected room requirements. * - * Checks if any selected room has inquiry-only status. If so, forces - * the entire booking to inquiry mode. This override happens after room - * selection in Step 1 and respects the business rule: if ANY room requires - * inquiry, the whole booking becomes an inquiry. + * If ANY selected room is only bookable on request, the whole booking becomes an inquiry. + * Otherwise the status returns to what the travel called for when the booking started, + * and the create-flow rules run again, so a customer who swaps an on-request room for a + * free one in Step 1 is no longer submitted as an inquiry. An 'A' from the Step 3 BusPro + * fallback is cleared the same way; Step 3 validates again and re-applies it if needed. * * @param BookingDto $bookingDto The booking DTO to update */ public function updateBookingStatusFromRoomSelection(BookingDto $bookingDto): void { - // Skip if already inquiry - no need to check - if ('A' === $bookingDto->bookingStatus) { + if (true === $this->hasOnRequestRoomSelected($bookingDto)) { + $bookingDto->bookingStatus = Constants::BOOKING_STATUS_INQUIRY; + return; } - // Check selected rooms for inquiry-only status + // Sessions started before the initial status was recorded keep their current status + if (null === $bookingDto->initialBookingStatus) { + return; + } + + $bookingDto->bookingStatus = $bookingDto->initialBookingStatus; + $this->applyCreateBookingStatusRules($bookingDto); + } + + private function hasOnRequestRoomSelected(BookingDto $bookingDto): bool + { foreach ($bookingDto->getSelectedRooms() as $roomSelection) { $room = $bookingDto->travel->getRoomById($roomSelection->id); - if ($room + if (null !== $room && Constants::STATUS_ON_REQUEST === $room->status && $room->available > 0) { - $bookingDto->bookingStatus = 'A'; - - return; + return true; } } + + return false; } /** diff --git a/tests/Service/BookingConfiguratorStatusTest.php b/tests/Service/BookingConfiguratorStatusTest.php index dbc79d2b..5f9b84c2 100644 --- a/tests/Service/BookingConfiguratorStatusTest.php +++ b/tests/Service/BookingConfiguratorStatusTest.php @@ -228,6 +228,56 @@ class BookingConfiguratorStatusTest extends TestCase $this->assertSame('A', $bookingDto->bookingStatus); } + public function testRoomSelectionSwitchesToInquiryForOnRequestRoom(): void + { + $bookingDto = $this->startMixedRoomsBooking(); + + $this->selectRoom($bookingDto, 2); + $this->bookingService->updateBookingStatusFromRoomSelection($bookingDto); + + $this->assertSame('A', $bookingDto->bookingStatus); + } + + public function testRoomSelectionRevertsInquiryWhenOnRequestRoomIsDeselected(): void + { + $bookingDto = $this->startMixedRoomsBooking(); + + $this->selectRoom($bookingDto, 2); + $this->bookingService->updateBookingStatusFromRoomSelection($bookingDto); + $this->selectRoom($bookingDto, 1); + $this->bookingService->updateBookingStatusFromRoomSelection($bookingDto); + + $this->assertSame('F', $bookingDto->bookingStatus); + } + + public function testRoomSelectionKeepsInquiryRequiredByTravel(): void + { + $travel = $this->createTravelWithRooms([ + ['status' => Constants::STATUS_AVAILABLE, 'available' => 5], + ]); + $travel->allowedBookingStatus = ['A']; + $this->travelDataService->method('getTravelData')->willReturn($travel); + + $bookingDto = $this->bookingService->startFreshBooking($this->createRequestWithSession(), 123, 456); + + $this->selectRoom($bookingDto, 1); + $this->bookingService->updateBookingStatusFromRoomSelection($bookingDto); + + $this->assertSame('A', $bookingDto->bookingStatus); + } + + public function testRoomSelectionKeepsStatusOfSessionsWithoutInitialStatus(): void + { + $bookingDto = $this->startMixedRoomsBooking(); + $bookingDto->initialBookingStatus = null; + $bookingDto->bookingStatus = 'A'; + + $this->selectRoom($bookingDto, 1); + $this->bookingService->updateBookingStatusFromRoomSelection($bookingDto); + + $this->assertSame('A', $bookingDto->bookingStatus); + } + public function testApplyCreateBookingStatusRulesSetsOptionFromRegistry(): void { $bookingStatusRuleRegistry = $this->createStub(BookingStatusRuleRegistry::class); @@ -338,6 +388,27 @@ class BookingConfiguratorStatusTest extends TestCase return $group; } + /** + * Starts a booking on a travel with a free room (id 1) and an on-request room (id 2). + */ + private function startMixedRoomsBooking(): BookingDto + { + $travel = $this->createTravelWithRooms([ + ['status' => Constants::STATUS_AVAILABLE, 'available' => 5], + ['status' => Constants::STATUS_ON_REQUEST, 'available' => 3], + ]); + $this->travelDataService->method('getTravelData')->willReturn($travel); + + return $this->bookingService->startFreshBooking($this->createRequestWithSession(), 123, 456); + } + + private function selectRoom(BookingDto $bookingDto, int $roomId): void + { + foreach ($bookingDto->roomSelections as $roomSelection) { + $roomSelection->quantity = $roomId === $roomSelection->id ? 1 : 0; + } + } + private function createRequestWithSession(): Request { $session = new Session(new MockArraySessionStorage());