feat: indicate family insurance eligibility
This commit is contained in:
@@ -92,7 +92,9 @@ class Step2ParticipantController extends AbstractController
|
|||||||
return $this->redirectToRoute('app_booking_create_step_2');
|
return $this->redirectToRoute('app_booking_create_step_2');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->renderParticipantForm($form, $index, $bookingDto);
|
// Notifications were already cleared from the DTO above, so they have to be handed
|
||||||
|
// to the render path too - otherwise every invalid submission silently swallows them
|
||||||
|
return $this->renderParticipantForm($form, $index, $bookingDto, $notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,17 +126,25 @@ class Step2ParticipantController extends AbstractController
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param FormInterface<mixed> $form */
|
/**
|
||||||
|
* @param FormInterface<mixed> $form
|
||||||
|
* @param array<array{type: string, message: string}> $notifications
|
||||||
|
*/
|
||||||
private function renderParticipantForm(
|
private function renderParticipantForm(
|
||||||
FormInterface $form,
|
FormInterface $form,
|
||||||
int $index,
|
int $index,
|
||||||
BookingDto $bookingDto,
|
BookingDto $bookingDto,
|
||||||
|
array $notifications = [],
|
||||||
): Response {
|
): Response {
|
||||||
$bookingCreateContext = $this->createContextFactory->create(
|
$bookingCreateContext = $this->createContextFactory->create(
|
||||||
$bookingDto,
|
$bookingDto,
|
||||||
RoomPricingCalculator::PRICING_MODE_SELECTION
|
RoomPricingCalculator::PRICING_MODE_SELECTION
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The rendered template includes _partials/_flashes.html.twig, so flashes added here
|
||||||
|
// are consumed by this very response instead of leaking into the next page
|
||||||
|
$this->addNotificationsAsFlashMessages($notifications);
|
||||||
|
|
||||||
return $this->render('booking/create/step_2_participant.html.twig', [
|
return $this->render('booking/create/step_2_participant.html.twig', [
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
'participantIndex' => $index,
|
'participantIndex' => $index,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class BookingCreateContext
|
|||||||
public readonly ?array $cardsData = null,
|
public readonly ?array $cardsData = null,
|
||||||
public readonly bool $isSubmitted = false,
|
public readonly bool $isSubmitted = false,
|
||||||
public readonly ?array $participantPrices = null,
|
public readonly ?array $participantPrices = null,
|
||||||
|
public readonly bool $familyInsuranceUpgradeAvailable = false,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,18 +70,12 @@ class BookingDto
|
|||||||
*/
|
*/
|
||||||
public ?string $originalFingerprint = null;
|
public ?string $originalFingerprint = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the applicant has already been notified that family insurance became
|
|
||||||
* available after they picked a non-family insurance while dependents were incomplete.
|
|
||||||
* Prevents re-showing the same hint on every subsequent participant card submission.
|
|
||||||
*/
|
|
||||||
public bool $familyInsuranceHintShown = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the applicant's current insurance choice was made while family insurance
|
* Whether the applicant's current insurance choice was made while family insurance
|
||||||
* was not yet eligible (i.e. before all dependents' dates of birth were known).
|
* was not yet eligible (i.e. before the dependents' dates of birth were known).
|
||||||
* Set at selection time; used to distinguish "family insurance just became
|
* Set at selection time; used by FamilyInsuranceAvailabilityChecker to distinguish
|
||||||
* available" from "it was available all along and the applicant chose otherwise".
|
* "the applicant never had the chance" from "it was available all along and the
|
||||||
|
* applicant chose otherwise".
|
||||||
*/
|
*/
|
||||||
public bool $applicantInsuranceChosenWhileFamilyIneligible = false;
|
public bool $applicantInsuranceChosenWhileFamilyIneligible = false;
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ namespace App\Form\Service;
|
|||||||
|
|
||||||
use App\BusProNet\Model\Insurance;
|
use App\BusProNet\Model\Insurance;
|
||||||
use App\Form\Model\BookingDto;
|
use App\Form\Model\BookingDto;
|
||||||
use App\Form\Model\ParticipantDto;
|
|
||||||
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||||
use App\Service\BookingPriceCalculator;
|
use App\Service\BookingPriceCalculator;
|
||||||
|
use App\Service\FamilyInsuranceAvailabilityChecker;
|
||||||
use App\Service\InsuranceManager;
|
use App\Service\InsuranceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +38,7 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly InsuranceManager $insuranceService,
|
private readonly InsuranceManager $insuranceService,
|
||||||
private readonly BookingPriceCalculator $priceCalculatorService,
|
private readonly BookingPriceCalculator $priceCalculatorService,
|
||||||
|
private readonly FamilyInsuranceAvailabilityChecker $familyInsuranceService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,19 +126,6 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
* @param int $participantIndex The index of the participant being processed
|
* @param int $participantIndex The index of the participant being processed
|
||||||
*/
|
*/
|
||||||
public function processField(array $submittedData, BookingDto $bookingDto, int $participantIndex): void
|
public function processField(array $submittedData, BookingDto $bookingDto, int $participantIndex): void
|
||||||
{
|
|
||||||
$this->applyInsuranceSelection($submittedData, $bookingDto, $participantIndex);
|
|
||||||
|
|
||||||
// Runs after the selection above is fully resolved, so a submission that itself
|
|
||||||
// switches the applicant to family insurance doesn't trigger a stale "please
|
|
||||||
// recheck" notification about the choice it just made.
|
|
||||||
$this->notifyApplicantIfFamilyInsuranceNewlyAvailable($bookingDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
|
||||||
*/
|
|
||||||
private function applyInsuranceSelection(array $submittedData, BookingDto $bookingDto, int $participantIndex): void
|
|
||||||
{
|
{
|
||||||
$participant = $bookingDto->getParticipant($participantIndex);
|
$participant = $bookingDto->getParticipant($participantIndex);
|
||||||
if (null === $participant) {
|
if (null === $participant) {
|
||||||
@@ -283,8 +271,9 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Records whether the applicant's insurance choice was made while family insurance
|
* Records whether the applicant's insurance choice was made while family insurance
|
||||||
* was NOT yet eligible, so the notification below can tell "just became available"
|
* was NOT yet eligible, so the standing hint rendered in step 2 can tell "never had
|
||||||
* apart from "was available all along and the applicant chose non-family anyway".
|
* the chance" apart from "was available all along and the applicant chose non-family
|
||||||
|
* anyway".
|
||||||
*
|
*
|
||||||
* Only called when the applicant (index 0) makes a genuine new selection - not on
|
* Only called when the applicant (index 0) makes a genuine new selection - not on
|
||||||
* mere resubmission/price-tier reassignment of an already-standing choice.
|
* mere resubmission/price-tier reassignment of an already-standing choice.
|
||||||
@@ -295,79 +284,11 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$applicant = $bookingDto->getParticipant(0);
|
if (null === $bookingDto->getParticipant(0)) {
|
||||||
if (null === $applicant) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = !$this->hasEligibleFamilyInsurance($bookingDto, $applicant);
|
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = !$this->familyInsuranceService->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.
|
|
||||||
*
|
|
||||||
* Used by both the selection-time recording above and the notification below, so they
|
|
||||||
* can never disagree on what "family insurance is eligible" means.
|
|
||||||
*/
|
|
||||||
private function hasEligibleFamilyInsurance(BookingDto $bookingDto, ParticipantDto $applicant): bool
|
|
||||||
{
|
|
||||||
if (!$bookingDto->isFamilyBooking()) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the applicant when family insurance becomes available after the fact.
|
|
||||||
*
|
|
||||||
* Participants are entered one card at a time, in any order, so the applicant is
|
|
||||||
* likely to pick a non-family insurance before any dependent's date of birth makes
|
|
||||||
* the booking eligible for family insurance. Once that happens, nothing prompts the
|
|
||||||
* applicant to revisit their earlier choice. This surfaces a one-time hint via the
|
|
||||||
* existing notification/toast pipeline (App\Form\Model\ParticipantDto::addNotification()),
|
|
||||||
* mirroring the cross-participant side effect already used by
|
|
||||||
* ParticipantAssignedRoomFieldHandler::resolveRoomCapacityConflict() for room conflicts.
|
|
||||||
*
|
|
||||||
* Requires `applicantInsuranceChosenWhileFamilyIneligible` to be true - i.e. the
|
|
||||||
* choice was actually made before family insurance was possible - not just that
|
|
||||||
* family insurance happens to be eligible now (it may have been all along).
|
|
||||||
*/
|
|
||||||
private function notifyApplicantIfFamilyInsuranceNewlyAvailable(BookingDto $bookingDto): void
|
|
||||||
{
|
|
||||||
if ($bookingDto->familyInsuranceHintShown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$bookingDto->applicantInsuranceChosenWhileFamilyIneligible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$applicant = $bookingDto->getParticipant(0);
|
|
||||||
if (null === $applicant || null === $applicant->insurance || true === $applicant->insurance->familyInsurance) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->hasEligibleFamilyInsurance($bookingDto, $applicant)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$applicant->addNotification(
|
|
||||||
'info',
|
|
||||||
'Für eure Konstellation ist auch eine Familienversicherung verfügbar und kann über die anmeldende Person gebucht werden.'
|
|
||||||
);
|
|
||||||
$bookingDto->familyInsuranceHintShown = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class BookingCreateContextFactory
|
|||||||
private readonly ParticipantCardAssembler $participantCardDataService,
|
private readonly ParticipantCardAssembler $participantCardDataService,
|
||||||
private readonly BookingSummaryAssembler $summaryDataService,
|
private readonly BookingSummaryAssembler $summaryDataService,
|
||||||
private readonly BookingPriceCalculator $priceCalculator,
|
private readonly BookingPriceCalculator $priceCalculator,
|
||||||
|
private readonly FamilyInsuranceAvailabilityChecker $familyInsuranceService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ class BookingCreateContextFactory
|
|||||||
bookingDto: $bookingDto,
|
bookingDto: $bookingDto,
|
||||||
summaryData: $baseContext['summaryData'],
|
summaryData: $baseContext['summaryData'],
|
||||||
groupedRooms: $baseContext['groupedRooms'],
|
groupedRooms: $baseContext['groupedRooms'],
|
||||||
|
familyInsuranceUpgradeAvailable: $baseContext['familyInsuranceUpgradeAvailable'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@ class BookingCreateContextFactory
|
|||||||
groupedRooms: $baseContext['groupedRooms'],
|
groupedRooms: $baseContext['groupedRooms'],
|
||||||
cardsData: $this->participantCardDataService->getAllCardsDataWithValidation($bookingDto),
|
cardsData: $this->participantCardDataService->getAllCardsDataWithValidation($bookingDto),
|
||||||
isSubmitted: $isSubmitted,
|
isSubmitted: $isSubmitted,
|
||||||
|
familyInsuranceUpgradeAvailable: $baseContext['familyInsuranceUpgradeAvailable'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,17 +60,19 @@ class BookingCreateContextFactory
|
|||||||
summaryData: $baseContext['summaryData'],
|
summaryData: $baseContext['summaryData'],
|
||||||
groupedRooms: $baseContext['groupedRooms'],
|
groupedRooms: $baseContext['groupedRooms'],
|
||||||
participantPrices: $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingDto),
|
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
|
private function buildBaseContext(BookingDto $bookingDto, string $pricingMode): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'summaryData' => $this->summaryDataService->getSummaryData($bookingDto, $pricingMode),
|
'summaryData' => $this->summaryDataService->getSummaryData($bookingDto, $pricingMode),
|
||||||
'groupedRooms' => $this->groupRoomsBySelectionType($bookingDto->travel->getAvailableRooms()),
|
'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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
{% set isSubmitted = isSubmitted|default(false) %}
|
{% set isSubmitted = isSubmitted|default(false) %}
|
||||||
{% set needsAttention = needsAttention|default(false) %}
|
{% set needsAttention = needsAttention|default(false) %}
|
||||||
{% set attentionLabels = attentionLabels|default([]) %}
|
{% set attentionLabels = attentionLabels|default([]) %}
|
||||||
|
{% set hintLabel = hintLabel|default(null) %}
|
||||||
{% set mode = mode|default('create') %}
|
{% set mode = mode|default('create') %}
|
||||||
|
|
||||||
{# Determine display states #}
|
{# Determine display states #}
|
||||||
@@ -75,6 +76,13 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{# Additive informational hint - unlike attentionLabels this does not replace the
|
||||||
|
room/price line and is not styled as something the participant got wrong #}
|
||||||
|
{% if hintLabel and not isCanceled %}
|
||||||
|
<div class="mt-2 text-sm font-medium text-primary-light" {{ qa_attribute('participant-hint', index) }}>
|
||||||
|
{{ hintLabel }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
{% if not isCanceled %}
|
{% if not isCanceled %}
|
||||||
|
|||||||
@@ -551,6 +551,15 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="pb-4">
|
<div class="pb-4">
|
||||||
|
{# The applicant has to choose an insurance before the dependents' dates of birth
|
||||||
|
are known, so a family insurance cannot be offered at that point. Once the
|
||||||
|
constellation qualifies, this standing hint points them back at the option. #}
|
||||||
|
{% if participantIndex == 0 and bookingCreateContext is defined and bookingCreateContext.familyInsuranceUpgradeAvailable %}
|
||||||
|
{% include '_partials/_alert.html.twig' with {
|
||||||
|
level: 'info',
|
||||||
|
messages: ['Für eure Konstellation ist auch eine Familienversicherung verfügbar und kann über die anmeldende Person gebucht werden.']
|
||||||
|
} %}
|
||||||
|
{% endif %}
|
||||||
<fieldset class="border border-primary-bg">
|
<fieldset class="border border-primary-bg">
|
||||||
<legend class="{{ html_classes('w-full bg-primary-bg p-2 font-semibold uppercase', { 'text-red-500': form.insurance is defined and not form.insurance.vars.valid }) }}">
|
<legend class="{{ html_classes('w-full bg-primary-bg p-2 font-semibold uppercase', { 'text-red-500': form.insurance is defined and not form.insurance.vars.valid }) }}">
|
||||||
Reiseversicherung*
|
Reiseversicherung*
|
||||||
|
|||||||
@@ -51,7 +51,10 @@
|
|||||||
'index': loop.index0,
|
'index': loop.index0,
|
||||||
'participantNumber': loop.index,
|
'participantNumber': loop.index,
|
||||||
'mode': 'create',
|
'mode': 'create',
|
||||||
'isSubmitted': bookingCreateContext.isSubmitted
|
'isSubmitted': bookingCreateContext.isSubmitted,
|
||||||
|
'hintLabel': loop.index0 == 0 and bookingCreateContext.familyInsuranceUpgradeAvailable
|
||||||
|
? 'Familienversicherung möglich - jetzt wechseln'
|
||||||
|
: null
|
||||||
} %}
|
} %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Form\Model\BookingDto;
|
|||||||
use App\Form\Model\ParticipantDto;
|
use App\Form\Model\ParticipantDto;
|
||||||
use App\Form\Service\ParticipantInsuranceFieldHandler;
|
use App\Form\Service\ParticipantInsuranceFieldHandler;
|
||||||
use App\Service\BookingPriceCalculator;
|
use App\Service\BookingPriceCalculator;
|
||||||
|
use App\Service\FamilyInsuranceAvailabilityChecker;
|
||||||
use App\Service\InsuranceManager;
|
use App\Service\InsuranceManager;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
@@ -34,7 +35,13 @@ class ParticipantInsuranceFieldHandlerTest extends TestCase
|
|||||||
$this->priceCalculatorService->method('resolveInsuranceTravelPrice')
|
$this->priceCalculatorService->method('resolveInsuranceTravelPrice')
|
||||||
->willReturn(500.0);
|
->willReturn(500.0);
|
||||||
|
|
||||||
$this->handler = new ParticipantInsuranceFieldHandler($this->insuranceService, $this->priceCalculatorService);
|
// Real checker over the mocked collaborators - the selection-time recording below is
|
||||||
|
// only meaningful against the actual "is a family insurance available" definition
|
||||||
|
$this->handler = new ParticipantInsuranceFieldHandler(
|
||||||
|
$this->insuranceService,
|
||||||
|
$this->priceCalculatorService,
|
||||||
|
new FamilyInsuranceAvailabilityChecker($this->insuranceService, $this->priceCalculatorService),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetFieldName(): void
|
public function testGetFieldName(): void
|
||||||
@@ -279,21 +286,6 @@ class ParticipantInsuranceFieldHandlerTest extends TestCase
|
|||||||
$this->assertNull($participant->insurance);
|
$this->assertNull($participant->insurance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testProcessFieldNotifiesApplicantWhenFamilyInsuranceNewlyAvailable(): void
|
|
||||||
{
|
|
||||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
|
||||||
$applicant = $bookingDto->getParticipant(0);
|
|
||||||
|
|
||||||
$this->priceCalculatorService->method('calculateTotalBookingPriceExcludingInsurance')->willReturn(500.0);
|
|
||||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([$this->createInsurance('2', 'Reise-Rücktritt Familie')]);
|
|
||||||
|
|
||||||
// Dependent's own card is submitted (e.g. just their date of birth) - no insurance selection
|
|
||||||
$this->handler->processField([], $bookingDto, 1);
|
|
||||||
|
|
||||||
$this->assertNotEmpty($applicant->notifications, 'Applicant should be notified that family insurance is now available');
|
|
||||||
$this->assertTrue($bookingDto->familyInsuranceHintShown);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testProcessFieldRecordsIneligibilityWhenApplicantSelectsInsuranceBeforeFamilyEligible(): void
|
public function testProcessFieldRecordsIneligibilityWhenApplicantSelectsInsuranceBeforeFamilyEligible(): void
|
||||||
{
|
{
|
||||||
$nonFamilyInsurance = $this->createInsurance('1');
|
$nonFamilyInsurance = $this->createInsurance('1');
|
||||||
@@ -405,124 +397,6 @@ class ParticipantInsuranceFieldHandlerTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testProcessFieldDoesNotNotifyWhenApplicantSwitchesToFamilyInsuranceInSameSubmission(): void
|
|
||||||
{
|
|
||||||
$familyInsurance = $this->createInsurance('2');
|
|
||||||
$familyInsurance->familyInsurance = true;
|
|
||||||
|
|
||||||
$nonFamilyInsurance = $this->createInsurance('1');
|
|
||||||
$nonFamilyInsurance->familyInsurance = false;
|
|
||||||
|
|
||||||
$travel = new Travel();
|
|
||||||
$travel->dateFrom = new \DateTimeImmutable('2025-08-01');
|
|
||||||
$travel->dateTo = new \DateTimeImmutable('2025-08-08');
|
|
||||||
$travel->insurances = [$nonFamilyInsurance, $familyInsurance];
|
|
||||||
|
|
||||||
$applicant = new ParticipantDto();
|
|
||||||
$applicant->index = 0;
|
|
||||||
$applicant->dateOfBirth = new \DateTimeImmutable('1990-06-15');
|
|
||||||
$applicant->insurance = $nonFamilyInsurance;
|
|
||||||
|
|
||||||
$child = new ParticipantDto();
|
|
||||||
$child->index = 1;
|
|
||||||
$child->dateOfBirth = new \DateTimeImmutable('2015-01-01');
|
|
||||||
|
|
||||||
$bookingDto = new BookingDto($travel, 1);
|
|
||||||
$bookingDto->participants = [$applicant, $child];
|
|
||||||
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = true;
|
|
||||||
|
|
||||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([$familyInsurance]);
|
|
||||||
|
|
||||||
// Applicant's own submission switches them to family insurance in this same request
|
|
||||||
$this->handler->processField(['insurance' => '2'], $bookingDto, 0);
|
|
||||||
|
|
||||||
$this->assertSame($familyInsurance, $applicant->insurance);
|
|
||||||
$this->assertEmpty($applicant->notifications, 'Must not show a stale "please recheck" hint for a choice this submission already made');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testProcessFieldDoesNotNotifyWhenHintAlreadyShown(): void
|
|
||||||
{
|
|
||||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
|
||||||
$bookingDto->familyInsuranceHintShown = true;
|
|
||||||
$applicant = $bookingDto->getParticipant(0);
|
|
||||||
|
|
||||||
$this->priceCalculatorService->method('calculateTotalBookingPriceExcludingInsurance')->willReturn(500.0);
|
|
||||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([$this->createInsurance('2')]);
|
|
||||||
|
|
||||||
$this->handler->processField([], $bookingDto, 1);
|
|
||||||
|
|
||||||
$this->assertEmpty($applicant->notifications, 'Hint must not repeat once already shown');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testProcessFieldDoesNotNotifyWhenApplicantHasNoInsurance(): void
|
|
||||||
{
|
|
||||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
|
||||||
$applicant = $bookingDto->getParticipant(0);
|
|
||||||
$applicant->insurance = null;
|
|
||||||
|
|
||||||
$this->handler->processField([], $bookingDto, 1);
|
|
||||||
|
|
||||||
$this->assertEmpty($applicant->notifications, 'Nothing to alert about when applicant has not chosen insurance yet');
|
|
||||||
$this->assertFalse($bookingDto->familyInsuranceHintShown);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testProcessFieldDoesNotNotifyWhenApplicantAlreadyHasFamilyInsurance(): void
|
|
||||||
{
|
|
||||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
|
||||||
$applicant = $bookingDto->getParticipant(0);
|
|
||||||
$familyInsurance = $this->createInsurance('2');
|
|
||||||
$familyInsurance->familyInsurance = true;
|
|
||||||
$applicant->insurance = $familyInsurance;
|
|
||||||
|
|
||||||
$this->handler->processField([], $bookingDto, 1);
|
|
||||||
|
|
||||||
$this->assertEmpty($applicant->notifications, 'Nothing to alert about when applicant already has family insurance');
|
|
||||||
$this->assertFalse($bookingDto->familyInsuranceHintShown);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testProcessFieldDoesNotNotifyWhenNoEligibleFamilyInsuranceExists(): void
|
|
||||||
{
|
|
||||||
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
|
||||||
$applicant = $bookingDto->getParticipant(0);
|
|
||||||
|
|
||||||
$this->priceCalculatorService->method('calculateTotalBookingPriceExcludingInsurance')->willReturn(500.0);
|
|
||||||
$this->insuranceService->method('getEligibleInsurances')->willReturn([]); // No eligible family insurance
|
|
||||||
|
|
||||||
$this->handler->processField([], $bookingDto, 1);
|
|
||||||
|
|
||||||
$this->assertEmpty($applicant->notifications, 'Nothing to alert about when no family insurance is actually eligible');
|
|
||||||
$this->assertFalse($bookingDto->familyInsuranceHintShown);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createFamilyBookingDtoWithNonFamilyApplicantInsurance(): BookingDto
|
|
||||||
{
|
|
||||||
$nonFamilyInsurance = $this->createInsurance('1');
|
|
||||||
$nonFamilyInsurance->familyInsurance = false;
|
|
||||||
|
|
||||||
$familyInsurance = $this->createInsurance('2');
|
|
||||||
$familyInsurance->familyInsurance = true;
|
|
||||||
|
|
||||||
$travel = new Travel();
|
|
||||||
$travel->dateFrom = new \DateTimeImmutable('2025-08-01');
|
|
||||||
$travel->dateTo = new \DateTimeImmutable('2025-08-08');
|
|
||||||
$travel->insurances = [$nonFamilyInsurance, $familyInsurance];
|
|
||||||
|
|
||||||
$applicant = new ParticipantDto();
|
|
||||||
$applicant->index = 0;
|
|
||||||
$applicant->dateOfBirth = new \DateTimeImmutable('1990-06-15');
|
|
||||||
$applicant->insurance = $nonFamilyInsurance;
|
|
||||||
|
|
||||||
$child = new ParticipantDto();
|
|
||||||
$child->index = 1;
|
|
||||||
$child->dateOfBirth = new \DateTimeImmutable('2015-01-01');
|
|
||||||
|
|
||||||
$bookingDto = new BookingDto($travel, 1);
|
|
||||||
$bookingDto->participants = [$applicant, $child];
|
|
||||||
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = true;
|
|
||||||
|
|
||||||
return $bookingDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetFieldStateModificationsReturnsEmptyArray(): void
|
public function testGetFieldStateModificationsReturnsEmptyArray(): void
|
||||||
{
|
{
|
||||||
$bookingDto = $this->createMockBookingDto();
|
$bookingDto = $this->createMockBookingDto();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use App\Form\Model\ParticipantCardPriceDto;
|
|||||||
use App\Service\BookingCreateContextFactory;
|
use App\Service\BookingCreateContextFactory;
|
||||||
use App\Service\BookingPriceCalculator;
|
use App\Service\BookingPriceCalculator;
|
||||||
use App\Service\BookingSummaryAssembler;
|
use App\Service\BookingSummaryAssembler;
|
||||||
|
use App\Service\FamilyInsuranceAvailabilityChecker;
|
||||||
use App\Service\ParticipantCardAssembler;
|
use App\Service\ParticipantCardAssembler;
|
||||||
use App\Service\RoomPricingCalculator;
|
use App\Service\RoomPricingCalculator;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -62,10 +63,14 @@ class BookingCreateContextFactoryTest extends TestCase
|
|||||||
$priceCalculator->expects($this->never())
|
$priceCalculator->expects($this->never())
|
||||||
->method('calculateAllParticipantIndividualPrices');
|
->method('calculateAllParticipantIndividualPrices');
|
||||||
|
|
||||||
|
$familyInsuranceService = $this->createMock(FamilyInsuranceAvailabilityChecker::class);
|
||||||
|
$familyInsuranceService->method('isFamilyInsuranceUpgradeAvailable')->willReturn(true);
|
||||||
|
|
||||||
$service = new BookingCreateContextFactory(
|
$service = new BookingCreateContextFactory(
|
||||||
$participantCardDataService,
|
$participantCardDataService,
|
||||||
$summaryDataService,
|
$summaryDataService,
|
||||||
$priceCalculator,
|
$priceCalculator,
|
||||||
|
$familyInsuranceService,
|
||||||
);
|
);
|
||||||
|
|
||||||
$context = $service->create($bookingDto, RoomPricingCalculator::PRICING_MODE_SELECTION);
|
$context = $service->create($bookingDto, RoomPricingCalculator::PRICING_MODE_SELECTION);
|
||||||
@@ -73,6 +78,7 @@ class BookingCreateContextFactoryTest extends TestCase
|
|||||||
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
||||||
$this->assertSame($bookingDto, $context->bookingDto);
|
$this->assertSame($bookingDto, $context->bookingDto);
|
||||||
$this->assertSame($summaryData, $context->summaryData);
|
$this->assertSame($summaryData, $context->summaryData);
|
||||||
|
$this->assertTrue($context->familyInsuranceUpgradeAvailable);
|
||||||
$this->assertNull($context->cardsData);
|
$this->assertNull($context->cardsData);
|
||||||
$this->assertFalse($context->isSubmitted);
|
$this->assertFalse($context->isSubmitted);
|
||||||
$this->assertSame([11 => $roomByPax], $context->groupedRooms->byPax);
|
$this->assertSame([11 => $roomByPax], $context->groupedRooms->byPax);
|
||||||
@@ -120,10 +126,14 @@ class BookingCreateContextFactoryTest extends TestCase
|
|||||||
$priceCalculator->expects($this->never())
|
$priceCalculator->expects($this->never())
|
||||||
->method('calculateAllParticipantIndividualPrices');
|
->method('calculateAllParticipantIndividualPrices');
|
||||||
|
|
||||||
|
$familyInsuranceService = $this->createMock(FamilyInsuranceAvailabilityChecker::class);
|
||||||
|
$familyInsuranceService->method('isFamilyInsuranceUpgradeAvailable')->willReturn(true);
|
||||||
|
|
||||||
$service = new BookingCreateContextFactory(
|
$service = new BookingCreateContextFactory(
|
||||||
$participantCardDataService,
|
$participantCardDataService,
|
||||||
$summaryDataService,
|
$summaryDataService,
|
||||||
$priceCalculator,
|
$priceCalculator,
|
||||||
|
$familyInsuranceService,
|
||||||
);
|
);
|
||||||
|
|
||||||
$context = $service->createWithParticipantCards($bookingDto, true);
|
$context = $service->createWithParticipantCards($bookingDto, true);
|
||||||
@@ -131,6 +141,7 @@ class BookingCreateContextFactoryTest extends TestCase
|
|||||||
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
||||||
$this->assertSame([$cardData], $context->cardsData);
|
$this->assertSame([$cardData], $context->cardsData);
|
||||||
$this->assertTrue($context->isSubmitted);
|
$this->assertTrue($context->isSubmitted);
|
||||||
|
$this->assertTrue($context->familyInsuranceUpgradeAvailable);
|
||||||
$this->assertSame([], $context->groupedRooms->byPax);
|
$this->assertSame([], $context->groupedRooms->byPax);
|
||||||
$this->assertSame([10 => $room], $context->groupedRooms->byRoom);
|
$this->assertSame([10 => $room], $context->groupedRooms->byRoom);
|
||||||
}
|
}
|
||||||
@@ -169,16 +180,21 @@ class BookingCreateContextFactoryTest extends TestCase
|
|||||||
->with($bookingDto)
|
->with($bookingDto)
|
||||||
->willReturn([123.45]);
|
->willReturn([123.45]);
|
||||||
|
|
||||||
|
$familyInsuranceService = $this->createMock(FamilyInsuranceAvailabilityChecker::class);
|
||||||
|
$familyInsuranceService->method('isFamilyInsuranceUpgradeAvailable')->willReturn(true);
|
||||||
|
|
||||||
$service = new BookingCreateContextFactory(
|
$service = new BookingCreateContextFactory(
|
||||||
$participantCardDataService,
|
$participantCardDataService,
|
||||||
$summaryDataService,
|
$summaryDataService,
|
||||||
$priceCalculator,
|
$priceCalculator,
|
||||||
|
$familyInsuranceService,
|
||||||
);
|
);
|
||||||
|
|
||||||
$context = $service->createWithParticipantPrices($bookingDto);
|
$context = $service->createWithParticipantPrices($bookingDto);
|
||||||
|
|
||||||
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
$this->assertInstanceOf(BookingCreateContext::class, $context);
|
||||||
$this->assertSame([123.45], $context->participantPrices);
|
$this->assertSame([123.45], $context->participantPrices);
|
||||||
|
$this->assertTrue($context->familyInsuranceUpgradeAvailable);
|
||||||
$this->assertNull($context->cardsData);
|
$this->assertNull($context->cardsData);
|
||||||
$this->assertSame([10 => $room], $context->groupedRooms->byRoom);
|
$this->assertSame([10 => $room], $context->groupedRooms->byRoom);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Service;
|
||||||
|
|
||||||
|
use App\BusProNet\Model\Insurance;
|
||||||
|
use App\BusProNet\Model\Travel;
|
||||||
|
use App\Form\Model\BookingDto;
|
||||||
|
use App\Form\Model\ParticipantDto;
|
||||||
|
use App\Service\BookingPriceCalculator;
|
||||||
|
use App\Service\FamilyInsuranceAvailabilityChecker;
|
||||||
|
use App\Service\InsuranceManager;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class FamilyInsuranceAvailabilityCheckerTest extends TestCase
|
||||||
|
{
|
||||||
|
private FamilyInsuranceAvailabilityChecker $checker;
|
||||||
|
private InsuranceManager $insuranceService;
|
||||||
|
private BookingPriceCalculator $priceCalculatorService;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->insuranceService = $this->createMock(InsuranceManager::class);
|
||||||
|
$this->priceCalculatorService = $this->createMock(BookingPriceCalculator::class);
|
||||||
|
|
||||||
|
$this->insuranceService->method('getSelectableInsurances')
|
||||||
|
->willReturnCallback(fn (Travel $travel) => $travel->insurances ?? []);
|
||||||
|
|
||||||
|
$this->priceCalculatorService->method('calculateTotalBookingPriceExcludingInsurance')
|
||||||
|
->willReturn(500.0);
|
||||||
|
|
||||||
|
$this->checker = new FamilyInsuranceAvailabilityChecker($this->insuranceService, $this->priceCalculatorService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpgradeIsAvailableWhenFamilyBecameEligibleAfterTheApplicantChose(): void
|
||||||
|
{
|
||||||
|
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||||
|
|
||||||
|
$this->allowAllInsurancesAsEligible();
|
||||||
|
|
||||||
|
$this->assertTrue($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpgradeIsNotAvailableWhenTheApplicantChoseWhileFamilyWasAlreadyEligible(): void
|
||||||
|
{
|
||||||
|
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||||
|
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = false;
|
||||||
|
|
||||||
|
$this->allowAllInsurancesAsEligible();
|
||||||
|
|
||||||
|
$this->assertFalse(
|
||||||
|
$this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto),
|
||||||
|
'An applicant who was offered family insurance and picked something else must not be nagged'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpgradeIsNotAvailableWhenApplicantHasNoInsuranceYet(): void
|
||||||
|
{
|
||||||
|
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||||
|
$bookingDto->getParticipant(0)->insurance = null;
|
||||||
|
|
||||||
|
$this->allowAllInsurancesAsEligible();
|
||||||
|
|
||||||
|
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpgradeIsNotAvailableWhenApplicantAlreadyHasFamilyInsurance(): void
|
||||||
|
{
|
||||||
|
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||||
|
$familyInsurance = $this->createInsurance('2');
|
||||||
|
$familyInsurance->familyInsurance = true;
|
||||||
|
$bookingDto->getParticipant(0)->insurance = $familyInsurance;
|
||||||
|
|
||||||
|
$this->allowAllInsurancesAsEligible();
|
||||||
|
|
||||||
|
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpgradeIsNotAvailableWhenNoFamilyInsuranceProductExists(): void
|
||||||
|
{
|
||||||
|
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||||
|
$bookingDto->travel->insurances = array_values(array_filter(
|
||||||
|
$bookingDto->travel->insurances,
|
||||||
|
static fn (Insurance $i) => false === $i->familyInsurance
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->allowAllInsurancesAsEligible();
|
||||||
|
|
||||||
|
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpgradeIsNotAvailableWhenNoFamilyPriceTierMatches(): void
|
||||||
|
{
|
||||||
|
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||||
|
|
||||||
|
// Family composition qualifies, but no family insurance matches the total price
|
||||||
|
$this->insuranceService->method('getEligibleInsurances')->willReturn([]);
|
||||||
|
|
||||||
|
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpgradeIsNotAvailableWhenTheBookingIsNotAFamilyBooking(): void
|
||||||
|
{
|
||||||
|
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||||
|
// Dependent is an adult - no children left, so the constellation no longer qualifies
|
||||||
|
$bookingDto->getParticipant(1)->dateOfBirth = new \DateTimeImmutable('1992-01-01');
|
||||||
|
|
||||||
|
$this->allowAllInsurancesAsEligible();
|
||||||
|
|
||||||
|
$this->assertFalse($this->checker->isFamilyInsuranceUpgradeAvailable($bookingDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasEligibleFamilyInsuranceIgnoresTheSelectionTimeRecording(): void
|
||||||
|
{
|
||||||
|
// The raw availability predicate is what the field handler records against, so it
|
||||||
|
// must not itself depend on the flag it is used to compute
|
||||||
|
$bookingDto = $this->createFamilyBookingDtoWithNonFamilyApplicantInsurance();
|
||||||
|
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = false;
|
||||||
|
|
||||||
|
$this->allowAllInsurancesAsEligible();
|
||||||
|
|
||||||
|
$this->assertTrue($this->checker->hasEligibleFamilyInsurance($bookingDto));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function allowAllInsurancesAsEligible(): void
|
||||||
|
{
|
||||||
|
$this->insuranceService->method('getEligibleInsurances')->willReturnCallback(
|
||||||
|
fn (array $insurances) => $insurances
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createFamilyBookingDtoWithNonFamilyApplicantInsurance(): BookingDto
|
||||||
|
{
|
||||||
|
$nonFamilyInsurance = $this->createInsurance('1');
|
||||||
|
$nonFamilyInsurance->familyInsurance = false;
|
||||||
|
|
||||||
|
$familyInsurance = $this->createInsurance('2', 'Reise-Rücktritt Familie');
|
||||||
|
$familyInsurance->familyInsurance = true;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$travel->dateFrom = new \DateTimeImmutable('2025-08-01');
|
||||||
|
$travel->dateTo = new \DateTimeImmutable('2025-08-08');
|
||||||
|
$travel->insurances = [$nonFamilyInsurance, $familyInsurance];
|
||||||
|
|
||||||
|
$applicant = new ParticipantDto();
|
||||||
|
$applicant->index = 0;
|
||||||
|
$applicant->dateOfBirth = new \DateTimeImmutable('1990-06-15');
|
||||||
|
$applicant->insurance = $nonFamilyInsurance;
|
||||||
|
|
||||||
|
$child = new ParticipantDto();
|
||||||
|
$child->index = 1;
|
||||||
|
$child->dateOfBirth = new \DateTimeImmutable('2015-01-01');
|
||||||
|
|
||||||
|
$bookingDto = new BookingDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$applicant, $child];
|
||||||
|
$bookingDto->applicantInsuranceChosenWhileFamilyIneligible = true;
|
||||||
|
|
||||||
|
return $bookingDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createInsurance(string|int $id, string $label = 'Test Insurance'): Insurance
|
||||||
|
{
|
||||||
|
$insurance = new Insurance();
|
||||||
|
$insurance->id = (string) $id;
|
||||||
|
$insurance->label = $label;
|
||||||
|
|
||||||
|
return $insurance;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user