fix: refresh live room availability during create flow
This commit is contained in:
1 parent
a73e8a593f
commit
cdce7d57e0
26 files changed
+1976
-54
No files matched your search
@@ -4,6 +4,7 @@ namespace App\Controller\Api;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\Exception\ApiClientException;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\BusProNet\Utility\DateCodeUtility;
|
||||
use App\Service\TravelDataProvider;
|
||||
@@ -91,7 +92,11 @@ class TravelController extends AbstractController
|
||||
try {
|
||||
$result = $this->apiClient->getHotelAvailability($dateId, $hotelId, $dateTo);
|
||||
|
||||
return $this->json($result);
|
||||
if ($result instanceof Notification) {
|
||||
return $this->json(['error' => $result->message, 'type' => 'api_error']);
|
||||
}
|
||||
|
||||
return $this->json($result->getItems(), Response::HTTP_OK, [], ['groups' => ['api:list', 'api:single']]);
|
||||
} catch (ApiClientException $e) {
|
||||
return $this->json(['error' => $e->getMessage(), 'type' => 'api_error']);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use App\Exception\BookingSessionNotFoundException;
|
||||
use App\Exception\HotelNotInTravelException;
|
||||
use App\Exception\TravelNotFoundException;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Htmx\HxTrait;
|
||||
use App\Service\BookingAvailabilityRefreshResult;
|
||||
use App\Service\BookingSessionManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -20,6 +22,8 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
*/
|
||||
abstract class AbstractBookingCreateController extends AbstractController
|
||||
{
|
||||
use HxTrait;
|
||||
|
||||
protected function loadBookingCreateDto(BookingSessionManager $bookingSessionService, Request $request): BookingDto
|
||||
{
|
||||
return $bookingSessionService->getOrCreateBookingCreateDto($request);
|
||||
@@ -91,4 +95,53 @@ abstract class AbstractBookingCreateController extends AbstractController
|
||||
|
||||
return $this->redirectToRoute($route);
|
||||
}
|
||||
|
||||
protected function redirectIfRoomSelectionsStale(
|
||||
Request $request,
|
||||
BookingDto $bookingDto,
|
||||
BookingSessionManager $bookingSessionService,
|
||||
bool $forceRefresh = false,
|
||||
?\Closure $onRefreshFailure = null,
|
||||
): ?Response {
|
||||
$result = $bookingSessionService->refreshCreateAvailability($request, $bookingDto, $forceRefresh);
|
||||
|
||||
if (true === $forceRefresh && false === $result->availabilityRefreshed) {
|
||||
if (null !== $onRefreshFailure) {
|
||||
return $onRefreshFailure();
|
||||
}
|
||||
|
||||
$this->addFlash('error', 'Die Verfügbarkeit konnte gerade nicht aktualisiert werden. Bitte versuche es erneut.');
|
||||
|
||||
return $this->htmxRedirect($request, $this->generateUrl('app_booking_create_error'));
|
||||
}
|
||||
|
||||
if ($redirect = $this->redirectIfNoRoomsAvailable($request, $result)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
if (false === $result->roomSelectionQuantityReset) {
|
||||
if (true === $forceRefresh && true === $result->roomSelectionSnapshotChanged) {
|
||||
$this->addFlash('warning', 'Die Zimmerpreise haben sich aktualisiert. Bitte überprüfe deine Buchung erneut.');
|
||||
|
||||
return $this->htmxRedirect($request, $this->generateUrl('app_booking_create_step_4'));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->addFlash('error', 'Mindestens eine Zimmerauswahl ist nicht mehr verfügbar. Bitte wähle die Zimmer erneut.');
|
||||
|
||||
return $this->htmxRedirect($request, $this->generateUrl('app_booking_create_step_1'));
|
||||
}
|
||||
|
||||
protected function redirectIfNoRoomsAvailable(Request $request, BookingAvailabilityRefreshResult $result): ?Response
|
||||
{
|
||||
if (true === $result->hasAvailableRooms) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->addFlash('error', 'Leider sind für diese Reise aktuell keine Zimmer verfügbar.');
|
||||
|
||||
return $this->htmxRedirect($request, $this->generateUrl('app_booking_create_error'));
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,19 @@ class Step1Controller extends AbstractBookingCreateController
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$availabilityRefresh = $this->bookingSessionService->refreshCreateAvailability($request, $bookingCreateDto);
|
||||
if ($availabilityRefresh->roomSelectionSnapshotChanged) {
|
||||
$oldRoomSelectionSnapshot = $this->bookingSessionService->saveBaselineSnapshot($request, $bookingCreateDto);
|
||||
}
|
||||
|
||||
if ($availabilityRefresh->roomSelectionQuantityReset) {
|
||||
$this->addFlash('warning', 'Mindestens eine Zimmerauswahl ist nicht mehr verfügbar und wurde zurückgesetzt.');
|
||||
}
|
||||
|
||||
if ($redirect = $this->redirectIfNoRoomsAvailable($request, $availabilityRefresh)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$form = $this->createForm(BookingCreateStep1Type::class, $bookingCreateDto, [
|
||||
'validation_groups' => ['booking_create_step_1'],
|
||||
]);
|
||||
|
||||
@@ -18,7 +18,6 @@ use App\Service\BookingCreateContextFactory;
|
||||
use App\Service\BookingSessionManager;
|
||||
use App\Service\ParticipantDataPrefiller;
|
||||
use App\Service\RoomAssigner;
|
||||
use App\Service\TravelDataProvider;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
@@ -35,7 +34,6 @@ class Step2Controller extends AbstractBookingCreateController
|
||||
private readonly BookingConfigurator $bookingService,
|
||||
private readonly BookingSessionManager $bookingSessionService,
|
||||
private readonly BookingCreateContextFactory $createContextFactory,
|
||||
private readonly TravelDataProvider $travelDataService,
|
||||
private readonly RoomAssigner $roomAssignmentService,
|
||||
private readonly ParticipantDataPrefiller $prepopulationService,
|
||||
) {
|
||||
@@ -58,8 +56,11 @@ class Step2Controller extends AbstractBookingCreateController
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
// Enrich with fresh availability data
|
||||
$this->travelDataService->enrichWithFreshAvailabilities($bookingCreateDto->travel);
|
||||
if ($redirect = $this->redirectIfRoomSelectionsStale($request, $bookingCreateDto, $this->bookingSessionService)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$this->bookingService->updateBookingStatusFromRoomSelection($bookingCreateDto);
|
||||
|
||||
// Ensure correct number of participants first, then prepopulate the applicant if needed.
|
||||
$this->ensureCorrectNumberOfParticipants($bookingCreateDto);
|
||||
|
||||
@@ -13,8 +13,6 @@ use App\Service\BookingSessionManager;
|
||||
use App\Service\ParticipantDataPrefiller;
|
||||
use App\Service\ParticipantFormSupport;
|
||||
use App\Service\RoomPricingCalculator;
|
||||
use App\Service\TravelDataProvider;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -23,7 +21,7 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||
/**
|
||||
* Handles participant editing and refresh actions within booking step 2.
|
||||
*/
|
||||
class Step2ParticipantController extends AbstractController
|
||||
class Step2ParticipantController extends AbstractBookingCreateController
|
||||
{
|
||||
use HxTrait;
|
||||
|
||||
@@ -31,7 +29,6 @@ class Step2ParticipantController extends AbstractController
|
||||
private readonly BookingConfigurator $bookingService,
|
||||
private readonly BookingSessionManager $bookingSessionService,
|
||||
private readonly BookingCreateContextFactory $createContextFactory,
|
||||
private readonly TravelDataProvider $travelDataService,
|
||||
private readonly ParticipantDataPrefiller $prepopulationService,
|
||||
private readonly ParticipantFormSupport $participantFormSupportService,
|
||||
) {
|
||||
@@ -51,48 +48,53 @@ class Step2ParticipantController extends AbstractController
|
||||
if ($result instanceof Response) {
|
||||
return $result;
|
||||
}
|
||||
$bookingDto = $result;
|
||||
$bookingCreateDto = $result;
|
||||
|
||||
$this->participantFormSupportService->ensureParticipantExists($bookingDto, $index);
|
||||
$this->participantFormSupportService->ensureParticipantExists($bookingCreateDto, $index);
|
||||
|
||||
$this->travelDataService->enrichWithFreshAvailabilities($bookingDto->travel);
|
||||
if ($redirect = $this->redirectIfRoomSelectionsStale($request, $bookingCreateDto, $this->bookingSessionService)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$form = $this->createParticipantForm($bookingDto, $index);
|
||||
$this->bookingService->updateBookingStatusFromRoomSelection($bookingCreateDto);
|
||||
$this->bookingSessionService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
|
||||
|
||||
$form = $this->createParticipantForm($bookingCreateDto, $index);
|
||||
|
||||
$form->handleRequest($request);
|
||||
$isSubmitted = $form->isSubmitted();
|
||||
|
||||
$isDummyDataFill = $this
|
||||
->prepopulationService
|
||||
->isDummyDataFillRequested($bookingDto->participants[$index], $bookingDto->getMode())
|
||||
->isDummyDataFillRequested($bookingCreateDto->participants[$index], $bookingCreateDto->getMode())
|
||||
;
|
||||
if (true === $isSubmitted && true === $isDummyDataFill) {
|
||||
$this->prepopulationService->fillDummyParticipant($bookingDto->participants[$index], $index);
|
||||
$this->prepopulationService->fillDummyParticipant($bookingCreateDto->participants[$index], $index);
|
||||
|
||||
$this->bookingService->preselectDefaultServices($bookingDto);
|
||||
$this->bookingService->applyCreateBookingStatusRules($bookingDto);
|
||||
$this->bookingSessionService->saveBookingDto($request, $bookingDto, BookingDto::MODE_CREATE);
|
||||
$this->bookingService->preselectDefaultServices($bookingCreateDto);
|
||||
$this->bookingService->applyCreateBookingStatusRules($bookingCreateDto);
|
||||
$this->bookingSessionService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
|
||||
|
||||
$form = $this->createParticipantForm($bookingDto, $index);
|
||||
$form = $this->createParticipantForm($bookingCreateDto, $index);
|
||||
|
||||
return $this->renderParticipantForm($form, $index, $bookingDto);
|
||||
return $this->renderParticipantForm($form, $index, $bookingCreateDto);
|
||||
}
|
||||
|
||||
if (true === $isSubmitted) {
|
||||
$this->bookingService->preselectDefaultServices($bookingDto);
|
||||
$this->bookingService->applyCreateBookingStatusRules($bookingDto);
|
||||
$this->bookingService->preselectDefaultServices($bookingCreateDto);
|
||||
$this->bookingService->applyCreateBookingStatusRules($bookingCreateDto);
|
||||
}
|
||||
|
||||
$notifications = $this->participantFormSupportService->collectAndClearNotifications($bookingDto);
|
||||
$notifications = $this->participantFormSupportService->collectAndClearNotifications($bookingCreateDto);
|
||||
|
||||
if (true === $isSubmitted && true === $form->isValid()) {
|
||||
$this->bookingSessionService->saveBookingDto($request, $bookingDto, BookingDto::MODE_CREATE);
|
||||
$this->bookingSessionService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
|
||||
$this->addNotificationsAsFlashMessages($notifications);
|
||||
|
||||
return $this->redirectToRoute('app_booking_create_step_2');
|
||||
}
|
||||
|
||||
return $this->renderParticipantForm($form, $index, $bookingDto);
|
||||
return $this->renderParticipantForm($form, $index, $bookingCreateDto);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +116,11 @@ class Step2ParticipantController extends AbstractController
|
||||
|
||||
$this->participantFormSupportService->ensureParticipantExists($bookingDto, $index);
|
||||
|
||||
$this->travelDataService->enrichWithFreshAvailabilities($bookingDto->travel);
|
||||
if ($redirect = $this->redirectIfRoomSelectionsStale($request, $bookingDto, $this->bookingSessionService)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$this->bookingService->updateBookingStatusFromRoomSelection($bookingDto);
|
||||
|
||||
return $this->handleParticipantRefresh(
|
||||
$request,
|
||||
|
||||
@@ -61,6 +61,10 @@ class Step3Controller extends AbstractBookingCreateController
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
if ($redirect = $this->redirectIfRoomSelectionsStale($request, $bookingCreateDto, $this->bookingSessionService)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$form = $this->createForm(BookingCreateStep3Type::class, $bookingCreateDto);
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
||||
@@ -62,6 +62,10 @@ class Step4Controller extends AbstractBookingCreateController
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
if ($redirect = $this->redirectIfRoomSelectionsStale($request, $bookingCreateDto, $this->bookingSessionService)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$newsletterTargetEmail = $this->resolveNewsletterTargetEmail($bookingCreateDto);
|
||||
$newsletterOptInVisible = null !== $newsletterTargetEmail;
|
||||
|
||||
@@ -72,6 +76,20 @@ class Step4Controller extends AbstractBookingCreateController
|
||||
$form->handleRequest($request);
|
||||
|
||||
if (true === $form->isSubmitted() && true === $form->isValid()) {
|
||||
if ($redirect = $this->redirectIfRoomSelectionsStale(
|
||||
$request,
|
||||
$bookingCreateDto,
|
||||
$this->bookingSessionService,
|
||||
forceRefresh: true,
|
||||
onRefreshFailure: function () use ($bookingCreateDto, $form, $newsletterOptInVisible, $newsletterTargetEmail): Response {
|
||||
$this->addFlash('error', 'Die Verfügbarkeit konnte gerade nicht aktualisiert werden. Bitte versuche es erneut.');
|
||||
|
||||
return $this->renderStepForm($bookingCreateDto, $form, $newsletterOptInVisible, $newsletterTargetEmail);
|
||||
},
|
||||
)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->bookingService->applyCreateBookingStatusRules($bookingCreateDto);
|
||||
|
||||
@@ -238,5 +256,14 @@ class Step4Controller extends AbstractBookingCreateController
|
||||
// Clear travel data cache (both local and remote variants)
|
||||
$this->cache->delete(sprintf('travel_unified_%d_%d_local', $dateId, $hotelId));
|
||||
$this->cache->delete(sprintf('travel_unified_%d_%d_remote', $dateId, $hotelId));
|
||||
|
||||
if (null !== $bookingDto->travel->dateTo) {
|
||||
$this->cache->delete(sprintf(
|
||||
'hotel_availability_%d_%d_%s',
|
||||
$dateId,
|
||||
$hotelId,
|
||||
$bookingDto->travel->dateTo->format('Ymd')
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user