wip: transportation services

This commit is contained in:
Björn Fromme committed 2026-03-16 11:59:10 +01:00
1 parent a019c7dae0
commit 81b8237570
18 files changed
+1113 -59

No files matched your search

@@ -87,6 +87,7 @@ class RoomSelectionCondition implements FieldConditionInterface
public function getDescription(): string
{
$codes = implode(', ', $this->requiredRoomCodes);
return "Field visible when room with code(s) [{$codes}] is selected";
}
@@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace App\Form\Service\Condition;
use App\BusProNet\Model\Service;
use App\Form\Model\BookingDtoInterface;
use App\Form\Service\Contract\FieldConditionInterface;
/**
* Condition that evaluates service sub-types.
*
* This condition enables field state logic based on the sub-type property
* of Service objects. It can be used with any service field (transportation,
* additional services, etc.) to show/hide or enable/disable dependent fields
* based on the selected service type.
*
* Common Use Cases:
* - Show/hide pickup fields when transportation sub-type is BUS
* - Show/hide parking fields when transportation sub-type is PKW
* - Enable/disable fields based on service category selection
* - Conditional logic for different service types
*/
class ServiceSubTypeCondition implements FieldConditionInterface
{
private const OPERATOR_EQUALS = 'equals';
private const OPERATOR_NOT_EQUALS = 'not_equals';
private const OPERATOR_IN = 'in';
private const OPERATOR_NOT_IN = 'not_in';
/**
* Creates a new service sub-type condition.
*
* @param string $serviceFieldName The name of the service field to check
* @param string $operator The comparison operator to use
* @param string|string[] $expectedSubType The expected service sub-type(s)
*/
public function __construct(
private readonly string $serviceFieldName,
private readonly string $operator,
private readonly string|array $expectedSubType,
) {
$this->validateOperator($operator);
$this->validateExpectedSubType($operator, $expectedSubType);
}
/**
* Evaluates the service sub-type condition against current form data.
*
* Retrieves the service from the specified field and checks its subType
* property against the expected value(s). Handles both form data and
* participant DTO data sources.
*
* @param BookingDtoInterface $bookingDto The current booking data (create or edit)
* @param int $participantIndex The index of the participant being evaluated
* @param array<string, mixed> $formData Current form data for condition evaluation
*
* @return bool True if the service sub-type meets the condition criteria, false otherwise
*/
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
{
$service = $this->getService($formData, $participantIndex, $bookingDto);
if (null === $service) {
// If no service is selected, condition fails
return false;
}
$actualSubType = $service->subType ?? '';
return match ($this->operator) {
self::OPERATOR_EQUALS => $actualSubType === $this->expectedSubType,
self::OPERATOR_NOT_EQUALS => $actualSubType !== $this->expectedSubType,
self::OPERATOR_IN => $this->isSubTypeIn($actualSubType, $this->expectedSubType),
self::OPERATOR_NOT_IN => !$this->isSubTypeIn($actualSubType, $this->expectedSubType),
default => false,
};
}
/**
* Returns the field names that this condition depends on.
*
* This condition depends on the service field being evaluated, so any changes
* to that field should trigger re-evaluation of dependent field states.
*
* @return string[] Array containing the service field name being evaluated
*/
public function getDependentFields(): array
{
return [$this->serviceFieldName];
}
/**
* Returns a human-readable description of the service sub-type condition.
*
* Generates a descriptive string explaining what service field is being
* checked and what the expected sub-type criteria are.
*
* @return string Description of the service sub-type condition
*/
public function getDescription(): string
{
$subTypeDescription = is_array($this->expectedSubType)
? '['.implode(', ', $this->expectedSubType).']'
: (string) $this->expectedSubType;
return match ($this->operator) {
self::OPERATOR_EQUALS => sprintf('Service "%s" sub-type equals %s', $this->serviceFieldName, $subTypeDescription),
self::OPERATOR_NOT_EQUALS => sprintf('Service "%s" sub-type does not equal %s', $this->serviceFieldName, $subTypeDescription),
self::OPERATOR_IN => sprintf('Service "%s" sub-type is in %s', $this->serviceFieldName, $subTypeDescription),
self::OPERATOR_NOT_IN => sprintf('Service "%s" sub-type is not in %s', $this->serviceFieldName, $subTypeDescription),
default => sprintf('Service "%s" sub-type %s %s', $this->serviceFieldName, $this->operator, $subTypeDescription),
};
}
/**
* Creates a condition for service sub-type equality.
*
* @param string $serviceFieldName The service field to check
* @param string $expectedSubType The expected service sub-type
*/
public static function equals(string $serviceFieldName, string $expectedSubType): self
{
return new self($serviceFieldName, self::OPERATOR_EQUALS, $expectedSubType);
}
/**
* Creates a condition for service sub-type non-equality.
*
* @param string $serviceFieldName The service field to check
* @param string $expectedSubType The sub-type that should not match
*/
public static function notEquals(string $serviceFieldName, string $expectedSubType): self
{
return new self($serviceFieldName, self::OPERATOR_NOT_EQUALS, $expectedSubType);
}
/**
* Creates a condition for service sub-type inclusion.
*
* @param string $serviceFieldName The service field to check
* @param string[] $allowedSubTypes The allowed sub-types
*/
public static function in(string $serviceFieldName, array $allowedSubTypes): self
{
return new self($serviceFieldName, self::OPERATOR_IN, $allowedSubTypes);
}
/**
* Creates a condition for service sub-type exclusion.
*
* @param string $serviceFieldName The service field to check
* @param string[] $excludedSubTypes The excluded sub-types
*/
public static function notIn(string $serviceFieldName, array $excludedSubTypes): self
{
return new self($serviceFieldName, self::OPERATOR_NOT_IN, $excludedSubTypes);
}
/**
* Retrieves service from form data or participant data.
*/
private function getService(array $formData, int $participantIndex, BookingDtoInterface $bookingDto): ?Service
{
// First check participant-specific form data
if (isset($formData['participants'][$participantIndex][$this->serviceFieldName])) {
$serviceData = $formData['participants'][$participantIndex][$this->serviceFieldName];
// Form data might contain Service objects or service IDs
if ($serviceData instanceof Service) {
return $serviceData;
}
}
// Then check participant DTO data
$participant = $bookingDto->getParticipant($participantIndex);
if (null !== $participant && property_exists($participant, $this->serviceFieldName)) {
return $participant->{$this->serviceFieldName};
}
return null;
}
/**
* Checks if sub-type is in the expected array.
*/
private function isSubTypeIn(string $actualSubType, string|array $expectedSubTypes): bool
{
if (is_string($expectedSubTypes)) {
return $actualSubType === $expectedSubTypes;
}
return in_array($actualSubType, $expectedSubTypes, true);
}
/**
* Validates that the operator is supported.
*/
private function validateOperator(string $operator): void
{
$validOperators = [
self::OPERATOR_EQUALS,
self::OPERATOR_NOT_EQUALS,
self::OPERATOR_IN,
self::OPERATOR_NOT_IN,
];
if (!in_array($operator, $validOperators, true)) {
throw new \InvalidArgumentException(sprintf('Invalid operator "%s". Valid operators: %s', $operator, implode(', ', $validOperators)));
}
}
/**
* Validates expected sub-type based on operator requirements.
*/
private function validateExpectedSubType(string $operator, string|array $expectedSubType): void
{
if (in_array($operator, [self::OPERATOR_IN, self::OPERATOR_NOT_IN], true) && !is_array($expectedSubType)) {
throw new \InvalidArgumentException(sprintf('Operator "%s" requires expectedSubType to be an array', $operator));
}
if (in_array($operator, [self::OPERATOR_EQUALS, self::OPERATOR_NOT_EQUALS], true) && !is_string($expectedSubType)) {
throw new \InvalidArgumentException(sprintf('Operator "%s" requires expectedSubType to be a string', $operator));
}
}
}