feat: persist travel snapshots and refresh extended availability
This commit is contained in:
1 parent
c1ab6d8687
commit
bc7fb794bf
31 files changed
+2328
-91
No files matched your search
@@ -12,6 +12,7 @@ use App\BusProNet\Model\Booking;
|
||||
use App\BusProNet\Model\BookingResponse;
|
||||
use App\BusProNet\Model\BookingUpdate;
|
||||
use App\BusProNet\Model\CrmAttributes;
|
||||
use App\BusProNet\Model\ExtendedServiceAvailabilityResponse;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\BusProNet\Model\PersonalData;
|
||||
use App\BusProNet\Model\PromoVoucher;
|
||||
@@ -38,6 +39,7 @@ class ApiClient
|
||||
public const TYPE_BASE_DATA_COUNTRIES = 'STAMMLAENDER';
|
||||
public const TYPE_MUTABLE_DATA = 'MOEGLICHEAENDERUNGEN';
|
||||
public const TYPE_AVAILABILITY = 'VERFUEGBARKEIT';
|
||||
public const TYPE_AVAILABILITY_EXTENDED = 'VERFUEGBARKEIT2';
|
||||
public const TYPE_AVAILABILITY_HOTEL = 'VERFUEGBARKEITHOTEL';
|
||||
public const TYPE_BOOKING_UPDATE = 'BUCHUNGAENDERUNG';
|
||||
public const TYPE_BOOKING = 'BUCHUNG';
|
||||
@@ -315,6 +317,21 @@ class ApiClient
|
||||
return $this->sendRequest(static::TYPE_AVAILABILITY, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ApiClientException
|
||||
*/
|
||||
public function getAvailabilitiesExtended(int $dateId): Notification|ExtendedServiceAvailabilityResponse
|
||||
{
|
||||
$data = [
|
||||
'user' => $this->config['bpn_username'],
|
||||
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_AVAILABILITY_EXTENDED),
|
||||
'satz' => ['@typ' => static::TYPE_AVAILABILITY_EXTENDED],
|
||||
'idreise' => $dateId,
|
||||
];
|
||||
|
||||
return $this->sendRequest(static::TYPE_AVAILABILITY_EXTENDED, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ApiClientException
|
||||
*/
|
||||
|
||||
@@ -36,14 +36,50 @@ class Booking
|
||||
public ?string $paymentType = null;
|
||||
public ?string $paymentLabel = null;
|
||||
public ?BankAccount $bankAccount = null;
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public array $participantsStatus = [];
|
||||
|
||||
/**
|
||||
* @var array<int, PersonalData>
|
||||
*/
|
||||
public array $participants = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Service>
|
||||
*/
|
||||
public array $transportationServices = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Service>
|
||||
*/
|
||||
public array $additionalServices = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Room>
|
||||
*/
|
||||
public array $rooms = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Pickup>
|
||||
*/
|
||||
public array $pickups = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Pickup>
|
||||
*/
|
||||
public array $dropOffs = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Surcharge>
|
||||
*/
|
||||
public array $surcharges = [];
|
||||
|
||||
/**
|
||||
* @var array<string|int, Insurance>
|
||||
*/
|
||||
public array $insurances = [];
|
||||
public ?int $invoiceNumber = null;
|
||||
public ?float $totalPrice = null;
|
||||
|
||||
@@ -12,8 +12,23 @@ namespace App\BusProNet\Model;
|
||||
*/
|
||||
class CrmAttributes
|
||||
{
|
||||
/**
|
||||
* @var array<int, CrmSelectionGroup>|null
|
||||
*/
|
||||
public ?array $selectionGroups = null;
|
||||
|
||||
/**
|
||||
* @var array<int, CrmAction>|null
|
||||
*/
|
||||
public ?array $crmActions = null;
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public array $hotelCodes = [];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public array $roles = [];
|
||||
}
|
||||
@@ -38,6 +38,9 @@ class CrmSelectionGroup
|
||||
#[Groups(['api:single'])]
|
||||
public ?string $label = null;
|
||||
|
||||
/**
|
||||
* @var array<int, CrmSelection>|null
|
||||
*/
|
||||
#[Groups(['api:single'])]
|
||||
public ?array $selections = null;
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Model;
|
||||
|
||||
/**
|
||||
* Extended availability details for one service from VERFUEGBARKEIT2.
|
||||
*
|
||||
* This model augments classic availability with optional timing/date/description
|
||||
* fields used to enrich persisted travel snapshots.
|
||||
*/
|
||||
class ExtendedAvailability
|
||||
{
|
||||
public ?int $serviceId = null;
|
||||
public ?string $status = null;
|
||||
public ?int $available = null;
|
||||
public ?float $price = null;
|
||||
public ?\DateTimeImmutable $dateFrom = null;
|
||||
public ?\DateTimeImmutable $dateTo = null;
|
||||
public ?string $timeFrom = null;
|
||||
public ?string $description = null;
|
||||
public ?int $ageFrom = null;
|
||||
public ?int $ageTo = null;
|
||||
public ?bool $mandatory = null;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Model;
|
||||
|
||||
/**
|
||||
* Response model for extended availability endpoint payloads.
|
||||
*/
|
||||
class ExtendedServiceAvailabilityResponse
|
||||
{
|
||||
/**
|
||||
* @param array<int, ExtendedAvailability> $services
|
||||
* @param array<string> $allowedBookingStatus
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $services,
|
||||
public readonly array $allowedBookingStatus = [],
|
||||
public readonly ?string $travelStatus = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, ExtendedAvailability>
|
||||
*/
|
||||
public function getServices(): array
|
||||
{
|
||||
return $this->services;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,9 @@ class Travel
|
||||
#[Groups(['api:list', 'api:single'])]
|
||||
public ?string $productCode = null;
|
||||
|
||||
#[Groups(['api:list', 'api:single'])]
|
||||
public ?int $productId = null;
|
||||
|
||||
#[Groups(['api:list', 'api:single'])]
|
||||
public ?string $label = null;
|
||||
|
||||
@@ -68,30 +71,48 @@ class Travel
|
||||
#[Groups(['api:list', 'api:single'])]
|
||||
public ?float $priceFrom = null;
|
||||
|
||||
/**
|
||||
* @var array<int, CrmSelectionGroup>
|
||||
*/
|
||||
#[Groups(['api:single'])]
|
||||
public array $selectionGroups = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Service>
|
||||
*/
|
||||
#[Groups(['api:single'])]
|
||||
public array $additionalServices = [];
|
||||
|
||||
#[Groups(['api:single'])]
|
||||
public bool $additionalServicesMutable = true;
|
||||
|
||||
/**
|
||||
* @var array<int, Service>
|
||||
*/
|
||||
#[Groups(['api:single'])]
|
||||
public array $transportationServices = [];
|
||||
|
||||
#[Groups(['api:single'])]
|
||||
public bool $transportationServicesMutable = true;
|
||||
|
||||
/**
|
||||
* @var array<int, Pickup>
|
||||
*/
|
||||
#[Groups(['api:single'])]
|
||||
public array $pickups = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Pickup>
|
||||
*/
|
||||
#[Groups(['api:single'])]
|
||||
public array $dropOffs = [];
|
||||
|
||||
#[Groups(['api:single'])]
|
||||
public bool $pickupsMutable = true;
|
||||
|
||||
/**
|
||||
* @var array<int, Room>
|
||||
*/
|
||||
#[Groups(['api:single'])]
|
||||
public array $rooms = [];
|
||||
|
||||
@@ -108,7 +129,7 @@ class Travel
|
||||
public ?Guide $guide = null;
|
||||
|
||||
/**
|
||||
* @var array<Insurance> Available insurances for this travel package
|
||||
* @var array<string|int, Insurance> Available insurances for this travel package
|
||||
*/
|
||||
#[Groups(['api:single'])]
|
||||
public array $insurances = [];
|
||||
|
||||
@@ -61,6 +61,8 @@ class ApiResponseParser extends AbstractParser
|
||||
return (new MutableDataParser())->parse($resultNode);
|
||||
case ApiClient::TYPE_AVAILABILITY:
|
||||
return (new AvailabilitiesParser())->parseServices($resultNode);
|
||||
case ApiClient::TYPE_AVAILABILITY_EXTENDED:
|
||||
return (new ExtendedAvailabilitiesParser())->parseServices($resultNode);
|
||||
case ApiClient::TYPE_AVAILABILITY_HOTEL:
|
||||
return (new AvailabilitiesParser())->parseRooms($resultNode);
|
||||
case ApiClient::TYPE_BOOKING_UPDATE:
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
use App\BusProNet\Model\ExtendedAvailability;
|
||||
use App\BusProNet\Model\ExtendedServiceAvailabilityResponse;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
/**
|
||||
* Parser for VERFUEGBARKEIT2 service payloads.
|
||||
*
|
||||
* Extracts extended service-level attributes (dates, times, hints, constraints)
|
||||
* plus optional travel-level booking-status metadata.
|
||||
*/
|
||||
class ExtendedAvailabilitiesParser extends AbstractParser
|
||||
{
|
||||
/**
|
||||
* Parses extended availability services from API result node.
|
||||
*/
|
||||
public function parseServices(Crawler $result): ExtendedServiceAvailabilityResponse
|
||||
{
|
||||
$availabilities = [];
|
||||
|
||||
$result
|
||||
->filterXPath('//leistungen/leistung')
|
||||
->each(function (Crawler $node) use (&$availabilities) {
|
||||
$serviceId = (int) $node->attr('id');
|
||||
|
||||
$availability = new ExtendedAvailability();
|
||||
$availability->serviceId = $serviceId;
|
||||
$availability->status = $node->attr('status');
|
||||
$availability->available = (int) $node->attr('frei');
|
||||
$availability->price = $this->stringToFloat($node->attr('preis'));
|
||||
$availability->dateFrom = $this->stringToDate($node->attr('termin'));
|
||||
$availability->dateTo = $this->stringToDate($node->attr('bis'));
|
||||
$availability->timeFrom = $node->attr('uhrzeit_von');
|
||||
$availability->description = $node->attr('hinweis');
|
||||
|
||||
$ageFrom = $node->attr('altervon');
|
||||
if (null !== $ageFrom && '' !== trim($ageFrom)) {
|
||||
$availability->ageFrom = (int) $ageFrom;
|
||||
}
|
||||
|
||||
$ageTo = $node->attr('alterbis');
|
||||
if (null !== $ageTo && '' !== trim($ageTo)) {
|
||||
$availability->ageTo = (int) $ageTo;
|
||||
}
|
||||
|
||||
$mandatory = $node->attr('pflicht');
|
||||
if (null !== $mandatory && '' !== trim($mandatory)) {
|
||||
$availability->mandatory = $this->stringToBool($mandatory);
|
||||
}
|
||||
|
||||
$availabilities[$serviceId] = $availability;
|
||||
})
|
||||
;
|
||||
|
||||
$allowedBookingStatus = [];
|
||||
$travelStatus = null;
|
||||
$reiseNode = $result->filterXPath('//reise');
|
||||
|
||||
if (0 < $reiseNode->count()) {
|
||||
$travelStatus = $reiseNode->attr('status');
|
||||
|
||||
$bookingStatusPossible = $reiseNode->attr('buchungstatusmoeglich') ?? '';
|
||||
if ('' !== trim($bookingStatusPossible)) {
|
||||
$allowedBookingStatus = str_split($bookingStatusPossible);
|
||||
}
|
||||
}
|
||||
|
||||
return new ExtendedServiceAvailabilityResponse($availabilities, $allowedBookingStatus, $travelStatus);
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,9 @@ class TravelParser extends AbstractParser
|
||||
$travel->dateFrom = $dateFrom;
|
||||
$travel->dateTo = $dateTo;
|
||||
$travel->code = $this->getAttrOrNullValue($node, 'code');
|
||||
$travel->productId = null !== $this->getAttrOrNullValue($node, 'idprodukt')
|
||||
? (int) $this->getAttrOrNullValue($node, 'idprodukt')
|
||||
: null;
|
||||
|
||||
// Parse product code from parent reise node using DOM
|
||||
$domNode = $node->getNode(0);
|
||||
|
||||
Reference in new issue
Block a user