feat: replace overly complex and unnecessary form choice loader for rooms
This commit is contained in:
@@ -64,10 +64,6 @@ services:
|
|||||||
$cache: '@cache.app'
|
$cache: '@cache.app'
|
||||||
$logger: '@monolog.logger.bpn'
|
$logger: '@monolog.logger.bpn'
|
||||||
|
|
||||||
App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory:
|
|
||||||
arguments:
|
|
||||||
$choiceListFactory: '@form.choice_list_factory.default'
|
|
||||||
|
|
||||||
# Field Handlers with dependencies
|
# Field Handlers with dependencies
|
||||||
App\Form\Service\ParticipantInsuranceFieldHandler: ~
|
App\Form\Service\ParticipantInsuranceFieldHandler: ~
|
||||||
App\Form\Service\ParticipantBulkInsuranceFieldHandler: ~
|
App\Form\Service\ParticipantBulkInsuranceFieldHandler: ~
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Form\ChoiceLoader;
|
|
||||||
|
|
||||||
use App\Form\Model\RoomSelectionDto;
|
|
||||||
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 from all selected room types
|
|
||||||
* in Step 1, regardless of current capacity or assignments. Capacity conflicts
|
|
||||||
* are automatically resolved by the field handler through intelligent
|
|
||||||
* auto-unassignment when needed.
|
|
||||||
*/
|
|
||||||
class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface
|
|
||||||
{
|
|
||||||
private ?ChoiceListInterface $choiceList = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param RoomSelectionDto[] $selectedRooms the rooms selected in the previous step
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private readonly ChoiceListFactoryInterface $factory,
|
|
||||||
private readonly array $selectedRooms,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* Always includes all selected rooms from Step 1 regardless of current capacity.
|
|
||||||
* Capacity conflicts are handled by the field handler during assignment.
|
|
||||||
*
|
|
||||||
* @return array<string, int>
|
|
||||||
*/
|
|
||||||
private function generateRoomChoicesForParticipant(): array
|
|
||||||
{
|
|
||||||
$participantRoomChoices = [];
|
|
||||||
|
|
||||||
foreach ($this->selectedRooms as $roomSelection) {
|
|
||||||
// Always include all selected rooms - capacity conflicts handled in field handler
|
|
||||||
$participantRoomChoices[$roomSelection->roomLabel] = $roomSelection->roomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $participantRoomChoices;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Form\Service\Factory;
|
|
||||||
|
|
||||||
use App\BusProNet\Model\Room;
|
|
||||||
use App\Form\ChoiceLoader\ParticipantRoomChoiceLoader;
|
|
||||||
use App\Form\Model\RoomSelectionDto;
|
|
||||||
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory service for creating ParticipantRoomChoiceLoader instances.
|
|
||||||
*
|
|
||||||
* Provides factory methods for both create and edit contexts, converting
|
|
||||||
* different room data formats into a unified choice loader interface.
|
|
||||||
*/
|
|
||||||
class ParticipantRoomChoiceLoaderFactory
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly ChoiceListFactoryInterface $choiceListFactory,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a ParticipantRoomChoiceLoader for create context.
|
|
||||||
*
|
|
||||||
* Uses room selections from step 1 (RoomSelectionDto objects) to generate
|
|
||||||
* available room choices for participants.
|
|
||||||
*
|
|
||||||
* @param RoomSelectionDto[] $selectedRooms rooms selected in create flow step 1
|
|
||||||
*/
|
|
||||||
public function createForCreate(array $selectedRooms): ParticipantRoomChoiceLoader
|
|
||||||
{
|
|
||||||
return new ParticipantRoomChoiceLoader(
|
|
||||||
$this->choiceListFactory,
|
|
||||||
$selectedRooms
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a ParticipantRoomChoiceLoader for edit context.
|
|
||||||
*
|
|
||||||
* Uses already-booked rooms from the existing booking to generate available
|
|
||||||
* room choices. Participants can only be reassigned within the room types
|
|
||||||
* that have already been booked.
|
|
||||||
*
|
|
||||||
* @param Room[] $bookedRooms rooms from the existing booking
|
|
||||||
*/
|
|
||||||
public function createForEdit(array $bookedRooms): ParticipantRoomChoiceLoader
|
|
||||||
{
|
|
||||||
// Convert Booking Room objects to RoomSelectionDto format for the choice loader
|
|
||||||
$roomSelections = $this->convertBookedRoomsToSelections($bookedRooms);
|
|
||||||
|
|
||||||
return new ParticipantRoomChoiceLoader(
|
|
||||||
$this->choiceListFactory,
|
|
||||||
$roomSelections
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts booked Room objects to RoomSelectionDto format.
|
|
||||||
*
|
|
||||||
* This adapter method allows reusing the existing ParticipantRoomChoiceLoader
|
|
||||||
* logic for edit context by converting the Booking->rooms array into the
|
|
||||||
* same format used in the create flow.
|
|
||||||
*
|
|
||||||
* @param Room[] $bookedRooms rooms from existing booking
|
|
||||||
*
|
|
||||||
* @return RoomSelectionDto[] converted room selections
|
|
||||||
*/
|
|
||||||
private function convertBookedRoomsToSelections(array $bookedRooms): array
|
|
||||||
{
|
|
||||||
$selections = [];
|
|
||||||
|
|
||||||
foreach ($bookedRooms as $room) {
|
|
||||||
$selection = new RoomSelectionDto();
|
|
||||||
$selection->roomId = $room->id;
|
|
||||||
$selection->roomLabel = $room->label;
|
|
||||||
$selection->roomPrice = $room->price;
|
|
||||||
// In edit context, totalCount represents how many of this room type were booked
|
|
||||||
$selection->quantity = $room->totalCount ?? 1;
|
|
||||||
// maxPax is the capacity per room
|
|
||||||
$selection->capacity = $room->maxPax ?? 1;
|
|
||||||
|
|
||||||
$selections[] = $selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $selections;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,12 +7,12 @@ namespace App\Form\Service;
|
|||||||
use App\BusProNet\Constants;
|
use App\BusProNet\Constants;
|
||||||
use App\BusProNet\Model\Insurance;
|
use App\BusProNet\Model\Insurance;
|
||||||
use App\BusProNet\Model\Pickup;
|
use App\BusProNet\Model\Pickup;
|
||||||
|
use App\BusProNet\Model\Room;
|
||||||
use App\BusProNet\Model\Service;
|
use App\BusProNet\Model\Service;
|
||||||
use App\BusProNet\Utility\DirectionMapper;
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
use App\Form\Model\BookingDto;
|
use App\Form\Model\BookingDto;
|
||||||
use App\Form\Model\RoomSelectionDto;
|
use App\Form\Model\RoomSelectionDto;
|
||||||
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
|
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
|
||||||
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
|
|
||||||
use App\Service\BookingPriceCalculatorService;
|
use App\Service\BookingPriceCalculatorService;
|
||||||
use App\Service\InsuranceService;
|
use App\Service\InsuranceService;
|
||||||
use App\Service\ServiceAvailabilityCalculator;
|
use App\Service\ServiceAvailabilityCalculator;
|
||||||
@@ -36,21 +36,7 @@ use App\Service\ServiceAvailabilityCalculator;
|
|||||||
*/
|
*/
|
||||||
class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Initializes the provider with required dependencies.
|
|
||||||
*
|
|
||||||
* The provider automatically registers all field option providers during
|
|
||||||
* construction to ensure they're available for form building. This approach
|
|
||||||
* keeps all field configuration logic centralized and makes it easy to add
|
|
||||||
* new dynamic fields.
|
|
||||||
*
|
|
||||||
* @param ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory Factory for creating room choice loaders
|
|
||||||
* @param ServiceAvailabilityCalculator $serviceAvailabilityCalculator Service for calculating dynamic availability
|
|
||||||
* @param InsuranceService $insuranceService Service for insurance operations
|
|
||||||
* @param BookingPriceCalculatorService $priceCalculatorService Service for calculating participant prices
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
|
|
||||||
private readonly ServiceAvailabilityCalculator $serviceAvailabilityCalculator,
|
private readonly ServiceAvailabilityCalculator $serviceAvailabilityCalculator,
|
||||||
private readonly InsuranceService $insuranceService,
|
private readonly InsuranceService $insuranceService,
|
||||||
private readonly BookingPriceCalculatorService $priceCalculatorService,
|
private readonly BookingPriceCalculatorService $priceCalculatorService,
|
||||||
@@ -83,8 +69,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
{
|
{
|
||||||
// Room assignment field provider (available for both create and edit workflows)
|
// Room assignment field provider (available for both create and edit workflows)
|
||||||
$this->fieldOptionProviders['assignedRoomId'] = function (BookingDto $bookingDto, int $participantIndex, array $options = []) {
|
$this->fieldOptionProviders['assignedRoomId'] = function (BookingDto $bookingDto, int $participantIndex, array $options = []) {
|
||||||
$choiceLoader = null;
|
$choices = [];
|
||||||
$singleChoice = false;
|
|
||||||
|
|
||||||
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
|
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
|
||||||
// Create context: use selected rooms from step 1, filtered by participant age
|
// Create context: use selected rooms from step 1, filtered by participant age
|
||||||
@@ -93,26 +78,19 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
$bookingDto,
|
$bookingDto,
|
||||||
$participantIndex
|
$participantIndex
|
||||||
);
|
);
|
||||||
$choiceLoader = $this->roomChoiceLoaderFactory->createForCreate($filteredRooms);
|
$choices = $this->buildRoomChoicesFromSelections($filteredRooms);
|
||||||
$singleChoice = 1 === count($filteredRooms);
|
|
||||||
} elseif (BookingDto::MODE_EDIT === $bookingDto->getMode()) {
|
} elseif (BookingDto::MODE_EDIT === $bookingDto->getMode()) {
|
||||||
// Edit context: use already-booked rooms from booking
|
// Edit context: use already-booked rooms from booking
|
||||||
$choiceLoader = $this->roomChoiceLoaderFactory->createForEdit(
|
$choices = $this->buildRoomChoicesFromBookedRooms($bookingDto->booking->rooms);
|
||||||
$bookingDto->booking->rooms
|
|
||||||
);
|
|
||||||
$singleChoice = 1 === count($bookingDto->booking->rooms);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$singleChoice = 1 === count($choices);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'label' => 'Zimmer',
|
'label' => 'Zimmer',
|
||||||
// No placeholder when only one room available - the only option is pre-selected
|
// No placeholder when only one room available - the only option is pre-selected
|
||||||
'placeholder' => $singleChoice ? false : 'Nicht zugeordnet',
|
'placeholder' => $singleChoice ? false : 'Nicht zugeordnet',
|
||||||
// Use factory to create context-aware choice loader that:
|
'choices' => $choices,
|
||||||
// - Shows only available rooms for this participant
|
|
||||||
// - Excludes rooms already assigned to other participants
|
|
||||||
// - Respects room capacity and booking constraints
|
|
||||||
// - Filters Baby rooms for participants over 2 years old
|
|
||||||
'choice_loader' => $choiceLoader,
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -548,6 +526,40 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds room choices array from RoomSelectionDto objects.
|
||||||
|
*
|
||||||
|
* @param RoomSelectionDto[] $roomSelections Room selections from step 1
|
||||||
|
*
|
||||||
|
* @return array<string, int> Choices array with label => id
|
||||||
|
*/
|
||||||
|
private function buildRoomChoicesFromSelections(array $roomSelections): array
|
||||||
|
{
|
||||||
|
$choices = [];
|
||||||
|
foreach ($roomSelections as $roomSelection) {
|
||||||
|
$choices[$roomSelection->roomLabel] = $roomSelection->roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds room choices array from booked Room objects.
|
||||||
|
*
|
||||||
|
* @param Room[] $bookedRooms Rooms from existing booking
|
||||||
|
*
|
||||||
|
* @return array<string, int> Choices array with label => id
|
||||||
|
*/
|
||||||
|
private function buildRoomChoicesFromBookedRooms(array $bookedRooms): array
|
||||||
|
{
|
||||||
|
$choices = [];
|
||||||
|
foreach ($bookedRooms as $room) {
|
||||||
|
$choices[$room->label] = $room->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $choices;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters rental services by selected skipass duration.
|
* Filters rental services by selected skipass duration.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use App\BusProNet\Model\Travel;
|
|||||||
use App\BusProNet\Utility\DirectionMapper;
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
use App\Form\Model\BookingDto;
|
use App\Form\Model\BookingDto;
|
||||||
use App\Form\Model\ParticipantDto;
|
use App\Form\Model\ParticipantDto;
|
||||||
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
|
|
||||||
use App\Form\Service\ParticipantFieldOptionsProvider;
|
use App\Form\Service\ParticipantFieldOptionsProvider;
|
||||||
use App\Service\BookingPriceCalculatorService;
|
use App\Service\BookingPriceCalculatorService;
|
||||||
use App\Service\InsuranceService;
|
use App\Service\InsuranceService;
|
||||||
@@ -24,13 +23,11 @@ class ParticipantFieldOptionsProviderBabyTest extends TestCase
|
|||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$roomChoiceLoaderFactory = $this->createMock(ParticipantRoomChoiceLoaderFactory::class);
|
|
||||||
$this->serviceAvailabilityCalculator = $this->createMock(ServiceAvailabilityCalculator::class);
|
$this->serviceAvailabilityCalculator = $this->createMock(ServiceAvailabilityCalculator::class);
|
||||||
$insuranceService = $this->createMock(InsuranceService::class);
|
$insuranceService = $this->createMock(InsuranceService::class);
|
||||||
$priceCalculatorService = $this->createMock(BookingPriceCalculatorService::class);
|
$priceCalculatorService = $this->createMock(BookingPriceCalculatorService::class);
|
||||||
|
|
||||||
$this->provider = new ParticipantFieldOptionsProvider(
|
$this->provider = new ParticipantFieldOptionsProvider(
|
||||||
$roomChoiceLoaderFactory,
|
|
||||||
$this->serviceAvailabilityCalculator,
|
$this->serviceAvailabilityCalculator,
|
||||||
$insuranceService,
|
$insuranceService,
|
||||||
$priceCalculatorService
|
$priceCalculatorService
|
||||||
|
|||||||
Reference in New Issue
Block a user