Array mapping service IDs to remaining availability counts */ public function calculateRemainingAvailability(BookingDto $bookingDto, int $currentParticipantIndex): array { $serviceUsage = $this->calculateServiceUsage($bookingDto, $currentParticipantIndex); $remainingAvailability = []; // Get all services from the travel data $allServices = $this->getAllServicesFromTravel($bookingDto); foreach ($allServices as $service) { $originalAvailability = $service->available ?? null; $usedCount = $serviceUsage[$service->id] ?? 0; // Only track services that have availability limits set if (null !== $originalAvailability && $originalAvailability > 0) { $remaining = max(0, $originalAvailability - $usedCount); $remainingAvailability[$service->id] = $remaining; } } return $remainingAvailability; } /** * Filter services array to only include those with remaining availability. * * @param array $services Array of Service objects to filter * @param BookingDto $bookingDto The booking data with participant selections * @param int $participantIndex The index of the participant currently filling the form * * @return array Filtered array containing only available services */ public function filterAvailableServices(array $services, BookingDto $bookingDto, int $participantIndex): array { $remainingAvailability = $this->calculateRemainingAvailability($bookingDto, $participantIndex); return array_filter($services, function (Service $service) use ($remainingAvailability) { // If service has no availability limit set (null), treat as unlimited if (null === $service->available) { return true; } // If availability is 0, service is sold out at the API level if (0 === $service->available) { return false; } // For services with positive availability, check remaining availability return ($remainingAvailability[$service->id] ?? $service->available) > 0; }); } /** * Check if a specific service is unavailable (sold out) for the current participant. * * @param int $serviceId The ID of the service to check * @param BookingDto $bookingDto The booking data with participant selections * @param int $participantIndex The index of the participant currently filling the form * * @return bool True if the service is unavailable (has availability limit and remaining is 0) */ public function isServiceUnavailable(int $serviceId, BookingDto $bookingDto, int $participantIndex): bool { $allServices = $this->getAllServicesFromTravel($bookingDto); $service = null; foreach ($allServices as $serviceObj) { if ($serviceObj->id === $serviceId) { $service = $serviceObj; break; } } // If service not found, treat as available (not unavailable) if (null === $service) { return false; } // If no availability tracking (null), service is unlimited and available if (null === $service->available) { return false; } // If availability is 0, service is sold out at the API level if (0 === $service->available) { return true; } // For services with positive availability, calculate remaining based on booking selections $remainingAvailability = $this->calculateRemainingAvailability($bookingDto, $participantIndex); return ($remainingAvailability[$serviceId] ?? $service->available) <= 0; } /** * Calculate how many times each service has been selected by other participants. * * @param BookingDto $bookingDto The booking data with participant selections * @param int $currentParticipantIndex The index of the participant currently filling the form * * @return array Array mapping service IDs to usage counts */ private function calculateServiceUsage(BookingDto $bookingDto, int $currentParticipantIndex): array { $serviceUsage = []; foreach ($bookingDto->participants as $index => $participant) { // Skip the current participant to avoid counting their potential selections if ($index === $currentParticipantIndex) { continue; } // Count service selections for this participant $participantUsage = $this->countParticipantServiceUsage($participant); foreach ($participantUsage as $serviceId => $count) { $serviceUsage[$serviceId] = ($serviceUsage[$serviceId] ?? 0) + $count; } } return $serviceUsage; } /** * Count service usage for a single participant and add to the usage array. * * @param ParticipantDto $participant The participant DTO object * * @return array Usage counts keyed by service ID */ private function countParticipantServiceUsage(ParticipantDto $participant): array { $serviceUsage = []; // Board service (single selection) foreach ($participant->board as $service) { if (null !== $service->id) { $serviceUsage[$service->id] = ($serviceUsage[$service->id] ?? 0) + 1; } } // Ski pass service (single selection) if (null !== $participant->skiPass && null !== $participant->skiPass->id) { $serviceUsage[$participant->skiPass->id] = ($serviceUsage[$participant->skiPass->id] ?? 0) + 1; } // Parking service (single selection) if (null !== $participant->parkingService && null !== $participant->parkingService->id) { $serviceUsage[$participant->parkingService->id] = ($serviceUsage[$participant->parkingService->id] ?? 0) + 1; } // Transportation services (single selection each) if (null !== $participant->transportationOutbound && null !== $participant->transportationOutbound->id) { $serviceUsage[$participant->transportationOutbound->id] = ($serviceUsage[$participant->transportationOutbound->id] ?? 0) + 1; } if (null !== $participant->transportationInbound && null !== $participant->transportationInbound->id) { $serviceUsage[$participant->transportationInbound->id] = ($serviceUsage[$participant->transportationInbound->id] ?? 0) + 1; } // Courses (multiple selection) foreach ($participant->courses as $course) { if (null !== $course->id) { $serviceUsage[$course->id] = ($serviceUsage[$course->id] ?? 0) + 1; } } // Additional services (multiple selection) foreach ($participant->additionalServices as $additionalService) { if (null !== $additionalService->id) { $serviceUsage[$additionalService->id] = ($serviceUsage[$additionalService->id] ?? 0) + 1; } } // Rentals (multiple selection) foreach ($participant->rentals as $rental) { if (null !== $rental->id) { $serviceUsage[$rental->id] = ($serviceUsage[$rental->id] ?? 0) + 1; } } return $serviceUsage; } /** * Get all services from the travel data for availability calculation. * * @param BookingDto $bookingDto The booking data containing travel information * * @return array Array of all available services */ private function getAllServicesFromTravel(BookingDto $bookingDto): array { $allServices = []; // Get all transportation services $transportationServices = array_merge( $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::OUTBOUND_TRAVEL), $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::INBOUND_TRAVEL) ); $allServices = array_merge($allServices, $transportationServices); // Get additional services by category using proper constants $additionalServiceTokens = [ Constants::TOKEN_COURSES, Constants::TOKEN_ADDITIONAL, Constants::TOKEN_RENTALS, Constants::TOKEN_SKI_PASS, Constants::TOKEN_BOARD, Constants::TOKEN_PARKING, ]; foreach ($additionalServiceTokens as $token) { $categoryServices = $bookingDto->travel->getAdditionalServicesBySubTypes($token); $allServices = array_merge($allServices, $categoryServices); } return $allServices; } }