wip: transportation services

This commit is contained in:
Björn Fromme
2026-03-16 11:59:10 +01:00
parent a019c7dae0
commit 81b8237570
18 changed files with 1113 additions and 59 deletions
@@ -5,7 +5,9 @@ declare(strict_types=1);
namespace App\Form\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Service;
use App\BusProNet\Utility\DirectionMapper;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDtoInterface;
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
@@ -187,6 +189,75 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
],
];
// Transportation field providers - handles outbound/inbound transportation and pickup selection
// Outbound Transportation
$this->fieldOptionProviders['transportationOutbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
'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' => '#', // Will be configured when HTMX integration is implemented
'hx-target' => '#booking-summary',
'hx-trigger' => 'change',
],
];
// Inbound Transportation
$this->fieldOptionProviders['transportationInbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
'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' => '#', // Will be configured when HTMX integration is implemented
'hx-target' => '#booking-summary',
'hx-trigger' => 'change',
],
];
// Outbound Pickup (conditional - only shown when outbound transportation is bus)
$this->fieldOptionProviders['pickupOutbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Zustieg Hinfahrt',
'choices' => $bookingDto->travel->pickupsTo,
'choice_label' => fn (Pickup $pickup) => $this->formatPickupLabel($pickup),
'choice_value' => 'id',
'expanded' => false, // Dropdown for pickups
'multiple' => false,
'required' => true,
'placeholder' => 'Zustieg auswählen',
];
// Inbound Pickup (conditional - only shown when inbound transportation is bus)
$this->fieldOptionProviders['pickupInbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Zustieg Rückfahrt',
'choices' => $bookingDto->travel->pickupsFro,
'choice_label' => fn (Pickup $pickup) => $this->formatPickupLabel($pickup),
'choice_value' => 'id',
'expanded' => false,
'multiple' => false,
'required' => true,
'placeholder' => 'Zustieg auswählen',
];
// Parking (conditional - only shown when at least one transportation direction is PKW)
$this->fieldOptionProviders['parking'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Parkplatz',
'choices' => $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_PARKING, true),
'choice_label' => fn (?Service $service) => $this->formatServiceLabelWithPrice($service),
'choice_value' => 'id',
'expanded' => true,
'multiple' => false,
'required' => false,
];
// Future field providers would be added here, for example:
//
// $this->fieldOptionProviders['mealPreference'] = fn($bookingDto, $participantIndex) => [
@@ -219,6 +290,73 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
return sprintf('%s (€%s)', $service->label, number_format($service->price, 2, ',', '.'));
}
/**
* Format transportation service labels with type indicator and pricing.
*
* Creates user-friendly labels for transportation services that include:
* - Transportation type icon (🚌 for bus, 🚗 for car)
* - Service name
* - Pricing (with discount indication for negative prices)
* - Availability warning for limited services
*
* @param Service $service The transportation service to format
*
* @return string The formatted transportation service label
*/
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 (null !== $service->price) {
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;
}
/**
* Format pickup labels with city and street information.
*
* Creates user-friendly labels for pickup locations following the existing pattern:
* - Primary format: "City (Street)" if street is available
* - Fallback format: "City" if no street information
*
* @param Pickup $pickup The pickup location to format
*
* @return string The formatted pickup location label
*/
private function formatPickupLabel(Pickup $pickup): string
{
$label = $pickup->city ?? '';
if (null !== $pickup->street && '' !== trim($pickup->street)) {
$label .= ' ('.$pickup->street.')';
}
return $label;
}
/**
* Filters services based on participant's age constraints.
*