chore: remote obsolete contingents-related plumbing
This commit is contained in:
1 parent
8c99fba6fa
commit
b2fccae431
19 files changed
+1
-1711
No files matched your search
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Model;
|
||||
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
|
||||
class ContingentCalendarEvent
|
||||
{
|
||||
public function __construct(
|
||||
#[Groups(['api:contingent'])]
|
||||
public string $date,
|
||||
#[Groups(['api:contingent'])]
|
||||
public string $status,
|
||||
#[Groups(['api:contingent'])]
|
||||
public int $pax,
|
||||
#[Groups(['api:contingent'])]
|
||||
public int $available,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Model;
|
||||
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
|
||||
class ContingentDateSummary
|
||||
{
|
||||
public function __construct(
|
||||
#[Groups(['api:contingent'])]
|
||||
public string $date,
|
||||
#[Groups(['api:contingent'])]
|
||||
public ?int $minNights,
|
||||
#[Groups(['api:contingent'])]
|
||||
public int $total,
|
||||
#[Groups(['api:contingent'])]
|
||||
public int $capacity,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Model;
|
||||
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
|
||||
class ContingentRoomAvailability
|
||||
{
|
||||
public function __construct(
|
||||
#[Groups(['api:contingent'])]
|
||||
public string $date,
|
||||
#[Groups(['api:contingent'])]
|
||||
public string $roomCode,
|
||||
#[Groups(['api:contingent'])]
|
||||
public string $roomLabel,
|
||||
#[Groups(['api:contingent'])]
|
||||
public string $bookingUrl,
|
||||
#[Groups(['api:contingent'])]
|
||||
public int $pax,
|
||||
#[Groups(['api:contingent'])]
|
||||
public int $available,
|
||||
#[Groups(['api:contingent'])]
|
||||
public string $status,
|
||||
#[Groups(['api:contingent'])]
|
||||
public ?float $minPrice,
|
||||
#[Groups(['api:contingent'])]
|
||||
public ?int $minNights,
|
||||
#[Groups(['api:contingent'])]
|
||||
public ?float $additionalNightMinPrice,
|
||||
#[Groups(['api:contingent'])]
|
||||
public ?int $additionalNightMinNights,
|
||||
#[Groups(['api:contingent'])]
|
||||
public float $priceForSelection,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Utility;
|
||||
|
||||
class BookingUrlUtility
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $defaultBaseUrl,
|
||||
) {
|
||||
}
|
||||
|
||||
public function build(int $hotelId, int $dateId, ?string $myEpUrl = null): string
|
||||
{
|
||||
$query = http_build_query([
|
||||
'date_id' => $dateId,
|
||||
'hotel_id' => $hotelId,
|
||||
], '', '&', PHP_QUERY_RFC3986);
|
||||
|
||||
$baseUrlInput = null !== $myEpUrl && '' !== trim($myEpUrl)
|
||||
? $myEpUrl
|
||||
: $this->defaultBaseUrl;
|
||||
$baseUrl = $this->normalizeBaseUrl($baseUrlInput);
|
||||
if ('' === $baseUrl) {
|
||||
return sprintf('/bookings/create?%s', $query);
|
||||
}
|
||||
|
||||
return sprintf('%s/bookings/create?%s', $baseUrl, $query);
|
||||
}
|
||||
|
||||
private function normalizeBaseUrl(string $url): string
|
||||
{
|
||||
$trimmedUrl = trim($url);
|
||||
|
||||
return rtrim($trimmedUrl, '/');
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\BusProNet\XmlLoader;
|
||||
|
||||
use App\BusProNet\XmlParser\ContingentParser;
|
||||
use League\Flysystem\FilesystemException;
|
||||
use League\Flysystem\FilesystemOperator;
|
||||
use League\Flysystem\StorageAttributes;
|
||||
use Psr\Cache\InvalidArgumentException;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
class ContingentLoader extends AbstractLoader
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ContingentParser $parser,
|
||||
TagAwareCacheInterface $cache,
|
||||
FilesystemOperator $xmlExportContingents,
|
||||
) {
|
||||
parent::__construct($cache, $xmlExportContingents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function generateFilesMap(): array
|
||||
{
|
||||
try {
|
||||
return $this->cache->get('bpn_contingent_files', function (ItemInterface $item) {
|
||||
$item->expiresAfter(3 * 60 * 60);
|
||||
$item->tag(['xml-sync']);
|
||||
|
||||
$xmlFiles = $this->xmlExport
|
||||
->listContents('.')
|
||||
->filter(fn (StorageAttributes $attributes) => $attributes->isFile() && str_starts_with($attributes->path(), 'HotelZimmer_'));
|
||||
|
||||
$mapping = [];
|
||||
|
||||
foreach ($xmlFiles as $file) {
|
||||
if (1 !== preg_match('/HotelZimmer_(\d+)\.xml$/', $file->path(), $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapping[(int) $matches[1]] = $file->path();
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
});
|
||||
} catch (InvalidArgumentException) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{roomTypes: array<int, array<string, mixed>>, rows: array<int, array<string, mixed>>}
|
||||
*/
|
||||
public function loadByHotelId(int $hotelId): array
|
||||
{
|
||||
$cacheKey = sprintf('contingent_hotel_%d', $hotelId);
|
||||
|
||||
try {
|
||||
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($hotelId) {
|
||||
$item->expiresAfter(3600);
|
||||
$item->tag(['xml-sync']);
|
||||
|
||||
return $this->loadByHotelIdUncached($hotelId);
|
||||
});
|
||||
} catch (InvalidArgumentException) {
|
||||
return $this->loadByHotelIdUncached($hotelId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{roomTypes: array<int, array<string, mixed>>, rows: array<int, array<string, mixed>>}
|
||||
*/
|
||||
private function loadByHotelIdUncached(int $hotelId): array
|
||||
{
|
||||
$mapping = $this->generateFilesMap();
|
||||
|
||||
if (false === isset($mapping[$hotelId])) {
|
||||
return ['roomTypes' => [], 'rows' => []];
|
||||
}
|
||||
|
||||
$filename = $mapping[$hotelId];
|
||||
|
||||
try {
|
||||
$xml = $this->xmlExport->read($filename);
|
||||
} catch (FilesystemException) {
|
||||
return ['roomTypes' => [], 'rows' => []];
|
||||
}
|
||||
|
||||
$linkedHotelId = $this->getRootLinkedHotelId($xml);
|
||||
if (null !== $linkedHotelId) {
|
||||
if (false === isset($mapping[$linkedHotelId])) {
|
||||
return ['roomTypes' => [], 'rows' => []];
|
||||
}
|
||||
|
||||
try {
|
||||
$xml = $this->xmlExport->read($mapping[$linkedHotelId]);
|
||||
} catch (FilesystemException) {
|
||||
return ['roomTypes' => [], 'rows' => []];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->parser->parse($xml);
|
||||
}
|
||||
|
||||
private function getRootLinkedHotelId(string $xml): ?int
|
||||
{
|
||||
$document = new \DOMDocument();
|
||||
if (false === @$document->loadXML($xml)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$root = $document->documentElement;
|
||||
if (null === $root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$link = $root->getAttribute('idbuspro_kontingent_aus');
|
||||
if ('' === $link) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $link;
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
class ContingentParser extends AbstractParser
|
||||
{
|
||||
private const CONTROL_ROOM_CODE = 'BelKal';
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
private array $ignoredRoomCodes = [
|
||||
'PDGS',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array{roomTypes: array<int, array<string, mixed>>, rows: array<int, array<string, mixed>>}
|
||||
*/
|
||||
public function parse(string $xml): array
|
||||
{
|
||||
$crawler = new Crawler($xml);
|
||||
$roomTypes = $this->parseRoomTypes($crawler);
|
||||
$rows = $this->parseRows($crawler, $roomTypes);
|
||||
|
||||
return [
|
||||
'roomTypes' => $roomTypes,
|
||||
'rows' => $rows,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function parseRoomTypes(Crawler $crawler): array
|
||||
{
|
||||
$roomTypes = [];
|
||||
|
||||
$crawler
|
||||
->filterXPath('//unterbringungen/unterbringung')
|
||||
->each(function (Crawler $node) use (&$roomTypes) {
|
||||
$roomId = $this->getIntOrNullAttribute($node->attr('idbuspro'));
|
||||
$code = $node->attr('code');
|
||||
|
||||
if (null === $roomId || null === $code) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (true === in_array($code, $this->ignoredRoomCodes, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$label = $node->attr('zimmerbezeichnung')
|
||||
?? $this->getStringOrNullValue($node->filterXPath('.//text'))
|
||||
?? $code;
|
||||
|
||||
$pax = $this->getIntOrNullAttribute($node->attr('pax_max')) ?? 0;
|
||||
$link = $this->getIntOrNullAttribute($node->attr('idbuspro_kontingent_aus'));
|
||||
|
||||
$roomTypes[$roomId] = [
|
||||
'idBusPro' => $roomId,
|
||||
'code' => $code,
|
||||
'pax' => $pax,
|
||||
'label' => $label,
|
||||
'isControlRoom' => self::CONTROL_ROOM_CODE === $code,
|
||||
'link' => $link,
|
||||
];
|
||||
});
|
||||
|
||||
return $roomTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $roomTypes
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function parseRows(Crawler $crawler, array $roomTypes): array
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
$crawler
|
||||
->filterXPath('//kapazitaeten/kapazitaet')
|
||||
->each(function (Crawler $dateNode) use (&$rows, $roomTypes) {
|
||||
$date = $this->stringToDate($dateNode->attr('termin'));
|
||||
|
||||
if (null === $date) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($roomTypes as $roomType) {
|
||||
$roomId = $roomType['link'] ?? $roomType['idBusPro'];
|
||||
|
||||
$roomNode = $this->findRoomNode($dateNode, $roomId);
|
||||
if (null === $roomNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$capacity = $this->getIntOrNullAttribute($roomNode->attr('kontingent')) ?? 0;
|
||||
$free = $this->getIntOrNullAttribute($roomNode->attr('frei')) ?? 0;
|
||||
$statusRaw = $roomNode->attr('status') ?? '';
|
||||
|
||||
$rows[] = [
|
||||
'date' => $date,
|
||||
'roomCode' => $roomType['code'],
|
||||
'roomLabel' => $roomType['label'],
|
||||
'pax' => $roomType['isControlRoom'] ? 0 : $capacity * (int) ($roomType['pax'] ?? 0),
|
||||
'available' => $roomType['isControlRoom'] ? 0 : $free * (int) ($roomType['pax'] ?? 0),
|
||||
'status' => $this->mapStatus($statusRaw),
|
||||
'minPrice' => $this->stringToFloat($roomNode->attr('abpreis')),
|
||||
'minNights' => $this->getIntOrNullAttribute($roomNode->attr('abpreis_naechte')),
|
||||
'additionalNightMinPrice' => $this->stringToFloat($roomNode->attr('abpreis_verlaengerung')),
|
||||
'additionalNightMinNights' => $this->getIntOrNullAttribute($roomNode->attr('abpreis_verlaengerung_naechte')),
|
||||
'isControlRoom' => $roomType['isControlRoom'],
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function findRoomNode(Crawler $dateNode, int $roomId): ?Crawler
|
||||
{
|
||||
$roomNode = $dateNode->filterXPath(sprintf('.//zimmerliste/zimmer[@idbuspro="%d"]', $roomId));
|
||||
if (0 < $roomNode->count()) {
|
||||
return $roomNode->first();
|
||||
}
|
||||
|
||||
$roomNode = $dateNode->filterXPath(sprintf('.//zimmerliste/zimmer[@id="%d"]', $roomId));
|
||||
if (0 < $roomNode->count()) {
|
||||
return $roomNode->first();
|
||||
}
|
||||
|
||||
$roomNode = $dateNode->filterXPath(sprintf('.//zimmerliste/zimmer[@id_zimmer="%d"]', $roomId));
|
||||
if (0 < $roomNode->count()) {
|
||||
return $roomNode->first();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function mapStatus(?string $status): string
|
||||
{
|
||||
return match (strtoupper((string) $status)) {
|
||||
'A' => 'ON_REQUEST',
|
||||
'S' => 'BLOCKED',
|
||||
default => 'OK',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user