wip: insurance booking in edit flow

This commit is contained in:
Björn Fromme
2025-10-07 18:09:16 +02:00
parent 7304fb50c6
commit c3008d7f7a
10 changed files with 1215 additions and 0 deletions
+19
View File
@@ -42,6 +42,7 @@ class Booking
public array $pickupsOutbound = [];
public array $pickupsInbound = [];
public array $surcharges = [];
public array $insurances = [];
public ?int $invoiceNumber = null;
public ?float $totalPrice = null;
public ?string $travelInfoUrl = null;
@@ -150,6 +151,24 @@ class Booking
return null;
}
/**
* Gets the insurance assigned to a specific participant.
*
* @param int $participantIndex The participant index (0-based)
*
* @return Insurance|null The assigned insurance or null if none assigned
*/
public function getInsuranceForParticipant(int $participantIndex): ?Insurance
{
foreach ($this->insurances as $insurance) {
if (in_array($participantIndex, $insurance->mapping ?? [], true)) {
return $insurance;
}
}
return null;
}
/**
* Retrieves ski pass for a specific participant.
*
+10
View File
@@ -95,6 +95,16 @@ class Insurance
#[Groups(['api:single', 'api:list'])]
public array $containedInsuranceIds = [];
/**
* @var array<int> Participant indices assigned to this insurance (booking data only)
*/
public array $mapping = [];
/**
* @var array<int, float> Individual prices per participant index (booking data only)
*/
public array $individualPrice = [];
public function __toString(): string
{
return (string) $this->id;
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\XmlParser;
use App\BusProNet\Model\Insurance;
use Symfony\Component\DomCrawler\Crawler;
/**
* Parses insurance data from booking XML responses.
*
* This parser handles the simplified insurance structure found in booking data,
* which includes participant mappings and individual pricing.
*/
class BookingInsurancesParser extends AbstractParser
{
public function parse(Crawler $result): array
{
$insurances = [];
$result->each(function (Crawler $node) use (&$insurances) {
$insurance = new Insurance();
$insurance->id = (int) $node->attr('idversicherung');
$insurance->label = $node->attr('bezeichnung');
$insurance->price = $this->stringToFloat($node->attr('gesamtpreis'));
// Parse participant mapping (zuordnung attribute)
$mapping = $this->stringToArray($node->attr('zuordnung'));
$insurance->mapping = array_map(function ($index) {
return (int) $index - 1;
}, $mapping);
// Parse individual prices per participant
$individualPrices = array_map(
function ($price) {
return $this->stringToFloat($price);
},
$this->stringToArray($node->attr('einzelpreis', ''), '/')
);
$insurance->individualPrice = array_combine($insurance->mapping, $individualPrices);
$insurances[$insurance->id] = $insurance;
});
return $insurances;
}
}
@@ -16,6 +16,7 @@ class BookingParser extends AbstractParser
private readonly RoomsParser $roomsParser;
private readonly PickupsParser $pickupsParser;
private readonly SurchargesParser $surchargesParser;
private readonly BookingInsurancesParser $insurancesParser;
public function __construct()
{
@@ -23,6 +24,7 @@ class BookingParser extends AbstractParser
$this->roomsParser = new RoomsParser();
$this->pickupsParser = new PickupsParser();
$this->surchargesParser = new SurchargesParser();
$this->insurancesParser = new BookingInsurancesParser();
}
public function parse(Crawler $node): Booking
@@ -101,6 +103,11 @@ class BookingParser extends AbstractParser
$booking->surcharges = $this->surchargesParser->parse($surchargesData);
}
$insurancesData = $node->filterXPath('//versicherungen/versicherung');
if (0 < $insurancesData->count()) {
$booking->insurances = $this->insurancesParser->parse($insurancesData);
}
return $booking;
}
+4
View File
@@ -60,6 +60,10 @@ class BookingEditDto implements BookingDtoInterface
$pickup = $booking->getPickupForParticipant($index);
$participantData->pickup = $pickup;
// Insurance - get insurance for participant
$insurance = $booking->getInsuranceForParticipant($index);
$participantData->insurance = $insurance;
// Room assignment - extract from booking room mappings
$room = $booking->getRoomForParticipant($index);
$participantData->assignedRoomId = $room?->id;
@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace App\Form\Service\Condition;
use App\Form\Model\BookingDtoInterface;
use App\Form\Model\BookingEditDto;
use App\Form\Service\Contract\FieldConditionInterface;
/**
* Condition that determines if insurance fields are editable based on booking and travel dates.
*
* Implements time-based mutability constraints for insurance booking in the edit flow:
* - Standard case: Insurance editable up to 30 days before travel date
* - Late booking case: If booking made < 30 days before travel, insurance editable up to 3 days after booking date
*/
class InsuranceMutabilityCondition implements FieldConditionInterface
{
private const DAYS_BEFORE_TRAVEL_THRESHOLD = 30;
private const DAYS_AFTER_BOOKING_THRESHOLD = 3;
/**
* Evaluates whether the insurance field should be readonly.
*
* Returns true if the field should be readonly (locked), false if editable.
*
* Logic:
* 1. Always editable in create flow
* 2. In edit flow, check standard case: editable if >= 30 days before travel
* 3. In edit flow, check late booking case: editable if within 3 days of booking date
* 4. Otherwise: readonly
*
* @param BookingDtoInterface $bookingDto The current booking data
* @param int $participantIndex The participant index (unused for insurance mutability)
* @param array<string, mixed> $formData Current form data (unused for insurance mutability)
*
* @return bool True if field should be readonly, false if editable
*/
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
{
// Only apply mutability constraints to edit flow
if (!$bookingDto instanceof BookingEditDto) {
return false; // Always editable in create flow
}
$now = \Carbon\Carbon::now()->toDateTimeImmutable();
$travelDate = $bookingDto->travel->dateFrom;
$bookingDate = $bookingDto->booking->bookingDate;
// Calculate days until travel
$daysUntilTravel = $now->diff($travelDate)->days;
$isBeforeTravel = $now < $travelDate;
// Standard case: Editable if >= 30 days before travel
if ($isBeforeTravel && $daysUntilTravel >= self::DAYS_BEFORE_TRAVEL_THRESHOLD) {
return false; // Editable
}
// Late booking case: Check if booking was made < 30 days before travel
$daysFromBookingToTravel = $bookingDate->diff($travelDate)->days;
$wasLateBooking = $daysFromBookingToTravel < self::DAYS_BEFORE_TRAVEL_THRESHOLD;
if ($wasLateBooking) {
// Editable if within 3 days of booking date
$daysSinceBooking = $bookingDate->diff($now)->days;
return $daysSinceBooking > self::DAYS_AFTER_BOOKING_THRESHOLD; // True = readonly (past threshold)
}
// Default: Not editable (readonly)
return true;
}
/**
* Returns field names that trigger re-evaluation of this condition.
*
* Insurance mutability is based on dates, not other form fields,
* so no field dependencies are needed.
*
* @return string[] Empty array - no field dependencies
*/
public function getDependentFields(): array
{
return [];
}
/**
* Returns a human-readable description of this condition.
*
* @return string Description of the insurance mutability logic
*/
public function getDescription(): string
{
return sprintf(
'Insurance is not editable (>= %d days before travel or > %d days after late booking)',
self::DAYS_BEFORE_TRAVEL_THRESHOLD,
self::DAYS_AFTER_BOOKING_THRESHOLD
);
}
}