From bd13ae653730845d95fee71febdfe44de10793d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Thu, 21 Nov 2024 18:38:41 +0100 Subject: [PATCH] wip: first working version --- src/BusProNet/ApiClient.php | 179 ++++++++---------- .../BookingResponseParser.php | 16 +- .../BookingUpdateResponseParser.php | 19 ++ .../ApiResponseParser/ResponseParser.php | 2 + src/BusProNet/DataLoader/TravelDataLoader.php | 14 +- src/BusProNet/Model/Booking.php | 93 ++++++++- src/BusProNet/Model/BookingUpdate.php | 11 ++ src/BusProNet/Model/PersonalData.php | 3 + src/BusProNet/Model/Pickup.php | 1 + src/BusProNet/Model/Room.php | 2 + src/Controller/Booking/DownloadController.php | 76 ++++++++ src/Controller/Booking/EditController.php | 86 +++++++++ src/Controller/Booking/IndexController.php | 37 ++++ src/Controller/BookingController.php | 160 ---------------- src/Form/BookingType.php | 1 + src/Form/Model/BookingData.php | 2 + src/Form/Model/ParticipantData.php | 19 +- src/Form/ParticipantType.php | 45 ++++- src/Validator/Constraints/Booking.php | 16 ++ .../Constraints/BookingValidator.php | 39 ++++ templates/booking/edit.html.twig | 11 ++ templates/booking/index.html.twig | 34 ++-- templates/layout.html.twig | 7 + templates/personal_data/index.html.twig | 36 ++-- 24 files changed, 590 insertions(+), 319 deletions(-) create mode 100644 src/BusProNet/ApiResponseParser/BookingUpdateResponseParser.php create mode 100644 src/BusProNet/Model/BookingUpdate.php create mode 100644 src/Controller/Booking/DownloadController.php create mode 100644 src/Controller/Booking/EditController.php create mode 100644 src/Controller/Booking/IndexController.php delete mode 100644 src/Controller/BookingController.php create mode 100644 src/Validator/Constraints/Booking.php create mode 100644 src/Validator/Constraints/BookingValidator.php diff --git a/src/BusProNet/ApiClient.php b/src/BusProNet/ApiClient.php index e94ad39..4ca50d2 100644 --- a/src/BusProNet/ApiClient.php +++ b/src/BusProNet/ApiClient.php @@ -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); diff --git a/src/BusProNet/ApiResponseParser/BookingResponseParser.php b/src/BusProNet/ApiResponseParser/BookingResponseParser.php index 667245b..aa22bfe 100644 --- a/src/BusProNet/ApiResponseParser/BookingResponseParser.php +++ b/src/BusProNet/ApiResponseParser/BookingResponseParser.php @@ -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']; diff --git a/src/BusProNet/ApiResponseParser/BookingUpdateResponseParser.php b/src/BusProNet/ApiResponseParser/BookingUpdateResponseParser.php new file mode 100644 index 0000000..929e5d3 --- /dev/null +++ b/src/BusProNet/ApiResponseParser/BookingUpdateResponseParser.php @@ -0,0 +1,19 @@ +valid = 'möglich' === (string) $xml->aenderung; + $bookingUpdate->totalPrice = $this->stringToFloat((string) $xml->gesamtpreis); + + return $bookingUpdate; + } +} \ No newline at end of file diff --git a/src/BusProNet/ApiResponseParser/ResponseParser.php b/src/BusProNet/ApiResponseParser/ResponseParser.php index c67210c..4a0935a 100644 --- a/src/BusProNet/ApiResponseParser/ResponseParser.php +++ b/src/BusProNet/ApiResponseParser/ResponseParser.php @@ -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'); diff --git a/src/BusProNet/DataLoader/TravelDataLoader.php b/src/BusProNet/DataLoader/TravelDataLoader.php index 7bb72fd..decb44e 100644 --- a/src/BusProNet/DataLoader/TravelDataLoader.php +++ b/src/BusProNet/DataLoader/TravelDataLoader.php @@ -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; } diff --git a/src/BusProNet/Model/Booking.php b/src/BusProNet/Model/Booking.php index 5258314..bbd999a 100644 --- a/src/BusProNet/Model/Booking.php +++ b/src/BusProNet/Model/Booking.php @@ -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; } } \ No newline at end of file diff --git a/src/BusProNet/Model/BookingUpdate.php b/src/BusProNet/Model/BookingUpdate.php new file mode 100644 index 0000000..6225f37 --- /dev/null +++ b/src/BusProNet/Model/BookingUpdate.php @@ -0,0 +1,11 @@ + $this->name, 'anschrift' => $this->address->toPayload(), 'kommunikation' => $this->communication->toPayload(), + 'sonstiges1' => $this->height, + 'sonstiges2' => $this->weight, + 'sonstiges3' => $this->shoeSize, ]; } } \ No newline at end of file diff --git a/src/BusProNet/Model/Pickup.php b/src/BusProNet/Model/Pickup.php index 28c141d..0eda5c2 100644 --- a/src/BusProNet/Model/Pickup.php +++ b/src/BusProNet/Model/Pickup.php @@ -11,4 +11,5 @@ class Pickup public ?string $street = null; public ?\DateTimeImmutable $time = null; public ?float $price = null; + public array $mapping = []; } \ No newline at end of file diff --git a/src/BusProNet/Model/Room.php b/src/BusProNet/Model/Room.php index 654abdd..ae88418 100644 --- a/src/BusProNet/Model/Room.php +++ b/src/BusProNet/Model/Room.php @@ -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; diff --git a/src/Controller/Booking/DownloadController.php b/src/Controller/Booking/DownloadController.php new file mode 100644 index 0000000..3097c0b --- /dev/null +++ b/src/Controller/Booking/DownloadController.php @@ -0,0 +1,76 @@ + '\d+'], + defaults: ['fileType' => 'documents'] + )] + #[Route( + path: '/bookings/{id}/confirmation', + name: 'app_booking_confirmation', + requirements: ['id' => '\d+'], + defaults: ['fileType' => 'confirmation'] + )] + #[IsGranted("ROLE_USER")] + public function documents(int $id, string $fileType, Request $request): Response + { + $bpnUser = $request->getSession()->get('bpn_user'); + + if (null === $bpnUser) { + return $this->security->logout(); + } + + $type = match ($fileType) { + 'documents' => 'Dokumentendruck', + 'confirmation' => 'Vorgangdruck', + }; + + $file = $this + ->apiClient + ->getDocuments($bpnUser->getEmail(), $bpnUser->getPassword(), $id, $type) + ; + + if (null === $file || $file instanceof Notification) { + $this->addFlash('error', 'Keine Dokumente vorhanden'); + + return $this->redirectToRoute('app_bookings'); + } + + $filename = u($file->filename)->ascii(); + + $response = new Response($file->content); + + $disposition = $response->headers->makeDisposition( + ResponseHeaderBag::DISPOSITION_ATTACHMENT, + $filename, + md5($filename) + ); + + $response->headers->set('Content-Disposition', $disposition); + $response->headers->set('Content-Type', $file->mimeType); + + return $response; + } +} \ No newline at end of file diff --git a/src/Controller/Booking/EditController.php b/src/Controller/Booking/EditController.php new file mode 100644 index 0000000..8c3823f --- /dev/null +++ b/src/Controller/Booking/EditController.php @@ -0,0 +1,86 @@ + '\d+'])] + #[IsGranted("ROLE_USER")] + public function edit(int $id, Request $request): Response + { + $bpnUser = $request->getSession()->get('bpn_user'); + + if (null === $bpnUser) { + return $this->security->logout(); + } + + $booking = $this->apiClient->getBooking($bpnUser->getEmail(), $bpnUser->getPassword(), $id); + + //$this->denyAccessUnlessGranted('VIEW', $booking); + + $travelData = $this->travelDataLoader->loadById($booking->travelId); + + if (null === $travelData) { + $this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar'); + + return $this->redirectToRoute('app_bookings'); + } + + $booking->travelData = $travelData; + $mutableFields = $this->apiClient->getMutableFields($booking->travelId); + $availabilities = $this->apiClient->getAvailabilities($booking->travelId); + + $this->travelDataLoader->enrichServicesData($travelData, $availabilities); + $this->pickupDataLoader->enrichPickupsData($travelData); + + $formData = BookingData::fromBooking($booking); + + $form = $this->createForm(BookingType::class, $formData, [ + 'hx_post' => $this->generateUrl('app_booking_edit', ['id' => $id]), + 'hx_target' => '#app', + 'hx_swap' => 'innerHTML show:top', + 'attr' => ['novalidate' => 'novalidate'], + 'travel' => $travelData, + 'mutable_fields' => $mutableFields, + ]); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->apiClient->updateBooking($formData, false); + + $this->addFlash('success', 'Buchung erfolgreich aktualisiert'); + + return new HxRedirectResponse($this->generateUrl('app_booking_edit', ['id' => $id])); + } + + return $this->render('booking/edit.html.twig', [ + 'booking' => $booking, + 'travelData' => $travelData, + 'mutableFields' => $mutableFields, + 'availabilities' => $availabilities, + 'form' => $form->createView(), + ]); + } +} \ No newline at end of file diff --git a/src/Controller/Booking/IndexController.php b/src/Controller/Booking/IndexController.php new file mode 100644 index 0000000..9f68711 --- /dev/null +++ b/src/Controller/Booking/IndexController.php @@ -0,0 +1,37 @@ +getSession()->get('bpn_user'); + + if (null === $bpnUser) { + return $this->security->logout(); + } + + $bookings = $this->apiClient->getBookings($bpnUser->getEmail(), $bpnUser->getPassword()); + + return $this->render('booking/index.html.twig', [ + 'bookings' => $bookings->getItems(), + ]); + } +} \ No newline at end of file diff --git a/src/Controller/BookingController.php b/src/Controller/BookingController.php deleted file mode 100644 index d03121c..0000000 --- a/src/Controller/BookingController.php +++ /dev/null @@ -1,160 +0,0 @@ -getSession()->get('bpn_user'); - - if (null === $bpnUser) { - return $this->security->logout(); - } - - $bookings = $this->apiClient->getBookings($bpnUser->getEmail(), $bpnUser->getPassword()); - - return $this->render('booking/index.html.twig', [ - 'bookings' => $bookings->getItems(), - ]); - } - - #[Route('/bookings/{id}/edit', name: 'app_booking_edit', requirements: ['id' => '\d+'])] - #[IsGranted("ROLE_USER")] - public function edit(int $id, Request $request): Response - { - $bpnUser = $request->getSession()->get('bpn_user'); - - if (null === $bpnUser) { - return $this->security->logout(); - } - - $booking = $this->apiClient->getBooking($bpnUser->getEmail(), $bpnUser->getPassword(), $id); - - //$this->denyAccessUnlessGranted('VIEW', $booking); - - $travelData = $this->travelDataLoader->loadById($booking->travelId); - - if (null === $travelData) { - $this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar'); - - return $this->redirectToRoute('app_bookings'); - } - - $mutableFields = $this->apiClient->getMutableFields($booking->travelId); - $availabilities = $this->apiClient->getAvailabilities($booking->travelId); - - $this->travelDataLoader->enrichServicesData($travelData, $availabilities); - $this->pickupDataLoader->enrichPickupsData($travelData); - - $formData = BookingData::fromBooking($booking); - - $form = $this->createForm(BookingType::class, $formData, [ - 'hx_post' => $this->generateUrl('app_booking_edit', ['id' => $id]), - 'hx_target' => '#app', - 'hx_swap' => 'innerHTML show:top', - 'attr' => ['novalidate' => 'novalidate'], - 'travel' => $travelData, - 'mutable_fields' => $mutableFields, - ]); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $this->apiClient->updateBooking($bpnUser->getEmail(), $bpnUser->getPassword(), $formData); - } - - return $this->render('booking/edit.html.twig', [ - 'booking' => $booking, - 'travelData' => $travelData, - 'mutableFields' => $mutableFields, - 'availabilities' => $availabilities, - 'form' => $form->createView(), - ]); - } - - #[Route('/bookings/{id}/documents', name: 'app_booking_documents', requirements: ['id' => '\d+'])] - #[IsGranted("ROLE_USER")] - public function documents(int $id, Request $request): Response - { - $bpnUser = $request->getSession()->get('bpn_user'); - - if (null === $bpnUser) { - return $this->security->logout(); - } - - $file = $this - ->apiClient - ->getDocuments($bpnUser->getEmail(), $bpnUser->getPassword(), $id, 'Dokumentdruck') - ; - - if (null === $file || $file instanceof Notification) { - $this->addFlash('error', 'Keine Dokumente vorhanden'); - - return $this->redirectToRoute('app_bookings'); - } - - return $this->createDownloadResponse($file); - } - - #[Route('/bookings/{id}/confirmation', name: 'app_booking_confirmation', requirements: ['id' => '\d+'])] - #[IsGranted("ROLE_USER")] - public function confirmation(int $id, Request $request): Response - { - $bpnUser = $request->getSession()->get('bpn_user'); - - if (null === $bpnUser) { - return $this->security->logout(); - } - - $file = $this - ->apiClient - ->getDocuments($bpnUser->getEmail(), $bpnUser->getPassword(), $id, 'Vorgangdruck') - ; - - return $this->createDownloadResponse($file); - } - - private function createDownloadResponse(File $file): Response - { - $filename = u($file->filename)->ascii(); - - $response = new Response($file->content); - - $disposition = $response->headers->makeDisposition( - ResponseHeaderBag::DISPOSITION_ATTACHMENT, - $filename, - md5($filename) - ); - - $response->headers->set('Content-Disposition', $disposition); - $response->headers->set('Content-Type', $file->mimeType); - - return $response; - } -} \ No newline at end of file diff --git a/src/Form/BookingType.php b/src/Form/BookingType.php index 6e54807..84f3924 100644 --- a/src/Form/BookingType.php +++ b/src/Form/BookingType.php @@ -26,6 +26,7 @@ final class BookingType extends AbstractType ->getTransportationServicesByDirection('HIN'), 'selectable_transportation_services_fro' => $options['travel'] ->getTransportationServicesByDirection('RUECK'), + 'selectable_pickups' => $options['travel']->pickups, ], 'allow_add' => false, 'allow_delete' => false, diff --git a/src/Form/Model/BookingData.php b/src/Form/Model/BookingData.php index f256d04..9226bab 100644 --- a/src/Form/Model/BookingData.php +++ b/src/Form/Model/BookingData.php @@ -5,8 +5,10 @@ namespace App\Form\Model; use App\BusProNet\Model\Booking; use App\BusProNet\Model\PersonalData; use App\BusProNet\Model\Service; +use App\Validator\Constraints as AppAssert; use Symfony\Component\Validator\Constraints as Assert; +#[AppAssert\Booking] class BookingData { public ?Booking $booking = null; diff --git a/src/Form/Model/ParticipantData.php b/src/Form/Model/ParticipantData.php index 3726d1d..b7f43f7 100644 --- a/src/Form/Model/ParticipantData.php +++ b/src/Form/Model/ParticipantData.php @@ -3,6 +3,7 @@ namespace App\Form\Model; use App\BusProNet\Model\PersonalData; +use App\BusProNet\Model\Pickup; use App\BusProNet\Model\Service; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -47,9 +48,10 @@ class ParticipantData public array $rentals = []; public ?Service $transportationServiceTo = null; public ?Service $transportationServiceFro = null; + public ?Pickup $pickup = null; #[Assert\Callback] - public function assertBodyMeasurements(ExecutionContextInterface $context): void + public function assertBodyMeasurementsValid(ExecutionContextInterface $context): void { if (0 === count($this->rentals)) { return; @@ -63,20 +65,31 @@ class ParticipantData } if (empty($this->shoeSize)) { - $context->buildViolation('Bitte angeben Leihmaterial') + $context->buildViolation('Bitte angeben wegen Leihmaterial') ->atPath('shoeSize') ->addViolation() ; } if (empty($this->weight)) { - $context->buildViolation('Bitte angeben Leihmaterial') + $context->buildViolation('Bitte angeben wegen Leihmaterial') ->atPath('weight') ->addViolation() ; } } + #[Assert\Callback] + public function assertPickupSelected(ExecutionContextInterface $context): void + { + if (null !== $this->transportationServiceTo && null === $this->pickup) { + $context->buildViolation('Bitte auswählen') + ->atPath('pickupTo') + ->addViolation() + ; + } + } + public static function fromPersonalData(PersonalData $personalData): static { $instance = new static(); diff --git a/src/Form/ParticipantType.php b/src/Form/ParticipantType.php index 9807b2d..a51975f 100644 --- a/src/Form/ParticipantType.php +++ b/src/Form/ParticipantType.php @@ -3,6 +3,7 @@ namespace App\Form; use App\BusProNet\Form\CountryType; +use App\BusProNet\Model\Pickup; use App\BusProNet\Model\Service; use App\Form\Model\ParticipantData; use Symfony\Component\Form\AbstractType; @@ -35,6 +36,8 @@ class ParticipantType extends AbstractType ]) ->add('gender', ChoiceType::class, [ 'label' => 'Geschlecht', + 'required' => false, + 'placeholder' => 'keine Angabe', 'choices' => [ 'männlich' => 'M', 'weiblich' => 'W', @@ -65,9 +68,9 @@ class ParticipantType extends AbstractType 'required' => false, ]) ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) { - /** @var ParticipantData $participant */ - $participant = $event->getData(); - $participantIndex = $participant->index; + /** @var ParticipantData $participantData */ + $participantData = $event->getData(); + $participantIndex = $participantData->index; $form = $event->getForm(); @@ -139,8 +142,43 @@ class ParticipantType extends AbstractType 'multiple' => false, 'choices' => $options['selectable_transportation_services_fro'], ]) + ->add('pickup', ChoiceType::class, [ + 'label' => 'Zustieg (bei Busanreise)', + 'multiple' => false, + 'expanded' => false, + 'choices' => $options['selectable_pickups'], + 'choice_value' => 'id', + 'choice_label' => function (?Pickup $pickup) { + if (null === $pickup) { + return null; + } + + $price = $pickup->price; + + if (null === $price || 0.0 === $price) { + return $pickup->city; + } + + return sprintf('%s (%s€)', + $pickup->city, + number_format($price, 2, ',', '.') + ); + }, + ]) ; }) + ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) { + $data = $event->getData(); + $form = $event->getForm(); + + $transportationId = $data['transportationServiceTo']; + $transportation = $options['travel']->pickups[$transportationId] ?? null; + + if (null !== $transportation && 'PKW' === $transportation->subType) { + $form->remove('pickup'); + unset($data['pickup']); + } + }) ; } @@ -155,6 +193,7 @@ class ParticipantType extends AbstractType 'selectable_rentals' => [], 'selectable_transportation_services_to' => [], 'selectable_transportation_services_fro' => [], + 'selectable_pickups' => [], ]); } } \ No newline at end of file diff --git a/src/Validator/Constraints/Booking.php b/src/Validator/Constraints/Booking.php new file mode 100644 index 0000000..51a2253 --- /dev/null +++ b/src/Validator/Constraints/Booking.php @@ -0,0 +1,16 @@ +apiClient->updateBooking($bookingData); + + if ($result instanceof Notification) { + $this->context + ->buildViolation($result->message) + ->addViolation() + ; + } + + if ($result instanceof BookingUpdate && false === $result->valid) { + $this->context + ->buildViolation($constraint->message) + ->addViolation() + ; + } + } +} diff --git a/templates/booking/edit.html.twig b/templates/booking/edit.html.twig index 5d8ca2c..97a399d 100644 --- a/templates/booking/edit.html.twig +++ b/templates/booking/edit.html.twig @@ -2,6 +2,14 @@ {% block content %} {{ form_start(form) }} + {% if not form.vars.valid %} +
+
+ Fehler +
+ {{ form_errors(form) }} +
+ {% endif %} {% for child in form.participants %} {% set participant = child.vars.data %} @@ -57,6 +65,9 @@
{{ form_row(child.transportationServiceTo) }} {{ form_row(child.transportationServiceFro) }} + {% if child.pickup is defined %} + {{ form_row(child.pickup) }} + {% endif %}
{% endfor %} diff --git a/templates/booking/index.html.twig b/templates/booking/index.html.twig index a5281b5..ef624b2 100644 --- a/templates/booking/index.html.twig +++ b/templates/booking/index.html.twig @@ -1,14 +1,12 @@ {% extends 'layout.html.twig' %} {% block content %} - - +
+
+ - - - - + + {% for booking in bookings %} - - {% endfor %} - -
- Buchungsdatum - - Reisedatum + Buchungsdatum/
Reisedatum
Reise @@ -17,25 +15,19 @@ Vorgangsnr. - Preis - - offen + Preis/
offen
Status
- {{ booking.bookingDate | date('d.m.Y') }} - - {{ booking.travelDate | date('d.m.Y') }} + {{ booking.bookingDate | date('d.m.Y') }}/
{{ booking.travelDate | date('d.m.Y') }}
{{ booking.travel }} @@ -44,10 +36,7 @@ {{ booking.bookingNumber }} - {{ booking.price|format_currency('EUR') }} - - {{ booking.balance ? booking.balance|format_currency('EUR') : '-' }} + {{ booking.price|format_currency('EUR') }}/
{{ booking.balance ? booking.balance|format_currency('EUR') : '-' }}
{{ booking.status }} @@ -76,6 +65,7 @@
+ + + {% endblock %} \ No newline at end of file diff --git a/templates/layout.html.twig b/templates/layout.html.twig index 81a0ba2..caf0c63 100644 --- a/templates/layout.html.twig +++ b/templates/layout.html.twig @@ -42,6 +42,13 @@ {% endif %}
+ {% for label, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} {% block content %}{% endblock %}
diff --git a/templates/personal_data/index.html.twig b/templates/personal_data/index.html.twig index e43b1c2..d6fcb51 100644 --- a/templates/personal_data/index.html.twig +++ b/templates/personal_data/index.html.twig @@ -5,23 +5,25 @@ Persönliche Daten {{ form_start(form) }} -
- {{ form_row(form.gender) }} - {{ form_row(form.firstName) }} - {{ form_row(form.name) }} - {{ form_row(form.dateOfBirth) }} -
-
- {{ form_row(form.street) }} - {{ form_row(form.postCode) }} - {{ form_row(form.city) }} - {{ form_row(form.country) }} -
-
- {{ form_row(form.email) }} - {{ form_row(form.phone) }} - {{ form_row(form.mobile) }} -
+
+
+ {{ form_row(form.gender) }} + {{ form_row(form.firstName) }} + {{ form_row(form.name) }} + {{ form_row(form.dateOfBirth) }} +
+
+ {{ form_row(form.street) }} + {{ form_row(form.postCode) }} + {{ form_row(form.city) }} + {{ form_row(form.country) }} +
+
+ {{ form_row(form.email) }} + {{ form_row(form.phone) }} + {{ form_row(form.mobile) }} +
+
{{ form_rest(form) }}