fix: reconcile assigned family insurance rate on updated participant
This commit is contained in:
@@ -850,7 +850,13 @@ class BookingDataProcessorTest extends TestCase
|
||||
$bookingDto->participants = [$applicant, $dependent];
|
||||
|
||||
$insuranceService = $this->createMock(InsuranceManager::class);
|
||||
$insuranceService->method('getSelectableInsurances')->willReturn([]);
|
||||
// Both insurances must already look eligible, otherwise the submit-time
|
||||
// reconciliation step (added for stale-insurance re-validation) would clear
|
||||
// them before propagation even runs - this test is only about propagation.
|
||||
$insuranceService->method('getSelectableInsurances')->willReturn([$familyInsurance, $dependentInsurance]);
|
||||
$insuranceService->method('getEligibleInsurances')->willReturnCallback(
|
||||
fn (array $insurances) => $insurances
|
||||
);
|
||||
$insuranceService->method('batchAssignInsuranceToParticipants')->willReturn([
|
||||
0 => $familyInsurance,
|
||||
1 => null,
|
||||
@@ -858,6 +864,7 @@ class BookingDataProcessorTest extends TestCase
|
||||
|
||||
$priceCalculatorService = $this->createMock(BookingPriceCalculator::class);
|
||||
$priceCalculatorService->method('calculateIndividualParticipantPriceExcludingInsurance')->willReturn(0.0);
|
||||
$priceCalculatorService->method('resolveInsuranceTravelPrice')->willReturn(0.0);
|
||||
|
||||
$payloadBuilder = $this->createMock(BookingPayloadBuilder::class);
|
||||
$payloadBuilder->method('buildCreatePayload')->willReturn([]);
|
||||
@@ -925,4 +932,148 @@ class BookingDataProcessorTest extends TestCase
|
||||
$this->assertSame($familyInsurance, $formData->participants[0]->insurance);
|
||||
$this->assertNull($formData->participants[1]->insurance, 'Dependent must lose their pre-existing individual insurance in edit mode once the applicant selects family insurance');
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression: an insurance selected earlier can go stale by submission time (e.g. its
|
||||
* price tier no longer matches the current total, because the user added services on an
|
||||
* earlier step after choosing insurance and jumped straight to submission without
|
||||
* revisiting the insurance field). createBookingRequestPayload() must re-validate and
|
||||
* reassign to the correct tier right before the payload is built, so a stale ID never
|
||||
* reaches the BPN API.
|
||||
*/
|
||||
public function testCreateBookingRequestPayloadReassignsStaleInsuranceToCurrentPriceTier(): void
|
||||
{
|
||||
$staleInsurance = new Insurance();
|
||||
$staleInsurance->id = 'tier-low';
|
||||
$staleInsurance->familyInsurance = false;
|
||||
|
||||
$correctTierInsurance = new Insurance();
|
||||
$correctTierInsurance->id = 'tier-high';
|
||||
$correctTierInsurance->familyInsurance = false;
|
||||
|
||||
$applicant = $this->createMockParticipantDto(0, 'F');
|
||||
$applicant->insurance = $staleInsurance;
|
||||
|
||||
$bookingDto = new BookingDto($this->createMockTravel(), 1);
|
||||
$bookingDto->participants = [$applicant];
|
||||
|
||||
$insuranceService = $this->createMock(InsuranceManager::class);
|
||||
$insuranceService->method('getSelectableInsurances')->willReturn([$staleInsurance, $correctTierInsurance]);
|
||||
// Only the higher tier is eligible at the current (updated) price.
|
||||
$insuranceService->method('getEligibleInsurances')->willReturn([$correctTierInsurance]);
|
||||
$insuranceService->method('reassignInsuranceForPriceChange')->willReturn($correctTierInsurance);
|
||||
$insuranceService->method('batchAssignInsuranceToParticipants')->willReturn([0 => $correctTierInsurance]);
|
||||
|
||||
$priceCalculatorService = $this->createMock(BookingPriceCalculator::class);
|
||||
$priceCalculatorService->method('calculateIndividualParticipantPriceExcludingInsurance')->willReturn(0.0);
|
||||
$priceCalculatorService->method('resolveInsuranceTravelPrice')->willReturn(2600.0);
|
||||
|
||||
$payloadBuilder = $this->createMock(BookingPayloadBuilder::class);
|
||||
$payloadBuilder->method('buildCreatePayload')->willReturn([]);
|
||||
|
||||
$processor = new BookingDataProcessor(
|
||||
$insuranceService,
|
||||
$priceCalculatorService,
|
||||
new ServiceMappingCollector(),
|
||||
new ParticipantServiceProcessor(new NullLogger()),
|
||||
$payloadBuilder,
|
||||
new PersonalDataSynchronizer(),
|
||||
);
|
||||
|
||||
$processor->createBookingRequestPayload($bookingDto, 'ANFRAGE');
|
||||
|
||||
$this->assertSame($correctTierInsurance, $applicant->insurance);
|
||||
$this->assertCount(1, $applicant->notifications);
|
||||
$this->assertSame('warning', array_values($applicant->notifications)[0]['type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression: when a stale insurance has no eligible replacement at all (e.g. the total
|
||||
* price now falls outside every tier of that type), it must be cleared rather than
|
||||
* silently left in place and submitted anyway.
|
||||
*/
|
||||
public function testCreateBookingRequestPayloadClearsStaleInsuranceWithoutEligibleReplacement(): void
|
||||
{
|
||||
$staleInsurance = new Insurance();
|
||||
$staleInsurance->id = 'tier-low';
|
||||
$staleInsurance->familyInsurance = false;
|
||||
|
||||
$applicant = $this->createMockParticipantDto(0, 'F');
|
||||
$applicant->insurance = $staleInsurance;
|
||||
|
||||
$bookingDto = new BookingDto($this->createMockTravel(), 1);
|
||||
$bookingDto->participants = [$applicant];
|
||||
|
||||
$insuranceService = $this->createMock(InsuranceManager::class);
|
||||
$insuranceService->method('getSelectableInsurances')->willReturn([$staleInsurance]);
|
||||
$insuranceService->method('getEligibleInsurances')->willReturn([]);
|
||||
$insuranceService->method('reassignInsuranceForPriceChange')->willReturn(null);
|
||||
$insuranceService->method('batchAssignInsuranceToParticipants')->willReturn([0 => null]);
|
||||
|
||||
$priceCalculatorService = $this->createMock(BookingPriceCalculator::class);
|
||||
$priceCalculatorService->method('calculateIndividualParticipantPriceExcludingInsurance')->willReturn(0.0);
|
||||
$priceCalculatorService->method('resolveInsuranceTravelPrice')->willReturn(9999.0);
|
||||
|
||||
$payloadBuilder = $this->createMock(BookingPayloadBuilder::class);
|
||||
$payloadBuilder->method('buildCreatePayload')->willReturn([]);
|
||||
|
||||
$processor = new BookingDataProcessor(
|
||||
$insuranceService,
|
||||
$priceCalculatorService,
|
||||
new ServiceMappingCollector(),
|
||||
new ParticipantServiceProcessor(new NullLogger()),
|
||||
$payloadBuilder,
|
||||
new PersonalDataSynchronizer(),
|
||||
);
|
||||
|
||||
$processor->createBookingRequestPayload($bookingDto, 'ANFRAGE');
|
||||
|
||||
$this->assertNull($applicant->insurance);
|
||||
$this->assertCount(1, $applicant->notifications);
|
||||
$this->assertSame('warning', array_values($applicant->notifications)[0]['type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression: an insurance that's still eligible at submission time must be left
|
||||
* untouched - the reconciliation step must not fire (or notify) on the common,
|
||||
* unremarkable case where nothing has gone stale.
|
||||
*/
|
||||
public function testCreateBookingRequestPayloadLeavesStillEligibleInsuranceUntouched(): void
|
||||
{
|
||||
$insurance = new Insurance();
|
||||
$insurance->id = 'tier-1';
|
||||
$insurance->familyInsurance = false;
|
||||
|
||||
$applicant = $this->createMockParticipantDto(0, 'F');
|
||||
$applicant->insurance = $insurance;
|
||||
|
||||
$bookingDto = new BookingDto($this->createMockTravel(), 1);
|
||||
$bookingDto->participants = [$applicant];
|
||||
|
||||
$insuranceService = $this->createMock(InsuranceManager::class);
|
||||
$insuranceService->method('getSelectableInsurances')->willReturn([$insurance]);
|
||||
$insuranceService->method('getEligibleInsurances')->willReturn([$insurance]);
|
||||
$insuranceService->method('batchAssignInsuranceToParticipants')->willReturn([0 => $insurance]);
|
||||
|
||||
$priceCalculatorService = $this->createMock(BookingPriceCalculator::class);
|
||||
$priceCalculatorService->method('calculateIndividualParticipantPriceExcludingInsurance')->willReturn(0.0);
|
||||
$priceCalculatorService->method('resolveInsuranceTravelPrice')->willReturn(500.0);
|
||||
|
||||
$payloadBuilder = $this->createMock(BookingPayloadBuilder::class);
|
||||
$payloadBuilder->method('buildCreatePayload')->willReturn([]);
|
||||
|
||||
$processor = new BookingDataProcessor(
|
||||
$insuranceService,
|
||||
$priceCalculatorService,
|
||||
new ServiceMappingCollector(),
|
||||
new ParticipantServiceProcessor(new NullLogger()),
|
||||
$payloadBuilder,
|
||||
new PersonalDataSynchronizer(),
|
||||
);
|
||||
|
||||
$processor->createBookingRequestPayload($bookingDto, 'ANFRAGE');
|
||||
|
||||
$this->assertSame($insurance, $applicant->insurance);
|
||||
$this->assertCount(0, $applicant->notifications);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user