wip: backport booking create flow form handling to edit flow
This commit is contained in:
@@ -69,6 +69,7 @@ class BookingDataProcessor
|
||||
* Resets all existing participant-to-service mappings to start with a clean slate.
|
||||
*
|
||||
* This ensures that service assignments are rebuilt from scratch based on current form selections.
|
||||
* Includes resetting room mappings to allow room reassignments during edit.
|
||||
*
|
||||
* @param object $bookingData The booking data object containing services to reset
|
||||
*/
|
||||
@@ -79,6 +80,7 @@ class BookingDataProcessor
|
||||
...$bookingData->transportationServices,
|
||||
...$bookingData->pickupsOutbound,
|
||||
...$bookingData->pickupsInbound,
|
||||
...$bookingData->rooms,
|
||||
];
|
||||
|
||||
foreach ($servicesToReset as $service) {
|
||||
@@ -90,7 +92,7 @@ class BookingDataProcessor
|
||||
* Processes all services for a single participant.
|
||||
*
|
||||
* This orchestrator method handles the complete service assignment workflow for one participant,
|
||||
* including additional services, transportation services, and pickup locations.
|
||||
* including additional services, transportation services, pickup locations, and room assignments.
|
||||
*
|
||||
* @param object $participant The participant data from the form
|
||||
* @param object $bookingData The booking data object to update
|
||||
@@ -105,6 +107,7 @@ class BookingDataProcessor
|
||||
$this->processAdditionalServices($participant, $bookingData, $travelData);
|
||||
$this->processTransportationServices($participant, $bookingData, $travelData);
|
||||
$this->processPickupLocations($participant, $bookingData);
|
||||
$this->processRoomAssignment($participant, $bookingData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,6 +193,31 @@ class BookingDataProcessor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes room assignment for a participant.
|
||||
*
|
||||
* Maps the participant to their assigned room. Allows room reassignment during
|
||||
* booking edits while maintaining the constraint that participants can only be
|
||||
* assigned to room types that have already been booked.
|
||||
*
|
||||
* @param object $participant The participant data from the form
|
||||
* @param object $bookingData The booking data object to update
|
||||
*/
|
||||
private function processRoomAssignment(object $participant, object $bookingData): void
|
||||
{
|
||||
if (null === $participant->assignedRoomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the room in the existing booking by ID
|
||||
foreach ($bookingData->rooms as $room) {
|
||||
if ($room->id === $participant->assignedRoomId) {
|
||||
$room->mapping[] = $participant->index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes services and pickups with no participant mappings.
|
||||
*
|
||||
|
||||
@@ -60,6 +60,10 @@ class BookingEditDto implements BookingDtoInterface
|
||||
$pickup = $booking->getPickupForParticipant($index);
|
||||
$participantData->pickup = $pickup;
|
||||
|
||||
// Room assignment - extract from booking room mappings
|
||||
$room = $booking->getRoomForParticipant($index);
|
||||
$participantData->assignedRoomId = $room?->id;
|
||||
|
||||
$instance->participants[$index] = $participantData;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service\Factory;
|
||||
|
||||
use App\BusProNet\Model\Room;
|
||||
use App\Form\ChoiceLoader\ParticipantRoomChoiceLoader;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Form\Model\RoomSelectionDto;
|
||||
@@ -11,6 +12,9 @@ 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
|
||||
{
|
||||
@@ -20,12 +24,16 @@ class ParticipantRoomChoiceLoaderFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ParticipantRoomChoiceLoader for the given participant and room data.
|
||||
* Creates a ParticipantRoomChoiceLoader for create context.
|
||||
*
|
||||
* @param ParticipantDto[] $allParticipants
|
||||
* @param RoomSelectionDto[] $selectedRooms
|
||||
* Uses room selections from step 1 (RoomSelectionDto objects) to generate
|
||||
* available room choices for participants.
|
||||
*
|
||||
* @param ParticipantDto[] $allParticipants all participants from the booking
|
||||
* @param RoomSelectionDto[] $selectedRooms rooms selected in create flow step 1
|
||||
* @param int $participantIndex index of the participant needing room choices
|
||||
*/
|
||||
public function create(
|
||||
public function createForCreate(
|
||||
array $allParticipants,
|
||||
array $selectedRooms,
|
||||
int $participantIndex,
|
||||
@@ -37,4 +45,62 @@ class ParticipantRoomChoiceLoaderFactory
|
||||
$participantIndex
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ParticipantDto[] $allParticipants all participants from the booking
|
||||
* @param Room[] $bookedRooms rooms from the existing booking
|
||||
* @param int $participantIndex index of the participant needing room choices
|
||||
*/
|
||||
public function createForEdit(
|
||||
array $allParticipants,
|
||||
array $bookedRooms,
|
||||
int $participantIndex,
|
||||
): ParticipantRoomChoiceLoader {
|
||||
// Convert Booking Room objects to RoomSelectionDto format for the choice loader
|
||||
$roomSelections = $this->convertBookedRoomsToSelections($bookedRooms);
|
||||
|
||||
return new ParticipantRoomChoiceLoader(
|
||||
$this->choiceListFactory,
|
||||
$allParticipants,
|
||||
$roomSelections,
|
||||
$participantIndex
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\BusProNet\Model\Service;
|
||||
use App\BusProNet\Utility\DirectionMapper;
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Model\BookingEditDto;
|
||||
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
|
||||
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
|
||||
use App\Service\InsuranceMatchingService;
|
||||
@@ -80,25 +81,44 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
||||
*/
|
||||
protected function registerFieldOptionProviders(): void
|
||||
{
|
||||
// Room assignment field provider (only available for create workflow)
|
||||
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
|
||||
// Room assignment field provider (available for both create and edit workflows)
|
||||
$this->fieldOptionProviders['assignedRoomId'] = function (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) {
|
||||
$choiceLoader = null;
|
||||
$disabled = false;
|
||||
|
||||
if ($bookingDto instanceof BookingCreateDto) {
|
||||
// Create context: use selected rooms from step 1
|
||||
$choiceLoader = $this->roomChoiceLoaderFactory->createForCreate(
|
||||
$bookingDto->participants,
|
||||
$bookingDto->getSelectedRooms(),
|
||||
$participantIndex
|
||||
);
|
||||
// Disable when only one room type selected (auto-assigned)
|
||||
$disabled = 1 === count($bookingDto->getSelectedRooms());
|
||||
} elseif ($bookingDto instanceof BookingEditDto) {
|
||||
// Edit context: use already-booked rooms from booking
|
||||
$choiceLoader = $this->roomChoiceLoaderFactory->createForEdit(
|
||||
$bookingDto->participants,
|
||||
$bookingDto->booking->rooms,
|
||||
$participantIndex
|
||||
);
|
||||
// Disable when only one room in booking (no reassignment needed)
|
||||
$disabled = 1 === count($bookingDto->booking->rooms);
|
||||
}
|
||||
|
||||
return [
|
||||
'label' => 'Zimmer',
|
||||
'placeholder' => 'Nicht zugeordnet',
|
||||
// Use factory to create context-aware choice loader that:
|
||||
// - Shows only available rooms for this participant
|
||||
// - Excludes rooms already assigned to other participants
|
||||
// - Respects room capacity and booking constraints
|
||||
'choice_loader' => $bookingDto instanceof BookingCreateDto
|
||||
? $this->roomChoiceLoaderFactory->create(
|
||||
$bookingDto->participants,
|
||||
$bookingDto->getSelectedRooms(),
|
||||
$participantIndex
|
||||
)
|
||||
: null,
|
||||
// Make field read-only when only one room type is selected
|
||||
// Room is auto-assigned, no user choice needed
|
||||
'disabled' => $bookingDto instanceof BookingCreateDto && 1 === count($bookingDto->getSelectedRooms()),
|
||||
'choice_loader' => $choiceLoader,
|
||||
// Make field read-only when only one room type is available
|
||||
// Room is auto-assigned or cannot be changed, no user choice needed
|
||||
'disabled' => $disabled,
|
||||
];
|
||||
};
|
||||
|
||||
// Courses field provider - provides age-appropriate courses from travel data
|
||||
$this->fieldOptionProviders['courses'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
|
||||
@@ -615,7 +635,6 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates label for rental insurance checkbox including pricing information.
|
||||
*/
|
||||
|
||||
@@ -233,17 +233,11 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Room assignment - read-only display #}
|
||||
{# Room assignment - editable dropdown #}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="font-semibold mb-1 block">Zimmer</label>
|
||||
{% set room = bookingData.roomForParticipant(participantData.index) %}
|
||||
{% if room %}
|
||||
<div class="text-sm">{{ room.label }} ({{ room.individualPrice[participantData.index]|format_currency('EUR') }})</div>
|
||||
{% else %}
|
||||
<div class="text-sm text-gray-500">Keine Unterkunft zugeordnet</div>
|
||||
{% if participant.assignedRoomId is defined %}
|
||||
{{ form_row(participant.assignedRoomId) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if participant.remarksRoom is defined %}
|
||||
{{ form_row(participant.remarksRoom) }}
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user