wip: booking process, refactor participant room assignment logic
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Form;
|
||||
|
||||
use App\BusProNet\Form\CountryType;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Form\Service\ParticipantRoomChoiceLoaderFactory;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
@@ -16,6 +17,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class BookingCreateParticipantType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ParticipantRoomChoiceLoaderFactory $choiceLoaderFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
@@ -58,10 +64,16 @@ class BookingCreateParticipantType extends AbstractType
|
||||
$data = $event->getData();
|
||||
$form = $event->getForm();
|
||||
|
||||
$choiceLoader = $this->choiceLoaderFactory->createChoiceLoader(
|
||||
$options['participant_adapter'],
|
||||
$options['selected_rooms'],
|
||||
$data->index
|
||||
);
|
||||
|
||||
$form->add('assignedRoomId', ChoiceType::class, [
|
||||
'label' => 'Zimmer',
|
||||
'placeholder' => 'Bitte wählen',
|
||||
'choices' => $options['room_choices'][$data->index],
|
||||
'choice_loader' => $choiceLoader,
|
||||
]);
|
||||
})
|
||||
;
|
||||
@@ -71,7 +83,8 @@ class BookingCreateParticipantType extends AbstractType
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ParticipantDto::class,
|
||||
'room_choices' => [],
|
||||
'participant_adapter' => null,
|
||||
'selected_rooms' => [],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ class BookingCreateStep2Type extends AbstractType
|
||||
$form = $event->getForm();
|
||||
|
||||
$adapter = new ParticipantDataAdapter($data->participants);
|
||||
$roomChoices = $this->generateRoomChoices($adapter, $data->getSelectedRooms());
|
||||
$this->addParticipantsField($form, $roomChoices);
|
||||
$this->addParticipantsField($form, $adapter, $data->getSelectedRooms());
|
||||
}
|
||||
|
||||
public function onPreSubmit(FormEvent $event): void
|
||||
@@ -39,73 +38,14 @@ class BookingCreateStep2Type extends AbstractType
|
||||
$bookingCreateDto = $form->getData();
|
||||
|
||||
$adapter = new ParticipantDataAdapter($data['participants']);
|
||||
$roomChoices = $this->generateRoomChoices($adapter, $bookingCreateDto->getSelectedRooms());
|
||||
$form->remove('participants');
|
||||
$this->addParticipantsField($form, $roomChoices);
|
||||
$this->addParticipantsField($form, $adapter, $bookingCreateDto->getSelectedRooms());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates room occupancy based on participant assignments.
|
||||
*
|
||||
* @return array<int, int>
|
||||
* Adds the participants collection field to the form.
|
||||
*/
|
||||
private function calculateRoomOccupancy(ParticipantDataAdapter $adapter): array
|
||||
{
|
||||
$roomOccupancy = [];
|
||||
|
||||
foreach ($adapter->getParticipants() as $participant) {
|
||||
$assignedRoomId = $adapter->getAssignedRoomId($participant);
|
||||
if (null !== $assignedRoomId) {
|
||||
$roomOccupancy[$assignedRoomId] = ($roomOccupancy[$assignedRoomId] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $roomOccupancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates room choices for each participant based on availability and current assignments.
|
||||
*
|
||||
* @return array<int, array<string, int>>
|
||||
*/
|
||||
private function generateRoomChoices(ParticipantDataAdapter $adapter, array $selectedRooms): array
|
||||
{
|
||||
$roomOccupancy = $this->calculateRoomOccupancy($adapter);
|
||||
$roomChoices = [];
|
||||
|
||||
foreach ($adapter->getParticipants() as $index => $participant) {
|
||||
$participantRoomChoices = [];
|
||||
$assignedRoomId = $adapter->getAssignedRoomId($participant);
|
||||
|
||||
foreach ($selectedRooms as $roomSelection) {
|
||||
$currentOccupancy = $roomOccupancy[$roomSelection->roomId] ?? 0;
|
||||
|
||||
// If this participant is already assigned to this room, exclude them from occupancy count
|
||||
$adjustedOccupancy = $currentOccupancy;
|
||||
if ($assignedRoomId === $roomSelection->roomId) {
|
||||
--$adjustedOccupancy;
|
||||
}
|
||||
|
||||
$remainingCapacity = $roomSelection->minPax - $adjustedOccupancy;
|
||||
|
||||
// Include room if it has capacity OR if it's the participant's current assignment
|
||||
if ($remainingCapacity > 0 || $assignedRoomId === $roomSelection->roomId) {
|
||||
$participantRoomChoices[$roomSelection->roomLabel] = $roomSelection->roomId;
|
||||
}
|
||||
}
|
||||
|
||||
$roomChoices[$index] = $participantRoomChoices;
|
||||
}
|
||||
|
||||
return $roomChoices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the participants collection field to the form with the given room choices.
|
||||
*
|
||||
* @param array<int, array<string, int>> $roomChoices
|
||||
*/
|
||||
private function addParticipantsField(FormInterface $form, array $roomChoices): void
|
||||
private function addParticipantsField(FormInterface $form, ParticipantDataAdapter $adapter, array $selectedRooms): void
|
||||
{
|
||||
$form->add('participants', CollectionType::class, [
|
||||
'entry_type' => BookingCreateParticipantType::class,
|
||||
@@ -113,7 +53,8 @@ class BookingCreateStep2Type extends AbstractType
|
||||
'allow_delete' => false,
|
||||
'by_reference' => false,
|
||||
'entry_options' => [
|
||||
'room_choices' => $roomChoices,
|
||||
'participant_adapter' => $adapter,
|
||||
'selected_rooms' => $selectedRooms,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class BoolingEditParticipantType extends AbstractType
|
||||
class BookingEditParticipantType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
@@ -34,7 +34,7 @@ class BoolingEditParticipantType extends AbstractType
|
||||
$form = $event->getForm();
|
||||
|
||||
$personalDataMutable = $options['personal_data_mutable'] && $participantData->mutable;
|
||||
$isApplicant = 1 === $participantIndex;
|
||||
$isApplicant = 0 === $participantIndex;
|
||||
|
||||
$form
|
||||
->add('firstName', TextType::class, [
|
||||
@@ -23,7 +23,7 @@ final class BookingEditType extends AbstractType
|
||||
|
||||
$travelData = $data->travel;
|
||||
$form->add('participants', CollectionType::class, [
|
||||
'entry_type' => BoolingEditParticipantType::class,
|
||||
'entry_type' => BookingEditParticipantType::class,
|
||||
'entry_options' => [
|
||||
'selectable_courses' => $this->mergeSelectableServices($data, Constants::TOKEN_COURSES),
|
||||
'selectable_ski_passes' => $this->mergeSelectableServices($data, Constants::TOKEN_SKI_PASS),
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\ChoiceLoader;
|
||||
|
||||
use App\Form\DataAdapters\ParticipantDataAdapter;
|
||||
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||
|
||||
/**
|
||||
* Choice loader for participant room assignments.
|
||||
*
|
||||
* Generates room choices for each participant based on availability,
|
||||
* current assignments, and room capacity constraints. This loader
|
||||
* ensures participants can only select rooms with available capacity
|
||||
* while maintaining their current assignment if applicable.
|
||||
*/
|
||||
class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface
|
||||
{
|
||||
private ?ChoiceListInterface $choiceList = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly ChoiceListFactoryInterface $factory,
|
||||
private readonly ParticipantDataAdapter $adapter,
|
||||
private readonly array $selectedRooms,
|
||||
private readonly int $participantIndex,
|
||||
) {
|
||||
}
|
||||
|
||||
public function loadChoiceList(?callable $value = null): ChoiceListInterface
|
||||
{
|
||||
if (null === $this->choiceList) {
|
||||
$choices = $this->generateRoomChoicesForParticipant();
|
||||
$this->choiceList = $this->factory->createListFromChoices($choices, $value);
|
||||
}
|
||||
|
||||
return $this->choiceList;
|
||||
}
|
||||
|
||||
public function loadChoicesForValues(array $values, ?callable $value = null): array
|
||||
{
|
||||
if (empty($values)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->loadChoiceList($value)->getChoicesForValues($values);
|
||||
}
|
||||
|
||||
public function loadValuesForChoices(array $choices, ?callable $value = null): array
|
||||
{
|
||||
if (empty($choices)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->loadChoiceList($value)->getValuesForChoices($choices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates room choices for the specific participant.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
private function generateRoomChoicesForParticipant(): array
|
||||
{
|
||||
$roomOccupancy = $this->calculateRoomOccupancy();
|
||||
$participantRoomChoices = [];
|
||||
$assignedRoomId = $this->getParticipantAssignedRoomId();
|
||||
|
||||
foreach ($this->selectedRooms as $roomSelection) {
|
||||
$currentOccupancy = $roomOccupancy[$roomSelection->roomId] ?? 0;
|
||||
|
||||
// If this participant is already assigned to this room, exclude them from occupancy count
|
||||
$adjustedOccupancy = $currentOccupancy;
|
||||
if ($assignedRoomId === $roomSelection->roomId) {
|
||||
--$adjustedOccupancy;
|
||||
}
|
||||
|
||||
$remainingCapacity = $roomSelection->minPax - $adjustedOccupancy;
|
||||
|
||||
// Include room if it has capacity OR if it's the participant's current assignment
|
||||
if ($remainingCapacity > 0 || $assignedRoomId === $roomSelection->roomId) {
|
||||
$participantRoomChoices[$roomSelection->roomLabel] = $roomSelection->roomId;
|
||||
}
|
||||
}
|
||||
|
||||
return $participantRoomChoices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates room occupancy based on participant assignments.
|
||||
*
|
||||
* @return array<int, int>
|
||||
*/
|
||||
private function calculateRoomOccupancy(): array
|
||||
{
|
||||
$roomOccupancy = [];
|
||||
|
||||
foreach ($this->adapter->getParticipants() as $participant) {
|
||||
$assignedRoomId = $this->adapter->getAssignedRoomId($participant);
|
||||
if (null !== $assignedRoomId) {
|
||||
$roomOccupancy[$assignedRoomId] = ($roomOccupancy[$assignedRoomId] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $roomOccupancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the assigned room ID for the current participant.
|
||||
*/
|
||||
private function getParticipantAssignedRoomId(): ?int
|
||||
{
|
||||
$participants = $this->adapter->getParticipants();
|
||||
$participant = $participants[$this->participantIndex] ?? null;
|
||||
|
||||
return null !== $participant ? $this->adapter->getAssignedRoomId($participant) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service;
|
||||
|
||||
use App\Form\ChoiceLoader\ParticipantRoomChoiceLoader;
|
||||
use App\Form\DataAdapters\ParticipantDataAdapter;
|
||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
||||
|
||||
/**
|
||||
* Factory service for creating ParticipantRoomChoiceLoader instances.
|
||||
*
|
||||
* This service provides a clean way to create choice loaders with proper
|
||||
* dependency injection instead of manually instantiating factories in form types.
|
||||
*/
|
||||
class ParticipantRoomChoiceLoaderFactory
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ChoiceListFactoryInterface $choiceListFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ParticipantRoomChoiceLoader for the given participant and room data.
|
||||
*/
|
||||
public function createChoiceLoader(
|
||||
ParticipantDataAdapter $adapter,
|
||||
array $selectedRooms,
|
||||
int $participantIndex,
|
||||
): ParticipantRoomChoiceLoader {
|
||||
return new ParticipantRoomChoiceLoader(
|
||||
$this->choiceListFactory,
|
||||
$adapter,
|
||||
$selectedRooms,
|
||||
$participantIndex
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user