Files
myep/src/Service/ServiceAvailabilityCalculator.php
T

250 lines
9.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Service;
use App\BusProNet\Utility\DirectionMapper;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
/**
* Calculates dynamic service availability based on current booking selections.
*
* This service tracks how many participants have selected each service within
* the current booking session and calculates remaining availability for
* display to other participants. This prevents overbooking within a single
* booking workflow.
*/
class ServiceAvailabilityCalculator
{
/**
* Calculate remaining availability for all services based on current participant selections.
*
* @param BookingDto $bookingDto The booking data with participant selections
* @param int $currentParticipantIndex The index of the participant currently filling the form
*
* @return array<int, int> 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<int, Service> $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<int, Service> 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<int, int> 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<int, int> 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<Service> 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;
}
}