diff --git a/src/BusProNet/DataProcessor/BookingDataProcessor.php b/src/BusProNet/DataProcessor/BookingDataProcessor.php index 1401e02..38348a2 100644 --- a/src/BusProNet/DataProcessor/BookingDataProcessor.php +++ b/src/BusProNet/DataProcessor/BookingDataProcessor.php @@ -75,8 +75,8 @@ class BookingDataProcessor $servicesToReset = [ ...$bookingData->additionalServices, ...$bookingData->transportationServices, - ...$bookingData->pickupsTo, - ...$bookingData->pickupsFro, + ...$bookingData->pickupsOutbound, + ...$bookingData->pickupsInbound, ]; foreach ($servicesToReset as $service) { @@ -120,7 +120,7 @@ class BookingDataProcessor $servicesToMap = [ ...$participant->courses, ...$participant->additionalServices, - ...$participant->skiPass, + ...($participant->skiPass ? [$participant->skiPass] : []), ...$participant->board, ...$participant->rentals, ]; @@ -149,7 +149,7 @@ class BookingDataProcessor */ private function processTransportationServices(object $participant, object $bookingData, object $travelData): void { - foreach ([$participant->transportationServiceTo, $participant->transportationServiceFro] as $service) { + foreach ([$participant->transportationOutbound, $participant->transportationInbound] as $service) { if (false === isset($bookingData->transportationServices[$service->id])) { $serviceToAdd = $travelData->transportationServices[$service->id] ?? null; if (null !== $serviceToAdd) { @@ -172,11 +172,11 @@ class BookingDataProcessor */ private function processPickupLocations(object $participant, object $bookingData): void { - if ('BUS' === $participant->transportationServiceTo->subType && null !== $selectedPickup = $participant->pickup) { - if (false === isset($bookingData->pickupsTo[$selectedPickup->id])) { - $bookingData->pickupsTo[$selectedPickup->id] = $selectedPickup; + if ('BUS' === $participant->transportationOutbound->subType && null !== $selectedPickup = $participant->pickupOutbound) { + if (false === isset($bookingData->pickupsOutbound[$selectedPickup->id])) { + $bookingData->pickupsOutbound[$selectedPickup->id] = $selectedPickup; } - $bookingData->pickupsTo[$selectedPickup->id]->mapping[] = $participant->index; + $bookingData->pickupsOutbound[$selectedPickup->id]->mapping[] = $participant->index; } } @@ -202,15 +202,15 @@ class BookingDataProcessor } } - foreach ($bookingData->pickupsTo as $pickup) { + foreach ($bookingData->pickupsOutbound as $pickup) { if (0 === count($pickup->mapping)) { - unset($bookingData->pickupsTo[$pickup->id]); + unset($bookingData->pickupsOutbound[$pickup->id]); } } - foreach ($bookingData->pickupsFro as $pickup) { + foreach ($bookingData->pickupsInbound as $pickup) { if (0 === count($pickup->mapping)) { - unset($bookingData->pickupsFro[$pickup->id]); + unset($bookingData->pickupsInbound[$pickup->id]); } } } @@ -393,9 +393,9 @@ class BookingDataProcessor */ private function buildPickupPayload(array &$payload, object $bookingData): void { - if (0 < count($bookingData->pickupsTo)) { + if (0 < count($bookingData->pickupsOutbound)) { $payload['zustiege']['zustieg'] = []; - foreach ($bookingData->pickupsTo as $pickup) { + foreach ($bookingData->pickupsOutbound as $pickup) { $payload['zustiege']['zustieg'][] = [ '@idzustieg' => $pickup->id, '@anzahl' => count($pickup->mapping), diff --git a/src/BusProNet/Model/Booking.php b/src/BusProNet/Model/Booking.php index 0c04216..63e0418 100644 --- a/src/BusProNet/Model/Booking.php +++ b/src/BusProNet/Model/Booking.php @@ -39,8 +39,8 @@ class Booking public array $transportationServices = []; public array $additionalServices = []; public array $rooms = []; - public array $pickupsTo = []; - public array $pickupsFro = []; + public array $pickupsOutbound = []; + public array $pickupsInbound = []; public array $surcharges = []; public ?int $invoiceNumber = null; public ?float $totalPrice = null; @@ -106,7 +106,7 @@ class Booking * Retrieves transportation service for a specific participant and direction. * * Finds the transportation service that matches the participant index - * and travel direction (e.g., 'H' for outbound, 'R' for return). + * and travel direction (e.g., 'H' for outbound, 'R' for inbound). * * @param int $participantIndex The participant index to search for * @param string $direction The travel direction ('H' or 'R') @@ -141,7 +141,7 @@ class Booking */ public function getPickupForParticipant(int $participantIndex): ?Pickup { - foreach ($this->pickupsTo as $pickup) { + foreach ($this->pickupsOutbound as $pickup) { if (in_array($participantIndex, $pickup->mapping)) { return $pickup; } diff --git a/src/BusProNet/Model/Travel.php b/src/BusProNet/Model/Travel.php index babd402..82219c6 100644 --- a/src/BusProNet/Model/Travel.php +++ b/src/BusProNet/Model/Travel.php @@ -63,10 +63,10 @@ class Travel public bool $transportationServicesMutable = true; #[Groups(['api:single'])] - public array $pickupsTo = []; + public array $pickupsOutbound = []; #[Groups(['api:single'])] - public array $pickupsFro = []; + public array $pickupsInbound = []; #[Groups(['api:single'])] public bool $pickupsMutable = true; diff --git a/src/BusProNet/XmlLoader/PickupLoader.php b/src/BusProNet/XmlLoader/PickupLoader.php index 65c6cf6..0e46d6d 100644 --- a/src/BusProNet/XmlLoader/PickupLoader.php +++ b/src/BusProNet/XmlLoader/PickupLoader.php @@ -61,7 +61,7 @@ class PickupLoader extends AbstractLoader public function patchPickupsDetails(Travel $travel): void { - foreach ($travel->pickupsTo as $pickupId => $pickup) { + foreach ($travel->pickupsOutbound as $pickupId => $pickup) { $pickupData = $this->loadById($pickupId); $pickup->code = $pickupData->code; @@ -69,7 +69,7 @@ class PickupLoader extends AbstractLoader $pickup->city = $pickupData->city; $pickup->street = $pickupData->street; } - foreach ($travel->pickupsFro as $pickupId => $pickup) { + foreach ($travel->pickupsInbound as $pickupId => $pickup) { $pickupData = $this->loadById($pickupId); $pickup->code = $pickupData->code; diff --git a/src/BusProNet/XmlParser/BookingParser.php b/src/BusProNet/XmlParser/BookingParser.php index 91d189e..64c3eb8 100644 --- a/src/BusProNet/XmlParser/BookingParser.php +++ b/src/BusProNet/XmlParser/BookingParser.php @@ -89,11 +89,11 @@ class BookingParser extends AbstractParser $pickupsData = $node->filterXPath('//zustiege/zustieg'); if (0 < $pickupsData->count()) { - $booking->pickupsTo = $this->pickupsParser->parse($pickupsData); + $booking->pickupsOutbound = $this->pickupsParser->parse($pickupsData); } $pickupsData = $node->filterXPath('//zustiege_rueck/zustieg_rueck'); if (0 < $pickupsData->count()) { - $booking->pickupsFro = $this->pickupsParser->parse($pickupsData); + $booking->pickupsInbound = $this->pickupsParser->parse($pickupsData); } $surchargesData = $node->filterXPath('//zuschlaege/zuschlag'); diff --git a/src/BusProNet/XmlParser/TravelParser.php b/src/BusProNet/XmlParser/TravelParser.php index 40ca784..cdeb1d9 100644 --- a/src/BusProNet/XmlParser/TravelParser.php +++ b/src/BusProNet/XmlParser/TravelParser.php @@ -61,8 +61,8 @@ class TravelParser extends AbstractParser $travel->transportationServices = $this ->getTransportationServices($node->filterXPath('//lei_befoerderung/leistung')); $travel->rooms = $this->getRooms($hotelNode); - $travel->pickupsTo = $this->getPickups($node->filterXPath('//zustiege/zustieg'), $dateFrom); - $travel->pickupsFro = $this->getPickups($node->filterXPath('//zustiege_rueck/zustieg_rueck')); + $travel->pickupsOutbound = $this->getPickups($node->filterXPath('//zustiege/zustieg'), $dateFrom); + $travel->pickupsInbound = $this->getPickups($node->filterXPath('//zustiege_rueck/zustieg_rueck')); $travel->guide = $this->getGuide($node); return $travel; diff --git a/src/Form/BookingEditParticipantType.php b/src/Form/BookingEditParticipantType.php index 8d664bd..49e3197 100644 --- a/src/Form/BookingEditParticipantType.php +++ b/src/Form/BookingEditParticipantType.php @@ -214,7 +214,7 @@ class BookingEditParticipantType extends AbstractType return $attributes; }; $form - ->add('transportationServiceTo', ChoiceType::class, [ + ->add('transportationOutbound', ChoiceType::class, [ ...$commonChoiceFieldOptions, 'label' => 'Anreise', 'required' => true, @@ -222,7 +222,7 @@ class BookingEditParticipantType extends AbstractType 'choices' => $options['selectable_transportation_services_to'], 'choice_attr' => $transportationChoiceAttributes, ]) - ->add('transportationServiceFro', ChoiceType::class, [ + ->add('transportationInbound', ChoiceType::class, [ ...$commonChoiceFieldOptions, 'label' => 'Rückreise', 'required' => true, @@ -232,7 +232,7 @@ class BookingEditParticipantType extends AbstractType ]); // Pickup - $form->add('pickup', ChoiceType::class, [ + $form->add('pickupOutbound', ChoiceType::class, [ 'label' => 'Zustieg', 'multiple' => false, 'expanded' => false, @@ -319,12 +319,12 @@ class BookingEditParticipantType extends AbstractType } } - $transportationId = $data['transportationServiceTo'] ?? null; + $transportationId = $data['transportationOutbound'] ?? null; $transportation = $options['travel']->pickups[$transportationId] ?? null; if (null !== $transportation && 'PKW' === $transportation->subType) { - $form->remove('pickup'); - unset($data['pickup']); + $form->remove('pickupOutbound'); + unset($data['pickupOutbound']); } // forcibly select mandatory services that potentially have been disabled in PRE_SET_DATA diff --git a/src/Form/BookingEditType.php b/src/Form/BookingEditType.php index 5a1eb2d..d95ca2e 100644 --- a/src/Form/BookingEditType.php +++ b/src/Form/BookingEditType.php @@ -46,7 +46,7 @@ class BookingEditType extends AbstractType ->getTransportationServicesByDirection('HIN', false), 'selectable_transportation_services_fro' => $travelData ->getTransportationServicesByDirection('RUECK', false), - 'selectable_pickups' => $travelData->pickupsTo, + 'selectable_pickups' => $travelData->pickupsOutbound, 'personal_data_mutable' => $travelData->participantDataMutable, 'additional_services_mutable' => $travelData->additionalServicesMutable, 'transportation_services_mutable' => $travelData->transportationServicesMutable, diff --git a/src/Form/Model/BookingEditDto.php b/src/Form/Model/BookingEditDto.php index 0af129b..7301092 100644 --- a/src/Form/Model/BookingEditDto.php +++ b/src/Form/Model/BookingEditDto.php @@ -58,14 +58,9 @@ class BookingEditDto implements BookingDtoInterface $participantData->transportationOutbound = $outboundTransportation; $participantData->transportationInbound = $inboundTransportation; - // Backward compatibility: also set deprecated properties - $participantData->transportationServiceTo = $outboundTransportation; - $participantData->transportationServiceFro = $inboundTransportation; - // Pickup handling (currently only supports outbound pickup) $pickupOutbound = $booking->getPickupForParticipant($index); $participantData->pickupOutbound = $pickupOutbound; - $participantData->pickup = $pickupOutbound; // Backward compatibility $instance->participants[$index] = $participantData; } diff --git a/src/Form/Model/ParticipantDto.php b/src/Form/Model/ParticipantDto.php index 45710df..0992281 100644 --- a/src/Form/Model/ParticipantDto.php +++ b/src/Form/Model/ParticipantDto.php @@ -71,11 +71,6 @@ class ParticipantDto // License plate for participants with parking (optional, visible only when parking is selected) public ?string $licensePlate = null; - // Deprecated properties for backward compatibility - will be removed in future version - public ?Service $transportationServiceTo = null; - public ?Service $transportationServiceFro = null; - public ?Pickup $pickup = null; - public static function fromPersonalData(PersonalData $personalData): static { $instance = new static(); @@ -98,6 +93,11 @@ class ParticipantDto return $instance; } + public function isApplicant(): bool + { + return 0 === $this->index; + } + public function isCanceled(): bool { return 'S' === $this->status; diff --git a/src/Form/Service/ParticipantFieldOptionsProvider.php b/src/Form/Service/ParticipantFieldOptionsProvider.php index ca071d5..75ca8d6 100644 --- a/src/Form/Service/ParticipantFieldOptionsProvider.php +++ b/src/Form/Service/ParticipantFieldOptionsProvider.php @@ -328,7 +328,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider // Outbound Pickup (conditional - only shown when outbound transportation is bus) $this->fieldOptionProviders['pickupOutbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [ 'label' => 'Zustieg Hinfahrt', - 'choices' => $bookingDto->travel->pickupsTo, + 'choices' => $bookingDto->travel->pickupsOutbound, 'choice_label' => fn (?Pickup $pickup) => $this->formatPickupLabelWithPrice($pickup), 'choice_value' => 'id', 'expanded' => false, // Dropdown for pickups @@ -340,7 +340,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider // Inbound Pickup (conditional - only shown when inbound transportation is bus) $this->fieldOptionProviders['pickupInbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [ 'label' => 'Ausstieg Rückfahrt', - 'choices' => $bookingDto->travel->pickupsFro, + 'choices' => $bookingDto->travel->pickupsInbound, 'choice_label' => fn (?Pickup $pickup) => $this->formatPickupLabelWithPrice($pickup), 'choice_value' => 'id', 'expanded' => false, diff --git a/src/Form/Service/ParticipantPickupInboundFieldHandler.php b/src/Form/Service/ParticipantPickupInboundFieldHandler.php index 74ecccf..eee134b 100644 --- a/src/Form/Service/ParticipantPickupInboundFieldHandler.php +++ b/src/Form/Service/ParticipantPickupInboundFieldHandler.php @@ -64,7 +64,7 @@ class ParticipantPickupInboundFieldHandler extends AbstractParticipantFieldHandl // Validate pickup selection against available inbound pickups $validSelection = null; if (null !== $selectedPickup) { - $validSelection = $this->findValidPickup($selectedPickup, $bookingDto->travel->pickupsFro); + $validSelection = $this->findValidPickup($selectedPickup, $bookingDto->travel->pickupsInbound); } $participant->pickupInbound = $validSelection; diff --git a/src/Form/Service/ParticipantPickupOutboundFieldHandler.php b/src/Form/Service/ParticipantPickupOutboundFieldHandler.php index 85a0c9b..5585cb2 100644 --- a/src/Form/Service/ParticipantPickupOutboundFieldHandler.php +++ b/src/Form/Service/ParticipantPickupOutboundFieldHandler.php @@ -65,7 +65,7 @@ class ParticipantPickupOutboundFieldHandler extends AbstractParticipantFieldHand // Validate pickup selection against available outbound pickups $validSelection = null; if (null !== $selectedPickup) { - $validSelection = $this->findValidPickup($selectedPickup, $bookingDto->travel->pickupsTo); + $validSelection = $this->findValidPickup($selectedPickup, $bookingDto->travel->pickupsOutbound); } $participant->pickupOutbound = $validSelection; diff --git a/src/Form/Service/ParticipantRentalsFieldHandler.php b/src/Form/Service/ParticipantRentalsFieldHandler.php index f299356..71bdd2a 100644 --- a/src/Form/Service/ParticipantRentalsFieldHandler.php +++ b/src/Form/Service/ParticipantRentalsFieldHandler.php @@ -14,7 +14,7 @@ use App\Form\Service\Abstract\AbstractParticipantFieldHandler; * * This handler manages rental equipment selections for participants in the booking * creation process. It processes the rentals field from form submissions, - * filters out age-inappropriate options and duration-inappropriate options, and + * filters out age-inappropriate options and duration-inappropriate options, and * updates the participant DTO with only valid selections. * * Dependencies: dateOfBirth (for age evaluation) and skiPass (for duration filtering) @@ -68,7 +68,7 @@ class ParticipantRentalsFieldHandler extends AbstractParticipantFieldHandler if (null === $participant->skiPass) { // No skipass selected = clear all rental selections $participant->rentals = []; - + return; } diff --git a/src/Service/TravelDataService.php b/src/Service/TravelDataService.php index 99ffdcc..8864819 100644 --- a/src/Service/TravelDataService.php +++ b/src/Service/TravelDataService.php @@ -511,6 +511,39 @@ class TravelDataService } } + /** + * Fetch availability data with short-term caching. + * + * Retrieves availability information from the API with caching to reduce + * API calls during booking form interactions. Uses a short TTL to ensure + * reasonably fresh data while avoiding excessive API requests. + * + * @param int $dateId The travel date ID for API call + * @param int $ttl Cache TTL in seconds (default: 60 seconds) + * + * @return BaseData|null The availability data or null if not available or error occurred + */ + public function getAvailabilityDataCached(int $dateId, int $ttl = 60): ?BaseData + { + $cacheKey = sprintf('availability_%d', $dateId); + + try { + return $this->cache->get($cacheKey, function (ItemInterface $item) use ($dateId, $ttl) { + $item->expiresAfter($ttl); + + return $this->getAvailabilityData($dateId); + }); + } catch (InvalidArgumentException $e) { + $this->logger->error('Cache error in getAvailabilityDataCached', [ + 'dateId' => $dateId, + 'error' => $e->getMessage(), + ]); + + // Fallback to direct API call + return $this->getAvailabilityData($dateId); + } + } + /** * Apply availability data to travel services. * diff --git a/src/Validator/Constraints/ParticipantValidator.php b/src/Validator/Constraints/ParticipantValidator.php index d988355..74b00bb 100644 --- a/src/Validator/Constraints/ParticipantValidator.php +++ b/src/Validator/Constraints/ParticipantValidator.php @@ -41,7 +41,7 @@ class ParticipantValidator extends ConstraintValidator return; } - foreach (['transportationServiceTo', 'transportationServiceFro'] as $property) { + foreach (['transportationOutbound', 'transportationInbound'] as $property) { if (null === $participant->{$property}) { $this->context->buildViolation('Bitte angeben') ->atPath($property) @@ -54,12 +54,12 @@ class ParticipantValidator extends ConstraintValidator public function assertPickupSelected(ParticipantDto $participant): void { if ( - null !== $participant->transportationServiceTo - && 'BUS' === $participant->transportationServiceTo->subType - && null === $participant->pickup + null !== $participant->transportationOutbound + && 'BUS' === $participant->transportationOutbound->subType + && null === $participant->pickupOutbound ) { $this->context->buildViolation('Bitte auswählen') - ->atPath('pickup') + ->atPath('pickupOutbound') ->addViolation() ; } diff --git a/templates/booking/edit.html.twig b/templates/booking/edit.html.twig index 5287505..448ead8 100644 --- a/templates/booking/edit.html.twig +++ b/templates/booking/edit.html.twig @@ -224,15 +224,15 @@ Hin-/Rückreise
-
- {{ form_row(child.transportationServiceTo) }} - {% if child.pickup is defined %} +
+ {{ form_row(child.transportationOutbound) }} + {% if child.pickupOutbound is defined %}
- {{ form_row(child.pickup) }} + {{ form_row(child.pickupOutbound) }}
{% endif %}
- {{ form_row(child.transportationServiceFro) }} + {{ form_row(child.transportationInbound) }}
{% if not travelData.transportationServicesMutable %}
diff --git a/tests/BusProNet/DataProcessor/BookingDataProcessorTest.php b/tests/BusProNet/DataProcessor/BookingDataProcessorTest.php index 4cbe8c8..d002a74 100644 --- a/tests/BusProNet/DataProcessor/BookingDataProcessorTest.php +++ b/tests/BusProNet/DataProcessor/BookingDataProcessorTest.php @@ -248,8 +248,8 @@ class BookingDataProcessorTest extends TestCase $formData = $this->createCompleteFormData(); $participant = $formData->participants[0]; - $participant->transportationServiceTo = $this->createMockService(1); - $participant->transportationServiceFro = $this->createMockService(2); + $participant->transportationOutbound = $this->createMockService(1); + $participant->transportationInbound = $this->createMockService(2); return $formData; } @@ -261,8 +261,8 @@ class BookingDataProcessorTest extends TestCase $participant = $formData->participants[0]; $busService = $this->createMockService(1); $busService->subType = 'BUS'; - $participant->transportationServiceTo = $busService; - $participant->pickup = $this->createMockPickup(1); + $participant->transportationOutbound = $busService; + $participant->pickupOutbound = $this->createMockPickup(1); return $formData; } @@ -274,7 +274,7 @@ class BookingDataProcessorTest extends TestCase $participant = $formData->participants[0]; $trainService = $this->createMockService(1); $trainService->subType = 'TRAIN'; - $participant->transportationServiceTo = $trainService; + $participant->transportationOutbound = $trainService; return $formData; } @@ -387,7 +387,7 @@ class BookingDataProcessorTest extends TestCase private function createFormDataWithoutPickups(): BookingEditDto { $formData = $this->createCompleteFormData(); - $formData->booking->pickupsTo = []; + $formData->booking->pickupsOutbound = []; return $formData; } @@ -405,8 +405,8 @@ class BookingDataProcessorTest extends TestCase $booking->paymentType = 'CC'; $booking->additionalServices = []; $booking->transportationServices = []; - $booking->pickupsTo = []; - $booking->pickupsFro = []; + $booking->pickupsOutbound = []; + $booking->pickupsInbound = []; $booking->participants = [ $this->createMockPersonalData('Participant0'), $this->createMockPersonalData('Participant1'), @@ -451,12 +451,12 @@ class BookingDataProcessorTest extends TestCase $participant->mobile = null; $participant->courses = []; $participant->additionalServices = []; - $participant->skiPass = []; + $participant->skiPass = null; $participant->board = []; $participant->rentals = []; - $participant->transportationServiceTo = $this->createMockService(1); - $participant->transportationServiceFro = $this->createMockService(2); - $participant->pickup = null; + $participant->transportationOutbound = $this->createMockService(1); + $participant->transportationInbound = $this->createMockService(2); + $participant->pickupOutbound = null; return $participant; }