wip: prepare transportation services
This commit is contained in:
@@ -0,0 +1,872 @@
|
|||||||
|
# Transportation Services Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Implement comprehensive transportation services for the MyEP Next Booking system, supporting bus and self-organized (car/PKW) transportation with directional pickup selection, discount handling, and optional parking services. This implementation addresses BusProNet's inconsistent direction naming conventions while following established architectural patterns.
|
||||||
|
|
||||||
|
## Current State Analysis
|
||||||
|
|
||||||
|
### Existing Infrastructure ✅
|
||||||
|
|
||||||
|
**Transportation Data Model:**
|
||||||
|
- `Service` model has `direction`, `subType`, `price` properties
|
||||||
|
- `Travel` model has `transportationServices[]`, `pickupsTo[]`, `pickupsFro[]`
|
||||||
|
- `Booking` model supports transportation service mapping
|
||||||
|
- XML parsing handles transportation services and pickups
|
||||||
|
- Data processing includes transportation service management
|
||||||
|
|
||||||
|
**Form System Integration:**
|
||||||
|
- `ParticipantDto` has transportation and pickup properties
|
||||||
|
- Field handler registry supports service processing
|
||||||
|
- Conditional field state system available
|
||||||
|
- HTMX integration for real-time updates
|
||||||
|
- Pricing integration system in place
|
||||||
|
|
||||||
|
### Direction Naming Inconsistencies 🔍
|
||||||
|
|
||||||
|
**Problem Identified:** BusProNet uses inconsistent direction codes across different contexts:
|
||||||
|
|
||||||
|
1. **Travel Data Context:** `'HIN'` and `'RUECK'` (full German words)
|
||||||
|
2. **Booking Data Context:** `'H'` and `'R'` (single letter abbreviations)
|
||||||
|
3. **Internal Properties:** `To`/`Fro` (archaic English)
|
||||||
|
|
||||||
|
**Evidence:**
|
||||||
|
- Comment in `BookingEditDto.php`: `"Different keys for direction used in booking data (H <=> HIN, R <=> RUECK)!"`
|
||||||
|
- `Travel->getTransportationServicesByDirection('HIN'/'RUECK')`
|
||||||
|
- `Booking->getTransportationServiceForParticipantAndDirection($index, 'H'/'R')`
|
||||||
|
|
||||||
|
### Missing Components 🚧
|
||||||
|
|
||||||
|
- Direction mapping utility for consistency
|
||||||
|
- Transportation field handlers and options providers
|
||||||
|
- Conditional pickup field logic (only show when bus selected)
|
||||||
|
- Parking service integration (subtype PAR)
|
||||||
|
- Modern English property naming (Outbound/Inbound)
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### Phase 1: Foundation - Direction Mapping & Naming 🎯
|
||||||
|
|
||||||
|
#### 1.1 Create Direction Mapping Utility
|
||||||
|
|
||||||
|
**File:** `src/BusProNet/Utility/DirectionMapper.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\BusProNet\Utility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles direction mapping between BusProNet's inconsistent direction codes.
|
||||||
|
*
|
||||||
|
* BusProNet uses different direction codes in different contexts:
|
||||||
|
* - Travel data: 'HIN' (outbound), 'RUECK' (inbound)
|
||||||
|
* - Booking data: 'H' (outbound), 'R' (inbound)
|
||||||
|
*
|
||||||
|
* This utility provides consistent mapping between formats.
|
||||||
|
*/
|
||||||
|
final class DirectionMapper
|
||||||
|
{
|
||||||
|
// Travel data format (full German words)
|
||||||
|
public const OUTBOUND_TRAVEL = 'HIN';
|
||||||
|
public const INBOUND_TRAVEL = 'RUECK';
|
||||||
|
|
||||||
|
// Booking data format (single letter abbreviations)
|
||||||
|
public const OUTBOUND_BOOKING = 'H';
|
||||||
|
public const INBOUND_BOOKING = 'R';
|
||||||
|
|
||||||
|
// English naming for internal use
|
||||||
|
public const OUTBOUND = 'outbound';
|
||||||
|
public const INBOUND = 'inbound';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps travel direction code to booking direction code.
|
||||||
|
*/
|
||||||
|
public static function travelToBooking(string $travelDirection): string
|
||||||
|
{
|
||||||
|
return match($travelDirection) {
|
||||||
|
self::OUTBOUND_TRAVEL => self::OUTBOUND_BOOKING,
|
||||||
|
self::INBOUND_TRAVEL => self::INBOUND_BOOKING,
|
||||||
|
default => throw new \InvalidArgumentException("Unknown travel direction: $travelDirection")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps booking direction code to travel direction code.
|
||||||
|
*/
|
||||||
|
public static function bookingToTravel(string $bookingDirection): string
|
||||||
|
{
|
||||||
|
return match($bookingDirection) {
|
||||||
|
self::OUTBOUND_BOOKING => self::OUTBOUND_TRAVEL,
|
||||||
|
self::INBOUND_BOOKING => self::INBOUND_TRAVEL,
|
||||||
|
default => throw new \InvalidArgumentException("Unknown booking direction: $bookingDirection")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps direction code to English name.
|
||||||
|
*/
|
||||||
|
public static function toEnglish(string $direction): string
|
||||||
|
{
|
||||||
|
return match($direction) {
|
||||||
|
self::OUTBOUND_TRAVEL, self::OUTBOUND_BOOKING => self::OUTBOUND,
|
||||||
|
self::INBOUND_TRAVEL, self::INBOUND_BOOKING => self::INBOUND,
|
||||||
|
default => throw new \InvalidArgumentException("Unknown direction: $direction")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all outbound direction codes.
|
||||||
|
*/
|
||||||
|
public static function getOutboundCodes(): array
|
||||||
|
{
|
||||||
|
return [self::OUTBOUND_TRAVEL, self::OUTBOUND_BOOKING];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all inbound direction codes.
|
||||||
|
*/
|
||||||
|
public static function getInboundCodes(): array
|
||||||
|
{
|
||||||
|
return [self::INBOUND_TRAVEL, self::INBOUND_BOOKING];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2 Update ParticipantDto Properties
|
||||||
|
|
||||||
|
**Current Properties (archaic naming):**
|
||||||
|
```php
|
||||||
|
public ?Service $transportationServiceTo = null;
|
||||||
|
public ?Service $transportationServiceFro = null;
|
||||||
|
public ?Pickup $pickup = null;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Updated Properties (modern English):**
|
||||||
|
```php
|
||||||
|
public ?Service $transportationOutbound = null; // Maps to 'HIN'/'H'
|
||||||
|
public ?Service $transportationInbound = null; // Maps to 'RUECK'/'R'
|
||||||
|
public ?Pickup $pickupOutbound = null; // Maps to pickupsTo
|
||||||
|
public ?Pickup $pickupInbound = null; // Maps to pickupsFro
|
||||||
|
public ?Service $parking = null; // New parking service
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3 Update BookingEditDto Direction Mapping
|
||||||
|
|
||||||
|
**Current Implementation:**
|
||||||
|
```php
|
||||||
|
// Different keys for direction used in booking data (H <=> HIN, R <=> RUECK)!
|
||||||
|
$participantData->transportationServiceTo = $booking
|
||||||
|
->getTransportationServiceForParticipantAndDirection($index, 'H');
|
||||||
|
$participantData->transportationServiceFro = $booking
|
||||||
|
->getTransportationServiceForParticipantAndDirection($index, 'R');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Updated Implementation:**
|
||||||
|
```php
|
||||||
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
|
|
||||||
|
$participantData->transportationOutbound = $booking
|
||||||
|
->getTransportationServiceForParticipantAndDirection($index, DirectionMapper::OUTBOUND_BOOKING);
|
||||||
|
$participantData->transportationInbound = $booking
|
||||||
|
->getTransportationServiceForParticipantAndDirection($index, DirectionMapper::INBOUND_BOOKING);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Transportation Field Implementation 🚀
|
||||||
|
|
||||||
|
#### 2.1 Transportation Service Field Handlers
|
||||||
|
|
||||||
|
**A. Outbound Transportation Handler**
|
||||||
|
**File:** `src/Form/Service/ParticipantTransportationOutboundFieldHandler.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service;
|
||||||
|
|
||||||
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles processing of outbound transportation service selection.
|
||||||
|
*
|
||||||
|
* Manages outbound (HIN) transportation options including bus and
|
||||||
|
* self-organized (PKW) services with pricing and availability validation.
|
||||||
|
*/
|
||||||
|
class ParticipantTransportationOutboundFieldHandler extends AbstractParticipantFieldHandler
|
||||||
|
{
|
||||||
|
public function getFieldName(): string
|
||||||
|
{
|
||||||
|
return 'transportationOutbound';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDependencies(): array
|
||||||
|
{
|
||||||
|
return ['dateOfBirth']; // For age-based discounts
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
||||||
|
{
|
||||||
|
return true; // Always process to handle deselection
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void
|
||||||
|
{
|
||||||
|
$participant = $this->getParticipant($bookingDto, $participantIndex);
|
||||||
|
if (null === $participant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedTransportation = $this->getFieldValue($submittedData, $this->getFieldName());
|
||||||
|
|
||||||
|
// Get available outbound transportation services
|
||||||
|
$availableServices = $bookingDto->travel->getTransportationServicesByDirection(
|
||||||
|
DirectionMapper::OUTBOUND_TRAVEL,
|
||||||
|
true // filter available
|
||||||
|
);
|
||||||
|
|
||||||
|
// Validate and convert selection to Service object
|
||||||
|
$validSelection = null;
|
||||||
|
if (null !== $selectedTransportation) {
|
||||||
|
if ($this->isServiceValidForParticipant($selectedTransportation, $availableServices, $bookingDto, $participantIndex)) {
|
||||||
|
$validSelection = $this->findServiceInAvailableServices($selectedTransportation, $availableServices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update participant with validated selection
|
||||||
|
$participant->transportationOutbound = $validSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... validation methods similar to existing handlers
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**B. Inbound Transportation Handler**
|
||||||
|
**File:** `src/Form/Service/ParticipantTransportationInboundFieldHandler.php`
|
||||||
|
- Similar structure for inbound (RUECK) transportation
|
||||||
|
- Field name: `transportationInbound`
|
||||||
|
- Uses `DirectionMapper::INBOUND_TRAVEL`
|
||||||
|
|
||||||
|
#### 2.2 Pickup Field Handlers
|
||||||
|
|
||||||
|
**A. Outbound Pickup Handler**
|
||||||
|
**File:** `src/Form/Service/ParticipantPickupOutboundFieldHandler.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles outbound pickup location selection.
|
||||||
|
*
|
||||||
|
* Pickup selection is only processed when outbound transportation
|
||||||
|
* is bus type. Automatically clears pickup when transportation
|
||||||
|
* changes to self-organized (PKW).
|
||||||
|
*/
|
||||||
|
class ParticipantPickupOutboundFieldHandler extends AbstractParticipantFieldHandler
|
||||||
|
{
|
||||||
|
public function getFieldName(): string
|
||||||
|
{
|
||||||
|
return 'pickupOutbound';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDependencies(): array
|
||||||
|
{
|
||||||
|
return ['transportationOutbound']; // Must process transportation first
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
||||||
|
{
|
||||||
|
return true; // Always process to handle clearing
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void
|
||||||
|
{
|
||||||
|
$participant = $this->getParticipant($bookingDto, $participantIndex);
|
||||||
|
if (null === $participant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only process pickup if outbound transportation is bus
|
||||||
|
if (null === $participant->transportationOutbound || 'BUS' !== $participant->transportationOutbound->subType) {
|
||||||
|
$participant->pickupOutbound = null; // Clear pickup for non-bus transport
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedPickup = $this->getFieldValue($submittedData, $this->getFieldName());
|
||||||
|
|
||||||
|
// Validate pickup selection against available outbound pickups
|
||||||
|
$validSelection = null;
|
||||||
|
if (null !== $selectedPickup) {
|
||||||
|
$availablePickups = $bookingDto->travel->pickupsTo;
|
||||||
|
$validSelection = $this->findPickupInAvailable($selectedPickup, $availablePickups);
|
||||||
|
}
|
||||||
|
|
||||||
|
$participant->pickupOutbound = $validSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... pickup validation methods
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**B. Inbound Pickup Handler**
|
||||||
|
**File:** `src/Form/Service/ParticipantPickupInboundFieldHandler.php`
|
||||||
|
- Similar structure for inbound pickup
|
||||||
|
- Field name: `pickupInbound`
|
||||||
|
- Depends on `transportationInbound`
|
||||||
|
- Uses `travel->pickupsFro`
|
||||||
|
|
||||||
|
#### 2.3 Parking Service Handler
|
||||||
|
|
||||||
|
**File:** `src/Form/Service/ParticipantParkingFieldHandler.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service;
|
||||||
|
|
||||||
|
use App\BusProNet\Constants;
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles parking service selection for self-organized transportation.
|
||||||
|
*
|
||||||
|
* Parking is only available when at least one direction uses
|
||||||
|
* self-organized (PKW) transportation.
|
||||||
|
*/
|
||||||
|
class ParticipantParkingFieldHandler extends AbstractParticipantFieldHandler
|
||||||
|
{
|
||||||
|
public function getFieldName(): string
|
||||||
|
{
|
||||||
|
return 'parking';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDependencies(): array
|
||||||
|
{
|
||||||
|
return ['transportationOutbound', 'transportationInbound'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
||||||
|
{
|
||||||
|
return true; // Always process for state changes
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void
|
||||||
|
{
|
||||||
|
$participant = $this->getParticipant($bookingDto, $participantIndex);
|
||||||
|
if (null === $participant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if parking is applicable (at least one PKW direction)
|
||||||
|
if (!$this->isParkingApplicable($participant)) {
|
||||||
|
$participant->parking = null; // Clear parking for bus-only transport
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedParking = $this->getFieldValue($submittedData, $this->getFieldName());
|
||||||
|
|
||||||
|
// Get available parking services (subtype PAR)
|
||||||
|
$availableParkingServices = $bookingDto->travel->getAdditionalServicesBySubTypes('PAR', true);
|
||||||
|
|
||||||
|
$validSelection = null;
|
||||||
|
if (null !== $selectedParking) {
|
||||||
|
$validSelection = $this->findServiceInAvailableServices($selectedParking, $availableParkingServices);
|
||||||
|
}
|
||||||
|
|
||||||
|
$participant->parking = $validSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isParkingApplicable($participant): bool
|
||||||
|
{
|
||||||
|
$outboundIsPkw = $participant->transportationOutbound?->subType === 'PKW';
|
||||||
|
$inboundIsPkw = $participant->transportationInbound?->subType === 'PKW';
|
||||||
|
|
||||||
|
return $outboundIsPkw || $inboundIsPkw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.4 Field Options Provider Integration
|
||||||
|
|
||||||
|
**Update:** `src/Form/Service/ParticipantFieldOptionsProvider.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
protected function registerFieldOptionProviders(): void
|
||||||
|
{
|
||||||
|
// ... existing providers
|
||||||
|
|
||||||
|
// Outbound Transportation
|
||||||
|
$this->fieldOptionProviders['transportationOutbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
|
'label' => 'Hinfahrt',
|
||||||
|
'choices' => $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::OUTBOUND_TRAVEL),
|
||||||
|
'choice_label' => fn(Service $service) => $this->formatTransportationServiceLabel($service),
|
||||||
|
'choice_value' => 'id',
|
||||||
|
'expanded' => true,
|
||||||
|
'multiple' => false,
|
||||||
|
'required' => true,
|
||||||
|
'attr' => [
|
||||||
|
'hx-post' => $this->urlGenerator->generate('booking_create_step_2_refresh'),
|
||||||
|
'hx-target' => '#booking-summary',
|
||||||
|
'hx-trigger' => 'change',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Inbound Transportation
|
||||||
|
$this->fieldOptionProviders['transportationInbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
|
'label' => 'Rückfahrt',
|
||||||
|
'choices' => $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::INBOUND_TRAVEL),
|
||||||
|
'choice_label' => fn(Service $service) => $this->formatTransportationServiceLabel($service),
|
||||||
|
'choice_value' => 'id',
|
||||||
|
'expanded' => true,
|
||||||
|
'multiple' => false,
|
||||||
|
'required' => true,
|
||||||
|
'attr' => [
|
||||||
|
'hx-post' => $this->urlGenerator->generate('booking_create_step_2_refresh'),
|
||||||
|
'hx-target' => '#booking-summary',
|
||||||
|
'hx-trigger' => 'change',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Outbound Pickup (conditional)
|
||||||
|
$this->fieldOptionProviders['pickupOutbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
|
'label' => 'Zustieg Hinfahrt',
|
||||||
|
'choices' => $bookingDto->travel->pickupsTo,
|
||||||
|
'choice_label' => 'label',
|
||||||
|
'choice_value' => 'id',
|
||||||
|
'expanded' => false, // Dropdown for pickups
|
||||||
|
'multiple' => false,
|
||||||
|
'required' => true,
|
||||||
|
'placeholder' => 'Zustieg auswählen',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Inbound Pickup (conditional)
|
||||||
|
$this->fieldOptionProviders['pickupInbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
|
'label' => 'Zustieg Rückfahrt',
|
||||||
|
'choices' => $bookingDto->travel->pickupsFro,
|
||||||
|
'choice_label' => 'label',
|
||||||
|
'choice_value' => 'id',
|
||||||
|
'expanded' => false,
|
||||||
|
'multiple' => false,
|
||||||
|
'required' => true,
|
||||||
|
'placeholder' => 'Zustieg auswählen',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Parking (conditional)
|
||||||
|
$this->fieldOptionProviders['parking'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
|
'label' => 'Parkplatz',
|
||||||
|
'choices' => $bookingDto->travel->getAdditionalServicesBySubTypes('PAR', true),
|
||||||
|
'choice_label' => fn(Service $service) => $this->formatServiceLabelWithPrice($service),
|
||||||
|
'choice_value' => 'id',
|
||||||
|
'expanded' => true,
|
||||||
|
'multiple' => false,
|
||||||
|
'required' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format transportation service labels with type and pricing.
|
||||||
|
*/
|
||||||
|
private function formatTransportationServiceLabel(Service $service): string
|
||||||
|
{
|
||||||
|
$label = $service->label;
|
||||||
|
|
||||||
|
// Add transportation type indicator
|
||||||
|
$typeIndicator = match($service->subType) {
|
||||||
|
'BUS' => '🚌',
|
||||||
|
'PKW' => '🚗',
|
||||||
|
default => ''
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($typeIndicator) {
|
||||||
|
$label = $typeIndicator . ' ' . $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add pricing with discount indication
|
||||||
|
if ($service->price > 0) {
|
||||||
|
$label .= sprintf(' (+€%.2f)', $service->price);
|
||||||
|
} elseif ($service->price < 0) {
|
||||||
|
$label .= sprintf(' (-€%.2f Discount)', abs($service->price));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add availability warning if limited
|
||||||
|
if (null !== $service->available && $service->available <= 5) {
|
||||||
|
$label .= sprintf(' (nur %d verfügbar)', $service->available);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $label;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Conditional Field States & UX 🎨
|
||||||
|
|
||||||
|
#### 3.1 Transportation Type Condition
|
||||||
|
|
||||||
|
**File:** `src/Form/Service/Condition/TransportationTypeCondition.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service\Condition;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Condition that evaluates transportation service type.
|
||||||
|
*
|
||||||
|
* Used to show/hide fields based on whether transportation
|
||||||
|
* is bus or self-organized (PKW) for specific directions.
|
||||||
|
*/
|
||||||
|
class TransportationTypeCondition implements FieldConditionInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $direction, // 'outbound' or 'inbound'
|
||||||
|
private readonly string $expectedType, // 'BUS' or 'PKW'
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||||
|
{
|
||||||
|
$participant = $bookingDto->getParticipant($participantIndex);
|
||||||
|
if (null === $participant) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$transportationService = match($this->direction) {
|
||||||
|
'outbound' => $participant->transportationOutbound,
|
||||||
|
'inbound' => $participant->transportationInbound,
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (null === $transportationService) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->expectedType === $transportationService->subType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDependentFields(): array
|
||||||
|
{
|
||||||
|
return match($this->direction) {
|
||||||
|
'outbound' => ['transportationOutbound'],
|
||||||
|
'inbound' => ['transportationInbound'],
|
||||||
|
default => [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return sprintf('%s transportation is %s', ucfirst($this->direction), $this->expectedType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 Update Field State Provider
|
||||||
|
|
||||||
|
**Update:** `src/Form/Service/CreateFieldStateProvider.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Form\Service\Condition\TransportationTypeCondition;
|
||||||
|
use App\Form\Service\Condition\CompositeCondition;
|
||||||
|
|
||||||
|
protected function registerFieldStateConditions(): void
|
||||||
|
{
|
||||||
|
// ... existing conditions
|
||||||
|
|
||||||
|
// Show outbound pickup only when outbound transportation is BUS
|
||||||
|
$this->fieldStateConditions['pickupOutbound'] = [
|
||||||
|
'hidden' => CompositeCondition::not(
|
||||||
|
new TransportationTypeCondition('outbound', 'BUS')
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Show inbound pickup only when inbound transportation is BUS
|
||||||
|
$this->fieldStateConditions['pickupInbound'] = [
|
||||||
|
'hidden' => CompositeCondition::not(
|
||||||
|
new TransportationTypeCondition('inbound', 'BUS')
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Show parking only when at least one direction is PKW
|
||||||
|
$this->fieldStateConditions['parking'] = [
|
||||||
|
'hidden' => CompositeCondition::not(
|
||||||
|
CompositeCondition::or(
|
||||||
|
new TransportationTypeCondition('outbound', 'PKW'),
|
||||||
|
new TransportationTypeCondition('inbound', 'PKW')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Form Integration & Service Configuration ⚙️
|
||||||
|
|
||||||
|
#### 4.1 Update Form Type
|
||||||
|
|
||||||
|
**Update:** `src/Form/BookingCreateParticipantType.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Add transportation fields to dynamic fields list
|
||||||
|
$dynamicFields = [
|
||||||
|
'assignedRoomId',
|
||||||
|
'courses',
|
||||||
|
'additionalServices',
|
||||||
|
'board',
|
||||||
|
'rentals',
|
||||||
|
'skiPass',
|
||||||
|
'transportationOutbound', // New
|
||||||
|
'transportationInbound', // New
|
||||||
|
'pickupOutbound', // New
|
||||||
|
'pickupInbound', // New
|
||||||
|
'parking', // New
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 Service Registration
|
||||||
|
|
||||||
|
**Update:** `config/services.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Transportation field handlers
|
||||||
|
App\Form\Service\ParticipantTransportationOutboundFieldHandler:
|
||||||
|
tags:
|
||||||
|
- { name: 'app.participant_field_handler', field: 'transportationOutbound' }
|
||||||
|
|
||||||
|
App\Form\Service\ParticipantTransportationInboundFieldHandler:
|
||||||
|
tags:
|
||||||
|
- { name: 'app.participant_field_handler', field: 'transportationInbound' }
|
||||||
|
|
||||||
|
App\Form\Service\ParticipantPickupOutboundFieldHandler:
|
||||||
|
tags:
|
||||||
|
- { name: 'app.participant_field_handler', field: 'pickupOutbound' }
|
||||||
|
|
||||||
|
App\Form\Service\ParticipantPickupInboundFieldHandler:
|
||||||
|
tags:
|
||||||
|
- { name: 'app.participant_field_handler', field: 'pickupInbound' }
|
||||||
|
|
||||||
|
App\Form\Service\ParticipantParkingFieldHandler:
|
||||||
|
tags:
|
||||||
|
- { name: 'app.participant_field_handler', field: 'parking' }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.3 Constants Update
|
||||||
|
|
||||||
|
**Update:** `src/BusProNet/Constants.php`
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Add parking token if not already defined
|
||||||
|
public const TOKEN_PARKING = 'PAR';
|
||||||
|
|
||||||
|
// Transportation subtypes
|
||||||
|
public const SUBTYPE_BUS = 'BUS';
|
||||||
|
public const SUBTYPE_PKW = 'PKW';
|
||||||
|
```
|
||||||
|
|
||||||
|
## UX Design & User Experience 🎯
|
||||||
|
|
||||||
|
### Section Organization
|
||||||
|
|
||||||
|
**Transportation will be organized in logical sections:**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Outbound Transportation Section -->
|
||||||
|
<div class="form-section" data-section="transportation-outbound">
|
||||||
|
<h4>🚌 Hinfahrt (Outbound Transportation)</h4>
|
||||||
|
|
||||||
|
<div class="transportation-options">
|
||||||
|
<!-- Radio buttons for transportation type -->
|
||||||
|
{{ form_row(form.transportationOutbound) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Conditional pickup field (shown only for bus) -->
|
||||||
|
<div class="pickup-selection" data-conditional="bus-outbound">
|
||||||
|
{{ form_row(form.pickupOutbound) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inbound Transportation Section -->
|
||||||
|
<div class="form-section" data-section="transportation-inbound">
|
||||||
|
<h4>🚗 Rückfahrt (Inbound Transportation)</h4>
|
||||||
|
|
||||||
|
<div class="transportation-options">
|
||||||
|
{{ form_row(form.transportationInbound) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Conditional pickup field -->
|
||||||
|
<div class="pickup-selection" data-conditional="bus-inbound">
|
||||||
|
{{ form_row(form.pickupInbound) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Parking Section (conditional) -->
|
||||||
|
<div class="form-section" data-section="parking" data-conditional="pkw-selected">
|
||||||
|
<h4>🅿️ Parken (Parking)</h4>
|
||||||
|
{{ form_row(form.parking) }}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Progressive Disclosure Features
|
||||||
|
|
||||||
|
1. **Smart Field Visibility:**
|
||||||
|
- Pickup fields only appear when bus is selected
|
||||||
|
- Parking only appears when PKW is selected
|
||||||
|
- Smooth transitions using existing HTMX integration
|
||||||
|
|
||||||
|
2. **Visual Indicators:**
|
||||||
|
- Transportation type icons (🚌 bus, 🚗 car)
|
||||||
|
- Pricing with discount indicators
|
||||||
|
- Availability warnings for limited services
|
||||||
|
- Required field indicators
|
||||||
|
|
||||||
|
3. **Real-time Feedback:**
|
||||||
|
- Pricing updates immediately
|
||||||
|
- Pickup/parking fields show/hide smoothly
|
||||||
|
- Booking summary reflects transportation selections
|
||||||
|
- Validation feedback on selection changes
|
||||||
|
|
||||||
|
## Pricing Integration 💰
|
||||||
|
|
||||||
|
### Transportation Service Pricing
|
||||||
|
|
||||||
|
- **Bus Services:** Standard pricing per direction
|
||||||
|
- **PKW (Self-organized):** Often negative prices (discounts)
|
||||||
|
- **Parking:** Additional cost for PKW travelers
|
||||||
|
- **Combined Pricing:** Total transportation cost = outbound + inbound + parking
|
||||||
|
|
||||||
|
### Service Label Examples
|
||||||
|
|
||||||
|
- `🚌 Bus nach München (+€45,00)`
|
||||||
|
- `🚗 Eigenanreise (-€20,00 Discount)`
|
||||||
|
- `🅿️ Parkplatz Hotel (+€15,00)`
|
||||||
|
- `🚌 Bus Hinfahrt (nur 3 verfügbar)`
|
||||||
|
|
||||||
|
## Data Flow & Validation 🔄
|
||||||
|
|
||||||
|
### Form Submission Flow
|
||||||
|
|
||||||
|
1. **Transportation Selection:** User selects outbound/inbound transport
|
||||||
|
2. **Conditional Fields Update:** Pickup/parking fields show/hide via HTMX
|
||||||
|
3. **Field Handler Processing:** Services validated against age/availability
|
||||||
|
4. **Pricing Calculation:** Total transportation cost calculated
|
||||||
|
5. **Booking Summary Update:** Summary reflects all transportation selections
|
||||||
|
|
||||||
|
### Validation Rules
|
||||||
|
|
||||||
|
- **Transportation Required:** Both directions must have transportation
|
||||||
|
- **Pickup Required:** When bus is selected, pickup is mandatory
|
||||||
|
- **Parking Optional:** Available only with PKW transportation
|
||||||
|
- **Service Availability:** Validate against available quantities
|
||||||
|
- **Date Constraints:** Services must be valid for travel dates
|
||||||
|
|
||||||
|
## Testing Strategy 🧪
|
||||||
|
|
||||||
|
### Unit Testing Focus
|
||||||
|
|
||||||
|
1. **Direction Mapper:** Test all direction code conversions
|
||||||
|
2. **Field Handlers:** Test transportation/pickup processing logic
|
||||||
|
3. **Conditional States:** Test pickup/parking visibility logic
|
||||||
|
4. **Service Validation:** Test availability and age constraints
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
|
||||||
|
1. **Form Flow:** Complete transportation selection workflow
|
||||||
|
2. **HTMX Updates:** Real-time field visibility and pricing updates
|
||||||
|
3. **Data Processing:** Transportation data for BPN API submission
|
||||||
|
4. **Backward Compatibility:** Ensure existing booking edit still works
|
||||||
|
|
||||||
|
### Manual Testing Scenarios
|
||||||
|
|
||||||
|
1. **Bus Transportation:** Select bus both directions with pickups
|
||||||
|
2. **Mixed Transportation:** Bus one direction, PKW other direction
|
||||||
|
3. **PKW Transportation:** Self-organized both directions with parking
|
||||||
|
4. **Limited Availability:** Test behavior with limited service availability
|
||||||
|
5. **Discount Services:** Verify negative pricing for PKW options
|
||||||
|
|
||||||
|
## Migration Strategy 🔄
|
||||||
|
|
||||||
|
### Backward Compatibility
|
||||||
|
|
||||||
|
1. **Property Mapping:** Update existing code using old property names
|
||||||
|
2. **Direction Constants:** Maintain compatibility with existing direction codes
|
||||||
|
3. **Data Import:** Handle existing bookings with old property structure
|
||||||
|
4. **API Consistency:** Ensure BPN XML submission uses correct direction codes
|
||||||
|
|
||||||
|
### Deployment Steps
|
||||||
|
|
||||||
|
1. **Phase 1:** Deploy direction mapper and updated properties
|
||||||
|
2. **Phase 2:** Deploy field handlers and form integration
|
||||||
|
3. **Phase 3:** Deploy UX improvements and conditional states
|
||||||
|
4. **Phase 4:** Deploy pricing integration and final testing
|
||||||
|
|
||||||
|
## Implementation Timeline 📅
|
||||||
|
|
||||||
|
### Sprint 1: Foundation (Week 1)
|
||||||
|
- ✅ Create documentation
|
||||||
|
- ✅ Implement DirectionMapper utility
|
||||||
|
- ✅ Update ParticipantDto properties
|
||||||
|
- ✅ Update BookingEditDto mapping
|
||||||
|
|
||||||
|
### Sprint 2: Core Implementation (Week 2)
|
||||||
|
- Create transportation field handlers
|
||||||
|
- Create pickup field handlers
|
||||||
|
- Add parking service handler
|
||||||
|
- Update field options provider
|
||||||
|
|
||||||
|
### Sprint 3: UX & Integration (Week 3)
|
||||||
|
- Implement conditional field states
|
||||||
|
- Update form integration
|
||||||
|
- Add transportation service configuration
|
||||||
|
- Implement HTMX real-time updates
|
||||||
|
|
||||||
|
### Sprint 4: Testing & Polish (Week 4)
|
||||||
|
- Add pricing integration
|
||||||
|
- Comprehensive testing
|
||||||
|
- UX refinements
|
||||||
|
- Documentation updates
|
||||||
|
|
||||||
|
## Success Criteria ✅
|
||||||
|
|
||||||
|
### Technical Success
|
||||||
|
- [ ] Direction mapping handles all BPN inconsistencies correctly
|
||||||
|
- [ ] Transportation services integrate with existing pricing system
|
||||||
|
- [ ] Conditional pickup/parking fields work seamlessly
|
||||||
|
- [ ] HTMX updates provide smooth UX
|
||||||
|
- [ ] Field handlers follow established patterns
|
||||||
|
- [ ] Backward compatibility maintained
|
||||||
|
|
||||||
|
### UX Success
|
||||||
|
- [ ] Clear separation of outbound/inbound transportation
|
||||||
|
- [ ] Progressive disclosure prevents overwhelming users
|
||||||
|
- [ ] Visual indicators for discounts and availability
|
||||||
|
- [ ] Real-time pricing feedback
|
||||||
|
- [ ] Intuitive field organization
|
||||||
|
- [ ] Mobile-responsive transportation selection
|
||||||
|
|
||||||
|
### Business Success
|
||||||
|
- [ ] Support for complex transportation scenarios
|
||||||
|
- [ ] Parking space booking integration
|
||||||
|
- [ ] Discount management for self-organized travel
|
||||||
|
- [ ] Data integrity for BPN API submission
|
||||||
|
- [ ] Scalable architecture for future enhancements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2025-09-02
|
||||||
|
**Status:** 🚧 Implementation in Progress
|
||||||
|
**Next Phase:** Direction Mapping & Naming Foundation
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\BusProNet\Utility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles direction mapping between BusProNet's inconsistent direction codes.
|
||||||
|
*
|
||||||
|
* BusProNet uses different direction codes in different contexts:
|
||||||
|
* - Travel data: 'HIN' (outbound), 'RUECK' (inbound)
|
||||||
|
* - Booking data: 'H' (outbound), 'R' (inbound)
|
||||||
|
*
|
||||||
|
* This utility provides consistent mapping between formats and enables
|
||||||
|
* clean English naming for internal application use.
|
||||||
|
*/
|
||||||
|
final class DirectionMapper
|
||||||
|
{
|
||||||
|
// Travel data format (full German words)
|
||||||
|
public const OUTBOUND_TRAVEL = 'HIN';
|
||||||
|
public const INBOUND_TRAVEL = 'RUECK';
|
||||||
|
|
||||||
|
// Booking data format (single letter abbreviations)
|
||||||
|
public const OUTBOUND_BOOKING = 'H';
|
||||||
|
public const INBOUND_BOOKING = 'R';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps travel direction code to booking direction code.
|
||||||
|
*
|
||||||
|
* @param string $travelDirection The travel direction code ('HIN' or 'RUECK')
|
||||||
|
*
|
||||||
|
* @return string The booking direction code ('H' or 'R')
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException When direction code is not recognized
|
||||||
|
*/
|
||||||
|
public static function travelToBooking(string $travelDirection): string
|
||||||
|
{
|
||||||
|
return match ($travelDirection) {
|
||||||
|
self::OUTBOUND_TRAVEL => self::OUTBOUND_BOOKING,
|
||||||
|
self::INBOUND_TRAVEL => self::INBOUND_BOOKING,
|
||||||
|
default => throw new \InvalidArgumentException("Unknown travel direction: $travelDirection"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps booking direction code to travel direction code.
|
||||||
|
*
|
||||||
|
* @param string $bookingDirection The booking direction code ('H' or 'R')
|
||||||
|
*
|
||||||
|
* @return string The travel direction code ('HIN' or 'RUECK')
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException When direction code is not recognized
|
||||||
|
*/
|
||||||
|
public static function bookingToTravel(string $bookingDirection): string
|
||||||
|
{
|
||||||
|
return match ($bookingDirection) {
|
||||||
|
self::OUTBOUND_BOOKING => self::OUTBOUND_TRAVEL,
|
||||||
|
self::INBOUND_BOOKING => self::INBOUND_TRAVEL,
|
||||||
|
default => throw new \InvalidArgumentException("Unknown booking direction: $bookingDirection"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all outbound direction codes.
|
||||||
|
*
|
||||||
|
* @return string[] Array of outbound direction codes in all formats
|
||||||
|
*/
|
||||||
|
public static function getOutboundCodes(): array
|
||||||
|
{
|
||||||
|
return [self::OUTBOUND_TRAVEL, self::OUTBOUND_BOOKING];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all inbound direction codes.
|
||||||
|
*
|
||||||
|
* @return string[] Array of inbound direction codes in all formats
|
||||||
|
*/
|
||||||
|
public static function getInboundCodes(): array
|
||||||
|
{
|
||||||
|
return [self::INBOUND_TRAVEL, self::INBOUND_BOOKING];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a direction code is outbound.
|
||||||
|
*
|
||||||
|
* @param string $direction The direction code to check
|
||||||
|
*
|
||||||
|
* @return bool True if the direction is outbound, false otherwise
|
||||||
|
*/
|
||||||
|
public static function isOutbound(string $direction): bool
|
||||||
|
{
|
||||||
|
return in_array($direction, self::getOutboundCodes(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a direction code is inbound.
|
||||||
|
*
|
||||||
|
* @param string $direction The direction code to check
|
||||||
|
*
|
||||||
|
* @return bool True if the direction is inbound, false otherwise
|
||||||
|
*/
|
||||||
|
public static function isInbound(string $direction): bool
|
||||||
|
{
|
||||||
|
return in_array($direction, self::getInboundCodes(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that a direction code is recognized.
|
||||||
|
*
|
||||||
|
* @param string $direction The direction code to validate
|
||||||
|
*
|
||||||
|
* @return bool True if the direction is valid, false otherwise
|
||||||
|
*/
|
||||||
|
public static function isValid(string $direction): bool
|
||||||
|
{
|
||||||
|
return self::isOutbound($direction) || self::isInbound($direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use App\BusProNet\Constants;
|
|||||||
use App\BusProNet\Model\Booking;
|
use App\BusProNet\Model\Booking;
|
||||||
use App\BusProNet\Model\PersonalData;
|
use App\BusProNet\Model\PersonalData;
|
||||||
use App\BusProNet\Model\Travel;
|
use App\BusProNet\Model\Travel;
|
||||||
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
class BookingEditDto implements BookingDtoInterface
|
class BookingEditDto implements BookingDtoInterface
|
||||||
@@ -46,12 +47,25 @@ class BookingEditDto implements BookingDtoInterface
|
|||||||
->getAdditionalServicesForParticipantByGroup($index, Constants::TOKEN_BOARD);
|
->getAdditionalServicesForParticipantByGroup($index, Constants::TOKEN_BOARD);
|
||||||
$participantData->rentals = $booking
|
$participantData->rentals = $booking
|
||||||
->getAdditionalServicesForParticipantByGroup($index, Constants::TOKEN_RENTALS);
|
->getAdditionalServicesForParticipantByGroup($index, Constants::TOKEN_RENTALS);
|
||||||
// Different keys for direction used in booking data (H <=> HIN, R <=> RUECK)!
|
// Transportation services using improved direction mapping
|
||||||
$participantData->transportationServiceTo = $booking
|
// Direction mapping handles BusProNet's inconsistent codes (H <=> HIN, R <=> RUECK)
|
||||||
->getTransportationServiceForParticipantAndDirection($index, 'H');
|
$outboundTransportation = $booking
|
||||||
$participantData->transportationServiceFro = $booking
|
->getTransportationServiceForParticipantAndDirection($index, DirectionMapper::OUTBOUND_BOOKING);
|
||||||
->getTransportationServiceForParticipantAndDirection($index, 'R');
|
$inboundTransportation = $booking
|
||||||
$participantData->pickup = $booking->getPickupForParticipant($index);
|
->getTransportationServiceForParticipantAndDirection($index, DirectionMapper::INBOUND_BOOKING);
|
||||||
|
|
||||||
|
// Set new improved property names
|
||||||
|
$participantData->transportationOutbound = $outboundTransportation;
|
||||||
|
$participantData->transportationInbound = $inboundTransportation;
|
||||||
|
|
||||||
|
// Backward compatibility: also set deprecated properties
|
||||||
|
$participantData->transportationServiceTo = $outboundTransportation;
|
||||||
|
$participantData->transportationServiceFro = $inboundTransportation;
|
||||||
|
|
||||||
|
// Pickup handling (currently only supports outbound pickup)
|
||||||
|
$pickupOutbound = $booking->getPickupForParticipant($index);
|
||||||
|
$participantData->pickupOutbound = $pickupOutbound;
|
||||||
|
$participantData->pickup = $pickupOutbound; // Backward compatibility
|
||||||
|
|
||||||
$instance->participants[$index] = $participantData;
|
$instance->participants[$index] = $participantData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,19 @@ class ParticipantDto
|
|||||||
public ?Service $skiPass = null;
|
public ?Service $skiPass = null;
|
||||||
public array $board = [];
|
public array $board = [];
|
||||||
public array $rentals = [];
|
public array $rentals = [];
|
||||||
|
|
||||||
|
// Transportation services with improved naming (outbound/inbound)
|
||||||
|
public ?Service $transportationOutbound = null;
|
||||||
|
public ?Service $transportationInbound = null;
|
||||||
|
|
||||||
|
// Pickup locations for each direction
|
||||||
|
public ?Pickup $pickupOutbound = null;
|
||||||
|
public ?Pickup $pickupInbound = null;
|
||||||
|
|
||||||
|
// Parking service for self-organized transportation
|
||||||
|
public ?Service $parking = null;
|
||||||
|
|
||||||
|
// Deprecated properties for backward compatibility - will be removed in future version
|
||||||
public ?Service $transportationServiceTo = null;
|
public ?Service $transportationServiceTo = null;
|
||||||
public ?Service $transportationServiceFro = null;
|
public ?Service $transportationServiceFro = null;
|
||||||
public ?Pickup $pickup = null;
|
public ?Pickup $pickup = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user