wip: form refactoring
This commit is contained in:
@@ -23,8 +23,7 @@ class CreateStep1Controller extends AbstractController
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly BookingService $bookingService,
|
private readonly BookingService $bookingService,
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +33,10 @@ class CreateStep1Controller extends AbstractController
|
|||||||
public function index(Request $request): Response
|
public function index(Request $request): Response
|
||||||
{
|
{
|
||||||
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
|
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
|
||||||
|
|
||||||
|
// Capture the current room selection state before form processing
|
||||||
|
$oldRoomSelectionSnapshot = $this->bookingService->createRoomSelectionSnapshot($bookingCreateDto);
|
||||||
|
|
||||||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||||||
|
|
||||||
// Validate step access - allow step 1 or redirect to current step
|
// Validate step access - allow step 1 or redirect to current step
|
||||||
@@ -46,6 +49,9 @@ class CreateStep1Controller extends AbstractController
|
|||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
if ($this->bookingService->hasRoomSelectionChanged($oldRoomSelectionSnapshot, $bookingCreateDto)) {
|
||||||
|
$this->bookingService->resetParticipantAssignments($bookingCreateDto);
|
||||||
|
}
|
||||||
$bookingCreateDto->currentStep = 2;
|
$bookingCreateDto->currentStep = 2;
|
||||||
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
||||||
|
|
||||||
@@ -80,8 +86,6 @@ class CreateStep1Controller extends AbstractController
|
|||||||
]);
|
]);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
|
||||||
|
|
||||||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||||||
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($summary['selectedRooms'], $availableRooms);
|
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($summary['selectedRooms'], $availableRooms);
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ class CreateStep2Controller extends AbstractController
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly BookingService $bookingService,
|
private readonly BookingService $bookingService,
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use App\BusProNet\Form\CountryType;
|
use App\BusProNet\Form\CountryType;
|
||||||
use App\Form\Model\BookingCreateDto;
|
|
||||||
use App\Form\Model\ParticipantDto;
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Model\ParticipantDto;
|
||||||
use App\Form\Service\ParticipantFieldOptionsProvider;
|
use App\Form\Service\ParticipantFieldOptionsProvider;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||||
@@ -85,14 +84,12 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse up the form tree to get the root form's data.
|
// Get the booking DTO from the root form
|
||||||
$rootForm = $form;
|
$bookingDto = $this->fieldOptionsProvider->getBookingDtoFromForm($form);
|
||||||
while ($rootForm->getParent()) {
|
|
||||||
$rootForm = $rootForm->getParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var BookingDtoInterface $bookingDto */
|
if (null === $bookingDto) {
|
||||||
$bookingDto = $rootForm->getData();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->addDynamicFields($form, $bookingDto, $participantData->index);
|
$this->addDynamicFields($form, $bookingDto, $participantData->index);
|
||||||
$this->applyFieldStates($form, $bookingDto, $participantData->index);
|
$this->applyFieldStates($form, $bookingDto, $participantData->index);
|
||||||
@@ -110,14 +107,8 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the root form data to access BookingDtoInterface
|
// Get the booking DTO from the root form
|
||||||
$rootForm = $form;
|
$bookingDto = $this->fieldOptionsProvider->getBookingDtoFromForm($form);
|
||||||
while ($rootForm->getParent()) {
|
|
||||||
$rootForm = $rootForm->getParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var BookingDtoInterface $bookingDto */
|
|
||||||
$bookingDto = $rootForm->getData();
|
|
||||||
|
|
||||||
if (null === $bookingDto) {
|
if (null === $bookingDto) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use App\BusProNet\Form\CountryType;
|
|||||||
use App\BusProNet\Model\Pickup;
|
use App\BusProNet\Model\Pickup;
|
||||||
use App\BusProNet\Model\Service;
|
use App\BusProNet\Model\Service;
|
||||||
use App\Form\Model\ParticipantDto;
|
use App\Form\Model\ParticipantDto;
|
||||||
use App\Form\Model\BookingDtoInterface;
|
|
||||||
use App\Form\Service\EditFieldStateProvider;
|
use App\Form\Service\EditFieldStateProvider;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||||
@@ -23,7 +22,8 @@ class BookingEditParticipantType extends AbstractType
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly EditFieldStateProvider $fieldStateProvider,
|
private readonly EditFieldStateProvider $fieldStateProvider,
|
||||||
) {}
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
@@ -39,16 +39,15 @@ class BookingEditParticipantType extends AbstractType
|
|||||||
|
|
||||||
$form = $event->getForm();
|
$form = $event->getForm();
|
||||||
|
|
||||||
// Traverse up the form tree to get the root form's data (BookingEditDto)
|
// Get the booking DTO from the root form
|
||||||
$rootForm = $form;
|
$bookingDto = $this->fieldStateProvider->getBookingDtoFromForm($form);
|
||||||
while ($rootForm->getParent()) {
|
|
||||||
$rootForm = $rootForm->getParent();
|
if (null === $bookingDto) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
/** @var BookingDtoInterface $bookingDto */
|
|
||||||
$bookingDto = $rootForm->getData();
|
|
||||||
|
|
||||||
// Helper to get state for a field
|
// Helper to get state for a field
|
||||||
$getState = fn(string $field) => $this->fieldStateProvider->getFieldState($field, $bookingDto, $participantIndex);
|
$getState = fn (string $field) => $this->fieldStateProvider->getFieldState($field, $bookingDto, $participantIndex);
|
||||||
|
|
||||||
$form
|
$form
|
||||||
->add('firstName', TextType::class, $this->mergeFieldState([
|
->add('firstName', TextType::class, $this->mergeFieldState([
|
||||||
@@ -248,7 +247,7 @@ class BookingEditParticipantType extends AbstractType
|
|||||||
$pickupLabel = $pickup->city;
|
$pickupLabel = $pickup->city;
|
||||||
|
|
||||||
if (null !== $pickup->street) {
|
if (null !== $pickup->street) {
|
||||||
$pickupLabel .= ' (' . $pickup->street . ')';
|
$pickupLabel .= ' ('.$pickup->street.')';
|
||||||
}
|
}
|
||||||
|
|
||||||
$price = $pickup->price;
|
$price = $pickup->price;
|
||||||
@@ -285,21 +284,20 @@ class BookingEditParticipantType extends AbstractType
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse up the form tree to get the root form's data (BookingEditDto)
|
// Get the booking DTO from the root form
|
||||||
$rootForm = $form;
|
$bookingDto = $this->fieldStateProvider->getBookingDtoFromForm($form);
|
||||||
while ($rootForm->getParent()) {
|
|
||||||
$rootForm = $rootForm->getParent();
|
if (null === $bookingDto) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
/** @var BookingDtoInterface $bookingDto */
|
|
||||||
$bookingDto = $rootForm->getData();
|
|
||||||
$participantIndex = $form->getData()->index;
|
$participantIndex = $form->getData()->index;
|
||||||
|
|
||||||
// Helper to get state for a field based on submitted data
|
// Helper to get state for a field based on submitted data
|
||||||
$getState = fn(string $field) => $this->fieldStateProvider->getFieldState($field, $bookingDto, $participantIndex, $data);
|
$getState = fn (string $field) => $this->fieldStateProvider->getFieldState($field, $bookingDto, $participantIndex, $data);
|
||||||
|
|
||||||
// Re-apply field states to all personal data fields
|
// Re-apply field states to all personal data fields
|
||||||
$personalDataFields = [
|
$personalDataFields = [
|
||||||
'firstName', 'lastName', 'dateOfBirth', 'gender', 'nationality', 'email', 'mobile'
|
'firstName', 'lastName', 'dateOfBirth', 'gender', 'nationality', 'email', 'mobile',
|
||||||
];
|
];
|
||||||
foreach ($personalDataFields as $field) {
|
foreach ($personalDataFields as $field) {
|
||||||
if ($form->has($field)) {
|
if ($form->has($field)) {
|
||||||
@@ -389,6 +387,7 @@ class BookingEditParticipantType extends AbstractType
|
|||||||
$fieldOptions[$key] = $value;
|
$fieldOptions[$key] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $fieldOptions;
|
return $fieldOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,11 +40,6 @@ class BookingCreateDto implements BookingDtoInterface
|
|||||||
return $this->participants;
|
return $this->participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTravel(): Travel
|
|
||||||
{
|
|
||||||
return $this->travel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasParticipant(int $index): bool
|
public function hasParticipant(int $index): bool
|
||||||
{
|
{
|
||||||
return isset($this->participants[$index]);
|
return isset($this->participants[$index]);
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Form\Model;
|
namespace App\Form\Model;
|
||||||
|
|
||||||
use App\BusProNet\Model\Travel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for unified access to booking data across create and edit workflows.
|
* Interface for unified access to booking data across create and edit workflows.
|
||||||
*
|
*
|
||||||
@@ -23,13 +21,6 @@ interface BookingDtoInterface
|
|||||||
*/
|
*/
|
||||||
public function getParticipants(): array;
|
public function getParticipants(): array;
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the travel data for the booking.
|
|
||||||
*
|
|
||||||
* @return Travel The travel data containing services, hotels, and other booking options
|
|
||||||
*/
|
|
||||||
public function getTravel(): Travel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a participant exists at the given index.
|
* Checks if a participant exists at the given index.
|
||||||
*
|
*
|
||||||
@@ -47,11 +38,4 @@ interface BookingDtoInterface
|
|||||||
* @return ParticipantDto|null The participant DTO or null if not found
|
* @return ParticipantDto|null The participant DTO or null if not found
|
||||||
*/
|
*/
|
||||||
public function getParticipant(int $index): ?ParticipantDto;
|
public function getParticipant(int $index): ?ParticipantDto;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Gets all selected rooms for the booking.
|
|
||||||
*
|
|
||||||
* @return array<int, RoomSelectionDto> Array of selected room DTOs (may be empty for edit DTOs)
|
|
||||||
*/
|
|
||||||
public function getSelectedRooms(): array;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,18 +10,19 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
|
|
||||||
class BookingEditDto implements BookingDtoInterface
|
class BookingEditDto implements BookingDtoInterface
|
||||||
{
|
{
|
||||||
public ?Booking $booking = null;
|
|
||||||
public ?Travel $travel = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<int, ParticipantDto>
|
* @var array<int, ParticipantDto>
|
||||||
*/
|
*/
|
||||||
#[Assert\Valid]
|
#[Assert\Valid]
|
||||||
public array $participants = [];
|
public array $participants = [];
|
||||||
|
|
||||||
|
public function __construct(public Booking $booking, public Travel $travel)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public static function fromBooking(Booking $booking, Travel $travel): static
|
public static function fromBooking(Booking $booking, Travel $travel): static
|
||||||
{
|
{
|
||||||
$instance = new static();
|
$instance = new static($booking, $travel);
|
||||||
|
|
||||||
$instance->booking = $booking;
|
$instance->booking = $booking;
|
||||||
$instance->travel = $travel;
|
$instance->travel = $travel;
|
||||||
@@ -69,11 +70,6 @@ class BookingEditDto implements BookingDtoInterface
|
|||||||
return $this->participants;
|
return $this->participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTravel(): Travel
|
|
||||||
{
|
|
||||||
return $this->travel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasParticipant(int $index): bool
|
public function hasParticipant(int $index): bool
|
||||||
{
|
{
|
||||||
return isset($this->participants[$index]);
|
return isset($this->participants[$index]);
|
||||||
@@ -87,7 +83,7 @@ class BookingEditDto implements BookingDtoInterface
|
|||||||
/**
|
/**
|
||||||
* Gets all selected rooms for the booking (edit context).
|
* Gets all selected rooms for the booking (edit context).
|
||||||
*
|
*
|
||||||
* @return array<int, RoomSelectionDto> Always returns an empty array for edit DTOs unless implemented.
|
* @return array<int, RoomSelectionDto> always returns an empty array for edit DTOs unless implemented
|
||||||
*/
|
*/
|
||||||
public function getSelectedRooms(): array
|
public function getSelectedRooms(): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler;
|
namespace App\Form\ParticipantFieldHandler;
|
||||||
|
|
||||||
use App\Form\Model\BookingCreateDto;
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler;
|
namespace App\Form\ParticipantFieldHandler;
|
||||||
|
|
||||||
use App\Form\Model\BookingCreateDto;
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ declare(strict_types=1);
|
|||||||
namespace App\Form\Service;
|
namespace App\Form\Service;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
|
||||||
use App\Form\ParticipantFieldHandler\Condition\MutabilityCondition;
|
|
||||||
use App\Form\ParticipantFieldHandler\Condition\ApplicantCondition;
|
use App\Form\ParticipantFieldHandler\Condition\ApplicantCondition;
|
||||||
use App\Form\ParticipantFieldHandler\Condition\CompositeCondition;
|
use App\Form\ParticipantFieldHandler\Condition\CompositeCondition;
|
||||||
|
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
||||||
|
use App\Form\ParticipantFieldHandler\Condition\MutabilityCondition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field state provider for the booking edit workflow.
|
* Field state provider for the booking edit workflow.
|
||||||
@@ -21,6 +21,8 @@ use App\Form\ParticipantFieldHandler\Condition\CompositeCondition;
|
|||||||
*/
|
*/
|
||||||
class EditFieldStateProvider implements FieldStateProviderInterface
|
class EditFieldStateProvider implements FieldStateProviderInterface
|
||||||
{
|
{
|
||||||
|
use FormTraversalTrait;
|
||||||
|
|
||||||
/** @var array<string, array<string, FieldConditionInterface>> */
|
/** @var array<string, array<string, FieldConditionInterface>> */
|
||||||
private array $fieldStateConditions = [];
|
private array $fieldStateConditions = [];
|
||||||
|
|
||||||
@@ -109,6 +111,7 @@ class EditFieldStateProvider implements FieldStateProviderInterface
|
|||||||
foreach ($this->fieldStateConditions[$fieldName] as $condition) {
|
foreach ($this->fieldStateConditions[$fieldName] as $condition) {
|
||||||
$dependencies = array_merge($dependencies, $condition->getDependentFields());
|
$dependencies = array_merge($dependencies, $condition->getDependentFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_unique($dependencies);
|
return array_unique($dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +124,7 @@ class EditFieldStateProvider implements FieldStateProviderInterface
|
|||||||
$allStates[$fieldName] = $fieldState;
|
$allStates[$fieldName] = $fieldState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $allStates;
|
return $allStates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait providing form tree traversal utilities.
|
||||||
|
*
|
||||||
|
* This trait contains common form navigation logic used across different
|
||||||
|
* form services and types. It centralizes the logic for finding root forms
|
||||||
|
* and extracting booking DTOs from form hierarchies.
|
||||||
|
*/
|
||||||
|
trait FormTraversalTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets the BookingDtoInterface from the root of the form tree.
|
||||||
|
*
|
||||||
|
* This helper method traverses up the form tree to find the root form
|
||||||
|
* and extracts the BookingDtoInterface data. This is shared logic used
|
||||||
|
* by both create and edit participant forms.
|
||||||
|
*
|
||||||
|
* @param FormInterface $form The form to start traversing from
|
||||||
|
*
|
||||||
|
* @return BookingDtoInterface|null The booking DTO or null if not found
|
||||||
|
*/
|
||||||
|
public function getBookingDtoFromForm(FormInterface $form): ?BookingDtoInterface
|
||||||
|
{
|
||||||
|
// Traverse up the form tree to get the root form's data
|
||||||
|
$rootForm = $form;
|
||||||
|
while ($rootForm->getParent()) {
|
||||||
|
$rootForm = $rootForm->getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $rootForm->getData();
|
||||||
|
|
||||||
|
return $data instanceof BookingDtoInterface ? $data : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Form\Service;
|
namespace App\Form\Service;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingCreateDto;
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
||||||
|
|
||||||
@@ -26,6 +27,8 @@ use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
|||||||
*/
|
*/
|
||||||
class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
||||||
{
|
{
|
||||||
|
use FormTraversalTrait;
|
||||||
|
|
||||||
/** @var array<string, callable> Field option providers indexed by field name */
|
/** @var array<string, callable> Field option providers indexed by field name */
|
||||||
private array $fieldOptionProviders = [];
|
private array $fieldOptionProviders = [];
|
||||||
|
|
||||||
@@ -120,7 +123,7 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
|||||||
*/
|
*/
|
||||||
private function registerFieldOptionProviders(): void
|
private function registerFieldOptionProviders(): void
|
||||||
{
|
{
|
||||||
// Room assignment field provider
|
// Room assignment field provider (only available for create workflow)
|
||||||
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
'label' => 'Zimmer',
|
'label' => 'Zimmer',
|
||||||
'placeholder' => 'Bitte wählen',
|
'placeholder' => 'Bitte wählen',
|
||||||
@@ -128,11 +131,13 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
|||||||
// - Shows only available rooms for this participant
|
// - Shows only available rooms for this participant
|
||||||
// - Excludes rooms already assigned to other participants
|
// - Excludes rooms already assigned to other participants
|
||||||
// - Respects room capacity and booking constraints
|
// - Respects room capacity and booking constraints
|
||||||
'choice_loader' => $this->roomChoiceLoaderFactory->create(
|
'choice_loader' => $bookingDto instanceof BookingCreateDto
|
||||||
$bookingDto->participants,
|
? $this->roomChoiceLoaderFactory->create(
|
||||||
$bookingDto->getSelectedRooms(),
|
$bookingDto->participants,
|
||||||
$participantIndex
|
$bookingDto->getSelectedRooms(),
|
||||||
),
|
$participantIndex
|
||||||
|
)
|
||||||
|
: null,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Future field providers would be added here, for example:
|
// Future field providers would be added here, for example:
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use App\BusProNet\Model\Room;
|
|||||||
use App\BusProNet\Model\Travel;
|
use App\BusProNet\Model\Travel;
|
||||||
use App\Form\Model\BookingCreateDto;
|
use App\Form\Model\BookingCreateDto;
|
||||||
use App\Form\Model\RoomSelectionDto;
|
use App\Form\Model\RoomSelectionDto;
|
||||||
use App\Service\TravelDataService;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
@@ -14,8 +13,7 @@ class BookingService
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly TravelDataService $travelDataService,
|
private readonly TravelDataService $travelDataService,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
public function getOrCreateBookingCreateDto(Request $request): BookingCreateDto
|
public function getOrCreateBookingCreateDto(Request $request): BookingCreateDto
|
||||||
{
|
{
|
||||||
@@ -43,7 +41,7 @@ class BookingService
|
|||||||
$availableRooms = $travelData->getAvailableRooms();
|
$availableRooms = $travelData->getAvailableRooms();
|
||||||
|
|
||||||
$roomSelections = array_map(
|
$roomSelections = array_map(
|
||||||
fn (Room $room) => $this->createRoomSelection($room, $roomsIdsAndQuantities),
|
fn(Room $room) => $this->createRoomSelection($room, $roomsIdsAndQuantities),
|
||||||
$availableRooms
|
$availableRooms
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,7 +101,6 @@ class BookingService
|
|||||||
return $participantsCount;
|
return $participantsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the number of participants assigned to each room ID.
|
* Calculates the number of participants assigned to each room ID.
|
||||||
*
|
*
|
||||||
@@ -124,7 +121,6 @@ class BookingService
|
|||||||
/**
|
/**
|
||||||
* Returns a summary of selected rooms and the resulting participant count for a booking.
|
* Returns a summary of selected rooms and the resulting participant count for a booking.
|
||||||
*
|
*
|
||||||
* @param BookingCreateDto $bookingCreateDto
|
|
||||||
* @return array{selectedRooms: array, participantCount: int}
|
* @return array{selectedRooms: array, participantCount: int}
|
||||||
*/
|
*/
|
||||||
public function getRoomSummaryAndParticipantCount(BookingCreateDto $bookingCreateDto): array
|
public function getRoomSummaryAndParticipantCount(BookingCreateDto $bookingCreateDto): array
|
||||||
@@ -142,19 +138,20 @@ class BookingService
|
|||||||
* Groups available rooms by selection type ('by_pax' or 'by_room').
|
* Groups available rooms by selection type ('by_pax' or 'by_room').
|
||||||
*
|
*
|
||||||
* @param array<int, Room> $rooms Rooms indexed by room ID
|
* @param array<int, Room> $rooms Rooms indexed by room ID
|
||||||
|
*
|
||||||
* @return array{by_pax: array<int, Room>, by_room: array<int, Room>}
|
* @return array{by_pax: array<int, Room>, by_room: array<int, Room>}
|
||||||
*/
|
*/
|
||||||
public function groupRoomsBySelectionType(array $rooms): array
|
public function groupRoomsBySelectionType(array $rooms): array
|
||||||
{
|
{
|
||||||
$groups = [
|
$groups = [
|
||||||
'by_pax' => [],
|
Room::SELECTION_TYPE_BY_PAX => [],
|
||||||
'by_room' => [],
|
Room::SELECTION_TYPE_BY_ROOM => [],
|
||||||
];
|
];
|
||||||
foreach ($rooms as $room) {
|
foreach ($rooms as $room) {
|
||||||
if (stripos($room->label, 'bett') !== false) {
|
if (false !== stripos($room->label, 'bett')) {
|
||||||
$groups['by_pax'][$room->id] = $room;
|
$groups[Room::SELECTION_TYPE_BY_PAX][$room->id] = $room;
|
||||||
} else {
|
} else {
|
||||||
$groups['by_room'][$room->id] = $room;
|
$groups[Room::SELECTION_TYPE_BY_ROOM][$room->id] = $room;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,15 +161,16 @@ class BookingService
|
|||||||
/**
|
/**
|
||||||
* Groups roomSelections by selection type ('by_pax' or 'by_room'), using Room::getSelectionType().
|
* Groups roomSelections by selection type ('by_pax' or 'by_room'), using Room::getSelectionType().
|
||||||
*
|
*
|
||||||
* @param array $roomSelections Array of selected RoomSelectionDto
|
* @param array $roomSelections Array of selected RoomSelectionDto
|
||||||
* @param array<int, Room> $roomsById Rooms indexed by room ID
|
* @param array<int, Room> $roomsById Rooms indexed by room ID
|
||||||
|
*
|
||||||
* @return array{by_pax: array, by_room: array}
|
* @return array{by_pax: array, by_room: array}
|
||||||
*/
|
*/
|
||||||
public function groupRoomSelectionsByType(array $roomSelections, array $roomsById): array
|
public function groupRoomSelectionsByType(array $roomSelections, array $roomsById): array
|
||||||
{
|
{
|
||||||
$groups = [
|
$groups = [
|
||||||
'by_pax' => [],
|
Room::SELECTION_TYPE_BY_PAX => [],
|
||||||
'by_room' => [],
|
Room::SELECTION_TYPE_BY_ROOM => [],
|
||||||
];
|
];
|
||||||
foreach ($roomSelections as $roomSelection) {
|
foreach ($roomSelections as $roomSelection) {
|
||||||
$room = $roomsById[$roomSelection->roomId] ?? null;
|
$room = $roomsById[$roomSelection->roomId] ?? null;
|
||||||
@@ -181,7 +179,50 @@ class BookingService
|
|||||||
$groups[$type][] = $roomSelection;
|
$groups[$type][] = $roomSelection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $groups;
|
return $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the room selection has changed between two DTOs.
|
||||||
|
*/
|
||||||
|
public function shouldResetAssignments(BookingCreateDto $oldDto, BookingCreateDto $newDto): bool
|
||||||
|
{
|
||||||
|
$old = array_map(fn($roomSelectionDto) => [$roomSelectionDto->roomId, $roomSelectionDto->quantity], $oldDto->roomSelections);
|
||||||
|
$new = array_map(fn($roomSelectionDto) => [$roomSelectionDto->roomId, $roomSelectionDto->quantity], $newDto->roomSelections);
|
||||||
|
|
||||||
|
return $old !== $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all participant room assignments in the DTO.
|
||||||
|
*/
|
||||||
|
public function resetParticipantAssignments(BookingCreateDto $dto): void
|
||||||
|
{
|
||||||
|
foreach ($dto->participants as $participant) {
|
||||||
|
$participant->assignedRoomId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a snapshot of the current room selection state.
|
||||||
|
*
|
||||||
|
* @return array<int, array{int, int}> Array of [roomId, quantity] pairs
|
||||||
|
*/
|
||||||
|
public function createRoomSelectionSnapshot(BookingCreateDto $dto): array
|
||||||
|
{
|
||||||
|
return array_map(
|
||||||
|
fn($roomSelection) => [$roomSelection->roomId, $roomSelection->quantity],
|
||||||
|
$dto->roomSelections
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if room selection has changed compared to a previous snapshot.
|
||||||
|
*/
|
||||||
|
public function hasRoomSelectionChanged(array $oldSnapshot, BookingCreateDto $newDto): bool
|
||||||
|
{
|
||||||
|
$newSnapshot = $this->createRoomSelectionSnapshot($newDto);
|
||||||
|
return $oldSnapshot !== $newSnapshot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user