wip: first working version

This commit is contained in:
Björn Fromme
2024-11-21 18:38:41 +01:00
parent d4c4e69d09
commit bd13ae6537
24 changed files with 590 additions and 319 deletions
+83 -96
View File
@@ -6,6 +6,7 @@ use App\BusProNet\ApiResponseParser\ResponseParser;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\BookingUpdate;
use App\BusProNet\Model\CrmAttributes;
use App\BusProNet\Model\File;
use App\BusProNet\Model\Notification;
@@ -13,6 +14,7 @@ use App\BusProNet\Model\PersonalData;
use App\Form\Model\BookingData;
use Psr\Log\LoggerInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Uid\Uuid;
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -44,14 +46,12 @@ class ApiClient
public function getPersonalData(string $email, string $password): Notification|PersonalData
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Adressdaten',
'email' => $email,
'passwort' => $password,
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Adressdaten',
'email' => $email,
'passwort' => $password,
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
@@ -63,16 +63,14 @@ class ApiClient
public function updatePersonalData(string $email, string $password, PersonalData $personalData): Notification|PersonalData
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Adressdaten_Ändern',
'email' => $email,
'passwort' => $password,
'idadresse' => $personalData->addressId,
'adressdaten' => $personalData->toPayload(),
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Adressdaten_Ändern',
'email' => $email,
'passwort' => $password,
'idadresse' => $personalData->addressId,
'adressdaten' => $personalData->toPayload(),
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
@@ -84,14 +82,12 @@ class ApiClient
public function getBookings(string $email, string $password): Notification|BaseData
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Vorgänge',
'email' => $email,
'passwort' => $password,
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Vorgänge',
'email' => $email,
'passwort' => $password,
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
@@ -103,33 +99,29 @@ class ApiClient
public function getBooking(string $email, string $password, int $id): Notification|Booking
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Vorgang_Details',
'email' => $email,
'passwort' => $password,
'idbuchung' => $id,
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Vorgang_Details',
'email' => $email,
'passwort' => $password,
'idbuchung' => $id,
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
}
public function updateBooking(string $email, string $password, BookingData $formData): Notification
public function updateBooking(BookingData $formData, bool $dryRun = true): Notification|BookingUpdate
{
$booking = $formData->booking;
$mode = $dryRun ? 'Anfrage' : 'Buchung';
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_BOOKING_UPDATE),
'satz' => ['@typ' => static::TYPE_BOOKING_UPDATE],
'buchungsart' => 'Buchung',
'idbuchung' => $booking->id,
...$booking->toPayload($formData),
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_BOOKING_UPDATE),
'satz' => ['@typ' => static::TYPE_BOOKING_UPDATE],
'buchungsart' => $mode,
...$booking->toPayload($formData),
];
return $this->sendRequest(static::TYPE_BOOKING_UPDATE, $data);
@@ -141,13 +133,11 @@ class ApiClient
public function getMutableFields(int $id): Notification|BaseData
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_MUTABLE_FIELDS),
'satz' => ['@typ' => static::TYPE_MUTABLE_FIELDS],
'art' => 'Vorgang_Details',
'idreise' => $id,
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_MUTABLE_FIELDS),
'satz' => ['@typ' => static::TYPE_MUTABLE_FIELDS],
'art' => 'Vorgang_Details',
'idreise' => $id,
];
return $this->sendRequest(static::TYPE_MUTABLE_FIELDS, $data);
@@ -159,12 +149,10 @@ class ApiClient
public function getAvailabilities(int $id): Notification|BaseData
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_AVAILABILITY),
'satz' => ['@typ' => static::TYPE_AVAILABILITY],
'idreise' => $id,
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_AVAILABILITY),
'satz' => ['@typ' => static::TYPE_AVAILABILITY],
'idreise' => $id,
];
return $this->sendRequest(static::TYPE_AVAILABILITY, $data);
@@ -176,13 +164,11 @@ class ApiClient
public function resetPassword(string $email): Notification
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Passwort_Anfrage',
'email' => $email,
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Passwort_Anfrage',
'email' => $email,
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
@@ -194,14 +180,12 @@ class ApiClient
public function getCrmAttributes(string $email, string $password): Notification|CrmAttributes
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'SelektionCRM',
'email' => $email,
'passwort' => $password,
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'SelektionCRM',
'email' => $email,
'passwort' => $password,
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
@@ -213,11 +197,9 @@ class ApiClient
public function getBaseData(string $type): Notification|BaseData
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], $type),
'satz' => ['@typ' => $type],
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], $type),
'satz' => ['@typ' => $type],
];
return $this->sendRequest($type, $data);
@@ -230,15 +212,13 @@ class ApiClient
public function getDocuments(string $email, string $password, int $id, string $type): mixed
{
$data = [
'anfrage' => [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => $type,
'email' => $email,
'passwort' => $password,
'idbuchung' => $id,
],
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => $type,
'email' => $email,
'passwort' => $password,
'idbuchung' => $id,
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
@@ -249,18 +229,18 @@ class ApiClient
*/
private function sendRequest(string $type, array $data): mixed
{
$requestId = (string) Uuid::v7();
$requestId = Uuid::v7();
$body = $this
->serializer
->serialize($data, 'xml')
->serialize($data, 'xml', [
XmlEncoder::ROOT_NODE_NAME => 'anfrage',
XmlEncoder::ENCODING => 'UTF-8',
])
;
if (true === $this->config['debug']) {
$this->logger->info('Request sent', [
'id' => $requestId,
'request' => $body,
]);
$this->dumpXmlToFile('request', $requestId, $body);
}
try {
@@ -275,10 +255,7 @@ class ApiClient
$xml = $response->getContent();
if (true === $this->config['debug']) {
$this->logger->info('Response received', [
'id' => $requestId,
'response' => $xml,
]);
$this->dumpXmlToFile('response', $requestId, $xml);
}
return $this->responseParser->parseXmlString($type, $xml);
@@ -289,6 +266,15 @@ class ApiClient
throw new ApiClientException($e->getMessage());
}
private function dumpXmlToFile(string $type, string $requestId, string $body): void
{
if (false === file_exists($this->config['target_folder_dumps'])) {
mkdir($this->config['target_folder_dumps']);
}
file_put_contents($this->config['target_folder_dumps'].'/'.$requestId.'_'.$type.'.xml', $body);
}
private function createKey(string $username, string $password, string $type): string
{
$date = (new \DateTimeImmutable())->format('Ymd');
@@ -302,6 +288,7 @@ class ApiClient
$optionsResolver->setRequired(['bpn_url', 'bpn_username', 'bpn_password']);
$optionsResolver->setDefaults([
'debug' => false,
'target_folder_dumps' => '/var/www/html/var/bpn',
]);
return $optionsResolver->resolve($options);
@@ -20,6 +20,7 @@ class BookingResponseParser
$booking = new Booking();
$booking->id = (int) $xml->idbuchung;
$booking->agencyId = (int) $xml->idagentur;
$booking->bookingNumber = (int) $xml->vorgang;
$booking->invoiceNumber = (int) $xml->zahlungsdaten->rechnung;
$booking->totalPrice = $this->stringToFloat((string) $xml->zahlungsdaten->gesamtbetrag);
@@ -35,9 +36,16 @@ class BookingResponseParser
$booking->applicant = $this->parsePersonalData($xml->anmelder);
$booking->participantsStatus = $this->stringToArray((string) $xml->status_teilnehmer, '/');
$participantsStatus = $this->stringToArray((string) $xml->status_teilnehmer, '/');
$booking->participantsStatus = $this->arrayToOneBased($participantsStatus);
$booking->participants = $this->parseParticipants($xml);
$paymentData = $xml->zahlung;
$booking->paymentId = (int) $paymentData->attributes()['idzahlungsart'];
$booking->paymentLabel = (string) $paymentData->attributes()['bezeichnung'];
$booking->paymentType = (string) $paymentData->attributes()['art'];
if ($xml->beförderungen) {
$booking->transportationServices = $this
->parseServices($xml->beförderungen->beförderung, Service::TYPE_TRANSPORTATION);
@@ -149,8 +157,10 @@ class BookingResponseParser
$room = new Room();
$room->id = $id;
$room->label = (string) $attributes['zimmer'];
$room->dateFrom = $attributes['termin'] ? $this->stringToDate((string) $attributes['anreise']) : null;
$room->dateTo = $attributes['terminbis'] ? $this->stringToDate((string) $attributes['abreise']) : null;
$room->category = (string) $attributes['kategorie'];
$room->boardId = (int) $attributes['idverpflegung'];
$room->dateFrom = $attributes['anreise'] ? $this->stringToDate((string) $attributes['anreise']) : null;
$room->dateTo = $attributes['abreise'] ? $this->stringToDate((string) $attributes['abreise']) : null;
$room->totalCount = (int) $attributes['anzahl'];
$room->minPax = (int) $attributes['minpax'];
$room->maxPax = (int) $attributes['maxpax'];
@@ -0,0 +1,19 @@
<?php
namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\BookingUpdate;
class BookingUpdateResponseParser
{
use ResponseParserTrait;
public function parse(\SimpleXMLElement $xml): BookingUpdate
{
$bookingUpdate = new BookingUpdate();
$bookingUpdate->valid = 'möglich' === (string) $xml->aenderung;
$bookingUpdate->totalPrice = $this->stringToFloat((string) $xml->gesamtpreis);
return $bookingUpdate;
}
}
@@ -49,6 +49,8 @@ class ResponseParser
return (new MutableFieldsResponseParser())->parse($xml);
case ApiClient::TYPE_AVAILABILITY:
return (new AvailabilitiesResponseParser())->parse($xml);
case ApiClient::TYPE_BOOKING_UPDATE:
return (new BookingUpdateResponseParser())->parse($xml);
}
throw new ResponseParserException('Unable to parse XML response');
@@ -166,14 +166,16 @@ class TravelDataLoader extends AbstractDataLoader
$room = new Room();
$room->id = $roomId;
$room->code = (string) $item->attributes()['zimmercode'];
$room->label = (string) $item->attributes()['zimmertext'];
$room->minPax = (int) $item->attributes()['MinPax'];
$room->maxPax = (int) $item->attributes()['MaxPax'];
$room->nights = (int) $item->attributes()['naechte'];
$room->code = (string) $attributes['zimmercode'];
$room->category = (string) $attributes['kat'];
$room->boardId = (int) $attributes['idbuspro_vp'];
$room->label = (string) $attributes['zimmertext'];
$room->minPax = (int) $attributes['MinPax'];
$room->maxPax = (int) $attributes['MaxPax'];
$room->nights = (int) $attributes['naechte'];
$room->price = $attributes['preis'] ? $this->stringToFloat((string) $attributes['preis']) : null;
$room->status = (string) $item->status;
$room->available = (int) $item->attributes()['verfuegbar'];
$room->available = (int) $attributes['verfuegbar'];
$rooms[$roomId] = $room;
}
+84 -9
View File
@@ -7,6 +7,7 @@ use App\Form\Model\BookingData;
class Booking
{
public ?int $id = null;
public ?int $agencyId = null;
public ?int $bookingNumber = null;
public ?Travel $travelData = null;
public ?string $status = null;
@@ -22,11 +23,15 @@ class Booking
public ?string $hotelName = null;
public ?bool $document = null;
public ?float $payment = null;
public ?string $paymentId = null;
public ?string $paymentType = null;
public ?string $paymentLabel = null;
public array $participantsStatus = [];
public array $participants = [];
public array $transportationServices = [];
public array $additionalServices = [];
public array $rooms = [];
public array $pickups = [];
public ?int $invoiceNumber = null;
public ?float $totalPrice = null;
@@ -111,11 +116,11 @@ class Booking
public function toPayload(?BookingData $formData): array
{
// Reset services to participants mappings
foreach ([...$this->additionalServices, ...$this->transportationServices] as $service) {
// Reset mappings
foreach ([...$this->additionalServices, ...$this->transportationServices, ...$this->pickups] as $service) {
$service->mapping = [];
}
// Update mappings and add previously unselected services
// Update mappings, add services and pickups
foreach ($formData->participants as $participant) {
$servicesToMap = [
...$participant->courses,
@@ -140,6 +145,12 @@ class Booking
}
$this->transportationServices[$service->id]->mapping[] = $participant->index;
}
if ('BUS' === $participant->transportationServiceTo->subType && null !== $selectedPickup = $participant->pickup) {
if (false === isset($this->pickups[$selectedPickup->id])) {
$this->pickups[$selectedPickup->id] = $selectedPickup;
}
$this->pickups[$selectedPickup->id]->mapping[] = $participant->index;
}
}
// Remove services with empty mappings
foreach ($this->additionalServices as $service) {
@@ -166,15 +177,79 @@ class Booking
$this->participants[$participant->index]->communication->mobile = $participant->mobile;
}
return [
$payload = [
'idbuchung' => $this->id,
'status' => $this->status,
'idagentur' => $this->agencyId,
'idreise' => $this->travelId,
'idpartner' => $this->hotelId,
'anmelder' => $this->applicant->toPayload(),
'teilnehmerliste' => [],
'beförderungen' => [],
'unterbringungen' => [],
'zusatzleistungen' => [],
'zustiege' => [],
'zahlung' => [
'@idzahlungsart' => $this->paymentId,
'@bezeichnung' => $this->paymentLabel,
'@art' => $this->paymentType,
],
'teilnehmerliste' => [
'teilnehmer' => [],
],
'zusatzleistungen' => [
'zusatzleistung' => [],
],
'beförderungen' => [
'beförderung' => [],
],
'ferienzielunterbringungen' => [
'ferienzielunterbringung' => [],
],
];
foreach ($this->participants as $index => $participant) {
$payload['teilnehmerliste']['teilnehmer'][] = [
'@id' => $index,
'status' => $this->participantsStatus[$index],
...$participant->toPayload(),
];
}
foreach ($this->additionalServices as $service) {
$payload['zusatzleistungen']['zusatzleistung'][] = [
'@idleistung' => $service->id,
'@anzahl' => count($service->mapping),
'@zuordnung' => implode(',', $service->mapping),
];
}
foreach ($this->transportationServices as $service) {
$payload['beförderungen']['beförderung'][] = [
'@idleistung' => $service->id,
'@anzahl' => count($service->mapping),
'@zuordnung' => implode(',', $service->mapping),
];
}
foreach ($this->rooms as $room) {
$payload['ferienzielunterbringungen']['ferienzielunterbringung'][] = [
'@idzimmer' => $room->id,
'@kategorie' => $room->category,
'@idverpflegung' => $room->boardId,
'@anreise' => $room->dateFrom ? $room->dateFrom->format('d.m.Y') : null,
'@abreise' => $room->dateTo ? $room->dateTo->format('d.m.Y') : null,
'@anzahl' => count($room->mapping),
'@zuordnung' => implode(',', $room->mapping),
];
}
if (0 < count($this->pickups)) {
$payload['zustiege']['zustieg'] = [];
foreach ($this->pickups as $pickup) {
$payload['zustiege']['zustieg'][] = [
'@idzustieg' => $pickup->id,
'@anzahl' => count($pickup->mapping),
'@zuordnung' => implode(',', $pickup->mapping),
];
}
}
return $payload;
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\BusProNet\Model;
class BookingUpdate
{
public bool $valid = false;
public ?string $status = null;
public array $prices = [];
public ?float $totalPrice = null;
}
+3
View File
@@ -44,6 +44,9 @@ class PersonalData
'name' => $this->name,
'anschrift' => $this->address->toPayload(),
'kommunikation' => $this->communication->toPayload(),
'sonstiges1' => $this->height,
'sonstiges2' => $this->weight,
'sonstiges3' => $this->shoeSize,
];
}
}
+1
View File
@@ -11,4 +11,5 @@ class Pickup
public ?string $street = null;
public ?\DateTimeImmutable $time = null;
public ?float $price = null;
public array $mapping = [];
}
+2
View File
@@ -5,6 +5,8 @@ namespace App\BusProNet\Model;
class Room
{
public ?int $id = null;
public ?string $category = null;
public ?int $boardId = null;
public ?string $code = null;
public ?string $label = null;
public ?\DateTimeImmutable $dateFrom = null;