feat: indicate family insurance eligibility

This commit is contained in:
Björn Fromme
2026-08-05 15:11:58 +02:00
parent 0b642dcfe4
commit 2cfa87f44b
12 changed files with 327 additions and 234 deletions
+6 -1
View File
@@ -19,6 +19,7 @@ class BookingCreateContextFactory
private readonly ParticipantCardAssembler $participantCardDataService,
private readonly BookingSummaryAssembler $summaryDataService,
private readonly BookingPriceCalculator $priceCalculator,
private readonly FamilyInsuranceAvailabilityChecker $familyInsuranceService,
) {
}
@@ -32,6 +33,7 @@ class BookingCreateContextFactory
bookingDto: $bookingDto,
summaryData: $baseContext['summaryData'],
groupedRooms: $baseContext['groupedRooms'],
familyInsuranceUpgradeAvailable: $baseContext['familyInsuranceUpgradeAvailable'],
);
}
@@ -45,6 +47,7 @@ class BookingCreateContextFactory
groupedRooms: $baseContext['groupedRooms'],
cardsData: $this->participantCardDataService->getAllCardsDataWithValidation($bookingDto),
isSubmitted: $isSubmitted,
familyInsuranceUpgradeAvailable: $baseContext['familyInsuranceUpgradeAvailable'],
);
}
@@ -57,17 +60,19 @@ class BookingCreateContextFactory
summaryData: $baseContext['summaryData'],
groupedRooms: $baseContext['groupedRooms'],
participantPrices: $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingDto),
familyInsuranceUpgradeAvailable: $baseContext['familyInsuranceUpgradeAvailable'],
);
}
/**
* @return array{summaryData: BookingSummaryDto, groupedRooms: RoomGroupsDto}
* @return array{summaryData: BookingSummaryDto, groupedRooms: RoomGroupsDto, familyInsuranceUpgradeAvailable: bool}
*/
private function buildBaseContext(BookingDto $bookingDto, string $pricingMode): array
{
return [
'summaryData' => $this->summaryDataService->getSummaryData($bookingDto, $pricingMode),
'groupedRooms' => $this->groupRoomsBySelectionType($bookingDto->travel->getAvailableRooms()),
'familyInsuranceUpgradeAvailable' => $this->familyInsuranceService->isFamilyInsuranceUpgradeAvailable($bookingDto),
];
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\BusProNet\Model\Insurance;
use App\Form\Model\BookingDto;
/**
* Single source of truth for "the applicant could still upgrade to a family insurance".
*
* Participants are entered one card at a time, applicant first, so at the moment the
* applicant has to pick an insurance the family constellation is not yet known and no
* family insurance can be offered. Only the applicant may hold a family insurance, so
* without a standing hint the opportunity is silently lost once the dependents' dates
* of birth make the booking eligible.
*
* This is deliberately a derived predicate rather than a one-time event: it stays true
* for as long as the applicant can still act on it and disappears by itself once they
* switch to a family insurance, deliberately pick something else while eligible, or the
* constellation stops qualifying.
*/
class FamilyInsuranceAvailabilityChecker
{
public function __construct(
private readonly InsuranceManager $insuranceService,
private readonly BookingPriceCalculator $priceCalculatorService,
) {
}
/**
* Whether the applicant should be pointed at a family insurance they cannot have
* chosen earlier.
*
* Requires `applicantInsuranceChosenWhileFamilyIneligible` - i.e. the standing choice
* was actually made before family insurance was possible - so an applicant who was
* offered a family insurance and picked something else is not nagged about it.
*/
public function isFamilyInsuranceUpgradeAvailable(BookingDto $bookingDto): bool
{
if (false === $bookingDto->applicantInsuranceChosenWhileFamilyIneligible) {
return false;
}
$applicant = $bookingDto->getParticipant(0);
if (null === $applicant || null === $applicant->insurance || true === $applicant->insurance->familyInsurance) {
return false;
}
return $this->hasEligibleFamilyInsurance($bookingDto);
}
/**
* Checks whether a family insurance is actually eligible right now - not just whether
* the participant composition qualifies as a family booking, but whether a selectable
* family insurance product actually matches the current total booking price.
*/
public function hasEligibleFamilyInsurance(BookingDto $bookingDto): bool
{
if (false === $bookingDto->isFamilyBooking()) {
return false;
}
$applicant = $bookingDto->getParticipant(0);
if (null === $applicant) {
return false;
}
$selectableInsurances = $this->insuranceService->getSelectableInsurances($bookingDto->travel);
$familyInsurances = array_values(array_filter($selectableInsurances, static fn (Insurance $i) => true === $i->familyInsurance));
if ([] === $familyInsurances) {
return false;
}
$totalPrice = $this->priceCalculatorService->calculateTotalBookingPriceExcludingInsurance($bookingDto);
return [] !== $this->insuranceService->getEligibleInsurances($familyInsurances, $applicant, $bookingDto, $totalPrice);
}
}