From 37cda348b03eeeed03f5b336e064e7ad7bddcc1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Thu, 23 Oct 2025 10:49:14 +0200 Subject: [PATCH] feat: remove room selection field entirely for single room type addresses #869axcukn --- docs/PROJECT_OVERVIEW.md | 17 ++++++++++--- src/Form/Model/BookingDto.php | 24 +++++++++++++++++++ src/Form/Service/CreateFieldStateProvider.php | 4 ++-- templates/booking/_participant_form.html.twig | 24 ++++++++++++------- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/docs/PROJECT_OVERVIEW.md b/docs/PROJECT_OVERVIEW.md index 272d745..1eb9f97 100644 --- a/docs/PROJECT_OVERVIEW.md +++ b/docs/PROJECT_OVERVIEW.md @@ -85,7 +85,8 @@ The room assignment system allows participants to freely select any room from St **Key Features:** - **Flexible Selection**: All rooms from Step 1 always appear in participant dropdown, regardless of current capacity -- **Auto-Assignment**: When exactly ONE room type selected, all participants auto-assigned and dropdown disabled +- **Auto-Assignment**: When exactly ONE room type selected, all participants auto-assigned via `RoomAssignmentService` +- **Field Display**: Single room type hides dropdown field entirely, displays room label as read-only text - **Conflict Resolution**: When participant selects room at capacity, system automatically unassigns minimum participants needed - **Unassignment Priority**: Highest index participants unassigned first (keeps applicant and early participants stable) - **User Notifications**: Unassigned participants receive warning notifications via toast system @@ -96,8 +97,11 @@ The room assignment system allows participants to freely select any room from St - `detectRoomCapacityConflict()`: Calculates if assignment would exceed capacity - `resolveRoomCapacityConflict()`: Unassigns minimum participants (highest index first) - Generates notifications for unassigned participants only (not for user-initiated assignment) -- `RoomAssignmentService`: Unchanged - still auto-assigns when single room type selected -- `ParticipantFieldOptionsProvider`: Unchanged - disables dropdown when single room type (auto-assigned) + - Handles missing field gracefully when single room type (field excluded from form) +- `RoomAssignmentService`: Auto-assigns when single room type selected at DTO level +- `CreateFieldStateProvider`: Hides `assignedRoomId` field when `SingleRoomTypeCondition` is true +- `BookingDto::getSingleRoomLabel()`: Returns room label for template display when single room type +- Template: Conditionally renders dropdown (multiple rooms) or read-only label (single room) **Example Scenario:** - User selects 1x "Doppelzimmer" (capacity 2), 3 participants @@ -248,6 +252,13 @@ Critical for correct pricing and auto-reassignment: - `BulkInsuranceBookingCondition` hides dependent participant insurance fields - Uses `InsuranceService::batchAssignInsuranceToParticipants()` for price tier matching +### Room Assignment Field +- **Single room type**: Field completely hidden, room label displayed as read-only text +- **Multiple room types**: Dropdown shown with all selected rooms from Step 1 +- `SingleRoomTypeCondition` controls field visibility in `CreateFieldStateProvider` +- Auto-assignment by `RoomAssignmentService` happens at DTO level, independent of form field +- Field handler gracefully handles missing field when excluded from form + ## Data Flow ### Create Flow diff --git a/src/Form/Model/BookingDto.php b/src/Form/Model/BookingDto.php index 6c32365..857f150 100644 --- a/src/Form/Model/BookingDto.php +++ b/src/Form/Model/BookingDto.php @@ -151,6 +151,30 @@ class BookingDto return null !== $this->booking && 'O' === $this->booking->status; } + /** + * Gets the label of the single selected room type. + * + * Returns the room label when exactly one room type is selected (auto-assignment scenario). + * Used by templates to display the room assignment when the dropdown is hidden. + * + * @return string|null The room label or null if not a single room type scenario + */ + public function getSingleRoomLabel(): ?string + { + $selectedRooms = $this->getSelectedRooms(); + + // Only return label when exactly one room type selected + if (1 !== count($selectedRooms)) { + return null; + } + + $roomSelection = reset($selectedRooms); + $availableRooms = $this->travel->getAvailableRooms(); + $room = $availableRooms[$roomSelection->roomId] ?? null; + + return $room?->label; + } + #[Assert\Callback(callback: 'validateRoomSelection', groups: ['booking_create_step_1'])] public function validateRoomSelection(ExecutionContextInterface $context): void { diff --git a/src/Form/Service/CreateFieldStateProvider.php b/src/Form/Service/CreateFieldStateProvider.php index a9d8b4e..07348cc 100644 --- a/src/Form/Service/CreateFieldStateProvider.php +++ b/src/Form/Service/CreateFieldStateProvider.php @@ -205,9 +205,9 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider 'required' => new ApplicantCondition(), ]; - // Make assignedRoomId readonly when only one room type is selected + // Hide assignedRoomId field when only one room type is selected (auto-assigned by RoomAssignmentService) $this->fieldStateConditions['assignedRoomId'] = [ - 'readonly' => new SingleRoomTypeCondition(), + 'hidden' => new SingleRoomTypeCondition(), ]; // Example field state conditions would be registered here diff --git a/templates/booking/_participant_form.html.twig b/templates/booking/_participant_form.html.twig index 39d5e76..92d53f6 100644 --- a/templates/booking/_participant_form.html.twig +++ b/templates/booking/_participant_form.html.twig @@ -85,14 +85,22 @@ {# Room assignment #}
- {{ form_row(form.assignedRoomId, { - 'attr': { - 'hx-trigger': 'change', - 'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})), - 'hx-target': '#main-content', - 'hx-swap': 'innerHTML' - } - }) }} + {% if form.assignedRoomId is defined %} + {{ form_row(form.assignedRoomId, { + 'attr': { + 'hx-trigger': 'change', + 'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})), + 'hx-target': '#main-content', + 'hx-swap': 'innerHTML' + } + }) }} + {% else %} + {# Display room label directly when only one room type selected (auto-assigned) #} +
+ +
{{ bookingDto.singleRoomLabel }}
+
+ {% endif %} {% if form.remarksRoom is defined %} {{ form_row(form.remarksRoom) }} {% endif %}