feat: prevent booking flow without available rooms

This commit is contained in:
Björn Fromme
2025-09-24 11:14:45 +02:00
parent 5642193610
commit eed7ba4d7b
5 changed files with 71 additions and 7 deletions
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Controller\Booking;
use App\Exception\NoRoomsAvailableException;
use App\Form\BookingCreateStep1Type;
use App\Service\BookingService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -32,7 +33,13 @@ class CreateStep1Controller extends AbstractController
#[Route('/bookings/create', name: 'app_booking_create_step_1')]
public function index(Request $request): Response
{
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
try {
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
} catch (NoRoomsAvailableException $e) {
$this->addFlash('error', 'Leider sind für diese Reise aktuell keine Zimmer verfügbar.');
// TODO: Redirect to travel listing or hotel details page
throw $this->createNotFoundException('No rooms available for this travel.');
}
// Get or create baseline snapshot for change detection
$oldRoomSelectionSnapshot = $this->bookingService->getOrCreateBaselineSnapshot($request, $bookingCreateDto);
@@ -83,7 +90,12 @@ class CreateStep1Controller extends AbstractController
#[Route('/bookings/create/room-summary', name: 'app_booking_create_step_1_room_summary', methods: ['POST'])]
public function roomSummary(Request $request): Response
{
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
try {
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
} catch (NoRoomsAvailableException $e) {
// For HTMX requests, return a simple error message
return new Response('<div class="text-red-700 p-4">Keine Zimmer verfügbar</div>', 400);
}
// Process the form to update the DTO with the latest room selection
$form = $this->createForm(BookingCreateStep1Type::class, $bookingCreateDto, [
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Controller\Booking;
use App\Controller\Traits\HtmxControllerTrait;
use App\Exception\NoRoomsAvailableException;
use App\Form\BookingCreateStep2Type;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\ParticipantDto;
@@ -38,9 +39,15 @@ class CreateStep2Controller extends AbstractController
#[Route('/bookings/create/participants', name: 'app_booking_create_step_2')]
public function participants(Request $request): Response
{
$bookingCreateDto = $this
->bookingService
->getOrCreateBookingCreateDto($request);
try {
$bookingCreateDto = $this
->bookingService
->getOrCreateBookingCreateDto($request);
} catch (NoRoomsAvailableException $e) {
$this->addFlash('error', 'Leider sind für diese Reise aktuell keine Zimmer verfügbar.');
// TODO: Redirect to travel listing or hotel details page
throw $this->createNotFoundException('No rooms available for this travel.');
}
// Enrich with fresh availability data
$this->enrichWithFreshAvailabilities($bookingCreateDto);
@@ -93,7 +100,12 @@ class CreateStep2Controller extends AbstractController
#[Route('/bookings/create/participants/refresh', name: 'app_booking_create_step_2_refresh', methods: ['POST'])]
public function refreshParticipantForm(Request $request): Response
{
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
try {
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
} catch (NoRoomsAvailableException $e) {
// For HTMX requests, return a simple error message
return new Response('<div class="text-red-700 p-4">Keine Zimmer verfügbar</div>', 400);
}
// Enrich with fresh availability data
$this->enrichWithFreshAvailabilities($bookingCreateDto);
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Controller\Booking;
use App\Exception\NoRoomsAvailableException;
use App\Service\BookingService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
@@ -31,7 +32,13 @@ class CreateStep3Controller extends AbstractController
#[Route('/bookings/create/confirm', name: 'app_booking_create_step_3')]
public function confirm(Request $request): Response
{
$bookingCreateDto = $this->bookingCreateService->getOrCreateBookingCreateDto($request);
try {
$bookingCreateDto = $this->bookingCreateService->getOrCreateBookingCreateDto($request);
} catch (NoRoomsAvailableException $e) {
$this->addFlash('error', 'Leider sind für diese Reise aktuell keine Zimmer verfügbar.');
// TODO: Redirect to travel listing or hotel details page
throw $this->createNotFoundException('No rooms available for this travel.');
}
// Validate step access
$this->validateStepAccess($bookingCreateDto, 3);
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Exception;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Exception thrown when attempting to create a booking but no rooms are available.
*
* This exception is used to prevent users from entering the booking flow
* when there are no available rooms for the selected travel dates.
*/
class NoRoomsAvailableException extends HttpException
{
public function __construct(int $dateId, int $hotelId, ?\Throwable $previous = null)
{
$message = sprintf(
'Leider sind für diese Reise aktuell keine Zimmer verfügbar (Travel ID: %d, Hotel ID: %d)',
$dateId,
$hotelId
);
parent::__construct(400, $message, $previous);
}
}
+6
View File
@@ -4,6 +4,7 @@ namespace App\Service;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Travel;
use App\Exception\NoRoomsAvailableException;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\RoomSelectionDto;
use Symfony\Component\HttpFoundation\Request;
@@ -78,6 +79,11 @@ class BookingService
$roomsIdsAndQuantities = $this->processRoomQuantities($request);
$availableRooms = $travelData->getAvailableRooms();
// Prevent booking flow entry when no rooms are available
if (empty($availableRooms)) {
throw new NoRoomsAvailableException($dateId, $hotelId);
}
$roomSelections = array_map(
fn (Room $room) => $this->createRoomSelection($room, $roomsIdsAndQuantities),
$availableRooms