Files
myep/docs/SERVICE_AVAILABILITY_SYSTEM.md
T

12 KiB

Dynamic Service Availability System

Overview

The Dynamic Service Availability System prevents overbooking within a single booking session by tracking service selections across all participants and dynamically adjusting availability in real-time. This ensures that services with limited capacity cannot be overbooked during the form creation process.

Problem Statement

Business Challenge

  • Services have limited availability (e.g., "Advanced Ski Course: 5 available")
  • Multiple participants in a booking can select the same services
  • Without dynamic tracking, services could be overbooked within a single booking session
  • Users need immediate feedback when services become unavailable

Technical Requirements

  • Track service selections across all participants in current booking session
  • Recalculate remaining availability during HTMX form refresh cycles
  • Hide services that have reached capacity limits
  • Maintain session-scoped availability (not permanent database changes)
  • Support all service types with consistent behavior

Architecture Overview

Core Components

1. ServiceAvailabilityCalculator (src/Service/ServiceAvailabilityCalculator.php)

Purpose: Central service for calculating dynamic availability based on current selections

Key Methods:

public function calculateRemainingAvailability(BookingCreateDto $bookingDto, int $currentParticipantIndex): array
public function filterAvailableServices(array $services, BookingCreateDto $bookingDto, int $participantIndex): array
public function isServiceAvailable(int $serviceId, BookingCreateDto $bookingDto, int $participantIndex): bool

Responsibilities:

  • Calculate service usage across all participants (excluding current participant)
  • Determine remaining availability per service
  • Filter service arrays to only include available services
  • Handle all service types consistently

2. Enhanced ParticipantFieldOptionsProvider

Integration Point: Field option generation with availability filtering

Enhanced Methods:

private function filterServicesByAvailability(array $services, BookingDtoInterface $bookingDto, int $participantIndex): array

Updated Field Providers:

  • Courses (TOKEN_COURSES)
  • Additional Services (TOKEN_ADDITIONAL)
  • Board/Meal Plans (TOKEN_BOARD)
  • Rentals (TOKEN_RENTALS)
  • Ski Passes (TOKEN_SKI_PASS)
  • Transportation Services (Outbound/Inbound)

Implementation Details

Service Usage Calculation

The system tracks how many participants have selected each service:

private function calculateServiceUsage(BookingCreateDto $bookingDto, int $currentParticipantIndex): array
{
    $serviceUsage = [];
    
    foreach ($bookingDto->participants as $index => $participant) {
        // Skip current participant to avoid counting their potential selections
        if ($index === $currentParticipantIndex) {
            continue;
        }
        
        // Count all service types for this participant
        $this->countParticipantServiceUsage($participant, $serviceUsage);
    }
    
    return $serviceUsage;
}

Availability Calculation Logic

For each service, remaining availability is calculated as:

If service.available is null or <= 0:
    Service is unlimited (always available)
Else:
    Remaining = max(0, original_availability - usage_count)

Service Type Handling

The system handles all major service types:

Single Selection Services:

  • Board/Meal Plans
  • Ski Passes
  • Transportation (Outbound/Inbound)

Multiple Selection Services:

  • Courses
  • Additional Services
  • Rentals

Integration with Form System

Field Option Filtering Chain

'choices' => $this->filterServicesByAvailability(
    $this->filterServicesByAgeConstraints(
        $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_COURSES),
        $bookingDto,
        $participantIndex
    ),
    $bookingDto,
    $participantIndex
),

HTMX Integration

  • Availability recalculated during each form refresh cycle
  • Services dynamically hidden when capacity reached
  • Real-time feedback without full page refresh
  • Maintains consistency across all participants

Business Rules

Availability Behavior

  • Available Services: Shown normally with pricing
  • Unavailable Services: Hidden completely from selection
  • High Availability: Services with very high limits effectively always available
  • Per-Participant Limit: Each participant can select a service maximum once

Service Capacity Management

  • Original Availability: Parsed from XML data at booking initialization
  • Dynamic Availability: Calculated in real-time based on current selections
  • Session Scope: Availability tracking only within current booking session
  • No Persistence: Changes not saved to database or XML files
  • Unlimited Services: Services with null or ≤0 availability are treated as unlimited
  • Company Strategy: High availability values used for services that should always be bookable

Edge Cases Handled

  • Null or missing availability values (treated as unlimited availability)
  • Zero or negative availability values (treated as unlimited availability)
  • Services already selected by current participant (not counted against them)
  • Invalid or missing participant data (gracefully ignored)
  • Empty service arrays (handled without errors)
  • Services without availability limits (always remain available)

Usage Examples

Scenario 1: Course Selection with Limited Capacity

Initial State:
- Advanced Ski Course: 3 available

Participant 1: Selects Advanced Ski Course → 2 remaining
Participant 2: Sees Advanced Ski Course available → Selects it → 1 remaining  
Participant 3: Sees Advanced Ski Course available → Selects it → 0 remaining
Participant 4: Advanced Ski Course hidden (not available)

Note: Most services will have unlimited availability (null or high values) and remain visible.

Scenario 2: Multiple Service Types

