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 {
|
export default class extends Controller {
|
||||||
|
|
||||||
static targets = [ 'fields', 'field', 'addButton', 'index' ]
|
static targets = ['fields', 'field', 'addButton', 'index']
|
||||||
static values = {
|
static values = {
|
||||||
prototype: String,
|
prototype: String,
|
||||||
maxItems: Number,
|
maxItems: Number,
|
||||||
itemsCount: Number,
|
itemsCount: Number,
|
||||||
}
|
}
|
||||||
|
|
||||||
connect () {
|
connect() {
|
||||||
this.index = this.itemsCountValue = this.fieldTargets.length
|
this.itemsCountValue = this.fieldTargets.length
|
||||||
}
|
}
|
||||||
|
|
||||||
addItem() {
|
addItem() {
|
||||||
let prototype = JSON.parse(this.prototypeValue)
|
const index = this.fieldTargets.length + 1
|
||||||
const newField = prototype.replace(/__name__/g, this.index)
|
const prototype = JSON.parse(this.prototypeValue)
|
||||||
|
const newField = prototype.replace(/__name__/g, index)
|
||||||
this.fieldsTarget.insertAdjacentHTML('beforeend', newField)
|
this.fieldsTarget.insertAdjacentHTML('beforeend', newField)
|
||||||
this.index++
|
|
||||||
this.itemsCountValue++
|
this.itemsCountValue++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class BodyDimensionsType extends AbstractType
|
|||||||
$builder
|
$builder
|
||||||
->add('height', ChoiceType::class, [
|
->add('height', ChoiceType::class, [
|
||||||
'label' => 'Körpergröße',
|
'label' => 'Körpergröße',
|
||||||
'required' => false,
|
'required' => $options['height_required'] ?? false,
|
||||||
'expanded' => false,
|
'expanded' => false,
|
||||||
'multiple' => false,
|
'multiple' => false,
|
||||||
'placeholder' => 'Keine Angabe',
|
'placeholder' => 'Keine Angabe',
|
||||||
@@ -29,7 +29,7 @@ class BodyDimensionsType extends AbstractType
|
|||||||
])
|
])
|
||||||
->add('shoeSize', ChoiceType::class, [
|
->add('shoeSize', ChoiceType::class, [
|
||||||
'label' => 'Schuhgröße',
|
'label' => 'Schuhgröße',
|
||||||
'required' => false,
|
'required' => $options['shoeSize_required'] ?? false,
|
||||||
'expanded' => false,
|
'expanded' => false,
|
||||||
'multiple' => false,
|
'multiple' => false,
|
||||||
'placeholder' => 'Keine Angabe',
|
'placeholder' => 'Keine Angabe',
|
||||||
@@ -37,7 +37,7 @@ class BodyDimensionsType extends AbstractType
|
|||||||
])
|
])
|
||||||
->add('weight', ChoiceType::class, [
|
->add('weight', ChoiceType::class, [
|
||||||
'label' => 'Gewicht',
|
'label' => 'Gewicht',
|
||||||
'required' => false,
|
'required' => $options['weight_required'] ?? false,
|
||||||
'expanded' => false,
|
'expanded' => false,
|
||||||
'multiple' => false,
|
'multiple' => false,
|
||||||
'placeholder' => 'Keine Angabe',
|
'placeholder' => 'Keine Angabe',
|
||||||
@@ -56,6 +56,13 @@ class BodyDimensionsType extends AbstractType
|
|||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'inherit_data' => true,
|
'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
|
// Get all fields that have state conditions
|
||||||
$allFieldStates = $this->fieldStateProvider->getAllFieldStates($bookingDto, $participantIndex, $formData);
|
$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) {
|
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)) {
|
if ($form->has($fieldName)) {
|
||||||
$field = $form->get($fieldName);
|
$field = $form->get($fieldName);
|
||||||
$currentOptions = $field->getConfig()->getOptions();
|
$currentOptions = $field->getConfig()->getOptions();
|
||||||
@@ -156,6 +166,38 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
$form->add($fieldName, $fieldType::class, $updatedOptions);
|
$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\BusProNet\Model\Service;
|
||||||
use App\Validator\Constraints as AppAssert;
|
use App\Validator\Constraints as AppAssert;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
|
|
||||||
#[AppAssert\Participant(groups: ['booking_edit'])]
|
#[AppAssert\Participant(groups: ['booking_edit'])]
|
||||||
|
#[Assert\Callback('validateBodyDimensionsForRentals', groups: ['booking_create_step_2', 'booking_edit'])]
|
||||||
class ParticipantDto
|
class ParticipantDto
|
||||||
{
|
{
|
||||||
public ?int $index = null;
|
public ?int $index = null;
|
||||||
@@ -25,6 +27,7 @@ class ParticipantDto
|
|||||||
public ?string $title = null;
|
public ?string $title = null;
|
||||||
public ?string $gender = null;
|
public ?string $gender = null;
|
||||||
public ?string $nationality = null;
|
public ?string $nationality = null;
|
||||||
|
|
||||||
public ?string $height = null;
|
public ?string $height = null;
|
||||||
public ?string $shoeSize = null;
|
public ?string $shoeSize = null;
|
||||||
public ?string $weight = null;
|
public ?string $weight = null;
|
||||||
@@ -81,4 +84,44 @@ class ParticipantDto
|
|||||||
{
|
{
|
||||||
return 'O' === $this->status;
|
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;
|
namespace App\Form\Service;
|
||||||
|
|
||||||
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||||
|
use App\Form\Service\Condition\RentalSelectionCondition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field state provider for the booking create workflow.
|
* Field state provider for the booking create workflow.
|
||||||
@@ -44,6 +45,21 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
*/
|
*/
|
||||||
protected function registerFieldStateConditions(): void
|
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
|
// Example field state conditions would be registered here
|
||||||
// For demonstration purposes, here are some example patterns:
|
// For demonstration purposes, here are some example patterns:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user