feat: bulk insurance booking by applicant
This commit is contained in:
@@ -67,8 +67,9 @@ services:
|
|||||||
arguments:
|
arguments:
|
||||||
$choiceListFactory: '@form.choice_list_factory.default'
|
$choiceListFactory: '@form.choice_list_factory.default'
|
||||||
|
|
||||||
# Insurance Field Handler with dependencies
|
# Insurance Field Handlers with dependencies
|
||||||
App\Form\Service\ParticipantInsuranceFieldHandler: ~
|
App\Form\Service\ParticipantInsuranceFieldHandler: ~
|
||||||
|
App\Form\Service\ParticipantBulkInsuranceFieldHandler: ~
|
||||||
|
|
||||||
# Participant Field Handler Registry - most handlers instantiate dependencies directly, some use services
|
# Participant Field Handler Registry - most handlers instantiate dependencies directly, some use services
|
||||||
App\Form\Service\ParticipantFieldHandlerRegistry:
|
App\Form\Service\ParticipantFieldHandlerRegistry:
|
||||||
@@ -91,4 +92,5 @@ services:
|
|||||||
- 'App\Form\Service\ParticipantRentalInsuranceFieldHandler'
|
- 'App\Form\Service\ParticipantRentalInsuranceFieldHandler'
|
||||||
- 'App\Form\Service\ParticipantLicensePlateFieldHandler'
|
- 'App\Form\Service\ParticipantLicensePlateFieldHandler'
|
||||||
# Complex handlers with dependencies - use service references
|
# Complex handlers with dependencies - use service references
|
||||||
|
- '@App\Form\Service\ParticipantBulkInsuranceFieldHandler'
|
||||||
- '@App\Form\Service\ParticipantInsuranceFieldHandler'
|
- '@App\Form\Service\ParticipantInsuranceFieldHandler'
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
'pickupInbound',
|
'pickupInbound',
|
||||||
'parking',
|
'parking',
|
||||||
'licensePlate',
|
'licensePlate',
|
||||||
|
'bulkInsuranceBooking',
|
||||||
'insurance',
|
'insurance',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -216,7 +217,7 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
$dynamicFields = [
|
$dynamicFields = [
|
||||||
'assignedRoomId', 'remarksRoom', 'courses', 'additionalServices', 'board', 'rentals', 'rentalInsurance',
|
'assignedRoomId', 'remarksRoom', 'courses', 'additionalServices', 'board', 'rentals', 'rentalInsurance',
|
||||||
'skiPass', 'transportationOutbound', 'transportationInbound', 'pickupOutbound', 'pickupInbound', 'parking',
|
'skiPass', 'transportationOutbound', 'transportationInbound', 'pickupOutbound', 'pickupInbound', 'parking',
|
||||||
'licensePlate', 'insurance',
|
'licensePlate', 'bulkInsuranceBooking', 'insurance',
|
||||||
];
|
];
|
||||||
foreach ($dynamicFields as $fieldName) {
|
foreach ($dynamicFields as $fieldName) {
|
||||||
if ($form->has($fieldName)) {
|
if ($form->has($fieldName)) {
|
||||||
@@ -248,6 +249,7 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
'pickupInbound' => ChoiceType::class,
|
'pickupInbound' => ChoiceType::class,
|
||||||
'parking' => CheckboxType::class,
|
'parking' => CheckboxType::class,
|
||||||
'licensePlate' => TextType::class,
|
'licensePlate' => TextType::class,
|
||||||
|
'bulkInsuranceBooking' => CheckboxType::class,
|
||||||
'insurance' => ChoiceType::class,
|
'insurance' => ChoiceType::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ class ParticipantDto
|
|||||||
// Selected insurance for this participant (individual insurance selection per participant)
|
// Selected insurance for this participant (individual insurance selection per participant)
|
||||||
public ?Insurance $insurance = null;
|
public ?Insurance $insurance = null;
|
||||||
|
|
||||||
|
// Bulk insurance booking flag (applicant only: when checked, assigns same insurance type to all participants)
|
||||||
|
public bool $bulkInsuranceBooking = false;
|
||||||
|
|
||||||
public static function fromPersonalData(PersonalData $personalData): static
|
public static function fromPersonalData(PersonalData $personalData): static
|
||||||
{
|
{
|
||||||
$instance = new static();
|
$instance = new static();
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service\Condition;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Condition that evaluates whether bulk insurance booking is active for dependent participants.
|
||||||
|
*
|
||||||
|
* This condition determines if the applicant (first participant) has enabled bulk insurance
|
||||||
|
* booking for all participants. When active, dependent participants (index > 0) will have
|
||||||
|
* their insurance field replaced with a "wie Anmelder" (same as applicant) message, and the
|
||||||
|
* applicant's insurance selection (or lack thereof) will be automatically applied to all
|
||||||
|
* participants based on their individual travel prices and age constraints.
|
||||||
|
*
|
||||||
|
* The condition is satisfied when:
|
||||||
|
* 1. Evaluating a dependent participant (not the applicant)
|
||||||
|
* 2. The applicant has bulkInsuranceBooking flag set to true
|
||||||
|
*/
|
||||||
|
class BulkInsuranceBookingCondition implements FieldConditionInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Evaluates if bulk insurance booking is active for the given participant.
|
||||||
|
*
|
||||||
|
* For the applicant (index 0), always returns false since they control the bulk booking.
|
||||||
|
* For dependent participants, returns true if the applicant has bulk insurance booking enabled,
|
||||||
|
* regardless of whether an insurance is selected (applies to "no insurance" as well).
|
||||||
|
*
|
||||||
|
* @param BookingDtoInterface $bookingDto The current booking data
|
||||||
|
* @param int $participantIndex The index of the participant being evaluated
|
||||||
|
* @param array<string, mixed> $formData Current form data for condition evaluation
|
||||||
|
*
|
||||||
|
* @return bool True if bulk insurance booking is active for this dependent participant
|
||||||
|
*/
|
||||||
|
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||||
|
{
|
||||||
|
// Applicant is never affected by bulk insurance booking (they control it)
|
||||||
|
if (0 === $participantIndex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get applicant data
|
||||||
|
$applicant = $bookingDto->getParticipant(0);
|
||||||
|
if (null === $applicant) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if applicant has bulk insurance booking enabled
|
||||||
|
// No need to check for insurance selection - bulk applies even when no insurance is selected
|
||||||
|
return $this->getBulkInsuranceBookingValue($formData, $applicant);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns field names that this condition depends on.
|
||||||
|
*
|
||||||
|
* This condition depends on the applicant's bulkInsuranceBooking flag.
|
||||||
|
* Any changes to this field should trigger re-evaluation of dependent participant field states.
|
||||||
|
*
|
||||||
|
* @return string[] Array containing field names this condition depends on
|
||||||
|
*/
|
||||||
|
public function getDependentFields(): array
|
||||||
|
{
|
||||||
|
return ['bulkInsuranceBooking'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a human-readable description of this condition.
|
||||||
|
*
|
||||||
|
* @return string Description of the bulk insurance booking condition
|
||||||
|
*/
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Applicant has bulk insurance booking enabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bulk insurance booking flag value from form data or participant DTO.
|
||||||
|
*/
|
||||||
|
private function getBulkInsuranceBookingValue(array $formData, object $applicant): bool
|
||||||
|
{
|
||||||
|
// First check form data (for fresh submissions)
|
||||||
|
if (isset($formData['participants'][0]['bulkInsuranceBooking'])) {
|
||||||
|
return (bool) $formData['participants'][0]['bulkInsuranceBooking'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to participant DTO
|
||||||
|
if (property_exists($applicant, 'bulkInsuranceBooking')) {
|
||||||
|
return (bool) $applicant->bulkInsuranceBooking;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace App\Form\Service;
|
|||||||
|
|
||||||
use App\BusProNet\Utility\DirectionMapper;
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||||
|
use App\Form\Service\Condition\BulkInsuranceBookingCondition;
|
||||||
use App\Form\Service\Condition\CompositeCondition;
|
use App\Form\Service\Condition\CompositeCondition;
|
||||||
use App\Form\Service\Condition\DateOfBirthProvidedCondition;
|
use App\Form\Service\Condition\DateOfBirthProvidedCondition;
|
||||||
use App\Form\Service\Condition\FieldValueCondition;
|
use App\Form\Service\Condition\FieldValueCondition;
|
||||||
@@ -87,9 +88,38 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
|
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Hide insurance field until date of birth is provided
|
// Bulk insurance booking conditions
|
||||||
|
$bulkInsuranceBookingCondition = new BulkInsuranceBookingCondition();
|
||||||
|
|
||||||
|
// Show bulk insurance booking checkbox ONLY for applicant (index 0) and when insurance field is visible
|
||||||
|
$this->fieldStateConditions['bulkInsuranceBooking'] = [
|
||||||
|
'hidden' => CompositeCondition::or(
|
||||||
|
CompositeCondition::not($dateOfBirthProvidedCondition), // Hide until date of birth provided
|
||||||
|
new class() implements \App\Form\Service\Contract\FieldConditionInterface {
|
||||||
|
public function evaluate(\App\Form\Model\BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||||
|
{
|
||||||
|
return $participantIndex > 0; // Hide for all participants except applicant
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDependentFields(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Participant is not the applicant';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Hide insurance field until date of birth is provided OR when bulk insurance booking is active for dependent participants
|
||||||
$this->fieldStateConditions['insurance'] = [
|
$this->fieldStateConditions['insurance'] = [
|
||||||
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
|
'hidden' => CompositeCondition::or(
|
||||||
|
CompositeCondition::not($dateOfBirthProvidedCondition),
|
||||||
|
$bulkInsuranceBookingCondition
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Room-specific field conditions
|
// Room-specific field conditions
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingCreateDto;
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||||
|
use App\Service\InsuranceMatchingService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles bulk insurance booking for the applicant (first participant).
|
||||||
|
*
|
||||||
|
* When the applicant enables bulk insurance booking, their selected insurance type
|
||||||
|
* (subType + familyInsurance) is automatically assigned to all participants with
|
||||||
|
* automatic price tier adjustment based on each participant's individual travel price.
|
||||||
|
*
|
||||||
|
* This handler processes the bulkInsuranceBooking checkbox state and triggers
|
||||||
|
* insurance assignment to dependent participants when enabled.
|
||||||
|
*
|
||||||
|
* Dependencies: insurance (applicant must have insurance selected before enabling bulk)
|
||||||
|
*/
|
||||||
|
class ParticipantBulkInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly InsuranceMatchingService $insuranceMatchingService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFieldName(): string
|
||||||
|
{
|
||||||
|
return 'bulkInsuranceBooking';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDependencies(): array
|
||||||
|
{
|
||||||
|
// Depends on insurance field to ensure insurance is selected before bulk assignment
|
||||||
|
return ['insurance'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this handler should process the field.
|
||||||
|
*
|
||||||
|
* Only process for the applicant (index 0). Dependent participants don't have
|
||||||
|
* the bulkInsuranceBooking checkbox - their insurance is controlled by the handler.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||||
|
* @param int $participantIndex The index of the participant being processed
|
||||||
|
*
|
||||||
|
* @return bool True if this is the applicant, false otherwise
|
||||||
|
*/
|
||||||
|
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
||||||
|
{
|
||||||
|
return 0 === $participantIndex; // Only process for applicant
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the bulk insurance booking checkbox for the applicant.
|
||||||
|
*
|
||||||
|
* When bulk insurance is enabled and applicant has insurance selected,
|
||||||
|
* assigns the same insurance type to all participants based on their
|
||||||
|
* individual pricing and eligibility criteria.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||||
|
* @param BookingDtoInterface $bookingDto The booking DTO to update
|
||||||
|
* @param int $participantIndex The index of the participant (must be 0)
|
||||||
|
*/
|
||||||
|
public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void
|
||||||
|
{
|
||||||
|
$participant = $this->getParticipant($bookingDto, $participantIndex);
|
||||||
|
if (null === $participant || !$bookingDto instanceof BookingCreateDto) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get checkbox value from submitted data
|
||||||
|
$bulkInsuranceBooking = $this->getFieldValue($submittedData, $this->getFieldName());
|
||||||
|
$isBulkEnabled = (bool) $bulkInsuranceBooking;
|
||||||
|
|
||||||
|
// Store checkbox state
|
||||||
|
$participant->bulkInsuranceBooking = $isBulkEnabled;
|
||||||
|
|
||||||
|
// If bulk insurance is enabled and applicant has insurance, assign to all participants
|
||||||
|
if (true === $isBulkEnabled && null !== $participant->insurance) {
|
||||||
|
$this->applyBulkInsuranceToAllParticipants($bookingDto, $participant->insurance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If bulk insurance is disabled, clear dependent participants' insurances
|
||||||
|
if (false === $isBulkEnabled) {
|
||||||
|
$this->clearDependentParticipantsInsurance($bookingDto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the applicant's insurance type to all participants with automatic price tier adjustment.
|
||||||
|
*
|
||||||
|
* Uses InsuranceMatchingService to find the appropriate price tier for each participant
|
||||||
|
* based on their individual travel price and eligibility criteria.
|
||||||
|
*
|
||||||
|
* @param BookingCreateDto $bookingDto The booking DTO with all participants
|
||||||
|
* @param object $applicantInsurance The insurance selected by the applicant
|
||||||
|
*/
|
||||||
|
private function applyBulkInsuranceToAllParticipants(BookingCreateDto $bookingDto, object $applicantInsurance): void
|
||||||
|
{
|
||||||
|
$availableInsurances = $bookingDto->travel->insurances ?? [];
|
||||||
|
|
||||||
|
// Get insurance assignments for all participants
|
||||||
|
$assignments = $this->insuranceMatchingService->batchAssignInsuranceToParticipants(
|
||||||
|
$availableInsurances,
|
||||||
|
$applicantInsurance,
|
||||||
|
$bookingDto
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply assignments to participants (skip applicant index 0, they keep their selection)
|
||||||
|
foreach ($assignments as $index => $insurance) {
|
||||||
|
if (0 === $index) {
|
||||||
|
continue; // Skip applicant
|
||||||
|
}
|
||||||
|
|
||||||
|
$participant = $bookingDto->getParticipant($index);
|
||||||
|
if (null !== $participant) {
|
||||||
|
$participant->insurance = $insurance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears insurance selections for dependent participants when bulk booking is disabled.
|
||||||
|
*
|
||||||
|
* @param BookingCreateDto $bookingDto The booking DTO with all participants
|
||||||
|
*/
|
||||||
|
private function clearDependentParticipantsInsurance(BookingCreateDto $bookingDto): void
|
||||||
|
{
|
||||||
|
foreach ($bookingDto->getParticipants() as $index => $participant) {
|
||||||
|
if (0 === $index) {
|
||||||
|
continue; // Skip applicant
|
||||||
|
}
|
||||||
|
|
||||||
|
$participant->insurance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -405,6 +405,17 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
'required' => false,
|
'required' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Bulk insurance booking checkbox (applicant only - controls insurance assignment for all participants)
|
||||||
|
$this->fieldOptionProviders['bulkInsuranceBooking'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
|
||||||
|
'label' => 'Für alle Teilnehmer buchen',
|
||||||
|
'required' => false,
|
||||||
|
'attr' => [
|
||||||
|
'hx-post' => $this->urlGenerator->generate('app_booking_create_step_2_refresh'),
|
||||||
|
'hx-swap' => 'none',
|
||||||
|
'hx-trigger' => 'change',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
// Insurance field provider - provides age and eligibility filtered insurances for participants
|
// Insurance field provider - provides age and eligibility filtered insurances for participants
|
||||||
$this->fieldOptionProviders['insurance'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
|
$this->fieldOptionProviders['insurance'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
|
||||||
'label' => 'Reiseversicherung',
|
'label' => 'Reiseversicherung',
|
||||||
|
|||||||
@@ -82,13 +82,22 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
* where the selection is cleared (field not present in data). This ensures
|
* where the selection is cleared (field not present in data). This ensures
|
||||||
* the participant DTO is updated with null when no insurance is selected.
|
* the participant DTO is updated with null when no insurance is selected.
|
||||||
*
|
*
|
||||||
|
* However, when bulk insurance booking is active for a dependent participant,
|
||||||
|
* we skip processing to prevent overwriting the insurance assigned by the
|
||||||
|
* bulk insurance handler.
|
||||||
|
*
|
||||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||||
* @param int $participantIndex The index of the participant being processed
|
* @param int $participantIndex The index of the participant being processed
|
||||||
*
|
*
|
||||||
* @return bool Always returns true for insurance selection fields
|
* @return bool True if processing should occur, false if bulk insurance handles it
|
||||||
*/
|
*/
|
||||||
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
||||||
{
|
{
|
||||||
|
// Skip processing for dependent participants when bulk insurance booking is active
|
||||||
|
if ($participantIndex > 0 && $this->isBulkInsuranceBookingActive($submittedData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true; // Always process to handle deselection cases
|
return true; // Always process to handle deselection cases
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,4 +267,35 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
{
|
{
|
||||||
return (string) $currentInsurance->id === (string) $selectedInsuranceId;
|
return (string) $currentInsurance->id === (string) $selectedInsuranceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if bulk insurance booking is active for dependent participants.
|
||||||
|
*
|
||||||
|
* Bulk insurance is active when the applicant has enabled the bulkInsuranceBooking
|
||||||
|
* flag and has selected an insurance.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $submittedData The submitted form data
|
||||||
|
*
|
||||||
|
* @return bool True if bulk insurance booking is active
|
||||||
|
*/
|
||||||
|
private function isBulkInsuranceBookingActive(array $submittedData): bool
|
||||||
|
{
|
||||||
|
// Check if applicant data exists
|
||||||
|
if (!isset($submittedData['participants'][0])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$applicantData = $submittedData['participants'][0];
|
||||||
|
|
||||||
|
// Check if bulk insurance booking is enabled
|
||||||
|
$bulkInsuranceBooking = $applicantData['bulkInsuranceBooking'] ?? false;
|
||||||
|
if (false === (bool) $bulkInsuranceBooking) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if applicant has selected an insurance
|
||||||
|
$applicantInsurance = $applicantData['insurance'] ?? null;
|
||||||
|
|
||||||
|
return null !== $applicantInsurance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ class InsuranceMatchingService
|
|||||||
*/
|
*/
|
||||||
public function reassignInsuranceForPriceChange(array $availableInsurances, Insurance $currentInsurance, ParticipantDto $participant, BookingCreateDto $booking): ?Insurance
|
public function reassignInsuranceForPriceChange(array $availableInsurances, Insurance $currentInsurance, ParticipantDto $participant, BookingCreateDto $booking): ?Insurance
|
||||||
{
|
{
|
||||||
// Group insurances of the same type (subType + familyInsurance)
|
// Group insurances of the same type
|
||||||
$sameTypeInsurances = $this->filterInsurancesByType($availableInsurances, $currentInsurance->subType, $currentInsurance->familyInsurance);
|
$sameTypeInsurances = $this->filterInsurancesByType($availableInsurances, $currentInsurance);
|
||||||
|
|
||||||
// Get eligible insurances for this participant
|
// Get eligible insurances for this participant
|
||||||
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking);
|
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking);
|
||||||
@@ -96,8 +96,8 @@ class InsuranceMatchingService
|
|||||||
{
|
{
|
||||||
$assignments = [];
|
$assignments = [];
|
||||||
|
|
||||||
// Group insurances of the same type (subType + familyInsurance)
|
// Group insurances of the same type
|
||||||
$sameTypeInsurances = $this->filterInsurancesByType($availableInsurances, $selectedInsurance->subType, $selectedInsurance->familyInsurance);
|
$sameTypeInsurances = $this->filterInsurancesByType($availableInsurances, $selectedInsurance);
|
||||||
|
|
||||||
// Assign appropriate insurance to each participant
|
// Assign appropriate insurance to each participant
|
||||||
foreach ($booking->getParticipants() as $index => $participant) {
|
foreach ($booking->getParticipants() as $index => $participant) {
|
||||||
@@ -329,22 +329,39 @@ class InsuranceMatchingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters insurances by type (subType and familyInsurance combination).
|
* Filters insurances by type based on label (for packages) or subType (for individual insurances).
|
||||||
*
|
*
|
||||||
* This method groups insurances of the same type together for reassignment or batch assignment.
|
* 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.
|
* Insurance type matching strategy:
|
||||||
|
* - **Packages**: Match by label + familyInsurance (packages with same label are different price tiers)
|
||||||
|
* - **Individual insurances**: Match by subType + familyInsurance
|
||||||
*
|
*
|
||||||
* @param array<Insurance> $insurances All available insurances to filter
|
* Example: "Reise-Rücktritt + Selbstbehaltübernahme" at €10, €14, €26 are the same type,
|
||||||
* @param string|null $subType The insurance subType to match (e.g., 'RRV', 'PAK')
|
* but different from "Reiseschutz Platin Auto/Bahn/Bus (Europa) + Selbstbehaltübernahme".
|
||||||
* @param bool $familyInsurance Whether to match family or individual insurances
|
*
|
||||||
|
* @param array<Insurance> $insurances All available insurances to filter
|
||||||
|
* @param Insurance $referenceInsurance The insurance to match against
|
||||||
*
|
*
|
||||||
* @return array<Insurance> Filtered insurances of the same type
|
* @return array<Insurance> Filtered insurances of the same type
|
||||||
*/
|
*/
|
||||||
private function filterInsurancesByType(array $insurances, ?string $subType, bool $familyInsurance): array
|
private function filterInsurancesByType(array $insurances, Insurance $referenceInsurance): array
|
||||||
{
|
{
|
||||||
|
// For packages, match by label (packages with same label are different price tiers of same type)
|
||||||
|
if (true === $referenceInsurance->package) {
|
||||||
|
return array_filter(
|
||||||
|
$insurances,
|
||||||
|
fn (Insurance $insurance) => true === $insurance->package
|
||||||
|
&& $insurance->label === $referenceInsurance->label
|
||||||
|
&& $insurance->familyInsurance === $referenceInsurance->familyInsurance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For individual insurances, match by subType
|
||||||
return array_filter(
|
return array_filter(
|
||||||
$insurances,
|
$insurances,
|
||||||
fn (Insurance $insurance) => $insurance->subType === $subType && $insurance->familyInsurance === $familyInsurance
|
fn (Insurance $insurance) => false === $insurance->package
|
||||||
|
&& $insurance->subType === $referenceInsurance->subType
|
||||||
|
&& $insurance->familyInsurance === $referenceInsurance->familyInsurance
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,14 +183,49 @@
|
|||||||
'hx-swap': 'none'
|
'hx-swap': 'none'
|
||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
|
|
||||||
{{ _self.service_field(participant, 'insurance', 'Reiseversicherung', {
|
{# Insurance field OR assigned insurance display for dependent participants #}
|
||||||
'attr': {
|
{% set participantData = participant.vars.data %}
|
||||||
'hx-trigger': 'change',
|
{% set showBulkInsurance = loop.index > 1 and form.vars.data.participants[0].bulkInsuranceBooking %}
|
||||||
'hx-post': path('app_booking_create_step_2_refresh'),
|
|
||||||
'hx-swap': 'none'
|
{% if showBulkInsurance %}
|
||||||
}
|
<fieldset class="mb-1">
|
||||||
}) }}
|
<legend class="font-semibold mb-1">Reiseversicherung</legend>
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
{% if participantData.insurance %}
|
||||||
|
{{ participantData.insurance.label }}
|
||||||
|
{% if participantData.insurance.price and participantData.insurance.price > 0 %}
|
||||||
|
<span class="text-gray-500">(€{{ participantData.insurance.price|number_format(2, ',', '.') }})</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="italic text-gray-500 ml-2">– wie Anmelder</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="italic">wie Anmelder</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{% else %}
|
||||||
|
<fieldset class="mb-1">
|
||||||
|
<legend class="font-semibold mb-1">Reiseversicherung</legend>
|
||||||
|
|
||||||
|
{# Bulk insurance booking checkbox (applicant only) #}
|
||||||
|
{% if participant.bulkInsuranceBooking is defined %}
|
||||||
|
{{ form_row(participant.bulkInsuranceBooking) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if participant.insurance is defined %}
|
||||||
|
{{ form_row(participant.insurance, {
|
||||||
|
'attr': {
|
||||||
|
'hx-trigger': 'change',
|
||||||
|
'hx-post': path('app_booking_create_step_2_refresh'),
|
||||||
|
'hx-swap': 'none'
|
||||||
|
},
|
||||||
|
'label': false
|
||||||
|
}) }}
|
||||||
|
{% else %}
|
||||||
|
<div class="text-sm text-gray-500">Nicht wählbar</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Transportation Services Section #}
|
{# Transportation Services Section #}
|
||||||
|
|||||||
Reference in New Issue
Block a user