feat: correct determination of inquiry status and conditional voucher field display

This commit is contained in:
Björn Fromme
2025-11-25 16:41:25 +01:00
parent f33e55f201
commit ecc4741f56
19 changed files with 468 additions and 193 deletions
+2 -3
View File
@@ -14,6 +14,7 @@ use App\BusProNet\Model\Notification;
use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\PromoVoucher;
use App\BusProNet\Model\PurchaseVoucher;
use App\BusProNet\Model\ServiceAvailabilityResponse;
use App\BusProNet\Model\Travel;
use App\BusProNet\Traits\ApiClientTrait;
use App\BusProNet\XmlParser\ApiResponseParser;
@@ -226,8 +227,6 @@ class ApiClient
*/
public function createBookingInquiry(BookingDto $bookingDto, bool $debug = false): Notification|BookingResponse
{
// Forcibly override booking status to generate inquiry payload
$bookingDto->bookingStatus = 'A';
$payload = $this->bookingDataProcessor->createBookingRequestPayload($bookingDto, Constants::BOOKING_TYPE_INQUIRY);
$data = [
@@ -285,7 +284,7 @@ class ApiClient
/**
* @throws ApiClientException
*/
public function getAvailabilities(int $dateId): Notification|BaseData
public function getAvailabilities(int $dateId): Notification|ServiceAvailabilityResponse
{
$data = [
'user' => $this->config['bpn_username'],
+5
View File
@@ -52,6 +52,11 @@ final class Constants
public const BOOKING_TYPE_INQUIRY = 'Anfrage';
public const BOOKING_TYPE_BOOKING = 'Buchung';
// Booking status codes from buchungstatusmoeglich attribute
public const BOOKING_STATUS_FREE = 'F';
public const BOOKING_STATUS_INQUIRY = 'A';
public const BOOKING_STATUS_OPEN = 'O'; // Optionsbuchung - not relevant for current implementation
// Payment methods
public const PAYMENT_METHOD_TRANSFER = 'transfer';
public const PAYMENT_METHOD_DEBIT = 'debit';
@@ -893,10 +893,12 @@ class BookingDataProcessor
}
}
// Add promotional voucher as aktionscode (allowed for inquiry bookings)
$promotionalCode = $this->getPromoVoucherCodeForParticipant($participant);
if (null !== $promotionalCode) {
$participantData['aktionscode'] = $promotionalCode;
// Add promotional voucher as aktionscode (excluded from inquiry bookings)
if (false === $isInquiryBooking) {
$promotionalCode = $this->getPromoVoucherCodeForParticipant($participant);
if (null !== $promotionalCode) {
$participantData['aktionscode'] = $promotionalCode;
}
}
// Add goodwill voucher as participant-level einloesecode
@@ -926,15 +928,16 @@ class BookingDataProcessor
$this->addServicesFromMap($payload, 'versicherungen', 'versicherung', '@idversicherung', $insuranceMap);
// Add purchase vouchers (regular purchase vouchers, excluding goodwill vouchers)
// Purchase vouchers are allowed for both inquiry and final bookings
// Goodwill vouchers are handled separately and excluded from inquiry bookings
$purchaseVouchers = $this->collectPurchaseVouchers($bookingDto);
if (false === empty($purchaseVouchers)) {
$payload['gutscheine']['gutschein'] = [];
foreach ($purchaseVouchers as $code) {
$payload['gutscheine']['gutschein'][] = [
'@einloesecode' => $code,
];
// All vouchers (purchase, promotional, goodwill) are excluded from inquiry bookings
if (false === $isInquiryBooking) {
$purchaseVouchers = $this->collectPurchaseVouchers($bookingDto);
if (false === empty($purchaseVouchers)) {
$payload['gutscheine']['gutschein'] = [];
foreach ($purchaseVouchers as $code) {
$payload['gutscheine']['gutschein'][] = [
'@einloesecode' => $code,
];
}
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* Response model for service availability API calls.
*
* Contains both service-level availability data and travel-level
* booking status information from the VERFUEGBARKEIT endpoint.
*/
class ServiceAvailabilityResponse
{
/**
* @param array<int, Availability> $services Service availability data indexed by service ID
* @param array<string> $allowedBookingStatus Allowed booking status codes (e.g., ['F', 'O', 'A'])
* @param string|null $travelStatus Travel status from API (e.g., 'frei', 'anfrage')
*/
public function __construct(
private readonly array $services,
public readonly array $allowedBookingStatus = [],
public readonly ?string $travelStatus = null,
) {
}
/**
* @return array<int, Availability>
*/
public function getServices(): array
{
return $this->services;
}
}
+70 -6
View File
@@ -56,6 +56,15 @@ class Travel
#[Groups(['api:single'])]
public ?string $status = null;
/**
* Allowed booking status codes from buchungstatusmoeglich attribute.
* Array of status codes (F, A, O) indicating which booking types are permitted.
*
* @var array<string>
*/
#[Groups(['api:single'])]
public array $allowedBookingStatus = [];
#[Groups(['api:list', 'api:single'])]
public ?float $priceFrom = null;
@@ -223,19 +232,18 @@ class Travel
}
/**
* Retrieves all available rooms for booking.
* Retrieves all bookable rooms (both regular and inquiry).
*
* Filters the rooms collection to return only rooms that have availability
* greater than zero and have an available status. This ensures only
* bookable rooms are returned for selection.
* Returns rooms that have availability greater than zero and a bookable status
* (either 'Frei' for regular booking or 'Anfrage' for inquiry booking).
*
* @return array<int, Room> The filtered array of available rooms, indexed by room ID
* @return array<int, Room> The filtered array of bookable rooms, indexed by room ID
*/
public function getAvailableRooms(): array
{
$result = [];
foreach ($this->rooms as $room) {
if ($room->available > 0 && Constants::STATUS_AVAILABLE === $room->status) {
if ($room->available > 0 && $this->isBookableRoomStatus($room->status)) {
$result[$room->id] = $room;
}
}
@@ -243,6 +251,50 @@ class Travel
return $result;
}
/**
* Checks if the travel has any bookable rooms.
*
* @return bool True if at least one room is bookable
*/
public function hasBookableRooms(): bool
{
return [] !== $this->getAvailableRooms();
}
/**
* Checks if all bookable rooms require inquiry booking.
*
* Returns true if there are bookable rooms but none with 'Frei' status.
* This indicates the booking must be submitted as an inquiry.
*
* @return bool True if only inquiry rooms are available
*/
public function requiresInquiryBooking(): bool
{
$bookableRooms = $this->getAvailableRooms();
if ([] === $bookableRooms) {
return false;
}
foreach ($bookableRooms as $room) {
if (Constants::STATUS_AVAILABLE === $room->status) {
return false;
}
}
return true;
}
/**
* Checks if a room status allows booking.
*/
private function isBookableRoomStatus(?string $status): bool
{
return Constants::STATUS_AVAILABLE === $status
|| Constants::STATUS_ON_REQUEST === $status;
}
/**
* Retrieves a room by its ID.
*
@@ -260,4 +312,16 @@ class Travel
return false === empty($rooms) ? reset($rooms) : null;
}
/**
* Checks if a specific booking status code is allowed for this travel.
*
* @param string $statusCode The booking status code to check (F, A, or O)
*
* @return bool True if the status is allowed, false otherwise
*/
public function isBookingStatusAllowed(string $statusCode): bool
{
return in_array($statusCode, $this->allowedBookingStatus, true);
}
}
+12 -5
View File
@@ -4,6 +4,7 @@ namespace App\BusProNet\XmlLoader;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\MutableData;
use App\BusProNet\Model\ServiceAvailabilityResponse;
use App\BusProNet\Model\Travel;
use App\BusProNet\Utility\DateCodeUtility;
use App\BusProNet\XmlParser\TravelParser;
@@ -278,20 +279,26 @@ class TravelLoader extends AbstractLoader
* Apply availability data to travel services.
*
* Updates the availability status of additional and transportation
* services based on the provided availability data.
* services, and the allowed booking status, based on the provided
* availability data.
*
* @param Travel $travel The travel object to update
* @param BaseData $availabilities The availability data for services
* @param Travel $travel The travel object to update
* @param ServiceAvailabilityResponse $availabilities The availability data for services
*/
public function patchAvailabilities(Travel $travel, BaseData $availabilities): void
public function patchAvailabilities(Travel $travel, ServiceAvailabilityResponse $availabilities): void
{
$serviceAvailabilities = $availabilities->getItems();
$serviceAvailabilities = $availabilities->getServices();
foreach ([...$travel->additionalServices, ...$travel->transportationServices] as $service) {
if (array_key_exists($service->id, $serviceAvailabilities)) {
$service->available = $serviceAvailabilities[$service->id]->available;
}
}
// Patch travel-level booking status from availability response
if ([] !== $availabilities->allowedBookingStatus) {
$travel->allowedBookingStatus = $availabilities->allowedBookingStatus;
}
}
/**
@@ -5,11 +5,12 @@ namespace App\BusProNet\XmlParser;
use App\BusProNet\Model\Availability;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\ServiceAvailabilityResponse;
use Symfony\Component\DomCrawler\Crawler;
class AvailabilitiesParser extends AbstractParser
{
public function parseServices(Crawler $result): BaseData
public function parseServices(Crawler $result): ServiceAvailabilityResponse
{
$availabilities = [];
@@ -26,7 +27,21 @@ class AvailabilitiesParser extends AbstractParser
})
;
return new BaseData($availabilities);
// Parse travel-level booking status from <reise> node
$allowedBookingStatus = [];
$travelStatus = null;
$reiseNode = $result->filterXPath('//reise');
if ($reiseNode->count() > 0) {
$travelStatus = $reiseNode->attr('status');
$bookingStatusPossible = $reiseNode->attr('buchungstatusmoeglich') ?? '';
if ('' !== $bookingStatusPossible) {
$allowedBookingStatus = str_split($bookingStatusPossible);
}
}
return new ServiceAvailabilityResponse($availabilities, $allowedBookingStatus, $travelStatus);
}
public function parseRooms(Crawler $result): BaseData
-1
View File
@@ -62,7 +62,6 @@ class TravelParser extends AbstractParser
}
$travel->type = $node->attr('reiseart');
$travel->status = $this->getStringOrNullValue($node->filterXPath('//status_hin'));
$travel->priceFrom = $this->stringToFloat($this->getStringOrNullValue($node->filterXPath('//abpreis')));
$travel->selectionGroups = $this->getSelectionGroups($node->filterXPath('//selektiongruppe'));
$travel->additionalServices = $this
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\Controller\Booking\Create;
use App\BusProNet\XmlLoader\AgencyLoader;
use App\Exception\BookingNotPossibleException;
use App\Exception\HotelNotFoundException;
use App\Exception\HotelNotInTravelException;
use App\Exception\NoRoomsAvailableException;
@@ -70,8 +69,6 @@ class IndexController extends AbstractController
throw $this->createNotFoundException(sprintf('Hotel ID %d is not available for travel ID %d', $hotelId, $dateId));
} catch (NoRoomsAvailableException) {
throw $this->createNotFoundException('No rooms available for this travel.');
} catch (BookingNotPossibleException) {
throw $this->createNotFoundException('Booking is not possible for this travel (Buchungsstop).');
}
}
@@ -70,6 +70,9 @@ class Step1Controller extends AbstractController
$this->bookingService->resetParticipantAssignments($bookingCreateDto);
}
// Check if room selection forces inquiry mode
$this->bookingService->updateBookingStatusFromRoomSelection($bookingCreateDto);
$bookingCreateDto->currentStep = 2;
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
@@ -88,6 +88,24 @@ class Step3Controller extends AbstractController
}
if (false === $inquiryResponse->isInquiryValid()) {
// Check if API suggests inquiry booking instead of showing error
if ($this->shouldFallbackToInquiryMode($inquiryResponse)) {
// Auto-switch to inquiry mode
$bookingCreateDto->bookingStatus = 'A';
$bookingCreateDto->currentStep = 4;
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
$message = 'Buchung konnte nicht validiert werden.';
if (null !== $inquiryResponse->message && '' !== trim($inquiryResponse->message)) {
$message = $inquiryResponse->message;
}
$this->addFlash('info', $message);
return $this->hxRedirect($request, $this->generateUrl('app_booking_create_step_4'));
}
// Not a fallback scenario - show validation error
$errorMessage = 'Buchung konnte nicht validiert werden.';
if (null !== $inquiryResponse->message && '' !== trim($inquiryResponse->message)) {
$errorMessage .= ' '.$inquiryResponse->message;
@@ -199,4 +217,31 @@ class Step3Controller extends AbstractController
'groupedSelectedRooms' => $groupedSelectedRooms,
]);
}
/**
* Determines if validation failure should trigger inquiry mode fallback.
*
* Checks if the API response indicates that the booking should proceed as
* an inquiry rather than showing an error. This handles scenarios where
* availability changes between booking initialization and validation.
*
* @param \App\BusProNet\Model\BookingResponse $response The API response
*
* @return bool True if should fallback to inquiry mode, false if should show error
*/
private function shouldFallbackToInquiryMode(\App\BusProNet\Model\BookingResponse $response): bool
{
// Check for "nicht möglich" status + inquiry suggestion in message
if ('nicht möglich' !== $response->status) {
return false;
}
$message = $response->message ?? '';
if ('' === $message) {
return false;
}
// Check if message suggests inquiry booking (case-insensitive)
return 1 === preg_match('/anfrage/i', $message);
}
}
@@ -21,6 +21,7 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Cache\CacheInterface;
/**
* Handles the fourth step of the booking creation process (confirmation).
@@ -36,6 +37,7 @@ class Step4Controller extends AbstractController
private readonly BookingSummaryDataService $summaryDataService,
private readonly BookingPriceCalculatorService $priceCalculator,
private readonly ApiClient $apiClient,
private readonly CacheInterface $cache,
private readonly LoggerInterface $logger,
) {
}
@@ -99,6 +101,7 @@ class Step4Controller extends AbstractController
// Success: Store booking number in flash and clear session
$this->addFlash('booking_number', $bookingResponse->transactionNumber);
$this->clearTravelDataCache($bookingCreateDto);
$this->bookingService->clearBookingCreateDto($request);
return $this->hxRedirect($request, $this->generateUrl('app_booking_create_success'));
@@ -151,4 +154,25 @@ class Step4Controller extends AbstractController
'participantPrices' => $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingCreateDto),
]);
}
/**
* Clears travel data and availability cache after successful booking.
*/
private function clearTravelDataCache(BookingDto $bookingDto): void
{
$dateId = $bookingDto->travel->id;
$hotelId = $bookingDto->hotelId;
// Clear availability cache
$this->cache->delete(sprintf('availability_%d', $dateId));
// Clear travel data cache (both local and remote variants)
$this->cache->delete(sprintf('travel_unified_%d_%d_local', $dateId, $hotelId));
$this->cache->delete(sprintf('travel_unified_%d_%d_remote', $dateId, $hotelId));
$this->logger->info('Cleared travel data cache after successful booking', [
'dateId' => $dateId,
'hotelId' => $hotelId,
]);
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Form\Service\Condition;
use App\Form\Model\BookingDto;
use App\Form\Service\Contract\FieldConditionInterface;
/**
* Condition that checks if the booking is a final booking (not an inquiry).
*
* This condition is used to exclude certain fields (like vouchers) from inquiry
* bookings where they are not applicable. When a booking has inquiry status ('A'),
* fields with this condition will be excluded from the form.
*/
class FinalBookingOnlyCondition implements FieldConditionInterface
{
/**
* Evaluates if the booking is a final booking.
*
* @param BookingDto $bookingDto The current booking data (create or edit)
* @param int $participantIndex The index of the participant being evaluated
* @param array<string, mixed> $formData Current form data (unused)
*
* @return bool True if booking status is 'F' (final), false if 'A' (inquiry)
*/
public function evaluate(BookingDto $bookingDto, int $participantIndex, array $formData): bool
{
return 'F' === $bookingDto->bookingStatus;
}
public function getDependentFields(): array
{
return [];
}
public function getDescription(): string
{
return 'Only available for final bookings (not inquiry bookings)';
}
}
+12 -3
View File
@@ -13,6 +13,7 @@ use App\Form\Service\Condition\BulkInsuranceBookingCondition;
use App\Form\Service\Condition\CompositeCondition;
use App\Form\Service\Condition\DateOfBirthProvidedCondition;
use App\Form\Service\Condition\FieldValueCondition;
use App\Form\Service\Condition\FinalBookingOnlyCondition;
use App\Form\Service\Condition\RentalSelectionCondition;
use App\Form\Service\Condition\RoomSelectionCondition;
use App\Form\Service\Condition\ServiceSubTypeCondition;
@@ -267,9 +268,17 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
'static_text' => new SingleRoomTypeCondition(),
];
// Voucher fields (purchaseVoucherCode, promoVoucherCode) have no state conditions
// They are enabled by default for all participants in create mode
// No visibility rules - always shown to allow optional voucher entry
// Voucher fields - hidden for inquiry bookings (status='A')
// Vouchers cannot be redeemed or submitted for inquiry bookings
$finalBookingOnlyCondition = new FinalBookingOnlyCondition();
$this->fieldStateConditions['purchaseVoucherCode'] = [
'hidden' => CompositeCondition::not($finalBookingOnlyCondition),
];
$this->fieldStateConditions['promoVoucherCode'] = [
'hidden' => CompositeCondition::not($finalBookingOnlyCondition),
];
// Example field state conditions would be registered here
// For demonstration purposes, here are some example patterns:
+75 -21
View File
@@ -5,7 +5,6 @@ namespace App\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Travel;
use App\Exception\BookingNotPossibleException;
use App\Exception\BookingSessionNotFoundException;
use App\Exception\NoRoomsAvailableException;
use App\Form\Model\BookingDto;
@@ -192,7 +191,6 @@ class BookingService
* Handles three booking status types:
* - 'Frei': Regular booking with availability checks
* - 'Anfrage': Inquiry booking, allows booking even with 0 availability
* - 'Buchungsstop': Booking stopped, no bookings allowed
*/
public function startFreshBooking(Request $request, int $dateId, int $hotelId, ?int $agencyId = null): BookingDto
{
@@ -201,30 +199,23 @@ class BookingService
throw new NotFoundHttpException(sprintf('Travel data not found for date ID %d and hotel ID %d', $dateId, $hotelId));
}
// Handle Buchungsstop - no bookings allowed at all
if ('Buchungsstop' === $travelData->status) {
throw new BookingNotPossibleException($dateId, $hotelId);
// Fetch and patch availability data (includes allowedBookingStatus from API)
$availabilities = $this->travelDataService->getAvailabilityData($dateId);
if (null !== $availabilities) {
$this->travelDataService->patchAvailabilities($travelData, $availabilities);
}
// Determine booking status based on travel status
$isInquiryBooking = 'Anfrage' === $travelData->status;
$bookingStatus = $isInquiryBooking ? 'A' : 'F';
// Get available rooms
// Get bookable rooms (Frei or Anfrage with available > 0)
$availableRooms = $travelData->getAvailableRooms();
// For regular bookings (Frei), prevent entry when no rooms are available
// For inquiry bookings (Anfrage), allow even with 0 availability
if (false === $isInquiryBooking && empty($availableRooms)) {
// No bookable rooms means booking is not possible
if ([] === $availableRooms) {
throw new NoRoomsAvailableException($dateId, $hotelId);
}
// For inquiry bookings with 0 availability, get all rooms ignoring availability count
if ($isInquiryBooking && empty($availableRooms)) {
$availableRooms = array_filter($travelData->rooms, function (Room $room) {
return Constants::STATUS_AVAILABLE === $room->status;
});
}
// Determine initial booking status based on room availability
// This is for early UI decisions (e.g., voucher visibility), final verdict is in step 3
$bookingStatus = $this->determineInitialBookingStatus($travelData);
// Create room selections with zero quantities (user will set these in step 1)
$roomSelections = array_map(
@@ -334,11 +325,11 @@ class BookingService
}
/**
* Groups available rooms by selection type ('by_pax' or 'by_room').
* Groups available rooms by selection type ('by_pax' or 'by_room') and sorts them by maxPax.
*
* @param array<int, Room> $rooms Rooms indexed by room ID
*
* @return array{by_pax: array<int, Room>, by_room: array<int, Room>}
* @return array{by_pax: array<int, Room>, by_room: array<int, Room>} Rooms grouped and sorted by maxPax (ascending)
*/
public function groupRoomsBySelectionType(array $rooms): array
{
@@ -354,6 +345,10 @@ class BookingService
}
}
// Sort each group by maxPax (ascending order - smallest capacity first)
uasort($groups[Room::SELECTION_TYPE_BY_PAX], fn (Room $a, Room $b): int => $a->maxPax <=> $b->maxPax);
uasort($groups[Room::SELECTION_TYPE_BY_ROOM], fn (Room $a, Room $b): int => $a->maxPax <=> $b->maxPax);
return $groups;
}
@@ -491,4 +486,63 @@ class BookingService
$participant->additionalServices = $currentSelections;
}
}
/**
* Determines the initial booking status based on travel configuration.
*
* This method implements a multi-stage check to determine if a booking
* should start as inquiry ('A') or final ('F') booking:
* 1. Checks if final bookings are allowed via buchungstatusmoeglich attribute from API
* 2. Checks if only inquiry rooms are available (no Frei rooms with available > 0)
*
* @param Travel $travelData The travel data to evaluate
*
* @return string 'A' for inquiry booking, 'F' for final booking
*/
private function determineInitialBookingStatus(Travel $travelData): string
{
// Check 1: Allowed booking status from buchungstatusmoeglich attribute (from availability API)
// Only applies if the API provided status restrictions
if ([] !== $travelData->allowedBookingStatus
&& false === $travelData->isBookingStatusAllowed(Constants::BOOKING_STATUS_FREE)) {
return 'A';
}
// Check 2: Only inquiry rooms available (all rooms with available > 0 have status 'Anfrage')
if ($travelData->requiresInquiryBooking()) {
return 'A';
}
return 'F';
}
/**
* Updates booking status based on selected room requirements.
*
* Checks if any selected room has inquiry-only status. If so, forces
* the entire booking to inquiry mode. This override happens after room
* selection in Step 1 and respects the business rule: if ANY room requires
* inquiry, the whole booking becomes an inquiry.
*
* @param BookingDto $bookingDto The booking DTO to update
*/
public function updateBookingStatusFromRoomSelection(BookingDto $bookingDto): void
{
// Skip if already inquiry - no need to check
if ('A' === $bookingDto->bookingStatus) {
return;
}
// Check selected rooms for inquiry-only status
foreach ($bookingDto->getSelectedRooms() as $roomSelection) {
$room = $bookingDto->travel->getRoomById($roomSelection->roomId);
if ($room
&& Constants::STATUS_ON_REQUEST === $room->status
&& $room->available > 0) {
$bookingDto->bookingStatus = 'A';
return;
}
}
}
}
+9 -7
View File
@@ -8,6 +8,7 @@ use App\BusProNet\ApiClient;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\Notification;
use App\BusProNet\Model\ServiceAvailabilityResponse;
use App\BusProNet\Model\Travel;
use App\BusProNet\XmlLoader\HotelLoader;
use App\BusProNet\XmlLoader\InsuranceLoader;
@@ -525,9 +526,9 @@ class TravelDataService
* @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
* @return ServiceAvailabilityResponse|null The availability data or null if not available or error occurred
*/
public function getAvailabilityData(int $dateId, bool $cached = false, int $ttl = 60): ?BaseData
public function getAvailabilityData(int $dateId, bool $cached = false, int $ttl = 60): ?ServiceAvailabilityResponse
{
if ($cached) {
$cacheKey = sprintf('availability_%d', $dateId);
@@ -556,7 +557,7 @@ class TravelDataService
/**
* Fetches availability data directly from the API without caching.
*/
private function fetchAvailabilityData(int $dateId): ?BaseData
private function fetchAvailabilityData(int $dateId): ?ServiceAvailabilityResponse
{
try {
$availabilities = $this->apiClient->getAvailabilities($dateId);
@@ -590,12 +591,13 @@ class TravelDataService
* Apply availability data to travel services.
*
* Updates the availability status of additional and transportation
* services based on the provided availability data.
* services, and the allowed booking status, based on the provided
* availability data.
*
* @param Travel $travel The travel object to update
* @param BaseData $availabilities The availability data for services
* @param Travel $travel The travel object to update
* @param ServiceAvailabilityResponse $availabilities The availability data for services
*/
public function patchAvailabilities(Travel $travel, BaseData $availabilities): void
public function patchAvailabilities(Travel $travel, ServiceAvailabilityResponse $availabilities): void
{
$this->travelLoader->patchAvailabilities($travel, $availabilities);