wip: conditional mandatory body dimensions
This commit is contained in:
@@ -1,23 +1,23 @@
|
||||
import { Controller } from '@hotwired/stimulus'
|
||||
import {Controller} from '@hotwired/stimulus'
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [ 'fields', 'field', 'addButton', 'index' ]
|
||||
static targets = ['fields', 'field', 'addButton', 'index']
|
||||
static values = {
|
||||
prototype: String,
|
||||
maxItems: Number,
|
||||
itemsCount: Number,
|
||||
}
|
||||
|
||||
connect () {
|
||||
this.index = this.itemsCountValue = this.fieldTargets.length
|
||||
connect() {
|
||||
this.itemsCountValue = this.fieldTargets.length
|
||||
}
|
||||
|
||||
addItem() {
|
||||
let prototype = JSON.parse(this.prototypeValue)
|
||||
const newField = prototype.replace(/__name__/g, this.index)
|
||||
const index = this.fieldTargets.length + 1
|
||||
const prototype = JSON.parse(this.prototypeValue)
|
||||
const newField = prototype.replace(/__name__/g, index)
|
||||
this.fieldsTarget.insertAdjacentHTML('beforeend', newField)
|
||||
this.index++
|
||||
this.itemsCountValue++
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user