wip: insurance booking

This commit is contained in:
Björn Fromme
2025-09-29 19:54:20 +02:00
parent 0809f07d46
commit 90ae78262a
19 changed files with 807 additions and 128 deletions
@@ -5,6 +5,7 @@ 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;
@@ -12,7 +13,10 @@ use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDtoInterface;
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
use App\Form\Service\ServiceAgeEvaluator;
use App\Service\InsuranceMatchingService;
use App\Service\ServiceAvailabilityCalculator;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Provides dynamic field options for participant form fields.
@@ -43,10 +47,14 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
*
* @param ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory Factory for creating room choice loaders
* @param ServiceAvailabilityCalculator $serviceAvailabilityCalculator Service for calculating dynamic availability
* @param InsuranceMatchingService $insuranceMatchingService Service for matching insurances to participants
* @param UrlGeneratorInterface $urlGenerator URL generator for HTMX endpoints
*/
public function __construct(
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
private readonly ServiceAvailabilityCalculator $serviceAvailabilityCalculator,
private readonly InsuranceMatchingService $insuranceMatchingService,
private readonly UrlGeneratorInterface $urlGenerator,
) {
parent::__construct();
}
@@ -329,8 +337,8 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
return $attributes;
},
'attr' => [
'hx-post' => '#', // Will be configured when HTMX integration is implemented
'hx-target' => '#booking-summary',
'hx-post' => $this->urlGenerator->generate('app_booking_create_step_2_refresh'),
'hx-swap' => 'none',
'hx-trigger' => 'change',
],
];
@@ -360,8 +368,8 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
return $attributes;
},
'attr' => [
'hx-post' => '#', // Will be configured when HTMX integration is implemented
'hx-target' => '#booking-summary',
'hx-post' => $this->urlGenerator->generate('app_booking_create_step_2_refresh'),
'hx-swap' => 'none',
'hx-trigger' => 'change',
],
];
@@ -397,6 +405,26 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
'required' => false,
];
// Insurance field provider - provides age and eligibility filtered insurances for participants
$this->fieldOptionProviders['insurance'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Reiseversicherung',
'multiple' => false,
'expanded' => true,
'required' => false,
'choices' => array_merge(
[0 => null], // "no insurance" option
$this->getEligibleInsurances($bookingDto, $participantIndex)
),
'choice_label' => fn (?Insurance $insurance) => $this->formatInsuranceLabel($insurance),
'choice_value' => 'id',
'help' => 'Wählen Sie eine passende Reiseversicherung für diese Person aus.',
'attr' => [
'hx-post' => $this->urlGenerator->generate('app_booking_create_step_2_refresh'),
'hx-swap' => 'none',
'hx-trigger' => 'change',
],
];
// Future field providers would be added here, for example:
//
// $this->fieldOptionProviders['mealPreference'] = fn($bookingDto, $participantIndex) => [
@@ -672,4 +700,68 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
return $rentalInsuranceService->description;
}
/**
* Gets eligible insurances for a participant based on eligibility criteria.
*
* @param BookingDtoInterface $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(BookingDtoInterface $bookingDto, int $participantIndex): array
{
$participant = $bookingDto->getParticipant($participantIndex);
if (null === $participant) {
return [];
}
$availableInsurances = $bookingDto->travel->insurances ?? [];
// Only apply insurance filtering for BookingCreateDto (creation workflow)
if (!$bookingDto instanceof BookingCreateDto) {
return $availableInsurances;
}
// Use insurance matching service to filter based on eligibility criteria
return $this->insuranceMatchingService->getEligibleInsurances(
$availableInsurances,
$participant,
$bookingDto
);
}
/**
* Formats insurance label with pricing and type information.
*
* @param Insurance|null $insurance The insurance to format, or null for "No Insurance" option
*
* @return string The formatted insurance label
*/
private function formatInsuranceLabel(?Insurance $insurance): string
{
if (null === $insurance) {
return 'Keine Versicherung';
}
$label = $insurance->label;
// Add pricing information (consistent with other services)
if (null !== $insurance->price && $insurance->price > 0) {
$label .= sprintf(' (€%s)', number_format($insurance->price, 2, ',', '.'));
}
// Add type information if available
if (null !== $insurance->subType) {
$typeLabel = match ($insurance->subType) {
'RRV' => 'Reiserücktrittsversicherung',
'PAK' => 'Reiseschutz',
'OHN' => 'Selbstbehalt',
default => $insurance->subType,
};
$label .= sprintf(' (%s)', $typeLabel);
}
return $label;
}
}