wip: booking process
This commit is contained in:
1 parent
bef18971c3
commit
47faa6b08e
26 files changed
+1123
-271
No files matched your search
@@ -2,18 +2,11 @@
|
||||
|
||||
namespace App\BusProNet\XmlLoader;
|
||||
|
||||
use App\BusProNet\Constants;
|
||||
use App\BusProNet\Model\BaseData;
|
||||
use App\BusProNet\Model\CrmSelection;
|
||||
use App\BusProNet\Model\CrmSelectionGroup;
|
||||
use App\BusProNet\Model\Guide;
|
||||
use App\BusProNet\Model\MutableData;
|
||||
use App\BusProNet\Model\Pickup;
|
||||
use App\BusProNet\Model\Room;
|
||||
use App\BusProNet\Model\Service;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\BusProNet\Utility\DayTimeUtility;
|
||||
use App\BusProNet\Utility\TravelCodeUtility;
|
||||
use App\BusProNet\XmlParser\TravelParser;
|
||||
use League\Flysystem\FilesystemOperator;
|
||||
use League\Flysystem\StorageAttributes;
|
||||
use Psr\Cache\InvalidArgumentException;
|
||||
@@ -21,17 +14,41 @@ use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
/**
|
||||
* XML loader for travel data from BusProNet XML exports.
|
||||
*
|
||||
* This loader processes XML files containing travel information including dates,
|
||||
* hotels, services, rooms, and pricing. It provides caching and mapping functionality
|
||||
* to efficiently load and parse travel data from XML exports.
|
||||
*/
|
||||
class TravelLoader extends AbstractLoader
|
||||
{
|
||||
/**
|
||||
* @param HotelLoader $hotelDataLoader Hotel data loader for hotel information
|
||||
* @param TravelParser $travelParser Parser for XML travel data
|
||||
* @param string $travelInfoBaseUrl Base URL for travel information pages
|
||||
* @param CacheInterface $cache Cache interface for performance optimization
|
||||
* @param FilesystemOperator $xmlExport Filesystem operator for XML file access
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly HotelLoader $hotelDataLoader,
|
||||
CacheInterface $cache,
|
||||
private readonly TravelParser $travelParser,
|
||||
private readonly string $travelInfoBaseUrl,
|
||||
CacheInterface $cache,
|
||||
FilesystemOperator $xmlExport,
|
||||
) {
|
||||
parent::__construct($cache, $xmlExport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cached mapping of travel IDs to their file locations and metadata.
|
||||
*
|
||||
* Scans all XML files starting with 'Ziel_' and extracts travel information
|
||||
* including ID, code, label, dates, and associated hotels. The mapping is
|
||||
* cached for 3 hours to improve performance.
|
||||
*
|
||||
* @return array<int, array> Mapping of travel IDs to their metadata and file paths
|
||||
*/
|
||||
public function generateFilesMap(): array
|
||||
{
|
||||
try {
|
||||
@@ -85,6 +102,15 @@ class TravelLoader extends AbstractLoader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a travel code to its corresponding travel ID.
|
||||
*
|
||||
* Uses the cached files mapping to find the travel ID associated with
|
||||
* the given travel code. Returns null if no matching travel is found.
|
||||
*
|
||||
* @param string $travelCode The travel code to look up
|
||||
* @return int|null The travel ID or null if not found
|
||||
*/
|
||||
public function mapCodeToId(string $travelCode): ?int
|
||||
{
|
||||
$mapping = $this->generateFilesMap();
|
||||
@@ -98,6 +124,17 @@ class TravelLoader extends AbstractLoader
|
||||
return $travelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a travel object by its ID and optional hotel ID.
|
||||
*
|
||||
* Retrieves travel data from XML exports. If filename is provided, loads directly
|
||||
* from that file. Otherwise, uses the cached mapping to find the appropriate file.
|
||||
*
|
||||
* @param int $travelId The travel ID to load
|
||||
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
||||
* @param string|null $filename Optional filename to load from directly
|
||||
* @return Travel|null The loaded travel object or null if not found
|
||||
*/
|
||||
public function loadById(int $travelId, ?int $hotelId = null, ?string $filename = null): ?Travel
|
||||
{
|
||||
if (null !== $filename) {
|
||||
@@ -115,217 +152,41 @@ class TravelLoader extends AbstractLoader
|
||||
return $this->loadXml($travelId, $hotelId, $filename);
|
||||
}
|
||||
|
||||
private function loadXml(int $id, ?int $hotelId, string $filename): ?Travel
|
||||
/**
|
||||
* Load travel data from a specific XML file.
|
||||
*
|
||||
* Reads and parses XML file content to extract travel information for
|
||||
* the specified travel ID and optional hotel ID.
|
||||
*
|
||||
* @param int $travelId The travel ID to load
|
||||
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
||||
* @param string $filename The XML filename to load from
|
||||
* @return Travel|null The loaded travel object or null if not found
|
||||
*/
|
||||
private function loadXml(int $travelId, ?int $hotelId, string $filename): ?Travel
|
||||
{
|
||||
$xml = $this->xmlExport->read($filename);
|
||||
$crawler = new Crawler($xml);
|
||||
|
||||
$travelNode = $crawler->filterXPath(sprintf('//reise/termin[@idbuspro="%d"]', $id));
|
||||
$travelNode = $crawler->filterXPath(sprintf('//reise/termin[@idbuspro="%d"]', $travelId));
|
||||
|
||||
if (0 === $travelNode->count()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->parseXml($travelNode->first(), $hotelId);
|
||||
}
|
||||
|
||||
public function parseXml(Crawler $node, ?int $hotelId): Travel
|
||||
{
|
||||
$hotelNode = $this->getHotelNode($node, $hotelId);
|
||||
|
||||
$dateFrom = $this->stringToDate($node->attr('termin'));
|
||||
|
||||
$travel = new Travel();
|
||||
$travel->id = (int) $node->attr('idbuspro');
|
||||
$travel->hotelId = (int) $hotelNode->attr('idbuspro');
|
||||
$travel->label = $this->getStringOrNullValue($node->filterXPath('//text'));
|
||||
$travel->dateFrom = $dateFrom;
|
||||
$travel->dateTo = $this->stringToDate($node->attr('bis'));
|
||||
$travel->code = $node->attr('code');
|
||||
$travel->type = $node->attr('reiseart');
|
||||
$travel->priceFrom = $this->stringToFloat($this->getStringOrNullValue($node->filterXPath('//abpreis')));
|
||||
$travel->selectionGroups = $this->getSelectionGroups($node->filterXPath('//selektiongruppe'));
|
||||
$travel->additionalServices = $this
|
||||
->getAdditionalServices($node->filterXPath('//lei_sonstiges/leistung'));
|
||||
$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->guide = $this->getGuide($node);
|
||||
|
||||
return $travel;
|
||||
}
|
||||
|
||||
public function getSelectionGroups(Crawler $node): array
|
||||
{
|
||||
$selectionGroups = [];
|
||||
|
||||
$node->each(function (Crawler $groupNode) use (&$selectionGroups) {
|
||||
$groupId = (int) $groupNode->attr('idbuspro');
|
||||
$selectionGroup = new CrmSelectionGroup();
|
||||
$selectionGroup->id = $groupId;
|
||||
$selectionGroup->label = $groupNode->attr('bezeichnung');
|
||||
|
||||
$groupNode
|
||||
->filterXPath('//selektion')
|
||||
->each(function (Crawler $selectionNode) use (&$selectionGroups, &$selectionGroup, $groupId) {
|
||||
$selectionId = (int) $selectionNode->attr('idbuspro');
|
||||
$selection = new CrmSelection();
|
||||
$selection->id = $selectionId;
|
||||
$selection->label = $selectionNode->attr('bezeichnung');
|
||||
$selectionGroups[$groupId]['selections'][$selectionId] = $selectionNode->attr('bezeichnung');
|
||||
$selectionGroup->selections[] = $selection;
|
||||
})
|
||||
;
|
||||
|
||||
$selectionGroups[$groupId] = $selectionGroup;
|
||||
});
|
||||
|
||||
return $selectionGroups;
|
||||
}
|
||||
|
||||
public function getAdditionalServices(Crawler $node): array
|
||||
{
|
||||
$additionalServices = [];
|
||||
|
||||
$node->each(function (Crawler $serviceNode) use (&$additionalServices) {
|
||||
$serviceId = (int) $serviceNode->attr('idbuspro');
|
||||
|
||||
$service = new Service();
|
||||
$service->source = Constants::SOURCE_TRAVEL;
|
||||
$service->category = Constants::CATEGORY_ADDITIONAL;
|
||||
$service->id = $serviceId;
|
||||
$service->subType = $serviceNode->attr('unterart');
|
||||
$service->mandatory = $this->stringToBool($serviceNode->attr('pflicht'));
|
||||
$service->dateFrom = $this->stringToDate($serviceNode->attr('termin'));
|
||||
$service->dateTo = $this->stringToDate($serviceNode->attr('bis'));
|
||||
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('//text'));
|
||||
$service->price = $this
|
||||
->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('//preis')));
|
||||
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('//status'));
|
||||
|
||||
$additionalServices[$serviceId] = $service;
|
||||
});
|
||||
|
||||
return $additionalServices;
|
||||
}
|
||||
|
||||
public function getGuide(Crawler $travelNode): ?Guide
|
||||
{
|
||||
$guideNodes = $travelNode
|
||||
->filterXPath('//lei_befoerderung/leistung[@unterart="BUS"]/zustiegsplanung/reiseleiter');
|
||||
|
||||
if (0 < $guideNodes->count()) {
|
||||
$guideNode = $guideNodes->first();
|
||||
|
||||
$guide = new Guide();
|
||||
$guide->name = $this->getStringOrNullValue($guideNode->filterXPath('//name'));
|
||||
$guide->phone = $this->getStringOrNullValue($guideNode->filterXPath('//telefon'));
|
||||
|
||||
return $guide;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getTransportationServices(Crawler $node): array
|
||||
{
|
||||
$transportationServices = [];
|
||||
|
||||
$node->each(function (Crawler $serviceNode) use (&$transportationServices) {
|
||||
$serviceId = (int) $serviceNode->attr('idbuspro');
|
||||
|
||||
$service = new Service();
|
||||
$service->source = Constants::SOURCE_TRAVEL;
|
||||
$service->category = Constants::CATEGORY_TRANSPORTATION;
|
||||
$service->id = $serviceId;
|
||||
$service->subType = $serviceNode->attr('unterart');
|
||||
$service->dateFrom = $this->stringToDate($serviceNode->attr('termin'));
|
||||
$service->dateTo = $this->stringToDate($serviceNode->attr('bis'));
|
||||
$service->label = $this->getStringOrNullValue($serviceNode->filterXPath('//text'));
|
||||
$service->direction = $this->getStringOrNullValue($serviceNode->filterXPath('//richtung'));
|
||||
$service->status = $this->getStringOrNullValue($serviceNode->filterXPath('//status'));
|
||||
|
||||
if (null !== $timeFrom = $serviceNode->attr('uhrzeit_von')) {
|
||||
$service->timeFrom = $timeFrom;
|
||||
$service->dayTime = (new DayTimeUtility())->mapTime($timeFrom);
|
||||
}
|
||||
|
||||
$transportationServices[$serviceId] = $service;
|
||||
});
|
||||
|
||||
return $transportationServices;
|
||||
}
|
||||
|
||||
public function getPickups(Crawler $node, ?\DateTimeImmutable $defaultDate = null): array
|
||||
{
|
||||
$pickups = [];
|
||||
|
||||
$node->each(function (Crawler $pickupNode) use (&$pickups, $defaultDate) {
|
||||
$pickupId = (int) $pickupNode->attr('idbuspro');
|
||||
|
||||
$pickup = new Pickup();
|
||||
$pickup->id = $pickupId;
|
||||
$pickup->price = $pickupNode->attr('preis') ?
|
||||
$this->stringToFloat($pickupNode->attr('preis')) : null;
|
||||
|
||||
// parse date and time only for direction 'to' indicated by provided default date
|
||||
if (null !== $defaultDate) {
|
||||
$pickup->time = $this->stringToDateTimeFuzzy($pickupNode->attr('zeit'), $defaultDate);
|
||||
}
|
||||
|
||||
$pickups[$pickupId] = $pickup;
|
||||
});
|
||||
|
||||
return $pickups;
|
||||
}
|
||||
|
||||
public function getHotelNode(Crawler $node, ?int $hotelId): Crawler
|
||||
{
|
||||
// In case not hotel id is provided, take the first hotel node (which is most probably the only one)
|
||||
if (null === $hotelId) {
|
||||
return $node->filterXPath('//hotel')->first();
|
||||
}
|
||||
|
||||
$hotelNode = $node->filterXPath(sprintf('//hotel[@idbuspro="%d"]', $hotelId));
|
||||
|
||||
return $hotelNode->first();
|
||||
}
|
||||
|
||||
public function getRooms(Crawler $node): array
|
||||
{
|
||||
$roomNodes = $node->filterXPath('//zimmer/preis');
|
||||
|
||||
if (0 === $roomNodes->count()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rooms = [];
|
||||
|
||||
$roomNodes->each(function (Crawler $roomNode) use (&$rooms) {
|
||||
$roomId = (int) $roomNode->attr('idbuspro_zimmer');
|
||||
|
||||
$room = new Room();
|
||||
$room->id = $roomId;
|
||||
$room->code = $roomNode->attr('zimmercode');
|
||||
$room->category = $roomNode->attr('kat');
|
||||
$room->boardId = (int) $roomNode->attr('idbuspro_vp');
|
||||
$room->label = $roomNode->attr('zimmertext');
|
||||
$room->minPax = (int) $roomNode->attr('minpax');
|
||||
$room->maxPax = (int) $roomNode->attr('maxpax');
|
||||
$room->nights = (int) $roomNode->attr('naechte');
|
||||
$room->price = $roomNode->attr('preis') ?
|
||||
$this->stringToFloat($roomNode->attr('preis')) : null;
|
||||
$room->status = $roomNode->attr('status');
|
||||
$room->available = (int) $roomNode->attr('verfuegbar');
|
||||
|
||||
$rooms[$roomId] = $room;
|
||||
});
|
||||
|
||||
return $rooms;
|
||||
return $this->travelParser->parse($travelNode->first(), $hotelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mutability settings to travel object.
|
||||
*
|
||||
* Updates the travel object's mutability flags based on the provided
|
||||
* mutable data configuration. This controls which travel components
|
||||
* can be modified during booking.
|
||||
*
|
||||
* @param Travel $travel The travel object to update
|
||||
* @param BaseData $mutableData The mutability configuration data
|
||||
*/
|
||||
public function patchMutability(Travel $travel, BaseData $mutableData): void
|
||||
{
|
||||
if (null !== $item = $mutableData->getItemByKey(MutableData::CATEGORY_ADDITIONAL_SERVICES)) {
|
||||
@@ -353,6 +214,15 @@ 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.
|
||||
*
|
||||
* @param Travel $travel The travel object to update
|
||||
* @param BaseData $availabilities The availability data for services
|
||||
*/
|
||||
public function patchAvailabilities(Travel $travel, BaseData $availabilities): void
|
||||
{
|
||||
$serviceAvailabilities = $availabilities->getItems();
|
||||
@@ -364,6 +234,14 @@ class TravelLoader extends AbstractLoader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance booking data with travel information and URLs.
|
||||
*
|
||||
* Loads travel data for each booking and generates travel info URLs
|
||||
* for bookings that are within 5 days of their travel date.
|
||||
*
|
||||
* @param BaseData $bookings The booking data to enhance
|
||||
*/
|
||||
public function patchBookings(BaseData $bookings): void
|
||||
{
|
||||
$travelCodeUtility = new TravelCodeUtility();
|
||||
|
||||
Reference in new issue
Block a user