Files
myep/src/Form/Service/ParticipantFieldOptionsProvider.php
T

771 lines
34 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Form\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Insurance;
use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Service;
use App\BusProNet\Utility\DirectionMapper;
use App\Form\Model\BookingDto;
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
use App\Service\BookingPriceCalculatorService;
use App\Service\InsuranceService;
use App\Service\ServiceAvailabilityCalculator;
/**
* Provides dynamic field options for participant form fields.
*
* This service generates context-aware Symfony form field options for
* dynamic fields in the participant form system. It handles fields that
* require options to be calculated based on the current booking context,
* participant data, and business logic.
*
* Key Responsibilities:
* - Manages field option providers for dynamic fields
* - Provides context-aware field configurations
* - Handles interdependent field relationships
* - Supports extensible field option generation
*
* The provider uses a callable pattern for field options, enabling
* lazy evaluation and complex conditional field behavior.
*/
class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
{
/**
* Initializes the provider with required dependencies.
*
* The provider automatically registers all field option providers during
* construction to ensure they're available for form building. This approach
* keeps all field configuration logic centralized and makes it easy to add
* new dynamic fields.
*
* @param ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory Factory for creating room choice loaders
* @param ServiceAvailabilityCalculator $serviceAvailabilityCalculator Service for calculating dynamic availability
* @param InsuranceService $insuranceService Service for insurance operations
* @param BookingPriceCalculatorService $priceCalculatorService Service for calculating participant prices
*/
public function __construct(
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
private readonly ServiceAvailabilityCalculator $serviceAvailabilityCalculator,
private readonly InsuranceService $insuranceService,
private readonly BookingPriceCalculatorService $priceCalculatorService,
) {
parent::__construct();
}
/**
* Registers all field option providers during service initialization.
*
* This method defines the option generation logic for each supported dynamic field.
* Each provider is a callable that receives the booking DTO and participant
* index and returns appropriate Symfony form field options.
*
* Adding new fields:
* To add support for a new dynamic field, simply add a new provider here:
*
* $this->fieldOptionProviders['newField'] = fn($bookingDto, $participantIndex) => [
* 'label' => 'New Field Label',
* 'choices' => $this->generateChoicesFor($bookingDto, $participantIndex),
* ];
*
* Provider Pattern Benefits:
* - Lazy evaluation (options only generated when needed)
* - Context-aware configuration
* - Easy to test individual field logic
* - Supports complex interdependencies
*/
protected function registerFieldOptionProviders(): void
{
// Room assignment field provider (available for both create and edit workflows)
$this->fieldOptionProviders['assignedRoomId'] = function (BookingDto $bookingDto, int $participantIndex, array $options = []) {
$choiceLoader = null;
$disabled = false;
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
// Create context: use selected rooms from step 1
$choiceLoader = $this->roomChoiceLoaderFactory->createForCreate(
$bookingDto->getSelectedRooms()
);
// Disable when only one room type selected (auto-assigned)
$disabled = 1 === count($bookingDto->getSelectedRooms());
} elseif (BookingDto::MODE_EDIT === $bookingDto->getMode()) {
// Edit context: use already-booked rooms from booking
$choiceLoader = $this->roomChoiceLoaderFactory->createForEdit(
$bookingDto->booking->rooms
);
// Disable when only one room in booking (no reassignment needed)
$disabled = 1 === count($bookingDto->booking->rooms);
}
return [
'label' => 'Zimmer',
'placeholder' => 'Nicht zugeordnet',
// Use factory to create context-aware choice loader that:
// - Shows only available rooms for this participant
// - Excludes rooms already assigned to other participants
// - Respects room capacity and booking constraints
'choice_loader' => $choiceLoader,
// Make field read-only when only one room type is available
// Room is auto-assigned or cannot be changed, no user choice needed
'disabled' => $disabled,
];
};
// Courses field provider - provides age-appropriate courses from travel data
$this->fieldOptionProviders['courses'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Kurse',
'multiple' => true,
'expanded' => true,
'required' => false,
'choices' => $this->filterServicesByAgeConstraints(
$bookingDto->travel->getAdditionalServicesBySubTypes(
Constants::TOKEN_COURSES,
BookingDto::MODE_CREATE === $bookingDto->getMode() // Only filter by availability in create mode
),
$bookingDto,
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
}
$attributes = [];
// Add service description as data attribute for frontend use
if (null !== $service->description && '' !== trim($service->description)) {
$attributes['data-description'] = $service->description;
}
// Make readonly if service is unavailable (intelligently handles edit mode)
if ($this->shouldMakeServiceReadonly($service, $bookingDto, $participantIndex, 'courses')) {
$attributes['readonly'] = true;
$attributes['data-tooltip'] = 'ausgebucht';
}
return $attributes;
},
];
// Additional services field provider - provides age-appropriate additional services with mandatory pre-selection
$this->fieldOptionProviders['additionalServices'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Zusatzleistungen',
'multiple' => true,
'expanded' => true,
'required' => false,
'choices' => $this->filterServicesByAgeConstraints(
$bookingDto->travel->getAdditionalServicesBySubTypes(
Constants::TOKEN_ADDITIONAL,
BookingDto::MODE_EDIT !== $bookingDto->getMode() // Only filter by availability in create mode
),
$bookingDto,
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
}
$attributes = [];
// Make readonly and checked for mandatory services (pre-selection handled by service layer)
if (true === $service->mandatory) {
$attributes['checked'] = true;
$attributes['readonly'] = true;
$attributes['class'] = 'text-pink';
$attributes['data-tooltip'] = 'Diese Leistung ist nicht abwählbar';
}
// Add service description as data attribute for frontend use
if (null !== $service->description && '' !== trim($service->description)) {
$attributes['data-description'] = $service->description;
}
// Make readonly if service is unavailable (only if not already mandatory)
if (false === $service->mandatory && $this->shouldMakeServiceReadonly($service, $bookingDto, $participantIndex, 'additionalServices')) {
$attributes['readonly'] = true;
$attributes['data-tooltip'] = 'ausgebucht';
}
return $attributes;
},
];
// Board field provider - provides age-appropriate board options from travel data
$this->fieldOptionProviders['board'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Verpflegung',
'multiple' => true,
'expanded' => true,
'required' => false,
'choices' => $this->filterServicesByAgeConstraints(
$bookingDto->travel->getAdditionalServicesBySubTypes(
Constants::TOKEN_BOARD,
BookingDto::MODE_CREATE === $bookingDto->getMode() // Only filter by availability in create mode
),
$bookingDto,
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
}
$attributes = [];
// Make readonly if service is unavailable (intelligently handles edit mode)
if ($this->shouldMakeServiceReadonly($service, $bookingDto, $participantIndex, 'board')) {
$attributes['readonly'] = true;
$attributes['data-tooltip'] = 'ausgebucht';
}
return $attributes;
},
];
// Rentals field provider - provides age-appropriate rental options filtered by selected skipass duration
$this->fieldOptionProviders['rentals'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Leihmaterial',
'multiple' => true,
'expanded' => true,
'required' => false,
'choices' => $this->filterServicesByAgeConstraints(
$this->filterRentalsBySkiPassDuration(
$bookingDto->travel->getAdditionalServicesBySubTypes(
Constants::TOKEN_RENTALS,
BookingDto::MODE_CREATE === $bookingDto->getMode(), // Only filter by availability in create mode
true // Filter by travel date range
),
$bookingDto,
$participantIndex
),
$bookingDto,
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
}
$attributes = [];
// Add service description as data attribute for frontend use
if (null !== $service->description && '' !== trim($service->description)) {
$attributes['data-description'] = $service->description;
}
// Make readonly if service is unavailable (intelligently handles edit mode)
if ($this->shouldMakeServiceReadonly($service, $bookingDto, $participantIndex, 'rentals')) {
$attributes['readonly'] = true;
$attributes['data-tooltip'] = 'ausgebucht';
}
return $attributes;
},
];
// Rental insurance field provider - provides rental insurance options when rental services are selected
$this->fieldOptionProviders['rentalInsurance'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => $this->getRentalInsuranceCheckboxLabel($bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_RENTAL_INSURANCE, true, true)),
'required' => false,
'property_path' => 'rentalInsuranceSelected',
'help' => $this->getRentalInsuranceDescription($bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_RENTAL_INSURANCE, true, true)),
];
// License plate field provider - provides text input for vehicle license plate when parking is selected
$this->fieldOptionProviders['licensePlate'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Kennzeichen',
'required' => false,
'attr' => [
'placeholder' => 'z.B. AB-CD 123',
'maxlength' => 20,
],
'help' => 'Bitte gib das Kennzeichen deines Fahrzeugs an. Du kannst es aber auch später nachreichen.',
'sanitize_html' => true,
];
// Skipass field provider - provides age-appropriate skipass options from travel data filtered by date range
$this->fieldOptionProviders['skiPass'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Skipass',
'multiple' => false,
'expanded' => true,
'required' => true,
'choices' => $this->filterServicesByAgeConstraints(
$bookingDto->travel->getAdditionalServicesBySubTypes(
Constants::TOKEN_SKI_PASS,
BookingDto::MODE_CREATE === $bookingDto->getMode(), // Only filter by availability in create mode
true // Filter by travel date range
),
$bookingDto,
$participantIndex
),
'choice_value' => 'id',
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
}
$attributes = [];
// Add service description as data attribute for frontend use
if (null !== $service->description && '' !== trim($service->description)) {
$attributes['data-description'] = $service->description;
}
// Make readonly if service is unavailable (intelligently handles edit mode)
if ($this->shouldMakeServiceReadonly($service, $bookingDto, $participantIndex, 'skiPass')) {
$attributes['readonly'] = true;
$attributes['data-tooltip'] = 'ausgebucht';
}
return $attributes;
},
];
// Room remarks field provider - provides textarea for room-specific remarks (only for 'mbz' rooms)
$this->fieldOptionProviders['remarksRoom'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Wünsche oder Anmerkungen zum Zimmer',
'required' => false,
'sanitize_html' => true,
'attr' => [
'rows' => 2,
],
];
// Transportation field providers - handles outbound/inbound transportation and pickup selection
// Outbound Transportation
$this->fieldOptionProviders['transportationOutbound'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Hinfahrt',
'choices' => $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::OUTBOUND_TRAVEL),
'choice_label' => fn (Service $service) => $this->formatTransportationServiceLabel($service),
'choice_value' => 'id',
'expanded' => true,
'multiple' => false,
'required' => true,
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
}
$attributes = [];
// Make readonly if service is unavailable (intelligently handles edit mode)
if ($this->shouldMakeServiceReadonly($service, $bookingDto, $participantIndex, 'transportationOutbound')) {
$attributes['readonly'] = true;
$attributes['data-tooltip'] = 'ausgebucht';
}
return $attributes;
},
];
// Inbound Transportation
$this->fieldOptionProviders['transportationInbound'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Rückfahrt',
'choices' => $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::INBOUND_TRAVEL),
'choice_label' => fn (Service $service) => $this->formatTransportationServiceLabel($service),
'choice_value' => 'id',
'expanded' => true,
'multiple' => false,
'required' => true,
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
return [];
}
$attributes = [];
// Make readonly if service is unavailable (intelligently handles edit mode)
if ($this->shouldMakeServiceReadonly($service, $bookingDto, $participantIndex, 'transportationInbound')) {
$attributes['readonly'] = true;
$attributes['data-tooltip'] = 'ausgebucht';
}
return $attributes;
},
];
// Pickup (conditional - only shown when either transportation direction is bus)
// Uses outbound pickups list, applies to both directions
$this->fieldOptionProviders['pickup'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Zu- und Ausstieg',
'choices' => $bookingDto->travel->pickupsOutbound,
'choice_label' => fn (?Pickup $pickup) => $this->formatPickupLabelWithPrice($pickup),
'choice_value' => 'id',
'expanded' => false, // Dropdown for pickups
'multiple' => false,
'required' => true,
'placeholder' => 'Zu- und Ausstieg auswählen',
];
// Parking (conditional - only shown when outbound transportation is PKW)
// Simple checkbox since there's only ever one parking type
$this->fieldOptionProviders['parking'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => $this->getParkingCheckboxLabel($bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_PARKING, true)),
'required' => false,
];
// Bulk insurance booking checkbox (applicant only - controls insurance assignment for all participants)
// Only registered in create mode - insurance cannot be modified in edit mode due to API limitation
$this->fieldOptionProviders['bulkInsuranceBooking'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Für alle Teilnehmer buchen',
'required' => false,
];
// Insurance field provider - provides age and eligibility filtered insurances for participants
// Only registered in create mode - insurance cannot be modified in edit mode due to API limitation
$this->fieldOptionProviders['insurance'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Reiseversicherung',
'multiple' => false,
'expanded' => true,
'required' => false,
'choices' => $this->getEligibleInsurances($bookingDto, $participantIndex),
'choice_value' => 'id',
'choice_label' => function (?Insurance $insurance) {
$label = $insurance->label;
if (null !== $insurance->price && $insurance->price > 0) {
$label .= sprintf(' (€%s)', number_format($insurance->price, 2, ',', '.'));
}
return $label;
},
'placeholder' => 'Keine Versicherung gewünscht',
];
// Future field providers would be added here, for example:
//
// $this->fieldOptionProviders['mealPreference'] = fn($bookingDto, $participantIndex) => [
// 'label' => 'Meal Preference',
// 'choices' => [
// 'Standard' => 'standard',
// 'Vegetarian' => 'vegetarian',
// 'Vegan' => 'vegan',
// ],
// ];
}
/**
* Filters rental services by selected skipass duration.
*
* Only returns rentals that have exactly the same dateFrom and dateTo
* as the participant's selected skipass. This ensures rental equipment
* is only available for the exact duration of the skipass.
*
* @param array $rentals Array of rental Service objects to filter
* @param BookingDto $bookingDto The booking DTO containing participant data
* @param int $participantIndex Index of the participant to evaluate
*
* @return array Filtered array of rentals matching skipass duration
*/
private function filterRentalsBySkiPassDuration(array $rentals, BookingDto $bookingDto, int $participantIndex): array
{
$participant = $bookingDto->getParticipant($participantIndex);
if (null === $participant || null === $participant->skiPass) {
return []; // No skipass selected = no rentals available
}
$selectedSkiPass = $participant->skiPass;
// If skipass has no valid dates, return empty rentals
if (null === $selectedSkiPass->dateFrom || null === $selectedSkiPass->dateTo) {
return [];
}
return array_filter($rentals, function (Service $rental) use ($selectedSkiPass) {
// Rental must have valid dates to be considered
if (null === $rental->dateFrom || null === $rental->dateTo) {
return false;
}
// Exact date matching: rental dates must match skipass dates exactly
return $rental->dateFrom->format('Y-m-d') === $selectedSkiPass->dateFrom->format('Y-m-d')
&& $rental->dateTo->format('Y-m-d') === $selectedSkiPass->dateTo->format('Y-m-d');
});
}
/**
* Formats service label with pricing information.
*
* @param Service|null $service The service to format
*
* @return string The formatted label
*/
private function formatServiceLabelWithPrice(?Service $service): string
{
if (null === $service) {
return '';
}
if (null === $service->price || 0.0 === $service->price) {
return $service->label;
}
if ($service->price < 0) {
// Negative prices are discounts
return sprintf('%s (-%s€ Rabatt)', $service->label, number_format(abs($service->price), 2, ',', '.'));
}
return sprintf('%s (€%s)', $service->label, number_format($service->price, 2, ',', '.'));
}
/**
* Format transportation service labels with type indicator and pricing.
*
* Creates user-friendly labels for transportation services that include:
* - Transportation type icon (🚌 for bus, 🚗 for car)
* - Service name
* - Pricing (with discount indication for negative prices)
* - Availability warning for limited services
*
* @param Service $service The transportation service to format
*
* @return string The formatted transportation service label
*/
private function formatTransportationServiceLabel(Service $service): string
{
$label = $service->label;
if (null === $service->price || 0.0 === $service->price) {
return $service->label;
}
if ($service->price > 0) {
$label .= sprintf(' (€%s)', number_format($service->price, 2, ',', '.'));
} else {
$label .= sprintf(' (-%s€ Rabatt)', number_format(abs($service->price), 2, ',', '.'));
}
// Add availability warning if limited
if (null !== $service->available && $service->available <= 5) {
$label .= sprintf(' (nur %d verfügbar)', $service->available);
}
return $label;
}
private function formatPickupLabelWithPrice(?Pickup $pickup): string
{
if (null === $pickup) {
return '';
}
return $pickup->getLabelWithPrice();
}
/**
* Gets the parking checkbox label with pricing information.
*
* Creates a checkbox label for the single parking service including pricing.
* Since there's only ever one parking type, we take the first available service.
*
* @param array $parkingServices Array of available parking services
*
* @return string The formatted checkbox label with pricing
*/
private function getParkingCheckboxLabel(array $parkingServices): string
{
if (empty($parkingServices)) {
return Constants::SERVICE_LABELS[Constants::TOKEN_PARKING];
}
$parkingService = reset($parkingServices); // Get the first (and only) parking service
return $this->formatServiceLabelWithPrice($parkingService);
}
/**
* Checks if a service should be rendered as read-only due to unavailability.
*
* @param Service $service The service to check
* @param BookingDto $bookingDto The booking DTO containing participant data
* @param int $participantIndex Index of the participant currently selecting services
*
* @return bool True if the service should be read-only due to unavailability
*/
private function isServiceUnavailableForParticipant(Service $service, BookingDto $bookingDto, int $participantIndex): bool
{
if (BookingDto::MODE_EDIT === $bookingDto->getMode()) {
// For non-create workflows, don't apply availability restrictions
return false;
}
return $this->serviceAvailabilityCalculator->isServiceUnavailable($service->id, $bookingDto, $participantIndex);
}
/**
* Determines if a service should be rendered as read-only.
*
* This method intelligently handles readonly state for services in both create and edit modes:
*
* - CREATE MODE: Uses existing availability calculator logic
* - EDIT MODE: Services unavailable (available <= 0) are readonly ONLY if participant doesn't already have them
*
* This prevents fingerprint false positives in edit mode by allowing participants to keep
* services they already have, even if those services are now fully booked.
*
* @param Service $service The service to check
* @param BookingDto $bookingDto The booking DTO containing participant data
* @param int $participantIndex Index of the participant currently selecting services
* @param string $fieldName Name of the service field (e.g., 'courses', 'board', 'rentals')
*
* @return bool True if the service should be read-only
*/
private function shouldMakeServiceReadonly(Service $service, BookingDto $bookingDto, int $participantIndex, string $fieldName): bool
{
// In CREATE mode, use existing availability logic
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
return $this->isServiceUnavailableForParticipant($service, $bookingDto, $participantIndex);
}
// In EDIT mode, apply intelligent readonly logic
// If service is available (available > 0), it's never readonly
if (null !== $service->available && $service->available > 0) {
return false;
}
// Service is unavailable - check if participant already has it
$participant = $bookingDto->getParticipant($participantIndex);
if (null === $participant) {
return true; // Readonly if no participant data
}
// Check if participant has this service based on field type
$participantHasService = match ($fieldName) {
'courses' => $this->hasServiceById($participant->courses, $service->id),
'additionalServices' => $this->hasServiceById($participant->additionalServices, $service->id),
'board' => $this->hasServiceById($participant->board, $service->id),
'rentals' => $this->hasServiceById($participant->rentals, $service->id),
'skiPass' => $participant->skiPass?->id === $service->id,
'transportationOutbound' => $participant->transportationOutbound?->id === $service->id,
'transportationInbound' => $participant->transportationInbound?->id === $service->id,
default => false,
};
// Make readonly only if participant doesn't have it
return false === $participantHasService;
}
/**
* Checks if a service array contains a service with the given ID.
*
* @param array $services Array of Service objects
* @param int $serviceId Service ID to search for
*
* @return bool True if the service is found in the array
*/
private function hasServiceById(array $services, int $serviceId): bool
{
foreach ($services as $service) {
if ($service->id === $serviceId) {
return true;
}
}
return false;
}
/**
* Filters services based on participant's age constraints.
*
* Removes services that have age restrictions the participant doesn't meet.
* If no age evaluator is configured or participant has no birth date,
* returns empty array to be handled by field visibility conditions.
*
* @param array $services Array of Service objects to filter
* @param BookingDto $bookingDto The booking DTO containing participant data
* @param int $participantIndex Index of the participant to evaluate
*
* @return array Filtered array of available services
*/
private function filterServicesByAgeConstraints(array $services, BookingDto $bookingDto, int $participantIndex): array
{
$ageEvaluator = new ServiceAgeEvaluator();
$participant = $bookingDto->getParticipant($participantIndex);
// If no birthdate provided, return empty array (handled by field visibility conditions)
if (null === $participant || null === $participant->dateOfBirth) {
return [];
}
return array_filter($services, function (Service $service) use ($bookingDto, $participantIndex, $ageEvaluator) {
// No age constraints = available to all
if (false === $ageEvaluator->canEvaluate($service)) {
return true;
}
return $ageEvaluator->isServiceAvailableForParticipant($service, $bookingDto, $participantIndex);
});
}
/**
* Generates label for rental insurance checkbox including pricing information.
*/
private function getRentalInsuranceCheckboxLabel(array $rentalInsuranceServices): string
{
if (empty($rentalInsuranceServices)) {
return Constants::SERVICE_LABELS[Constants::TOKEN_RENTAL_INSURANCE];
}
$rentalInsuranceService = reset($rentalInsuranceServices); // Get the first (and only) rental insurance service
return $this->formatServiceLabelWithPrice($rentalInsuranceService);
}
/**
* Gets the rental insurance description for help text.
*/
private function getRentalInsuranceDescription(array $rentalInsuranceServices): ?string
{
if (empty($rentalInsuranceServices)) {
return null;
}
$rentalInsuranceService = reset($rentalInsuranceServices); // Get the first (and only) rental insurance service
return $rentalInsuranceService->description;
}
/**
* Gets eligible insurances for a participant based on eligibility criteria.
*
* @param BookingDto $bookingDto The booking DTO containing travel and participant data
* @param int $participantIndex The index of the participant to get eligible insurances for
*
* @return array Array of eligible insurance objects filtered by age, family status, and other constraints
*/
private function getEligibleInsurances(BookingDto $bookingDto, int $participantIndex): array
{
$participant = $bookingDto->getParticipant($participantIndex);
if (null === $participant) {
return [];
}
// Get selectable (non-complementary) insurances with caching
$selectableInsurances = $this->insuranceService->getSelectableInsurances($bookingDto->travel);
// Calculate travel price for eligibility filtering
$travelPrice = $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $participantIndex);
// Filter based on eligibility criteria for this participant
return $this->insuranceService->getEligibleInsurances(
$selectableInsurances,
$participant,
$bookingDto,
$travelPrice
);
}
}