diff --git a/public/embed.html b/public/embed.html deleted file mode 100644 index 4c33dde..0000000 --- a/public/embed.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - Welcome! - - - - - - - - - - - - - - - -
- - diff --git a/src/BusProNet/ApiClient.php b/src/BusProNet/ApiClient.php index 4ca50d2..06e25e3 100644 --- a/src/BusProNet/ApiClient.php +++ b/src/BusProNet/ApiClient.php @@ -3,6 +3,7 @@ namespace App\BusProNet; use App\BusProNet\ApiResponseParser\ResponseParser; +use App\BusProNet\DataProcessor\BookingDataProcessor; use App\BusProNet\Exception\ApiClientException; use App\BusProNet\Model\BaseData; use App\BusProNet\Model\Booking; @@ -24,7 +25,7 @@ class ApiClient public const TYPE_NOTIFICATION = 'HINWEIS'; public const TYPE_CUSTOMER_DATA = 'KUNDENKONTO'; 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_BOOKING_UPDATE = 'BUCHUNGAENDERUNG'; @@ -113,15 +114,15 @@ class ApiClient public function updateBooking(BookingData $formData, bool $dryRun = true): Notification|BookingUpdate { - $booking = $formData->booking; $mode = $dryRun ? 'Anfrage' : 'Buchung'; + $payload = (new BookingDataProcessor())->createUpdateRequestPayload($formData); $data = [ '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), + ...$payload, ]; return $this->sendRequest(static::TYPE_BOOKING_UPDATE, $data); @@ -130,17 +131,17 @@ class ApiClient /** * @throws ApiClientException */ - public function getMutableFields(int $id): Notification|BaseData + public function getMutableData(int $id): Notification|BaseData { $data = [ '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], + 'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_MUTABLE_DATA), + 'satz' => ['@typ' => static::TYPE_MUTABLE_DATA], 'art' => 'Vorgang_Details', '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 { - $requestId = Uuid::v7(); + $requestId = date(DATE_ATOM).uniqid(); $body = $this ->serializer diff --git a/src/BusProNet/ApiResponseParser/BookingResponseParser.php b/src/BusProNet/ApiResponseParser/BookingResponseParser.php index aa22bfe..a2ef6e5 100644 --- a/src/BusProNet/ApiResponseParser/BookingResponseParser.php +++ b/src/BusProNet/ApiResponseParser/BookingResponseParser.php @@ -6,8 +6,10 @@ use App\BusProNet\Model\Address; use App\BusProNet\Model\Booking; use App\BusProNet\Model\Communication; use App\BusProNet\Model\PersonalData; +use App\BusProNet\Model\Pickup; use App\BusProNet\Model\Room; use App\BusProNet\Model\Service; +use App\BusProNet\Model\Surcharge; class BookingResponseParser { @@ -48,14 +50,20 @@ class BookingResponseParser if ($xml->beförderungen) { $booking->transportationServices = $this - ->parseServices($xml->beförderungen->beförderung, Service::TYPE_TRANSPORTATION); + ->parseServices($xml->beförderungen->beförderung, Service::CATEGORY_TRANSPORTATION); } if ($xml->zusatzleistungen) { $booking->additionalServices = $this - ->parseServices($xml->zusatzleistungen->zusatzleistung, Service::TYPE_ADDITIONAL); + ->parseServices($xml->zusatzleistungen->zusatzleistung, Service::CATEGORY_ADDITIONAL); } 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; @@ -135,8 +143,8 @@ class BookingResponseParser return $this->stringToFloat((string) $price); }, $this->stringToArray((string) $attributes['einzelpreis'], '/') ); - $service->individualPrice = $this->arrayToOneBased($individualPrices); - if (Service::TYPE_TRANSPORTATION === $type) { + $service->individualPrice = array_combine($mapping, $individualPrices); + if (Service::CATEGORY_TRANSPORTATION === $type) { $service->direction = (string) $attributes['richtung']; } @@ -150,7 +158,7 @@ class BookingResponseParser { $rooms = []; - foreach ($xml as $item) { + foreach ($xml->ferienzielunterbringung as $item) { $attributes = $item->attributes(); $id = (int) $attributes['idzimmer']; @@ -173,10 +181,69 @@ class BookingResponseParser return $this->stringToFloat((string) $price); }, $this->stringToArray((string) $attributes['einzelpreis'], '/') ); - $room->individualPrice = $this->arrayToOneBased($individualPrices); + $room->individualPrice = array_combine($mapping, $individualPrices); + $rooms[$id] = $room; } 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; + } } \ No newline at end of file diff --git a/src/BusProNet/ApiResponseParser/CrmAttributesResponseParser.php b/src/BusProNet/ApiResponseParser/CrmAttributesResponseParser.php index 9d42175..59e4ae6 100644 --- a/src/BusProNet/ApiResponseParser/CrmAttributesResponseParser.php +++ b/src/BusProNet/ApiResponseParser/CrmAttributesResponseParser.php @@ -2,8 +2,8 @@ namespace App\BusProNet\ApiResponseParser; -use App\BusProNet\Model\CrmAttribute; -use App\BusProNet\Model\CrmAttributeGroup; +use App\BusProNet\Model\CrmSelection; +use App\BusProNet\Model\CrmSelectionGroup; use App\BusProNet\Model\CrmAttributes; class CrmAttributesResponseParser @@ -21,14 +21,14 @@ class CrmAttributesResponseParser $hotelCode = null; foreach ($xml->selektionsmerkmale->selektionsgruppe as $item) { - $group = new CrmAttributeGroup(); + $group = new CrmSelectionGroup(); $group->label = (string) $item->attributes()['bezeichnung']; $attributes = []; foreach ($item->selektion as $subItem) { $subItemAttributes = $subItem->attributes(); $attributeLabel = (string) $subItemAttributes['bezeichnung']; - $attribute = new CrmAttribute(); + $attribute = new CrmSelection(); $attribute->id = (int) $subItemAttributes['id']; $attribute->label = $attributeLabel; $attribute->selected = $this->stringToBool((string) $subItemAttributes['auswahl']); @@ -49,12 +49,12 @@ class CrmAttributesResponseParser $attributes[] = $attribute; } - $group->attributes = $attributes; + $group->selections = $attributes; $groups[] = $group; } $response = new CrmAttributes(); - $response->attributeGroups = $groups; + $response->selectionGroups = $groups; $response->admin = $isAdmin; $response->manager = $isManager; $response->houseManager = $isHouseManager; diff --git a/src/BusProNet/ApiResponseParser/MutableDataResponseParser.php b/src/BusProNet/ApiResponseParser/MutableDataResponseParser.php new file mode 100644 index 0000000..82d921b --- /dev/null +++ b/src/BusProNet/ApiResponseParser/MutableDataResponseParser.php @@ -0,0 +1,44 @@ +ä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); + } +} \ No newline at end of file diff --git a/src/BusProNet/ApiResponseParser/MutableFieldsResponseParser.php b/src/BusProNet/ApiResponseParser/MutableFieldsResponseParser.php deleted file mode 100644 index ad9e3f3..0000000 --- a/src/BusProNet/ApiResponseParser/MutableFieldsResponseParser.php +++ /dev/null @@ -1,44 +0,0 @@ -ä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); - } -} \ No newline at end of file diff --git a/src/BusProNet/ApiResponseParser/ResponseParser.php b/src/BusProNet/ApiResponseParser/ResponseParser.php index 4a0935a..1701802 100644 --- a/src/BusProNet/ApiResponseParser/ResponseParser.php +++ b/src/BusProNet/ApiResponseParser/ResponseParser.php @@ -14,6 +14,10 @@ class ResponseParser { $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 $responseType = $type; $responseTypeXml = $xml->xpath('satz/@typ'); @@ -45,8 +49,8 @@ class ResponseParser break; case ApiClient::TYPE_BASE_DATA_COUNTRIES: return (new CountriesResponseParser())->parse($xml); - case ApiClient::TYPE_MUTABLE_FIELDS: - return (new MutableFieldsResponseParser())->parse($xml); + case ApiClient::TYPE_MUTABLE_DATA: + return (new MutableDataResponseParser())->parse($xml); case ApiClient::TYPE_AVAILABILITY: return (new AvailabilitiesResponseParser())->parse($xml); case ApiClient::TYPE_BOOKING_UPDATE: diff --git a/src/BusProNet/DataLoader/PickupDataLoader.php b/src/BusProNet/DataLoader/PickupDataLoader.php index e58a30b..ce63a6e 100644 --- a/src/BusProNet/DataLoader/PickupDataLoader.php +++ b/src/BusProNet/DataLoader/PickupDataLoader.php @@ -52,7 +52,7 @@ class PickupDataLoader extends AbstractDataLoader return $pickup; } - public function enrichPickupsData(Travel $travel): void + public function patchPickupsDetails(Travel $travel): void { foreach ($travel->pickups as $pickupId => $pickup) { $pickupData = $this->loadById($pickupId); diff --git a/src/BusProNet/DataLoader/TravelDataLoader.php b/src/BusProNet/DataLoader/TravelDataLoader.php index decb44e..00249bb 100644 --- a/src/BusProNet/DataLoader/TravelDataLoader.php +++ b/src/BusProNet/DataLoader/TravelDataLoader.php @@ -3,8 +3,9 @@ namespace App\BusProNet\DataLoader; use App\BusProNet\Model\BaseData; -use App\BusProNet\Model\CrmAttribute; -use App\BusProNet\Model\CrmAttributeGroup; +use App\BusProNet\Model\CrmSelection; +use App\BusProNet\Model\CrmSelectionGroup; +use App\BusProNet\Model\MutableData; use App\BusProNet\Model\Pickup; use App\BusProNet\Model\Room; use App\BusProNet\Model\Service; @@ -71,17 +72,17 @@ class TravelDataLoader extends AbstractDataLoader foreach ($xml->selektiongruppe as $item) { $groupId = (int) $item->attributes()['idbuspro']; - $selectionGroup = new CrmAttributeGroup(); + $selectionGroup = new CrmSelectionGroup(); $selectionGroup->id = $groupId; $selectionGroup->label = (string) $item->attributes()['bezeichnung']; foreach ($item->selektion as $subItem) { $selectionId = (int) $subItem->attributes()['idbuspro']; - $selection = new CrmAttribute(); + $selection = new CrmSelection(); $selection->id = $selectionId; $selection->label = (string) $subItem->attributes()['bezeichnung']; $selectionGroups[$groupId]['selections'][$selectionId] = (string) $subItem->attributes()['bezeichnung']; - $selectionGroup->attributes[] = $selection; + $selectionGroup->selections[] = $selection; } $selectionGroups[$groupId] = $selectionGroup; @@ -99,6 +100,7 @@ class TravelDataLoader extends AbstractDataLoader $serviceId = (int) $attributes['idbuspro']; $service = new Service(); + $service->category = Service::CATEGORY_ADDITIONAL; $service->id = $serviceId; $service->subType = (string) $attributes['unterart']; $service->mandatory = $this->stringToBool((string) $attributes['pflicht']); @@ -123,6 +125,7 @@ class TravelDataLoader extends AbstractDataLoader $serviceId = (int) $attributes['idbuspro']; $service = new Service(); + $service->category = Service::CATEGORY_TRANSPORTATION; $service->id = $serviceId; $service->subType = (string) $attributes['unterart']; $service->dateFrom = $this->stringToDate((string) $attributes['termin']); @@ -183,7 +186,34 @@ class TravelDataLoader extends AbstractDataLoader 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(); diff --git a/src/BusProNet/DataProcessor/BookingDataProcessor.php b/src/BusProNet/DataProcessor/BookingDataProcessor.php new file mode 100644 index 0000000..ef2c958 --- /dev/null +++ b/src/BusProNet/DataProcessor/BookingDataProcessor.php @@ -0,0 +1,156 @@ +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; + } +} \ No newline at end of file diff --git a/src/BusProNet/Model/BaseData.php b/src/BusProNet/Model/BaseData.php index 9aca655..9f05600 100644 --- a/src/BusProNet/Model/BaseData.php +++ b/src/BusProNet/Model/BaseData.php @@ -11,4 +11,9 @@ class BaseData { return $this->items; } + + public function getItemByKey(string $key): mixed + { + return $this->items[$key] ?? null; + } } \ No newline at end of file diff --git a/src/BusProNet/Model/Booking.php b/src/BusProNet/Model/Booking.php index bbd999a..eb60ed8 100644 --- a/src/BusProNet/Model/Booking.php +++ b/src/BusProNet/Model/Booking.php @@ -2,14 +2,11 @@ namespace App\BusProNet\Model; -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; public ?PersonalData $applicant = null; public ?int $participantCount = null; @@ -32,6 +29,7 @@ class Booking public array $additionalServices = []; public array $rooms = []; public array $pickups = []; + public array $surcharges = []; public ?int $invoiceNumber = null; public ?float $totalPrice = null; @@ -47,6 +45,7 @@ class Booking public function getAdditionalServicesByGroup(mixed $group): array { $group = (array) $group; + return array_filter($this->additionalServices, function (Service $service) use ($group) { return in_array($service->subType, $group); }); @@ -54,9 +53,9 @@ class Booking 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); }); } @@ -75,6 +74,17 @@ class Booking 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 { foreach ($this->rooms as $room) { @@ -90,21 +100,22 @@ class Booking { $price = 0.0; + // Sum up all services 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]; - } 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); - - if (isset($room->individualPrice[$participantIndex])) { - $price += $room->individualPrice[$participantIndex]; - } elseif ($room->price) { - $price += $room->price; - } + $price += $room->individualPrice[$participantIndex]; return $price; } @@ -113,143 +124,4 @@ class Booking { 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; - } } \ No newline at end of file diff --git a/src/BusProNet/Model/CrmAttributeGroup.php b/src/BusProNet/Model/CrmAttributeGroup.php deleted file mode 100644 index 679bbec..0000000 --- a/src/BusProNet/Model/CrmAttributeGroup.php +++ /dev/null @@ -1,10 +0,0 @@ -attributeGroups as $group) { - /** @var CrmAttributeGroup $group */ + foreach ($this->selectionGroups as $group) { + /** @var CrmSelectionGroup $group */ if (false === isset($crmSelections[$group->label])) { $crmSelections[$group->label] = []; } - foreach ($group->attributes as $attribute) { - /** @var CrmAttribute $attribute */ + foreach ($group->selections as $attribute) { + /** @var CrmSelection $attribute */ if (false === $attribute->selected) { continue; } diff --git a/src/BusProNet/Model/CrmAttribute.php b/src/BusProNet/Model/CrmSelection.php similarity index 87% rename from src/BusProNet/Model/CrmAttribute.php rename to src/BusProNet/Model/CrmSelection.php index 9b1cd57..8ecbb4c 100644 --- a/src/BusProNet/Model/CrmAttribute.php +++ b/src/BusProNet/Model/CrmSelection.php @@ -2,7 +2,7 @@ namespace App\BusProNet\Model; -class CrmAttribute +class CrmSelection { public ?int $id = null; public ?string $label = null; diff --git a/src/BusProNet/Model/CrmSelectionGroup.php b/src/BusProNet/Model/CrmSelectionGroup.php new file mode 100644 index 0000000..3756ec1 --- /dev/null +++ b/src/BusProNet/Model/CrmSelectionGroup.php @@ -0,0 +1,22 @@ + 'Skipass', + 71 => 'Verpflegung', + 85 => 'Übernachtungen', + 86 => 'Programm', + ]; + + public ?int $id = null; + public ?string $label = null; + public ?array $selections = null; +} \ No newline at end of file diff --git a/src/BusProNet/Model/MutableData.php b/src/BusProNet/Model/MutableData.php new file mode 100644 index 0000000..ecf4bf8 --- /dev/null +++ b/src/BusProNet/Model/MutableData.php @@ -0,0 +1,24 @@ +category = $category; + $this->mutable = $mutable; + $this->mutableBefore = $mutableUntil; + } + + public ?string $category = null; + public bool $mutable = false; + public ?\DateTimeImmutable $mutableBefore = null; +} \ No newline at end of file diff --git a/src/BusProNet/Model/MutableField.php b/src/BusProNet/Model/MutableField.php deleted file mode 100644 index 5cf1cbc..0000000 --- a/src/BusProNet/Model/MutableField.php +++ /dev/null @@ -1,17 +0,0 @@ -type = $type; - $this->mutable = $mutable; - $this->mutableBefore = $mutableUntil; - } - - public ?string $type = null; - public bool $mutable = false; - public ?\DateTimeImmutable $mutableBefore = null; -} \ No newline at end of file diff --git a/src/BusProNet/Model/Service.php b/src/BusProNet/Model/Service.php index a1371b3..0e7b21f 100644 --- a/src/BusProNet/Model/Service.php +++ b/src/BusProNet/Model/Service.php @@ -4,9 +4,8 @@ namespace App\BusProNet\Model; class Service { - public const TYPE_INCLUDED = 'included'; - public const TYPE_ADDITIONAL = 'additional'; - public const TYPE_TRANSPORTATION = 'transportation'; + public const CATEGORY_ADDITIONAL = 'additional'; + public const CATEGORY_TRANSPORTATION = 'transportation'; public const TOKEN_COURSES = 'KUR'; 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 ?int $id = null; + public ?string $category = null; public ?string $status = null; public bool $mandatory = false; public ?string $label = null; diff --git a/src/BusProNet/Model/Surcharge.php b/src/BusProNet/Model/Surcharge.php new file mode 100644 index 0000000..c3d810e --- /dev/null +++ b/src/BusProNet/Model/Surcharge.php @@ -0,0 +1,11 @@ +available > 0); }); } + + public function getIncludedServices(): array + { + $services = []; + + foreach (CrmSelectionGroup::$includedServicesMapping as $id => $label) { + if (isset($this->selectionGroups[$id])) { + $services[] = $this->selectionGroups[$id]; + } + } + + return $services; + } } \ No newline at end of file diff --git a/src/Controller/Booking/EditController.php b/src/Controller/Booking/EditController.php index 8c3823f..1339cbd 100644 --- a/src/Controller/Booking/EditController.php +++ b/src/Controller/Booking/EditController.php @@ -35,11 +35,17 @@ class EditController extends AbstractController 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) { $this->addFlash('error', 'Reisedaten nicht (mehr) verfügbar'); @@ -47,22 +53,25 @@ class EditController extends AbstractController return $this->redirectToRoute('app_bookings'); } - $booking->travelData = $travelData; - $mutableFields = $this->apiClient->getMutableFields($booking->travelId); - $availabilities = $this->apiClient->getAvailabilities($booking->travelId); + // Fetch mutability information via API + $mutableData = $this->apiClient->getMutableData($bookingData->travelId); - $this->travelDataLoader->enrichServicesData($travelData, $availabilities); - $this->pickupDataLoader->enrichPickupsData($travelData); + // Fetch availability information via API + $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, [ '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); @@ -76,9 +85,9 @@ class EditController extends AbstractController } return $this->render('booking/edit.html.twig', [ - 'booking' => $booking, + 'bookingData' => $bookingData, 'travelData' => $travelData, - 'mutableFields' => $mutableFields, + 'mutableData' => $mutableData, 'availabilities' => $availabilities, 'form' => $form->createView(), ]); diff --git a/src/Form/BookingType.php b/src/Form/BookingType.php index 84f3924..6a25be8 100644 --- a/src/Form/BookingType.php +++ b/src/Form/BookingType.php @@ -3,45 +3,53 @@ namespace App\Form; use App\BusProNet\Model\Service; -use App\BusProNet\Model\Travel; use App\Form\Model\BookingData; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; final class BookingType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { - $builder->add('participants', CollectionType::class, [ - 'entry_type' => ParticipantType::class, - 'entry_options' => [ - 'selectable_courses' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_COURSES), - 'selectable_ski_passes' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_SKI_PASS), - 'selectable_services' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_ADDITIONAL), - 'selectable_board' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_BOARD), - 'selectable_rentals' => $options['travel']->getAdditionalServicesByGroup(Service::TOKEN_RENTALS), - 'selectable_transportation_services_to' => $options['travel'] - ->getTransportationServicesByDirection('HIN'), - 'selectable_transportation_services_fro' => $options['travel'] - ->getTransportationServicesByDirection('RUECK'), - 'selectable_pickups' => $options['travel']->pickups, - ], - 'allow_add' => false, - 'allow_delete' => false, - ]); + $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_options' => [ + 'selectable_courses' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_COURSES), + 'selectable_ski_passes' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_SKI_PASS), + 'selectable_services' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_ADDITIONAL), + 'selectable_board' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_BOARD), + 'selectable_rentals' => $travelData->getAdditionalServicesByGroup(Service::TOKEN_RENTALS), + 'selectable_transportation_services_to' => $travelData + ->getTransportationServicesByDirection('HIN'), + 'selectable_transportation_services_fro' => $travelData + ->getTransportationServicesByDirection('RUECK'), + '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_delete' => false, + ]); + }); } public function configureOptions(OptionsResolver $resolver): void { $resolver - ->setDefaults([ - 'data_class' => BookingData::class, - 'mutable_fields' => [], - ]) - ->setRequired(['travel']) - ->setAllowedTypes('travel', Travel::class) + ->setDefaults(['data_class' => BookingData::class]) ; } } \ No newline at end of file diff --git a/src/Form/Model/BookingData.php b/src/Form/Model/BookingData.php index 9226bab..87eb700 100644 --- a/src/Form/Model/BookingData.php +++ b/src/Form/Model/BookingData.php @@ -5,22 +5,26 @@ namespace App\Form\Model; use App\BusProNet\Model\Booking; use App\BusProNet\Model\PersonalData; use App\BusProNet\Model\Service; +use App\BusProNet\Model\Travel; use App\Validator\Constraints as AppAssert; use Symfony\Component\Validator\Constraints as Assert; -#[AppAssert\Booking] +#[Assert\GroupSequence(['BookingData', 'Api'])] +#[AppAssert\Booking(groups:['Api'])] class BookingData { public ?Booking $booking = null; + public ?Travel $travel = null; #[Assert\Valid] public array $participants = []; - public static function fromBooking(Booking $booking): static + public static function fromBooking(Booking $booking, Travel $travel): static { $instance = new static(); $instance->booking = $booking; + $instance->travel = $travel; foreach ($booking->participants as $index => $participant) { /** @var PersonalData $participant */ @@ -41,6 +45,8 @@ class BookingData ->getTransportationServiceForParticipantAndDirection($index, 'H'); $participantData->transportationServiceFro = $booking ->getTransportationServiceForParticipantAndDirection($index, 'R'); + $participantData->pickup = $booking->getPickupForParticipant($index); + $instance->participants[$index] = $participantData; } diff --git a/src/Form/ParticipantType.php b/src/Form/ParticipantType.php index a51975f..62a4f6f 100644 --- a/src/Form/ParticipantType.php +++ b/src/Form/ParticipantType.php @@ -21,52 +21,57 @@ class ParticipantType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { + if (true === $options['personal_data_mutable']) { + $builder + ->add('firstName', TextType::class, [ + 'label' => 'Vorname', + ]) + ->add('lastName', TextType::class, [ + 'label' => 'Nachname', + ]) + ->add('dateOfBirth', BirthdayType::class, [ + 'label' => 'Geburtsdatum', + 'html5' => true, + 'widget' => 'single_text', + 'input' => 'datetime_immutable', + ]) + ->add('gender', ChoiceType::class, [ + 'label' => 'Geschlecht', + 'required' => false, + 'placeholder' => 'keine Angabe', + 'choices' => [ + 'männlich' => 'M', + 'weiblich' => 'W', + 'divers' => 'D', + ], + ]) + ->add('nationality', CountryType::class, [ + 'label' => 'Nationalität', + 'property' => 'nationality', + 'preferred_choices' => ['D', 'A', 'CH'], + ]) + ->add('email', EmailType::class, [ + 'label' => 'Email', + ]) + ->add('mobile', TextType::class, [ + 'label' => 'Telefon (mobil)', + ]) + ->add('height', IntegerType::class, [ + 'label' => 'Körpergröße [cm]', + 'required' => false, + ]) + ->add('shoeSize', IntegerType::class, [ + 'label' => 'Schuhgröße', + 'required' => false, + ]) + ->add('weight', IntegerType::class, [ + 'label' => 'Gewicht [kg]', + 'required' => false, + ]) + ; + } + $builder - ->add('firstName', TextType::class, [ - 'label' => 'Vorname', - ]) - ->add('lastName', TextType::class, [ - 'label' => 'Nachname', - ]) - ->add('dateOfBirth', BirthdayType::class, [ - 'label' => 'Geburtsdatum', - 'html5' => true, - 'widget' => 'single_text', - 'input' => 'datetime_immutable', - ]) - ->add('gender', ChoiceType::class, [ - 'label' => 'Geschlecht', - 'required' => false, - 'placeholder' => 'keine Angabe', - 'choices' => [ - 'männlich' => 'M', - 'weiblich' => 'W', - 'divers' => 'D', - ], - ]) - ->add('nationality', CountryType::class, [ - 'label' => 'Nationalität', - 'property' => 'nationality', - 'preferred_choices' => ['D', 'A', 'CH'], - ]) - ->add('email', EmailType::class, [ - 'label' => 'Email', - ]) - ->add('mobile', TextType::class, [ - 'label' => 'Telefon (mobil)', - ]) - ->add('height', IntegerType::class, [ - 'label' => 'Körpergröße [cm]', - 'required' => false, - ]) - ->add('shoeSize', IntegerType::class, [ - 'label' => 'Schuhgröße', - 'required' => false, - ]) - ->add('weight', IntegerType::class, [ - 'label' => 'Gewicht [kg]', - 'required' => false, - ]) ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) { /** @var ParticipantData $participantData */ $participantData = $event->getData(); @@ -104,68 +109,90 @@ class ParticipantType extends AbstractType }, ]; - $form - ->add('courses', ChoiceType::class, [ - ...$commonChoiceFieldOptions, - 'label' => 'Kurse', - 'choices' => $options['selectable_courses'], - ]) - ->add('additionalServices', ChoiceType::class, [ - ...$commonChoiceFieldOptions, - 'label' => 'Zusatzleistungen', - 'choices' => $options['selectable_services'], - ]) - ->add('skiPass', ChoiceType::class, [ - ...$commonChoiceFieldOptions, - 'label' => 'Skipass', - 'choices' => $options['selectable_ski_passes'], - ]) - ->add('board', ChoiceType::class, [ - ...$commonChoiceFieldOptions, - 'label' => 'Verpflegung', - 'choices' => $options['selectable_board'], - ]) - ->add('rentals', ChoiceType::class, [ - ...$commonChoiceFieldOptions, - 'label' => 'Verleih', - 'choices' => $options['selectable_rentals'], - ]) - ->add('transportationServiceTo', ChoiceType::class, [ - ...$commonChoiceFieldOptions, - 'label' => 'Anreise', - 'multiple' => false, - 'choices' => $options['selectable_transportation_services_to'], - ]) - ->add('transportationServiceFro', ChoiceType::class, [ - ...$commonChoiceFieldOptions, - 'label' => 'Rückreise', - '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; - } + // Additional services + if (true === $options['additional_services_mutable']) { - $price = $pickup->price; + if (0 < count($options['selectable_courses'])) { + $form->add('courses', ChoiceType::class, [ + ...$commonChoiceFieldOptions, + 'label' => 'Kurse', + 'choices' => $options['selectable_courses'], + ]); + } + if (0 < count($options['selectable_services'])) { + $form->add('additionalServices', ChoiceType::class, [ + ...$commonChoiceFieldOptions, + 'label' => 'Zusatzleistungen', + 'choices' => $options['selectable_services'], + ]); + } + if (0 < count($options['selectable_ski_passes'])) { + $form->add('skiPass', ChoiceType::class, [ + ...$commonChoiceFieldOptions, + 'label' => 'Skipass', + 'choices' => $options['selectable_ski_passes'], + ]); + } + if (0 < count($options['selectable_board'])) { + $form->add('board', ChoiceType::class, [ + ...$commonChoiceFieldOptions, + 'label' => 'Verpflegung', + 'choices' => $options['selectable_board'], + ]); + } + if (0 < count($options['selectable_rentals'])) { + $form->add('rentals', ChoiceType::class, [ + ...$commonChoiceFieldOptions, + 'label' => 'Verleih', + 'choices' => $options['selectable_rentals'], + ]); + } + } - if (null === $price || 0.0 === $price) { - return $pickup->city; - } + // Transportation + if (true === $options['transportation_services_mutable']) { + $form + ->add('transportationServiceTo', ChoiceType::class, [ + ...$commonChoiceFieldOptions, + 'label' => 'Anreise', + 'multiple' => false, + 'choices' => $options['selectable_transportation_services_to'], + ]) + ->add('transportationServiceFro', ChoiceType::class, [ + ...$commonChoiceFieldOptions, + 'label' => 'Rückreise', + 'multiple' => false, + 'choices' => $options['selectable_transportation_services_fro'], + ]); + } - return sprintf('%s (%s€)', - $pickup->city, - number_format($price, 2, ',', '.') - ); - }, - ]) - ; + // Pickup + if (true === $options['pickups_mutable']) { + $form + ->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(); @@ -194,6 +221,10 @@ class ParticipantType extends AbstractType 'selectable_transportation_services_to' => [], 'selectable_transportation_services_fro' => [], 'selectable_pickups' => [], + 'personal_data_mutable' => true, + 'additional_services_mutable' => true, + 'transportation_services_mutable' => true, + 'pickups_mutable' => true, ]); } } \ No newline at end of file diff --git a/templates/booking/edit.html.twig b/templates/booking/edit.html.twig index 97a399d..6ac668e 100644 --- a/templates/booking/edit.html.twig +++ b/templates/booking/edit.html.twig @@ -10,65 +10,166 @@ {{ form_errors(form) }} {% endif %} +
+
+ Reisedaten +
+ + + + + + + + + + + + + + + + + + + +
+ Reise + + {{ bookingData.travel }} +
+ Reiseedatum + + {{ bookingData.travelDate|date('d.m.Y') }} +
+ Buchungsdatum + + {{ bookingData.bookingDate|date('d.m.Y') }} +
+ Inklusivleistungen + +
    + {% for selectionGroup in travelData.includedServices %} + {% for service in selectionGroup.selections %} +
  • + {{ service.label }} +
  • + {% endfor %} + {% endfor %} +
+
+
{% for child in form.participants %} {% set participant = child.vars.data %} Teilnehmer {{ participant.index }} -
-
- - Unterkunft - -
- {{ booking.roomForParticipant(participant.index).label }} -
-
- - Preis - -
- {{ booking.priceForParticipant(participant.index)|format_currency('EUR') }} -
-
-
-
- {{ form_row(child.firstName) }} - {{ form_row(child.lastName) }} - {{ form_row(child.gender) }} -
-
- {{ form_row(child.dateOfBirth) }} - {{ form_row(child.nationality) }} -
-
- {{ form_row(child.email) }} - {{ form_row(child.mobile) }} -
-
- {{ form_row(child.height) }} - {{ form_row(child.shoeSize) }} - {{ form_row(child.weight) }} -
-
- {{ form_row(child.courses) }} - {{ form_row(child.skiPass) }} -
-
- {{ form_row(child.additionalServices) }} - {{ form_row(child.board) }} -
-
- {{ form_row(child.rentals) }} -
-
- {{ form_row(child.transportationServiceTo) }} - {{ form_row(child.transportationServiceFro) }} - {% if child.pickup is defined %} - {{ form_row(child.pickup) }} - {% endif %} -
+
+
+ Persönliche Daten +
+
+ {% if travelData.participantDataMutable %} + {{ form_row(child.firstName) }} + {{ form_row(child.lastName) }} + {{ form_row(child.gender) }} + {% else %} + Teilnehmerdaten nicht mehr änderbar + {% endif %} +
+
+ {% if travelData.participantDataMutable %} + {{ form_row(child.dateOfBirth) }} + {{ form_row(child.nationality) }} + {% else %} + Teilnehmerdaten nicht mehr änderbar + {% endif %} +
+
+ {% if travelData.participantDataMutable %} + {{ form_row(child.email) }} + {{ form_row(child.mobile) }} + {% else %} + Teilnehmerdaten nicht mehr änderbar + {% endif %} +
+
+ {% if travelData.participantDataMutable %} + {{ form_row(child.height) }} + {{ form_row(child.shoeSize) }} + {{ form_row(child.weight) }} + {% else %} + Teilnehmerdaten nicht mehr änderbar + {% endif %} +
+
+
+
+ Unterkunft +
+ {% set room = bookingData.roomForParticipant(participant.index) %} + {{ room.label }} ({{ room.individualPrice[participant.index]|format_currency('EUR') }}) +
+
+
+ Leistungen +
+
+ {% 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 %} +
+
+ {% 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 %} +
+
+ {% if travelData.additionalServicesMutable %} + {% if child.rentals is defined %} + {{ form_row(child.rentals) }} + {% endif %} + {% else %} + Leistungen nicht mehr änderbar + {% endif %} +
+
+ {% 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 %} +
+
+
+
+ Gesamtpreis +
+ {{ bookingData.priceForParticipant(participant.index)|format_currency('EUR') }} +
{% endfor %} {{ form_rest(form) }}