266 lines
11 KiB
PHP
266 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Service;
|
|
|
|
use App\BusProNet\Model\Insurance;
|
|
use App\BusProNet\Model\Service;
|
|
use App\Form\Model\BookingDto;
|
|
use App\Form\Model\ParticipantDto;
|
|
|
|
/**
|
|
* Calculates pricing for individual participants in a booking.
|
|
*
|
|
* Handles room allocation costs, service selections, and provides
|
|
* both inclusive and exclusive insurance calculations for eligibility.
|
|
*/
|
|
class ParticipantPricingCalculator
|
|
{
|
|
/** @var array<string, float> Request-scoped cache for participant prices */
|
|
private array $participantPriceCache = [];
|
|
|
|
public function __construct(
|
|
private readonly RoomPricingCalculator $roomPricingCalculator,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Calculates the total price for an individual participant.
|
|
*
|
|
* This method calculates the complete price breakdown for a single participant,
|
|
* including their room allocation (full room price) and all selected services.
|
|
*
|
|
* @param BookingDto $bookingDto The booking data containing all participants
|
|
* @param int $participantIndex The index of the participant to calculate for
|
|
*
|
|
* @return float The total price for the specified participant
|
|
*/
|
|
public function calculateIndividualParticipantPrice(BookingDto $bookingDto, int $participantIndex): float
|
|
{
|
|
$participant = $bookingDto->getParticipant($participantIndex);
|
|
if (null === $participant) {
|
|
return 0.0;
|
|
}
|
|
|
|
$totalPrice = 0.0;
|
|
|
|
// Add room price if participant is assigned to a room
|
|
if (null !== $participant->assignedRoomId) {
|
|
$room = $this->roomPricingCalculator->getRoomById($bookingDto, $participant->assignedRoomId);
|
|
if (null !== $room && null !== $room->price) {
|
|
// Each participant pays the full room price
|
|
$totalPrice += $room->price;
|
|
}
|
|
}
|
|
|
|
// Add service prices for this participant (with booking context for bulk insurance)
|
|
$totalPrice += $this->calculateParticipantServiceTotal($participant, true, $bookingDto);
|
|
|
|
return $totalPrice;
|
|
}
|
|
|
|
/**
|
|
* Calculates individual prices for all participants in a booking.
|
|
*
|
|
* @param BookingDto $bookingDto The booking data containing all participants
|
|
*
|
|
* @return array Array indexed by participant index containing individual prices
|
|
*/
|
|
public function calculateAllParticipantIndividualPrices(BookingDto $bookingDto): array
|
|
{
|
|
$participantPrices = [];
|
|
$participants = $bookingDto->getParticipants();
|
|
|
|
foreach ($participants as $index => $participant) {
|
|
$participantPrices[$index] = $this->calculateIndividualParticipantPrice($bookingDto, $index);
|
|
}
|
|
|
|
return $participantPrices;
|
|
}
|
|
|
|
/**
|
|
* Calculates the total price for an individual participant excluding insurance.
|
|
*
|
|
* This method is used for insurance eligibility filtering to avoid circular dependency
|
|
* where insurance selection affects travel price which affects insurance eligibility.
|
|
*
|
|
* Only includes services where versicherungsberechnung='J' in the XML. Services with
|
|
* versicherungsberechnung='N' (like CO2 compensation) are excluded from the calculation
|
|
* as per BPN API requirements for insurance tier determination.
|
|
*
|
|
* @param BookingDto $bookingDto The booking data containing all participants
|
|
* @param int $participantIndex The index of the participant to calculate for
|
|
*
|
|
* @return float The total price for the specified participant excluding insurance and non-calculated services
|
|
*/
|
|
public function calculateIndividualParticipantPriceExcludingInsurance(
|
|
BookingDto $bookingDto,
|
|
int $participantIndex,
|
|
): float {
|
|
$participant = $bookingDto->getParticipant($participantIndex);
|
|
if (null === $participant) {
|
|
return 0.0;
|
|
}
|
|
|
|
// Generate cache key based on participant state that affects pricing
|
|
$stateComponents = [
|
|
'room' => $participant->assignedRoomId ?? 'none',
|
|
'skiPass' => $participant->skiPass?->id ?? 'none',
|
|
'rentalInsurance' => $participant->rentalInsurance?->id ?? 'none',
|
|
'transportationOut' => $participant->transportationOutbound?->id ?? 'none',
|
|
'transportationIn' => $participant->transportationInbound?->id ?? 'none',
|
|
'pickup' => $participant->pickup?->id ?? 'none',
|
|
'parking' => $participant->parkingService?->id ?? 'none',
|
|
'courses' => implode('_', array_map(fn ($s) => $s->id, $participant->courses ?? [])),
|
|
'additionalServices' => implode('_', array_map(fn ($s) => $s->id, $participant->additionalServices ?? [])),
|
|
'board' => implode('_', array_map(fn ($s) => $s->id, $participant->board ?? [])),
|
|
'rentals' => implode('_', array_map(fn ($s) => $s->id, $participant->rentals ?? [])),
|
|
];
|
|
|
|
$cacheKey = sprintf(
|
|
'participant_price_%d_%s',
|
|
$participantIndex,
|
|
md5(json_encode($stateComponents))
|
|
);
|
|
|
|
return $this->participantPriceCache[$cacheKey] ??= (function () use ($bookingDto, $participant) {
|
|
$totalPrice = 0.0;
|
|
|
|
// Add room price if participant is assigned to a room
|
|
if (null !== $participant->assignedRoomId) {
|
|
$room = $this->roomPricingCalculator->getRoomById($bookingDto, $participant->assignedRoomId);
|
|
if (null !== $room && null !== $room->price) {
|
|
// Each participant pays the full room price
|
|
$totalPrice += $room->price;
|
|
}
|
|
}
|
|
|
|
// Add service prices for this participant (excluding insurance)
|
|
// For insurance eligibility calculation, only include services marked with versicherungsberechnung='J'
|
|
$totalPrice += $this->calculateParticipantServiceTotal($participant, false, null, true);
|
|
|
|
return $totalPrice;
|
|
})();
|
|
}
|
|
|
|
/**
|
|
* Gets the effective insurance for a participant, considering bulk insurance assignment.
|
|
*
|
|
* When bulk insurance is active and the participant is a dependent (index > 0),
|
|
* returns the applicant's insurance. Otherwise returns the participant's own insurance.
|
|
*
|
|
* This method is used for pricing calculations to show correct prices when bulk
|
|
* insurance is enabled, even though the actual assignment happens in the processor.
|
|
*
|
|
* @param ParticipantDto $participant The participant to get insurance for
|
|
* @param BookingDto|null $bookingDto Optional booking DTO for bulk insurance check
|
|
*
|
|
* @return Insurance|null The effective insurance for pricing purposes
|
|
*/
|
|
public function getEffectiveInsurance(ParticipantDto $participant, ?BookingDto $bookingDto): ?Insurance
|
|
{
|
|
// If no booking context, use participant's own insurance
|
|
if (null === $bookingDto) {
|
|
return $participant->insurance;
|
|
}
|
|
|
|
// Applicant always uses their own insurance
|
|
if (0 === $participant->index) {
|
|
return $participant->insurance;
|
|
}
|
|
|
|
// Check if bulk insurance is active
|
|
$applicant = $bookingDto->getParticipant(0);
|
|
if (null === $applicant || false === $applicant->bulkInsuranceBooking) {
|
|
return $participant->insurance;
|
|
}
|
|
|
|
// Bulk insurance is active - use applicant's insurance for dependent participants
|
|
return $applicant->insurance;
|
|
}
|
|
|
|
/**
|
|
* Calculates the total service cost for a single participant.
|
|
*
|
|
* @param ParticipantDto $participant The participant to calculate services for
|
|
* @param bool $includeInsurance Whether to include insurance pricing (default: true)
|
|
* @param BookingDto|null $bookingDto Optional booking DTO for bulk insurance resolution
|
|
* @param bool $onlyInsuranceCalculationServices Only include services with includeInInsuranceCalculation=true (default: false)
|
|
*
|
|
* @return float The total service cost for this participant
|
|
*/
|
|
public function calculateParticipantServiceTotal(
|
|
ParticipantDto $participant,
|
|
bool $includeInsurance = true,
|
|
?BookingDto $bookingDto = null,
|
|
bool $onlyInsuranceCalculationServices = false,
|
|
): float {
|
|
$serviceTotal = 0.0;
|
|
|
|
// Single service selections
|
|
if (null !== $participant->skiPass && null !== $participant->skiPass->price) {
|
|
if (false === $onlyInsuranceCalculationServices || true === $participant->skiPass->includeInInsuranceCalculation) {
|
|
$serviceTotal += $participant->skiPass->price;
|
|
}
|
|
}
|
|
|
|
if (null !== $participant->rentalInsurance && null !== $participant->rentalInsurance->price) {
|
|
if (false === $onlyInsuranceCalculationServices || true === $participant->rentalInsurance->includeInInsuranceCalculation) {
|
|
$serviceTotal += $participant->rentalInsurance->price;
|
|
}
|
|
}
|
|
|
|
// Get effective insurance (considering bulk insurance for dependent participants)
|
|
$effectiveInsurance = $this->getEffectiveInsurance($participant, $bookingDto);
|
|
if ($includeInsurance && null !== $effectiveInsurance && null !== $effectiveInsurance->price) {
|
|
$serviceTotal += $effectiveInsurance->price;
|
|
}
|
|
|
|
// Transportation services
|
|
if (null !== $participant->transportationOutbound && null !== $participant->transportationOutbound->price) {
|
|
if (false === $onlyInsuranceCalculationServices || true === $participant->transportationOutbound->includeInInsuranceCalculation) {
|
|
$serviceTotal += $participant->transportationOutbound->price;
|
|
}
|
|
}
|
|
|
|
if (null !== $participant->transportationInbound && null !== $participant->transportationInbound->price) {
|
|
if (false === $onlyInsuranceCalculationServices || true === $participant->transportationInbound->includeInInsuranceCalculation) {
|
|
$serviceTotal += $participant->transportationInbound->price;
|
|
}
|
|
}
|
|
|
|
if (null !== $participant->pickup && null !== $participant->pickup->price) {
|
|
$serviceTotal += $participant->pickup->price;
|
|
}
|
|
|
|
if (null !== $participant->parkingService && null !== $participant->parkingService->price) {
|
|
if (false === $onlyInsuranceCalculationServices || true === $participant->parkingService->includeInInsuranceCalculation) {
|
|
$serviceTotal += $participant->parkingService->price;
|
|
}
|
|
}
|
|
|
|
// Multiple service selections
|
|
$multipleServiceArrays = [
|
|
'courses' => $participant->courses,
|
|
'additionalServices' => $participant->additionalServices,
|
|
'board' => $participant->board,
|
|
'rentals' => $participant->rentals,
|
|
];
|
|
|
|
foreach ($multipleServiceArrays as $serviceArray) {
|
|
if (true === is_array($serviceArray)) {
|
|
foreach ($serviceArray as $service) {
|
|
if ($service instanceof Service && null !== $service->price) {
|
|
if (false === $onlyInsuranceCalculationServices || true === $service->includeInInsuranceCalculation) {
|
|
$serviceTotal += $service->price;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $serviceTotal;
|
|
}
|
|
}
|