wip: modernized edit flow, fix insurance tier calculation

This commit is contained in:
Björn Fromme
2025-10-09 17:42:39 +02:00
parent cba0f747cd
commit 67c642bc2f
31 changed files with 272 additions and 536 deletions
+6 -8
View File
@@ -15,9 +15,7 @@ use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\Travel;
use App\BusProNet\Traits\ApiClientTrait;
use App\BusProNet\XmlParser\ApiResponseParser;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDto;
use App\Form\Model\BookingEditDto;
use App\Form\Model\RegistrationDto;
use League\Flysystem\FilesystemException;
use League\Flysystem\FilesystemOperator;
@@ -215,14 +213,14 @@ class ApiClient
* First phase of the two-phase booking process. Validates all booking data
* and returns pricing information without creating an actual booking.
*
* @param BookingCreateDto $bookingDto The booking creation form data
* @param bool $debug Enable debug mode (XML dumps)
* @param BookingDto $bookingDto The booking creation form data
* @param bool $debug Enable debug mode (XML dumps)
*
* @return Notification|BookingResponse Notification on error, BookingResponse on success
*
* @throws ApiClientException If the API request fails
*/
public function createBookingInquiry(BookingCreateDto $bookingDto, bool $debug = false): Notification|BookingResponse
public function createBookingInquiry(BookingDto $bookingDto, bool $debug = false): Notification|BookingResponse
{
$payload = $this->bookingDataProcessor->createBookingRequestPayload($bookingDto, 'Anfrage');
@@ -242,14 +240,14 @@ class ApiClient
* Second phase of the two-phase booking process. Creates the actual booking
* after successful inquiry validation.
*
* @param BookingCreateDto $bookingDto The booking creation form data
* @param bool $debug Enable debug mode (XML dumps)
* @param BookingDto $bookingDto The booking creation form data
* @param bool $debug Enable debug mode (XML dumps)
*
* @return Notification|BookingResponse Notification on error, BookingResponse with booking number on success
*
* @throws ApiClientException If the API request fails
*/
public function createBooking(BookingCreateDto $bookingDto, bool $debug = false): Notification|BookingResponse
public function createBooking(BookingDto $bookingDto, bool $debug = false): Notification|BookingResponse
{
$payload = $this->bookingDataProcessor->createBookingRequestPayload($bookingDto, 'Buchung');
@@ -12,9 +12,7 @@ use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\Travel;
use App\BusProNet\Utility\DirectionMapper;
use App\Form\Model\BankAccountDto;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDto;
use App\Form\Model\BookingEditDto;
use App\Form\Model\ParticipantDto;
use App\Service\InsuranceMatchingService;
@@ -39,6 +37,12 @@ class BookingDataProcessor
*
* Extracts all service selections from the booking entity and assigns them
* to participant DTOs, creating a unified data structure identical to create mode.
*
* IMPORTANT: Address handling for edit mode
* - ParticipantDto::fromPersonalData() clones address objects to prevent shared references
* - First participant's address is NOT auto-populated from applicant
* - Template uses placeholders to show applicant's address as hints
* - This prevents address data from being inadvertently modified during form binding
*/
public function createBookingDtoFromBooking(Booking $booking, Travel $travel): BookingDto
{
@@ -351,8 +355,8 @@ class BookingDataProcessor
* Insurance can be either individual or package-based, with automatic price-tier adjustment.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
* @param Travel $travelData The travel data containing available insurances
* @param Booking $bookingData The booking data object to update
* @param Travel $travelData The travel data containing available insurances
*/
private function processInsurance(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void
{
@@ -654,7 +658,7 @@ class BookingDataProcessor
* ('Anfrage') or a final booking commit ('Buchung').
*
* @param BookingDto $bookingDto The booking creation form data
* @param string $bookingType Either 'Anfrage' (inquiry) or 'Buchung' (final booking)
* @param string $bookingType Either 'Anfrage' (inquiry) or 'Buchung' (final booking)
*
* @return array The structured payload array for BusProNet API submission
*/
@@ -822,11 +826,11 @@ class BookingDataProcessor
* - abreise (departure date)
* - anzahl (number of rooms of this type booked)
*
* @param array $payload The payload array to modify
* @param array $roomMap Map of room ID to participant IDs
* @param BookingCreateDto $bookingDto The booking data for accessing room details and quantities
* @param array $payload The payload array to modify
* @param array $roomMap Map of room ID to participant IDs
* @param BookingDto $bookingDto The booking data for accessing room details and quantities
*/
private function addRoomMappingsToPayload(array &$payload, array $roomMap, BookingCreateDto $bookingDto): void
private function addRoomMappingsToPayload(array &$payload, array $roomMap, BookingDto $bookingDto): void
{
if (empty($roomMap)) {
return;
@@ -870,7 +874,7 @@ class BookingDataProcessor
*
* @return array<string, array<int>> Map of room ID to participant IDs
*/
private function collectRoomMappings(BookingCreateDto $bookingDto): array
private function collectRoomMappings(BookingDto $bookingDto): array
{
$roomMap = [];
@@ -893,7 +897,7 @@ class BookingDataProcessor
*
* @return array<string, array<int>> Map of service ID to participant IDs
*/
private function collectServiceMappings(BookingCreateDto $bookingDto): array
private function collectServiceMappings(BookingDto $bookingDto): array
{
$serviceMap = [];
@@ -944,7 +948,7 @@ class BookingDataProcessor
*
* @return array<string, array<int>> Map of transportation service ID to participant IDs
*/
private function collectTransportationMappings(BookingCreateDto $bookingDto): array
private function collectTransportationMappings(BookingDto $bookingDto): array
{
$transportationMap = [];
@@ -971,7 +975,7 @@ class BookingDataProcessor
*
* @return array<string, array<int>> Map of pickup ID to participant IDs
*/
private function collectPickupMappings(BookingCreateDto $bookingDto): array
private function collectPickupMappings(BookingDto $bookingDto): array
{
$pickupMap = [];
@@ -993,7 +997,7 @@ class BookingDataProcessor
*
* @return array<string, array<int>> Map of insurance ID to participant IDs
*/
private function collectInsuranceMappings(BookingCreateDto $bookingDto): array
private function collectInsuranceMappings(BookingDto $bookingDto): array
{
$insuranceMap = [];
@@ -1033,7 +1037,12 @@ class BookingDataProcessor
}
// Get all available insurances from travel data
$availableInsurances = array_values($bookingDto->travel->insurances);
$availableInsurances = $bookingDto->travel->insurances;
// Exclude complementary insurances from bulk assignment logic
// They are only available as part of packages and cannot be directly selected
$availableInsurances = array_filter($availableInsurances, fn ($insurance) => !$insurance->complementary);
$availableInsurances = array_values($availableInsurances);
// Use InsuranceMatchingService for proper type-based assignment with price tier matching
$assignments = $this->insuranceMatchingService->batchAssignInsuranceToParticipants(
@@ -1055,7 +1064,7 @@ class BookingDataProcessor
// In edit mode: only apply bulk insurance if participant has no insurance
// This respects the rule that once assigned, insurance cannot be changed
if ($bookingDto instanceof BookingEditDto && null !== $participant->insurance) {
if (BookingDto::MODE_EDIT === $bookingDto->getMode() && null !== $participant->insurance) {
continue; // Skip participants with existing insurance assignment
}
+10
View File
@@ -96,4 +96,14 @@ class Service
#[Groups(['api:single', 'api:list'])]
public ?string $description = null;
/**
* Indicates whether this service should be included in insurance eligibility price calculation.
*
* Maps to XML attribute 'versicherungsberechnung' (J=true, N=false).
* When false, this service's price is excluded when determining which insurance
* tier is appropriate for a participant.
*/
#[Groups(['api:single', 'api:list'])]
public bool $includeInInsuranceCalculation = true;
}
+6
View File
@@ -145,6 +145,9 @@ class TravelParser extends AbstractParser
// Parse age constraints
$this->parseServiceAgeConstraints($serviceNode, $service);
// Parse insurance calculation flag
$service->includeInInsuranceCalculation = $this->stringToBool($serviceNode->attr('versicherungsberechnung'));
$additionalServices[$serviceId] = $service;
});
@@ -192,6 +195,9 @@ class TravelParser extends AbstractParser
$service->dayTime = (new DayTimeUtility())->mapTime($timeFrom);
}
// Parse insurance calculation flag
$service->includeInInsuranceCalculation = $this->stringToBool($serviceNode->attr('versicherungsberechnung'));
$transportationServices[$serviceId] = $service;
});