14 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
- Service Usage Calculation: Test counting across multiple participants
- Availability Filtering: Verify services hidden when capacity reached
- Edge Cases: Handle null values, empty arrays, invalid data
- Service Types: Test all service categories (courses, rentals, etc.)
- Current Participant Exclusion: Ensure current participant selections not counted
Integration Testing
- Form Field Generation: Verify filtered choices in field options
- HTMX Refresh Cycles: Test availability updates during form interactions
- Multiple Participants: Test complex scenarios with many participants
- 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
- Availability Display: Show remaining count in service labels (optional)
- Reservation System: Temporary hold on services during selection
- Priority Booking: VIP participants get access to limited services first
- Cross-Session Tracking: Track availability across multiple booking sessions
- Analytics: Collect data on service demand and capacity utilization
Performance Optimizations
- Caching Layer: Cache availability calculations for identical participant sets
- Lazy Loading: Only calculate availability for visible services
- Background Updates: Pre-calculate availability for common scenarios
- 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 objectsApp\Form\Model\BookingCreateDto- Booking and participant dataApp\BusProNet\Constants- Service type constantsApp\BusProNet\Utility\DirectionMapper- Transportation direction mapping
Related Systems
- 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
Future Enhancements
Planned: API Validation Stage
Important Note: The current availability system uses XML data that may become outdated during the booking creation process. A future enhancement will implement a validation stage that checks final service selections against real-time availability via the BusProNet API before final booking confirmation.
Planned Implementation
- Pre-Submission Validation: Before final booking submission, validate all selected services against current API availability
- Real-Time Check: Call BusProNet API to get current availability status
- Conflict Resolution: Handle cases where selected services are no longer available
- User Feedback: Provide clear messaging when services become unavailable during booking process
Technical Integration Points
// Future validation service (planned)
class BookingValidationService
{
public function validateServiceAvailability(BookingCreateDto $bookingDto): ValidationResult;
public function resolveAvailabilityConflicts(BookingCreateDto $bookingDto): ConflictResolution;
}
Business Logic
- Session Availability: Current system prevents overbooking within single booking session
- API Validation: Future system will prevent overbooking across all booking sessions system-wide
- Two-Stage Protection: Provides both immediate feedback and final validation
This planned enhancement will complement the existing dynamic availability system by adding a final validation layer that ensures booking integrity against the authoritative BusProNet API data.
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
Future Enhancement: API validation stage planned for booking finalization
Documentation: Comprehensive with examples and troubleshooting