Array mapping service IDs to remaining availability counts */ public function calculateRemainingAvailability(BookingCreateDto $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 BookingCreateDto $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, BookingCreateDto $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, treat as unlimited if (null === $service->available || $service->available <= 0) { return true; } // For services with availability limits, 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 BookingCreateDto $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, BookingCreateDto $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 or has no availability limit, it's not unavailable if (null === $service || null === $service->available || $service->available <= 0) { return false; } $remainingAvailability = $this->calculateRemainingAvailability($bookingDto, $participantIndex); return ($remainingAvailability[$serviceId] ?? $service->available) <= 0; } /** * Calculate how many times each service has been selected by other participants. * * @param BookingCreateDto $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(BookingCreateDto $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 $this->countParticipantServiceUsage($participant, $serviceUsage); } return $serviceUsage; } /** * Count service usage for a single participant and add to the usage array. * * @param mixed $participant The participant DTO object * @param array $serviceUsage Reference to the service usage array to update */ private function countParticipantServiceUsage($participant, array &$serviceUsage): void { // Board service (single selection) if (isset($participant->board) && $participant->board instanceof Service) { $serviceUsage[$participant->board->id] = ($serviceUsage[$participant->board->id] ?? 0) + 1; } // Ski pass service (single selection) if (isset($participant->skiPass) && $participant->skiPass instanceof Service) { $serviceUsage[$participant->skiPass->id] = ($serviceUsage[$participant->skiPass->id] ?? 0) + 1; } // Transportation services (single selection each) if (isset($participant->transportationOutbound) && $participant->transportationOutbound instanceof Service) { $serviceUsage[$participant->transportationOutbound->id] = ($serviceUsage[$participant->transportationOutbound->id] ?? 0) + 1; } if (isset($participant->transportationInbound) && $participant->transportationInbound instanceof Service) { $serviceUsage[$participant->transportationInbound->id] = ($serviceUsage[$participant->transportationInbound->id] ?? 0) + 1; } // Courses (multiple selection) if (isset($participant->courses) && is_array($participant->courses)) { foreach ($participant->courses as $course) { if ($course instanceof Service) { $serviceUsage[$course->id] = ($serviceUsage[$course->id] ?? 0) + 1; } } } // Additional services (multiple selection) if (isset($participant->additionalServices) && is_array($participant->additionalServices)) { foreach ($participant->additionalServices as $additionalService) { if ($additionalService instanceof Service) { $serviceUsage[$additionalService->id] = ($serviceUsage[$additionalService->id] ?? 0) + 1; } } } // Rentals (multiple selection) if (isset($participant->rentals) && is_array($participant->rentals)) { foreach ($participant->rentals as $rental) { if ($rental instanceof Service) { $serviceUsage[$rental->id] = ($serviceUsage[$rental->id] ?? 0) + 1; } } } } /** * Get all services from the travel data for availability calculation. * * @param BookingCreateDto $bookingDto The booking data containing travel information * * @return array Array of all available services */ private function getAllServicesFromTravel(BookingCreateDto $bookingDto): array { $allServices = []; // Get 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, ]; foreach ($additionalServiceTokens as $token) { $categoryServices = $bookingDto->travel->getAdditionalServicesBySubTypes($token) ?? []; $allServices = array_merge($allServices, $categoryServices); } return $allServices; } }