fix: incorrect rules around family insurances
addresses #869dr80qj
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\BusProNet\Model\Insurance;
|
||||
use App\Form\Model\BookingDto;
|
||||
|
||||
/**
|
||||
@@ -76,6 +77,22 @@ class BookingPriceCalculator
|
||||
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.
|
||||
*
|
||||
@@ -95,4 +112,22 @@ class BookingPriceCalculator
|
||||
$participantIndex
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the travel price to use for insurance eligibility checks.
|
||||
*
|
||||
* Family insurances are always evaluated against the total booking price (all
|
||||
* participants combined); everything else uses the individual participant's price.
|
||||
* Non-applicant participants can never hold a family insurance (InsuranceManager
|
||||
* restricts family insurances to the applicant), so this does not need to know
|
||||
* whether $participantIndex is the applicant.
|
||||
*/
|
||||
public function resolveInsuranceTravelPrice(BookingDto $bookingDto, int $participantIndex, Insurance $insurance): float
|
||||
{
|
||||
if (true === $insurance->familyInsurance) {
|
||||
return $this->calculateTotalBookingPriceExcludingInsurance($bookingDto);
|
||||
}
|
||||
|
||||
return $this->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $participantIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,42 @@ class InsuranceManager
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns eligible insurances for a participant, pricing family and non-family
|
||||
* insurances on the correct basis.
|
||||
*
|
||||
* Family insurances must be priced by the total booking price, not the individual
|
||||
* participant's price - this splits the given insurances by type and evaluates
|
||||
* each group against the appropriate price before merging the results back together.
|
||||
*
|
||||
* @param array<Insurance> $insurances Available insurances to filter
|
||||
* @param ParticipantDto $participant The participant to match insurances for
|
||||
* @param BookingDto $booking The booking context for additional criteria
|
||||
* @param float $individualPrice The participant's individual travel price (excluding insurance)
|
||||
* @param float $totalBookingPrice The total booking price across all participants (excluding insurance)
|
||||
*
|
||||
* @return array<Insurance> Filtered array of eligible insurances, sorted by price
|
||||
*/
|
||||
public function getEligibleInsurancesForParticipant(
|
||||
array $insurances,
|
||||
ParticipantDto $participant,
|
||||
BookingDto $booking,
|
||||
float $individualPrice,
|
||||
float $totalBookingPrice,
|
||||
): array {
|
||||
$nonFamilyInsurances = array_values(array_filter($insurances, static fn (Insurance $i) => false === $i->familyInsurance));
|
||||
$familyInsurances = array_values(array_filter($insurances, static fn (Insurance $i) => true === $i->familyInsurance));
|
||||
|
||||
$eligibleInsurances = $this->getEligibleInsurances($nonFamilyInsurances, $participant, $booking, $individualPrice);
|
||||
|
||||
if (!empty($familyInsurances)) {
|
||||
$eligibleFamilyInsurances = $this->getEligibleInsurances($familyInsurances, $participant, $booking, $totalBookingPrice);
|
||||
$eligibleInsurances = $this->sortByPrice(array_merge($eligibleInsurances, $eligibleFamilyInsurances));
|
||||
}
|
||||
|
||||
return $eligibleInsurances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-reassigns an insurance to the same type with appropriate price tier.
|
||||
*
|
||||
@@ -159,12 +195,28 @@ class InsuranceManager
|
||||
BookingDto $booking,
|
||||
array $participantPrices,
|
||||
): array {
|
||||
$assignments = [];
|
||||
|
||||
// Group insurances of the same type
|
||||
$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) {
|
||||
$travelPrice = $participantPrices[$index] ?? 0.0;
|
||||
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking, $travelPrice);
|
||||
@@ -242,7 +294,7 @@ class InsuranceManager
|
||||
int $travelDurationDays,
|
||||
): bool {
|
||||
// Family insurance constraints
|
||||
if (false === $this->checkFamilyInsuranceConstraints($insurance, $booking)) {
|
||||
if (false === $this->checkFamilyInsuranceConstraints($insurance, $participant, $booking)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -274,23 +326,15 @@ class InsuranceManager
|
||||
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 (BookingDto::MODE_EDIT === $booking->getMode()) {
|
||||
return true; // Skip family constraints for edit mode
|
||||
}
|
||||
|
||||
$isFamilyBooking = $booking->isFamilyBooking();
|
||||
|
||||
// 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;
|
||||
if (true === $insurance->familyInsurance) {
|
||||
if (false === $booking->isFamilyBooking()) {
|
||||
return false; // Family insurance only available for family bookings
|
||||
}
|
||||
if (0 !== $participant->index) {
|
||||
return false; // Family insurance only assignable to the applicant
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user