feat: remove room selection field entirely for single room type

addresses #869axcukn
This commit is contained in:
Björn Fromme
2025-10-23 10:49:14 +02:00
parent 39d944351c
commit bb630da5ed
4 changed files with 56 additions and 13 deletions
+14 -3
View File
@@ -85,7 +85,8 @@ The room assignment system allows participants to freely select any room from St
**Key Features:** **Key Features:**
- **Flexible Selection**: All rooms from Step 1 always appear in participant dropdown, regardless of current capacity - **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 - **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) - **Unassignment Priority**: Highest index participants unassigned first (keeps applicant and early participants stable)
- **User Notifications**: Unassigned participants receive warning notifications via toast system - **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 - `detectRoomCapacityConflict()`: Calculates if assignment would exceed capacity
- `resolveRoomCapacityConflict()`: Unassigns minimum participants (highest index first) - `resolveRoomCapacityConflict()`: Unassigns minimum participants (highest index first)
- Generates notifications for unassigned participants only (not for user-initiated assignment) - Generates notifications for unassigned participants only (not for user-initiated assignment)
- `RoomAssignmentService`: Unchanged - still auto-assigns when single room type selected - Handles missing field gracefully when single room type (field excluded from form)
- `ParticipantFieldOptionsProvider`: Unchanged - disables dropdown when single room type (auto-assigned) - `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:** **Example Scenario:**
- User selects 1x "Doppelzimmer" (capacity 2), 3 participants - 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 - `BulkInsuranceBookingCondition` hides dependent participant insurance fields
- Uses `InsuranceService::batchAssignInsuranceToParticipants()` for price tier matching - 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 ## Data Flow
### Create Flow ### Create Flow
+24
View File
@@ -151,6 +151,30 @@ class BookingDto
return null !== $this->booking && 'O' === $this->booking->status; 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'])] #[Assert\Callback(callback: 'validateRoomSelection', groups: ['booking_create_step_1'])]
public function validateRoomSelection(ExecutionContextInterface $context): void public function validateRoomSelection(ExecutionContextInterface $context): void
{ {
@@ -205,9 +205,9 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
'required' => new ApplicantCondition(), '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'] = [ $this->fieldStateConditions['assignedRoomId'] = [
'readonly' => new SingleRoomTypeCondition(), 'hidden' => new SingleRoomTypeCondition(),
]; ];
// Example field state conditions would be registered here // Example field state conditions would be registered here
+16 -8
View File
@@ -85,14 +85,22 @@
{# Room assignment #} {# Room assignment #}
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
{{ form_row(form.assignedRoomId, { {% if form.assignedRoomId is defined %}
'attr': { {{ form_row(form.assignedRoomId, {
'hx-trigger': 'change', 'attr': {
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})), 'hx-trigger': 'change',
'hx-target': '#main-content', 'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-swap': 'innerHTML' 'hx-target': '#main-content',
} 'hx-swap': 'innerHTML'
}) }} }
}) }}
{% else %}
{# Display room label directly when only one room type selected (auto-assigned) #}
<div>
<label class="font-semibold mb-1 block">Zimmer</label>
<div class="text-sm text-gray-600">{{ bookingDto.singleRoomLabel }}</div>
</div>
{% endif %}
{% if form.remarksRoom is defined %} {% if form.remarksRoom is defined %}
{{ form_row(form.remarksRoom) }} {{ form_row(form.remarksRoom) }}
{% endif %} {% endif %}