feat: selectable drop-offs for inbound bus travel
This commit is contained in:
@@ -124,6 +124,10 @@ class BookingDataProcessor
|
||||
// Pickup location
|
||||
$participantData->pickup = $booking->getPickupForParticipant($index);
|
||||
|
||||
// Drop-off location
|
||||
$participantData->dropOff = $booking->getDropOffForParticipant($index);
|
||||
$participantData->differentDropOff = null !== $participantData->dropOff;
|
||||
|
||||
// Parking service (for self-organized PKW transportation)
|
||||
$parkingServices = $booking->getAdditionalServicesForParticipantByGroup($index, Constants::TOKEN_PARKING);
|
||||
if (false === empty($parkingServices)) {
|
||||
@@ -236,9 +240,9 @@ class BookingDataProcessor
|
||||
}
|
||||
|
||||
// Enrich pickup with data from travel (includes both outbound and inbound prices)
|
||||
// The outbound pickups already have inbound prices merged in from TravelParser
|
||||
if (null !== $participant->pickup && isset($travel->pickupsOutbound[$participant->pickup->id])) {
|
||||
$enrichedPickup = $travel->pickupsOutbound[$participant->pickup->id];
|
||||
// The pickups already have inbound prices merged in from TravelParser
|
||||
if (null !== $participant->pickup && isset($travel->pickups[$participant->pickup->id])) {
|
||||
$enrichedPickup = $travel->pickups[$participant->pickup->id];
|
||||
|
||||
// Copy all properties from enriched pickup to participant's pickup
|
||||
$participant->pickup->price = $enrichedPickup->price;
|
||||
@@ -251,6 +255,19 @@ class BookingDataProcessor
|
||||
$participant->pickup->code = $enrichedPickup->code;
|
||||
}
|
||||
|
||||
// Enrich drop-off with data from travel
|
||||
if (null !== $participant->dropOff && isset($travel->dropOffs[$participant->dropOff->id])) {
|
||||
$enrichedDropOff = $travel->dropOffs[$participant->dropOff->id];
|
||||
$participant->dropOff->price = $enrichedDropOff->price;
|
||||
$participant->dropOff->priceOutbound = $enrichedDropOff->priceOutbound;
|
||||
$participant->dropOff->priceInbound = $enrichedDropOff->priceInbound;
|
||||
$participant->dropOff->time = $enrichedDropOff->time;
|
||||
$participant->dropOff->city = $enrichedDropOff->city;
|
||||
$participant->dropOff->street = $enrichedDropOff->street;
|
||||
$participant->dropOff->postalCode = $enrichedDropOff->postalCode;
|
||||
$participant->dropOff->code = $enrichedDropOff->code;
|
||||
}
|
||||
|
||||
// Enrich insurance
|
||||
if (null !== $participant->insurance && isset($travel->insurances[$participant->insurance->id])) {
|
||||
$participant->insurance = $travel->insurances[$participant->insurance->id];
|
||||
@@ -301,6 +318,7 @@ class BookingDataProcessor
|
||||
$this->payloadBuilder->buildParticipantPayload($payload, $bookingData, $formData->participants);
|
||||
$this->payloadBuilder->buildServicesPayload($payload, $bookingData);
|
||||
$this->payloadBuilder->buildPickupPayload($payload, $bookingData);
|
||||
$this->payloadBuilder->buildDropOffPayload($payload, $bookingData);
|
||||
|
||||
// Add purchase vouchers if any exist
|
||||
$purchaseVouchers = $this->mappingCollector->collectPurchaseVouchers($formData);
|
||||
|
||||
@@ -203,9 +203,9 @@ class BookingPayloadBuilder
|
||||
*/
|
||||
public function buildPickupPayload(array &$payload, Booking $bookingData): void
|
||||
{
|
||||
if (0 < count($bookingData->pickupsOutbound)) {
|
||||
if (0 < count($bookingData->pickups)) {
|
||||
$payload['zustiege']['zustieg'] = [];
|
||||
foreach ($bookingData->pickupsOutbound as $pickup) {
|
||||
foreach ($bookingData->pickups as $pickup) {
|
||||
$uniqueMapping = array_unique($pickup->mapping);
|
||||
$payload['zustiege']['zustieg'][] = [
|
||||
'@idzustieg' => $pickup->id,
|
||||
@@ -216,6 +216,29 @@ class BookingPayloadBuilder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the drop-off locations section of the payload.
|
||||
*
|
||||
* Only included in payload if there are actual drop-off assignments for bus transportation.
|
||||
*
|
||||
* @param array $payload The payload array to modify
|
||||
* @param Booking $bookingData The booking data object
|
||||
*/
|
||||
public function buildDropOffPayload(array &$payload, Booking $bookingData): void
|
||||
{
|
||||
if (0 < count($bookingData->dropOffs)) {
|
||||
$payload['ausstiege']['ausstieg'] = [];
|
||||
foreach ($bookingData->dropOffs as $dropOff) {
|
||||
$uniqueMapping = array_unique($dropOff->mapping);
|
||||
$payload['ausstiege']['ausstieg'][] = [
|
||||
'@idzustieg' => $dropOff->id,
|
||||
'@anzahl' => count($uniqueMapping),
|
||||
'@zuordnung' => implode(',', array_map(fn ($index) => $index + 1, $uniqueMapping)),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a create booking request payload.
|
||||
*
|
||||
@@ -367,6 +390,10 @@ class BookingPayloadBuilder
|
||||
$this->addRoomMappingsToPayload($payload, $roomMap, $bookingDto);
|
||||
$this->addServicesFromMap($payload, 'zusatzleistungen', 'zusatzleistung', '@idleistung', $serviceMap);
|
||||
$this->addServicesFromMap($payload, 'zustiege', 'zustieg', '@idzustieg', $pickupMap);
|
||||
|
||||
$dropOffMap = $this->mappingCollector->collectDropOffMappings($bookingDto);
|
||||
$this->addServicesFromMap($payload, 'ausstiege', 'ausstieg', '@idzustieg', $dropOffMap);
|
||||
|
||||
$this->addServicesFromMap($payload, 'versicherungen', 'versicherung', '@idversicherung', $insuranceMap);
|
||||
|
||||
// Add purchase vouchers (regular purchase vouchers, excluding goodwill vouchers)
|
||||
|
||||
@@ -36,8 +36,8 @@ class ParticipantServiceProcessor
|
||||
$servicesToReset = [
|
||||
...$bookingData->additionalServices,
|
||||
...$bookingData->transportationServices,
|
||||
...$bookingData->pickupsOutbound,
|
||||
...$bookingData->pickupsInbound,
|
||||
...$bookingData->pickups,
|
||||
...$bookingData->dropOffs,
|
||||
...$bookingData->rooms,
|
||||
...$bookingData->insurances,
|
||||
];
|
||||
@@ -66,6 +66,7 @@ class ParticipantServiceProcessor
|
||||
$this->processAdditionalServices($participant, $bookingData, $travelData);
|
||||
$this->processTransportationServices($participant, $bookingData, $travelData);
|
||||
$this->processPickupLocations($participant, $bookingData);
|
||||
$this->processDropOffLocations($participant, $bookingData);
|
||||
$this->processRoomAssignment($participant, $bookingData);
|
||||
$this->processInsurance($participant, $bookingData, $travelData);
|
||||
}
|
||||
@@ -92,15 +93,15 @@ class ParticipantServiceProcessor
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($bookingData->pickupsOutbound as $pickup) {
|
||||
foreach ($bookingData->pickups as $pickup) {
|
||||
if (0 === count($pickup->mapping)) {
|
||||
unset($bookingData->pickupsOutbound[$pickup->id]);
|
||||
unset($bookingData->pickups[$pickup->id]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($bookingData->pickupsInbound as $pickup) {
|
||||
if (0 === count($pickup->mapping)) {
|
||||
unset($bookingData->pickupsInbound[$pickup->id]);
|
||||
foreach ($bookingData->dropOffs as $dropOff) {
|
||||
if (0 === count($dropOff->mapping)) {
|
||||
unset($bookingData->dropOffs[$dropOff->id]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,10 +233,34 @@ class ParticipantServiceProcessor
|
||||
$hasInboundBus = null !== $participant->transportationInbound && 'BUS' === $participant->transportationInbound->subType;
|
||||
|
||||
if (($hasOutboundBus || $hasInboundBus) && null !== $selectedPickup = $participant->pickup) {
|
||||
if (false === isset($bookingData->pickupsOutbound[$selectedPickup->id])) {
|
||||
$bookingData->pickupsOutbound[$selectedPickup->id] = $selectedPickup;
|
||||
if (false === isset($bookingData->pickups[$selectedPickup->id])) {
|
||||
$bookingData->pickups[$selectedPickup->id] = $selectedPickup;
|
||||
}
|
||||
$bookingData->pickupsOutbound[$selectedPickup->id]->mapping[] = $participant->index;
|
||||
$bookingData->pickups[$selectedPickup->id]->mapping[] = $participant->index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes drop-off locations for participants using bus transportation.
|
||||
*
|
||||
* Only processes drop-off locations for bus transportation services and maps the participant
|
||||
* to their selected drop-off location.
|
||||
*
|
||||
* @param ParticipantDto $participant The participant data from the form
|
||||
* @param Booking $bookingData The booking data object to update
|
||||
*/
|
||||
private function processDropOffLocations(ParticipantDto $participant, Booking $bookingData): void
|
||||
{
|
||||
$hasOutboundBus = null !== $participant->transportationOutbound
|
||||
&& 'BUS' === $participant->transportationOutbound->subType;
|
||||
$hasInboundBus = null !== $participant->transportationInbound
|
||||
&& 'BUS' === $participant->transportationInbound->subType;
|
||||
|
||||
if (($hasOutboundBus || $hasInboundBus) && null !== $selectedDropOff = $participant->dropOff) {
|
||||
if (false === isset($bookingData->dropOffs[$selectedDropOff->id])) {
|
||||
$bookingData->dropOffs[$selectedDropOff->id] = $selectedDropOff;
|
||||
}
|
||||
$bookingData->dropOffs[$selectedDropOff->id]->mapping[] = $participant->index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,14 +31,14 @@ class PersonalDataSynchronizer
|
||||
* IMPORTANT: The applicant's address must never be modified. This method updates
|
||||
* participant addresses independently to ensure applicant data remains intact.
|
||||
*
|
||||
* @param array<ParticipantDto> $participants The participants array from the form
|
||||
* @param Booking $bookingData The booking data object to update
|
||||
* @param bool $isInternalAgencyBooking Whether to fill default names for canceled participants
|
||||
* @param array<ParticipantDto> $participants The participants array from the form
|
||||
* @param Booking $bookingData The booking data object to update
|
||||
* @param bool $isInternalAgencyBooking Whether to fill default names for canceled participants
|
||||
*/
|
||||
public function updateParticipantPersonalData(
|
||||
array $participants,
|
||||
Booking $bookingData,
|
||||
bool $isInternalAgencyBooking = false
|
||||
bool $isInternalAgencyBooking = false,
|
||||
): void {
|
||||
// Fill missing mandatory fields on applicant (for company bookings by travel agencies)
|
||||
// This is separate from participants - applicant may be a company while participants are real people
|
||||
|
||||
@@ -143,6 +143,28 @@ class ServiceMappingCollector
|
||||
return $pickupMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects drop-off location mappings.
|
||||
*
|
||||
* Collects the drop-off selection for the inbound/return direction.
|
||||
*
|
||||
* @return array<string, array<int>> Map of drop-off ID to participant IDs
|
||||
*/
|
||||
public function collectDropOffMappings(BookingDto $bookingDto): array
|
||||
{
|
||||
$dropOffMap = [];
|
||||
|
||||
foreach ($bookingDto->participants as $index => $participant) {
|
||||
$participantId = $index + 1;
|
||||
|
||||
if (null !== $participant->dropOff) {
|
||||
$dropOffMap[$participant->dropOff->id][] = $participantId;
|
||||
}
|
||||
}
|
||||
|
||||
return $dropOffMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects insurance mappings.
|
||||
*
|
||||
|
||||
@@ -41,8 +41,8 @@ class Booking
|
||||
public array $transportationServices = [];
|
||||
public array $additionalServices = [];
|
||||
public array $rooms = [];
|
||||
public array $pickupsOutbound = [];
|
||||
public array $pickupsInbound = [];
|
||||
public array $pickups = [];
|
||||
public array $dropOffs = [];
|
||||
public array $surcharges = [];
|
||||
public array $insurances = [];
|
||||
public ?int $invoiceNumber = null;
|
||||
@@ -136,8 +136,7 @@ class Booking
|
||||
* Retrieves pickup service for a specific participant.
|
||||
*
|
||||
* Finds the pickup service assigned to the specified participant
|
||||
* from the outbound pickup services. The API only supports pickups
|
||||
* when outbound transportation is bus.
|
||||
* from the pickup services.
|
||||
*
|
||||
* @param int $participantIndex The participant index to search for
|
||||
*
|
||||
@@ -145,7 +144,7 @@ class Booking
|
||||
*/
|
||||
public function getPickupForParticipant(int $participantIndex): ?Pickup
|
||||
{
|
||||
foreach ($this->pickupsOutbound as $pickup) {
|
||||
foreach ($this->pickups as $pickup) {
|
||||
if (in_array($participantIndex, $pickup->mapping)) {
|
||||
return $pickup;
|
||||
}
|
||||
@@ -154,6 +153,27 @@ class Booking
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves drop-off service for a specific participant.
|
||||
*
|
||||
* Finds the drop-off service assigned to the specified participant
|
||||
* from the drop-off services (inbound/return direction).
|
||||
*
|
||||
* @param int $participantIndex The participant index to search for
|
||||
*
|
||||
* @return Pickup|null The matching drop-off service or null if not found
|
||||
*/
|
||||
public function getDropOffForParticipant(int $participantIndex): ?Pickup
|
||||
{
|
||||
foreach ($this->dropOffs as $dropOff) {
|
||||
if (in_array($participantIndex, $dropOff->mapping)) {
|
||||
return $dropOff;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the insurance assigned to a specific participant.
|
||||
*
|
||||
|
||||
@@ -84,10 +84,10 @@ class Travel
|
||||
public bool $transportationServicesMutable = true;
|
||||
|
||||
#[Groups(['api:single'])]
|
||||
public array $pickupsOutbound = [];
|
||||
public array $pickups = [];
|
||||
|
||||
#[Groups(['api:single'])]
|
||||
public array $pickupsInbound = [];
|
||||
public array $dropOffs = [];
|
||||
|
||||
#[Groups(['api:single'])]
|
||||
public bool $pickupsMutable = true;
|
||||
|
||||
@@ -61,8 +61,8 @@ class PickupLoader extends AbstractLoader
|
||||
|
||||
public function patchPickupsDetails(Travel $travel): void
|
||||
{
|
||||
$travel->pickupsOutbound = $this->patchAndSortPickups($travel->pickupsOutbound);
|
||||
$travel->pickupsInbound = $this->patchAndSortPickups($travel->pickupsInbound);
|
||||
$travel->pickups = $this->patchAndSortPickups($travel->pickups);
|
||||
$travel->dropOffs = $this->patchAndSortPickups($travel->dropOffs);
|
||||
}
|
||||
|
||||
private function patchAndSortPickups(array $pickups): array
|
||||
|
||||
@@ -91,11 +91,12 @@ class BookingParser extends AbstractParser
|
||||
|
||||
$pickupsData = $node->filterXPath('//zustiege/zustieg');
|
||||
if (0 < $pickupsData->count()) {
|
||||
$booking->pickupsOutbound = $this->pickupsParser->parse($pickupsData);
|
||||
$booking->pickups = $this->pickupsParser->parse($pickupsData);
|
||||
}
|
||||
$pickupsData = $node->filterXPath('//zustiege_rueck/zustieg_rueck');
|
||||
if (0 < $pickupsData->count()) {
|
||||
$booking->pickupsInbound = $this->pickupsParser->parse($pickupsData);
|
||||
|
||||
$dropOffsData = $node->filterXPath('//ausstiege/ausstieg');
|
||||
if (0 < $dropOffsData->count()) {
|
||||
$booking->dropOffs = $this->pickupsParser->parse($dropOffsData);
|
||||
}
|
||||
|
||||
$surchargesData = $node->filterXPath('//zuschlaege/zuschlag');
|
||||
|
||||
@@ -76,11 +76,11 @@ class TravelParser extends AbstractParser
|
||||
$travel->transportationServices = $this
|
||||
->getTransportationServices($node->filterXPath('//lei_befoerderung/leistung'));
|
||||
$travel->rooms = $this->getRooms($hotelNode);
|
||||
$travel->pickupsOutbound = $this->getPickups($node->filterXPath('//zustiege/zustieg'), $dateFrom, true);
|
||||
$travel->pickupsInbound = $this->getPickups($node->filterXPath('//zustiege_rueck/zustieg_rueck'), $dateTo, false);
|
||||
$travel->pickups = $this->getPickups($node->filterXPath('//zustiege/zustieg'), $dateFrom, true);
|
||||
$travel->dropOffs = $this->getPickups($node->filterXPath('//zustiege_rueck/zustieg_rueck'), $dateTo, false);
|
||||
|
||||
// Merge inbound prices into outbound pickups for split pricing support
|
||||
$this->mergeInboundPricesIntoOutboundPickups($travel->pickupsOutbound, $travel->pickupsInbound);
|
||||
// Merge drop-off prices into pickups for split pricing support
|
||||
$this->mergeDropOffPricesIntoPickups($travel->pickups, $travel->dropOffs);
|
||||
|
||||
$travel->guide = $this->getGuide($node);
|
||||
|
||||
@@ -422,21 +422,21 @@ class TravelParser extends AbstractParser
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges inbound pickup prices into outbound pickups for split pricing support.
|
||||
* Merges drop-off prices into pickups for split pricing support.
|
||||
*
|
||||
* For each outbound pickup, finds the matching inbound pickup by ID and copies
|
||||
* the inbound price to the outbound pickup's priceInbound property. This enables
|
||||
* split pricing calculations where pickup costs are distributed between outbound
|
||||
* For each pickup, finds the matching drop-off by ID and copies
|
||||
* the inbound price to the pickup's priceInbound property. This enables
|
||||
* split pricing calculations where costs are distributed between outbound
|
||||
* and inbound transportation.
|
||||
*
|
||||
* @param array<int, Pickup> $pickupsOutbound Outbound pickups indexed by ID (modified in place)
|
||||
* @param array<int, Pickup> $pickupsInbound Inbound pickups indexed by ID
|
||||
* @param array<int, Pickup> $pickups Pickups indexed by ID (modified in place)
|
||||
* @param array<int, Pickup> $dropOffs Drop-offs indexed by ID
|
||||
*/
|
||||
private function mergeInboundPricesIntoOutboundPickups(array &$pickupsOutbound, array $pickupsInbound): void
|
||||
private function mergeDropOffPricesIntoPickups(array &$pickups, array $dropOffs): void
|
||||
{
|
||||
foreach ($pickupsOutbound as $pickupId => $outboundPickup) {
|
||||
if (isset($pickupsInbound[$pickupId])) {
|
||||
$outboundPickup->priceInbound = $pickupsInbound[$pickupId]->priceInbound;
|
||||
foreach ($pickups as $pickupId => $pickup) {
|
||||
if (isset($dropOffs[$pickupId])) {
|
||||
$pickup->priceInbound = $dropOffs[$pickupId]->priceInbound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user