wip: insurance booking
This commit is contained in:
@@ -19,11 +19,31 @@ final class Constants
|
|||||||
public const TOKEN_INSURANCES = ['RRV', 'PAK', 'OHN', 'PKG'];
|
public const TOKEN_INSURANCES = ['RRV', 'PAK', 'OHN', 'PKG'];
|
||||||
public const TOKEN_PARKING = 'PAR';
|
public const TOKEN_PARKING = 'PAR';
|
||||||
|
|
||||||
|
// Insurance subtype labels for display
|
||||||
|
public const INSURANCE_LABELS = [
|
||||||
|
'RRV' => 'Reiserücktrittsversicherung',
|
||||||
|
'PAK' => 'Reiseschutz',
|
||||||
|
'OHN' => 'Selbstbehalt',
|
||||||
|
];
|
||||||
|
|
||||||
// Normalized service group keys for pricing display
|
// Normalized service group keys for pricing display
|
||||||
public const GROUP_TRANSPORTATION = 'transportation';
|
public const GROUP_TRANSPORTATION = 'transportation';
|
||||||
public const GROUP_RENTALS = 'rentals';
|
public const GROUP_RENTALS = 'rentals';
|
||||||
public const GROUP_INSURANCE = 'insurance';
|
public const GROUP_INSURANCE = 'insurance';
|
||||||
|
|
||||||
|
// Service type display labels (German) - used in pricing calculator
|
||||||
|
public const SERVICE_LABELS = [
|
||||||
|
self::TOKEN_COURSES => 'Kurse',
|
||||||
|
self::TOKEN_SKI_PASS => 'Skipässe',
|
||||||
|
self::TOKEN_ADDITIONAL => 'Zusatzleistungen',
|
||||||
|
self::TOKEN_BOARD => 'Verpflegung',
|
||||||
|
self::TOKEN_RENTAL_INSURANCE => 'Leihmaterial-Versicherung',
|
||||||
|
self::TOKEN_PARKING => 'Parkplatz',
|
||||||
|
self::GROUP_TRANSPORTATION => 'Beförderung',
|
||||||
|
self::GROUP_RENTALS => 'Leihmaterial',
|
||||||
|
self::GROUP_INSURANCE => 'Reiseversicherungen',
|
||||||
|
];
|
||||||
|
|
||||||
public const STATUS_AVAILABLE = 'Frei';
|
public const STATUS_AVAILABLE = 'Frei';
|
||||||
public const STATUS_BLOCKED = 'Buchungsstop';
|
public const STATUS_BLOCKED = 'Buchungsstop';
|
||||||
public const STATUS_ON_REQUEST = 'Anfrage';
|
public const STATUS_ON_REQUEST = 'Anfrage';
|
||||||
|
|||||||
@@ -44,33 +44,6 @@ class InsuranceLoader extends AbstractLoader
|
|||||||
return $insurances[$id] ?? null;
|
return $insurances[$id] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads all insurances with resolved package relationships.
|
|
||||||
*
|
|
||||||
* @param string|null $filename The XML filename to load from
|
|
||||||
* @return array<string|int, Insurance> Array of Insurance objects with resolved relationships
|
|
||||||
*/
|
|
||||||
public function loadAllWithRelationships(?string $filename = 'versicherungen.xml'): array
|
|
||||||
{
|
|
||||||
$insurances = $this->loadAll($filename);
|
|
||||||
|
|
||||||
// Resolve package relationships
|
|
||||||
foreach ($insurances as $insurance) {
|
|
||||||
if ($insurance->package && !empty($insurance->containedInsuranceIds)) {
|
|
||||||
foreach ($insurance->containedInsuranceIds as $containedId) {
|
|
||||||
if (isset($insurances[$containedId])) {
|
|
||||||
$insurance->containedInsurances[] = $insurances[$containedId];
|
|
||||||
} else {
|
|
||||||
// Log missing insurance reference for debugging
|
|
||||||
error_log("Insurance package {$insurance->id} references missing insurance {$containedId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $insurances;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadXml(?string $filename = 'versicherungen.xml'): Crawler
|
private function loadXml(?string $filename = 'versicherungen.xml'): Crawler
|
||||||
{
|
{
|
||||||
$xml = $this->xmlExport->read($filename);
|
$xml = $this->xmlExport->read($filename);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Form\Service;
|
namespace App\Form\Service;
|
||||||
|
|
||||||
|
use App\BusProNet\Model\Insurance;
|
||||||
use App\BusProNet\Model\Pickup;
|
use App\BusProNet\Model\Pickup;
|
||||||
use App\BusProNet\Model\Service;
|
use App\BusProNet\Model\Service;
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
@@ -345,6 +346,11 @@ class ParticipantFieldHandlerRegistry
|
|||||||
return $value->id;
|
return $value->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Insurance objects -> convert to ID
|
||||||
|
if ($value instanceof Insurance) {
|
||||||
|
return $value->id;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle DateTimeInterface -> convert to string format
|
// Handle DateTimeInterface -> convert to string format
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof \DateTimeInterface) {
|
||||||
return $value->format('Y-m-d');
|
return $value->format('Y-m-d');
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ use App\Form\Model\BookingCreateDto;
|
|||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
|
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
|
||||||
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
|
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
|
||||||
use App\Form\Service\ServiceAgeEvaluator;
|
|
||||||
use App\Service\InsuranceMatchingService;
|
use App\Service\InsuranceMatchingService;
|
||||||
use App\Service\ServiceAvailabilityCalculator;
|
use App\Service\ServiceAvailabilityCalculator;
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
@@ -591,7 +590,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
private function getParkingCheckboxLabel(array $parkingServices): string
|
private function getParkingCheckboxLabel(array $parkingServices): string
|
||||||
{
|
{
|
||||||
if (empty($parkingServices)) {
|
if (empty($parkingServices)) {
|
||||||
return 'Parkplatz';
|
return Constants::SERVICE_LABELS[Constants::TOKEN_PARKING];
|
||||||
}
|
}
|
||||||
|
|
||||||
$parkingService = reset($parkingServices); // Get the first (and only) parking service
|
$parkingService = reset($parkingServices); // Get the first (and only) parking service
|
||||||
@@ -681,7 +680,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
private function getRentalInsuranceCheckboxLabel(array $rentalInsuranceServices): string
|
private function getRentalInsuranceCheckboxLabel(array $rentalInsuranceServices): string
|
||||||
{
|
{
|
||||||
if (empty($rentalInsuranceServices)) {
|
if (empty($rentalInsuranceServices)) {
|
||||||
return 'Leihmaterial-Versicherung';
|
return Constants::SERVICE_LABELS[Constants::TOKEN_RENTAL_INSURANCE];
|
||||||
}
|
}
|
||||||
$rentalInsuranceService = reset($rentalInsuranceServices); // Get the first (and only) rental insurance service
|
$rentalInsuranceService = reset($rentalInsuranceServices); // Get the first (and only) rental insurance service
|
||||||
|
|
||||||
@@ -751,15 +750,9 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
$label .= sprintf(' (€%s)', number_format($insurance->price, 2, ',', '.'));
|
$label .= sprintf(' (€%s)', number_format($insurance->price, 2, ',', '.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add type information if available
|
// Add type information if available using centralized mapping
|
||||||
if (null !== $insurance->subType) {
|
if (null !== $insurance->subType && isset(Constants::INSURANCE_LABELS[$insurance->subType])) {
|
||||||
$typeLabel = match ($insurance->subType) {
|
$label .= sprintf(' (%s)', Constants::INSURANCE_LABELS[$insurance->subType]);
|
||||||
'RRV' => 'Reiserücktrittsversicherung',
|
|
||||||
'PAK' => 'Reiseschutz',
|
|
||||||
'OHN' => 'Selbstbehalt',
|
|
||||||
default => $insurance->subType,
|
|
||||||
};
|
|
||||||
$label .= sprintf(' (%s)', $typeLabel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $label;
|
return $label;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ use App\Service\InsuranceMatchingService;
|
|||||||
class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly InsuranceMatchingService $insuranceMatchingService
|
private readonly InsuranceMatchingService $insuranceMatchingService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,13 +52,27 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
/**
|
/**
|
||||||
* Returns the field dependencies for proper processing order.
|
* Returns the field dependencies for proper processing order.
|
||||||
*
|
*
|
||||||
* This handler depends on dateOfBirth for age evaluation and insurance eligibility.
|
* This handler must run AFTER all fields that affect travel price, since insurance
|
||||||
|
* reassignment logic depends on accurate price calculations. It also depends on
|
||||||
|
* dateOfBirth for age evaluation and insurance eligibility.
|
||||||
*
|
*
|
||||||
* @return string[] Array containing 'dateOfBirth' dependency
|
* @return string[] Array of field dependencies
|
||||||
*/
|
*/
|
||||||
public function getDependencies(): array
|
public function getDependencies(): array
|
||||||
{
|
{
|
||||||
return ['dateOfBirth'];
|
return [
|
||||||
|
'dateOfBirth', // Required for age-based eligibility
|
||||||
|
'skiPass', // Affects travel price
|
||||||
|
'rentals', // Affects travel price
|
||||||
|
'courses', // Affects travel price
|
||||||
|
'additionalServices', // Affects travel price
|
||||||
|
'board', // Affects travel price
|
||||||
|
'transportationOutbound', // Affects travel price
|
||||||
|
'transportationInbound', // Affects travel price
|
||||||
|
'pickupOutbound', // Affects travel price
|
||||||
|
'pickupInbound', // Affects travel price
|
||||||
|
'parking', // Affects travel price
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,8 +96,12 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
* Processes the insurance field for a specific participant.
|
* Processes the insurance field for a specific participant.
|
||||||
*
|
*
|
||||||
* This method handles both explicit insurance selection from form data and automatic
|
* This method handles both explicit insurance selection from form data and automatic
|
||||||
* reassignment when the participant's travel price changes. It maintains the same
|
* reassignment when the participant's travel price changes. It distinguishes between:
|
||||||
* insurance type but adjusts to the appropriate price tier when needed.
|
* 1. New selection: User explicitly changed their insurance choice
|
||||||
|
* 2. Resubmission: Form resubmitted with existing insurance (e.g., after adding rentals)
|
||||||
|
*
|
||||||
|
* For resubmissions, it checks if the current insurance is still eligible with updated
|
||||||
|
* participant data and automatically reassigns to the correct price tier if needed.
|
||||||
*
|
*
|
||||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||||
* @param BookingDtoInterface $bookingDto The booking DTO to update (create or edit)
|
* @param BookingDtoInterface $bookingDto The booking DTO to update (create or edit)
|
||||||
@@ -100,59 +118,63 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
$currentInsurance = $participant->insurance;
|
$currentInsurance = $participant->insurance;
|
||||||
$availableInsurances = $bookingDto->travel->insurances ?? [];
|
$availableInsurances = $bookingDto->travel->insurances ?? [];
|
||||||
|
|
||||||
// Handle explicit insurance selection from form
|
// Determine if this is a new user selection or just form resubmission
|
||||||
if (null !== $selectedInsuranceId) {
|
$isNewSelection = null !== $selectedInsuranceId
|
||||||
|
&& (null === $currentInsurance || !$this->isSameInsurance($selectedInsuranceId, $currentInsurance));
|
||||||
|
|
||||||
|
// Handle explicit new insurance selection from user
|
||||||
|
if ($isNewSelection) {
|
||||||
$selectedInsurance = $this->findInsuranceById($availableInsurances, $selectedInsuranceId);
|
$selectedInsurance = $this->findInsuranceById($availableInsurances, $selectedInsuranceId);
|
||||||
|
|
||||||
// Check if the selected insurance is still eligible for this participant
|
|
||||||
if (null !== $selectedInsurance) {
|
if (null !== $selectedInsurance) {
|
||||||
$eligibleInsurances = $this->insuranceMatchingService->getEligibleInsurances($availableInsurances, $participant, $bookingDto);
|
$eligibleInsurances = $this->insuranceMatchingService->getEligibleInsurances($availableInsurances, $participant, $bookingDto);
|
||||||
$isSelectedInsuranceEligible = $this->isInsuranceInList($selectedInsurance, $eligibleInsurances);
|
$isSelectedInsuranceEligible = $this->isInsuranceInList($selectedInsurance, $eligibleInsurances);
|
||||||
|
|
||||||
if ($isSelectedInsuranceEligible) {
|
if ($isSelectedInsuranceEligible) {
|
||||||
// Insurance is still eligible - use it directly
|
// New selection is eligible - use it
|
||||||
$participant->insurance = $selectedInsurance;
|
$participant->insurance = $selectedInsurance;
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
// Insurance is no longer eligible - try to reassign to same type with new price tier
|
// New selection is not eligible - try to find alternative in same type
|
||||||
$reassignedInsurance = $this->insuranceMatchingService->reassignInsuranceForPriceChange(
|
$reassignedInsurance = $this->insuranceMatchingService->reassignInsuranceForPriceChange(
|
||||||
$availableInsurances,
|
$availableInsurances,
|
||||||
$selectedInsurance,
|
$selectedInsurance,
|
||||||
$participant,
|
$participant,
|
||||||
$bookingDto
|
$bookingDto
|
||||||
);
|
);
|
||||||
|
|
||||||
$participant->insurance = $reassignedInsurance;
|
$participant->insurance = $reassignedInsurance;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Insurance not found - clear selection
|
// Insurance not found - clear selection
|
||||||
$participant->insurance = null;
|
$participant->insurance = null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle automatic reassignment if participant had an insurance but it's no longer eligible
|
// Handle form resubmission with existing insurance (automatic reassignment check)
|
||||||
if (null !== $currentInsurance) {
|
if (null !== $currentInsurance && null !== $selectedInsuranceId) {
|
||||||
// Check if current insurance is still eligible
|
// Check if current insurance is still eligible with updated participant data
|
||||||
$eligibleInsurances = $this->insuranceMatchingService->getEligibleInsurances($availableInsurances, $participant, $bookingDto);
|
$eligibleInsurances = $this->insuranceMatchingService->getEligibleInsurances($availableInsurances, $participant, $bookingDto);
|
||||||
$isCurrentInsuranceStillEligible = $this->isInsuranceInList($currentInsurance, $eligibleInsurances);
|
$isCurrentInsuranceStillEligible = $this->isInsuranceInList($currentInsurance, $eligibleInsurances);
|
||||||
|
|
||||||
if (!$isCurrentInsuranceStillEligible) {
|
if (!$isCurrentInsuranceStillEligible) {
|
||||||
// Try to reassign to same insurance type with appropriate price tier
|
// Current insurance no longer eligible - try to reassign to same type with new price tier
|
||||||
$reassignedInsurance = $this->insuranceMatchingService->reassignInsuranceForPriceChange(
|
$reassignedInsurance = $this->insuranceMatchingService->reassignInsuranceForPriceChange(
|
||||||
$availableInsurances,
|
$availableInsurances,
|
||||||
$currentInsurance,
|
$currentInsurance,
|
||||||
$participant,
|
$participant,
|
||||||
$bookingDto
|
$bookingDto
|
||||||
);
|
);
|
||||||
|
|
||||||
$participant->insurance = $reassignedInsurance; // null if no suitable match found
|
$participant->insurance = $reassignedInsurance; // null if no suitable match found
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No insurance selected or reassignment needed - keep current state (may be null)
|
// Handle explicit deselection (user removed insurance)
|
||||||
|
if (null === $selectedInsuranceId) {
|
||||||
|
$participant->insurance = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -221,4 +243,19 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the submitted insurance ID matches the current insurance.
|
||||||
|
*
|
||||||
|
* This is used to distinguish between a new user selection and a form resubmission
|
||||||
|
* with the existing insurance selection (e.g., when user adds rentals that change travel price).
|
||||||
|
*
|
||||||
|
* @param string|int $selectedInsuranceId The insurance ID from form submission
|
||||||
|
* @param Insurance $currentInsurance The currently assigned insurance from DTO
|
||||||
|
*
|
||||||
|
* @return bool True if they represent the same insurance
|
||||||
|
*/
|
||||||
|
private function isSameInsurance(string|int $selectedInsuranceId, Insurance $currentInsurance): bool
|
||||||
|
{
|
||||||
|
return (string) $currentInsurance->id === (string) $selectedInsuranceId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ class BookingPriceCalculatorService
|
|||||||
if ($parkingTotal > 0) {
|
if ($parkingTotal > 0) {
|
||||||
$transportationItems['transportation_parking'] = [
|
$transportationItems['transportation_parking'] = [
|
||||||
'serviceId' => 'transportation_parking',
|
'serviceId' => 'transportation_parking',
|
||||||
'label' => 'Parkplatz',
|
'label' => Constants::SERVICE_LABELS[Constants::TOKEN_PARKING],
|
||||||
'unitPrice' => null,
|
'unitPrice' => null,
|
||||||
'participantCount' => $parkingParticipants,
|
'participantCount' => $parkingParticipants,
|
||||||
'totalPrice' => $parkingTotal,
|
'totalPrice' => $parkingTotal,
|
||||||
@@ -444,23 +444,12 @@ class BookingPriceCalculatorService
|
|||||||
*/
|
*/
|
||||||
private function getGroupNameForSubtype(string $subType): string
|
private function getGroupNameForSubtype(string $subType): string
|
||||||
{
|
{
|
||||||
$groupMapping = [
|
|
||||||
Constants::TOKEN_COURSES => 'Kurse',
|
|
||||||
Constants::TOKEN_SKI_PASS => 'Skipässe',
|
|
||||||
Constants::TOKEN_ADDITIONAL => 'Zusatzleistungen',
|
|
||||||
Constants::TOKEN_BOARD => 'Verpflegung',
|
|
||||||
Constants::TOKEN_RENTAL_INSURANCE => 'Leihmaterial-Versicherung',
|
|
||||||
Constants::GROUP_TRANSPORTATION => 'Beförderung',
|
|
||||||
Constants::GROUP_RENTALS => 'Leihmaterial', // Normalized rental subtype
|
|
||||||
Constants::GROUP_INSURANCE => 'Reiseversicherungen', // Normalized insurance subtype
|
|
||||||
];
|
|
||||||
|
|
||||||
// Handle rentals array (keep for backward compatibility with non-normalized subtypes)
|
// Handle rentals array (keep for backward compatibility with non-normalized subtypes)
|
||||||
if (true === in_array($subType, Constants::TOKEN_RENTALS, true)) {
|
if (true === in_array($subType, Constants::TOKEN_RENTALS, true)) {
|
||||||
return 'Leihmaterial';
|
return Constants::SERVICE_LABELS[Constants::GROUP_RENTALS];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $groupMapping[$subType] ?? 'Sonstige Leistungen';
|
return Constants::SERVICE_LABELS[$subType] ?? 'Sonstige Leistungen';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -525,8 +514,8 @@ class BookingPriceCalculatorService
|
|||||||
/**
|
/**
|
||||||
* Calculates the total service cost for a single participant.
|
* Calculates the total service cost for a single participant.
|
||||||
*
|
*
|
||||||
* @param ParticipantDto $participant The participant to calculate services for
|
* @param ParticipantDto $participant The participant to calculate services for
|
||||||
* @param bool $includeInsurance Whether to include insurance pricing (default: true)
|
* @param bool $includeInsurance Whether to include insurance pricing (default: true)
|
||||||
*
|
*
|
||||||
* @return float The total service cost for this participant
|
* @return float The total service cost for this participant
|
||||||
*/
|
*/
|
||||||
@@ -612,7 +601,7 @@ class BookingPriceCalculatorService
|
|||||||
*
|
*
|
||||||
* @param array $serviceAggregation The service aggregation array to update
|
* @param array $serviceAggregation The service aggregation array to update
|
||||||
* @param Insurance $insurance The insurance to add
|
* @param Insurance $insurance The insurance to add
|
||||||
* @param int $quantity The quantity of the insurance
|
* @param int $quantity The quantity of the insurance
|
||||||
*/
|
*/
|
||||||
private function addInsuranceToServiceAggregation(array &$serviceAggregation, Insurance $insurance, int $quantity): void
|
private function addInsuranceToServiceAggregation(array &$serviceAggregation, Insurance $insurance, int $quantity): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ use App\BusProNet\Model\Insurance;
|
|||||||
use App\Form\Model\BookingCreateDto;
|
use App\Form\Model\BookingCreateDto;
|
||||||
use App\Form\Model\ParticipantDto;
|
use App\Form\Model\ParticipantDto;
|
||||||
use App\Model\InsuranceEligibilityCriteria;
|
use App\Model\InsuranceEligibilityCriteria;
|
||||||
use App\Service\BookingPriceCalculatorService;
|
|
||||||
use App\Service\InsuranceTypeResolver;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,9 +21,9 @@ class InsuranceMatchingService
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly BookingPriceCalculatorService $priceCalculatorService,
|
private readonly BookingPriceCalculatorService $priceCalculatorService,
|
||||||
private readonly InsuranceTypeResolver $insuranceTypeResolver
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters insurances based on participant and booking criteria.
|
* Filters insurances based on participant and booking criteria.
|
||||||
*
|
*
|
||||||
@@ -78,6 +76,10 @@ class InsuranceMatchingService
|
|||||||
/**
|
/**
|
||||||
* Batch-assigns insurances of the same type to all participants based on individual pricing.
|
* Batch-assigns insurances of the same type to all participants based on individual pricing.
|
||||||
*
|
*
|
||||||
|
* FUTURE FEATURE: This method will be used when implementing the "applicant assigns
|
||||||
|
* insurance to all participants" feature. The applicant's selection will be propagated
|
||||||
|
* to all participants with automatic price tier adjustment based on individual prices.
|
||||||
|
*
|
||||||
* This method takes the applicant's insurance selection and assigns the same insurance type
|
* 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
|
* (subType + familyInsurance) to all participants, but selects the appropriate price tier
|
||||||
* based on each participant's individual travel price.
|
* based on each participant's individual travel price.
|
||||||
@@ -87,6 +89,8 @@ class InsuranceMatchingService
|
|||||||
* @param BookingCreateDto $booking The booking with all participants
|
* @param BookingCreateDto $booking The booking with all participants
|
||||||
*
|
*
|
||||||
* @return array<int, Insurance|null> Array indexed by participant index with assigned insurances
|
* @return array<int, Insurance|null> Array indexed by participant index with assigned insurances
|
||||||
|
*
|
||||||
|
* @internal Reserved for future feature implementation
|
||||||
*/
|
*/
|
||||||
public function batchAssignInsuranceToParticipants(array $availableInsurances, Insurance $selectedInsurance, BookingCreateDto $booking): array
|
public function batchAssignInsuranceToParticipants(array $availableInsurances, Insurance $selectedInsurance, BookingCreateDto $booking): array
|
||||||
{
|
{
|
||||||
@@ -106,9 +110,13 @@ class InsuranceMatchingService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates eligibility criteria from participant and booking data.
|
* Creates eligibility criteria from participant and booking data.
|
||||||
|
*
|
||||||
|
* This method performs early validation before creating the criteria object
|
||||||
|
* to avoid unnecessary object instantiation when criteria cannot be satisfied.
|
||||||
*/
|
*/
|
||||||
private function createEligibilityCriteria(ParticipantDto $participant, BookingCreateDto $booking): ?InsuranceEligibilityCriteria
|
private function createEligibilityCriteria(ParticipantDto $participant, BookingCreateDto $booking): ?InsuranceEligibilityCriteria
|
||||||
{
|
{
|
||||||
|
// Early return if travel dates are missing - cannot evaluate any criteria
|
||||||
$travelStartDate = $booking->travel->dateFrom;
|
$travelStartDate = $booking->travel->dateFrom;
|
||||||
$travelEndDate = $booking->travel->dateTo;
|
$travelEndDate = $booking->travel->dateTo;
|
||||||
|
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Service;
|
|
||||||
|
|
||||||
use App\BusProNet\Model\Insurance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves insurance types for both individual insurances and packages.
|
|
||||||
*
|
|
||||||
* This service provides consistent type resolution by mapping insurance subtypes
|
|
||||||
* to standardized type constants. For packages, it analyzes contained insurances
|
|
||||||
* to determine the dominant (non-deductible) type and applies family variants
|
|
||||||
* based on the familyInsurance flag.
|
|
||||||
*/
|
|
||||||
class InsuranceTypeResolver
|
|
||||||
{
|
|
||||||
public const TRAVEL_CANCELLATION = 'TRAVEL_CANCELLATION';
|
|
||||||
public const TRAVEL_CANCELLATION_FAMILY = 'TRAVEL_CANCELLATION_FAMILY';
|
|
||||||
public const TRAVEL_PROTECTION = 'TRAVEL_PROTECTION';
|
|
||||||
public const TRAVEL_PROTECTION_FAMILY = 'TRAVEL_PROTECTION_FAMILY';
|
|
||||||
public const DEDUCTIBLE = 'DEDUCTIBLE';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the standardized type for an insurance or package.
|
|
||||||
*
|
|
||||||
* @param Insurance $insurance The insurance to resolve the type for
|
|
||||||
*
|
|
||||||
* @return string|null The resolved type constant or null if unresolvable
|
|
||||||
*/
|
|
||||||
public function resolveType(Insurance $insurance): ?string
|
|
||||||
{
|
|
||||||
if (!$insurance->package) {
|
|
||||||
return $this->mapSubtypeToType($insurance->subType, $insurance->familyInsurance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->resolvePackageType($insurance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the type for an insurance package by analyzing contained insurances.
|
|
||||||
*
|
|
||||||
* @param Insurance $package The package to analyze
|
|
||||||
*
|
|
||||||
* @return string|null The resolved type or null if no primary type found
|
|
||||||
*/
|
|
||||||
private function resolvePackageType(Insurance $package): ?string
|
|
||||||
{
|
|
||||||
// Get primary (non-deductible) insurance type from contained insurances
|
|
||||||
$primaryType = null;
|
|
||||||
foreach ($package->containedInsurances as $contained) {
|
|
||||||
$baseType = $this->mapSubtypeToBaseType($contained->subType);
|
|
||||||
if ($baseType && self::DEDUCTIBLE !== $baseType) {
|
|
||||||
$primaryType = $baseType;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply family suffix using package's familyInsurance flag
|
|
||||||
return $primaryType ? $primaryType.($package->familyInsurance ? '_FAMILY' : '') : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a subtype to a full type including family variant.
|
|
||||||
*
|
|
||||||
* @param string|null $subtype The subtype to map
|
|
||||||
* @param bool $isFamily Whether this is a family insurance
|
|
||||||
*
|
|
||||||
* @return string|null The mapped type or null if unmappable
|
|
||||||
*/
|
|
||||||
private function mapSubtypeToType(?string $subtype, bool $isFamily): ?string
|
|
||||||
{
|
|
||||||
$baseType = $this->mapSubtypeToBaseType($subtype);
|
|
||||||
|
|
||||||
return $baseType ? $baseType.($isFamily ? '_FAMILY' : '') : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a subtype to its base type constant.
|
|
||||||
*
|
|
||||||
* @param string|null $subtype The subtype to map
|
|
||||||
*
|
|
||||||
* @return string|null The base type constant or null if unmappable
|
|
||||||
*/
|
|
||||||
private function mapSubtypeToBaseType(?string $subtype): ?string
|
|
||||||
{
|
|
||||||
return match ($subtype) {
|
|
||||||
'RRV' => self::TRAVEL_CANCELLATION,
|
|
||||||
'PAK' => self::TRAVEL_PROTECTION,
|
|
||||||
'OHN' => self::DEDUCTIBLE,
|
|
||||||
default => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all available insurance type constants.
|
|
||||||
*
|
|
||||||
* @return array<string> Array of all type constants
|
|
||||||
*/
|
|
||||||
public function getAllTypes(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
self::TRAVEL_CANCELLATION,
|
|
||||||
self::TRAVEL_CANCELLATION_FAMILY,
|
|
||||||
self::TRAVEL_PROTECTION,
|
|
||||||
self::TRAVEL_PROTECTION_FAMILY,
|
|
||||||
self::DEDUCTIBLE,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a type is a family variant.
|
|
||||||
*
|
|
||||||
* @param string $type The type to check
|
|
||||||
*
|
|
||||||
* @return bool True if the type is a family variant
|
|
||||||
*/
|
|
||||||
public function isFamilyType(string $type): bool
|
|
||||||
{
|
|
||||||
return str_ends_with($type, '_FAMILY');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the base type from a family type.
|
|
||||||
*
|
|
||||||
* @param string $type The type to get the base for
|
|
||||||
*
|
|
||||||
* @return string The base type (without _FAMILY suffix)
|
|
||||||
*/
|
|
||||||
public function getBaseType(string $type): string
|
|
||||||
{
|
|
||||||
return str_replace('_FAMILY', '', $type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div></div>{# Empty div to maintain grid layout when skipass not available #}
|
<div></div>{# Empty div to maintain grid layout when not available #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.courses is defined %}
|
{% if participant.courses is defined %}
|
||||||
{{ form_row(participant.courses, {
|
{{ form_row(participant.courses, {
|
||||||
@@ -105,6 +105,8 @@
|
|||||||
'hx-swap': 'none'
|
'hx-swap': 'none'
|
||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
|
{% else %}
|
||||||
|
<div></div>{# Empty div to maintain grid layout when not available #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.additionalServices is defined %}
|
{% if participant.additionalServices is defined %}
|
||||||
{{ form_row(participant.additionalServices, {
|
{{ form_row(participant.additionalServices, {
|
||||||
@@ -114,6 +116,8 @@
|
|||||||
'hx-swap': 'none'
|
'hx-swap': 'none'
|
||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
|
{% else %}
|
||||||
|
<div></div>{# Empty div to maintain grid layout when not available #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.rentals is defined %}
|
{% if participant.rentals is defined %}
|
||||||
{{ form_row(participant.rentals, {
|
{{ form_row(participant.rentals, {
|
||||||
@@ -123,6 +127,8 @@
|
|||||||
'hx-swap': 'none'
|
'hx-swap': 'none'
|
||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
|
{% else %}
|
||||||
|
<div></div>{# Empty div to maintain grid layout when not available #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.rentalInsurance is defined %}
|
{% if participant.rentalInsurance is defined %}
|
||||||
{{ form_row(participant.rentalInsurance, {
|
{{ form_row(participant.rentalInsurance, {
|
||||||
@@ -132,6 +138,8 @@
|
|||||||
'hx-swap': 'none'
|
'hx-swap': 'none'
|
||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
|
{% else %}
|
||||||
|
<div></div>{# Empty div to maintain grid layout when not available #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.board is defined %}
|
{% if participant.board is defined %}
|
||||||
{{ form_row(participant.board, {
|
{{ form_row(participant.board, {
|
||||||
@@ -141,6 +149,8 @@
|
|||||||
'hx-swap': 'none'
|
'hx-swap': 'none'
|
||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
|
{% else %}
|
||||||
|
<div></div>{# Empty div to maintain grid layout when not available #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.insurance is defined %}
|
{% if participant.insurance is defined %}
|
||||||
{{ form_row(participant.insurance, {
|
{{ form_row(participant.insurance, {
|
||||||
@@ -150,6 +160,8 @@
|
|||||||
'hx-swap': 'none'
|
'hx-swap': 'none'
|
||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
|
{% else %}
|
||||||
|
<div></div>{# Empty div to maintain grid layout when not available #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Tests\Service;
|
|||||||
use App\BusProNet\Model\Insurance;
|
use App\BusProNet\Model\Insurance;
|
||||||
use App\Form\Model\BookingCreateDto;
|
use App\Form\Model\BookingCreateDto;
|
||||||
use App\Form\Model\ParticipantDto;
|
use App\Form\Model\ParticipantDto;
|
||||||
|
use App\Service\BookingPriceCalculatorService;
|
||||||
use App\Service\InsuranceMatchingService;
|
use App\Service\InsuranceMatchingService;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -14,10 +15,17 @@ use PHPUnit\Framework\TestCase;
|
|||||||
class InsuranceMatchingServiceTest extends TestCase
|
class InsuranceMatchingServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
private InsuranceMatchingService $service;
|
private InsuranceMatchingService $service;
|
||||||
|
private BookingPriceCalculatorService $priceCalculator;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->service = new InsuranceMatchingService();
|
// Mock the price calculator service
|
||||||
|
$this->priceCalculator = $this->createMock(BookingPriceCalculatorService::class);
|
||||||
|
$this->priceCalculator
|
||||||
|
->method('calculateIndividualParticipantPriceExcludingInsurance')
|
||||||
|
->willReturn(500.0); // Default test price
|
||||||
|
|
||||||
|
$this->service = new InsuranceMatchingService($this->priceCalculator);
|
||||||
|
|
||||||
// Set a fixed test date for consistent test results
|
// Set a fixed test date for consistent test results
|
||||||
Carbon::setTestNow('2024-06-01 12:00:00');
|
Carbon::setTestNow('2024-06-01 12:00:00');
|
||||||
@@ -164,6 +172,7 @@ class InsuranceMatchingServiceTest extends TestCase
|
|||||||
public function testGetEligibleInsurancesHandlesParticipantWithoutBirthDate(): void
|
public function testGetEligibleInsurancesHandlesParticipantWithoutBirthDate(): void
|
||||||
{
|
{
|
||||||
$participant = new ParticipantDto();
|
$participant = new ParticipantDto();
|
||||||
|
$participant->index = 0; // Set participant index for price calculations
|
||||||
$participant->dateOfBirth = null;
|
$participant->dateOfBirth = null;
|
||||||
$booking = $this->createBooking('2024-08-01', '2024-08-08');
|
$booking = $this->createBooking('2024-08-01', '2024-08-08');
|
||||||
|
|
||||||
@@ -239,6 +248,7 @@ class InsuranceMatchingServiceTest extends TestCase
|
|||||||
private function createParticipant(string $dateOfBirth): ParticipantDto
|
private function createParticipant(string $dateOfBirth): ParticipantDto
|
||||||
{
|
{
|
||||||
$participant = new ParticipantDto();
|
$participant = new ParticipantDto();
|
||||||
|
$participant->index = 0; // Set participant index for price calculations
|
||||||
$participant->dateOfBirth = new \DateTimeImmutable($dateOfBirth);
|
$participant->dateOfBirth = new \DateTimeImmutable($dateOfBirth);
|
||||||
|
|
||||||
return $participant;
|
return $participant;
|
||||||
|
|||||||
@@ -1,257 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Tests\Service;
|
|
||||||
|
|
||||||
use App\BusProNet\Model\Insurance;
|
|
||||||
use App\Service\InsuranceTypeResolver;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class InsuranceTypeResolverTest extends TestCase
|
|
||||||
{
|
|
||||||
private InsuranceTypeResolver $resolver;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
$this->resolver = new InsuranceTypeResolver();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForIndividualTravelCancellationInsurance(): void
|
|
||||||
{
|
|
||||||
$insurance = new Insurance();
|
|
||||||
$insurance->package = false;
|
|
||||||
$insurance->subType = 'RRV';
|
|
||||||
$insurance->familyInsurance = false;
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($insurance);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::TRAVEL_CANCELLATION, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForIndividualFamilyTravelCancellationInsurance(): void
|
|
||||||
{
|
|
||||||
$insurance = new Insurance();
|
|
||||||
$insurance->package = false;
|
|
||||||
$insurance->subType = 'RRV';
|
|
||||||
$insurance->familyInsurance = true;
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($insurance);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::TRAVEL_CANCELLATION_FAMILY, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForIndividualTravelProtectionInsurance(): void
|
|
||||||
{
|
|
||||||
$insurance = new Insurance();
|
|
||||||
$insurance->package = false;
|
|
||||||
$insurance->subType = 'PAK';
|
|
||||||
$insurance->familyInsurance = false;
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($insurance);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::TRAVEL_PROTECTION, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForIndividualFamilyTravelProtectionInsurance(): void
|
|
||||||
{
|
|
||||||
$insurance = new Insurance();
|
|
||||||
$insurance->package = false;
|
|
||||||
$insurance->subType = 'PAK';
|
|
||||||
$insurance->familyInsurance = true;
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($insurance);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::TRAVEL_PROTECTION_FAMILY, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForIndividualDeductibleInsurance(): void
|
|
||||||
{
|
|
||||||
$insurance = new Insurance();
|
|
||||||
$insurance->package = false;
|
|
||||||
$insurance->subType = 'OHN';
|
|
||||||
$insurance->familyInsurance = false;
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($insurance);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::DEDUCTIBLE, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForIndividualUnknownSubtype(): void
|
|
||||||
{
|
|
||||||
$insurance = new Insurance();
|
|
||||||
$insurance->package = false;
|
|
||||||
$insurance->subType = 'UNKNOWN';
|
|
||||||
$insurance->familyInsurance = false;
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($insurance);
|
|
||||||
|
|
||||||
$this->assertNull($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForIndividualNullSubtype(): void
|
|
||||||
{
|
|
||||||
$insurance = new Insurance();
|
|
||||||
$insurance->package = false;
|
|
||||||
$insurance->subType = null;
|
|
||||||
$insurance->familyInsurance = false;
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($insurance);
|
|
||||||
|
|
||||||
$this->assertNull($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForPackageWithTravelProtectionContent(): void
|
|
||||||
{
|
|
||||||
// Create contained insurances
|
|
||||||
$protectionInsurance = new Insurance();
|
|
||||||
$protectionInsurance->subType = 'PAK';
|
|
||||||
|
|
||||||
$deductibleInsurance = new Insurance();
|
|
||||||
$deductibleInsurance->subType = 'OHN';
|
|
||||||
|
|
||||||
// Create package
|
|
||||||
$package = new Insurance();
|
|
||||||
$package->package = true;
|
|
||||||
$package->familyInsurance = false;
|
|
||||||
$package->containedInsurances = [$protectionInsurance, $deductibleInsurance];
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($package);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::TRAVEL_PROTECTION, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForFamilyPackageWithTravelProtectionContent(): void
|
|
||||||
{
|
|
||||||
// Create contained insurances
|
|
||||||
$protectionInsurance = new Insurance();
|
|
||||||
$protectionInsurance->subType = 'PAK';
|
|
||||||
|
|
||||||
$deductibleInsurance = new Insurance();
|
|
||||||
$deductibleInsurance->subType = 'OHN';
|
|
||||||
|
|
||||||
// Create family package
|
|
||||||
$package = new Insurance();
|
|
||||||
$package->package = true;
|
|
||||||
$package->familyInsurance = true;
|
|
||||||
$package->containedInsurances = [$protectionInsurance, $deductibleInsurance];
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($package);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::TRAVEL_PROTECTION_FAMILY, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForPackageWithTravelCancellationContent(): void
|
|
||||||
{
|
|
||||||
// Create contained insurances
|
|
||||||
$cancellationInsurance = new Insurance();
|
|
||||||
$cancellationInsurance->subType = 'RRV';
|
|
||||||
|
|
||||||
$deductibleInsurance = new Insurance();
|
|
||||||
$deductibleInsurance->subType = 'OHN';
|
|
||||||
|
|
||||||
// Create package
|
|
||||||
$package = new Insurance();
|
|
||||||
$package->package = true;
|
|
||||||
$package->familyInsurance = false;
|
|
||||||
$package->containedInsurances = [$cancellationInsurance, $deductibleInsurance];
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($package);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::TRAVEL_CANCELLATION, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForPackageWithOnlyDeductibleContent(): void
|
|
||||||
{
|
|
||||||
// Create contained insurance (only deductible)
|
|
||||||
$deductibleInsurance = new Insurance();
|
|
||||||
$deductibleInsurance->subType = 'OHN';
|
|
||||||
|
|
||||||
// Create package
|
|
||||||
$package = new Insurance();
|
|
||||||
$package->package = true;
|
|
||||||
$package->familyInsurance = false;
|
|
||||||
$package->containedInsurances = [$deductibleInsurance];
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($package);
|
|
||||||
|
|
||||||
$this->assertNull($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForPackageWithNoContainedInsurances(): void
|
|
||||||
{
|
|
||||||
$package = new Insurance();
|
|
||||||
$package->package = true;
|
|
||||||
$package->familyInsurance = false;
|
|
||||||
$package->containedInsurances = [];
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($package);
|
|
||||||
|
|
||||||
$this->assertNull($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveTypeForPackageWithMultiplePrimaryTypes(): void
|
|
||||||
{
|
|
||||||
// Create contained insurances with multiple primary types
|
|
||||||
$protectionInsurance = new Insurance();
|
|
||||||
$protectionInsurance->subType = 'PAK';
|
|
||||||
|
|
||||||
$cancellationInsurance = new Insurance();
|
|
||||||
$cancellationInsurance->subType = 'RRV';
|
|
||||||
|
|
||||||
// Create package - should return first non-deductible type found
|
|
||||||
$package = new Insurance();
|
|
||||||
$package->package = true;
|
|
||||||
$package->familyInsurance = false;
|
|
||||||
$package->containedInsurances = [$protectionInsurance, $cancellationInsurance];
|
|
||||||
|
|
||||||
$result = $this->resolver->resolveType($package);
|
|
||||||
|
|
||||||
$this->assertEquals(InsuranceTypeResolver::TRAVEL_PROTECTION, $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetAllTypes(): void
|
|
||||||
{
|
|
||||||
$types = $this->resolver->getAllTypes();
|
|
||||||
|
|
||||||
$expectedTypes = [
|
|
||||||
InsuranceTypeResolver::TRAVEL_CANCELLATION,
|
|
||||||
InsuranceTypeResolver::TRAVEL_CANCELLATION_FAMILY,
|
|
||||||
InsuranceTypeResolver::TRAVEL_PROTECTION,
|
|
||||||
InsuranceTypeResolver::TRAVEL_PROTECTION_FAMILY,
|
|
||||||
InsuranceTypeResolver::DEDUCTIBLE,
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertEquals($expectedTypes, $types);
|
|
||||||
$this->assertCount(5, $types);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testIsFamilyType(): void
|
|
||||||
{
|
|
||||||
$this->assertTrue($this->resolver->isFamilyType(InsuranceTypeResolver::TRAVEL_CANCELLATION_FAMILY));
|
|
||||||
$this->assertTrue($this->resolver->isFamilyType(InsuranceTypeResolver::TRAVEL_PROTECTION_FAMILY));
|
|
||||||
$this->assertFalse($this->resolver->isFamilyType(InsuranceTypeResolver::TRAVEL_CANCELLATION));
|
|
||||||
$this->assertFalse($this->resolver->isFamilyType(InsuranceTypeResolver::TRAVEL_PROTECTION));
|
|
||||||
$this->assertFalse($this->resolver->isFamilyType(InsuranceTypeResolver::DEDUCTIBLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetBaseType(): void
|
|
||||||
{
|
|
||||||
$this->assertEquals(
|
|
||||||
InsuranceTypeResolver::TRAVEL_CANCELLATION,
|
|
||||||
$this->resolver->getBaseType(InsuranceTypeResolver::TRAVEL_CANCELLATION_FAMILY)
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
InsuranceTypeResolver::TRAVEL_PROTECTION,
|
|
||||||
$this->resolver->getBaseType(InsuranceTypeResolver::TRAVEL_PROTECTION_FAMILY)
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
InsuranceTypeResolver::TRAVEL_CANCELLATION,
|
|
||||||
$this->resolver->getBaseType(InsuranceTypeResolver::TRAVEL_CANCELLATION)
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
InsuranceTypeResolver::DEDUCTIBLE,
|
|
||||||
$this->resolver->getBaseType(InsuranceTypeResolver::DEDUCTIBLE)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user