wip: continue implementation

This commit is contained in:
Björn Fromme
2024-11-27 11:31:48 +01:00
parent b5d52b0053
commit 84ab6446ea
81 changed files with 9127 additions and 898 deletions
+22 -1
View File
@@ -17,7 +17,6 @@ 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;
class ApiClient
@@ -77,6 +76,28 @@ class ApiClient
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
}
/**
* @throws ApiClientException
*/
public function updateNewsletterRegistration(string $email, string $password, PersonalData $personalData): Notification|PersonalData
{
$data = [
'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' => 'Newsletter',
'email' => $email,
'passwort' => $password,
'idadresse' => $personalData->addressId,
'newsletter' => [
'email' => $email,
'anmeldung' => $personalData->communication->newsletter ? 'True' : 'False',
],
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
}
/**
* @throws ApiClientException
*/
@@ -4,10 +4,11 @@ namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\Availability;
use App\BusProNet\Model\BaseData;
use App\BusProNet\TypeConversionTrait;
class AvailabilitiesResponseParser
{
use ResponseParserTrait;
use TypeConversionTrait;
public function parse(\SimpleXMLElement $xml): BaseData
{
@@ -10,10 +10,11 @@ use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Surcharge;
use App\BusProNet\TypeConversionTrait;
class BookingResponseParser
{
use ResponseParserTrait;
use TypeConversionTrait;
public function parse(\SimpleXMLElement $xml): Booking
{
@@ -39,7 +40,7 @@ class BookingResponseParser
$booking->applicant = $this->parsePersonalData($xml->anmelder);
$participantsStatus = $this->stringToArray((string) $xml->status_teilnehmer, '/');
$booking->participantsStatus = $this->arrayToOneBased($participantsStatus);
$booking->participantsStatus = array_combine(range(1, count($participantsStatus)), $participantsStatus);
$booking->participants = $this->parseParticipants($xml);
@@ -120,7 +121,7 @@ class BookingResponseParser
return $personalData;
}
private function parseServices(\SimpleXMLElement $xml, string $type): array
private function parseServices(\SimpleXMLElement $xml, string $category): array
{
$services = [];
@@ -130,6 +131,8 @@ class BookingResponseParser
$service = new Service();
$service->id = $id;
$service->category = $category;
$service->source = Service::SOURCE_BOOKING;
$service->label = (string) $attributes['leistung'];
$service->dateFrom = $attributes['termin'] ? $this->stringToDate((string) $attributes['termin']) : null;
$service->dateTo = $attributes['terminbis'] ? $this->stringToDate((string) $attributes['terminbis']) : null;
@@ -144,7 +147,7 @@ class BookingResponseParser
}, $this->stringToArray((string) $attributes['einzelpreis'], '/')
);
$service->individualPrice = array_combine($mapping, $individualPrices);
if (Service::CATEGORY_TRANSPORTATION === $type) {
if (Service::CATEGORY_TRANSPORTATION === $category) {
$service->direction = (string) $attributes['richtung'];
}
@@ -3,10 +3,11 @@
namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\BookingUpdate;
use App\BusProNet\TypeConversionTrait;
class BookingUpdateResponseParser
{
use ResponseParserTrait;
use TypeConversionTrait;
public function parse(\SimpleXMLElement $xml): BookingUpdate
{
@@ -4,10 +4,11 @@ namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\Booking;
use App\BusProNet\TypeConversionTrait;
class BookingsResponseParser
{
use ResponseParserTrait;
use TypeConversionTrait;
public function parse(\SimpleXMLElement $xml): BaseData
{
@@ -23,6 +24,7 @@ class BookingsResponseParser
$booking->price = $this->stringToFloat((string) $item->preis);
$booking->bookingDate = $this->stringToDateTime((string) $item->buchungsdatum);
$booking->travel = (string) $item->reise;
$booking->travelId = (int) $item->idreise;
$booking->travelDate = $this->stringToDate((string) $item->reisedatum);
$booking->document = $this->stringToBool((string) $item->reisedokument);
$booking->payment = $this->stringToFloat((string) $item->zahlung);
@@ -2,13 +2,14 @@
namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\CrmAttributes;
use App\BusProNet\Model\CrmSelection;
use App\BusProNet\Model\CrmSelectionGroup;
use App\BusProNet\Model\CrmAttributes;
use App\BusProNet\TypeConversionTrait;
class CrmAttributesResponseParser
{
use ResponseParserTrait;
use TypeConversionTrait;
private const BPN_CRM_ID_ADMIN = 1292;
private const BPN_CRM_ID_MANAGER = 1293;
@@ -4,10 +4,11 @@ namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\MutableData;
use App\BusProNet\TypeConversionTrait;
class MutableDataResponseParser
{
use ResponseParserTrait;
use TypeConversionTrait;
public function parse(\SimpleXMLElement $xml): BaseData
{
@@ -5,9 +5,12 @@ namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\Address;
use App\BusProNet\Model\Communication;
use App\BusProNet\Model\PersonalData;
use App\BusProNet\TypeConversionTrait;
class PersonalDataResponseParser
{
use TypeConversionTrait;
public function parse(\SimpleXMLElement $xml): PersonalData
{
$addressXml = $xml->adressdaten;
@@ -30,24 +33,25 @@ class PersonalDataResponseParser
$personalData->weight = $xml->sonstiges2 ? (int) $xml->sonstiges2 : null;
$personalData->shoeSize = $xml->sonstiges3 ? (int) $xml->sonstiges3 : null;
$postalXml = $addressXml->anschrift;
if ($postalXml = $addressXml->anschrift) {
$address = new Address();
$address->street = (string) $postalXml->strasse;
$address->postCode = (string) $postalXml->plz;
$address->city = (string) $postalXml->ort;
$address->country = (string) $postalXml->land;
$address = new Address();
$address->street = (string) $postalXml->strasse;
$address->postCode = (string) $postalXml->plz;
$address->city = (string) $postalXml->ort;
$address->country = (string) $postalXml->land;
$personalData->address = $address;
}
$personalData->address = $address;
if ($contactXml = $addressXml->kommunikation) {
$communication = new Communication();
$communication->phone = (string) $contactXml->telefonprivat;
$communication->mobile = (string) $contactXml->telefonmobil;
$communication->email = (string) $contactXml->email;
$communication->newsletter = $this->stringToBool((string) $contactXml->newsletter);
$contactXml = $addressXml->kommunikation;
$communication = new Communication();
$communication->phone = (string) $contactXml->telefonprivat;
$communication->mobile = (string) $contactXml->telefonmobil;
$communication->email = (string) $contactXml->email;
$personalData->communication = $communication;
$personalData->communication = $communication;
}
return $personalData;
}
@@ -33,6 +33,7 @@ class ResponseParser
switch ($subType) {
case 'Adressdaten':
case 'Adressdaten_Ändern':
case 'Newsletter':
return (new PersonalDataResponseParser())->parse($xml);
case 'SelektionCRM':
case 'SelektionCRM_Ändern':
@@ -2,12 +2,12 @@
namespace App\BusProNet\DataLoader;
use App\BusProNet\ApiResponseParser\ResponseParserTrait;
use App\BusProNet\TypeConversionTrait;
use Symfony\Contracts\Cache\CacheInterface;
abstract class AbstractDataLoader
{
use ResponseParserTrait;
use TypeConversionTrait;
public function __construct(protected readonly CacheInterface $cache, protected readonly ?string $xmlPath = null)
{
+77 -12
View File
@@ -11,38 +11,90 @@ use App\BusProNet\Model\Room;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use Symfony\Component\Finder\Finder;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
class TravelDataLoader extends AbstractDataLoader
{
public function __construct(
private readonly HotelDataLoader $hotelDataLoader,
CacheInterface $cache,
?string $xmlPath = null
) {
parent::__construct($cache, $xmlPath);
}
public function generateMapping(): array
{
return $this->cache->get('bpn_travels_mapping', function (ItemInterface $item) {
$item->expiresAfter(3600);
$finder = new Finder();
$finder->files()->in($this->xmlPath)->name('Ziel_*.xml');
$mapping = [];
$hotels = $this->hotelDataLoader->loadAll();
foreach ($finder as $file) {
$xml = simplexml_load_file($file->getRealPath());
foreach ($xml->reise->termin as $travelXml) {
$attributes = $travelXml->attributes();
$travelId = (int) $attributes['idbuspro'];
$mapping[$travelId] = [
'id' => (int) $attributes['idbuspro'],
'label' => (string) $travelXml->text,
'dateFrom' => $this->stringToDate((string) $attributes['termin']),
'dateTo' => $this->stringToDate((string) $attributes['bis']),
'hotels' => [],
'file' => $file->getRealPath(),
];
foreach ($travelXml->hotel as $hotelXml) {
$hotelId = (int) $hotelXml->attributes()['idbuspro'];
$mapping[$travelId]['hotels'][$hotelId] = [
'id' => $hotelId,
'label' => $hotels[$hotelId]->name,
];
}
}
}
return $mapping;
});
}
public function loadById(int $id, ?string $filename = null): ?Travel
{
if (null !== $filename) {
return $this->loadXml($id, $filename);
}
$finder = new Finder();
$finder->files()->in($this->xmlPath)->name('Ziel_*.xml');
$mapping = $this->generateMapping();
foreach ($finder as $file) {
if (null !== $xml = $this->loadXml($id, $file->getRealPath())) {
return $xml;
}
if (false === isset($mapping[$id])) {
return null;
}
return null;
$file = $mapping[$id]['file'];
return $this->loadXml($id, $file);
}
private function loadXml(int $id, string $filename): ?Travel
{
$xml = simplexml_load_file($filename);
foreach ($xml->reise->termin as $travel) {
if ($id === (int) $travel->attributes()['idbuspro']) {
return $this->parseXml($travel);
}
$travelXml = $xml->xpath(sprintf('//reise/termin[@idbuspro="%d"]', $id));
if (0 === count($travelXml)) {
return null;
}
return null;
return $this->parseXml($travelXml[0]);
}
public function parseXml(\SimpleXMLElement $xml): Travel
@@ -100,6 +152,7 @@ class TravelDataLoader extends AbstractDataLoader
$serviceId = (int) $attributes['idbuspro'];
$service = new Service();
$service->source = Service::SOURCE_TRAVEL;
$service->category = Service::CATEGORY_ADDITIONAL;
$service->id = $serviceId;
$service->subType = (string) $attributes['unterart'];
@@ -125,6 +178,7 @@ class TravelDataLoader extends AbstractDataLoader
$serviceId = (int) $attributes['idbuspro'];
$service = new Service();
$service->source = Service::SOURCE_TRAVEL;
$service->category = Service::CATEGORY_TRANSPORTATION;
$service->id = $serviceId;
$service->subType = (string) $attributes['unterart'];
@@ -223,4 +277,15 @@ class TravelDataLoader extends AbstractDataLoader
}
}
}
public function patchBookings(BaseData $bookings): void
{
foreach ($bookings->getItems() as $booking) {
if (null === $travelId = $booking->travelId) {
continue;
}
$booking->travelData = $this->loadById($travelId);
}
}
}
+1
View File
@@ -16,6 +16,7 @@ class Booking
public ?int $travelId = null;
public ?string $travelCode = null;
public ?\DateTimeImmutable $travelDate = null;
public ?Travel $travelData = null;
public ?int $hotelId = null;
public ?string $hotelName = null;
public ?bool $document = null;
+2
View File
@@ -7,6 +7,7 @@ class Communication
public ?string $phone = null;
public ?string $mobile = null;
public ?string $email = null;
public bool $newsletter = false;
public function toPayload(): array
{
@@ -14,6 +15,7 @@ class Communication
'email' => $this->email,
'telefonmobil' => $this->mobile,
'telefonprivat' => $this->phone,
'newsletter' => $this->newsletter ? 'True' : 'False',
];
}
}
+5
View File
@@ -8,10 +8,13 @@ class PersonalData
{
public ?int $addressId = null;
public ?int $personId = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
public ?string $name = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
public ?string $firstName = null;
public ?string $salutation = null;
public ?string $title = null;
public ?string $gender = null;
@@ -20,8 +23,10 @@ class PersonalData
public ?string $shoeSize = null;
public ?string $weight = null;
public ?\DateTimeImmutable $dateOfBirth = null;
#[Assert\Valid()]
public ?Address $address = null;
#[Assert\Valid()]
public ?Communication $communication = null;
+4
View File
@@ -4,6 +4,9 @@ namespace App\BusProNet\Model;
class Service
{
public const SOURCE_TRAVEL = 'travel';
public const SOURCE_BOOKING = 'booking';
public const CATEGORY_ADDITIONAL = 'additional';
public const CATEGORY_TRANSPORTATION = 'transportation';
@@ -14,6 +17,7 @@ class Service
public const TOKEN_RENTALS = ['VER', 'VE2', 'VE3', 'VE4', 'VE5', 'VE6', 'VE7', 'VE8'];
public ?int $id = null;
public ?string $source = null;
public ?string $category = null;
public ?string $status = null;
public bool $mandatory = false;
@@ -1,19 +1,12 @@
<?php
namespace App\BusProNet\ApiResponseParser;
namespace App\BusProNet;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
trait ResponseParserTrait
trait TypeConversionTrait
{
private function arrayToOneBased(array $data): array
{
$keys = range(1, count($data));
return array_combine($keys, $data);
}
protected function stringToArray(string $string, string $separator = ','): array
{
return explode($separator, $string);
@@ -24,11 +17,21 @@ trait ResponseParserTrait
return (float) str_replace(['.', ','], ['', '.'], $string);
}
protected function floatToString(float $float): string
{
return number_format($float, 2, ',', '');
}
protected function stringToBool(string $string): bool
{
return 'True' === $string;
}
protected function boolToString(bool $bool): string
{
return $bool ? 'True' : 'False';
}
protected function stringToDate(string $string): ?\DateTimeImmutable
{
try {