feat: unify pickup selection
This commit is contained in:
@@ -179,17 +179,13 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
||||
),
|
||||
];
|
||||
|
||||
// Show outbound pickup only when transportation is BUS (hidden by default)
|
||||
$this->fieldStateConditions['pickupOutbound'] = [
|
||||
// Show pickup only when either outbound or inbound transportation is BUS (hidden by default)
|
||||
$this->fieldStateConditions['pickup'] = [
|
||||
'hidden' => CompositeCondition::not(
|
||||
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_BUS_API)
|
||||
),
|
||||
];
|
||||
|
||||
// Show inbound pickup only when transportation is BUS (hidden by default)
|
||||
$this->fieldStateConditions['pickupInbound'] = [
|
||||
'hidden' => CompositeCondition::not(
|
||||
ServiceSubTypeCondition::equals('transportationInbound', DirectionMapper::SUBTYPE_BUS_API)
|
||||
CompositeCondition::or(
|
||||
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_BUS_API),
|
||||
ServiceSubTypeCondition::equals('transportationInbound', DirectionMapper::SUBTYPE_BUS_API)
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
@@ -121,17 +121,13 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
|
||||
'readonly' => $transportationServicesMutabilityCondition,
|
||||
];
|
||||
|
||||
// Pickup fields - shown only when transportation is BUS, readonly if pickups not mutable
|
||||
$this->fieldStateConditions['pickupOutbound'] = [
|
||||
// Pickup field (unified) - shown only when either transportation direction is BUS, readonly if pickups not mutable
|
||||
$this->fieldStateConditions['pickup'] = [
|
||||
'hidden' => CompositeCondition::not(
|
||||
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_BUS_API)
|
||||
),
|
||||
'readonly' => $pickupsMutabilityCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['pickupInbound'] = [
|
||||
'hidden' => CompositeCondition::not(
|
||||
ServiceSubTypeCondition::equals('transportationInbound', DirectionMapper::SUBTYPE_BUS_API)
|
||||
CompositeCondition::or(
|
||||
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_BUS_API),
|
||||
ServiceSubTypeCondition::equals('transportationInbound', DirectionMapper::SUBTYPE_BUS_API)
|
||||
)
|
||||
),
|
||||
'readonly' => $pickupsMutabilityCondition,
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,7 @@ class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
||||
'board', // Affects travel price
|
||||
'transportationOutbound', // Affects travel price
|
||||
'transportationInbound', // Affects travel price
|
||||
'pickupOutbound', // Affects travel price
|
||||
'pickupInbound', // Affects travel price
|
||||
'pickup', // Affects travel price
|
||||
'parking', // Affects travel price
|
||||
];
|
||||
}
|
||||
|
||||
+20
-26
@@ -10,36 +10,26 @@ use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||
|
||||
/**
|
||||
* Handles outbound pickup location selection.
|
||||
* Handles unified pickup location selection.
|
||||
*
|
||||
* Pickup selection is only processed when outbound transportation
|
||||
* is bus type. Automatically clears pickup when transportation
|
||||
* changes to self-organized (PKW).
|
||||
* Pickup selection is only processed when either outbound or inbound
|
||||
* transportation is bus type. Automatically clears pickup when both
|
||||
* transportation services change to self-organized (PKW).
|
||||
*
|
||||
* The pickup applies to both directions and uses the outbound pickup list.
|
||||
*/
|
||||
class ParticipantPickupOutboundFieldHandler extends AbstractParticipantFieldHandler
|
||||
class ParticipantPickupFieldHandler extends AbstractParticipantFieldHandler
|
||||
{
|
||||
public function getFieldName(): string
|
||||
{
|
||||
return 'pickupOutbound';
|
||||
return 'pickup';
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return ['transportationOutbound']; // Must process transportation first
|
||||
return ['transportationOutbound', 'transportationInbound'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this handler should process the field based on submitted data.
|
||||
*
|
||||
* For pickup selection fields, we always need to process to handle cases
|
||||
* where the pickup is cleared due to transportation changes. This ensures the
|
||||
* participant DTO is updated correctly when transportation switches from bus to PKW.
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param int $participantIndex The index of the participant being processed
|
||||
*
|
||||
* @return bool Always returns true for pickup selection fields
|
||||
*/
|
||||
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
||||
{
|
||||
return true; // Always process to handle clearing pickup
|
||||
@@ -52,10 +42,15 @@ class ParticipantPickupOutboundFieldHandler extends AbstractParticipantFieldHand
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process pickup if outbound transportation is bus
|
||||
if (null === $participant->transportationOutbound || DirectionMapper::SUBTYPE_BUS_API !== $participant->transportationOutbound->subType) {
|
||||
$participant->pickupOutbound = null; // Clear pickup for non-bus transport
|
||||
$participant->pickup = null; // Backward compatibility
|
||||
// Check if either outbound or inbound transportation is bus
|
||||
$hasOutboundBus = null !== $participant->transportationOutbound
|
||||
&& DirectionMapper::SUBTYPE_BUS_API === $participant->transportationOutbound->subType;
|
||||
$hasInboundBus = null !== $participant->transportationInbound
|
||||
&& DirectionMapper::SUBTYPE_BUS_API === $participant->transportationInbound->subType;
|
||||
|
||||
// Only process pickup if at least one direction has bus transport
|
||||
if (false === $hasOutboundBus && false === $hasInboundBus) {
|
||||
$participant->pickup = null; // Clear pickup for non-bus transport
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -68,8 +63,7 @@ class ParticipantPickupOutboundFieldHandler extends AbstractParticipantFieldHand
|
||||
$validSelection = $this->findValidPickup($selectedPickup, $bookingDto->travel->pickupsOutbound);
|
||||
}
|
||||
|
||||
$participant->pickupOutbound = $validSelection;
|
||||
$participant->pickup = $validSelection; // Backward compatibility
|
||||
$participant->pickup = $validSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,4 +90,4 @@ class ParticipantPickupOutboundFieldHandler extends AbstractParticipantFieldHand
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service;
|
||||
|
||||
use App\BusProNet\Model\Pickup;
|
||||
use App\BusProNet\Utility\DirectionMapper;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||
|
||||
/**
|
||||
* Handles inbound pickup location selection.
|
||||
*
|
||||
* Pickup selection is only processed when inbound transportation
|
||||
* is bus type. Automatically clears pickup when transportation
|
||||
* changes to self-organized (PKW).
|
||||
*/
|
||||
class ParticipantPickupInboundFieldHandler extends AbstractParticipantFieldHandler
|
||||
{
|
||||
public function getFieldName(): string
|
||||
{
|
||||
return 'pickupInbound';
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return ['transportationInbound']; // Must process transportation first
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this handler should process the field based on submitted data.
|
||||
*
|
||||
* For pickup selection fields, we always need to process to handle cases
|
||||
* where the pickup is cleared due to transportation changes. This ensures the
|
||||
* participant DTO is updated correctly when transportation switches from bus to PKW.
|
||||
*
|
||||
* @param array<string, mixed> $submittedData The submitted participant form data
|
||||
* @param int $participantIndex The index of the participant being processed
|
||||
*
|
||||
* @return bool Always returns true for pickup selection fields
|
||||
*/
|
||||
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
||||
{
|
||||
return true; // Always process to handle clearing pickup
|
||||
}
|
||||
|
||||
public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void
|
||||
{
|
||||
$participant = $this->getParticipant($bookingDto, $participantIndex);
|
||||
if (null === $participant) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process pickup if inbound transportation is bus
|
||||
if (null === $participant->transportationInbound || DirectionMapper::SUBTYPE_BUS_API !== $participant->transportationInbound->subType) {
|
||||
$participant->pickupInbound = null; // Clear pickup for non-bus transport
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$selectedPickup = $this->getFieldValue($submittedData, $this->getFieldName());
|
||||
|
||||
// Validate pickup selection against available inbound pickups
|
||||
$validSelection = null;
|
||||
if (null !== $selectedPickup) {
|
||||
$validSelection = $this->findValidPickup($selectedPickup, $bookingDto->travel->pickupsInbound);
|
||||
}
|
||||
|
||||
$participant->pickupInbound = $validSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a valid pickup from available pickups.
|
||||
*
|
||||
* @param mixed $selectedPickupId The submitted pickup ID
|
||||
* @param array<int, Pickup> $availablePickups Array of available pickup locations
|
||||
*
|
||||
* @return Pickup|null The valid pickup object, or null if invalid
|
||||
*/
|
||||
private function findValidPickup(mixed $selectedPickupId, array $availablePickups): ?Pickup
|
||||
{
|
||||
if (null === $selectedPickupId || false === is_string($selectedPickupId) && false === is_int($selectedPickupId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pickupId = (int) $selectedPickupId;
|
||||
|
||||
foreach ($availablePickups as $pickup) {
|
||||
if ($pickup->id === $pickupId) {
|
||||
return $pickup;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user