feat: htmx powered requests, improved loading indicator
feat: loading indicator and htmx form submit
This commit is contained in:
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Controller\Booking;
|
||||
|
||||
use App\Form\BookingCreateStep1Type;
|
||||
use App\Htmx\HxTrait;
|
||||
use App\Service\BookingService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -21,6 +22,7 @@ class CreateStep1Controller extends AbstractController
|
||||
{
|
||||
use BookingCreateTrait;
|
||||
use BookingExceptionHandlerTrait;
|
||||
use HxTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly BookingService $bookingService,
|
||||
@@ -50,6 +52,12 @@ class CreateStep1Controller extends AbstractController
|
||||
}
|
||||
|
||||
$form = $this->createForm(BookingCreateStep1Type::class, $bookingCreateDto, [
|
||||
'attr' => [
|
||||
'hx-post' => $this->generateUrl('app_booking_create_step_1'),
|
||||
'hx-target' => '#form-wrapper',
|
||||
'hx-select' => '#form-wrapper',
|
||||
'hx-swap' => 'outerHTML',
|
||||
],
|
||||
'validation_groups' => ['booking_create_step_1'],
|
||||
]);
|
||||
|
||||
@@ -66,7 +74,7 @@ class CreateStep1Controller extends AbstractController
|
||||
// Clear baseline snapshot when moving to step 2
|
||||
$this->bookingService->clearBaselineSnapshot($request);
|
||||
|
||||
return $this->redirectToRoute('app_booking_create_step_2');
|
||||
return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_2'));
|
||||
}
|
||||
|
||||
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||
@@ -85,33 +93,44 @@ class CreateStep1Controller extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* HTMX endpoint for live summary updates in step 1.
|
||||
* Handles HTMX requests for dynamic form updates when room selections change by submitting the form
|
||||
* without validation and returning freshly rendered blocks.
|
||||
*/
|
||||
#[Route('/bookings/create/room-summary', name: 'app_booking_create_step_1_room_summary', methods: ['POST'])]
|
||||
public function roomSummary(Request $request): Response
|
||||
#[Route('/bookings/create/refresh', name: 'app_booking_create_step_1_refresh', methods: ['POST'])]
|
||||
public function refreshRoomSelection(Request $request): Response
|
||||
{
|
||||
$result = $this->getOrCreateBookingCreateDto($this->bookingService, $request);
|
||||
$result = $this->getOrCreateBookingCreateDtoForHtmx($this->bookingService, $request);
|
||||
if ($result instanceof Response) {
|
||||
return $result;
|
||||
}
|
||||
$bookingCreateDto = $result;
|
||||
|
||||
// Process the form to update the DTO with the latest room selection
|
||||
// Process form data without validation to capture current state
|
||||
$form = $this->createForm(BookingCreateStep1Type::class, $bookingCreateDto, [
|
||||
'validation_groups' => false,
|
||||
]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
||||
|
||||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||||
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||
$groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms);
|
||||
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($summary['selectedRooms'], $availableRooms);
|
||||
|
||||
return $this->render('booking/_summary.html.twig', [
|
||||
'bookingCreateDto' => $bookingCreateDto,
|
||||
'roomSummary' => $summary['selectedRooms'],
|
||||
'participantCount' => $summary['participantCount'],
|
||||
'pricingData' => $summary['pricing'],
|
||||
'groupedSelectedRooms' => $groupedSelectedRooms,
|
||||
]);
|
||||
// The DTO is now updated with the latest selection.
|
||||
// We can now render the blocks with the fresh data.
|
||||
return $this->htmxOobResponse(
|
||||
'booking/create_step_1.html.twig',
|
||||
['room_selection_form', 'booking_summary'],
|
||||
[
|
||||
'form' => $form->createView(),
|
||||
'bookingCreateDto' => $bookingCreateDto,
|
||||
'participantCount' => $summary['participantCount'],
|
||||
'pricingData' => $summary['pricing'],
|
||||
'groupedRooms' => $groupedRooms,
|
||||
'groupedSelectedRooms' => $groupedSelectedRooms,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Booking;
|
||||
|
||||
use App\Controller\Traits\HtmxControllerTrait;
|
||||
use App\Form\BookingCreateStep2Type;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Htmx\HxTrait;
|
||||
use App\Service\BookingPriceCalculatorService;
|
||||
use App\Service\BookingService;
|
||||
use App\Service\RoomAssignmentService;
|
||||
@@ -27,7 +27,7 @@ class CreateStep2Controller extends AbstractController
|
||||
{
|
||||
use BookingCreateTrait;
|
||||
use BookingExceptionHandlerTrait;
|
||||
use HtmxControllerTrait;
|
||||
use HxTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly BookingService $bookingService,
|
||||
@@ -70,7 +70,13 @@ class CreateStep2Controller extends AbstractController
|
||||
$this->bookingService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
|
||||
|
||||
$form = $this->createForm(BookingCreateStep2Type::class, $bookingCreateDto, [
|
||||
'attr' => ['novalidate' => 'novalidate'],
|
||||
'attr' => [
|
||||
'novalidate' => 'novalidate',
|
||||
'hx-post' => $this->generateUrl('app_booking_create_step_2'),
|
||||
'hx-target' => '#form-wrapper',
|
||||
'hx-select' => '#form-wrapper',
|
||||
'hx-swap' => 'outerHTML',
|
||||
],
|
||||
'validation_groups' => ['booking_create_step_2'],
|
||||
]);
|
||||
|
||||
@@ -80,7 +86,7 @@ class CreateStep2Controller extends AbstractController
|
||||
$bookingCreateDto->currentStep = 3;
|
||||
$this->bookingService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
|
||||
|
||||
return $this->redirectToRoute('app_booking_create_step_3');
|
||||
return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_3'));
|
||||
}
|
||||
|
||||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace App\Controller\Booking;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\Controller\Traits\HtmxControllerTrait;
|
||||
use App\Form\BookingCreateStep3Type;
|
||||
use App\Htmx\HxTrait;
|
||||
use App\Service\BookingPriceCalculatorService;
|
||||
use App\Service\BookingService;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -23,7 +23,7 @@ class CreateStep3Controller extends AbstractController
|
||||
{
|
||||
use BookingCreateTrait;
|
||||
use BookingExceptionHandlerTrait;
|
||||
use HtmxControllerTrait;
|
||||
use HxTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly BookingService $bookingService,
|
||||
@@ -50,7 +50,14 @@ class CreateStep3Controller extends AbstractController
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$form = $this->createForm(BookingCreateStep3Type::class, $bookingCreateDto);
|
||||
$form = $this->createForm(BookingCreateStep3Type::class, $bookingCreateDto, [
|
||||
'attr' => [
|
||||
'hx-post' => $this->generateUrl('app_booking_create_step_3'),
|
||||
'hx-target' => '#form-wrapper',
|
||||
'hx-select' => '#form-wrapper',
|
||||
'hx-swap' => 'outerHTML',
|
||||
],
|
||||
]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
@@ -107,7 +114,7 @@ class CreateStep3Controller extends AbstractController
|
||||
$bookingCreateDto->currentStep = 4;
|
||||
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
||||
|
||||
return $this->redirectToRoute('app_booking_create_step_4');
|
||||
return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_4'));
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Booking inquiry exception', [
|
||||
'exception' => $e->getMessage(),
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace App\Controller\Booking;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\Controller\Traits\HtmxControllerTrait;
|
||||
use App\Form\BookingCreateStep4Type;
|
||||
use App\Htmx\HxTrait;
|
||||
use App\Service\BookingService;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -22,7 +22,7 @@ class CreateStep4Controller extends AbstractController
|
||||
{
|
||||
use BookingCreateTrait;
|
||||
use BookingExceptionHandlerTrait;
|
||||
use HtmxControllerTrait;
|
||||
use HxTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly BookingService $bookingService,
|
||||
@@ -48,7 +48,14 @@ class CreateStep4Controller extends AbstractController
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$form = $this->createForm(BookingCreateStep4Type::class, $bookingCreateDto);
|
||||
$form = $this->createForm(BookingCreateStep4Type::class, $bookingCreateDto, [
|
||||
'attr' => [
|
||||
'hx-post' => $this->generateUrl('app_booking_create_step_4'),
|
||||
'hx-target' => '#form-wrapper',
|
||||
'hx-select' => '#form-wrapper',
|
||||
'hx-swap' => 'outerHTML',
|
||||
],
|
||||
]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
@@ -80,7 +87,7 @@ class CreateStep4Controller extends AbstractController
|
||||
$this->addFlash('booking_number', $bookingResponse->transactionNumber);
|
||||
$this->bookingService->clearBookingCreateDto($request);
|
||||
|
||||
return $this->redirectToRoute('app_booking_success');
|
||||
return $this->hxRedirect($request, $this->generateUrl('app_booking_success'));
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Booking creation failed', [
|
||||
'exception' => $e->getMessage(),
|
||||
|
||||
@@ -8,10 +8,10 @@ use App\BusProNet\Exception\ApiClientException;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\BusProNet\XmlLoader\PickupLoader;
|
||||
use App\Controller\Traits\BookingDataTrait;
|
||||
use App\Controller\Traits\HtmxControllerTrait;
|
||||
use App\Entity\User;
|
||||
use App\Form\BookingEditType;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Htmx\HxTrait;
|
||||
use App\Security\Crypt;
|
||||
use App\Service\BookingPriceCalculatorService;
|
||||
use App\Service\BookingService;
|
||||
@@ -29,7 +29,7 @@ use Symfony\Contracts\Cache\CacheInterface;
|
||||
class EditController extends AbstractController
|
||||
{
|
||||
use BookingDataTrait;
|
||||
use HtmxControllerTrait;
|
||||
use HxTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly ApiClient $apiClient,
|
||||
@@ -54,42 +54,22 @@ class EditController extends AbstractController
|
||||
$email = $user->getEmail();
|
||||
$password = $this->crypt->decrypt($user->getPassword());
|
||||
|
||||
// Fetch original booking data via API and cache result for a short ttl
|
||||
$bookingData = $this->fetchBookingData($email, $password, $id);
|
||||
// Load form data from session (or API on first load)
|
||||
$formData = $this->loadFormData($request, $id, $email, $password);
|
||||
if (null === $formData) {
|
||||
return $this->redirectToRoute('app_bookings');
|
||||
}
|
||||
|
||||
// Fetch booking data for display (surcharges, canceled status, etc.)
|
||||
$bookingData = $this->fetchBookingData($email, $password, $id);
|
||||
if (null === $bookingData || $bookingData instanceof Notification) {
|
||||
$this->addFlash('error', 'Buchungsdaten nicht (mehr) verfügbar');
|
||||
|
||||
return $this->redirectToRoute('app_bookings');
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted('EDIT', $bookingData);
|
||||
|
||||
// Load according travel data
|
||||
$travelData = $this->travelDataService->getTravelData($bookingData->dateId);
|
||||
|
||||
if (null === $travelData) {
|
||||
$this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar');
|
||||
|
||||
return $this->redirectToRoute('app_bookings');
|
||||
}
|
||||
|
||||
// Fetch mutability and availability information via service
|
||||
// Fetch mutable data for form constraints
|
||||
$mutableData = $this->travelDataService->getMutabilityData($bookingData->dateId);
|
||||
$availabilities = $this->travelDataService->getAvailabilityData($bookingData->dateId);
|
||||
|
||||
if (null === $mutableData || null === $availabilities) {
|
||||
$this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar');
|
||||
|
||||
return $this->redirectToRoute('app_bookings');
|
||||
}
|
||||
|
||||
// Patch travel data with additional information from above
|
||||
$this->travelDataService->patchAvailabilities($travelData, $availabilities);
|
||||
$this->travelDataService->patchMutability($travelData, $mutableData);
|
||||
|
||||
// Create DTO for form
|
||||
$formData = $this->bookingDataProcessor->createBookingDtoFromBooking($bookingData, $travelData);
|
||||
|
||||
// Calculate pricing data for template
|
||||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($formData);
|
||||
@@ -104,7 +84,13 @@ class EditController extends AbstractController
|
||||
);
|
||||
|
||||
$form = $this->createForm(BookingEditType::class, $formData, [
|
||||
'attr' => ['novalidate' => 'novalidate'],
|
||||
'attr' => [
|
||||
'novalidate' => 'novalidate',
|
||||
'hx-post' => $this->generateUrl('app_booking_edit', ['id' => $id]),
|
||||
'hx-target' => '#form-wrapper',
|
||||
'hx-select' => '#form-wrapper',
|
||||
'hx-swap' => 'outerHTML',
|
||||
],
|
||||
'validation_groups' => ['booking_edit'],
|
||||
]);
|
||||
|
||||
@@ -136,6 +122,9 @@ class EditController extends AbstractController
|
||||
} catch (InvalidArgumentException $e) {
|
||||
}
|
||||
|
||||
// Clear session on successful save
|
||||
$this->bookingService->clearBookingDto($request, BookingDto::MODE_EDIT);
|
||||
|
||||
$this->addFlash('success', 'Buchung erfolgreich aktualisiert');
|
||||
|
||||
$this->logger->info('Booking update successful', [
|
||||
@@ -143,11 +132,13 @@ class EditController extends AbstractController
|
||||
'booking_id' => $id,
|
||||
]);
|
||||
|
||||
return $this->redirectToRoute('app_booking_edit', ['id' => $id]);
|
||||
return $this->hxRedirect($request, $this->generateUrl('app_booking_edit', ['id' => $id]));
|
||||
}
|
||||
} catch (ApiClientException $e) {
|
||||
$this->addFlash('error', 'Es ist ein Fehler in der Kommunikation mit dem Buchungssystem aufgetreten');
|
||||
}
|
||||
|
||||
return $this->hxRedirect($request, $this->generateUrl('app_booking_edit', ['id' => $id]));
|
||||
}
|
||||
|
||||
return $this->render('booking/edit.html.twig', [
|
||||
@@ -163,6 +154,21 @@ class EditController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads booking data from API, discarding all session changes.
|
||||
*/
|
||||
#[Route('/bookings/{id}/edit/reload', name: 'app_booking_edit_reload', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function reloadFromApi(int $id, Request $request): Response
|
||||
{
|
||||
// Clear session to discard all changes
|
||||
$this->bookingService->clearBookingDto($request, BookingDto::MODE_EDIT);
|
||||
|
||||
$this->addFlash('success', 'Änderungen verworfen, Daten neu geladen');
|
||||
|
||||
return $this->hxRedirect($request, $this->generateUrl('app_booking_edit', ['id' => $id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* HTMX endpoint for refreshing the participant form without validation.
|
||||
*/
|
||||
@@ -170,43 +176,20 @@ class EditController extends AbstractController
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function refreshParticipantForm(int $id, Request $request): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
$email = $user->getEmail();
|
||||
$password = $this->crypt->decrypt($user->getPassword());
|
||||
// Load form data from session
|
||||
$formData = $this->bookingService->getBookingDto($request, BookingDto::MODE_EDIT);
|
||||
|
||||
// Fetch original booking data via API and cache result for a short ttl
|
||||
$bookingData = $this->fetchBookingData($email, $password, $id);
|
||||
|
||||
if (null === $bookingData || $bookingData instanceof Notification) {
|
||||
return new Response('Buchungsdaten nicht verfügbar', Response::HTTP_BAD_REQUEST);
|
||||
if (null === $formData) {
|
||||
return new Response('Session expired. Please reload page.', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted('EDIT', $bookingData);
|
||||
|
||||
// Load travel data
|
||||
$travelData = $this->travelDataService->getTravelData($bookingData->dateId);
|
||||
|
||||
if (null === $travelData) {
|
||||
return new Response('Reisedaten nicht verfügbar', Response::HTTP_BAD_REQUEST);
|
||||
// Refresh availability data
|
||||
$availabilities = $this->travelDataService->getAvailabilityDataCached($formData->travel->id);
|
||||
if (null !== $availabilities) {
|
||||
$this->travelDataService->patchAvailabilities($formData->travel, $availabilities);
|
||||
}
|
||||
|
||||
// Fetch mutability and availability information
|
||||
$mutableData = $this->travelDataService->getMutabilityData($bookingData->dateId);
|
||||
$availabilities = $this->travelDataService->getAvailabilityDataCached($bookingData->dateId);
|
||||
|
||||
if (null === $mutableData || null === $availabilities) {
|
||||
return new Response('Reisedaten nicht verfügbar', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
// Patch travel data
|
||||
$this->travelDataService->patchAvailabilities($travelData, $availabilities);
|
||||
$this->travelDataService->patchMutability($travelData, $mutableData);
|
||||
|
||||
// Create fresh DTO from booking data
|
||||
$formData = $this->bookingDataProcessor->createBookingDtoFromBooking($bookingData, $travelData);
|
||||
|
||||
// Process form data without validation to capture current state
|
||||
// Process form without validation to capture current state
|
||||
$form = $this->createForm(BookingEditType::class, $formData, [
|
||||
'attr' => ['novalidate' => 'novalidate'],
|
||||
'validation_groups' => false,
|
||||
@@ -214,9 +197,23 @@ class EditController extends AbstractController
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
// Save updated DTO back to session
|
||||
$this->bookingService->saveBookingDto($request, $formData, BookingDto::MODE_EDIT);
|
||||
|
||||
// Collect notifications from all participants
|
||||
$notifications = $this->collectParticipantNotifications($formData);
|
||||
|
||||
// Fetch booking data and mutable data for display
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
$email = $user->getEmail();
|
||||
$password = $this->crypt->decrypt($user->getPassword());
|
||||
|
||||
$bookingData = $this->fetchBookingData($email, $password, $id);
|
||||
$mutableData = null !== $bookingData && !($bookingData instanceof Notification)
|
||||
? $this->travelDataService->getMutabilityData($bookingData->dateId)
|
||||
: null;
|
||||
|
||||
// Calculate pricing and summary data
|
||||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($formData);
|
||||
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($formData);
|
||||
@@ -277,4 +274,90 @@ class EditController extends AbstractController
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads form data from session or initializes from API on first load.
|
||||
*
|
||||
* @return BookingDto|null The form data, or null on error
|
||||
*/
|
||||
private function loadFormData(Request $request, int $bookingId, string $email, string $password): ?BookingDto
|
||||
{
|
||||
// Try to load from session first
|
||||
$formData = $this->bookingService->getBookingDto($request, BookingDto::MODE_EDIT);
|
||||
|
||||
if (null === $formData) {
|
||||
// First load: initialize from API
|
||||
return $this->initializeFromApi($request, $bookingId, $email, $password);
|
||||
}
|
||||
|
||||
// Subsequent load: refresh from session with staleness check
|
||||
return $this->refreshFromSession($formData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes form data from API on first load and stores in session.
|
||||
*
|
||||
* @return BookingDto|null The form data, or null on error
|
||||
*/
|
||||
private function initializeFromApi(Request $request, int $bookingId, string $email, string $password): ?BookingDto
|
||||
{
|
||||
$bookingData = $this->fetchBookingData($email, $password, $bookingId);
|
||||
|
||||
if (null === $bookingData || $bookingData instanceof Notification) {
|
||||
$this->addFlash('error', 'Buchungsdaten nicht (mehr) verfügbar');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted('EDIT', $bookingData);
|
||||
|
||||
$travelData = $this->travelDataService->getTravelData($bookingData->dateId);
|
||||
if (null === $travelData) {
|
||||
$this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$mutableData = $this->travelDataService->getMutabilityData($bookingData->dateId);
|
||||
$availabilities = $this->travelDataService->getAvailabilityData($bookingData->dateId);
|
||||
|
||||
if (null === $mutableData || null === $availabilities) {
|
||||
$this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->travelDataService->patchAvailabilities($travelData, $availabilities);
|
||||
$this->travelDataService->patchMutability($travelData, $mutableData);
|
||||
|
||||
$formData = $this->bookingDataProcessor->createBookingDtoFromBooking($bookingData, $travelData);
|
||||
$this->bookingService->saveBookingDto($request, $formData, BookingDto::MODE_EDIT);
|
||||
|
||||
return $formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes form data loaded from session with latest availability.
|
||||
*
|
||||
* @return BookingDto The refreshed form data
|
||||
*/
|
||||
private function refreshFromSession(BookingDto $formData): BookingDto
|
||||
{
|
||||
// Refresh availability data
|
||||
$availabilities = $this->travelDataService->getAvailabilityDataCached($formData->travel->id);
|
||||
if (null !== $availabilities) {
|
||||
$this->travelDataService->patchAvailabilities($formData->travel, $availabilities);
|
||||
}
|
||||
|
||||
// Show staleness warning if session is older than 5 minutes
|
||||
if (null !== $formData->lastSessionUpdate) {
|
||||
$ageInSeconds = (new \DateTimeImmutable())->getTimestamp() - $formData->lastSessionUpdate->getTimestamp();
|
||||
if ($ageInSeconds > 300) {
|
||||
$minutes = (int) ceil($ageInSeconds / 60);
|
||||
$this->addFlash('info', sprintf('Sie bearbeiten diese Buchung seit %d Minuten. Die Daten könnten veraltet sein.', $minutes));
|
||||
}
|
||||
}
|
||||
|
||||
return $formData;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user