fix: incorrect rules around family insurances

addresses #869dr80qj
This commit is contained in:
Björn Fromme
2026-08-05 15:11:37 +02:00
parent 736128476b
commit 8a46908856
15 changed files with 859 additions and 50 deletions
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Form\Service\Condition;
use App\Form\Model\BookingDto;
use App\Form\Service\Contract\FieldConditionInterface;
/**
* True for dependent participants when the applicant has a family insurance selected.
*
* Family insurance covers the whole family under a single policy on the applicant, so
* dependents must not be offered their own insurance selection while it's active.
*/
class FamilyInsuranceActiveCondition implements FieldConditionInterface
{
public function evaluate(BookingDto $bookingDto, int $participantIndex, array $formData): bool
{
if (0 === $participantIndex) {
return false;
}
$applicant = $bookingDto->getParticipant(0);
return null !== $applicant
&& null !== $applicant->insurance
&& true === $applicant->insurance->familyInsurance;
}
public function getDependentFields(): array
{
return ['insurance'];
}
public function getDescription(): string
{
return 'Applicant has a family insurance selected';
}
}
@@ -12,6 +12,7 @@ use App\Form\Service\Condition\BookingEligibilityCondition;
use App\Form\Service\Condition\BulkInsuranceBookingCondition;
use App\Form\Service\Condition\CompositeCondition;
use App\Form\Service\Condition\DateOfBirthProvidedCondition;
use App\Form\Service\Condition\FamilyInsuranceActiveCondition;
use App\Form\Service\Condition\FieldValueCondition;
use App\Form\Service\Condition\FinalBookingOnlyCondition;
use App\Form\Service\Condition\FirstParticipantCondition;
@@ -221,11 +222,13 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
),
];
// Hide insurance field until date of birth is provided OR when bulk insurance booking is active OR when participant is ineligible
// Hide insurance field until date of birth is provided OR when bulk insurance booking is active
// OR when the applicant has a family insurance active (it already covers dependents) OR when participant is ineligible
$this->fieldStateConditions['insurance'] = [
'hidden' => CompositeCondition::or(
CompositeCondition::not($dateOfBirthProvidedCondition),
$bulkInsuranceBookingCondition,
new FamilyInsuranceActiveCondition(),
$bookingEligibilityCondition
),
];
@@ -1159,15 +1159,17 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
// 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);
// Family insurances are priced by the total booking price rather than the individual
// participant's price - InsuranceManager applies the correct basis per insurance type
$individualPrice = $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $participantIndex);
$totalBookingPrice = $this->priceCalculatorService->calculateTotalBookingPriceExcludingInsurance($bookingDto);
// Filter based on eligibility criteria for this participant
$eligibleInsurances = $this->insuranceService->getEligibleInsurances(
$eligibleInsurances = $this->insuranceService->getEligibleInsurancesForParticipant(
$selectableInsurances,
$participant,
$bookingDto,
$travelPrice
$individualPrice,
$totalBookingPrice
);
// Inject synthetic "no insurance" option at the top of the list
@@ -136,9 +136,6 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
// Get selectable (non-complementary) insurances with caching
$selectableInsurances = $this->insuranceService->getSelectableInsurances($bookingDto->travel);
// Calculate travel price for eligibility checks
$travelPrice = $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $participantIndex);
// Determine if this is a new user selection or just form resubmission
$isNewSelection = null !== $selectedInsuranceId
&& (null === $currentInsurance || !$this->isSameInsurance($selectedInsuranceId, $currentInsurance));
@@ -161,6 +158,8 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
$selectedInsurance = $this->findInsuranceById($selectableInsurances, $selectedInsuranceId);
if (null !== $selectedInsurance) {
$travelPrice = $this->priceCalculatorService->resolveInsuranceTravelPrice($bookingDto, $participantIndex, $selectedInsurance);
$eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice);
$isSelectedInsuranceEligible = $this->isInsuranceInList($selectedInsurance, $eligibleInsurances);
@@ -198,6 +197,8 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
return;
}
$travelPrice = $this->priceCalculatorService->resolveInsuranceTravelPrice($bookingDto, $participantIndex, $currentInsurance);
// Check if current insurance is still eligible with updated participant data
$eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice);
$isCurrentInsuranceStillEligible = $this->isInsuranceInList($currentInsurance, $eligibleInsurances);