feat: unify pickup selection

This commit is contained in:
Björn Fromme
2026-03-16 11:59:10 +01:00
parent 0f4b16c194
commit e330626b03
19 changed files with 119 additions and 325 deletions
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\Form\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Insurance;
use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Service;
use App\BusProNet\Utility\DirectionMapper;
@@ -377,28 +376,17 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
],
];
// Outbound Pickup (conditional - only shown when outbound transportation is bus)
$this->fieldOptionProviders['pickupOutbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Zustieg Hinfahrt',
// Pickup (conditional - only shown when either transportation direction is bus)
// Uses outbound pickups list, applies to both directions
$this->fieldOptionProviders['pickup'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Zu- und Ausstieg',
'choices' => $bookingDto->travel->pickupsOutbound,
'choice_label' => fn (?Pickup $pickup) => $this->formatPickupLabelWithPrice($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' => 'Ausstieg Rückfahrt',
'choices' => $bookingDto->travel->pickupsInbound,
'choice_label' => fn (?Pickup $pickup) => $this->formatPickupLabelWithPrice($pickup),
'choice_value' => 'id',
'expanded' => false,
'multiple' => false,
'required' => true,
'placeholder' => 'Ausstieg auswählen',
'placeholder' => 'Zu- und Ausstieg auswählen',
];
// Parking (conditional - only shown when outbound transportation is PKW)
@@ -574,29 +562,6 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
return $this->formatServiceLabelWithPrice($parkingService);
}
/**
* Filters services based on remaining availability in the current booking session.
*
* Removes services that have been fully booked by other participants in
* the current booking session. This prevents overbooking within a single
* booking workflow while maintaining accurate availability counts.
*
* @param array $services Array of Service objects to filter
* @param BookingDtoInterface $bookingDto The booking DTO containing participant data
* @param int $participantIndex Index of the participant currently selecting services
*
* @return array Filtered array containing only services with remaining availability
*/
private function filterServicesByAvailability(array $services, BookingDtoInterface $bookingDto, int $participantIndex): array
{
if (!$bookingDto instanceof BookingCreateDto) {
// For non-create workflows, return all services (no availability tracking needed)
return $services;
}
return $this->serviceAvailabilityCalculator->filterAvailableServices($services, $bookingDto, $participantIndex);
}
/**
* Checks if a service should be rendered as read-only due to unavailability.
*
@@ -710,27 +675,4 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
$bookingDto
);
}
/**
* Formats insurance label with pricing and type information.
*
* @param Insurance|null $insurance The insurance to format, or null for "No Insurance" option
*
* @return string The formatted insurance label
*/
private function formatInsuranceLabel(?Insurance $insurance): string
{
if (null === $insurance) {
return 'Keine Versicherung';
}
$label = $insurance->label;
// Add pricing information (consistent with other services)
if (null !== $insurance->price && $insurance->price > 0) {
$label .= sprintf(' (€%s)', number_format($insurance->price, 2, ',', '.'));
}
return $label;
}
}