diff --git a/src/BusProNet/DataProcessor/BookingDataProcessor.php b/src/BusProNet/DataProcessor/BookingDataProcessor.php index e61f1be..d431a59 100644 --- a/src/BusProNet/DataProcessor/BookingDataProcessor.php +++ b/src/BusProNet/DataProcessor/BookingDataProcessor.php @@ -152,9 +152,9 @@ class BookingDataProcessor $room = $availableRooms[$bookingRoom->id] ?? null; if (null !== $room && null !== $bookingRoom->totalCount && $bookingRoom->totalCount > 0) { $roomSelection = new RoomSelectionDto(); - $roomSelection->roomId = $bookingRoom->id; - $roomSelection->roomLabel = $room->label; - $roomSelection->roomPrice = $room->price; + $roomSelection->id = $bookingRoom->id; + $roomSelection->label = $room->label; + $roomSelection->price = $room->price; $roomSelection->quantity = $bookingRoom->totalCount; // Actual quantity from booking XML (anzahl) $dto->roomSelections[] = $roomSelection; } diff --git a/src/BusProNet/DataProcessor/BookingPayloadBuilder.php b/src/BusProNet/DataProcessor/BookingPayloadBuilder.php index 27773f7..c490fed 100644 --- a/src/BusProNet/DataProcessor/BookingPayloadBuilder.php +++ b/src/BusProNet/DataProcessor/BookingPayloadBuilder.php @@ -451,7 +451,7 @@ class BookingPayloadBuilder $roomQuantities = []; foreach ($bookingDto->roomSelections as $selection) { if ($selection->quantity > 0) { - $roomQuantities[$selection->roomId] = $selection->quantity; + $roomQuantities[$selection->id] = $selection->quantity; } } diff --git a/src/Form/BookingParticipantType.php b/src/Form/BookingParticipantType.php index 174fab2..50f8414 100644 --- a/src/Form/BookingParticipantType.php +++ b/src/Form/BookingParticipantType.php @@ -316,7 +316,7 @@ class BookingParticipantType extends AbstractType private function addDynamicFields(FormInterface $form, BookingDto $bookingDto, int $participantIndex): void { $dynamicFields = [ - 'assignedRoomId' => ChoiceType::class, + 'assignedRoomId' => RoomAssignmentType::class, 'remarksRoom' => TextareaType::class, 'courses' => ChoiceType::class, 'additionalServices' => ChoiceType::class, @@ -350,7 +350,8 @@ class BookingParticipantType extends AbstractType $fieldOptions = $this->fieldOptionsProvider->getFieldOptions($fieldName, $bookingDto, $participantIndex); // Skip choice fields without any choices - if (ChoiceType::class === $fieldType && false === $this->hasValidFieldOptions($fieldOptions)) { + $isChoiceField = ChoiceType::class === $fieldType || RoomAssignmentType::class === $fieldType; + if ($isChoiceField && false === $this->hasValidFieldOptions($fieldOptions)) { continue; } diff --git a/src/Form/DataTransformer/RoomSelectionToIdTransformer.php b/src/Form/DataTransformer/RoomSelectionToIdTransformer.php new file mode 100644 index 0000000..c0df431 --- /dev/null +++ b/src/Form/DataTransformer/RoomSelectionToIdTransformer.php @@ -0,0 +1,79 @@ + + */ +class RoomSelectionToIdTransformer implements DataTransformerInterface +{ + /** + * @param RoomSelectionDto[] $roomSelections Available room selections for reverse lookup + */ + public function __construct( + private readonly array $roomSelections, + ) { + } + + /** + * Transforms an integer room ID to a RoomSelectionDto for form display. + * + * @param int|null $value The room ID from the model + * + * @return RoomSelectionDto|null The matching RoomSelectionDto or null + */ + public function transform(mixed $value): ?RoomSelectionDto + { + if (null === $value) { + return null; + } + + foreach ($this->roomSelections as $roomSelection) { + if ($roomSelection->id === $value) { + return $roomSelection; + } + } + + return null; + } + + /** + * Transforms a RoomSelectionDto back to an integer room ID for the model. + * + * @param RoomSelectionDto|null $value The selected RoomSelectionDto from the form + * + * @return int|null The room ID or null + * + * @throws TransformationFailedException If an unexpected value type is received + */ + public function reverseTransform(mixed $value): ?int + { + if (null === $value) { + return null; + } + + if ($value instanceof RoomSelectionDto) { + return $value->id; + } + + // Handle case where form submits scalar ID directly + if (is_int($value) || is_string($value)) { + return (int) $value; + } + + throw new TransformationFailedException(sprintf('Expected RoomSelectionDto, int, or null, got %s', get_debug_type($value))); + } +} diff --git a/src/Form/Model/BookingDto.php b/src/Form/Model/BookingDto.php index be94a46..451bdc1 100644 --- a/src/Form/Model/BookingDto.php +++ b/src/Form/Model/BookingDto.php @@ -292,7 +292,7 @@ class BookingDto public function createRoomSelectionSnapshot(): array { return array_map( - fn ($roomSelection) => [(int) $roomSelection->roomId, (int) $roomSelection->quantity], + fn ($roomSelection) => [(int) $roomSelection->id, (int) $roomSelection->quantity], $this->roomSelections ); } diff --git a/src/Form/Model/RoomSelectionDto.php b/src/Form/Model/RoomSelectionDto.php index bf88b9d..b73a747 100644 --- a/src/Form/Model/RoomSelectionDto.php +++ b/src/Form/Model/RoomSelectionDto.php @@ -1,5 +1,7 @@ addModelTransformer( + new RoomSelectionToIdTransformer($options['choices']) + ); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'expanded' => true, + 'multiple' => false, + 'choice_value' => fn (RoomSelectionDto|int|null $room) => $room instanceof RoomSelectionDto ? $room->id : $room, + 'choice_label' => fn (?RoomSelectionDto $room) => $room?->label, + ]); + } + + public function getParent(): string + { + return ChoiceType::class; + } +} diff --git a/src/Form/RoomSelectType.php b/src/Form/RoomSelectType.php index ac9a5da..96de6d4 100644 --- a/src/Form/RoomSelectType.php +++ b/src/Form/RoomSelectType.php @@ -17,7 +17,7 @@ class RoomSelectType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options): void { $builder - ->add('roomId', HiddenType::class) + ->add('id', HiddenType::class) ->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); @@ -36,11 +36,11 @@ class RoomSelectType extends AbstractType { $data = $form->getData(); - $view->vars['label_room'] = $data->roomLabel; + $view->vars['label_room'] = $data->label; $view->vars['label_price'] = null; - if (null !== $data->roomPrice) { - $formattedPrice = number_format((float) $data->roomPrice, 2, ',', '.'); + if (null !== $data->price) { + $formattedPrice = number_format((float) $data->price, 2, ',', '.'); $view->vars['label_price'] = sprintf(' %s € pro Person', $formattedPrice); } } diff --git a/src/Form/Service/EditFieldStateProvider.php b/src/Form/Service/EditFieldStateProvider.php index 23ba391..7cf85dd 100644 --- a/src/Form/Service/EditFieldStateProvider.php +++ b/src/Form/Service/EditFieldStateProvider.php @@ -17,6 +17,7 @@ use App\Form\Service\Condition\FieldValueCondition; use App\Form\Service\Condition\PersonalDataMutabilityCondition; use App\Form\Service\Condition\PickupsMutabilityCondition; use App\Form\Service\Condition\RentalSelectionCondition; +use App\Form\Service\Condition\RoomSelectionCondition; use App\Form\Service\Condition\ServiceSubTypeCondition; use App\Form\Service\Condition\SkiPassSelectionCondition; use App\Form\Service\Condition\TransportationServicesMutabilityCondition; @@ -104,6 +105,12 @@ class EditFieldStateProvider extends AbstractFieldStateProvider 'static_text' => new BookingModeCondition(BookingDto::MODE_EDIT), ]; + // Show remarks room field only when room with code 'mbz' (single bed) is selected + $sharedRoomCondition = new RoomSelectionCondition(['mbz']); + $this->fieldStateConditions['remarksRoom'] = [ + 'hidden' => CompositeCondition::not($sharedRoomCondition), + ]; + // Conditional visibility for service fields (same as create flow) $rentalCondition = new RentalSelectionCondition(); $skiPassCondition = new SkiPassSelectionCondition(); diff --git a/src/Form/Service/ParticipantAssignedRoomFieldHandler.php b/src/Form/Service/ParticipantAssignedRoomFieldHandler.php index f9e9180..00e45f3 100644 --- a/src/Form/Service/ParticipantAssignedRoomFieldHandler.php +++ b/src/Form/Service/ParticipantAssignedRoomFieldHandler.php @@ -110,7 +110,7 @@ class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandle // Get the room selection details $roomSelection = null; foreach ($bookingDto->roomSelections as $selection) { - if ($selection->roomId === $roomId) { + if ($selection->id === $roomId) { $roomSelection = $selection; break; } @@ -164,7 +164,7 @@ class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandle // Calculate how many need to be unassigned $roomSelection = null; foreach ($bookingDto->roomSelections as $selection) { - if ($selection->roomId === $roomId) { + if ($selection->id === $roomId) { $roomSelection = $selection; break; } @@ -229,8 +229,8 @@ class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandle private function getRoomLabelById(int $roomId, BookingDto $bookingDto): string { foreach ($bookingDto->roomSelections as $selection) { - if ($selection->roomId === $roomId) { - return $selection->roomLabel; + if ($selection->id === $roomId) { + return $selection->label; } } diff --git a/src/Form/Service/ParticipantFieldOptionsProvider.php b/src/Form/Service/ParticipantFieldOptionsProvider.php index 729b607..290caae 100644 --- a/src/Form/Service/ParticipantFieldOptionsProvider.php +++ b/src/Form/Service/ParticipantFieldOptionsProvider.php @@ -73,15 +73,14 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider if (BookingDto::MODE_CREATE === $bookingDto->getMode()) { // Create context: use selected rooms from step 1, filtered by participant age - $filteredRooms = $this->filterRoomsByParticipantAge( + $choices = $this->filterRoomsByParticipantAge( $bookingDto->getSelectedRooms(), $bookingDto, $participantIndex ); - $choices = $this->buildRoomChoicesFromSelections($filteredRooms); } elseif (BookingDto::MODE_EDIT === $bookingDto->getMode()) { - // Edit context: use already-booked rooms from booking - $choices = $this->buildRoomChoicesFromBookedRooms($bookingDto->booking->rooms); + // Edit context: convert booked rooms to RoomSelectionDto for consistent handling + $choices = $this->convertBookedRoomsToSelectionDtos($bookingDto->booking->rooms); } $singleChoice = 1 === count($choices); @@ -93,6 +92,8 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider // No placeholder when only one room available - the only option is pre-selected 'placeholder' => $singleChoice ? false : 'Nicht zugeordnet', 'choices' => $choices, + 'choice_value' => fn (RoomSelectionDto|int|null $room) => $room instanceof RoomSelectionDto ? $room->id : $room, + 'choice_label' => fn (?RoomSelectionDto $room) => $room?->label, ]; }; @@ -511,7 +512,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider $availableRooms = $bookingDto->travel->getAvailableRooms(); return array_filter($roomSelections, function (RoomSelectionDto $roomSelection) use ($availableRooms, $age) { - $room = $availableRooms[$roomSelection->roomId] ?? null; + $room = $availableRooms[$roomSelection->id] ?? null; // Exclude rooms not found in travel data to avoid validation issues if (null === $room) { @@ -529,37 +530,28 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider } /** - * Builds room choices array from RoomSelectionDto objects. + * Converts booked Room objects to RoomSelectionDto for consistent form handling. * - * @param RoomSelectionDto[] $roomSelections Room selections from step 1 - * - * @return array 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. + * In edit mode, we need to convert Room entities to RoomSelectionDto objects + * so the form can use the same choice_value and choice_label configuration + * as create mode, enabling consistent price display in the template. * * @param Room[] $bookedRooms Rooms from existing booking * - * @return array Choices array with label => id + * @return RoomSelectionDto[] Array of room selection DTOs */ - private function buildRoomChoicesFromBookedRooms(array $bookedRooms): array + private function convertBookedRoomsToSelectionDtos(array $bookedRooms): array { - $choices = []; + $selections = []; foreach ($bookedRooms as $room) { - $choices[$room->label] = $room->id; + $selection = new RoomSelectionDto(); + $selection->id = $room->id; + $selection->label = $room->label; + $selection->price = $room->price; + $selections[] = $selection; } - return $choices; + return $selections; } /** diff --git a/src/Service/BookingService.php b/src/Service/BookingService.php index c5b49a9..d35e799 100644 --- a/src/Service/BookingService.php +++ b/src/Service/BookingService.php @@ -255,9 +255,9 @@ class BookingService private function createRoomSelection(Room $room, array $roomsIdsAndQuantities): RoomSelectionDto { $selection = new RoomSelectionDto(); - $selection->roomId = $room->id; - $selection->roomLabel = $room->label; - $selection->roomPrice = $room->price; + $selection->id = $room->id; + $selection->label = $room->label; + $selection->price = $room->price; $selection->maxQuantity = $room->available; $selection->capacity = $room->minPax; $selection->quantity = $roomsIdsAndQuantities[$room->id] ?? 0; @@ -282,7 +282,7 @@ class BookingService $rooms = $travelData->getAvailableRooms(); foreach ($roomSelections as $roomSelection) { - $room = $rooms[$roomSelection->roomId]; + $room = $rooms[$roomSelection->id]; $participantsCount += $room->minPax * $roomSelection->quantity; } @@ -369,7 +369,7 @@ class BookingService Room::SELECTION_TYPE_BY_ROOM => [], ]; foreach ($roomSelections as $roomSelection) { - $room = $roomsById[$roomSelection->roomId] ?? null; + $room = $roomsById[$roomSelection->id] ?? null; if ($room) { $type = $room->getSelectionType(); $groups[$type][] = $roomSelection; @@ -535,7 +535,7 @@ class BookingService // Check selected rooms for inquiry-only status foreach ($bookingDto->getSelectedRooms() as $roomSelection) { - $room = $bookingDto->travel->getRoomById($roomSelection->roomId); + $room = $bookingDto->travel->getRoomById($roomSelection->id); if ($room && Constants::STATUS_ON_REQUEST === $room->status && $room->available > 0) { diff --git a/src/Service/BookingSummaryDataService.php b/src/Service/BookingSummaryDataService.php index 635c3c3..7b93ce4 100644 --- a/src/Service/BookingSummaryDataService.php +++ b/src/Service/BookingSummaryDataService.php @@ -118,8 +118,8 @@ class BookingSummaryDataService $availableRooms = $bookingDto->travel->getAvailableRooms(); foreach ($bookingDto->roomSelections as $selection) { - if ($selection->quantity > 0 && isset($availableRooms[$selection->roomId])) { - $room = $availableRooms[$selection->roomId]; + if ($selection->quantity > 0 && isset($availableRooms[$selection->id])) { + $room = $availableRooms[$selection->id]; $totalCapacity += $selection->quantity * ($room->maxPax ?? 0); } } diff --git a/src/Service/RoomAssignmentService.php b/src/Service/RoomAssignmentService.php index 8b8bad2..59cf6ef 100644 --- a/src/Service/RoomAssignmentService.php +++ b/src/Service/RoomAssignmentService.php @@ -66,7 +66,7 @@ class RoomAssignmentService $availableRooms = $dto->travel->getAvailableRooms(); foreach ($selectedRooms as $roomSelection) { - $room = $availableRooms[$roomSelection->roomId] ?? null; + $room = $availableRooms[$roomSelection->id] ?? null; if (null === $room) { continue; // Skip if room not found diff --git a/src/Service/RoomPricingCalculator.php b/src/Service/RoomPricingCalculator.php index 5d09dab..81cc799 100644 --- a/src/Service/RoomPricingCalculator.php +++ b/src/Service/RoomPricingCalculator.php @@ -78,7 +78,7 @@ class RoomPricingCalculator } foreach ($selectedRooms as $roomSelection) { - $room = $this->getRoomById($bookingDto, $roomSelection->roomId); + $room = $this->getRoomById($bookingDto, $roomSelection->id); if (null === $room || null === $room->price) { continue; } diff --git a/src/Validator/Constraints/RoomSelectionValidator.php b/src/Validator/Constraints/RoomSelectionValidator.php index 1dcd4af..20eee44 100644 --- a/src/Validator/Constraints/RoomSelectionValidator.php +++ b/src/Validator/Constraints/RoomSelectionValidator.php @@ -45,7 +45,7 @@ class RoomSelectionValidator extends ConstraintValidator $hasBabyRoom = false; foreach ($selectedRooms as $roomSelection) { - $room = $availableRooms[$roomSelection->roomId] ?? null; + $room = $availableRooms[$roomSelection->id] ?? null; if (null === $room) { continue; diff --git a/templates/booking/_form_theme.html.twig b/templates/booking/_form_theme.html.twig index 4ee14e7..578b69d 100644 --- a/templates/booking/_form_theme.html.twig +++ b/templates/booking/_form_theme.html.twig @@ -84,28 +84,3 @@ {% endfor -%} {%- endblock choice_widget_expanded -%} - -{# Room assignment field - simple scalar choices (label => id), no price column #} -{%- block _participant_assignedRoomId_widget -%} - - {%- for child in form %} - {%- set child_attr = {} -%} - {%- if attr['hx-trigger'] is defined -%} - {%- set child_attr = { - 'hx-trigger': attr['hx-trigger'], - 'hx-post': attr['hx-post'], - 'hx-target': attr['hx-target']|default('#main-content'), - 'hx-swap': attr['hx-swap'] - } -%} - {%- endif -%} - - - - - {% endfor -%} -
-
{{- child.vars.label -}}
-
- {{- form_widget(child, { 'attr': child_attr }) -}} -
-{%- endblock _participant_assignedRoomId_widget -%} diff --git a/tests/Form/Service/Condition/SingleRoomTypeConditionTest.php b/tests/Form/Service/Condition/SingleRoomTypeConditionTest.php index 7471f70..185582d 100644 --- a/tests/Form/Service/Condition/SingleRoomTypeConditionTest.php +++ b/tests/Form/Service/Condition/SingleRoomTypeConditionTest.php @@ -130,7 +130,7 @@ class SingleRoomTypeConditionTest extends TestCase foreach ($rooms as $roomData) { $roomSelection = new RoomSelectionDto(); - $roomSelection->roomId = $roomData['roomId']; + $roomSelection->id = $roomData['roomId']; $roomSelection->quantity = $roomData['quantity']; $bookingDto->roomSelections[] = $roomSelection; } diff --git a/tests/Form/Service/ParticipantAssignedRoomFieldHandlerTest.php b/tests/Form/Service/ParticipantAssignedRoomFieldHandlerTest.php index edf4da0..907cc45 100644 --- a/tests/Form/Service/ParticipantAssignedRoomFieldHandlerTest.php +++ b/tests/Form/Service/ParticipantAssignedRoomFieldHandlerTest.php @@ -331,8 +331,8 @@ class ParticipantAssignedRoomFieldHandlerTest extends TestCase private function createRoomSelection(int $roomId, string $label, int $capacity, int $quantity): RoomSelectionDto { $selection = new RoomSelectionDto(); - $selection->roomId = $roomId; - $selection->roomLabel = $label; + $selection->id = $roomId; + $selection->label = $label; $selection->capacity = $capacity; $selection->quantity = $quantity; diff --git a/tests/Service/BookingPriceCalculatorServiceTest.php b/tests/Service/BookingPriceCalculatorServiceTest.php index 313eb22..f8c9156 100644 --- a/tests/Service/BookingPriceCalculatorServiceTest.php +++ b/tests/Service/BookingPriceCalculatorServiceTest.php @@ -62,7 +62,7 @@ class BookingPriceCalculatorServiceTest extends TestCase $travel->rooms = [$room]; $roomSelection = new RoomSelectionDto(); - $roomSelection->roomId = 1; + $roomSelection->id = 1; $roomSelection->quantity = 2; // 2 rooms selected $bookingDto = new BookingDto($travel, 1); @@ -94,7 +94,7 @@ class BookingPriceCalculatorServiceTest extends TestCase $travel->rooms = [$room]; $roomSelection = new RoomSelectionDto(); - $roomSelection->roomId = 2; + $roomSelection->id = 2; $roomSelection->quantity = 1; // 1 room selected $bookingDto = new BookingDto($travel, 1); @@ -132,11 +132,11 @@ class BookingPriceCalculatorServiceTest extends TestCase $travel->rooms = [$singleRoom, $doubleRoom]; $singleRoomSelection = new RoomSelectionDto(); - $singleRoomSelection->roomId = 1; + $singleRoomSelection->id = 1; $singleRoomSelection->quantity = 1; // 1 single room $doubleRoomSelection = new RoomSelectionDto(); - $doubleRoomSelection->roomId = 2; + $doubleRoomSelection->id = 2; $doubleRoomSelection->quantity = 2; // 2 double rooms $bookingDto = new BookingDto($travel, 1); @@ -179,7 +179,7 @@ class BookingPriceCalculatorServiceTest extends TestCase $travel->rooms = [$room]; $roomSelection = new RoomSelectionDto(); - $roomSelection->roomId = 1; + $roomSelection->id = 1; $roomSelection->quantity = 1; $bookingDto = new BookingDto($travel, 1); @@ -203,7 +203,7 @@ class BookingPriceCalculatorServiceTest extends TestCase $travel->rooms = [$room]; $roomSelection = new RoomSelectionDto(); - $roomSelection->roomId = 1; + $roomSelection->id = 1; $roomSelection->quantity = 0; // Zero quantity - not selected $bookingDto = new BookingDto($travel, 1); diff --git a/tests/Service/RoomAssignmentServiceTest.php b/tests/Service/RoomAssignmentServiceTest.php index 6e9c195..cb5b0e4 100644 --- a/tests/Service/RoomAssignmentServiceTest.php +++ b/tests/Service/RoomAssignmentServiceTest.php @@ -200,7 +200,7 @@ class RoomAssignmentServiceTest extends TestCase foreach ($selections as $selection) { $roomSelectionDto = new RoomSelectionDto(); - $roomSelectionDto->roomId = $selection['roomId']; + $roomSelectionDto->id = $selection['roomId']; $roomSelectionDto->quantity = $selection['quantity']; if (isset($selection['capacity'])) { $roomSelectionDto->capacity = $selection['capacity']; diff --git a/tests/Validator/Constraints/RoomSelectionValidatorTest.php b/tests/Validator/Constraints/RoomSelectionValidatorTest.php index 619d236..0eb2bac 100644 --- a/tests/Validator/Constraints/RoomSelectionValidatorTest.php +++ b/tests/Validator/Constraints/RoomSelectionValidatorTest.php @@ -146,7 +146,7 @@ class RoomSelectionValidatorTest extends ConstraintValidatorTestCase $travel->rooms[] = $room; $roomSelection = new RoomSelectionDto(); - $roomSelection->roomId = $data['id']; + $roomSelection->id = $data['id']; $roomSelection->quantity = $data['quantity']; $roomSelections[] = $roomSelection; }