wip: modernized edit flow

This commit is contained in:
Björn Fromme
2026-03-16 11:59:10 +01:00
parent 0d073a36a3
commit 28831a3b06
75 changed files with 1662 additions and 747 deletions
+67 -27
View File
@@ -9,7 +9,7 @@ use App\BusProNet\Model\Insurance;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Service;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDtoInterface;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
/**
@@ -22,17 +22,18 @@ use App\Form\Model\ParticipantDto;
class BookingPriceCalculatorService
{
public function __construct(
private readonly ParticipantEligibilityService $participantEligibilityService
private readonly ParticipantEligibilityService $participantEligibilityService,
) {
}
/**
* Calculates comprehensive pricing breakdown for a booking.
*
* @param BookingDtoInterface $bookingDto The booking data to calculate pricing for
* @param BookingDto $bookingDto The booking data to calculate pricing for
*
* @return array{rooms: array, services: array, grandTotal: float} Complete pricing breakdown
*/
public function getPricingBreakdown(BookingDtoInterface $bookingDto): array
public function getPricingBreakdown(BookingDto $bookingDto): array
{
$roomPricing = $this->calculateRoomPricing($bookingDto);
$servicePricing = $this->calculateServicePricing($bookingDto);
@@ -48,11 +49,11 @@ class BookingPriceCalculatorService
/**
* Calculates pricing for all selected rooms.
*
* @param BookingDtoInterface $bookingDto The booking data containing room selections
* @param BookingDto $bookingDto The booking data containing room selections
*
* @return array Array of room pricing data with labels, quantities, and totals
*/
public function calculateRoomPricing(BookingDtoInterface $bookingDto): array
public function calculateRoomPricing(BookingDto $bookingDto): array
{
$roomPricing = [];
@@ -95,11 +96,11 @@ class BookingPriceCalculatorService
*
* Only includes services from eligible participants (those with available skipasses for their age).
*
* @param BookingDtoInterface $bookingDto The booking data containing participants and their service selections
* @param BookingDto $bookingDto The booking data containing participants and their service selections
*
* @return array Array of service groups with each group containing services of the same subtype
*/
public function calculateServicePricing(BookingDtoInterface $bookingDto): array
public function calculateServicePricing(BookingDto $bookingDto): array
{
$participants = $bookingDto->getParticipants();
@@ -132,11 +133,11 @@ class BookingPriceCalculatorService
/**
* Calculates the grand total for the entire booking.
*
* @param BookingDtoInterface $bookingDto The booking data to calculate total for
* @param BookingDto $bookingDto The booking data to calculate total for
*
* @return float The grand total price
*/
public function calculateGrandTotal(BookingDtoInterface $bookingDto): float
public function calculateGrandTotal(BookingDto $bookingDto): float
{
$roomTotal = $this->calculateRoomTotal($bookingDto);
$serviceTotal = $this->calculateServiceTotal($bookingDto);
@@ -147,7 +148,7 @@ class BookingPriceCalculatorService
/**
* Calculates total price for all rooms.
*/
public function calculateRoomTotal(BookingDtoInterface $bookingDto): float
public function calculateRoomTotal(BookingDto $bookingDto): float
{
$roomPricing = $this->calculateRoomPricing($bookingDto);
@@ -157,7 +158,7 @@ class BookingPriceCalculatorService
/**
* Calculates total price for all services.
*/
public function calculateServiceTotal(BookingDtoInterface $bookingDto): float
public function calculateServiceTotal(BookingDto $bookingDto): float
{
$servicePricing = $this->calculateServicePricing($bookingDto);
@@ -194,12 +195,12 @@ class BookingPriceCalculatorService
* This method calculates the complete price breakdown for a single participant,
* including their room allocation (full room price) and all selected services.
*
* @param BookingDtoInterface $bookingDto The booking data containing all participants
* @param BookingDto $bookingDto The booking data containing all participants
* @param int $participantIndex The index of the participant to calculate for
*
* @return float The total price for the specified participant
*/
public function calculateIndividualParticipantPrice(BookingDtoInterface $bookingDto, int $participantIndex): float
public function calculateIndividualParticipantPrice(BookingDto $bookingDto, int $participantIndex): float
{
if (false === $bookingDto instanceof BookingCreateDto) {
return 0.0;
@@ -221,8 +222,8 @@ class BookingPriceCalculatorService
}
}
// Add service prices for this participant
$totalPrice += $this->calculateParticipantServiceTotal($participant);
// Add service prices for this participant (with booking context for bulk insurance)
$totalPrice += $this->calculateParticipantServiceTotal($participant, true, $bookingDto);
return $totalPrice;
}
@@ -230,11 +231,11 @@ class BookingPriceCalculatorService
/**
* Calculates individual prices for all participants in a booking.
*
* @param BookingDtoInterface $bookingDto The booking data containing all participants
* @param BookingDto $bookingDto The booking data containing all participants
*
* @return array Array indexed by participant index containing individual prices
*/
public function calculateAllParticipantIndividualPrices(BookingDtoInterface $bookingDto): array
public function calculateAllParticipantIndividualPrices(BookingDto $bookingDto): array
{
if (false === $bookingDto instanceof BookingCreateDto) {
return [];
@@ -256,12 +257,12 @@ class BookingPriceCalculatorService
* This method is used for insurance eligibility filtering to avoid circular dependency
* where insurance selection affects travel price which affects insurance eligibility.
*
* @param BookingDtoInterface $bookingDto The booking data containing all participants
* @param BookingDto $bookingDto The booking data containing all participants
* @param int $participantIndex The index of the participant to calculate for
*
* @return float The total price for the specified participant excluding insurance
*/
public function calculateIndividualParticipantPriceExcludingInsurance(BookingDtoInterface $bookingDto, int $participantIndex): float
public function calculateIndividualParticipantPriceExcludingInsurance(BookingDto $bookingDto, int $participantIndex): float
{
if (false === $bookingDto instanceof BookingCreateDto) {
return 0.0;
@@ -300,11 +301,11 @@ class BookingPriceCalculatorService
*
* Note: Transportation discounts will be handled generically by groupServicesBySubtype as "Beförderung - Rabatt"
*
* @param BookingDtoInterface $bookingDto The booking data containing participants
* @param BookingDto $bookingDto The booking data containing participants
*
* @return array Array of transportation line items (Zustieg, Parkplatz)
*/
private function aggregateTransportationServices(BookingDtoInterface $bookingDto): array
private function aggregateTransportationServices(BookingDto $bookingDto): array
{
$participants = $bookingDto->getParticipants();
@@ -527,12 +528,13 @@ class BookingPriceCalculatorService
/**
* Calculates the total service cost for a single participant.
*
* @param ParticipantDto $participant The participant to calculate services for
* @param bool $includeInsurance Whether to include insurance pricing (default: true)
* @param ParticipantDto $participant The participant to calculate services for
* @param bool $includeInsurance Whether to include insurance pricing (default: true)
* @param BookingDto|null $bookingDto Optional booking DTO for bulk insurance resolution
*
* @return float The total service cost for this participant
*/
private function calculateParticipantServiceTotal(ParticipantDto $participant, bool $includeInsurance = true): float
private function calculateParticipantServiceTotal(ParticipantDto $participant, bool $includeInsurance = true, ?BookingDto $bookingDto = null): float
{
$serviceTotal = 0.0;
@@ -545,8 +547,10 @@ class BookingPriceCalculatorService
$serviceTotal += $participant->rentalInsurance->price;
}
if ($includeInsurance && null !== $participant->insurance && null !== $participant->insurance->price) {
$serviceTotal += $participant->insurance->price;
// Get effective insurance (considering bulk insurance for dependent participants)
$effectiveInsurance = $this->getEffectiveInsurance($participant, $bookingDto);
if ($includeInsurance && null !== $effectiveInsurance && null !== $effectiveInsurance->price) {
$serviceTotal += $effectiveInsurance->price;
}
// Transportation services
@@ -630,4 +634,40 @@ class BookingPriceCalculatorService
$serviceAggregation[$serviceKey]['participantCount'] += $quantity;
$serviceAggregation[$serviceKey]['totalPrice'] += $insurance->price * $quantity;
}
/**
* Gets the effective insurance for a participant, considering bulk insurance assignment.
*
* When bulk insurance is active and the participant is a dependent (index > 0),
* returns the applicant's insurance. Otherwise returns the participant's own insurance.
*
* This method is used for pricing calculations to show correct prices when bulk
* insurance is enabled, even though the actual assignment happens in the processor.
*
* @param ParticipantDto $participant The participant to get insurance for
* @param BookingDto|null $bookingDto Optional booking DTO for bulk insurance check
*
* @return Insurance|null The effective insurance for pricing purposes
*/
private function getEffectiveInsurance(ParticipantDto $participant, ?BookingDto $bookingDto): ?Insurance
{
// If no booking context, use participant's own insurance
if (null === $bookingDto) {
return $participant->insurance;
}
// Applicant always uses their own insurance
if (0 === $participant->index) {
return $participant->insurance;
}
// Check if bulk insurance is active
$applicant = $bookingDto->getParticipant(0);
if (null === $applicant || false === $applicant->bulkInsuranceBooking) {
return $participant->insurance;
}
// Bulk insurance is active - use applicant's insurance for dependent participants
return $applicant->insurance;
}
}
+56 -7
View File
@@ -7,6 +7,7 @@ use App\BusProNet\Model\Travel;
use App\Exception\BookingSessionNotFoundException;
use App\Exception\NoRoomsAvailableException;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDto;
use App\Form\Model\RoomSelectionDto;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -15,6 +16,7 @@ class BookingService
{
public const BOOKING_CREATE_KEY = 'booking_create';
public const BOOKING_CREATE_BASELINE_KEY = 'booking_create_baseline_snapshot';
public const BOOKING_EDIT_KEY = 'booking_edit';
public function __construct(
private readonly TravelDataService $travelDataService,
@@ -81,6 +83,46 @@ class BookingService
throw new BookingSessionNotFoundException();
}
/**
* Saves the booking DTO to the session.
*
* @param Request $request The HTTP request with session
* @param object $bookingDto The booking DTO to persist
* @param string $mode The booking mode (create/edit)
*/
public function saveBookingDto(Request $request, object $bookingDto, string $mode): void
{
$key = BookingDto::MODE_CREATE === $mode ? self::BOOKING_CREATE_KEY : self::BOOKING_EDIT_KEY;
$request->getSession()->set($key, $bookingDto);
}
/**
* Retrieves the booking DTO from the session.
*
* @param Request $request The HTTP request containing session data
* @param string $mode The booking mode (create/edit)
*
* @return object|null The booking DTO from session or null if not found
*/
public function getBookingDto(Request $request, string $mode): ?object
{
$key = BookingDto::MODE_CREATE === $mode ? self::BOOKING_CREATE_KEY : self::BOOKING_EDIT_KEY;
return $request->getSession()->get($key);
}
/**
* Clears the booking DTO from the session.
*
* @param Request $request The HTTP request with session
* @param string $mode The booking mode (create/edit)
*/
public function clearBookingDto(Request $request, string $mode): void
{
$key = BookingDto::MODE_CREATE === $mode ? self::BOOKING_CREATE_KEY : self::BOOKING_EDIT_KEY;
$request->getSession()->remove($key);
}
/**
* Saves the booking creation DTO to the session.
*
@@ -92,7 +134,7 @@ class BookingService
*/
public function saveBookingCreateDto(Request $request, BookingCreateDto $bookingCreateDto): void
{
$request->getSession()->set(self::BOOKING_CREATE_KEY, $bookingCreateDto);
$this->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
}
/**
@@ -209,10 +251,10 @@ class BookingService
*
* @return array<int, int> an array where the key is the room ID and the value is the count of assigned participants
*/
public function getRoomAssignmentCounts(BookingCreateDto $bookingCreateDto): array
public function getRoomAssignmentCounts(BookingDto $bookingDto): array
{
$counts = [];
foreach ($bookingCreateDto->participants as $participant) {
foreach ($bookingDto->getParticipants() as $participant) {
if (null !== $participant->assignedRoomId) {
$counts[$participant->assignedRoomId] = ($counts[$participant->assignedRoomId] ?? 0) + 1;
}
@@ -226,11 +268,18 @@ class BookingService
*
* @return array{selectedRooms: array, participantCount: int, pricing: array}
*/
public function getRoomSummaryAndParticipantCount(BookingCreateDto $bookingCreateDto): array
public function getRoomSummaryAndParticipantCount(BookingDto $bookingDto): array
{
$selectedRooms = $bookingCreateDto->getSelectedRooms();
$participantCount = $this->getParticipantsCount($selectedRooms, $bookingCreateDto->travel);
$pricing = $this->priceCalculator->getPricingBreakdown($bookingCreateDto);
$selectedRooms = $bookingDto->getSelectedRooms();
// In edit mode, count actual participants; in create mode, calculate from room selections
if (BookingDto::MODE_EDIT === $bookingDto->getMode()) {
$participantCount = count($bookingDto->getParticipants());
} else {
$participantCount = $this->getParticipantsCount($selectedRooms, $bookingDto->travel);
}
$pricing = $this->priceCalculator->getPricingBreakdown($bookingDto);
return [
'selectedRooms' => $selectedRooms,
+26 -19
View File
@@ -7,6 +7,7 @@ namespace App\Service;
use App\BusProNet\Model\Insurance;
use App\BusProNet\Traits\SortByPriceTrait;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Model\InsuranceEligibilityCriteria;
use Carbon\Carbon;
@@ -21,6 +22,7 @@ use Carbon\Carbon;
class InsuranceMatchingService
{
use SortByPriceTrait;
public function __construct(
private readonly BookingPriceCalculatorService $priceCalculatorService,
) {
@@ -29,13 +31,13 @@ class InsuranceMatchingService
/**
* Filters insurances based on participant and booking criteria.
*
* @param array<Insurance> $insurances Available insurances to filter
* @param ParticipantDto $participant The participant to match insurances for
* @param BookingCreateDto $booking The booking context for additional criteria
* @param array<Insurance> $insurances Available insurances to filter
* @param ParticipantDto $participant The participant to match insurances for
* @param BookingDto $booking The booking context for additional criteria
*
* @return array<Insurance> Filtered array of eligible insurances
*/
public function getEligibleInsurances(array $insurances, ParticipantDto $participant, BookingCreateDto $booking): array
public function getEligibleInsurances(array $insurances, ParticipantDto $participant, BookingDto $booking): array
{
$criteria = $this->createEligibilityCriteria($participant, $booking);
@@ -58,14 +60,14 @@ class InsuranceMatchingService
* current insurance is no longer eligible. It finds the same insurance type
* (subType + familyInsurance) with the correct price tier.
*
* @param array<Insurance> $availableInsurances All available insurances
* @param Insurance $currentInsurance The currently selected insurance
* @param ParticipantDto $participant The participant to reassign for
* @param BookingCreateDto $booking The booking context
* @param array<Insurance> $availableInsurances All available insurances
* @param Insurance $currentInsurance The currently selected insurance
* @param ParticipantDto $participant The participant to reassign for
* @param BookingDto $booking The booking context
*
* @return Insurance|null The reassigned insurance or null if no suitable match found
*/
public function reassignInsuranceForPriceChange(array $availableInsurances, Insurance $currentInsurance, ParticipantDto $participant, BookingCreateDto $booking): ?Insurance
public function reassignInsuranceForPriceChange(array $availableInsurances, Insurance $currentInsurance, ParticipantDto $participant, BookingDto $booking): ?Insurance
{
// Group insurances of the same type
$sameTypeInsurances = $this->filterInsurancesByType($availableInsurances, $currentInsurance);
@@ -88,15 +90,15 @@ class InsuranceMatchingService
* (subType + familyInsurance) to all participants, but selects the appropriate price tier
* based on each participant's individual travel price.
*
* @param array<Insurance> $availableInsurances All available insurances
* @param Insurance $selectedInsurance The insurance selected by the applicant
* @param BookingCreateDto $booking The booking with all participants
* @param array<Insurance> $availableInsurances All available insurances
* @param Insurance $selectedInsurance The insurance selected by the applicant
* @param BookingDto $booking The booking with all participants
*
* @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, BookingDto $booking): array
{
$assignments = [];
@@ -118,7 +120,7 @@ class InsuranceMatchingService
* 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, BookingDto $booking): ?InsuranceEligibilityCriteria
{
// Early return if travel dates are missing - cannot evaluate any criteria
$travelStartDate = $booking->travel->dateFrom;
@@ -183,8 +185,13 @@ class InsuranceMatchingService
* Family insurances should only be available for family bookings,
* and individual insurances should only be available for non-family bookings.
*/
private function checkFamilyInsuranceConstraints(Insurance $insurance, BookingCreateDto $booking): bool
private function checkFamilyInsuranceConstraints(Insurance $insurance, BookingDto $booking): bool
{
// Family booking detection only available in BookingCreateDto
if (!$booking instanceof BookingCreateDto) {
return true; // Skip family constraints for edit mode
}
$isFamilyBooking = $booking->isFamilyBooking();
// If it's a family insurance, it should only be available for family bookings
@@ -308,12 +315,12 @@ class InsuranceMatchingService
* It excludes insurance prices to prevent circular dependency where insurance selection
* affects travel price which then affects insurance eligibility.
*
* @param BookingCreateDto $booking The booking to calculate price for
* @param int $participantIndex The participant index to calculate for
* @param BookingDto $booking The booking to calculate price for
* @param int $participantIndex The participant index to calculate for
*
* @return float The total travel price for the participant excluding insurance
*/
private function calculateTravelPrice(BookingCreateDto $booking, int $participantIndex): float
private function calculateTravelPrice(BookingDto $booking, int $participantIndex): float
{
// Use the price calculator to get the participant's individual price excluding insurance
return $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($booking, $participantIndex);
@@ -343,7 +350,7 @@ class InsuranceMatchingService
* Example: "Reise-Rücktritt + Selbstbehaltübernahme" at €10, €14, €26 are the same type,
* but different from "Reiseschutz Platin Auto/Bahn/Bus (Europa) + Selbstbehaltübernahme".
*
* @param array<Insurance> $insurances All available insurances to filter
* @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
@@ -6,7 +6,7 @@ namespace App\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Service;
use App\Form\Model\BookingDtoInterface;
use App\Form\Model\BookingDto;
use Carbon\CarbonImmutable;
use Spatie\Blink\Blink;
@@ -31,12 +31,12 @@ class ParticipantEligibilityService
*
* Results are cached per request to avoid redundant calculations.
*
* @param BookingDtoInterface $bookingDto The current booking data
* @param BookingDto $bookingDto The current booking data
* @param int $participantIndex The index of the participant being evaluated
*
* @return bool True if participant is eligible (has available skipasses)
*/
public function isParticipantEligible(BookingDtoInterface $bookingDto, int $participantIndex): bool
public function isParticipantEligible(BookingDto $bookingDto, int $participantIndex): bool
{
$participant = $bookingDto->getParticipant($participantIndex);
@@ -66,7 +66,7 @@ class ParticipantEligibilityService
/**
* Checks if a skipass service is available for the given participant based on age constraints.
*/
private function isSkiPassAvailableForParticipant(Service $service, BookingDtoInterface $bookingDto, int $participantIndex): bool
private function isSkiPassAvailableForParticipant(Service $service, BookingDto $bookingDto, int $participantIndex): bool
{
$participant = $bookingDto->getParticipant($participantIndex);
+77 -29
View File
@@ -121,7 +121,7 @@ class TravelDataService
'error' => $e->getMessage(),
]);
throw $e;
} catch (HotelNotFoundException | HotelNotInTravelException $e) {
} catch (HotelNotFoundException|HotelNotInTravelException $e) {
$this->logger->debug('Hotel not found in XML', [
'dateId' => $dateId,
'hotelId' => $hotelId,
@@ -433,7 +433,44 @@ class TravelDataService
*
* @return BaseData|null The mutability data or null if not available or error occurred
*/
public function getMutabilityData(int $dateId): ?BaseData
/**
* Gets mutability data for a travel date.
*
* @param int $dateId The travel date ID for API call
* @param bool $cached Whether to use cached data (default: true, TTL: 12 hours)
*
* @return BaseData|null The mutability data or null if not available or error occurred
*/
public function getMutabilityData(int $dateId, bool $cached = true): ?BaseData
{
if ($cached) {
$cacheKey = sprintf('mutability_%d', $dateId);
try {
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($dateId) {
// 12 hours TTL - mutability dates have date-only granularity
$item->expiresAfter(43200);
return $this->fetchMutabilityData($dateId);
});
} catch (InvalidArgumentException $e) {
$this->logger->error('Cache error in getMutabilityData', [
'dateId' => $dateId,
'error' => $e->getMessage(),
]);
// Fallback to direct API call
return $this->fetchMutabilityData($dateId);
}
}
return $this->fetchMutabilityData($dateId);
}
/**
* Fetches mutability data directly from the API without caching.
*/
private function fetchMutabilityData(int $dateId): ?BaseData
{
try {
$mutableData = $this->apiClient->getMutableData($dateId);
@@ -482,16 +519,44 @@ class TravelDataService
}
/**
* Fetch availability data from API.
* Gets availability data for a travel date.
*
* Retrieves availability information from the API for a specific travel date.
* Handles API errors and notification responses gracefully.
*
* @param int $dateId The travel date ID for API call
* @param int $dateId The travel date ID for API call
* @param bool $cached Whether to use cached data (default: false, TTL when cached: 60 seconds)
* @param int $ttl Cache TTL in seconds when $cached is true (default: 60 seconds)
*
* @return BaseData|null The availability data or null if not available or error occurred
*/
public function getAvailabilityData(int $dateId): ?BaseData
public function getAvailabilityData(int $dateId, bool $cached = false, int $ttl = 60): ?BaseData
{
if ($cached) {
$cacheKey = sprintf('availability_%d', $dateId);
try {
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($dateId, $ttl) {
// Short TTL - availability is volatile and changes with bookings
$item->expiresAfter($ttl);
return $this->fetchAvailabilityData($dateId);
});
} catch (InvalidArgumentException $e) {
$this->logger->error('Cache error in getAvailabilityData', [
'dateId' => $dateId,
'error' => $e->getMessage(),
]);
// Fallback to direct API call
return $this->fetchAvailabilityData($dateId);
}
}
return $this->fetchAvailabilityData($dateId);
}
/**
* Fetches availability data directly from the API without caching.
*/
private function fetchAvailabilityData(int $dateId): ?BaseData
{
try {
$availabilities = $this->apiClient->getAvailabilities($dateId);
@@ -524,9 +589,7 @@ 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.
* @deprecated Use getAvailabilityData($dateId, cached: true) instead
*
* @param int $dateId The travel date ID for API call
* @param int $ttl Cache TTL in seconds (default: 60 seconds)
@@ -535,23 +598,7 @@ class TravelDataService
*/
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);
}
return $this->getAvailabilityData($dateId, cached: true, ttl: $ttl);
}
/**
@@ -606,7 +653,8 @@ class TravelDataService
{
try {
$insurances = $this->insuranceLoader->loadAll();
$travel->insurances = array_values($insurances);
// Keep insurances indexed by ID for efficient lookups
$travel->insurances = $insurances;
// Hydrate package relationships after loading
// Packages lose their containedInsurances during serialization, so rebuild them