Services with Limits:
- Rental Helmet: 10 available
- Advanced Course: 2 available
- Premium Board: 5 available

As participants select services:
- Each selection reduces availability for remaining participants
- Services become hidden when capacity reached
- Participants see only services they can still book

Performance Considerations

Optimization Strategies

  • Lightweight Calculations: Simple arithmetic operations only
  • No Database Queries: All data from memory (DTO objects)
  • Cached Service Lists: Service collections retrieved once per request
  • Efficient Filtering: Array operations with minimal overhead

Scalability

  • Memory Usage: Minimal additional memory footprint
  • Processing Time: Linear time complexity O(n) where n = participant count
  • HTMX Performance: No impact on response times
  • Large Bookings: Efficient even with many participants
  • Unlimited Services: Zero-cost filtering for services without availability limits

Configuration

Service Registration

The ServiceAvailabilityCalculator is automatically registered via Symfony's autowiring:

# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\:
        resource: '../src/'
        exclude:
            - '../src/Entity/'
            - '../src/Kernel.php'

Field Provider Integration

public function __construct(
    private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
    private readonly ServiceAvailabilityCalculator $serviceAvailabilityCalculator,
) {
    parent::__construct();
}

Testing Strategy

Unit Testing Scenarios

  1. Service Usage Calculation: Test counting across multiple participants
  2. Availability Filtering: Verify services hidden when capacity reached
  3. Edge Cases: Handle null values, empty arrays, invalid data
  4. Service Types: Test all service categories (courses, rentals, etc.)
  5. Current Participant Exclusion: Ensure current participant selections not counted

Integration Testing

  1. Form Field Generation: Verify filtered choices in field options
  2. HTMX Refresh Cycles: Test availability updates during form interactions
  3. Multiple Participants: Test complex scenarios with many participants
  4. Service Combinations: Test mixed service types and availability levels

Manual Testing Scenarios

Test Case 1: Basic Availability Reduction
- Create booking with 2 participants
- Select service with availability = 2 for participant 1
- Verify participant 2 sees availability reduced
- Select same service for participant 2  
- Verify service hidden for additional participants

Test Case 2: Mixed Service Types
- Test courses, rentals, and transportation together
- Verify each service type respects availability limits
- Confirm services with high limits remain available

Test Case 3: HTMX Integration  
- Make service selections via HTMX form refresh
- Verify real-time availability updates
- Test form submission and navigation between steps

Error Handling

Graceful Degradation

  • Missing Availability Data: Treats as unavailable (hidden)
  • Invalid Service Objects: Safely ignored in calculations
  • Corrupted Participant Data: Skips invalid participants
  • Service Lookup Failures: Continues processing other services

Logging and Monitoring

  • No explicit logging (availability is business logic, not error condition)
  • Integrates with existing form processing error handling
  • Symfony debug toolbar shows service container usage

Future Enhancements

Potential Improvements

  1. Availability Display: Show remaining count in service labels (optional)
  2. Reservation System: Temporary hold on services during selection
  3. Priority Booking: VIP participants get access to limited services first
  4. Cross-Session Tracking: Track availability across multiple booking sessions
  5. Analytics: Collect data on service demand and capacity utilization

Performance Optimizations

  1. Caching Layer: Cache availability calculations for identical participant sets
  2. Lazy Loading: Only calculate availability for visible services
  3. Background Updates: Pre-calculate availability for common scenarios
  4. Delta Updates: Only recalculate changed services during HTMX updates

Troubleshooting

Common Issues

Services Always Hidden:

  • Check if services have availability limits set (should be null/0 for unlimited)
  • Verify service ID matching between travel data and participant selections
  • Confirm participant data structure is correct
  • Most services should be unlimited and always visible

Availability Not Updating:

  • Verify HTMX integration is working
  • Check that form refresh includes all participant data
  • Ensure ServiceAvailabilityCalculator is being called

Performance Problems:

  • Review participant count and service selection complexity
  • Check for inefficient service lookups or data processing
  • Monitor memory usage with large booking sessions

Debugging Tools

// Debug availability calculation
$calculator = $container->get(ServiceAvailabilityCalculator::class);
$availability = $calculator->calculateRemainingAvailability($bookingDto, $participantIndex);
dump($availability);

// Debug service filtering  
$filtered = $calculator->filterAvailableServices($services, $bookingDto, $participantIndex);
dump($filtered);

Integration Points

Dependencies

  • App\BusProNet\Model\Service - Service data objects
  • App\Form\Model\BookingCreateDto - Booking and participant data
  • App\BusProNet\Constants - Service type constants
  • App\BusProNet\Utility\DirectionMapper - Transportation direction mapping
  • Field State System: Availability filtering integrates with conditional field states
  • Pricing System: Available services included in pricing calculations
  • HTMX Updates: Availability changes trigger form refreshes
  • Form Handlers: Service selections processed by specialized field handlers

Implementation Status: Completed and Tested
Last Updated: January 2025
Integration: Seamless with existing form system
Performance: Optimized for real-time updates
Testing: Verified working in development environment
Documentation: Comprehensive with examples and troubleshooting