wip: continue implemntation

This commit is contained in:
Björn Fromme
2024-11-22 17:30:20 +01:00
parent bd13ae6537
commit d3786795e4
26 changed files with 798 additions and 494 deletions
-35
View File
@@ -1,35 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Welcome!</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
<link rel="stylesheet" href="/assets/styles/app-1096e610e7245dcf120e944be588862e.css">
<script type="importmap">
{
"imports": {
"app": "/assets/app-9054f64ab2a2772c478ec2945ad30999.js",
"/assets/bootstrap.js": "/assets/bootstrap-e26ed6d3c7cb4cfb56ee7c0ce7d93cb4.js",
"/assets/styles/app.css": "data:application/javascript,",
"@symfony/stimulus-bundle": "/assets/@symfony/stimulus-bundle/loader-870999a02e9fc147c034d522826ea70d.js",
"htmx.org": "/assets/vendor/htmx.org/htmx.org.index-d0ecb202633ad81a9803dfd539c21378.js",
"@hotwired/stimulus": "/assets/vendor/@hotwired/stimulus/stimulus.index-b5b1d00e42695b8959b4a1e94e3bc92a.js",
"/assets/@symfony/stimulus-bundle/controllers.js": "/assets/@symfony/stimulus-bundle/controllers-7cbfd57985d28bce7c7110b2ca6c3224.js"
}
}
</script>
<!-- ES Module Shims: Import maps polyfill for modules browsers without import maps support -->
<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js"></script>
<link rel="modulepreload" href="/assets/app-9054f64ab2a2772c478ec2945ad30999.js">
<link rel="modulepreload" href="/assets/bootstrap-e26ed6d3c7cb4cfb56ee7c0ce7d93cb4.js">
<link rel="modulepreload" href="/assets/@symfony/stimulus-bundle/loader-870999a02e9fc147c034d522826ea70d.js">
<link rel="modulepreload" href="/assets/vendor/htmx.org/htmx.org.index-d0ecb202633ad81a9803dfd539c21378.js">
<link rel="modulepreload" href="/assets/vendor/@hotwired/stimulus/stimulus.index-b5b1d00e42695b8959b4a1e94e3bc92a.js">
<link rel="modulepreload" href="/assets/@symfony/stimulus-bundle/controllers-7cbfd57985d28bce7c7110b2ca6c3224.js">
<script type="module">import 'app';</script> </head>
<body>
<div hx-get="https://myep-next.ddev.site" hx-select="#main" hx-trigger="load"></div>
</body>
</html>
+9 -8
View File
@@ -3,6 +3,7 @@
namespace App\BusProNet; namespace App\BusProNet;
use App\BusProNet\ApiResponseParser\ResponseParser; use App\BusProNet\ApiResponseParser\ResponseParser;
use App\BusProNet\DataProcessor\BookingDataProcessor;
use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\Model\BaseData; use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\Booking; use App\BusProNet\Model\Booking;
@@ -24,7 +25,7 @@ class ApiClient
public const TYPE_NOTIFICATION = 'HINWEIS'; public const TYPE_NOTIFICATION = 'HINWEIS';
public const TYPE_CUSTOMER_DATA = 'KUNDENKONTO'; public const TYPE_CUSTOMER_DATA = 'KUNDENKONTO';
public const TYPE_BASE_DATA_COUNTRIES = 'STAMMLAENDER'; public const TYPE_BASE_DATA_COUNTRIES = 'STAMMLAENDER';
public const TYPE_MUTABLE_FIELDS = 'MOEGLICHEAENDERUNGEN'; public const TYPE_MUTABLE_DATA = 'MOEGLICHEAENDERUNGEN';
public const TYPE_AVAILABILITY = 'VERFUEGBARKEIT'; public const TYPE_AVAILABILITY = 'VERFUEGBARKEIT';
public const TYPE_BOOKING_UPDATE = 'BUCHUNGAENDERUNG'; public const TYPE_BOOKING_UPDATE = 'BUCHUNGAENDERUNG';
@@ -113,15 +114,15 @@ class ApiClient
public function updateBooking(BookingData $formData, bool $dryRun = true): Notification|BookingUpdate public function updateBooking(BookingData $formData, bool $dryRun = true): Notification|BookingUpdate
{ {
$booking = $formData->booking;
$mode = $dryRun ? 'Anfrage' : 'Buchung'; $mode = $dryRun ? 'Anfrage' : 'Buchung';
$payload = (new BookingDataProcessor())->createUpdateRequestPayload($formData);
$data = [ $data = [
'user' => $this->config['bpn_username'], 'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_BOOKING_UPDATE), 'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_BOOKING_UPDATE),
'satz' => ['@typ' => static::TYPE_BOOKING_UPDATE], 'satz' => ['@typ' => static::TYPE_BOOKING_UPDATE],
'buchungsart' => $mode, 'buchungsart' => $mode,
...$booking->toPayload($formData), ...$payload,
]; ];
return $this->sendRequest(static::TYPE_BOOKING_UPDATE, $data); return $this->sendRequest(static::TYPE_BOOKING_UPDATE, $data);
@@ -130,17 +131,17 @@ class ApiClient
/** /**
* @throws ApiClientException * @throws ApiClientException
*/ */
public function getMutableFields(int $id): Notification|BaseData public function getMutableData(int $id): Notification|BaseData
{ {
$data = [ $data = [
'user' => $this->config['bpn_username'], 'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_MUTABLE_FIELDS), 'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_MUTABLE_DATA),
'satz' => ['@typ' => static::TYPE_MUTABLE_FIELDS], 'satz' => ['@typ' => static::TYPE_MUTABLE_DATA],
'art' => 'Vorgang_Details', 'art' => 'Vorgang_Details',
'idreise' => $id, 'idreise' => $id,
]; ];
return $this->sendRequest(static::TYPE_MUTABLE_FIELDS, $data); return $this->sendRequest(static::TYPE_MUTABLE_DATA, $data);
} }
/** /**
@@ -229,7 +230,7 @@ class ApiClient
*/ */
private function sendRequest(string $type, array $data): mixed private function sendRequest(string $type, array $data): mixed
{ {
$requestId = Uuid::v7(); $requestId = date(DATE_ATOM).uniqid();
$body = $this $body = $this
->serializer ->serializer
@@ -6,8 +6,10 @@ use App\BusProNet\Model\Address;
use App\BusProNet\Model\Booking; use App\BusProNet\Model\Booking;
use App\BusProNet\Model\Communication; use App\BusProNet\Model\Communication;
use App\BusProNet\Model\PersonalData; use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Room; use App\BusProNet\Model\Room;
use App\BusProNet\Model\Service; use App\BusProNet\Model\Service;
use App\BusProNet\Model\Surcharge;
class BookingResponseParser class BookingResponseParser
{ {
@@ -48,14 +50,20 @@ class BookingResponseParser
if ($xml->beförderungen) { if ($xml->beförderungen) {
$booking->transportationServices = $this $booking->transportationServices = $this
->parseServices($xml->beförderungen->beförderung, Service::TYPE_TRANSPORTATION); ->parseServices($xml->beförderungen->beförderung, Service::CATEGORY_TRANSPORTATION);
} }
if ($xml->zusatzleistungen) { if ($xml->zusatzleistungen) {
$booking->additionalServices = $this $booking->additionalServices = $this
->parseServices($xml->zusatzleistungen->zusatzleistung, Service::TYPE_ADDITIONAL); ->parseServices($xml->zusatzleistungen->zusatzleistung, Service::CATEGORY_ADDITIONAL);
} }
if ($xml->ferienzielunterbringungen) { if ($xml->ferienzielunterbringungen) {
$booking->rooms = $this->parseRooms($xml->ferienzielunterbringungen->ferienzielunterbringung); $booking->rooms = $this->parseRooms($xml->ferienzielunterbringungen);
}
if ($xml->zustiege) {
$booking->pickups = $this->parsePickups($xml->zustiege);
}
if ($xml->zuschlaege) {
$booking->surcharges = $this->parseSurcharges($xml->zuschlaege);
} }
return $booking; return $booking;
@@ -135,8 +143,8 @@ class BookingResponseParser
return $this->stringToFloat((string) $price); return $this->stringToFloat((string) $price);
}, $this->stringToArray((string) $attributes['einzelpreis'], '/') }, $this->stringToArray((string) $attributes['einzelpreis'], '/')
); );
$service->individualPrice = $this->arrayToOneBased($individualPrices); $service->individualPrice = array_combine($mapping, $individualPrices);
if (Service::TYPE_TRANSPORTATION === $type) { if (Service::CATEGORY_TRANSPORTATION === $type) {
$service->direction = (string) $attributes['richtung']; $service->direction = (string) $attributes['richtung'];
} }
@@ -150,7 +158,7 @@ class BookingResponseParser
{ {
$rooms = []; $rooms = [];
foreach ($xml as $item) { foreach ($xml->ferienzielunterbringung as $item) {
$attributes = $item->attributes(); $attributes = $item->attributes();
$id = (int) $attributes['idzimmer']; $id = (int) $attributes['idzimmer'];
@@ -173,10 +181,69 @@ class BookingResponseParser
return $this->stringToFloat((string) $price); return $this->stringToFloat((string) $price);
}, $this->stringToArray((string) $attributes['einzelpreis'], '/') }, $this->stringToArray((string) $attributes['einzelpreis'], '/')
); );
$room->individualPrice = $this->arrayToOneBased($individualPrices); $room->individualPrice = array_combine($mapping, $individualPrices);
$rooms[$id] = $room; $rooms[$id] = $room;
} }
return $rooms; return $rooms;
} }
private function parsePickups(\SimpleXMLElement $xml): array
{
$pickups = [];
foreach ($xml->zustieg as $item) {
$attributes = $item->attributes();
$pickupId = (int) $attributes['idzustieg'];
$pickupDate = (string) $attributes['datum'];
$pickupTime = (string) $attributes['zeit'];
$pickup = new Pickup();
$pickup->id = $pickupId;
$pickup->code = (string) $attributes['code'];
$pickup->city = (string) $xml->ort;
$pickup->postalCode = (string) $xml->plz;
$pickup->street = (string) $xml->strasse;
$pickup->time = $this->stringToDateTime($pickupDate.' '.$pickupTime);
$pickup->price = $attributes['preis'] ? $this->stringToFloat((string) $attributes['preis']) : null;
$mapping = $this->stringToArray((string) $attributes['zuordnung']);
$pickup->mapping = array_map('intval', $mapping);
$pickups[$pickupId] = $pickup;
}
return $pickups;
}
private function parseSurcharges(\SimpleXMLElement $xml): array
{
$surcharges = [];
foreach ($xml->zuschlag as $item) {
$attributes = $item->attributes();
$label = (string) $attributes['bezeichnung'];
// Skip invalid entries
if (empty($label)) {
continue;
}
$surcharge = new Surcharge();
$surcharge->label = $label;
$surcharge->totalPrice = $this->stringToFloat((string) $attributes['gesamtpreis']);
$mapping = $this->stringToArray((string) $attributes['zuordnung']);
$surcharge->mapping = array_map('intval', $mapping);
$individualPrices = array_map(
function ($price) {
return $this->stringToFloat((string) $price);
}, $this->stringToArray((string) $attributes['einzelpreis'], '/')
);
$surcharge->individualPrice = array_combine($mapping, $individualPrices);
$surcharges[] = $surcharge;
}
return $surcharges;
}
} }
@@ -2,8 +2,8 @@
namespace App\BusProNet\ApiResponseParser; namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\CrmAttribute; use App\BusProNet\Model\CrmSelection;
use App\BusProNet\Model\CrmAttributeGroup; use App\BusProNet\Model\CrmSelectionGroup;
use App\BusProNet\Model\CrmAttributes; use App\BusProNet\Model\CrmAttributes;
class CrmAttributesResponseParser class CrmAttributesResponseParser
@@ -21,14 +21,14 @@ class CrmAttributesResponseParser
$hotelCode = null; $hotelCode = null;
foreach ($xml->selektionsmerkmale->selektionsgruppe as $item) { foreach ($xml->selektionsmerkmale->selektionsgruppe as $item) {
$group = new CrmAttributeGroup(); $group = new CrmSelectionGroup();
$group->label = (string) $item->attributes()['bezeichnung']; $group->label = (string) $item->attributes()['bezeichnung'];
$attributes = []; $attributes = [];
foreach ($item->selektion as $subItem) { foreach ($item->selektion as $subItem) {
$subItemAttributes = $subItem->attributes(); $subItemAttributes = $subItem->attributes();
$attributeLabel = (string) $subItemAttributes['bezeichnung']; $attributeLabel = (string) $subItemAttributes['bezeichnung'];
$attribute = new CrmAttribute(); $attribute = new CrmSelection();
$attribute->id = (int) $subItemAttributes['id']; $attribute->id = (int) $subItemAttributes['id'];
$attribute->label = $attributeLabel; $attribute->label = $attributeLabel;
$attribute->selected = $this->stringToBool((string) $subItemAttributes['auswahl']); $attribute->selected = $this->stringToBool((string) $subItemAttributes['auswahl']);
@@ -49,12 +49,12 @@ class CrmAttributesResponseParser
$attributes[] = $attribute; $attributes[] = $attribute;
} }
$group->attributes = $attributes; $group->selections = $attributes;
$groups[] = $group; $groups[] = $group;
} }
$response = new CrmAttributes(); $response = new CrmAttributes();
$response->attributeGroups = $groups; $response->selectionGroups = $groups;
$response->admin = $isAdmin; $response->admin = $isAdmin;
$response->manager = $isManager; $response->manager = $isManager;
$response->houseManager = $isHouseManager; $response->houseManager = $isHouseManager;
@@ -0,0 +1,44 @@
<?php
namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\MutableData;
class MutableDataResponseParser
{
use ResponseParserTrait;
public function parse(\SimpleXMLElement $xml): BaseData
{
$mutableFields = [];
foreach ($xml->änderungen->änderung as $item) {
$attributes = $item->attributes();
$category = match ((string) $attributes['art']) {
'anzahl_teilnehmer' => MutableData::CATEGORY_PARTICIPANT_COUNT,
'beförderung' => MutableData::CATEGORY_TRANSPORTATION,
'zustieg' => MutableData::CATEGORY_PICKUP,
'unterbringung' => MutableData::CATEGORY_ACCOMMODATION,
'zusatzleistung' => MutableData::CATEGORY_ADDITIONAL_SERVICES,
'teilnehmerdaten' => MutableData::CATEGORY_PARTICIPANT_DATA,
default => false,
};
if (false === $category) {
continue;
}
$mutableField = new MutableData($category, $this->stringToBool((string) $attributes['möglich']));
if ($attributes['möglichbiszum']) {
$mutableField->mutableBefore = $this->stringToDate((string) $attributes['möglichbiszum']);
}
$mutableFields[$category] = $mutableField;
}
return new BaseData($mutableFields);
}
}
@@ -1,44 +0,0 @@
<?php
namespace App\BusProNet\ApiResponseParser;
use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\MutableField;
class MutableFieldsResponseParser
{
use ResponseParserTrait;
public function parse(\SimpleXMLElement $xml): BaseData
{
$mutableFields = [];
foreach ($xml->änderungen->änderung as $item) {
$attributes = $item->attributes();
$type = match ((string) $attributes['art']) {
'anzahl_teilnehmer' => 'participant_count',
'beförderung' => 'transportation',
'zustieg' => 'pickup',
'unterbringung' => 'accommodation',
'zusatzleistung' => 'services',
'teilnehmerdaten' => 'participant_data',
default => false,
};
if (false === $type) {
continue;
}
$mutableField = new MutableField($type, $this->stringToBool((string) $attributes['möglich']));
if ($attributes['möglichbiszum']) {
$mutableField->mutableBefore = $this->stringToDate((string) $attributes['möglichbiszum']);
}
$mutableFields[$type] = $mutableField;
}
return new BaseData($mutableFields);
}
}
@@ -14,6 +14,10 @@ class ResponseParser
{ {
$xml = simplexml_load_string($content); $xml = simplexml_load_string($content);
if (false === $xml) {
throw new ResponseParserException('Empty response received from server');
}
// Override type when present in XML to catch error responses // Override type when present in XML to catch error responses
$responseType = $type; $responseType = $type;
$responseTypeXml = $xml->xpath('satz/@typ'); $responseTypeXml = $xml->xpath('satz/@typ');
@@ -45,8 +49,8 @@ class ResponseParser
break; break;
case ApiClient::TYPE_BASE_DATA_COUNTRIES: case ApiClient::TYPE_BASE_DATA_COUNTRIES:
return (new CountriesResponseParser())->parse($xml); return (new CountriesResponseParser())->parse($xml);
case ApiClient::TYPE_MUTABLE_FIELDS: case ApiClient::TYPE_MUTABLE_DATA:
return (new MutableFieldsResponseParser())->parse($xml); return (new MutableDataResponseParser())->parse($xml);
case ApiClient::TYPE_AVAILABILITY: case ApiClient::TYPE_AVAILABILITY:
return (new AvailabilitiesResponseParser())->parse($xml); return (new AvailabilitiesResponseParser())->parse($xml);
case ApiClient::TYPE_BOOKING_UPDATE: case ApiClient::TYPE_BOOKING_UPDATE:
@@ -52,7 +52,7 @@ class PickupDataLoader extends AbstractDataLoader
return $pickup; return $pickup;
} }
public function enrichPickupsData(Travel $travel): void public function patchPickupsDetails(Travel $travel): void
{ {
foreach ($travel->pickups as $pickupId => $pickup) { foreach ($travel->pickups as $pickupId => $pickup) {
$pickupData = $this->loadById($pickupId); $pickupData = $this->loadById($pickupId);
+36 -6
View File
@@ -3,8 +3,9 @@
namespace App\BusProNet\DataLoader; namespace App\BusProNet\DataLoader;
use App\BusProNet\Model\BaseData; use App\BusProNet\Model\BaseData;
use App\BusProNet\Model\CrmAttribute; use App\BusProNet\Model\CrmSelection;
use App\BusProNet\Model\CrmAttributeGroup; use App\BusProNet\Model\CrmSelectionGroup;
use App\BusProNet\Model\MutableData;
use App\BusProNet\Model\Pickup; use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Room; use App\BusProNet\Model\Room;
use App\BusProNet\Model\Service; use App\BusProNet\Model\Service;
@@ -71,17 +72,17 @@ class TravelDataLoader extends AbstractDataLoader
foreach ($xml->selektiongruppe as $item) { foreach ($xml->selektiongruppe as $item) {
$groupId = (int) $item->attributes()['idbuspro']; $groupId = (int) $item->attributes()['idbuspro'];
$selectionGroup = new CrmAttributeGroup(); $selectionGroup = new CrmSelectionGroup();
$selectionGroup->id = $groupId; $selectionGroup->id = $groupId;
$selectionGroup->label = (string) $item->attributes()['bezeichnung']; $selectionGroup->label = (string) $item->attributes()['bezeichnung'];
foreach ($item->selektion as $subItem) { foreach ($item->selektion as $subItem) {
$selectionId = (int) $subItem->attributes()['idbuspro']; $selectionId = (int) $subItem->attributes()['idbuspro'];
$selection = new CrmAttribute(); $selection = new CrmSelection();
$selection->id = $selectionId; $selection->id = $selectionId;
$selection->label = (string) $subItem->attributes()['bezeichnung']; $selection->label = (string) $subItem->attributes()['bezeichnung'];
$selectionGroups[$groupId]['selections'][$selectionId] = (string) $subItem->attributes()['bezeichnung']; $selectionGroups[$groupId]['selections'][$selectionId] = (string) $subItem->attributes()['bezeichnung'];
$selectionGroup->attributes[] = $selection; $selectionGroup->selections[] = $selection;
} }
$selectionGroups[$groupId] = $selectionGroup; $selectionGroups[$groupId] = $selectionGroup;
@@ -99,6 +100,7 @@ class TravelDataLoader extends AbstractDataLoader
$serviceId = (int) $attributes['idbuspro']; $serviceId = (int) $attributes['idbuspro'];
$service = new Service(); $service = new Service();
$service->category = Service::CATEGORY_ADDITIONAL;
$service->id = $serviceId; $service->id = $serviceId;
$service->subType = (string) $attributes['unterart']; $service->subType = (string) $attributes['unterart'];
$service->mandatory = $this->stringToBool((string) $attributes['pflicht']); $service->mandatory = $this->stringToBool((string) $attributes['pflicht']);
@@ -123,6 +125,7 @@ class TravelDataLoader extends AbstractDataLoader
$serviceId = (int) $attributes['idbuspro']; $serviceId = (int) $attributes['idbuspro'];
$service = new Service(); $service = new Service();
$service->category = Service::CATEGORY_TRANSPORTATION;
$service->id = $serviceId; $service->id = $serviceId;
$service->subType = (string) $attributes['unterart']; $service->subType = (string) $attributes['unterart'];
$service->dateFrom = $this->stringToDate((string) $attributes['termin']); $service->dateFrom = $this->stringToDate((string) $attributes['termin']);
@@ -183,7 +186,34 @@ class TravelDataLoader extends AbstractDataLoader
return $rooms; return $rooms;
} }
public function enrichServicesData(Travel $travel, BaseData $availabilities): void public function patchMutability(Travel $travel, BaseData $mutableData): void
{
if (null !== $item = $mutableData->getItemByKey(MutableData::CATEGORY_ADDITIONAL_SERVICES)) {
$travel->additionalServicesMutable = $item->mutable;
}
if (null !== $item = $mutableData->getItemByKey(MutableData::CATEGORY_TRANSPORTATION)) {
$travel->transportationServicesMutable = $item->mutable;
}
if (null !== $item = $mutableData->getItemByKey(MutableData::CATEGORY_PICKUP)) {
$travel->pickupsMutable = $item->mutable;
}
if (null !== $item = $mutableData->getItemByKey(MutableData::CATEGORY_ACCOMMODATION)) {
$travel->roomsMutable = $item->mutable;
}
if (null !== $item = $mutableData->getItemByKey(MutableData::CATEGORY_PARTICIPANT_COUNT)) {
$travel->participantCountMutable = $item->mutable;
}
if (null !== $item = $mutableData->getItemByKey(MutableData::CATEGORY_PARTICIPANT_DATA)) {
$travel->participantDataMutable = $item->mutable;
}
}
public function patchAvailabilities(Travel $travel, BaseData $availabilities): void
{ {
$serviceAvailabilities = $availabilities->getItems(); $serviceAvailabilities = $availabilities->getItems();
@@ -0,0 +1,156 @@
<?php
namespace App\BusProNet\DataProcessor;
use App\Form\Model\BookingData;
class BookingDataProcessor
{
public function createUpdateRequestPayload(?BookingData $formData): array
{
$bookingData = $formData->booking;
$travelData = $formData->travel;
// Reset mappings
foreach ([...$bookingData->additionalServices, ...$bookingData->transportationServices, ...$bookingData->pickups] as $service) {
$service->mapping = [];
}
// Update mappings, add services and pickups
foreach ($formData->participants as $participant) {
$servicesToMap = [
...$participant->courses,
...$participant->additionalServices,
...$participant->skiPass,
...$participant->board,
...$participant->rentals,
];
foreach ($servicesToMap as $service) {
if (false === isset($this->additionalServices[$service->id])) {
$serviceToAdd = $travelData->additionalServices[$service->id];
$bookingData->additionalServices[$service->id] = $serviceToAdd;
$bookingData->additionalServices[$service->id]->individualPrice[$participant->index] = $serviceToAdd->price;
}
$bookingData->additionalServices[$service->id]->mapping[] = $participant->index;
}
foreach ([$participant->transportationServiceTo, $participant->transportationServiceFro] as $service) {
if (false === isset($this->transportationServices[$service->id])) {
$serviceToAdd = $travelData->transportationServices[$service->id];
$bookingData->transportationServices[$service->id] = $serviceToAdd;
$bookingData->transportationServices[$service->id]->individualPrice[$participant->index] = $serviceToAdd->price;
}
$bookingData->transportationServices[$service->id]->mapping[] = $participant->index;
}
if ('BUS' === $participant->transportationServiceTo->subType && null !== $selectedPickup = $participant->pickup) {
if (false === isset($bookingData->pickups[$selectedPickup->id])) {
$bookingData->pickups[$selectedPickup->id] = $selectedPickup;
}
$bookingData->pickups[$selectedPickup->id]->mapping[] = $participant->index;
}
}
// Remove services/pickups with empty mappings
foreach ($bookingData->additionalServices as $service) {
if (0 === count($service->mapping)) {
unset($bookingData->additionalServices[$service->id]);
}
}
foreach ($bookingData->transportationServices as $service) {
if (0 === count($service->mapping)) {
unset($bookingData->transportationServices[$service->id]);
}
}
foreach ($bookingData->pickups as $pickup) {
if (0 === count($pickup->mapping)) {
unset($bookingData->pickups[$pickup->id]);
}
}
// Update participants' personal data
foreach ($formData->participants as $participant) {
$bookingData->participants[$participant->index]->firstName = $participant->firstName;
$bookingData->participants[$participant->index]->lastName = $participant->lastName;
$bookingData->participants[$participant->index]->dateOfBirth = $participant->dateOfBirth;
$bookingData->participants[$participant->index]->gender = $participant->gender;
$bookingData->participants[$participant->index]->nationality = $participant->nationality;
$bookingData->participants[$participant->index]->height = $participant->height;
$bookingData->participants[$participant->index]->weight = $participant->weight;
$bookingData->participants[$participant->index]->shoeSize = $participant->shoeSize;
$bookingData->participants[$participant->index]->communication->email = $participant->email;
$bookingData->participants[$participant->index]->communication->mobile = $participant->mobile;
}
$payload = [
'idbuchung' => $bookingData->id,
'status' => $bookingData->status,
'idagentur' => $bookingData->agencyId,
'idreise' => $bookingData->travelId,
'idpartner' => $bookingData->hotelId,
'anmelder' => $bookingData->applicant->toPayload(),
'zahlung' => [
'@idzahlungsart' => $bookingData->paymentId,
'@bezeichnung' => $bookingData->paymentLabel,
'@art' => $bookingData->paymentType,
],
'teilnehmerliste' => [
'teilnehmer' => [],
],
'zusatzleistungen' => [
'zusatzleistung' => [],
],
'beförderungen' => [
'beförderung' => [],
],
'ferienzielunterbringungen' => [
'ferienzielunterbringung' => [],
],
];
foreach ($bookingData->participants as $index => $participant) {
$payload['teilnehmerliste']['teilnehmer'][] = [
'@id' => $index,
'status' => $bookingData->participantsStatus[$index],
...$participant->toPayload(),
];
}
foreach ($bookingData->additionalServices as $service) {
$payload['zusatzleistungen']['zusatzleistung'][] = [
'@idleistung' => $service->id,
'@anzahl' => count($service->mapping),
'@zuordnung' => implode(',', $service->mapping),
];
}
foreach ($bookingData->transportationServices as $service) {
$payload['beförderungen']['beförderung'][] = [
'@idleistung' => $service->id,
'@anzahl' => count($service->mapping),
'@zuordnung' => implode(',', $service->mapping),
];
}
foreach ($bookingData->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($bookingData->pickups)) {
$payload['zustiege']['zustieg'] = [];
foreach ($bookingData->pickups as $pickup) {
$payload['zustiege']['zustieg'][] = [
'@idzustieg' => $pickup->id,
'@anzahl' => count($pickup->mapping),
'@zuordnung' => implode(',', $pickup->mapping),
];
}
}
return $payload;
}
}
+5
View File
@@ -11,4 +11,9 @@ class BaseData
{ {
return $this->items; return $this->items;
} }
public function getItemByKey(string $key): mixed
{
return $this->items[$key] ?? null;
}
} }
+24 -152
View File
@@ -2,14 +2,11 @@
namespace App\BusProNet\Model; namespace App\BusProNet\Model;
use App\Form\Model\BookingData;
class Booking class Booking
{ {
public ?int $id = null; public ?int $id = null;
public ?int $agencyId = null; public ?int $agencyId = null;
public ?int $bookingNumber = null; public ?int $bookingNumber = null;
public ?Travel $travelData = null;
public ?string $status = null; public ?string $status = null;
public ?PersonalData $applicant = null; public ?PersonalData $applicant = null;
public ?int $participantCount = null; public ?int $participantCount = null;
@@ -32,6 +29,7 @@ class Booking
public array $additionalServices = []; public array $additionalServices = [];
public array $rooms = []; public array $rooms = [];
public array $pickups = []; public array $pickups = [];
public array $surcharges = [];
public ?int $invoiceNumber = null; public ?int $invoiceNumber = null;
public ?float $totalPrice = null; public ?float $totalPrice = null;
@@ -47,6 +45,7 @@ class Booking
public function getAdditionalServicesByGroup(mixed $group): array public function getAdditionalServicesByGroup(mixed $group): array
{ {
$group = (array) $group; $group = (array) $group;
return array_filter($this->additionalServices, function (Service $service) use ($group) { return array_filter($this->additionalServices, function (Service $service) use ($group) {
return in_array($service->subType, $group); return in_array($service->subType, $group);
}); });
@@ -54,9 +53,9 @@ class Booking
public function getAdditionalServicesForParticipantByGroup(int $participantIndex, mixed $group): array public function getAdditionalServicesForParticipantByGroup(int $participantIndex, mixed $group): array
{ {
$services = $this->getAdditionalServicesByGroup($group); $selectedServices = $this->getAdditionalServicesByGroup($group);
return array_filter($services, function (Service $service) use ($participantIndex) { return array_filter($selectedServices, function (Service $service) use ($participantIndex) {
return in_array($participantIndex, $service->mapping); return in_array($participantIndex, $service->mapping);
}); });
} }
@@ -75,6 +74,17 @@ class Booking
return null; return null;
} }
public function getPickupForParticipant(int $participantIndex): ?Pickup
{
foreach ($this->pickups as $pickup) {
if (in_array($participantIndex, $pickup->mapping)) {
return $pickup;
}
}
return null;
}
public function getRoomForParticipant(int $participantIndex): ?Room public function getRoomForParticipant(int $participantIndex): ?Room
{ {
foreach ($this->rooms as $room) { foreach ($this->rooms as $room) {
@@ -90,21 +100,22 @@ class Booking
{ {
$price = 0.0; $price = 0.0;
// Sum up all services
foreach ([...$this->transportationServices, ...$this->additionalServices] as $service) { foreach ([...$this->transportationServices, ...$this->additionalServices] as $service) {
if (in_array($participantIndex, $service->mapping) && isset($service->individualPrice[$participantIndex])) { if (in_array($participantIndex, $service->mapping) || true === $service->mandatory) {
$price += $service->individualPrice[$participantIndex]; $price += $service->individualPrice[$participantIndex];
} elseif ($service->price) { }
$price += $service->price; }
// Sum up all surcharges
foreach ($this->surcharges as $surcharge) {
if (in_array($participantIndex, $surcharge->mapping)) {
$price += $surcharge->individualPrice[$participantIndex];
} }
} }
$room = $this->getRoomForParticipant($participantIndex); $room = $this->getRoomForParticipant($participantIndex);
if (isset($room->individualPrice[$participantIndex])) {
$price += $room->individualPrice[$participantIndex]; $price += $room->individualPrice[$participantIndex];
} elseif ($room->price) {
$price += $room->price;
}
return $price; return $price;
} }
@@ -113,143 +124,4 @@ class Booking
{ {
return $this->travelDate > new \DateTimeImmutable() && false === in_array($this->status, ['S', 'U']); return $this->travelDate > new \DateTimeImmutable() && false === in_array($this->status, ['S', 'U']);
} }
public function toPayload(?BookingData $formData): array
{
// Reset mappings
foreach ([...$this->additionalServices, ...$this->transportationServices, ...$this->pickups] as $service) {
$service->mapping = [];
}
// Update mappings, add services and pickups
foreach ($formData->participants as $participant) {
$servicesToMap = [
...$participant->courses,
...$participant->additionalServices,
...$participant->skiPass,
...$participant->board,
...$participant->rentals,
];
foreach ($servicesToMap as $service) {
if (false === isset($this->additionalServices[$service->id])) {
$serviceToAdd = $this->travelData->additionalServices[$service->id];
$this->additionalServices[$service->id] = $serviceToAdd;
$this->additionalServices[$service->id]->individualPrice[$participant->index] = $serviceToAdd->price;
}
$this->additionalServices[$service->id]->mapping[] = $participant->index;
}
foreach ([$participant->transportationServiceTo, $participant->transportationServiceFro] as $service) {
if (false === isset($this->transportationServices[$service->id])) {
$serviceToAdd = $this->travelData->transportationServices[$service->id];
$this->transportationServices[$service->id] = $serviceToAdd;
$this->transportationServices[$service->id]->individualPrice[$participant->index] = $serviceToAdd->price;
}
$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) {
if (0 === count($service->mapping)) {
unset($this->additionalServices[$service->id]);
}
}
foreach ($this->transportationServices as $service) {
if (0 === count($service->mapping)) {
unset($this->transportationServices[$service->id]);
}
}
// Update participants' personal data
foreach ($formData->participants as $participant) {
$this->participants[$participant->index]->firstName = $participant->firstName;
$this->participants[$participant->index]->lastName = $participant->lastName;
$this->participants[$participant->index]->dateOfBirth = $participant->dateOfBirth;
$this->participants[$participant->index]->gender = $participant->gender;
$this->participants[$participant->index]->nationality = $participant->nationality;
$this->participants[$participant->index]->height = $participant->height;
$this->participants[$participant->index]->weight = $participant->weight;
$this->participants[$participant->index]->shoeSize = $participant->shoeSize;
$this->participants[$participant->index]->communication->email = $participant->email;
$this->participants[$participant->index]->communication->mobile = $participant->mobile;
}
$payload = [
'idbuchung' => $this->id,
'status' => $this->status,
'idagentur' => $this->agencyId,
'idreise' => $this->travelId,
'idpartner' => $this->hotelId,
'anmelder' => $this->applicant->toPayload(),
'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;
}
} }
-10
View File
@@ -1,10 +0,0 @@
<?php
namespace App\BusProNet\Model;
class CrmAttributeGroup
{
public ?int $id = null;
public ?string $label = null;
public ?array $attributes = null;
}
+5 -5
View File
@@ -4,7 +4,7 @@ namespace App\BusProNet\Model;
class CrmAttributes class CrmAttributes
{ {
public ?array $attributeGroups = null; public ?array $selectionGroups = null;
public bool $admin = false; public bool $admin = false;
public bool $manager = false; public bool $manager = false;
public bool $houseManager = false; public bool $houseManager = false;
@@ -15,13 +15,13 @@ class CrmAttributes
{ {
$crmSelections = []; $crmSelections = [];
foreach ($this->attributeGroups as $group) { foreach ($this->selectionGroups as $group) {
/** @var CrmAttributeGroup $group */ /** @var CrmSelectionGroup $group */
if (false === isset($crmSelections[$group->label])) { if (false === isset($crmSelections[$group->label])) {
$crmSelections[$group->label] = []; $crmSelections[$group->label] = [];
} }
foreach ($group->attributes as $attribute) { foreach ($group->selections as $attribute) {
/** @var CrmAttribute $attribute */ /** @var CrmSelection $attribute */
if (false === $attribute->selected) { if (false === $attribute->selected) {
continue; continue;
} }
@@ -2,7 +2,7 @@
namespace App\BusProNet\Model; namespace App\BusProNet\Model;
class CrmAttribute class CrmSelection
{ {
public ?int $id = null; public ?int $id = null;
public ?string $label = null; public ?string $label = null;
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\BusProNet\Model;
class CrmSelectionGroup
{
/**
* Maps BusPro IDs of CRM selection groups representing
* included services
* @var array|string[]
*/
public static array $includedServicesMapping = [
7 => 'Skipass',
71 => 'Verpflegung',
85 => 'Übernachtungen',
86 => 'Programm',
];
public ?int $id = null;
public ?string $label = null;
public ?array $selections = null;
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\BusProNet\Model;
class MutableData
{
public const CATEGORY_PARTICIPANT_COUNT = 'participant_count';
public const CATEGORY_PARTICIPANT_DATA = 'participant_data';
public const CATEGORY_TRANSPORTATION = 'transportation';
public const CATEGORY_PICKUP = 'pickup';
public const CATEGORY_ACCOMMODATION = 'accommodation';
public const CATEGORY_ADDITIONAL_SERVICES = 'additional_services';
public function __construct(string $category, bool $mutable, ?\DateTimeImmutable $mutableUntil = null)
{
$this->category = $category;
$this->mutable = $mutable;
$this->mutableBefore = $mutableUntil;
}
public ?string $category = null;
public bool $mutable = false;
public ?\DateTimeImmutable $mutableBefore = null;
}
-17
View File
@@ -1,17 +0,0 @@
<?php
namespace App\BusProNet\Model;
class MutableField
{
public function __construct(string $type, bool $mutable, ?\DateTimeImmutable $mutableUntil = null)
{
$this->type = $type;
$this->mutable = $mutable;
$this->mutableBefore = $mutableUntil;
}
public ?string $type = null;
public bool $mutable = false;
public ?\DateTimeImmutable $mutableBefore = null;
}
+3 -3
View File
@@ -4,9 +4,8 @@ namespace App\BusProNet\Model;
class Service class Service
{ {
public const TYPE_INCLUDED = 'included'; public const CATEGORY_ADDITIONAL = 'additional';
public const TYPE_ADDITIONAL = 'additional'; public const CATEGORY_TRANSPORTATION = 'transportation';
public const TYPE_TRANSPORTATION = 'transportation';
public const TOKEN_COURSES = 'KUR'; public const TOKEN_COURSES = 'KUR';
public const TOKEN_SKI_PASS = 'SPA'; public const TOKEN_SKI_PASS = 'SPA';
@@ -15,6 +14,7 @@ class Service
public const TOKEN_RENTALS = ['VER', 'VE2', 'VE3', 'VE4', 'VE5', 'VE6', 'VE7', 'VE8']; public const TOKEN_RENTALS = ['VER', 'VE2', 'VE3', 'VE4', 'VE5', 'VE6', 'VE7', 'VE8'];
public ?int $id = null; public ?int $id = null;
public ?string $category = null;
public ?string $status = null; public ?string $status = null;
public bool $mandatory = false; public bool $mandatory = false;
public ?string $label = null; public ?string $label = null;
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\BusProNet\Model;
class Surcharge
{
public ?string $label = null;
public array $mapping = [];
public ?float $totalPrice = null;
public array $individualPrice = [];
}
+19
View File
@@ -13,9 +13,15 @@ class Travel
public ?float $priceFrom = null; public ?float $priceFrom = null;
public array $selectionGroups = []; public array $selectionGroups = [];
public array $additionalServices = []; public array $additionalServices = [];
public bool $additionalServicesMutable = true;
public array $transportationServices = []; public array $transportationServices = [];
public bool $transportationServicesMutable = true;
public array $pickups = []; public array $pickups = [];
public bool $pickupsMutable = true;
public array $rooms = []; public array $rooms = [];
public bool $roomsMutable = true;
public bool $participantDataMutable = true;
public bool $participantCountMutable = true;
public function getAdditionalServicesByGroup(mixed $group, bool $availableOnly = true): array public function getAdditionalServicesByGroup(mixed $group, bool $availableOnly = true): array
{ {
@@ -34,4 +40,17 @@ class Travel
&& (false === $availableOnly || $service->available > 0); && (false === $availableOnly || $service->available > 0);
}); });
} }
public function getIncludedServices(): array
{
$services = [];
foreach (CrmSelectionGroup::$includedServicesMapping as $id => $label) {
if (isset($this->selectionGroups[$id])) {
$services[] = $this->selectionGroups[$id];
}
}
return $services;
}
} }
+22 -13
View File
@@ -35,11 +35,17 @@ class EditController extends AbstractController
return $this->security->logout(); return $this->security->logout();
} }
$booking = $this->apiClient->getBooking($bpnUser->getEmail(), $bpnUser->getPassword(), $id); // Fetch original bookingData data via API
$bookingData = $this->apiClient->getBooking($bpnUser->getEmail(), $bpnUser->getPassword(), $id);
//$this->denyAccessUnlessGranted('VIEW', $booking); if (null === $bookingData) {
$this->addFlash('error', 'Buchungsdaten nicht (mehr) verfügbar');
$travelData = $this->travelDataLoader->loadById($booking->travelId); return $this->redirectToRoute('app_bookings');
}
// Load according travel data
$travelData = $this->travelDataLoader->loadById($bookingData->travelId);
if (null === $travelData) { if (null === $travelData) {
$this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar'); $this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar');
@@ -47,22 +53,25 @@ class EditController extends AbstractController
return $this->redirectToRoute('app_bookings'); return $this->redirectToRoute('app_bookings');
} }
$booking->travelData = $travelData; // Fetch mutability information via API
$mutableFields = $this->apiClient->getMutableFields($booking->travelId); $mutableData = $this->apiClient->getMutableData($bookingData->travelId);
$availabilities = $this->apiClient->getAvailabilities($booking->travelId);
$this->travelDataLoader->enrichServicesData($travelData, $availabilities); // Fetch availability information via API
$this->pickupDataLoader->enrichPickupsData($travelData); $availabilities = $this->apiClient->getAvailabilities($bookingData->travelId);
$formData = BookingData::fromBooking($booking); // Patch travel data with additional information from above
$this->travelDataLoader->patchAvailabilities($travelData, $availabilities);
$this->travelDataLoader->patchMutability($travelData, $mutableData);
$this->pickupDataLoader->patchPickupsDetails($travelData);
// Create DTO for form
$formData = BookingData::fromBooking($bookingData, $travelData);
$form = $this->createForm(BookingType::class, $formData, [ $form = $this->createForm(BookingType::class, $formData, [
'hx_post' => $this->generateUrl('app_booking_edit', ['id' => $id]), 'hx_post' => $this->generateUrl('app_booking_edit', ['id' => $id]),
'hx_target' => '#app', 'hx_target' => '#app',
'hx_swap' => 'innerHTML show:top', 'hx_swap' => 'innerHTML show:top',
'attr' => ['novalidate' => 'novalidate'], 'attr' => ['novalidate' => 'novalidate'],
'travel' => $travelData,
'mutable_fields' => $mutableFields,
]); ]);
$form->handleRequest($request); $form->handleRequest($request);
@@ -76,9 +85,9 @@ class EditController extends AbstractController
} }
return $this->render('booking/edit.html.twig', [ return $this->render('booking/edit.html.twig', [
'booking' => $booking, 'bookingData' => $bookingData,
'travelData' => $travelData, 'travelData' => $travelData,
'mutableFields' => $mutableFields, 'mutableData' => $mutableData,
'availabilities' => $availabilities, 'availabilities' => $availabilities,
'form' => $form->createView(), 'form' => $form->createView(),
]); ]);
+24 -16
View File
@@ -3,45 +3,53 @@
namespace App\Form; namespace App\Form;
use App\BusProNet\Model\Service; use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use App\Form\Model\BookingData; use App\Form\Model\BookingData;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
final class BookingType extends AbstractType final class BookingType extends AbstractType
{ {
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
$builder->add('participants', CollectionType::class, [ $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
/** @var BookingData $data */
$data = $event->getData();
$form = $event->getForm();
$travelData = $data->travel;
$form->add('participants', CollectionType::class, [
'entry_type' => ParticipantType::class, 'entry_type' => ParticipantType::class,
'entry_options' => [ 'entry_options' => [
'selectable_courses' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_COURSES), 'selectable_courses' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_COURSES),
'selectable_ski_passes' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_SKI_PASS), 'selectable_ski_passes' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_SKI_PASS),
'selectable_services' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_ADDITIONAL), 'selectable_services' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_ADDITIONAL),
'selectable_board' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_BOARD), 'selectable_board' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_BOARD),
'selectable_rentals' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_RENTALS), 'selectable_rentals' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_RENTALS),
'selectable_transportation_services_to' => $options['travel'] 'selectable_transportation_services_to' => $travelData
->getTransportationServicesByDirection('HIN'), ->getTransportationServicesByDirection('HIN'),
'selectable_transportation_services_fro' => $options['travel'] 'selectable_transportation_services_fro' => $travelData
->getTransportationServicesByDirection('RUECK'), ->getTransportationServicesByDirection('RUECK'),
'selectable_pickups' => $options['travel']->pickups, 'selectable_pickups' => $travelData->pickups,
'personal_data_mutable' => $travelData->participantDataMutable,
'additional_services_mutable' => $travelData->additionalServicesMutable,
'transportation_services_mutable' => $travelData->transportationServicesMutable,
'pickups_mutable' => $travelData->pickupsMutable,
], ],
'allow_add' => false, 'allow_add' => false,
'allow_delete' => false, 'allow_delete' => false,
]); ]);
});
} }
public function configureOptions(OptionsResolver $resolver): void public function configureOptions(OptionsResolver $resolver): void
{ {
$resolver $resolver
->setDefaults([ ->setDefaults(['data_class' => BookingData::class])
'data_class' => BookingData::class,
'mutable_fields' => [],
])
->setRequired(['travel'])
->setAllowedTypes('travel', Travel::class)
; ;
} }
} }
+8 -2
View File
@@ -5,22 +5,26 @@ namespace App\Form\Model;
use App\BusProNet\Model\Booking; use App\BusProNet\Model\Booking;
use App\BusProNet\Model\PersonalData; use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\Service; use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use App\Validator\Constraints as AppAssert; use App\Validator\Constraints as AppAssert;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
#[AppAssert\Booking] #[Assert\GroupSequence(['BookingData', 'Api'])]
#[AppAssert\Booking(groups:['Api'])]
class BookingData class BookingData
{ {
public ?Booking $booking = null; public ?Booking $booking = null;
public ?Travel $travel = null;
#[Assert\Valid] #[Assert\Valid]
public array $participants = []; public array $participants = [];
public static function fromBooking(Booking $booking): static public static function fromBooking(Booking $booking, Travel $travel): static
{ {
$instance = new static(); $instance = new static();
$instance->booking = $booking; $instance->booking = $booking;
$instance->travel = $travel;
foreach ($booking->participants as $index => $participant) { foreach ($booking->participants as $index => $participant) {
/** @var PersonalData $participant */ /** @var PersonalData $participant */
@@ -41,6 +45,8 @@ class BookingData
->getTransportationServiceForParticipantAndDirection($index, 'H'); ->getTransportationServiceForParticipantAndDirection($index, 'H');
$participantData->transportationServiceFro = $booking $participantData->transportationServiceFro = $booking
->getTransportationServiceForParticipantAndDirection($index, 'R'); ->getTransportationServiceForParticipantAndDirection($index, 'R');
$participantData->pickup = $booking->getPickupForParticipant($index);
$instance->participants[$index] = $participantData; $instance->participants[$index] = $participantData;
} }
+45 -14
View File
@@ -21,6 +21,7 @@ class ParticipantType extends AbstractType
{ {
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
if (true === $options['personal_data_mutable']) {
$builder $builder
->add('firstName', TextType::class, [ ->add('firstName', TextType::class, [
'label' => 'Vorname', 'label' => 'Vorname',
@@ -67,6 +68,10 @@ class ParticipantType extends AbstractType
'label' => 'Gewicht [kg]', 'label' => 'Gewicht [kg]',
'required' => false, 'required' => false,
]) ])
;
}
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) { ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) {
/** @var ParticipantData $participantData */ /** @var ParticipantData $participantData */
$participantData = $event->getData(); $participantData = $event->getData();
@@ -104,32 +109,49 @@ class ParticipantType extends AbstractType
}, },
]; ];
$form // Additional services
->add('courses', ChoiceType::class, [ if (true === $options['additional_services_mutable']) {
if (0 < count($options['selectable_courses'])) {
$form->add('courses', ChoiceType::class, [
...$commonChoiceFieldOptions, ...$commonChoiceFieldOptions,
'label' => 'Kurse', 'label' => 'Kurse',
'choices' => $options['selectable_courses'], 'choices' => $options['selectable_courses'],
]) ]);
->add('additionalServices', ChoiceType::class, [ }
if (0 < count($options['selectable_services'])) {
$form->add('additionalServices', ChoiceType::class, [
...$commonChoiceFieldOptions, ...$commonChoiceFieldOptions,
'label' => 'Zusatzleistungen', 'label' => 'Zusatzleistungen',
'choices' => $options['selectable_services'], 'choices' => $options['selectable_services'],
]) ]);
->add('skiPass', ChoiceType::class, [ }
if (0 < count($options['selectable_ski_passes'])) {
$form->add('skiPass', ChoiceType::class, [
...$commonChoiceFieldOptions, ...$commonChoiceFieldOptions,
'label' => 'Skipass', 'label' => 'Skipass',
'choices' => $options['selectable_ski_passes'], 'choices' => $options['selectable_ski_passes'],
]) ]);
->add('board', ChoiceType::class, [ }
if (0 < count($options['selectable_board'])) {
$form->add('board', ChoiceType::class, [
...$commonChoiceFieldOptions, ...$commonChoiceFieldOptions,
'label' => 'Verpflegung', 'label' => 'Verpflegung',
'choices' => $options['selectable_board'], 'choices' => $options['selectable_board'],
]) ]);
->add('rentals', ChoiceType::class, [ }
if (0 < count($options['selectable_rentals'])) {
$form->add('rentals', ChoiceType::class, [
...$commonChoiceFieldOptions, ...$commonChoiceFieldOptions,
'label' => 'Verleih', 'label' => 'Verleih',
'choices' => $options['selectable_rentals'], 'choices' => $options['selectable_rentals'],
]) ]);
}
}
// Transportation
if (true === $options['transportation_services_mutable']) {
$form
->add('transportationServiceTo', ChoiceType::class, [ ->add('transportationServiceTo', ChoiceType::class, [
...$commonChoiceFieldOptions, ...$commonChoiceFieldOptions,
'label' => 'Anreise', 'label' => 'Anreise',
@@ -141,7 +163,12 @@ class ParticipantType extends AbstractType
'label' => 'Rückreise', 'label' => 'Rückreise',
'multiple' => false, 'multiple' => false,
'choices' => $options['selectable_transportation_services_fro'], 'choices' => $options['selectable_transportation_services_fro'],
]) ]);
}
// Pickup
if (true === $options['pickups_mutable']) {
$form
->add('pickup', ChoiceType::class, [ ->add('pickup', ChoiceType::class, [
'label' => 'Zustieg (bei Busanreise)', 'label' => 'Zustieg (bei Busanreise)',
'multiple' => false, 'multiple' => false,
@@ -164,8 +191,8 @@ class ParticipantType extends AbstractType
number_format($price, 2, ',', '.') number_format($price, 2, ',', '.')
); );
}, },
]) ]);
; }
}) })
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) { ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
$data = $event->getData(); $data = $event->getData();
@@ -194,6 +221,10 @@ class ParticipantType extends AbstractType
'selectable_transportation_services_to' => [], 'selectable_transportation_services_to' => [],
'selectable_transportation_services_fro' => [], 'selectable_transportation_services_fro' => [],
'selectable_pickups' => [], 'selectable_pickups' => [],
'personal_data_mutable' => true,
'additional_services_mutable' => true,
'transportation_services_mutable' => true,
'pickups_mutable' => true,
]); ]);
} }
} }
+135 -34
View File
@@ -10,65 +10,166 @@
{{ form_errors(form) }} {{ form_errors(form) }}
</article> </article>
{% endif %} {% endif %}
<article>
<header>
<strong>Reisedaten</strong>
</header>
<table>
<tbody>
<tr>
<th>
<strong>Reise</strong>
</th>
<td>
{{ bookingData.travel }}
</td>
</tr>
<tr>
<th>
<strong>Reiseedatum</strong>
</th>
<td>
{{ bookingData.travelDate|date('d.m.Y') }}
</td>
</tr>
<tr>
<th>
<strong>Buchungsdatum</strong>
</th>
<td>
{{ bookingData.bookingDate|date('d.m.Y') }}
</td>
</tr>
<tr>
<th>
<strong>Inklusivleistungen</strong>
</th>
<td>
<ul>
{% for selectionGroup in travelData.includedServices %}
{% for service in selectionGroup.selections %}
<li>
{{ service.label }}
</li>
{% endfor %}
{% endfor %}
</ul>
</td>
</tr>
</tbody>
</table>
</article>
{% for child in form.participants %} {% for child in form.participants %}
{% set participant = child.vars.data %} {% set participant = child.vars.data %}
<details{% if not child.vars.valid %} open{% endif %}> <details{% if not child.vars.valid %} open{% endif %}>
<summary role="button" class="outline"> <summary role="button" class="outline">
Teilnehmer {{ participant.index }} Teilnehmer {{ participant.index }}
</summary> </summary>
<div class="grid"> <article>
<div> <header>
<strong> <strong>Persönliche Daten</strong>
Unterkunft </header>
</strong>
<br>
{{ booking.roomForParticipant(participant.index).label }}
</div>
<div>
<strong>
Preis
</strong>
<br>
{{ booking.priceForParticipant(participant.index)|format_currency('EUR') }}
</div>
</div>
<hr>
<fieldset class="grid"> <fieldset class="grid">
{% if travelData.participantDataMutable %}
{{ form_row(child.firstName) }} {{ form_row(child.firstName) }}
{{ form_row(child.lastName) }} {{ form_row(child.lastName) }}
{{ form_row(child.gender) }} {{ form_row(child.gender) }}
{% else %}
Teilnehmerdaten nicht mehr änderbar
{% endif %}
</fieldset> </fieldset>
<fieldset class="grid"> <fieldset class="grid">
{% if travelData.participantDataMutable %}
{{ form_row(child.dateOfBirth) }} {{ form_row(child.dateOfBirth) }}
{{ form_row(child.nationality) }} {{ form_row(child.nationality) }}
{% else %}
Teilnehmerdaten nicht mehr änderbar
{% endif %}
</fieldset> </fieldset>
<fieldset class="grid"> <fieldset class="grid">
{% if travelData.participantDataMutable %}
{{ form_row(child.email) }} {{ form_row(child.email) }}
{{ form_row(child.mobile) }} {{ form_row(child.mobile) }}
{% else %}
Teilnehmerdaten nicht mehr änderbar
{% endif %}
</fieldset> </fieldset>
<fieldset class="grid"> <fieldset class="grid">
{% if travelData.participantDataMutable %}
{{ form_row(child.height) }} {{ form_row(child.height) }}
{{ form_row(child.shoeSize) }} {{ form_row(child.shoeSize) }}
{{ form_row(child.weight) }} {{ form_row(child.weight) }}
</fieldset> {% else %}
<fieldset class="grid"> Teilnehmerdaten nicht mehr änderbar
{{ form_row(child.courses) }}
{{ form_row(child.skiPass) }}
</fieldset>
<fieldset class="grid">
{{ form_row(child.additionalServices) }}
{{ form_row(child.board) }}
</fieldset>
<fieldset class="grid">
{{ form_row(child.rentals) }}
</fieldset>
<fieldset class="grid">
{{ form_row(child.transportationServiceTo) }}
{{ form_row(child.transportationServiceFro) }}
{% if child.pickup is defined %}
{{ form_row(child.pickup) }}
{% endif %} {% endif %}
</fieldset> </fieldset>
</article>
<article>
<header>
<strong>Unterkunft</strong>
</header>
{% set room = bookingData.roomForParticipant(participant.index) %}
{{ room.label }} ({{ room.individualPrice[participant.index]|format_currency('EUR') }})
</article>
<article>
<header>
<strong>Leistungen</strong>
</header>
<fieldset class="grid">
{% if travelData.additionalServicesMutable %}
{% if child.courses is defined %}
{{ form_row(child.courses) }}
{% endif %}
{% if child.skiPass is defined %}
{{ form_row(child.skiPass) }}
{% endif %}
{% else %}
Leistungen nicht mehr änderbar
{% endif %}
</fieldset>
<fieldset class="grid">
{% if travelData.additionalServicesMutable %}
{% if child.additionalServices is defined %}
{{ form_row(child.additionalServices) }}
{% endif %}
{% if child.board is defined %}
{{ form_row(child.board) }}
{% endif %}
{% else %}
Leistungen nicht mehr änderbar
{% endif %}
</fieldset>
<fieldset class="grid">
{% if travelData.additionalServicesMutable %}
{% if child.rentals is defined %}
{{ form_row(child.rentals) }}
{% endif %}
{% else %}
Leistungen nicht mehr änderbar
{% endif %}
</fieldset>
<fieldset class="grid">
{% if travelData.additionalServicesMutable %}
{{ form_row(child.transportationServiceTo) }}
{{ form_row(child.transportationServiceFro) }}
{% else %}
Beförderungsleistung nicht mehr änderbar
{% endif %}
{% if child.pickup is defined %}
{% if travelData.pickupsMutable %}
{{ form_row(child.pickup) }}
{% else %}
Zustieg nicht mehr änderbar
{% endif %}
{% endif %}
</fieldset>
</article>
<article>
<header>
<strong>Gesamtpreis</strong>
</header>
{{ bookingData.priceForParticipant(participant.index)|format_currency('EUR') }}
</article>
</details> </details>
{% endfor %} {% endfor %}
{{ form_rest(form) }} {{ form_rest(form) }}