Files
myep/src/BusProNet/XmlParser/BookingParser.php
T

227 lines
10 KiB
PHP

<?php
namespace App\BusProNet\XmlParser;
use App\BusProNet\Constants;
use App\BusProNet\Model\Address;
use App\BusProNet\Model\BankAccount;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\PersonalData;
use Symfony\Component\DomCrawler\Crawler;
class BookingParser extends AbstractParser
{
private readonly ServicesParser $servicesParser;
private readonly RoomsParser $roomsParser;
private readonly PickupsParser $pickupsParser;
private readonly SurchargesParser $surchargesParser;
private readonly BookingInsurancesParser $insurancesParser;
public function __construct()
{
$this->servicesParser = new ServicesParser();
$this->roomsParser = new RoomsParser();
$this->pickupsParser = new PickupsParser();
$this->surchargesParser = new SurchargesParser();
$this->insurancesParser = new BookingInsurancesParser();
}
public function parse(Crawler $node): Booking
{
$booking = new Booking();
$booking->id = $this->getIntOrNullValue($node->filterXPath('//idbuchung'));
$booking->agencyId = $this->getIntOrNullValue($node->filterXPath('//idagentur'));
$booking->bookingNumber = $this->getIntOrNullValue($node->filterXPath('//vorgang'));
$booking->invoiceNumber = $this->getIntOrNullValue($node->filterXPath('//zahlungsdaten/rechnung'));
$booking->totalPrice = $this->getFloatOrNullValue($node->filterXPath('//zahlungsdaten/gesamtbetrag'));
$booking->status = $this->getStringOrNullValue($node->filterXPath('//status'));
$travelData = $node->filterXPath('//reise');
$booking->travelName = $this->getAttrOrNullValue($travelData, 'bezeichnung');
$booking->dateId = $this->getIntOrNullValue($node->filterXPath('//idreise'));
$booking->travelCode = $this->getAttrOrNullValue($travelData, 'code');
$booking->travelDate = $this->stringToDate($this->getAttrOrNullValue($travelData, 'termin'));
$booking->hotelId = $this->getIntOrNullValue($node->filterXPath('//idpartner'));
$booking->hotelName = $this->getStringOrNullValue($node->filterXPath('//partner'));
$booking->applicant = $this->parsePersonalData($node->filterXPath('//anmelder'));
$booking->participants = $this->parseParticipants($node->filterXPath('//teilnehmerliste/teilnehmer'));
$booking->participantsStatus = $this->mapParticipantsStatus(
array_keys($booking->participants),
$this->getArrayValue($node->filterXPath('//status_teilnehmer'), '/'),
);
$paymentData = $node->filterXPath('//zahlung');
$booking->paymentId = $this->getAttrOrNullValue($paymentData, 'idzahlungsart');
$booking->paymentLabel = $this->getAttrOrNullValue($paymentData, 'bezeichnung');
$booking->paymentType = $this->getAttrOrNullValue($paymentData, 'art');
if (0 < $paymentData->children()->count()) {
$bankDataNode = $paymentData->children()->first();
$bankAccount = new BankAccount();
$bankAccount->iban = $this->getAttrOrNullValue($bankDataNode, 'iban');
$bankAccount->bic = $this->getAttrOrNullValue($bankDataNode, 'bic');
$bankAccount->bankName = $this->getAttrOrNullValue($bankDataNode, 'kreditinstitut');
$bankAccount->holder = $this->getAttrOrNullValue($bankDataNode, 'kontoinhaber');
$booking->bankAccount = $bankAccount;
}
$transportationData = $node->filterXPath('//beförderungen/beförderung');
if (0 < $transportationData->count()) {
$booking->transportationServices = $this
->servicesParser
->parse($transportationData, Constants::CATEGORY_TRANSPORTATION, Constants::SOURCE_BOOKING);
}
$additionalServicesData = $node->filterXPath('//zusatzleistungen/zusatzleistung');
if (0 < $additionalServicesData->count()) {
$booking->additionalServices = $this
->servicesParser
->parse($additionalServicesData, Constants::CATEGORY_ADDITIONAL, Constants::SOURCE_BOOKING);
}
$roomsData = $node->filterXPath('//ferienzielunterbringungen/ferienzielunterbringung');
if (0 < $roomsData->count()) {
$booking->rooms = $this->roomsParser->parse($roomsData);
}
$pickupsData = $node->filterXPath('//zustiege/zustieg');
if (0 < $pickupsData->count()) {
$booking->pickups = $this->pickupsParser->parse($pickupsData);
}
$dropOffsData = $node->filterXPath('//ausstiege/ausstieg');
if (0 < $dropOffsData->count()) {
$booking->dropOffs = $this->pickupsParser->parse($dropOffsData);
}
$surchargesData = $node->filterXPath('//zuschlaege/zuschlag');
if (0 < $surchargesData->count()) {
$booking->surcharges = $this->surchargesParser->parse($surchargesData);
}
$insurancesData = $node->filterXPath('//versicherungen/versicherung');
if (0 < $insurancesData->count()) {
$booking->insurances = $this->insurancesParser->parse($insurancesData);
}
return $booking;
}
/** @return array<int, PersonalData> */
private function parseParticipants(Crawler $node): array
{
$participants = [];
$node->each(function (Crawler $node) use (&$participants) {
$id = (int) ($this->getAttrOrNullValue($node, 'id') ?? 0);
if ($id <= 0) {
return;
}
$participants[$id - 1] = $this->parsePersonalData($node);
});
return $participants;
}
/**
* Maps the positional status_teilnehmer list onto the participant keys.
*
* BusPro sends the statuses as a slash-separated list in teilnehmerliste order and
* without ids, while participants are keyed by "BusPro participant id - 1" (see
* parseParticipants()). Zipping the two in document order keeps both arrays on the same
* keys even when the ids are not contiguous from 1, which BookingPayloadBuilder relies
* on when it reads participantsStatus[$index] while iterating participants, and which
* gates editing in ParticipantController::isParticipantCanceled().
*
* Surplus entries on either side are dropped rather than shifting the remaining ones
* onto the wrong participant.
*
* @param list<int> $participantKeys Participant array keys, in document order
* @param list<string> $statusValues Status codes, in document order
*
* @return array<int, string>
*/
private function mapParticipantsStatus(array $participantKeys, array $statusValues): array
{
$count = min(count($participantKeys), count($statusValues));
if (0 === $count) {
return [];
}
return array_combine(
array_slice($participantKeys, 0, $count),
array_slice($statusValues, 0, $count),
);
}
private function parsePersonalData(Crawler $node): PersonalData
{
$personalData = new PersonalData();
$personalData->status = $this->getStringOrNullValue($node->filterXPath('//status'));
$personalData->addressId = $this->getIntOrNullValue($node->filterXPath('//idadresse'));
$personalData->personId = $this->getIntOrNullValue($node->filterXPath('//idadresseperson'));
$personalData->mutable = $this->getBoolValue($node->filterXPath('//aenderungmoeglich'));
$dateString = $this->getStringOrNullValue($node->filterXPath('//geburtsdatum'));
$personalData->dateOfBirth = $this->stringToDate($dateString);
$personalData->firstName = $this->getStringOrNullValue($node->filterXPath('//vorname'));
$personalData->name = $this->getStringOrNullValue($node->filterXPath('//name'));
$personalData->salutation = $this->getStringOrNullValue($node->filterXPath('//anrede'));
$personalData->title = $this->getStringOrNullValue($node->filterXPath('//titel'));
$genderValue = $this->getStringOrNullValue($node->filterXPath('//geschlecht'));
$personalData->gender = null !== $genderValue ? strtoupper($genderValue) : null;
$personalData->nationality = $this->getStringOrNullValue($node->filterXPath('//nationalitaet'));
$personalData->height = $this->getStringOrNullValue($node->filterXPath('//sonstiges1'));
$personalData->weight = $this->getStringOrNullValue($node->filterXPath('//sonstiges2'));
$personalData->shoeSize = $this->getStringOrNullValue($node->filterXPath('//sonstiges3'));
$addressNode = $node->filterXPath('//anschrift');
if (0 < $addressNode->count()) {
$address = new Address();
$address->street = $this->getStringOrNullValue($addressNode->filterXPath('//strasse'));
$address->postCode = $this->getStringOrNullValue($addressNode->filterXPath('//plz'));
$address->city = $this->getStringOrNullValue($addressNode->filterXPath('//ort'));
$address->country = $this->getStringOrNullValue($addressNode->filterXPath('//land'));
$personalData->address = $address;
}
$contactNode = $node->filterXPath('//kommunikation');
if (0 < $contactNode->count()) {
$personalData->communication->phone = $this->getStringOrNullValue($contactNode->filterXPath('//telefonprivat'));
$personalData->communication->mobile = $this->getStringOrNullValue($contactNode->filterXPath('//telefonmobil'));
$personalData->communication->email = $this->getStringOrNullValue($contactNode->filterXPath('//email'));
$personalData->communication->newsletter = $this->getBoolValue($contactNode->filterXPath('//newsletter'));
}
// assign default dummy address to ensure valid communication data
if (empty($personalData->communication->email)) {
$personalData->communication->email = '[email protected]';
}
// Parse wishes (room remarks and license plate)
$wishesNode = $node->filterXPath('//wünsche');
if (0 < $wishesNode->count()) {
$personalData->remarksRoom = $this->getStringOrNullValue($wishesNode->filterXPath('//unterbringungswunsch'));
$personalData->licensePlate = $this->getStringOrNullValue($wishesNode->filterXPath('//beförderungswunsch'));
}
return $personalData;
}
}