feat: authenticated booking

This commit is contained in:
Björn Fromme
2026-03-16 11:59:11 +01:00
parent 1e464cef0a
commit dc6de1734a
13 changed files with 866 additions and 50 deletions
@@ -32,11 +32,12 @@ class IndexController extends AbstractController
}
/**
* Initializes a fresh booking session and redirects to step 1.
* Initializes a fresh booking session and redirects to the login page.
*
* This endpoint provides a clean way to start the booking flow with just
* dateId and hotelId parameters. It clears any existing booking session
* and creates a fresh BookingCreateDto before redirecting to step 1.
* dateId and hotelId parameters. It clears any existing booking session,
* creates a fresh BookingDto, and redirects to the login page where users
* can authenticate (for prepopulation) or continue as guest.
*
* Optionally accepts an agency code parameter. If provided and valid, the
* corresponding agency ID is stored in the booking. If not provided or invalid,
@@ -59,17 +60,17 @@ class IndexController extends AbstractController
// Create fresh booking session with the provided parameters
$this->bookingService->startFreshBooking($request, $dateId, $hotelId, $agencyId);
// Redirect to step 1 of the booking flow
return $this->redirectToRoute('app_booking_create_step_1');
} catch (TravelNotFoundException $e) {
// Redirect to login page (optional authentication before Step 1)
return $this->redirectToRoute('app_login');
} catch (TravelNotFoundException) {
throw $this->createNotFoundException(sprintf('Travel not found for date ID %d', $dateId));
} catch (HotelNotFoundException $e) {
} catch (HotelNotFoundException) {
throw $this->createNotFoundException(sprintf('Hotel not found for hotel ID %d', $hotelId));
} catch (HotelNotInTravelException $e) {
} catch (HotelNotInTravelException) {
throw $this->createNotFoundException(sprintf('Hotel ID %d is not available for travel ID %d', $hotelId, $dateId));
} catch (NoRoomsAvailableException $e) {
} catch (NoRoomsAvailableException) {
throw $this->createNotFoundException('No rooms available for this travel.');
} catch (BookingNotPossibleException $e) {
} catch (BookingNotPossibleException) {
throw $this->createNotFoundException('Booking is not possible for this travel (Buchungsstop).');
}
}
@@ -15,6 +15,7 @@ use App\Htmx\HxTrait;
use App\Service\BookingPriceCalculatorService;
use App\Service\BookingService;
use App\Service\ParticipantCardDataService;
use App\Service\ParticipantPrepopulationService;
use App\Service\RoomAssignmentService;
use App\Service\TravelDataService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -42,6 +43,7 @@ class Step2Controller extends AbstractController
private readonly RoomAssignmentService $roomAssignmentService,
private readonly ParticipantCardDataService $participantCardService,
private readonly ParticipantFieldOptionsProvider $fieldOptionsProvider,
private readonly ParticipantPrepopulationService $prepopulationService,
) {
}
@@ -227,10 +229,29 @@ class Step2Controller extends AbstractController
for ($i = 0; $i < $participantsCount; ++$i) {
$participant = $participants[$i] ?? new ParticipantDto();
$participant->index = $i;
// Prepopulate applicant from authenticated user (index 0 only)
if (0 === $i && $this->getUser() && $this->shouldPrepopulate($participant)) {
$participant = $this->prepopulationService->prepopulateApplicantFromUser(
$this->getUser(),
$participant
);
}
$bookingCreateDto->participants[$i] = $participant;
}
}
/**
* Determines if a participant should be prepopulated.
*
* Only prepopulates if the participant is "fresh" (no name set yet).
*/
private function shouldPrepopulate(ParticipantDto $participant): bool
{
return null === $participant->firstName || '' === $participant->firstName;
}
/**
* Enriches travel data with cached availability information from BusProNet API.
*/