fix: resolve stale dependent selections after booking family insurance

This commit is contained in:
Björn Fromme committed 2026-08-24 16:58:09 +02:00
1 parent 35826088cb
commit c9c28792e5
11 files changed
+490 -173

No files matched your search

@@ -9,13 +9,14 @@ use App\BusProNet\Model\Booking;
use App\BusProNet\Model\Insurance;
use App\BusProNet\Model\Travel;
use App\BusProNet\Utility\DirectionMapper;
use App\BusProNet\XmlLoader\AgencyLoader;
use App\Form\Model\BankAccountDto;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Form\Model\RoomSelectionDto;
use App\Service\ApplicantInsuranceCascade;
use App\Service\BookingPriceCalculator;
use App\Service\InsuranceManager;
use function Symfony\Component\String\u;
/**
@@ -34,6 +35,7 @@ class BookingDataProcessor
private readonly ParticipantServiceProcessor $serviceProcessor,
private readonly BookingPayloadBuilder $payloadBuilder,
private readonly PersonalDataSynchronizer $personalDataSynchronizer,
private readonly ApplicantInsuranceCascade $applicantInsuranceCascade,
) {
}
@@ -303,7 +305,7 @@ class BookingDataProcessor
public function createUpdateRequestPayload(?BookingDto $formData): array
{
// Apply bulk or family insurance if applicable (modifies DTO in place)
$this->applyApplicantInsuranceToParticipants($formData);
$this->applicantInsuranceCascade->apply($formData);
$bookingData = $formData->booking;
$travelData = $formData->travel;
@@ -372,7 +374,7 @@ class BookingDataProcessor
$this->reconcileInsuranceEligibility($bookingDto);
// Apply bulk or family insurance if applicable (modifies DTO in place)
$this->applyApplicantInsuranceToParticipants($bookingDto);
$this->applicantInsuranceCascade->apply($bookingDto);
return $this->payloadBuilder->buildCreatePayload($bookingDto, $bookingType);
}
@@ -453,81 +455,4 @@ class BookingDataProcessor
return false;
}
/**
* Applies the applicant's insurance to other participants if their selection requires it.
*
* Runs when either:
* - Bulk insurance is active (applicant's bulkInsuranceBooking = true): assigns the
* applicant's insurance TYPE to all dependent participants with automatic price tier
* adjustment based on each participant's individual cost.
* - The applicant selected a family insurance: family insurance covers the whole family
* under a single policy, so dependents must never hold their own insurance, regardless
* of whether the bulk checkbox was checked.
*
* IMPORTANT: In edit mode, non-family bulk insurance only applies to participants who:
* 1. Currently have NO insurance assigned (insurance === null)
* 2. OR whose insurance was already assigned via previous bulk operation
*
* This prevents overriding individually selected insurances that are locked. Family
* insurance is exempt from this protection: dependents can never legitimately hold
* their own policy alongside it, so their insurance is always cleared, in edit mode too.
*
* @param BookingDto $bookingDto The booking DTO (create or edit flow)
*/
private function applyApplicantInsuranceToParticipants(BookingDto $bookingDto): void
{
$applicant = $bookingDto->getParticipant(0);
if (null === $applicant || null === $applicant->insurance) {
return;
}
$isFamilyInsurance = true === $applicant->insurance->familyInsurance;
// Check if bulk insurance is enabled OR the applicant's selection is a family
// insurance (which always applies to dependents, independent of the bulk checkbox)
if (false === $applicant->bulkInsuranceBooking && false === $isFamilyInsurance) {
return;
}
// Get selectable (non-complementary) insurances with caching
$selectableInsurances = $this->insuranceService->getSelectableInsurances($bookingDto->travel);
// Calculate travel prices for all participants (excluding insurance)
$participantPrices = [];
foreach ($bookingDto->getParticipants() as $index => $participant) {
$participantPrices[$index] = $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $index);
}
// Use InsuranceManager for proper type-based assignment with price tier matching
$assignments = $this->insuranceService->batchAssignInsuranceToParticipants(
$selectableInsurances,
$applicant->insurance,
$bookingDto,
$participantPrices
);
// Apply assignments to all participants; for non-family insurance skip the applicant
// (they already hold the insurance they selected themselves)
foreach ($assignments as $index => $insurance) {
if (0 === $index && false === $isFamilyInsurance) {
continue;
}
$participant = $bookingDto->getParticipant($index);
if (null === $participant) {
continue;
}
// In edit mode: non-family bulk assignment must not overwrite an individually
// locked insurance. Family insurance always overrides, since dependents can
// never legitimately keep their own policy alongside it.
if (BookingDto::MODE_EDIT === $bookingDto->getMode() && false === $isFamilyInsurance && null !== $participant->insurance) {
continue; // Skip participants with existing insurance assignment
}
$participant->insurance = $insurance;
}
}
}