diff --git a/src/BusProNet/DataProcessor/BookingDataProcessor.php b/src/BusProNet/DataProcessor/BookingDataProcessor.php index da207cc..76bb7b0 100644 --- a/src/BusProNet/DataProcessor/BookingDataProcessor.php +++ b/src/BusProNet/DataProcessor/BookingDataProcessor.php @@ -162,6 +162,8 @@ class BookingDataProcessor $roomSelection->label = $room->label; $roomSelection->price = $room->price; $roomSelection->quantity = $bookingRoom->totalCount; // Actual quantity from booking XML (anzahl) + $roomSelection->capacity = $room->minPax ?? 1; + $roomSelection->maxQuantity = $bookingRoom->totalCount + ($room->available ?? 0); $dto->roomSelections[] = $roomSelection; } } diff --git a/src/BusProNet/DataProcessor/BookingPayloadBuilder.php b/src/BusProNet/DataProcessor/BookingPayloadBuilder.php index 56991c9..85da2dd 100644 --- a/src/BusProNet/DataProcessor/BookingPayloadBuilder.php +++ b/src/BusProNet/DataProcessor/BookingPayloadBuilder.php @@ -172,7 +172,7 @@ class BookingPayloadBuilder '@idverpflegung' => $room->boardId, '@anreise' => $room->dateFrom ? $room->dateFrom->format('d.m.Y') : null, '@abreise' => $room->dateTo ? $room->dateTo->format('d.m.Y') : null, - '@anzahl' => $room->totalCount, + '@anzahl' => count($uniqueMapping), '@zuordnung' => implode(',', array_map(fn ($index) => $index + 1, $uniqueMapping)), ]; } @@ -456,21 +456,12 @@ class BookingPayloadBuilder $availableRooms = $bookingDto->travel->getAvailableRooms(); $payload['ferienzielunterbringungen']['ferienzielunterbringung'] = []; - // Build room selection quantity lookup - $roomQuantities = []; - foreach ($bookingDto->roomSelections as $selection) { - if ($selection->quantity > 0) { - $roomQuantities[$selection->id] = $selection->quantity; - } - } - foreach ($roomMap as $roomId => $participantIds) { $room = $availableRooms[$roomId] ?? null; if (null === $room) { continue; } - $quantity = $roomQuantities[$roomId] ?? 1; $uniqueParticipantIds = array_unique($participantIds); $payload['ferienzielunterbringungen']['ferienzielunterbringung'][] = [ @@ -479,7 +470,7 @@ class BookingPayloadBuilder '@idverpflegung' => $room->boardId, '@anreise' => $bookingDto->travel->dateFrom->format('d.m.Y'), '@abreise' => $bookingDto->travel->dateTo->format('d.m.Y'), - '@anzahl' => $quantity, + '@anzahl' => count($uniqueParticipantIds), '@zuordnung' => implode(',', $uniqueParticipantIds), ]; } diff --git a/src/Form/Model/ParticipantEditDto.php b/src/Form/Model/ParticipantEditDto.php index 96bcc32..30b6afc 100644 --- a/src/Form/Model/ParticipantEditDto.php +++ b/src/Form/Model/ParticipantEditDto.php @@ -132,6 +132,11 @@ class ParticipantEditDto #[Assert\Callback(groups: ['booking_create', 'booking_edit'])] public function validateEmailUniqueness(ExecutionContextInterface $context): void { + // Skip for internal agency bookings — staff use shared placeholder emails + if ($this->bookingContext->isInternalAgencyBooking()) { + return; + } + // Skip validation for applicant (index 0) - applicant's email can be shared with dependents if (true === $this->participant->isApplicant()) { return; diff --git a/src/Form/Service/Condition/AccommodationMutabilityCondition.php b/src/Form/Service/Condition/AccommodationMutabilityCondition.php new file mode 100644 index 0000000..e5bb0fc --- /dev/null +++ b/src/Form/Service/Condition/AccommodationMutabilityCondition.php @@ -0,0 +1,29 @@ +travel->roomsMutable; + } + + public function getDependentFields(): array + { + return []; + } + + public function getDescription(): string + { + return 'Accommodation is not mutable (edit flow)'; + } +} diff --git a/src/Form/Service/EditFieldStateProvider.php b/src/Form/Service/EditFieldStateProvider.php index 6a94907..a2502a0 100644 --- a/src/Form/Service/EditFieldStateProvider.php +++ b/src/Form/Service/EditFieldStateProvider.php @@ -5,11 +5,10 @@ declare(strict_types=1); namespace App\Form\Service; use App\BusProNet\Utility\DirectionMapper; -use App\Form\Model\BookingDto; use App\Form\Service\Abstract\AbstractFieldStateProvider; +use App\Form\Service\Condition\AccommodationMutabilityCondition; use App\Form\Service\Condition\AdditionalServicesMutabilityCondition; use App\Form\Service\Condition\AgeRangeCondition; -use App\Form\Service\Condition\BookingModeCondition; use App\Form\Service\Condition\CompositeCondition; use App\Form\Service\Condition\DateOfBirthProvidedCondition; use App\Form\Service\Condition\FieldValueCondition; @@ -99,10 +98,10 @@ class EditFieldStateProvider extends AbstractFieldStateProvider 'static_text' => $personalDataHiddenCondition, ]; - // Room assignments are fixed in edit mode - always render as static text - // The assignedRoomId value passes through unchanged from the loaded booking data + // Room assignments are readonly when BPN indicates accommodation is not mutable + $accommodationMutabilityCondition = new AccommodationMutabilityCondition(); $this->fieldStateConditions['assignedRoomId'] = [ - 'static_text' => new BookingModeCondition(BookingDto::MODE_EDIT), + 'static_text' => $accommodationMutabilityCondition, ]; // Show remarks room field only when room with code 'mbz' (single bed) is selected diff --git a/src/Form/Service/ParticipantAssignedRoomFieldHandler.php b/src/Form/Service/ParticipantAssignedRoomFieldHandler.php index 262b6be..6698531 100644 --- a/src/Form/Service/ParticipantAssignedRoomFieldHandler.php +++ b/src/Form/Service/ParticipantAssignedRoomFieldHandler.php @@ -122,7 +122,14 @@ class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandle } // Calculate total capacity for this room type - $totalCapacity = $roomSelection->quantity * $roomSelection->capacity; + // In edit mode, use maxQuantity which includes available rooms beyond the booked count. + // In create mode, use quantity which is the user's room selection from step 1. + if (BookingDto::MODE_EDIT === $bookingDto->getMode()) { + $effectiveQuantity = $roomSelection->maxQuantity; + } else { + $effectiveQuantity = $roomSelection->quantity ?? 0; + } + $totalCapacity = $effectiveQuantity * $roomSelection->capacity; // Count current occupancy (excluding current participant) $occupancy = 0; @@ -174,7 +181,12 @@ class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandle return; // Shouldn't happen } - $totalCapacity = $roomSelection->quantity * $roomSelection->capacity; + if (BookingDto::MODE_EDIT === $bookingDto->getMode()) { + $effectiveQuantity = $roomSelection->maxQuantity; + } else { + $effectiveQuantity = $roomSelection->quantity ?? 0; + } + $totalCapacity = $effectiveQuantity * $roomSelection->capacity; $currentOccupancy = count($participantsWithRoom); $spacesNeeded = ($currentOccupancy + 1) - $totalCapacity; diff --git a/src/Service/ParticipantCardDataService.php b/src/Service/ParticipantCardDataService.php index fc7edf6..904355c 100644 --- a/src/Service/ParticipantCardDataService.php +++ b/src/Service/ParticipantCardDataService.php @@ -237,8 +237,8 @@ class ParticipantCardDataService } else { $groups[] = 'booking_edit'; - // In edit mode, add strict_required only if applicant is immutable - if (false === ($bookingDto->participants[0]?->mutable ?? true)) { + // Strict validation in edit mode except for internal agency bookings + if (false === $bookingDto->isInternalAgencyBooking()) { $groups[] = 'strict_required'; } } diff --git a/src/Service/RoomPricingCalculator.php b/src/Service/RoomPricingCalculator.php index 81cc799..4f2ea2c 100644 --- a/src/Service/RoomPricingCalculator.php +++ b/src/Service/RoomPricingCalculator.php @@ -135,6 +135,10 @@ class RoomPricingCalculator } } + // Count current participant assignments from the DTO (reflects live state + // including new assignments not yet in the booking entity) + $assignmentCounts = $bookingDto->getRoomAssignmentCounts(); + // Build pricing array foreach ($roomGroups as $roomId => $data) { $room = $data['room']; @@ -144,10 +148,14 @@ class RoomPricingCalculator // Calculate average unit price (price per person) $unitPrice = $participantCount > 0 ? $totalPrice / $participantCount : 0.0; + // Use live assignment count for quantity display so the sidebar reflects + // new room assignments that haven't been persisted to the booking entity yet + $quantity = $assignmentCounts[$roomId] ?? $room->totalCount; + $roomPricing[] = [ 'roomId' => $room->id, 'label' => $room->label, - 'quantity' => $room->totalCount, + 'quantity' => $quantity, 'participantCount' => $participantCount, 'unitPrice' => $unitPrice, 'totalPrice' => $totalPrice, diff --git a/templates/booking/_summary.html.twig b/templates/booking/_summary.html.twig index 3347b0c..54ecadd 100644 --- a/templates/booking/_summary.html.twig +++ b/templates/booking/_summary.html.twig @@ -104,6 +104,18 @@ {% endif %} +