additionalServices, ...$bookingData->transportationServices, ...$bookingData->pickups, ...$bookingData->dropOffs, ...$bookingData->rooms, ...$bookingData->insurances, ]; foreach ($servicesToReset as $service) { $service->mapping = []; } } /** * Processes all services for a single participant. * * This orchestrator method handles the complete service assignment workflow for one participant, * including additional services, transportation services, pickup locations, and room assignments. * * @param ParticipantDto $participant The participant data from the form * @param Booking $bookingData The booking data object to update * @param Travel $travelData The travel data containing available services */ public function processParticipantServices(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void { if (true === $participant->isCanceled()) { return; } $this->processAdditionalServices($participant, $bookingData, $travelData); $this->processTransportationServices($participant, $bookingData, $travelData); $this->processPickupLocations($participant, $bookingData); $this->processDropOffLocations($participant, $bookingData); $this->processRoomAssignment($participant, $bookingData); $this->processInsurance($participant, $bookingData, $travelData); } /** * Removes services and pickups with no participant mappings. * * Cleans up unused services to prevent empty services from being sent to the API. * This includes additional services, transportation services, pickup locations, and insurances. * * @param Booking $bookingData The booking data object to clean up */ public function removeUnusedServices(Booking $bookingData): void { foreach ($bookingData->additionalServices as $service) { if (0 === count($service->mapping)) { unset($bookingData->additionalServices[$service->id]); } } foreach ($bookingData->transportationServices as $service) { if (0 === count($service->mapping)) { unset($bookingData->transportationServices[$service->id]); } } foreach ($bookingData->pickups as $pickup) { if (0 === count($pickup->mapping)) { unset($bookingData->pickups[$pickup->id]); } } foreach ($bookingData->dropOffs as $dropOff) { if (0 === count($dropOff->mapping)) { unset($bookingData->dropOffs[$dropOff->id]); } } foreach ($bookingData->insurances as $insurance) { if (0 === count($insurance->mapping)) { unset($bookingData->insurances[$insurance->id]); } } } /** * Collects additional services from a participant for mapping. * * Extracts all additional services (courses, board, rentals, ski pass, rental insurance, * parking, additional services) from a participant into a flat array of Service objects. * * @param ParticipantDto $participant The participant data * * @return array Array of services selected by this participant */ private function collectParticipantAdditionalServices(ParticipantDto $participant): array { $services = [ ...$participant->courses, ...$participant->additionalServices, ...$participant->board, ...$participant->rentals, ]; // Veg is encoded as an additional service in the edit/update payload. if (null !== $participant->veg) { $services[] = $participant->veg; } // Add ski pass if selected (single service, not an array) if (null !== $participant->skiPass) { $services[] = $participant->skiPass; } // Add rental insurance if selected (single service, not an array) if (null !== $participant->rentalInsurance) { $services[] = $participant->rentalInsurance; } // Add parking service if selected (for self-organized PKW transportation) if (true === $participant->parking && null !== $participant->parkingService) { $services[] = $participant->parkingService; } return $services; } /** * Processes additional services for a participant. * * Maps additional services (courses, ski passes, board options, rentals) to the participant. * Adds new services to the booking if they don't already exist and sets individual pricing. * * @param ParticipantDto $participant The participant data from the form * @param Booking $bookingData The booking data object to update * @param Travel $travelData The travel data containing available services */ private function processAdditionalServices(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void { $servicesToMap = $this->collectParticipantAdditionalServices($participant); foreach ($servicesToMap as $service) { if (false === isset($bookingData->additionalServices[$service->id])) { $serviceToAdd = $travelData->additionalServices[$service->id] ?? null; if (null === $serviceToAdd) { $this->logger->warning('Additional service not found in travel data, skipping mapping', [ 'bookingId' => $bookingData->id, 'serviceId' => $service->id, 'participantIndex' => $participant->index, ]); continue; } $bookingData->additionalServices[$service->id] = $serviceToAdd; $bookingData->additionalServices[$service->id]->individualPrice[$participant->index] = $serviceToAdd->price; } $bookingData->additionalServices[$service->id]->mapping[] = $participant->index; } } /** * Processes transportation services for a participant. * * Maps transportation services (both directions: to and from destination) to the participant. * Adds new transportation services to the booking if they don't already exist. * * @param ParticipantDto $participant The participant data from the form * @param Booking $bookingData The booking data object to update * @param Travel $travelData The travel data containing available services */ private function processTransportationServices(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void { foreach ([$participant->transportationOutbound, $participant->transportationInbound] as $service) { if (null === $service) { continue; } if (false === isset($bookingData->transportationServices[$service->id])) { $serviceToAdd = $travelData->transportationServices[$service->id] ?? null; if (null === $serviceToAdd) { $this->logger->warning('Transportation service not found in travel data, skipping mapping', [ 'bookingId' => $bookingData->id, 'serviceId' => $service->id, 'participantIndex' => $participant->index, ]); continue; } $bookingData->transportationServices[$service->id] = $serviceToAdd; $bookingData->transportationServices[$service->id]->individualPrice[$participant->index] = $serviceToAdd->price; } $bookingData->transportationServices[$service->id]->mapping[] = $participant->index; } } /** * Processes pickup locations for participants using bus transportation. * * Only processes pickup locations for bus transportation services and maps the participant * to their selected pickup location. * * @param ParticipantDto $participant The participant data from the form * @param Booking $bookingData The booking data object to update */ private function processPickupLocations(ParticipantDto $participant, Booking $bookingData): void { // Check if either transportation direction is BUS and pickup is selected $hasOutboundBus = null !== $participant->transportationOutbound && 'BUS' === $participant->transportationOutbound->subType; $hasInboundBus = null !== $participant->transportationInbound && 'BUS' === $participant->transportationInbound->subType; if (($hasOutboundBus || $hasInboundBus) && null !== $selectedPickup = $participant->pickup) { if (false === isset($bookingData->pickups[$selectedPickup->id])) { $bookingData->pickups[$selectedPickup->id] = $selectedPickup; } $bookingData->pickups[$selectedPickup->id]->mapping[] = $participant->index; } } /** * Processes drop-off locations for participants using bus transportation. * * Only processes drop-off locations for bus transportation services and maps the participant * to their selected drop-off location. * * @param ParticipantDto $participant The participant data from the form * @param Booking $bookingData The booking data object to update */ private function processDropOffLocations(ParticipantDto $participant, Booking $bookingData): void { $hasOutboundBus = null !== $participant->transportationOutbound && 'BUS' === $participant->transportationOutbound->subType; $hasInboundBus = null !== $participant->transportationInbound && 'BUS' === $participant->transportationInbound->subType; if (($hasOutboundBus || $hasInboundBus) && null !== $selectedDropOff = $participant->dropOff) { if (false === isset($bookingData->dropOffs[$selectedDropOff->id])) { $bookingData->dropOffs[$selectedDropOff->id] = $selectedDropOff; } $bookingData->dropOffs[$selectedDropOff->id]->mapping[] = $participant->index; } } /** * 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 ParticipantDto $participant The participant data from the form * @param Booking $bookingData The booking data object to update */ private function processRoomAssignment(ParticipantDto $participant, Booking $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; } } } /** * Processes travel insurance for a participant. * * Maps insurance to the participant. Adds new insurances to the booking if they don't exist. * Insurance can be either individual or package-based, with automatic price-tier adjustment. * * IMPORTANT: Excludes synthetic "no insurance" option from BPN transmission. * * @param ParticipantDto $participant The participant data from the form * @param Booking $bookingData The booking data object to update * @param Travel $travelData The travel data containing available insurances */ private function processInsurance(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void { // Skip if no insurance or if participant selected "keine Versicherung gewünscht" if (null === $participant->insurance || $participant->insurance->isNoInsurance()) { return; } $insurance = $participant->insurance; if (false === isset($bookingData->insurances[$insurance->id])) { $insuranceToAdd = $travelData->insurances[$insurance->id] ?? null; if (null !== $insuranceToAdd) { $bookingData->insurances[$insurance->id] = $insuranceToAdd; $bookingData->insurances[$insurance->id]->individualPrice[$participant->index] = $insuranceToAdd->price; } } // Only add mapping if insurance exists in booking data // This can legitimately be false if the insurance is no longer available in current travel data if (isset($bookingData->insurances[$insurance->id])) { $bookingData->insurances[$insurance->id]->mapping[] = $participant->index; } } }