Compare commits
2
Commits
master
...
75e4d46996
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75e4d46996 | ||
|
|
a73e8a593f |
No files matched your search
Generated
+6
-6
@@ -13028,16 +13028,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "rector/rector",
|
"name": "rector/rector",
|
||||||
"version": "2.5.6",
|
"version": "2.5.7",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/rectorphp/rector.git",
|
"url": "https://github.com/rectorphp/rector.git",
|
||||||
"reference": "c329e210d17f86d70424f2688e1294495f44c670"
|
"reference": "ba22f8c087848278fed6b4910d4cf1108096d8d3"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/rectorphp/rector/zipball/c329e210d17f86d70424f2688e1294495f44c670",
|
"url": "https://api.github.com/repos/rectorphp/rector/zipball/ba22f8c087848278fed6b4910d4cf1108096d8d3",
|
||||||
"reference": "c329e210d17f86d70424f2688e1294495f44c670",
|
"reference": "ba22f8c087848278fed6b4910d4cf1108096d8d3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -13076,7 +13076,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/rectorphp/rector/issues",
|
"issues": "https://github.com/rectorphp/rector/issues",
|
||||||
"source": "https://github.com/rectorphp/rector/tree/2.5.6"
|
"source": "https://github.com/rectorphp/rector/tree/2.5.7"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -13084,7 +13084,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-07-12T10:21:06+00:00"
|
"time": "2026-07-13T15:24:18+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/cli-parser",
|
"name": "sebastian/cli-parser",
|
||||||
|
|||||||
@@ -409,10 +409,11 @@ class BookingDataProcessor
|
|||||||
$participantPrices
|
$participantPrices
|
||||||
);
|
);
|
||||||
|
|
||||||
// Apply assignments to dependent participants (skip applicant at index 0)
|
// 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) {
|
foreach ($assignments as $index => $insurance) {
|
||||||
if (0 === $index) {
|
if (0 === $index && false === $assignments[0]?->familyInsurance) {
|
||||||
continue; // Skip applicant
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$participant = $bookingDto->getParticipant($index);
|
$participant = $bookingDto->getParticipant($index);
|
||||||
|
|||||||
@@ -136,8 +136,9 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
// Get selectable (non-complementary) insurances with caching
|
// Get selectable (non-complementary) insurances with caching
|
||||||
$selectableInsurances = $this->insuranceService->getSelectableInsurances($bookingDto->travel);
|
$selectableInsurances = $this->insuranceService->getSelectableInsurances($bookingDto->travel);
|
||||||
|
|
||||||
// Calculate travel price for eligibility checks
|
// Calculate individual travel price; family insurance on the applicant uses total booking price instead
|
||||||
$travelPrice = $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $participantIndex);
|
$isApplicant = 0 === $participantIndex;
|
||||||
|
$individualPrice = $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $participantIndex);
|
||||||
|
|
||||||
// Determine if this is a new user selection or just form resubmission
|
// Determine if this is a new user selection or just form resubmission
|
||||||
$isNewSelection = null !== $selectedInsuranceId
|
$isNewSelection = null !== $selectedInsuranceId
|
||||||
@@ -161,6 +162,8 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
$selectedInsurance = $this->findInsuranceById($selectableInsurances, $selectedInsuranceId);
|
$selectedInsurance = $this->findInsuranceById($selectableInsurances, $selectedInsuranceId);
|
||||||
|
|
||||||
if (null !== $selectedInsurance) {
|
if (null !== $selectedInsurance) {
|
||||||
|
$travelPrice = $this->resolveTravelPrice($isApplicant, $selectedInsurance, $individualPrice, $bookingDto);
|
||||||
|
|
||||||
$eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice);
|
$eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice);
|
||||||
$isSelectedInsuranceEligible = $this->isInsuranceInList($selectedInsurance, $eligibleInsurances);
|
$isSelectedInsuranceEligible = $this->isInsuranceInList($selectedInsurance, $eligibleInsurances);
|
||||||
|
|
||||||
@@ -198,6 +201,8 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$travelPrice = $this->resolveTravelPrice($isApplicant, $currentInsurance, $individualPrice, $bookingDto);
|
||||||
|
|
||||||
// Check if current insurance is still eligible with updated participant data
|
// Check if current insurance is still eligible with updated participant data
|
||||||
$eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice);
|
$eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice);
|
||||||
$isCurrentInsuranceStillEligible = $this->isInsuranceInList($currentInsurance, $eligibleInsurances);
|
$isCurrentInsuranceStillEligible = $this->isInsuranceInList($currentInsurance, $eligibleInsurances);
|
||||||
@@ -259,6 +264,16 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
private function resolveTravelPrice(bool $isApplicant, Insurance $insurance, float $individualPrice, BookingDto $bookingDto): float
|
||||||
|
{
|
||||||
|
if ($isApplicant && true === $insurance->familyInsurance) {
|
||||||
|
return $this->priceCalculatorService->calculateTotalBookingPriceExcludingInsurance($bookingDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $individualPrice;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds an insurance by ID from the available insurances array.
|
* Finds an insurance by ID from the available insurances array.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -76,6 +76,22 @@ class BookingPriceCalculator
|
|||||||
return $this->participantPricingCalculator->calculateAllParticipantIndividualPrices($bookingDto);
|
return $this->participantPricingCalculator->calculateAllParticipantIndividualPrices($bookingDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates total booking price across all participants, excluding insurance.
|
||||||
|
*
|
||||||
|
* Used for family insurance price-tier determination, where the tier is based on
|
||||||
|
* the combined trip cost rather than any single participant's price.
|
||||||
|
*/
|
||||||
|
public function calculateTotalBookingPriceExcludingInsurance(BookingDto $bookingDto): float
|
||||||
|
{
|
||||||
|
$total = 0.0;
|
||||||
|
foreach ($bookingDto->getParticipants() as $index => $participant) {
|
||||||
|
$total += $this->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the total price for an individual participant excluding insurance.
|
* Calculates the total price for an individual participant excluding insurance.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -159,12 +159,28 @@ class InsuranceManager
|
|||||||
BookingDto $booking,
|
BookingDto $booking,
|
||||||
array $participantPrices,
|
array $participantPrices,
|
||||||
): array {
|
): array {
|
||||||
$assignments = [];
|
|
||||||
|
|
||||||
// Group insurances of the same type
|
// Group insurances of the same type
|
||||||
$sameTypeInsurances = $this->filterByType($availableInsurances, $selectedInsurance);
|
$sameTypeInsurances = $this->filterByType($availableInsurances, $selectedInsurance);
|
||||||
|
|
||||||
// Assign appropriate insurance to each participant
|
// Family insurance: assign to applicant only, priced by total booking price
|
||||||
|
if (true === $selectedInsurance->familyInsurance) {
|
||||||
|
$totalPrice = array_sum($participantPrices);
|
||||||
|
$applicant = $booking->getParticipant(0);
|
||||||
|
$eligibleInsurances = null !== $applicant
|
||||||
|
? $this->getEligibleInsurances($sameTypeInsurances, $applicant, $booking, $totalPrice)
|
||||||
|
: [];
|
||||||
|
$tieredInsurance = !empty($eligibleInsurances) ? array_values($eligibleInsurances)[0] : null;
|
||||||
|
|
||||||
|
$assignments = [];
|
||||||
|
foreach ($booking->getParticipants() as $index => $participant) {
|
||||||
|
$assignments[$index] = 0 === $index ? $tieredInsurance : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $assignments;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-family insurance: assign to each participant with their individual price tier
|
||||||
|
$assignments = [];
|
||||||
foreach ($booking->getParticipants() as $index => $participant) {
|
foreach ($booking->getParticipants() as $index => $participant) {
|
||||||
$travelPrice = $participantPrices[$index] ?? 0.0;
|
$travelPrice = $participantPrices[$index] ?? 0.0;
|
||||||
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking, $travelPrice);
|
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking, $travelPrice);
|
||||||
@@ -242,7 +258,7 @@ class InsuranceManager
|
|||||||
int $travelDurationDays,
|
int $travelDurationDays,
|
||||||
): bool {
|
): bool {
|
||||||
// Family insurance constraints
|
// Family insurance constraints
|
||||||
if (false === $this->checkFamilyInsuranceConstraints($insurance, $booking)) {
|
if (false === $this->checkFamilyInsuranceConstraints($insurance, $participant, $booking)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,23 +290,15 @@ class InsuranceManager
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkFamilyInsuranceConstraints(Insurance $insurance, BookingDto $booking): bool
|
private function checkFamilyInsuranceConstraints(Insurance $insurance, ParticipantDto $participant, BookingDto $booking): bool
|
||||||
{
|
{
|
||||||
// Family booking detection only available in create mode
|
if (true === $insurance->familyInsurance) {
|
||||||
if (BookingDto::MODE_EDIT === $booking->getMode()) {
|
if (false === $booking->isFamilyBooking()) {
|
||||||
return true; // Skip family constraints for edit mode
|
return false; // Family insurance only available for family bookings
|
||||||
}
|
}
|
||||||
|
if (0 !== $participant->index) {
|
||||||
$isFamilyBooking = $booking->isFamilyBooking();
|
return false; // Family insurance only assignable to the applicant
|
||||||
|
}
|
||||||
// If it's a family insurance, it should only be available for family bookings
|
|
||||||
if (true === $insurance->familyInsurance && false === $isFamilyBooking) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not a family insurance, it should only be available for non-family bookings
|
|
||||||
if (false === $insurance->familyInsurance && true === $isFamilyBooking) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -425,6 +425,93 @@ class InsuranceManagerTest extends TestCase
|
|||||||
$this->assertNotNull($result[1]);
|
$this->assertNotNull($result[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testBatchAssignFamilyInsuranceToApplicantOnly(): void
|
||||||
|
{
|
||||||
|
$booking = $this->createFamilyBooking('2024-08-01', '2024-08-08');
|
||||||
|
|
||||||
|
// Tier matched by total price (300 + 0 = 300, fits 250–400 tier)
|
||||||
|
$lowTierInsurance = $this->createInsurance([
|
||||||
|
'id' => '1',
|
||||||
|
'package' => false,
|
||||||
|
'subType' => 'FAM',
|
||||||
|
'familyInsurance' => true,
|
||||||
|
'travelPriceFrom' => 0.0,
|
||||||
|
'travelPriceTo' => 249.99,
|
||||||
|
]);
|
||||||
|
$correctTierInsurance = $this->createInsurance([
|
||||||
|
'id' => '2',
|
||||||
|
'package' => false,
|
||||||
|
'subType' => 'FAM',
|
||||||
|
'familyInsurance' => true,
|
||||||
|
'travelPriceFrom' => 250.0,
|
||||||
|
'travelPriceTo' => 400.0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$availableInsurances = [$lowTierInsurance, $correctTierInsurance];
|
||||||
|
// Individual prices: applicant 300, child 0 → total 300
|
||||||
|
$participantPrices = [0 => 300.0, 1 => 0.0];
|
||||||
|
|
||||||
|
$result = $this->service->batchAssignInsuranceToParticipants(
|
||||||
|
$availableInsurances,
|
||||||
|
$correctTierInsurance,
|
||||||
|
$booking,
|
||||||
|
$participantPrices
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertCount(2, $result);
|
||||||
|
$this->assertSame($correctTierInsurance, $result[0], 'Applicant should get tier matching total price');
|
||||||
|
$this->assertNull($result[1], 'Non-applicant participants must be cleared for family insurance');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFamilyInsuranceIneligibleForNonApplicant(): void
|
||||||
|
{
|
||||||
|
$booking = $this->createFamilyBooking('2024-08-01', '2024-08-08');
|
||||||
|
|
||||||
|
$nonApplicant = $this->createParticipant('2015-01-01'); // child
|
||||||
|
$nonApplicant->index = 1;
|
||||||
|
|
||||||
|
$familyInsurance = $this->createInsurance(['familyInsurance' => true]);
|
||||||
|
|
||||||
|
$result = $this->service->getEligibleInsurances([$familyInsurance], $nonApplicant, $booking, 500.0);
|
||||||
|
|
||||||
|
$this->assertEmpty($result, 'Family insurance must not be eligible for non-applicant participants');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNonFamilyInsuranceEligibleForFamilyBooking(): void
|
||||||
|
{
|
||||||
|
$booking = $this->createFamilyBooking('2024-08-01', '2024-08-08');
|
||||||
|
|
||||||
|
$adult = $this->createParticipant('1990-06-15');
|
||||||
|
$adult->index = 0;
|
||||||
|
$child = $this->createParticipant('2015-01-01');
|
||||||
|
$child->index = 1;
|
||||||
|
|
||||||
|
$nonFamilyInsurance = $this->createInsurance(['familyInsurance' => false]);
|
||||||
|
|
||||||
|
$adultResult = $this->service->getEligibleInsurances([$nonFamilyInsurance], $adult, $booking, 500.0);
|
||||||
|
$childResult = $this->service->getEligibleInsurances([$nonFamilyInsurance], $child, $booking, 500.0);
|
||||||
|
|
||||||
|
$this->assertNotEmpty($adultResult, 'Non-family insurance must be available in family bookings for adults');
|
||||||
|
$this->assertNotEmpty($childResult, 'Non-family insurance must be available in family bookings for children');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFamilyInsuranceIneligibleForNonFamilyBooking(): void
|
||||||
|
{
|
||||||
|
// Two adults, no children — not a family booking
|
||||||
|
$booking = $this->createBooking('2024-08-01', '2024-08-08');
|
||||||
|
$adult1 = $this->createParticipant('1990-06-15');
|
||||||
|
$adult1->index = 0;
|
||||||
|
$adult2 = $this->createParticipant('1985-03-10');
|
||||||
|
$adult2->index = 1;
|
||||||
|
$booking->participants = [$adult1, $adult2];
|
||||||
|
|
||||||
|
$familyInsurance = $this->createInsurance(['familyInsurance' => true]);
|
||||||
|
|
||||||
|
$result = $this->service->getEligibleInsurances([$familyInsurance], $adult1, $booking, 500.0);
|
||||||
|
|
||||||
|
$this->assertEmpty($result, 'Family insurance must not be eligible when booking is not a family booking');
|
||||||
|
}
|
||||||
|
|
||||||
private function createParticipant(string $dateOfBirth): ParticipantDto
|
private function createParticipant(string $dateOfBirth): ParticipantDto
|
||||||
{
|
{
|
||||||
$participant = new ParticipantDto();
|
$participant = new ParticipantDto();
|
||||||
@@ -442,6 +529,20 @@ class InsuranceManagerTest extends TestCase
|
|||||||
return $booking;
|
return $booking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function createFamilyBooking(string $travelDateFrom, string $travelDateTo): BookingDto
|
||||||
|
{
|
||||||
|
$booking = $this->createBooking($travelDateFrom, $travelDateTo);
|
||||||
|
|
||||||
|
$adult = $this->createParticipant('1990-06-15'); // 34 years old
|
||||||
|
$adult->index = 0;
|
||||||
|
$child = $this->createParticipant('2015-01-01'); // 9 years old
|
||||||
|
$child->index = 1;
|
||||||
|
|
||||||
|
$booking->participants = [$adult, $child];
|
||||||
|
|
||||||
|
return $booking;
|
||||||
|
}
|
||||||
|
|
||||||
private function createTravel(string $dateFrom, string $dateTo): Travel
|
private function createTravel(string $dateFrom, string $dateTo): Travel
|
||||||
{
|
{
|
||||||
$travel = new Travel();
|
$travel = new Travel();
|
||||||
|
|||||||
Reference in new issue
Block a user