wip: additional services with constraints
This commit is contained in:
@@ -163,7 +163,7 @@ class BookingCreateParticipantType extends AbstractType
|
||||
*/
|
||||
private function addDynamicFields(FormInterface $form, BookingDtoInterface $bookingDto, int $participantIndex): void
|
||||
{
|
||||
$dynamicFields = ['assignedRoomId', 'courses']; // List of fields that need dynamic configuration
|
||||
$dynamicFields = ['assignedRoomId', 'courses', 'additionalServices', 'board', 'rentals']; // List of fields that need dynamic configuration
|
||||
|
||||
foreach ($dynamicFields as $fieldName) {
|
||||
if ($this->fieldOptionsProvider->hasFieldOptions($fieldName)) {
|
||||
|
||||
@@ -70,7 +70,7 @@ class BookingEditType extends AbstractType
|
||||
{
|
||||
// combine selectable services from travel data with additional services
|
||||
// from booking data
|
||||
$selectableServices = $data->travel->getAdditionalServicesByGroup($subType);
|
||||
$selectableServices = $data->travel->getAdditionalServicesBySubTypes($subType);
|
||||
$selectableServiceIds = array_map(function (Service $service) {
|
||||
return $service->id;
|
||||
}, $selectableServices);
|
||||
|
||||
@@ -83,8 +83,8 @@ class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface
|
||||
--$adjustedOccupancy;
|
||||
}
|
||||
|
||||
$totalAvailableRooms = $roomSelection->quantity;
|
||||
$remainingCapacity = $totalAvailableRooms - $adjustedOccupancy;
|
||||
$totalCapacity = $roomSelection->quantity * $roomSelection->capacity;
|
||||
$remainingCapacity = $totalCapacity - $adjustedOccupancy;
|
||||
|
||||
// Include room if it has capacity OR if it's the participant's current assignment
|
||||
if ($remainingCapacity > 0 || $assignedRoomId === $roomSelection->roomId) {
|
||||
|
||||
@@ -15,5 +15,5 @@ class RoomSelectionDto
|
||||
|
||||
public int $maxQuantity = 100;
|
||||
|
||||
public int $minPax = 0;
|
||||
public int $capacity = 0;
|
||||
}
|
||||
|
||||
@@ -141,4 +141,4 @@ abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
||||
* specific field state conditions.
|
||||
*/
|
||||
abstract protected function registerFieldStateConditions(): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandle
|
||||
protected function getParticipant(BookingDtoInterface $bookingDto, int $participantIndex): ?object
|
||||
{
|
||||
$participants = $bookingDto->getParticipants();
|
||||
|
||||
return $participants[$participantIndex] ?? null;
|
||||
}
|
||||
|
||||
@@ -151,4 +152,4 @@ abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandle
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,4 +138,4 @@ class AgeRangeCondition implements FieldConditionInterface
|
||||
|
||||
return (int) $dateOfBirth->diff($today)->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class ApplicantCondition implements FieldConditionInterface
|
||||
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
// Default: applicant is the first participant (index 0)
|
||||
return $participantIndex === 0;
|
||||
return 0 === $participantIndex;
|
||||
}
|
||||
|
||||
public function getDependentFields(): array
|
||||
@@ -42,4 +42,4 @@ class ApplicantCondition implements FieldConditionInterface
|
||||
{
|
||||
return 'Checks if the participant is the applicant (index 0)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,4 +231,4 @@ class CompositeCondition implements FieldConditionInterface
|
||||
throw new \InvalidArgumentException(sprintf('%s operator requires at least one condition', $operator));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,4 +263,4 @@ class FieldValueCondition implements FieldConditionInterface
|
||||
throw new \InvalidArgumentException(sprintf('Operator "%s" does not accept expectedValue parameter', $operator));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,4 @@ class MutabilityCondition implements FieldConditionInterface
|
||||
{
|
||||
return 'Checks if the participant\'s personal data is mutable (edit flow)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,4 +59,4 @@ interface FieldConditionInterface
|
||||
* @return string A brief description of the condition logic
|
||||
*/
|
||||
public function getDescription(): string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,4 @@ interface FieldOptionsProviderInterface
|
||||
* @return bool True if the field has registered option providers, false otherwise
|
||||
*/
|
||||
public function hasFieldOptions(string $fieldName): bool;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,4 +98,4 @@ interface FieldStateProviderInterface
|
||||
* @return array<string, array<string, mixed>> Field states indexed by field name
|
||||
*/
|
||||
public function getAllFieldStates(BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,4 +67,4 @@ interface ParticipantFieldHandlerInterface
|
||||
* @return string[] Array of field names that this handler may modify the state of
|
||||
*/
|
||||
public function getAffectedFieldNames(): array;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service;
|
||||
|
||||
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||
use App\Form\Service\Condition\ApplicantCondition;
|
||||
use App\Form\Service\Condition\CompositeCondition;
|
||||
use App\Form\Service\Condition\MutabilityCondition;
|
||||
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||
|
||||
/**
|
||||
* Field state provider for the booking edit workflow.
|
||||
|
||||
@@ -71,4 +71,4 @@ class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandle
|
||||
// Convert form string to integer, handling empty selections as null
|
||||
$participant->assignedRoomId = $this->normalizeIntValue($roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,4 +201,4 @@ class ParticipantFieldHandlerRegistry
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,15 +92,56 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'required' => false,
|
||||
'choices' => array_reduce(
|
||||
$bookingDto->travel->getAdditionalServicesByGroup(Constants::TOKEN_COURSES),
|
||||
function (array $choices, Service $service) {
|
||||
$choices[$service->label] = $service->id;
|
||||
'choices' => $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_COURSES),
|
||||
'choice_label' => 'label',
|
||||
];
|
||||
|
||||
return $choices;
|
||||
},
|
||||
[]
|
||||
),
|
||||
// Additional services field provider - provides additional services with mandatory pre-selection
|
||||
$this->fieldOptionProviders['additionalServices'] = fn (BookingDtoInterface $bookingDto) => [
|
||||
'label' => 'Zusatzleistungen',
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'required' => false,
|
||||
'choices' => $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_ADDITIONAL),
|
||||
'choice_value' => 'id',
|
||||
'choice_label' => 'label',
|
||||
'choice_attr' => function (?Service $service) {
|
||||
if (null === $service) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
|
||||
// Pre-select and make readonly for mandatory services
|
||||
if (true === $service->mandatory) {
|
||||
$attributes['checked'] = true;
|
||||
$attributes['readonly'] = true;
|
||||
$attributes['class'] = 'text-pink';
|
||||
$attributes['title'] = 'Diese Leistung ist nicht abwählbar';
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
},
|
||||
];
|
||||
|
||||
// Board field provider - provides available board options from travel data
|
||||
$this->fieldOptionProviders['board'] = fn (BookingDtoInterface $bookingDto) => [
|
||||
'label' => 'Verpflegung',
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'required' => false,
|
||||
'choices' => $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_BOARD),
|
||||
'choice_label' => 'label',
|
||||
];
|
||||
|
||||
// Rentals field provider - provides available rental options from travel data filtered by date range
|
||||
$this->fieldOptionProviders['rentals'] = fn (BookingDtoInterface $bookingDto) => [
|
||||
'label' => 'Leihmaterial',
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'required' => false,
|
||||
'choices' => $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_RENTALS, true, true),
|
||||
'choice_label' => 'label',
|
||||
];
|
||||
|
||||
// Future field providers would be added here, for example:
|
||||
|
||||
@@ -39,4 +39,4 @@ trait FormTraversalTrait
|
||||
|
||||
return $data instanceof BookingDtoInterface ? $data : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user