wip: insurance booking
This commit is contained in:
@@ -7,6 +7,9 @@ namespace App\Service;
|
||||
use App\BusProNet\Model\Insurance;
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Model\InsuranceEligibilityCriteria;
|
||||
use App\Service\BookingPriceCalculatorService;
|
||||
use App\Service\InsuranceTypeResolver;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
@@ -18,6 +21,11 @@ use Carbon\Carbon;
|
||||
*/
|
||||
class InsuranceMatchingService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BookingPriceCalculatorService $priceCalculatorService,
|
||||
private readonly InsuranceTypeResolver $insuranceTypeResolver
|
||||
) {
|
||||
}
|
||||
/**
|
||||
* Filters insurances based on participant and booking criteria.
|
||||
*
|
||||
@@ -29,67 +37,151 @@ class InsuranceMatchingService
|
||||
*/
|
||||
public function getEligibleInsurances(array $insurances, ParticipantDto $participant, BookingCreateDto $booking): array
|
||||
{
|
||||
$travelStartDate = $booking->travel->dateFrom;
|
||||
$travelEndDate = $booking->travel->dateTo;
|
||||
$criteria = $this->createEligibilityCriteria($participant, $booking);
|
||||
|
||||
// Cannot match insurances without travel dates
|
||||
if (null === $travelStartDate || null === $travelEndDate) {
|
||||
return [];
|
||||
if (null === $criteria) {
|
||||
return []; // Cannot match insurances without travel dates
|
||||
}
|
||||
|
||||
$bookingDate = Carbon::now()->toDateTimeImmutable();
|
||||
$travelPrice = $this->calculateTravelPrice($booking);
|
||||
$travelDurationDays = $this->calculateTravelDurationDays($travelStartDate, $travelEndDate);
|
||||
|
||||
return array_filter($insurances, function (Insurance $insurance) use ($participant, $travelStartDate, $travelEndDate, $bookingDate, $travelPrice, $travelDurationDays) {
|
||||
return $this->isInsuranceEligible($insurance, $participant, $travelStartDate, $travelEndDate, $bookingDate, $travelPrice, $travelDurationDays);
|
||||
});
|
||||
return array_filter(
|
||||
$insurances,
|
||||
fn (Insurance $insurance) => $this->isInsuranceEligible($insurance, $criteria)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific insurance is eligible for a participant.
|
||||
* Auto-reassigns an insurance to the same type with appropriate price tier.
|
||||
*
|
||||
* @param Insurance $insurance The insurance to check
|
||||
* @param ParticipantDto $participant The participant
|
||||
* @param \DateTimeImmutable $travelStartDate Travel start date
|
||||
* @param \DateTimeImmutable $travelEndDate Travel end date
|
||||
* @param \DateTimeImmutable $bookingDate Booking date
|
||||
* @param float $travelPrice Total travel price
|
||||
* @param int $travelDurationDays Travel duration in days
|
||||
* This method is used when a participant's individual price changes and their
|
||||
* current insurance is no longer eligible. It finds the same insurance type
|
||||
* (subType + familyInsurance) with the correct price tier.
|
||||
*
|
||||
* @return bool True if insurance is eligible
|
||||
* @param array<Insurance> $availableInsurances All available insurances
|
||||
* @param Insurance $currentInsurance The currently selected insurance
|
||||
* @param ParticipantDto $participant The participant to reassign for
|
||||
* @param BookingCreateDto $booking The booking context
|
||||
*
|
||||
* @return Insurance|null The reassigned insurance or null if no suitable match found
|
||||
*/
|
||||
private function isInsuranceEligible(
|
||||
Insurance $insurance,
|
||||
ParticipantDto $participant,
|
||||
\DateTimeImmutable $travelStartDate,
|
||||
\DateTimeImmutable $travelEndDate,
|
||||
\DateTimeImmutable $bookingDate,
|
||||
float $travelPrice,
|
||||
int $travelDurationDays,
|
||||
): bool {
|
||||
public function reassignInsuranceForPriceChange(array $availableInsurances, Insurance $currentInsurance, ParticipantDto $participant, BookingCreateDto $booking): ?Insurance
|
||||
{
|
||||
// Group insurances of the same type (subType + familyInsurance)
|
||||
$sameTypeInsurances = $this->filterInsurancesByType($availableInsurances, $currentInsurance->subType, $currentInsurance->familyInsurance);
|
||||
|
||||
// Get eligible insurances for this participant
|
||||
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking);
|
||||
|
||||
// Return the first eligible insurance (they should all be equivalent for the same type)
|
||||
return !empty($eligibleInsurances) ? array_values($eligibleInsurances)[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-assigns insurances of the same type to all participants based on individual pricing.
|
||||
*
|
||||
* This method takes the applicant's insurance selection and assigns the same insurance type
|
||||
* (subType + familyInsurance) to all participants, but selects the appropriate price tier
|
||||
* based on each participant's individual travel price.
|
||||
*
|
||||
* @param array<Insurance> $availableInsurances All available insurances
|
||||
* @param Insurance $selectedInsurance The insurance selected by the applicant
|
||||
* @param BookingCreateDto $booking The booking with all participants
|
||||
*
|
||||
* @return array<int, Insurance|null> Array indexed by participant index with assigned insurances
|
||||
*/
|
||||
public function batchAssignInsuranceToParticipants(array $availableInsurances, Insurance $selectedInsurance, BookingCreateDto $booking): array
|
||||
{
|
||||
$assignments = [];
|
||||
|
||||
// Group insurances of the same type (subType + familyInsurance)
|
||||
$sameTypeInsurances = $this->filterInsurancesByType($availableInsurances, $selectedInsurance->subType, $selectedInsurance->familyInsurance);
|
||||
|
||||
// Assign appropriate insurance to each participant
|
||||
foreach ($booking->getParticipants() as $index => $participant) {
|
||||
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking);
|
||||
$assignments[$index] = !empty($eligibleInsurances) ? array_values($eligibleInsurances)[0] : null;
|
||||
}
|
||||
|
||||
return $assignments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates eligibility criteria from participant and booking data.
|
||||
*/
|
||||
private function createEligibilityCriteria(ParticipantDto $participant, BookingCreateDto $booking): ?InsuranceEligibilityCriteria
|
||||
{
|
||||
$travelStartDate = $booking->travel->dateFrom;
|
||||
$travelEndDate = $booking->travel->dateTo;
|
||||
|
||||
if (null === $travelStartDate || null === $travelEndDate) {
|
||||
return null; // Cannot create criteria without travel dates
|
||||
}
|
||||
|
||||
return new InsuranceEligibilityCriteria(
|
||||
participant: $participant,
|
||||
travelStartDate: $travelStartDate,
|
||||
travelEndDate: $travelEndDate,
|
||||
bookingDate: Carbon::now()->toDateTimeImmutable(),
|
||||
travelPrice: $this->calculateTravelPrice($booking, $participant->index),
|
||||
travelDurationDays: $this->calculateTravelDurationDays($travelStartDate, $travelEndDate),
|
||||
booking: $booking,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific insurance is eligible for given criteria.
|
||||
*/
|
||||
private function isInsuranceEligible(Insurance $insurance, InsuranceEligibilityCriteria $criteria): bool
|
||||
{
|
||||
// Family insurance constraints
|
||||
if (false === $this->checkFamilyInsuranceConstraints($insurance, $criteria->booking)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Age constraints
|
||||
if (!$this->checkAgeConstraints($insurance, $participant, $travelStartDate)) {
|
||||
if (false === $this->checkAgeConstraints($insurance, $criteria->participant, $criteria->travelStartDate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Travel date constraints
|
||||
if (!$this->checkTravelDateConstraints($insurance, $travelStartDate, $travelEndDate)) {
|
||||
if (false === $this->checkTravelDateConstraints($insurance, $criteria->travelStartDate, $criteria->travelEndDate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Booking date constraints
|
||||
if (!$this->checkBookingDateConstraints($insurance, $bookingDate)) {
|
||||
if (false === $this->checkBookingDateConstraints($insurance, $criteria->bookingDate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Travel price constraints
|
||||
if (!$this->checkTravelPriceConstraints($insurance, $travelPrice)) {
|
||||
if (false === $this->checkTravelPriceConstraints($insurance, $criteria->travelPrice)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Travel duration constraints
|
||||
if (!$this->checkTravelDurationConstraints($insurance, $travelDurationDays)) {
|
||||
if (false === $this->checkTravelDurationConstraints($insurance, $criteria->travelDurationDays)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if family insurance constraints are met.
|
||||
*
|
||||
* Family insurances should only be available for family bookings,
|
||||
* and individual insurances should only be available for non-family bookings.
|
||||
*/
|
||||
private function checkFamilyInsuranceConstraints(Insurance $insurance, BookingCreateDto $booking): bool
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
@@ -103,8 +195,9 @@ class InsuranceMatchingService
|
||||
{
|
||||
$participantAge = $participant->getAge($travelStartDate);
|
||||
|
||||
// If no birth date is provided, skip age constraints (field will be hidden via field state conditions)
|
||||
if (null === $participantAge) {
|
||||
return false; // Cannot determine age eligibility without birth date
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check minimum age
|
||||
@@ -197,17 +290,21 @@ class InsuranceMatchingService
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total travel price from booking data.
|
||||
* Calculates the total travel price for a participant, excluding insurance prices.
|
||||
*
|
||||
* @param BookingCreateDto $booking The booking to calculate price for
|
||||
* This method calculates the travel price used for insurance eligibility filtering.
|
||||
* It excludes insurance prices to prevent circular dependency where insurance selection
|
||||
* affects travel price which then affects insurance eligibility.
|
||||
*
|
||||
* @return float The total travel price
|
||||
* @param BookingCreateDto $booking The booking to calculate price for
|
||||
* @param int $participantIndex The participant index to calculate for
|
||||
*
|
||||
* @return float The total travel price for the participant excluding insurance
|
||||
*/
|
||||
private function calculateTravelPrice(BookingCreateDto $booking): float
|
||||
private function calculateTravelPrice(BookingCreateDto $booking, int $participantIndex): float
|
||||
{
|
||||
// For now, return 0.0 as placeholder - this will be enhanced
|
||||
// when we integrate with the existing pricing calculation system
|
||||
return 0.0;
|
||||
// Use the price calculator to get the participant's individual price excluding insurance
|
||||
return $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($booking, $participantIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,4 +319,24 @@ class InsuranceMatchingService
|
||||
{
|
||||
return $startDate->diff($endDate)->days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters insurances by type (subType and familyInsurance combination).
|
||||
*
|
||||
* This method groups insurances of the same type together for reassignment or batch assignment.
|
||||
* Insurance type is defined as the combination of subType (RRV, PAK, OHN) and familyInsurance flag.
|
||||
*
|
||||
* @param array<Insurance> $insurances All available insurances to filter
|
||||
* @param string|null $subType The insurance subType to match (e.g., 'RRV', 'PAK')
|
||||
* @param bool $familyInsurance Whether to match family or individual insurances
|
||||
*
|
||||
* @return array<Insurance> Filtered insurances of the same type
|
||||
*/
|
||||
private function filterInsurancesByType(array $insurances, ?string $subType, bool $familyInsurance): array
|
||||
{
|
||||
return array_filter(
|
||||
$insurances,
|
||||
fn (Insurance $insurance) => $insurance->subType === $subType && $insurance->familyInsurance === $familyInsurance
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user