wip: conditional mandatory body dimensions

This commit is contained in:
Björn Fromme
2025-07-25 21:47:00 +02:00
parent 9c5597eb23
commit b992e4a5ea
6 changed files with 207 additions and 10 deletions
+10 -3
View File
@@ -14,7 +14,7 @@ class BodyDimensionsType extends AbstractType
$builder
->add('height', ChoiceType::class, [
'label' => 'Körpergröße',
'required' => false,
'required' => $options['height_required'] ?? false,
'expanded' => false,
'multiple' => false,
'placeholder' => 'Keine Angabe',
@@ -29,7 +29,7 @@ class BodyDimensionsType extends AbstractType
])
->add('shoeSize', ChoiceType::class, [
'label' => 'Schuhgröße',
'required' => false,
'required' => $options['shoeSize_required'] ?? false,
'expanded' => false,
'multiple' => false,
'placeholder' => 'Keine Angabe',
@@ -37,7 +37,7 @@ class BodyDimensionsType extends AbstractType
])
->add('weight', ChoiceType::class, [
'label' => 'Gewicht',
'required' => false,
'required' => $options['weight_required'] ?? false,
'expanded' => false,
'multiple' => false,
'placeholder' => 'Keine Angabe',
@@ -56,6 +56,13 @@ class BodyDimensionsType extends AbstractType
{
$resolver->setDefaults([
'inherit_data' => true,
'height_required' => false,
'weight_required' => false,
'shoeSize_required' => false,
]);
$resolver->setAllowedTypes('height_required', 'bool');
$resolver->setAllowedTypes('weight_required', 'bool');
$resolver->setAllowedTypes('shoeSize_required', 'bool');
}
}
+42
View File
@@ -142,7 +142,17 @@ class BookingCreateParticipantType extends AbstractType
// Get all fields that have state conditions
$allFieldStates = $this->fieldStateProvider->getAllFieldStates($bookingDto, $participantIndex, $formData);
// Handle body dimension fields separately as they are nested in bodyDimensions form
$bodyDimensionFields = ['height', 'weight', 'shoeSize'];
$bodyDimensionStates = [];
foreach ($allFieldStates as $fieldName => $fieldState) {
if (in_array($fieldName, $bodyDimensionFields, true)) {
// Collect body dimension field states for later processing
$bodyDimensionStates[$fieldName] = $fieldState;
continue;
}
if ($form->has($fieldName)) {
$field = $form->get($fieldName);
$currentOptions = $field->getConfig()->getOptions();
@@ -156,6 +166,38 @@ class BookingCreateParticipantType extends AbstractType
$form->add($fieldName, $fieldType::class, $updatedOptions);
}
}
// Apply body dimension field states to the nested bodyDimensions form
if (!empty($bodyDimensionStates) && $form->has('bodyDimensions')) {
$this->applyBodyDimensionStates($form, $bodyDimensionStates);
}
}
/**
* Applies field states to body dimension fields in the nested bodyDimensions form.
*
* This method handles the special case of body dimension fields which are embedded
* in a nested form type. It converts field state requirements into options that
* can be passed to the BodyDimensionsType.
*
* @param FormInterface $form The parent form containing bodyDimensions
* @param array<string, array<string, mixed>> $bodyDimensionStates Field states for body dimension fields
*/
private function applyBodyDimensionStates(FormInterface $form, array $bodyDimensionStates): void
{
$bodyDimensionsField = $form->get('bodyDimensions');
$currentOptions = $bodyDimensionsField->getConfig()->getOptions();
// Convert field states to BodyDimensionsType options
foreach ($bodyDimensionStates as $fieldName => $fieldState) {
if (isset($fieldState['required']) && true === $fieldState['required']) {
$currentOptions[$fieldName.'_required'] = true;
}
}
// Remove and re-add the bodyDimensions field with updated options
$form->remove('bodyDimensions');
$form->add('bodyDimensions', BodyDimensionsType::class, $currentOptions);
}
/**
+43
View File
@@ -7,8 +7,10 @@ use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Service;
use App\Validator\Constraints as AppAssert;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[AppAssert\Participant(groups: ['booking_edit'])]
#[Assert\Callback('validateBodyDimensionsForRentals', groups: ['booking_create_step_2', 'booking_edit'])]
class ParticipantDto
{
public ?int $index = null;
@@ -25,6 +27,7 @@ class ParticipantDto
public ?string $title = null;
public ?string $gender = null;
public ?string $nationality = null;
public ?string $height = null;
public ?string $shoeSize = null;
public ?string $weight = null;
@@ -81,4 +84,44 @@ class ParticipantDto
{
return 'O' === $this->status;
}
/**
* Validates that body dimension fields are provided when rental services are selected.
*
* This callback validator ensures that height, weight, and shoe size are mandatory
* when the participant has selected any rental services. This is required for
* proper equipment sizing and rental fulfillment.
*
* @param ExecutionContextInterface $context The validation context
*/
public function validateBodyDimensionsForRentals(ExecutionContextInterface $context): void
{
$rentals = $this->rentals ?? [];
// If no rental services are selected, body dimensions are not required
if (empty($rentals)) {
return;
}
// Validate height field
if (true === empty($this->height)) {
$context->buildViolation('Deine Körpergröße ist erforderlich wenn Leihmaterial ausgewählt wurde')
->atPath('height')
->addViolation();
}
// Validate weight field
if (true === empty($this->weight)) {
$context->buildViolation('Dein Gewicht ist erforderlich wenn Leihmaterial ausgewählt wurde')
->atPath('weight')
->addViolation();
}
// Validate shoe size field
if (true === empty($this->shoeSize)) {
$context->buildViolation('Deine Schuhgröße ist erforderlich wenn Leihmaterial ausgewählt wurde')
->atPath('shoeSize')
->addViolation();
}
}
}
@@ -0,0 +1,89 @@
<?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 whether rental services are selected for a participant.
*
* This condition checks if a participant has selected any rental services from the
* available travel services. When rental services are selected, body dimension
* fields (height, weight, shoeSize) should become mandatory for equipment sizing.
*
* The condition examines the participant's rentals array to determine if any
* services with rental subtypes have been selected, triggering mandatory
* body dimension requirements.
*/
class RentalSelectionCondition implements FieldConditionInterface
{
/**
* Evaluates whether the participant has selected any rental services.
*
* Checks if the participant's rentals array contains any Service objects,
* which would indicate rental services have been selected and body
* dimensions should be required for proper equipment sizing.
*
* @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 rental services are selected, false otherwise
*/
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
{
// First check submitted form data for rental selections
if (isset($formData['participants'][$participantIndex]['rentals'])) {
$selectedRentals = $formData['participants'][$participantIndex]['rentals'];
if (true === is_array($selectedRentals) && false === empty($selectedRentals)) {
return true;
}
}
// Then check participant DTO data for existing rental selections
$participant = $bookingDto->getParticipant($participantIndex);
if (null !== $participant) {
$rentals = $participant->rentals;
if (false === empty($rentals)) {
// Check if any rental services are actually selected
foreach ($rentals as $rental) {
if ($rental instanceof Service) {
return true;
}
}
}
}
return false;
}
/**
* Returns field names that trigger re-evaluation of this condition.
*
* This condition depends on the rentals field, so any changes to rental
* selections should trigger re-evaluation of body dimension field states.
*
* @return string[] Array containing the field names that affect this condition
*/
public function getDependentFields(): array
{
return ['rentals'];
}
/**
* Returns a human-readable description of this condition.
*
* Provides a clear description of the condition logic for debugging,
* logging, and developer documentation purposes.
*
* @return string A brief description of the condition logic
*/
public function getDescription(): string
{
return 'Body dimensions required when rental services are selected';
}
}
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Form\Service;
use App\Form\Service\Abstract\AbstractFieldStateProvider;
use App\Form\Service\Condition\RentalSelectionCondition;
/**
* Field state provider for the booking create workflow.
@@ -44,6 +45,21 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
*/
protected function registerFieldStateConditions(): void
{
$rentalCondition = new RentalSelectionCondition();
// Make body dimension fields required when rental services are selected
$this->fieldStateConditions['height'] = [
'required' => $rentalCondition,
];
$this->fieldStateConditions['weight'] = [
'required' => $rentalCondition,
];
$this->fieldStateConditions['shoeSize'] = [
'required' => $rentalCondition,
];
// Example field state conditions would be registered here
// For demonstration purposes, here are some example patterns: