wip: major refactoring
This commit is contained in:
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
$finder = (new PhpCsFixer\Finder())
|
$finder = (new PhpCsFixer\Finder())
|
||||||
->in(__DIR__)
|
->in(__DIR__)
|
||||||
->exclude('var')
|
->exclude('var');
|
||||||
;
|
|
||||||
|
|
||||||
return (new PhpCsFixer\Config())
|
return (new PhpCsFixer\Config())
|
||||||
->setRules([
|
->setRules([
|
||||||
'@Symfony' => true,
|
'@Symfony' => true,
|
||||||
|
'@DoctrineAnnotation' => true,
|
||||||
])
|
])
|
||||||
->setFinder($finder)
|
->setFinder($finder);
|
||||||
;
|
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
import { Controller } from '@hotwired/stimulus'
|
import { Controller } from '@hotwired/stimulus'
|
||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
|
static classes = ['closed']
|
||||||
static classes = [ 'closed' ]
|
static targets = ['toggle', 'icon']
|
||||||
static targets = [ 'toggle', 'icon' ]
|
|
||||||
static values = {
|
static values = {
|
||||||
open: {
|
open: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
storageKey: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
this.target = this.hasToggleTarget ? this.toggleTarget : this.element
|
this.target = this.hasToggleTarget ? this.toggleTarget : this.element
|
||||||
|
|
||||||
|
// Restore state from storage if storageKey is provided
|
||||||
|
if (this.hasStorageKey()) {
|
||||||
|
this.restoreState()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
@@ -30,10 +38,28 @@ export default class extends Controller {
|
|||||||
openValueChanged(open) {
|
openValueChanged(open) {
|
||||||
this.target.classList.toggle(this.closedClass, false === open)
|
this.target.classList.toggle(this.closedClass, false === open)
|
||||||
|
|
||||||
if (false === this.hasIconTarget) {
|
if (this.hasIconTarget) {
|
||||||
return
|
this.iconTarget.classList.toggle('rotate-90', true === open)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.iconTarget.classList.toggle('rotate-90', true === open)
|
// Save state to storage if storageKey is provided
|
||||||
|
if (this.hasStorageKey()) {
|
||||||
|
this.saveState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasStorageKey() {
|
||||||
|
return this.storageKeyValue && this.storageKeyValue.trim() !== ''
|
||||||
|
}
|
||||||
|
|
||||||
|
saveState() {
|
||||||
|
sessionStorage.setItem(`toggle_${this.storageKeyValue}`, this.openValue.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreState() {
|
||||||
|
const savedState = sessionStorage.getItem(`toggle_${this.storageKeyValue}`)
|
||||||
|
if (savedState !== null) {
|
||||||
|
this.openValue = savedState === 'true'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,15 +63,15 @@ services:
|
|||||||
$preferRemote: '%env(bool:APP_TRAVEL_PREFER_REMOTE)%'
|
$preferRemote: '%env(bool:APP_TRAVEL_PREFER_REMOTE)%'
|
||||||
$enableFallback: '%env(bool:APP_TRAVEL_ENABLE_FALLBACK)%'
|
$enableFallback: '%env(bool:APP_TRAVEL_ENABLE_FALLBACK)%'
|
||||||
|
|
||||||
App\Form\Service\ParticipantRoomChoiceLoaderFactory:
|
App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory:
|
||||||
arguments:
|
arguments:
|
||||||
$choiceListFactory: '@form.choice_list_factory.default'
|
$choiceListFactory: '@form.choice_list_factory.default'
|
||||||
|
|
||||||
# Participant Field Handler Registry with hybrid handler configuration
|
# Participant Field Handler Registry with hybrid handler configuration
|
||||||
App\Form\ParticipantFieldHandler\ParticipantFieldHandlerRegistry:
|
App\Form\Service\ParticipantFieldHandlerRegistry:
|
||||||
arguments:
|
arguments:
|
||||||
$handlers:
|
$handlers:
|
||||||
# Simple handlers (no dependencies) - use class names
|
# Simple handlers (no dependencies) - use class names
|
||||||
- 'App\Form\ParticipantFieldHandler\ParticipantAssignedRoomFieldHandler'
|
- 'App\Form\Service\ParticipantAssignedRoomFieldHandler'
|
||||||
# Complex handlers (with dependencies) would use service references like:
|
# Complex handlers (with dependencies) would use service references like:
|
||||||
# - '@participant.complex.handler'
|
# - '@participant.complex.handler'
|
||||||
|
|||||||
@@ -4,14 +4,29 @@ This system provides a flexible architecture for implementing conditional field
|
|||||||
|
|
||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
|
|
||||||
The system consists of several key components:
|
The system consists of several key components organized in a clean namespace structure:
|
||||||
|
|
||||||
1. **FieldConditionInterface** - Defines the contract for condition evaluation
|
### Core Interfaces (`src/Form/Service/Contract/`)
|
||||||
2. **Concrete Conditions** - Implement specific business logic (age ranges, field values, etc.)
|
1. **FieldStateProviderInterface** - Defines the contract for field state management
|
||||||
3. **CompositeCondition** - Combines conditions with AND/OR/NOT logic
|
2. **FieldOptionsProviderInterface** - Defines the contract for field option generation
|
||||||
4. **FieldStateProviderInterface** - Manages field state calculation
|
|
||||||
5. **ParticipantFieldOptionsProvider** - Enhanced to support state conditions
|
### Abstract Base Classes (`src/Form/Service/Abstract/`)
|
||||||
6. **Form Integration** - Applied in BookingCreateParticipantType
|
3. **AbstractFieldStateProvider** - Common field state functionality
|
||||||
|
4. **AbstractFieldOptionsProvider** - Common field option functionality
|
||||||
|
|
||||||
|
### Concrete Implementations (`src/Form/Service/`)
|
||||||
|
5. **CreateFieldStateProvider** - Field states for booking creation workflow
|
||||||
|
6. **EditFieldStateProvider** - Field states for booking edit workflow
|
||||||
|
7. **ParticipantFieldOptionsProvider** - Dynamic field option generation
|
||||||
|
|
||||||
|
### Condition System (`src/Form/Service/Condition/`)
|
||||||
|
8. **FieldConditionInterface** - Defines the contract for condition evaluation
|
||||||
|
9. **Concrete Conditions** - Implement specific business logic (age ranges, field values, etc.)
|
||||||
|
10. **CompositeCondition** - Combines conditions with AND/OR/NOT logic
|
||||||
|
|
||||||
|
### Form Integration
|
||||||
|
11. **BookingCreateParticipantType** - Uses CreateFieldStateProvider
|
||||||
|
12. **BookingEditParticipantType** - Uses EditFieldStateProvider
|
||||||
|
|
||||||
## Usage Examples
|
## Usage Examples
|
||||||
|
|
||||||
@@ -87,10 +102,12 @@ $this->fieldStateConditions['specialServices'] = [
|
|||||||
|
|
||||||
## Adding New Conditions
|
## Adding New Conditions
|
||||||
|
|
||||||
To register field state conditions, add them to the `registerFieldStateConditions()` method in `ParticipantFieldOptionsProvider`:
|
### For Create Workflow
|
||||||
|
To register field state conditions for the booking creation workflow, add them to the `registerFieldStateConditions()` method in `CreateFieldStateProvider`:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
private function registerFieldStateConditions(): void
|
// src/Form/Service/CreateFieldStateProvider.php
|
||||||
|
protected function registerFieldStateConditions(): void
|
||||||
{
|
{
|
||||||
// Age-based readonly state
|
// Age-based readonly state
|
||||||
$this->fieldStateConditions['assignedRoomId'] = [
|
$this->fieldStateConditions['assignedRoomId'] = [
|
||||||
@@ -113,6 +130,40 @@ private function registerFieldStateConditions(): void
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### For Edit Workflow
|
||||||
|
To register field state conditions for the booking edit workflow, add them to the `registerFieldStateConditions()` method in `EditFieldStateProvider`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// src/Form/Service/EditFieldStateProvider.php
|
||||||
|
protected function registerFieldStateConditions(): void
|
||||||
|
{
|
||||||
|
// Make personal data readonly for applicants or non-mutable fields
|
||||||
|
$personalDataFields = ['firstName', 'lastName', 'dateOfBirth', 'gender', 'nationality', 'email', 'mobile'];
|
||||||
|
foreach ($personalDataFields as $field) {
|
||||||
|
$this->fieldStateConditions[$field] = [
|
||||||
|
'readonly' => CompositeCondition::or(
|
||||||
|
new ApplicantCondition(),
|
||||||
|
new MutabilityCondition()
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Field Options
|
||||||
|
To register dynamic field options, add them to the `registerFieldOptionProviders()` method in `ParticipantFieldOptionsProvider`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// src/Form/Service/ParticipantFieldOptionsProvider.php
|
||||||
|
protected function registerFieldOptionProviders(): void
|
||||||
|
{
|
||||||
|
$this->fieldOptionProviders['newField'] = fn(BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
|
'label' => 'New Field Label',
|
||||||
|
'choices' => $this->generateChoicesFor($bookingDto, $participantIndex),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Performance Considerations
|
## Performance Considerations
|
||||||
|
|
||||||
- Conditions use lazy evaluation and short-circuit logic
|
- Conditions use lazy evaluation and short-circuit logic
|
||||||
@@ -136,6 +187,9 @@ The system supports real-time field state updates:
|
|||||||
Create new condition classes implementing `FieldConditionInterface`:
|
Create new condition classes implementing `FieldConditionInterface`:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
|
// src/Form/Service/Condition/CustomBusinessRuleCondition.php
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
|
||||||
class CustomBusinessRuleCondition implements FieldConditionInterface
|
class CustomBusinessRuleCondition implements FieldConditionInterface
|
||||||
{
|
{
|
||||||
public function evaluate(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
|
public function evaluate(BookingCreateDto $bookingDto, int $participantIndex, array $formData): bool
|
||||||
@@ -156,4 +210,30 @@ class CustomBusinessRuleCondition implements FieldConditionInterface
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Custom Field Options Providers
|
||||||
|
|
||||||
|
To create more complex field option logic, extend `AbstractFieldOptionsProvider`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// src/Form/Service/CustomFieldOptionsProvider.php
|
||||||
|
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
|
||||||
|
|
||||||
|
class CustomFieldOptionsProvider extends AbstractFieldOptionsProvider
|
||||||
|
{
|
||||||
|
protected function registerFieldOptionProviders(): void
|
||||||
|
{
|
||||||
|
$this->fieldOptionProviders['customField'] = fn(BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
|
'label' => 'Custom Field',
|
||||||
|
'choices' => $this->generateCustomChoices($bookingDto, $participantIndex),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateCustomChoices(BookingDtoInterface $bookingDto, int $participantIndex): array
|
||||||
|
{
|
||||||
|
// Custom choice generation logic
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
This system provides a powerful, maintainable foundation for complex conditional field behavior while maintaining clean separation of concerns and extensibility.
|
This system provides a powerful, maintainable foundation for complex conditional field behavior while maintaining clean separation of concerns and extensibility.
|
||||||
@@ -34,8 +34,8 @@ class CreateStep1Controller extends AbstractController
|
|||||||
{
|
{
|
||||||
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
|
$bookingCreateDto = $this->bookingService->getOrCreateBookingCreateDto($request);
|
||||||
|
|
||||||
// Capture the current room selection state before form processing
|
// Get or create baseline snapshot for change detection
|
||||||
$oldRoomSelectionSnapshot = $this->bookingService->createRoomSelectionSnapshot($bookingCreateDto);
|
$oldRoomSelectionSnapshot = $this->bookingService->getOrCreateBaselineSnapshot($request, $bookingCreateDto);
|
||||||
|
|
||||||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||||||
|
|
||||||
@@ -52,9 +52,13 @@ class CreateStep1Controller extends AbstractController
|
|||||||
if ($this->bookingService->hasRoomSelectionChanged($oldRoomSelectionSnapshot, $bookingCreateDto)) {
|
if ($this->bookingService->hasRoomSelectionChanged($oldRoomSelectionSnapshot, $bookingCreateDto)) {
|
||||||
$this->bookingService->resetParticipantAssignments($bookingCreateDto);
|
$this->bookingService->resetParticipantAssignments($bookingCreateDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
$bookingCreateDto->currentStep = 2;
|
$bookingCreateDto->currentStep = 2;
|
||||||
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
||||||
|
|
||||||
|
// Clear baseline snapshot when moving to step 2
|
||||||
|
$this->bookingService->clearBaselineSnapshot($request);
|
||||||
|
|
||||||
return $this->redirectToRoute('app_booking_create_step_2');
|
return $this->redirectToRoute('app_booking_create_step_2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ namespace App\Form;
|
|||||||
use App\BusProNet\Form\CountryType;
|
use App\BusProNet\Form\CountryType;
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
use App\Form\Model\ParticipantDto;
|
use App\Form\Model\ParticipantDto;
|
||||||
use App\Form\Service\ParticipantFieldOptionsProvider;
|
use App\Form\Service\Contract\FieldOptionsProviderInterface;
|
||||||
|
use App\Form\Service\CreateFieldStateProvider;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
@@ -20,7 +21,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
class BookingCreateParticipantType extends AbstractType
|
class BookingCreateParticipantType extends AbstractType
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ParticipantFieldOptionsProvider $fieldOptionsProvider,
|
private readonly FieldOptionsProviderInterface $fieldOptionsProvider,
|
||||||
|
private readonly CreateFieldStateProvider $fieldStateProvider,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the booking DTO from the root form
|
// Get the booking DTO from the root form
|
||||||
$bookingDto = $this->fieldOptionsProvider->getBookingDtoFromForm($form);
|
$bookingDto = $this->fieldStateProvider->getBookingDtoFromForm($form);
|
||||||
|
|
||||||
if (null === $bookingDto) {
|
if (null === $bookingDto) {
|
||||||
return;
|
return;
|
||||||
@@ -108,7 +110,7 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the booking DTO from the root form
|
// Get the booking DTO from the root form
|
||||||
$bookingDto = $this->fieldOptionsProvider->getBookingDtoFromForm($form);
|
$bookingDto = $this->fieldStateProvider->getBookingDtoFromForm($form);
|
||||||
|
|
||||||
if (null === $bookingDto) {
|
if (null === $bookingDto) {
|
||||||
return;
|
return;
|
||||||
@@ -138,7 +140,7 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
private function applyFieldStates(FormInterface $form, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): void
|
private function applyFieldStates(FormInterface $form, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): void
|
||||||
{
|
{
|
||||||
// Get all fields that have state conditions
|
// Get all fields that have state conditions
|
||||||
$allFieldStates = $this->fieldOptionsProvider->getAllFieldStates($bookingDto, $participantIndex, $formData);
|
$allFieldStates = $this->fieldStateProvider->getAllFieldStates($bookingDto, $participantIndex, $formData);
|
||||||
|
|
||||||
foreach ($allFieldStates as $fieldName => $fieldState) {
|
foreach ($allFieldStates as $fieldName => $fieldState) {
|
||||||
if ($form->has($fieldName)) {
|
if ($form->has($fieldName)) {
|
||||||
@@ -169,8 +171,8 @@ class BookingCreateParticipantType extends AbstractType
|
|||||||
$fieldOptions = $this->fieldOptionsProvider->getFieldOptions($fieldName, $bookingDto, $participantIndex);
|
$fieldOptions = $this->fieldOptionsProvider->getFieldOptions($fieldName, $bookingDto, $participantIndex);
|
||||||
|
|
||||||
// Apply dynamic field state if conditions exist
|
// Apply dynamic field state if conditions exist
|
||||||
if ($this->fieldOptionsProvider->hasStateConditions($fieldName)) {
|
if ($this->fieldStateProvider->hasStateConditions($fieldName)) {
|
||||||
$fieldState = $this->fieldOptionsProvider->getFieldState($fieldName, $bookingDto, $participantIndex);
|
$fieldState = $this->fieldStateProvider->getFieldState($fieldName, $bookingDto, $participantIndex);
|
||||||
$fieldOptions = $this->mergeFieldState($fieldOptions, $fieldState);
|
$fieldOptions = $this->mergeFieldState($fieldOptions, $fieldState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use App\Form\Model\BookingCreateDto;
|
use App\Form\Model\BookingCreateDto;
|
||||||
use App\Form\ParticipantFieldHandler\ParticipantFieldHandlerRegistry;
|
use App\Form\Service\ParticipantFieldHandlerRegistry;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace App\Form;
|
|||||||
use App\BusProNet\Constants;
|
use App\BusProNet\Constants;
|
||||||
use App\BusProNet\Model\Service;
|
use App\BusProNet\Model\Service;
|
||||||
use App\Form\Model\BookingEditDto;
|
use App\Form\Model\BookingEditDto;
|
||||||
use App\Form\ParticipantFieldHandler\ParticipantFieldHandlerRegistry;
|
use App\Form\Service\ParticipantFieldHandlerRegistry;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|||||||
@@ -23,12 +23,10 @@ class RoomSelectType extends AbstractType
|
|||||||
|
|
||||||
$form->add('quantity', ChoiceType::class, [
|
$form->add('quantity', ChoiceType::class, [
|
||||||
'label' => 'Anzahl '.$data->roomLabel,
|
'label' => 'Anzahl '.$data->roomLabel,
|
||||||
'required' => false,
|
'required' => true,
|
||||||
'placeholder' => '-',
|
'choices' => ['-' => 0] + array_combine(range(1, $data->maxQuantity), range(1, $data->maxQuantity)),
|
||||||
'choices' => array_combine(range(1, $data->maxQuantity), range(1, $data->maxQuantity)),
|
|
||||||
]);
|
]);
|
||||||
})
|
});
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service\Abstract;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldOptionsProviderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for field options providers.
|
||||||
|
*
|
||||||
|
* This class contains common field options generation logic shared between
|
||||||
|
* different field options provider implementations. It provides the core
|
||||||
|
* functionality for managing field option providers and generating dynamic
|
||||||
|
* field configurations.
|
||||||
|
*/
|
||||||
|
abstract class AbstractFieldOptionsProvider implements FieldOptionsProviderInterface
|
||||||
|
{
|
||||||
|
/** @var array<string, callable> Field option providers indexed by field name */
|
||||||
|
protected array $fieldOptionProviders = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->registerFieldOptionProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves form field options for a specified dynamic field.
|
||||||
|
*
|
||||||
|
* This method looks up the appropriate option provider for the field
|
||||||
|
* and executes it with the current booking and participant context to
|
||||||
|
* generate dynamic field options.
|
||||||
|
*
|
||||||
|
* The returned array contains Symfony form field options that will be
|
||||||
|
* used when building the form field. These options are merged with any
|
||||||
|
* static options defined in the form type.
|
||||||
|
*
|
||||||
|
* @param string $fieldName The name of the field to configure
|
||||||
|
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||||
|
* @param int $participantIndex The index of the participant being configured
|
||||||
|
*
|
||||||
|
* @return array<string, mixed> Symfony form field options, or empty array if field not supported
|
||||||
|
*/
|
||||||
|
public function getFieldOptions(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex): array
|
||||||
|
{
|
||||||
|
// Check if we have a provider for this field
|
||||||
|
if (!isset($this->fieldOptionProviders[$fieldName])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the provider with current context to generate dynamic options
|
||||||
|
return $this->fieldOptionProviders[$fieldName]($bookingDto, $participantIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a field has option provider support.
|
||||||
|
*
|
||||||
|
* This method allows form builders to determine if a field can be
|
||||||
|
* dynamically configured by this service. It's useful for deciding
|
||||||
|
* whether to use static field options or dynamic configuration.
|
||||||
|
*
|
||||||
|
* @param string $fieldName The name of the field to check
|
||||||
|
*
|
||||||
|
* @return bool True if the field has registered option providers, false otherwise
|
||||||
|
*/
|
||||||
|
public function hasFieldOptions(string $fieldName): bool
|
||||||
|
{
|
||||||
|
return isset($this->fieldOptionProviders[$fieldName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers field option providers during service initialization.
|
||||||
|
*
|
||||||
|
* This method must be implemented by concrete classes to define their
|
||||||
|
* specific field option providers. Each provider is a callable that
|
||||||
|
* receives the booking DTO and participant index and returns appropriate
|
||||||
|
* Symfony form field options.
|
||||||
|
*
|
||||||
|
* Example implementation:
|
||||||
|
*
|
||||||
|
* protected function registerFieldOptionProviders(): void
|
||||||
|
* {
|
||||||
|
* $this->fieldOptionProviders['fieldName'] = fn($bookingDto, $participantIndex) => [
|
||||||
|
* 'label' => 'Field Label',
|
||||||
|
* 'choices' => $this->generateChoicesFor($bookingDto, $participantIndex),
|
||||||
|
* ];
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
abstract protected function registerFieldOptionProviders(): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service\Abstract;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
use App\Form\Service\Contract\FieldStateProviderInterface;
|
||||||
|
use App\Form\Service\Trait\FormTraversalTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for field state providers.
|
||||||
|
*
|
||||||
|
* This class contains common field state evaluation logic shared between
|
||||||
|
* different field state provider implementations. It provides the core
|
||||||
|
* functionality for evaluating field conditions and applying state modifications.
|
||||||
|
*/
|
||||||
|
abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
||||||
|
{
|
||||||
|
use FormTraversalTrait;
|
||||||
|
|
||||||
|
/** @var array<string, array<string, FieldConditionInterface>> Field state conditions indexed by field name and state type */
|
||||||
|
protected array $fieldStateConditions = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->registerFieldStateConditions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the dynamic state for a specified field.
|
||||||
|
*
|
||||||
|
* Evaluates all configured conditions for a field and returns the appropriate
|
||||||
|
* state modifications. State conditions are organized by state type (readonly,
|
||||||
|
* disabled, etc.) and evaluated independently.
|
||||||
|
*
|
||||||
|
* @param string $fieldName The name of the field to evaluate
|
||||||
|
* @param BookingDtoInterface $bookingDto The current booking data for context (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 array<string, mixed> Symfony form field options for state modifications
|
||||||
|
*/
|
||||||
|
public function getFieldState(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
||||||
|
{
|
||||||
|
if (!isset($this->fieldStateConditions[$fieldName])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stateModifications = [];
|
||||||
|
$attributes = [];
|
||||||
|
|
||||||
|
foreach ($this->fieldStateConditions[$fieldName] as $stateType => $condition) {
|
||||||
|
if ($condition->evaluate($bookingDto, $participantIndex, $formData)) {
|
||||||
|
switch ($stateType) {
|
||||||
|
case 'readonly':
|
||||||
|
$attributes['readonly'] = true;
|
||||||
|
break;
|
||||||
|
case 'disabled':
|
||||||
|
$stateModifications['disabled'] = true;
|
||||||
|
break;
|
||||||
|
case 'required':
|
||||||
|
$stateModifications['required'] = true;
|
||||||
|
break;
|
||||||
|
case 'hidden':
|
||||||
|
$attributes['style'] = ($attributes['style'] ?? '').' display: none;';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($attributes)) {
|
||||||
|
$stateModifications['attr'] = $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stateModifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a field has state conditions configured.
|
||||||
|
*
|
||||||
|
* @param string $fieldName The name of the field to check
|
||||||
|
*
|
||||||
|
* @return bool True if the field has state conditions configured
|
||||||
|
*/
|
||||||
|
public function hasStateConditions(string $fieldName): bool
|
||||||
|
{
|
||||||
|
return isset($this->fieldStateConditions[$fieldName]) && !empty($this->fieldStateConditions[$fieldName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns field names that trigger state re-evaluation for a given field.
|
||||||
|
*
|
||||||
|
* @param string $fieldName The name of the field to get dependencies for
|
||||||
|
*
|
||||||
|
* @return string[] Array of field names that affect the specified field's state
|
||||||
|
*/
|
||||||
|
public function getFieldStateDependencies(string $fieldName): array
|
||||||
|
{
|
||||||
|
if (!isset($this->fieldStateConditions[$fieldName])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$dependencies = [];
|
||||||
|
|
||||||
|
foreach ($this->fieldStateConditions[$fieldName] as $condition) {
|
||||||
|
$dependencies = array_merge($dependencies, $condition->getDependentFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique($dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates field states for all configured fields at once.
|
||||||
|
*
|
||||||
|
* @param BookingDtoInterface $bookingDto The current booking data for context (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 array<string, array<string, mixed>> Field states indexed by field name
|
||||||
|
*/
|
||||||
|
public function getAllFieldStates(BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
||||||
|
{
|
||||||
|
$allStates = [];
|
||||||
|
|
||||||
|
foreach (array_keys($this->fieldStateConditions) as $fieldName) {
|
||||||
|
$fieldState = $this->getFieldState($fieldName, $bookingDto, $participantIndex, $formData);
|
||||||
|
if (!empty($fieldState)) {
|
||||||
|
$allStates[$fieldName] = $fieldState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $allStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers field state conditions during service initialization.
|
||||||
|
*
|
||||||
|
* This method must be implemented by concrete classes to define their
|
||||||
|
* specific field state conditions.
|
||||||
|
*/
|
||||||
|
abstract protected function registerFieldStateConditions(): void;
|
||||||
|
}
|
||||||
+3
-2
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler;
|
namespace App\Form\Service\Abstract;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\ParticipantFieldHandlerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class providing common functionality for participant field handlers.
|
* Abstract base class providing common functionality for participant field handlers.
|
||||||
@@ -150,4 +151,4 @@ abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandle
|
|||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-1
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
namespace App\Form\Service\Condition;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Condition that evaluates participant age against specified range criteria.
|
* Condition that evaluates participant age against specified range criteria.
|
||||||
+3
-2
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
namespace App\Form\Service\Condition;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Condition that checks if the participant is the applicant.
|
* Condition that checks if the participant is the applicant.
|
||||||
@@ -41,4 +42,4 @@ class ApplicantCondition implements FieldConditionInterface
|
|||||||
{
|
{
|
||||||
return 'Checks if the participant is the applicant (index 0)';
|
return 'Checks if the participant is the applicant (index 0)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-2
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
namespace App\Form\Service\Condition;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composite condition that combines multiple conditions with logical operators.
|
* Composite condition that combines multiple conditions with logical operators.
|
||||||
@@ -230,4 +231,4 @@ class CompositeCondition implements FieldConditionInterface
|
|||||||
throw new \InvalidArgumentException(sprintf('%s operator requires at least one condition', $operator));
|
throw new \InvalidArgumentException(sprintf('%s operator requires at least one condition', $operator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-2
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
namespace App\Form\Service\Condition;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Condition that evaluates field states based on other field values.
|
* Condition that evaluates field states based on other field values.
|
||||||
@@ -262,4 +263,4 @@ class FieldValueCondition implements FieldConditionInterface
|
|||||||
throw new \InvalidArgumentException(sprintf('Operator "%s" does not accept expectedValue parameter', $operator));
|
throw new \InvalidArgumentException(sprintf('Operator "%s" does not accept expectedValue parameter', $operator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-2
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
namespace App\Form\Service\Condition;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\FieldConditionInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Condition that checks if a participant's personal data is mutable in the edit flow.
|
* Condition that checks if a participant's personal data is mutable in the edit flow.
|
||||||
@@ -45,4 +46,4 @@ class MutabilityCondition implements FieldConditionInterface
|
|||||||
{
|
{
|
||||||
return 'Checks if the participant\'s personal data is mutable (edit flow)';
|
return 'Checks if the participant\'s personal data is mutable (edit flow)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-2
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler\Condition;
|
namespace App\Form\Service\Contract;
|
||||||
|
|
||||||
use App\Form\Model\BookingCreateDto;
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service\Contract;
|
||||||
|
|
||||||
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for providing dynamic field options based on context.
|
||||||
|
*
|
||||||
|
* Field option providers generate context-aware Symfony form field options
|
||||||
|
* for dynamic fields that require their configuration to be calculated based
|
||||||
|
* on the current booking state, participant data, and business logic.
|
||||||
|
*
|
||||||
|
* Key Responsibilities:
|
||||||
|
* - Generate dynamic field options based on booking context
|
||||||
|
* - Support context-aware field configurations
|
||||||
|
* - Enable extensible field option generation
|
||||||
|
* - Provide lazy evaluation of field options
|
||||||
|
*
|
||||||
|
* Field options typically include:
|
||||||
|
* - 'label': The field label text
|
||||||
|
* - 'placeholder': Placeholder text for input fields
|
||||||
|
* - 'choices': Available choices for choice fields
|
||||||
|
* - 'choice_loader': Dynamic choice loader for complex choices
|
||||||
|
* - 'disabled': Whether the field should be disabled
|
||||||
|
* - 'required': Whether the field is required
|
||||||
|
* - 'attr': HTML attributes for the field
|
||||||
|
*/
|
||||||
|
interface FieldOptionsProviderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Retrieves form field options for a specified dynamic field.
|
||||||
|
*
|
||||||
|
* This method looks up the appropriate option provider for the field
|
||||||
|
* and executes it with the current booking and participant context to
|
||||||
|
* generate dynamic field options.
|
||||||
|
*
|
||||||
|
* The returned array contains Symfony form field options that will be
|
||||||
|
* used when building the form field. These options are merged with any
|
||||||
|
* static options defined in the form type.
|
||||||
|
*
|
||||||
|
* @param string $fieldName The name of the field to configure
|
||||||
|
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
||||||
|
* @param int $participantIndex The index of the participant being configured
|
||||||
|
*
|
||||||
|
* @return array<string, mixed> Symfony form field options, or empty array if field not supported
|
||||||
|
*/
|
||||||
|
public function getFieldOptions(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a field has option provider support.
|
||||||
|
*
|
||||||
|
* This method allows form builders to determine if a field can be
|
||||||
|
* dynamically configured by this service. It's useful for deciding
|
||||||
|
* whether to use static field options or dynamic configuration.
|
||||||
|
*
|
||||||
|
* @param string $fieldName The name of the field to check
|
||||||
|
*
|
||||||
|
* @return bool True if the field has registered option providers, false otherwise
|
||||||
|
*/
|
||||||
|
public function hasFieldOptions(string $fieldName): bool;
|
||||||
|
}
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\Service;
|
namespace App\Form\Service\Contract;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler;
|
namespace App\Form\Service\Contract;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form\Service;
|
||||||
|
|
||||||
|
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field state provider for the booking create workflow.
|
||||||
|
*
|
||||||
|
* This service calculates dynamic field states (readonly, disabled, etc.)
|
||||||
|
* for participant fields in the create flow. It extends the common field
|
||||||
|
* state functionality provided by AbstractFieldStateProvider.
|
||||||
|
*
|
||||||
|
* Currently, no specific field state conditions are implemented for the
|
||||||
|
* create workflow, but the infrastructure is ready for future additions.
|
||||||
|
*/
|
||||||
|
class CreateFieldStateProvider extends AbstractFieldStateProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Registers field state conditions for the create workflow.
|
||||||
|
*
|
||||||
|
* This method defines the conditional logic for field states in the
|
||||||
|
* booking creation process. Each field can have multiple state conditions
|
||||||
|
* (readonly, disabled, hidden, required) that are evaluated independently.
|
||||||
|
*
|
||||||
|
* Adding new field state conditions:
|
||||||
|
* To add conditional state logic for a field, register conditions here:
|
||||||
|
*
|
||||||
|
* $this->fieldStateConditions['fieldName'] = [
|
||||||
|
* 'readonly' => new SomeCondition(),
|
||||||
|
* 'disabled' => CompositeCondition::and(
|
||||||
|
* new AgeRangeCondition(null, 17),
|
||||||
|
* FieldValueCondition::equals('someField', 'someValue')
|
||||||
|
* ),
|
||||||
|
* ];
|
||||||
|
*
|
||||||
|
* Condition Types:
|
||||||
|
* - 'readonly': Field is visible but not editable
|
||||||
|
* - 'disabled': Field interaction is disabled
|
||||||
|
* - 'required': Field becomes mandatory
|
||||||
|
* - 'hidden': Field is not displayed
|
||||||
|
*/
|
||||||
|
protected function registerFieldStateConditions(): void
|
||||||
|
{
|
||||||
|
// Example field state conditions would be registered here
|
||||||
|
// For demonstration purposes, here are some example patterns:
|
||||||
|
|
||||||
|
// Example 1: Make assignedRoomId readonly for participants under 18
|
||||||
|
// $this->fieldStateConditions['assignedRoomId'] = [
|
||||||
|
// 'readonly' => new AgeRangeCondition(null, 17),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// Example 2: Disable service selection if no room is assigned
|
||||||
|
// $this->fieldStateConditions['serviceSelection'] = [
|
||||||
|
// 'disabled' => FieldValueCondition::isEmpty('assignedRoomId'),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// Example 3: Complex condition with multiple criteria
|
||||||
|
// $this->fieldStateConditions['advancedOptions'] = [
|
||||||
|
// 'hidden' => CompositeCondition::or(
|
||||||
|
// new AgeRangeCondition(null, 15),
|
||||||
|
// FieldValueCondition::equals('userType', 'basic')
|
||||||
|
// ),
|
||||||
|
// ];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Form\Service;
|
namespace App\Form\Service;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Service\Condition\ApplicantCondition;
|
||||||
use App\Form\ParticipantFieldHandler\Condition\ApplicantCondition;
|
use App\Form\Service\Condition\CompositeCondition;
|
||||||
use App\Form\ParticipantFieldHandler\Condition\CompositeCondition;
|
use App\Form\Service\Condition\MutabilityCondition;
|
||||||
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||||
use App\Form\ParticipantFieldHandler\Condition\MutabilityCondition;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field state provider for the booking edit workflow.
|
* Field state provider for the booking edit workflow.
|
||||||
@@ -16,27 +15,19 @@ use App\Form\ParticipantFieldHandler\Condition\MutabilityCondition;
|
|||||||
* This service calculates dynamic field states (readonly, disabled, etc.)
|
* This service calculates dynamic field states (readonly, disabled, etc.)
|
||||||
* for participant fields in the edit flow, using edit-specific conditions.
|
* for participant fields in the edit flow, using edit-specific conditions.
|
||||||
*
|
*
|
||||||
* It is designed to be extensible and composable, allowing reuse of
|
* It extends the common field state functionality provided by
|
||||||
* existing condition classes and easy registration of new logic.
|
* AbstractFieldStateProvider and adds edit-specific field state logic.
|
||||||
*/
|
*/
|
||||||
class EditFieldStateProvider implements FieldStateProviderInterface
|
class EditFieldStateProvider extends AbstractFieldStateProvider
|
||||||
{
|
{
|
||||||
use FormTraversalTrait;
|
|
||||||
|
|
||||||
/** @var array<string, array<string, FieldConditionInterface>> */
|
|
||||||
private array $fieldStateConditions = [];
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->registerFieldStateConditions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers field state conditions for the edit workflow.
|
* Registers field state conditions for the edit workflow.
|
||||||
*
|
*
|
||||||
* Add or modify conditions as needed for your domain.
|
* This method defines the conditional logic for field states in the
|
||||||
|
* booking edit process. It makes personal data fields readonly when
|
||||||
|
* the participant is the applicant or when the field is not mutable.
|
||||||
*/
|
*/
|
||||||
private function registerFieldStateConditions(): void
|
protected function registerFieldStateConditions(): void
|
||||||
{
|
{
|
||||||
// Make all personal data fields readonly if not mutable OR if applicant
|
// Make all personal data fields readonly if not mutable OR if applicant
|
||||||
$personalDataFields = [
|
$personalDataFields = [
|
||||||
@@ -56,75 +47,5 @@ class EditFieldStateProvider implements FieldStateProviderInterface
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example: Only applicant can edit email
|
|
||||||
$this->fieldStateConditions['email']['readonly'] = new ApplicantCondition();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFieldState(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
|
||||||
{
|
|
||||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$stateModifications = [];
|
|
||||||
$attributes = [];
|
|
||||||
|
|
||||||
foreach ($this->fieldStateConditions[$fieldName] as $stateType => $condition) {
|
|
||||||
if ($condition->evaluate($bookingDto, $participantIndex, $formData)) {
|
|
||||||
switch ($stateType) {
|
|
||||||
case 'readonly':
|
|
||||||
$attributes['readonly'] = true;
|
|
||||||
break;
|
|
||||||
case 'disabled':
|
|
||||||
$stateModifications['disabled'] = true;
|
|
||||||
break;
|
|
||||||
case 'required':
|
|
||||||
$stateModifications['required'] = true;
|
|
||||||
break;
|
|
||||||
case 'hidden':
|
|
||||||
$attributes['style'] = ($attributes['style'] ?? '').' display: none;';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($attributes)) {
|
|
||||||
$stateModifications['attr'] = $attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $stateModifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasStateConditions(string $fieldName): bool
|
|
||||||
{
|
|
||||||
return isset($this->fieldStateConditions[$fieldName]) && !empty($this->fieldStateConditions[$fieldName]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFieldStateDependencies(string $fieldName): array
|
|
||||||
{
|
|
||||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$dependencies = [];
|
|
||||||
foreach ($this->fieldStateConditions[$fieldName] as $condition) {
|
|
||||||
$dependencies = array_merge($dependencies, $condition->getDependentFields());
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_unique($dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllFieldStates(BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
|
||||||
{
|
|
||||||
$allStates = [];
|
|
||||||
foreach (array_keys($this->fieldStateConditions) as $fieldName) {
|
|
||||||
$fieldState = $this->getFieldState($fieldName, $bookingDto, $participantIndex, $formData);
|
|
||||||
if (!empty($fieldState)) {
|
|
||||||
$allStates[$fieldName] = $fieldState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $allStates;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\Service;
|
namespace App\Form\Service\Factory;
|
||||||
|
|
||||||
use App\Form\ChoiceLoader\ParticipantRoomChoiceLoader;
|
use App\Form\ChoiceLoader\ParticipantRoomChoiceLoader;
|
||||||
use App\Form\Model\ParticipantDto;
|
use App\Form\Model\ParticipantDto;
|
||||||
+3
-2
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler;
|
namespace App\Form\Service;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles processing of the assignedRoomId field for booking participants.
|
* Handles processing of the assignedRoomId field for booking participants.
|
||||||
@@ -70,4 +71,4 @@ class ParticipantAssignedRoomFieldHandler extends AbstractParticipantFieldHandle
|
|||||||
// Convert form string to integer, handling empty selections as null
|
// Convert form string to integer, handling empty selections as null
|
||||||
$participant->assignedRoomId = $this->normalizeIntValue($roomId);
|
$participant->assignedRoomId = $this->normalizeIntValue($roomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-2
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\ParticipantFieldHandler;
|
namespace App\Form\Service;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
|
use App\Form\Service\Contract\ParticipantFieldHandlerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry for managing and executing participant field handlers in dependency order.
|
* Registry for managing and executing participant field handlers in dependency order.
|
||||||
@@ -200,4 +201,4 @@ class ParticipantFieldHandlerRegistry
|
|||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,98 +6,42 @@ namespace App\Form\Service;
|
|||||||
|
|
||||||
use App\Form\Model\BookingCreateDto;
|
use App\Form\Model\BookingCreateDto;
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
use App\Form\ParticipantFieldHandler\Condition\FieldConditionInterface;
|
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
|
||||||
|
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides dynamic field options and state for participant form fields.
|
* Provides dynamic field options for participant form fields.
|
||||||
*
|
*
|
||||||
* This service acts as both a field option provider and field state provider
|
* This service generates context-aware Symfony form field options for
|
||||||
* for the participant form system. It generates context-aware Symfony form
|
* dynamic fields in the participant form system. It handles fields that
|
||||||
* field options and calculates dynamic field states based on conditional logic.
|
* require options to be calculated based on the current booking context,
|
||||||
|
* participant data, and business logic.
|
||||||
*
|
*
|
||||||
* Key Responsibilities:
|
* Key Responsibilities:
|
||||||
* - Manages field option providers for dynamic fields
|
* - Manages field option providers for dynamic fields
|
||||||
* - Calculates field states based on conditional logic
|
|
||||||
* - Provides context-aware field configurations
|
* - Provides context-aware field configurations
|
||||||
* - Handles interdependent field relationships
|
* - Handles interdependent field relationships
|
||||||
* - Supports extensible field option and state generation
|
* - Supports extensible field option generation
|
||||||
*
|
*
|
||||||
* The provider uses a callable pattern for field options and a condition-based
|
* The provider uses a callable pattern for field options, enabling
|
||||||
* system for field states, enabling complex conditional field behavior.
|
* lazy evaluation and complex conditional field behavior.
|
||||||
*/
|
*/
|
||||||
class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
||||||
{
|
{
|
||||||
use FormTraversalTrait;
|
|
||||||
|
|
||||||
/** @var array<string, callable> Field option providers indexed by field name */
|
|
||||||
private array $fieldOptionProviders = [];
|
|
||||||
|
|
||||||
/** @var array<string, array<string, FieldConditionInterface>> Field state conditions indexed by field name and state type */
|
|
||||||
private array $fieldStateConditions = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the provider with required dependencies.
|
* Initializes the provider with required dependencies.
|
||||||
*
|
*
|
||||||
* The provider automatically registers all field option providers and
|
* The provider automatically registers all field option providers during
|
||||||
* field state conditions during construction to ensure they're available
|
* construction to ensure they're available for form building. This approach
|
||||||
* for form building. This approach keeps all field configuration logic
|
* keeps all field configuration logic centralized and makes it easy to add
|
||||||
* centralized and makes it easy to add new dynamic fields.
|
* new dynamic fields.
|
||||||
*
|
*
|
||||||
* @param ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory Factory for creating room choice loaders
|
* @param ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory Factory for creating room choice loaders
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
|
private readonly ParticipantRoomChoiceLoaderFactory $roomChoiceLoaderFactory,
|
||||||
) {
|
) {
|
||||||
$this->registerFieldOptionProviders();
|
parent::__construct();
|
||||||
$this->registerFieldStateConditions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves form field options for a specified dynamic field.
|
|
||||||
*
|
|
||||||
* This is the main entry point for getting field configurations. It looks up
|
|
||||||
* the appropriate option provider for the field and executes it with the
|
|
||||||
* current booking and participant context to generate dynamic field options.
|
|
||||||
*
|
|
||||||
* The returned array contains Symfony form field options such as:
|
|
||||||
* - 'label' - The field label
|
|
||||||
* - 'placeholder' - Placeholder text
|
|
||||||
* - 'choices' - Available choices for choice fields
|
|
||||||
* - 'choice_loader' - Dynamic choice loader for complex choices
|
|
||||||
* - 'disabled' - Whether the field should be disabled
|
|
||||||
* - 'required' - Whether the field is required
|
|
||||||
*
|
|
||||||
* @param string $fieldName The name of the field to configure
|
|
||||||
* @param BookingDtoInterface $bookingDto The current booking data for context (create or edit)
|
|
||||||
* @param int $participantIndex The index of the participant being configured
|
|
||||||
*
|
|
||||||
* @return array<string, mixed> Symfony form field options, or empty array if field not supported
|
|
||||||
*/
|
|
||||||
public function getFieldOptions(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex): array
|
|
||||||
{
|
|
||||||
// Check if we have a provider for this field
|
|
||||||
if (!isset($this->fieldOptionProviders[$fieldName])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the provider with current context to generate dynamic options
|
|
||||||
return $this->fieldOptionProviders[$fieldName]($bookingDto, $participantIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a field has option provider support.
|
|
||||||
*
|
|
||||||
* This method allows form builders to determine if a field can be
|
|
||||||
* dynamically configured by this service. It's useful for deciding
|
|
||||||
* whether to use static field options or dynamic configuration.
|
|
||||||
*
|
|
||||||
* @param string $fieldName The name of the field to check
|
|
||||||
*
|
|
||||||
* @return bool True if the field has registered option providers, false otherwise
|
|
||||||
*/
|
|
||||||
public function hasFieldOptions(string $fieldName): bool
|
|
||||||
{
|
|
||||||
return isset($this->fieldOptionProviders[$fieldName]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,7 +65,7 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
|||||||
* - Easy to test individual field logic
|
* - Easy to test individual field logic
|
||||||
* - Supports complex interdependencies
|
* - Supports complex interdependencies
|
||||||
*/
|
*/
|
||||||
private function registerFieldOptionProviders(): void
|
protected function registerFieldOptionProviders(): void
|
||||||
{
|
{
|
||||||
// Room assignment field provider (only available for create workflow)
|
// Room assignment field provider (only available for create workflow)
|
||||||
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
$this->fieldOptionProviders['assignedRoomId'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
|
||||||
@@ -151,158 +95,4 @@ class ParticipantFieldOptionsProvider implements FieldStateProviderInterface
|
|||||||
// ],
|
// ],
|
||||||
// ];
|
// ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the dynamic state for a specified field.
|
|
||||||
*
|
|
||||||
* Evaluates all configured conditions for a field and returns the appropriate
|
|
||||||
* state modifications. State conditions are organized by state type (readonly,
|
|
||||||
* disabled, etc.) and evaluated independently.
|
|
||||||
*
|
|
||||||
* @param string $fieldName The name of the field to evaluate
|
|
||||||
* @param BookingDtoInterface $bookingDto The current booking data for context (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 array<string, mixed> Symfony form field options for state modifications
|
|
||||||
*/
|
|
||||||
public function getFieldState(string $fieldName, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
|
||||||
{
|
|
||||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$stateModifications = [];
|
|
||||||
$attributes = [];
|
|
||||||
|
|
||||||
foreach ($this->fieldStateConditions[$fieldName] as $stateType => $condition) {
|
|
||||||
if ($condition->evaluate($bookingDto, $participantIndex, $formData)) {
|
|
||||||
switch ($stateType) {
|
|
||||||
case 'readonly':
|
|
||||||
$attributes['readonly'] = true;
|
|
||||||
break;
|
|
||||||
case 'disabled':
|
|
||||||
$stateModifications['disabled'] = true;
|
|
||||||
break;
|
|
||||||
case 'required':
|
|
||||||
$stateModifications['required'] = true;
|
|
||||||
break;
|
|
||||||
case 'hidden':
|
|
||||||
$attributes['style'] = ($attributes['style'] ?? '').' display: none;';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($attributes)) {
|
|
||||||
$stateModifications['attr'] = $attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $stateModifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a field has state conditions configured.
|
|
||||||
*
|
|
||||||
* @param string $fieldName The name of the field to check
|
|
||||||
*
|
|
||||||
* @return bool True if the field has state conditions configured
|
|
||||||
*/
|
|
||||||
public function hasStateConditions(string $fieldName): bool
|
|
||||||
{
|
|
||||||
return isset($this->fieldStateConditions[$fieldName]) && !empty($this->fieldStateConditions[$fieldName]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns field names that trigger state re-evaluation for a given field.
|
|
||||||
*
|
|
||||||
* @param string $fieldName The name of the field to get dependencies for
|
|
||||||
*
|
|
||||||
* @return string[] Array of field names that affect the specified field's state
|
|
||||||
*/
|
|
||||||
public function getFieldStateDependencies(string $fieldName): array
|
|
||||||
{
|
|
||||||
if (!isset($this->fieldStateConditions[$fieldName])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$dependencies = [];
|
|
||||||
|
|
||||||
foreach ($this->fieldStateConditions[$fieldName] as $condition) {
|
|
||||||
$dependencies = array_merge($dependencies, $condition->getDependentFields());
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_unique($dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates field states for all configured fields at once.
|
|
||||||
*
|
|
||||||
* @param BookingDtoInterface $bookingDto The current booking data for context (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 array<string, array<string, mixed>> Field states indexed by field name
|
|
||||||
*/
|
|
||||||
public function getAllFieldStates(BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): array
|
|
||||||
{
|
|
||||||
$allStates = [];
|
|
||||||
|
|
||||||
foreach (array_keys($this->fieldStateConditions) as $fieldName) {
|
|
||||||
$fieldState = $this->getFieldState($fieldName, $bookingDto, $participantIndex, $formData);
|
|
||||||
if (!empty($fieldState)) {
|
|
||||||
$allStates[$fieldName] = $fieldState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $allStates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers field state conditions during service initialization.
|
|
||||||
*
|
|
||||||
* This method defines the conditional logic for field states. Each field
|
|
||||||
* can have multiple state conditions (readonly, disabled, hidden, required)
|
|
||||||
* that are evaluated independently.
|
|
||||||
*
|
|
||||||
* Adding new field state conditions:
|
|
||||||
* To add conditional state logic for a field, register conditions here:
|
|
||||||
*
|
|
||||||
* $this->fieldStateConditions['fieldName'] = [
|
|
||||||
* 'readonly' => new SomeCondition(),
|
|
||||||
* 'disabled' => CompositeCondition::and(
|
|
||||||
* new AgeRangeCondition(null, 17),
|
|
||||||
* FieldValueCondition::equals('someField', 'someValue')
|
|
||||||
* ),
|
|
||||||
* ];
|
|
||||||
*
|
|
||||||
* Condition Types:
|
|
||||||
* - 'readonly': Field is visible but not editable
|
|
||||||
* - 'disabled': Field interaction is disabled
|
|
||||||
* - 'required': Field becomes mandatory
|
|
||||||
* - 'hidden': Field is not displayed
|
|
||||||
*/
|
|
||||||
private function registerFieldStateConditions(): void
|
|
||||||
{
|
|
||||||
// Example field state conditions would be registered here
|
|
||||||
// For demonstration purposes, here are some example patterns:
|
|
||||||
|
|
||||||
// Example 1: Make assignedRoomId readonly for participants under 18
|
|
||||||
// $this->fieldStateConditions['assignedRoomId'] = [
|
|
||||||
// 'readonly' => new AgeRangeCondition(null, 17),
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// Example 2: Disable service selection if no room is assigned
|
|
||||||
// $this->fieldStateConditions['serviceSelection'] = [
|
|
||||||
// 'disabled' => FieldValueCondition::isEmpty('assignedRoomId'),
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// Example 3: Complex condition with multiple criteria
|
|
||||||
// $this->fieldStateConditions['advancedOptions'] = [
|
|
||||||
// 'hidden' => CompositeCondition::or(
|
|
||||||
// new AgeRangeCondition(null, 15),
|
|
||||||
// FieldValueCondition::equals('userType', 'basic')
|
|
||||||
// ),
|
|
||||||
// ];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form\Service;
|
namespace App\Form\Service\Trait;
|
||||||
|
|
||||||
use App\Form\Model\BookingDtoInterface;
|
use App\Form\Model\BookingDtoInterface;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
@@ -39,4 +39,4 @@ trait FormTraversalTrait
|
|||||||
|
|
||||||
return $data instanceof BookingDtoInterface ? $data : null;
|
return $data instanceof BookingDtoInterface ? $data : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,14 +11,51 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|||||||
|
|
||||||
class BookingService
|
class BookingService
|
||||||
{
|
{
|
||||||
|
public const BOOKING_CREATE_KEY = 'booking_create';
|
||||||
|
public const BOOKING_CREATE_BASELINE_KEY = 'booking_create_baseline_snapshot';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly TravelDataService $travelDataService,
|
private readonly TravelDataService $travelDataService,
|
||||||
) {}
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or creates the baseline room selection snapshot for change detection.
|
||||||
|
*
|
||||||
|
* The baseline snapshot captures the initial room selection state when step 1
|
||||||
|
* is first loaded, before any HTMX modifications. This ensures accurate change
|
||||||
|
* detection for room assignment resets.
|
||||||
|
*/
|
||||||
|
public function getOrCreateBaselineSnapshot(Request $request, BookingCreateDto $bookingCreateDto): array
|
||||||
|
{
|
||||||
|
$baselineKey = self::BOOKING_CREATE_BASELINE_KEY;
|
||||||
|
|
||||||
|
if (!$request->getSession()->has($baselineKey) || $request->query->has('reset_baseline')) {
|
||||||
|
$baseline = $this->createRoomSelectionSnapshot($bookingCreateDto);
|
||||||
|
$request->getSession()->set($baselineKey, $baseline);
|
||||||
|
|
||||||
|
return $baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->getSession()->get($baselineKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the baseline snapshot from the session.
|
||||||
|
*
|
||||||
|
* Should be called when moving to the next step or when the baseline
|
||||||
|
* needs to be refreshed.
|
||||||
|
*/
|
||||||
|
public function clearBaselineSnapshot(Request $request): void
|
||||||
|
{
|
||||||
|
$request->getSession()->remove('booking_create_baseline_snapshot');
|
||||||
|
}
|
||||||
|
|
||||||
public function getOrCreateBookingCreateDto(Request $request): BookingCreateDto
|
public function getOrCreateBookingCreateDto(Request $request): BookingCreateDto
|
||||||
{
|
{
|
||||||
|
$bookingCreateKey = self::BOOKING_CREATE_KEY;
|
||||||
$bookingUuid = $request->query->get('uid');
|
$bookingUuid = $request->query->get('uid');
|
||||||
$bookingCreateDto = $request->getSession()->get('booking_create');
|
$bookingCreateDto = $request->getSession()->get($bookingCreateKey);
|
||||||
|
|
||||||
// No UID parameter - return existing DTO from session if available
|
// No UID parameter - return existing DTO from session if available
|
||||||
if (null === $bookingUuid && null !== $bookingCreateDto) {
|
if (null === $bookingUuid && null !== $bookingCreateDto) {
|
||||||
@@ -41,7 +78,7 @@ class BookingService
|
|||||||
$availableRooms = $travelData->getAvailableRooms();
|
$availableRooms = $travelData->getAvailableRooms();
|
||||||
|
|
||||||
$roomSelections = array_map(
|
$roomSelections = array_map(
|
||||||
fn(Room $room) => $this->createRoomSelection($room, $roomsIdsAndQuantities),
|
fn (Room $room) => $this->createRoomSelection($room, $roomsIdsAndQuantities),
|
||||||
$availableRooms
|
$availableRooms
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -55,7 +92,7 @@ class BookingService
|
|||||||
|
|
||||||
public function saveBookingCreateDto(Request $request, BookingCreateDto $bookingCreateDto): void
|
public function saveBookingCreateDto(Request $request, BookingCreateDto $bookingCreateDto): void
|
||||||
{
|
{
|
||||||
$request->getSession()->set('booking_create', $bookingCreateDto);
|
$request->getSession()->set(self::BOOKING_CREATE_KEY, $bookingCreateDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createRoomSelection(Room $room, array $roomsIdsAndQuantities): RoomSelectionDto
|
private function createRoomSelection(Room $room, array $roomsIdsAndQuantities): RoomSelectionDto
|
||||||
@@ -188,8 +225,8 @@ class BookingService
|
|||||||
*/
|
*/
|
||||||
public function shouldResetAssignments(BookingCreateDto $oldDto, BookingCreateDto $newDto): bool
|
public function shouldResetAssignments(BookingCreateDto $oldDto, BookingCreateDto $newDto): bool
|
||||||
{
|
{
|
||||||
$old = array_map(fn($roomSelectionDto) => [$roomSelectionDto->roomId, $roomSelectionDto->quantity], $oldDto->roomSelections);
|
$old = array_map(fn ($roomSelectionDto) => [$roomSelectionDto->roomId, $roomSelectionDto->quantity], $oldDto->roomSelections);
|
||||||
$new = array_map(fn($roomSelectionDto) => [$roomSelectionDto->roomId, $roomSelectionDto->quantity], $newDto->roomSelections);
|
$new = array_map(fn ($roomSelectionDto) => [$roomSelectionDto->roomId, $roomSelectionDto->quantity], $newDto->roomSelections);
|
||||||
|
|
||||||
return $old !== $new;
|
return $old !== $new;
|
||||||
}
|
}
|
||||||
@@ -212,7 +249,7 @@ class BookingService
|
|||||||
public function createRoomSelectionSnapshot(BookingCreateDto $dto): array
|
public function createRoomSelectionSnapshot(BookingCreateDto $dto): array
|
||||||
{
|
{
|
||||||
return array_map(
|
return array_map(
|
||||||
fn($roomSelection) => [$roomSelection->roomId, $roomSelection->quantity],
|
fn ($roomSelection) => [(int) $roomSelection->roomId, (int) $roomSelection->quantity],
|
||||||
$dto->roomSelections
|
$dto->roomSelections
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -223,6 +260,7 @@ class BookingService
|
|||||||
public function hasRoomSelectionChanged(array $oldSnapshot, BookingCreateDto $newDto): bool
|
public function hasRoomSelectionChanged(array $oldSnapshot, BookingCreateDto $newDto): bool
|
||||||
{
|
{
|
||||||
$newSnapshot = $this->createRoomSelectionSnapshot($newDto);
|
$newSnapshot = $this->createRoomSelectionSnapshot($newDto);
|
||||||
|
|
||||||
return $oldSnapshot !== $newSnapshot;
|
return $oldSnapshot !== $newSnapshot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<li>
|
<li>
|
||||||
{{ roomSelection.quantity }} x {{ roomSelection.roomLabel }}
|
{{ roomSelection.quantity }} x {{ roomSelection.roomLabel }}
|
||||||
{% if assignmentCounts is defined and assignmentCounts[roomSelection.roomId] is defined %}
|
{% if assignmentCounts is defined and assignmentCounts[roomSelection.roomId] is defined %}
|
||||||
<span class="block text-sm">{{ assignmentCounts[roomSelection.roomId] }}/{{ roomSelection.quantity }} belegt</span>
|
<span class="block text-sm">{{ assignmentCounts[roomSelection.roomId] }} belegt</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<li>
|
<li>
|
||||||
{{ roomSelection.quantity }} x {{ roomSelection.roomLabel }}
|
{{ roomSelection.quantity }} x {{ roomSelection.roomLabel }}
|
||||||
{% if assignmentCounts is defined and assignmentCounts[roomSelection.roomId] is defined %}
|
{% if assignmentCounts is defined and assignmentCounts[roomSelection.roomId] is defined %}
|
||||||
<span class="block text-sm">{{ assignmentCounts[roomSelection.roomId] }}/{{ roomSelection.quantity }} belegt</span>
|
<span class="block text-sm">{{ assignmentCounts[roomSelection.roomId] }} belegt</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<div id="participants-form" class="space-y-8 pb-8"{% if htmx_oob_swap is defined and htmx_oob_swap %} hx-swap-oob="true"{% endif %}>
|
<div id="participants-form" class="space-y-8 pb-8"{% if htmx_oob_swap is defined and htmx_oob_swap %} hx-swap-oob="true"{% endif %}>
|
||||||
{% for participant in form.participants %}
|
{% for participant in form.participants %}
|
||||||
{% set participantDataValid = participant.vars.valid %}
|
{% set participantDataValid = participant.vars.valid %}
|
||||||
<div class="{{ html_classes('border rounded px-4 py-2', { 'border-gray-300': participantDataValid, 'border-red-700': not participantDataValid }) }}" {{ stimulus_controller('toggle', {'open': participant.vars.valid == false}, { 'closed': 'hidden' }) }}>
|
<div class="{{ html_classes('border rounded px-4 py-2', { 'border-gray-300': participantDataValid, 'border-red-700': not participantDataValid }) }}" {{ stimulus_controller('toggle', {'storageKey': 'participant_' ~ loop.index0, 'open': participant.vars.valid == false}, { 'closed': 'hidden' }) }}>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="w-full flex items-center justify-between">
|
<legend class="w-full flex items-center justify-between">
|
||||||
<span class="font-bold text-xl">Teilnehmer:in {{ loop.index }}</span>
|
<span class="font-bold text-xl">Teilnehmer:in {{ loop.index }}</span>
|
||||||
|
|||||||
@@ -36,16 +36,16 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testCreateUpdateRequestPayloadWithCompleteData(): void
|
public function testCreateUpdateRequestPayloadWithCompleteData(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertIsArray($result);
|
$this->assertIsArray($result);
|
||||||
$this->assertArrayHasKey('idbuchung', $result);
|
$this->assertArrayHasKey('idbuchung', $result);
|
||||||
$this->assertArrayHasKey('teilnehmerliste', $result);
|
$this->assertArrayHasKey('teilnehmerliste', $result);
|
||||||
$this->assertArrayHasKey('zusatzleistungen', $result);
|
$this->assertArrayHasKey('zusatzleistungen', $result);
|
||||||
$this->assertArrayHasKey('beförderungen', $result);
|
$this->assertArrayHasKey('beförderungen', $result);
|
||||||
$this->assertArrayHasKey('ferienzielunterbringungen', $result);
|
$this->assertArrayHasKey('ferienzielunterbringungen', $result);
|
||||||
|
|
||||||
$this->assertEquals(123, $result['idbuchung']);
|
$this->assertEquals(123, $result['idbuchung']);
|
||||||
$this->assertEquals('ACTIVE', $result['status']);
|
$this->assertEquals('ACTIVE', $result['status']);
|
||||||
$this->assertCount(2, $result['teilnehmerliste']['teilnehmer']);
|
$this->assertCount(2, $result['teilnehmerliste']['teilnehmer']);
|
||||||
@@ -54,9 +54,9 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testCanceledParticipantsAreSkipped(): void
|
public function testCanceledParticipantsAreSkipped(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithCanceledParticipant();
|
$formData = $this->createFormDataWithCanceledParticipant();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertCount(2, $result['teilnehmerliste']['teilnehmer']);
|
$this->assertCount(2, $result['teilnehmerliste']['teilnehmer']);
|
||||||
$this->assertEquals(1, $result['teilnehmerliste']['teilnehmer'][0]['@id']);
|
$this->assertEquals(1, $result['teilnehmerliste']['teilnehmer'][0]['@id']);
|
||||||
$this->assertEquals(2, $result['teilnehmerliste']['teilnehmer'][1]['@id']);
|
$this->assertEquals(2, $result['teilnehmerliste']['teilnehmer'][1]['@id']);
|
||||||
@@ -65,12 +65,12 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testAdditionalServicesProcessing(): void
|
public function testAdditionalServicesProcessing(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithAdditionalServices();
|
$formData = $this->createFormDataWithAdditionalServices();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertNotEmpty($result['zusatzleistungen']['zusatzleistung']);
|
$this->assertNotEmpty($result['zusatzleistungen']['zusatzleistung']);
|
||||||
$this->assertCount(2, $result['zusatzleistungen']['zusatzleistung']);
|
$this->assertCount(2, $result['zusatzleistungen']['zusatzleistung']);
|
||||||
|
|
||||||
$service = $result['zusatzleistungen']['zusatzleistung'][0];
|
$service = $result['zusatzleistungen']['zusatzleistung'][0];
|
||||||
$this->assertEquals(1, $service['@idleistung']);
|
$this->assertEquals(1, $service['@idleistung']);
|
||||||
$this->assertEquals(1, $service['@anzahl']);
|
$this->assertEquals(1, $service['@anzahl']);
|
||||||
@@ -80,9 +80,9 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testTransportationServicesProcessing(): void
|
public function testTransportationServicesProcessing(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithTransportation();
|
$formData = $this->createFormDataWithTransportation();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertNotEmpty($result['beförderungen']['beförderung']);
|
$this->assertNotEmpty($result['beförderungen']['beförderung']);
|
||||||
$this->assertCount(2, $result['beförderungen']['beförderung']);
|
$this->assertCount(2, $result['beförderungen']['beförderung']);
|
||||||
}
|
}
|
||||||
@@ -90,9 +90,9 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testBusPickupLocationsProcessing(): void
|
public function testBusPickupLocationsProcessing(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithBusPickup();
|
$formData = $this->createFormDataWithBusPickup();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertArrayHasKey('zustiege', $result);
|
$this->assertArrayHasKey('zustiege', $result);
|
||||||
$this->assertNotEmpty($result['zustiege']['zustieg']);
|
$this->assertNotEmpty($result['zustiege']['zustieg']);
|
||||||
$this->assertEquals(1, $result['zustiege']['zustieg'][0]['@idzustieg']);
|
$this->assertEquals(1, $result['zustiege']['zustieg'][0]['@idzustieg']);
|
||||||
@@ -101,18 +101,18 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testNonBusTransportationSkipsPickup(): void
|
public function testNonBusTransportationSkipsPickup(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithNonBusTransportation();
|
$formData = $this->createFormDataWithNonBusTransportation();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertArrayNotHasKey('zustiege', $result);
|
$this->assertArrayNotHasKey('zustiege', $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnusedServicesAreRemoved(): void
|
public function testUnusedServicesAreRemoved(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithUnusedServices();
|
$formData = $this->createFormDataWithUnusedServices();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertEmpty($result['zusatzleistungen']['zusatzleistung']);
|
$this->assertEmpty($result['zusatzleistungen']['zusatzleistung']);
|
||||||
$this->assertEmpty($result['beförderungen']['beförderung']);
|
$this->assertEmpty($result['beförderungen']['beförderung']);
|
||||||
}
|
}
|
||||||
@@ -120,9 +120,9 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testParticipantPersonalDataUpdate(): void
|
public function testParticipantPersonalDataUpdate(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithUpdatedPersonalData();
|
$formData = $this->createFormDataWithUpdatedPersonalData();
|
||||||
|
|
||||||
$this->processor->createUpdateRequestPayload($formData);
|
$this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$participant = $formData->booking->participants[0];
|
$participant = $formData->booking->participants[0];
|
||||||
$this->assertEquals('Updated', $participant->firstName);
|
$this->assertEquals('Updated', $participant->firstName);
|
||||||
$this->assertEquals('Participant', $participant->name);
|
$this->assertEquals('Participant', $participant->name);
|
||||||
@@ -133,9 +133,9 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testInactiveParticipantsPersonalDataNotUpdated(): void
|
public function testInactiveParticipantsPersonalDataNotUpdated(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithInactiveParticipant();
|
$formData = $this->createFormDataWithInactiveParticipant();
|
||||||
|
|
||||||
$this->processor->createUpdateRequestPayload($formData);
|
$this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$participant = $formData->booking->participants[0];
|
$participant = $formData->booking->participants[0];
|
||||||
$this->assertEquals('Original', $participant->firstName);
|
$this->assertEquals('Original', $participant->firstName);
|
||||||
$this->assertEquals('Name', $participant->name);
|
$this->assertEquals('Name', $participant->name);
|
||||||
@@ -144,12 +144,12 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testApplicantDataSyncWithFirstParticipant(): void
|
public function testApplicantDataSyncWithFirstParticipant(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataForApplicantSync();
|
$formData = $this->createFormDataForApplicantSync();
|
||||||
|
|
||||||
$this->processor->createUpdateRequestPayload($formData);
|
$this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$applicant = $formData->booking->applicant;
|
$applicant = $formData->booking->applicant;
|
||||||
$firstParticipant = $formData->booking->participants[0];
|
$firstParticipant = $formData->booking->participants[0];
|
||||||
|
|
||||||
$this->assertEquals($firstParticipant->height, $applicant->height);
|
$this->assertEquals($firstParticipant->height, $applicant->height);
|
||||||
$this->assertEquals($firstParticipant->weight, $applicant->weight);
|
$this->assertEquals($firstParticipant->weight, $applicant->weight);
|
||||||
$this->assertEquals($firstParticipant->shoeSize, $applicant->shoeSize);
|
$this->assertEquals($firstParticipant->shoeSize, $applicant->shoeSize);
|
||||||
@@ -158,9 +158,9 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testBankAccountIncludedInPayload(): void
|
public function testBankAccountIncludedInPayload(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithBankAccount();
|
$formData = $this->createFormDataWithBankAccount();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertArrayHasKey('bankverbindung', $result['zahlung']);
|
$this->assertArrayHasKey('bankverbindung', $result['zahlung']);
|
||||||
$this->assertEquals('Test Bank', $result['zahlung']['bankverbindung']['@kreditinstitut']);
|
$this->assertEquals('Test Bank', $result['zahlung']['bankverbindung']['@kreditinstitut']);
|
||||||
$this->assertEquals('DE89370400440532013000', $result['zahlung']['bankverbindung']['@iban']);
|
$this->assertEquals('DE89370400440532013000', $result['zahlung']['bankverbindung']['@iban']);
|
||||||
@@ -169,20 +169,20 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testBankAccountNotIncludedWhenNull(): void
|
public function testBankAccountNotIncludedWhenNull(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithoutBankAccount();
|
$formData = $this->createFormDataWithoutBankAccount();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertArrayNotHasKey('bankverbindung', $result['zahlung']);
|
$this->assertArrayNotHasKey('bankverbindung', $result['zahlung']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAccommodationRoomsProcessing(): void
|
public function testAccommodationRoomsProcessing(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithRooms();
|
$formData = $this->createFormDataWithRooms();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertNotEmpty($result['ferienzielunterbringungen']['ferienzielunterbringung']);
|
$this->assertNotEmpty($result['ferienzielunterbringungen']['ferienzielunterbringung']);
|
||||||
|
|
||||||
$room = $result['ferienzielunterbringungen']['ferienzielunterbringung'][0];
|
$room = $result['ferienzielunterbringungen']['ferienzielunterbringung'][0];
|
||||||
$this->assertEquals(1, $room['@idzimmer']);
|
$this->assertEquals(1, $room['@idzimmer']);
|
||||||
$this->assertEquals('DOUBLE', $room['@kategorie']);
|
$this->assertEquals('DOUBLE', $room['@kategorie']);
|
||||||
@@ -193,9 +193,9 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testCommunicationObjectCreation(): void
|
public function testCommunicationObjectCreation(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithoutExistingCommunication();
|
$formData = $this->createFormDataWithoutExistingCommunication();
|
||||||
|
|
||||||
$this->processor->createUpdateRequestPayload($formData);
|
$this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$participant = $formData->booking->participants[0];
|
$participant = $formData->booking->participants[0];
|
||||||
$this->assertInstanceOf(Communication::class, $participant->communication);
|
$this->assertInstanceOf(Communication::class, $participant->communication);
|
||||||
$this->assertEquals('[email protected]', $participant->communication->email);
|
$this->assertEquals('[email protected]', $participant->communication->email);
|
||||||
@@ -204,150 +204,144 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
public function testEmptyPickupsToDoesNotCreateZustiegeSection(): void
|
public function testEmptyPickupsToDoesNotCreateZustiegeSection(): void
|
||||||
{
|
{
|
||||||
$formData = $this->createFormDataWithoutPickups();
|
$formData = $this->createFormDataWithoutPickups();
|
||||||
|
|
||||||
$result = $this->processor->createUpdateRequestPayload($formData);
|
$result = $this->processor->createUpdateRequestPayload($formData);
|
||||||
|
|
||||||
$this->assertArrayNotHasKey('zustiege', $result);
|
$this->assertArrayNotHasKey('zustiege', $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createCompleteFormData(): BookingEditDto
|
private function createCompleteFormData(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = new BookingEditDto();
|
$formData = new BookingEditDto($this->createMockBooking(), $this->createMockTravel());
|
||||||
|
|
||||||
$formData->booking = $this->createMockBooking();
|
|
||||||
$formData->travel = $this->createMockTravel();
|
|
||||||
$formData->participants = [
|
$formData->participants = [
|
||||||
$this->createMockParticipantDto(0, 'F'),
|
$this->createMockParticipantDto(0, 'F'),
|
||||||
$this->createMockParticipantDto(1, 'F'),
|
$this->createMockParticipantDto(1, 'F'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithCanceledParticipant(): BookingEditDto
|
private function createFormDataWithCanceledParticipant(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = new BookingEditDto();
|
$formData = new BookingEditDto($this->createMockBooking(), $this->createMockTravel());
|
||||||
|
|
||||||
$formData->booking = $this->createMockBooking();
|
|
||||||
$formData->travel = $this->createMockTravel();
|
|
||||||
$formData->participants = [
|
$formData->participants = [
|
||||||
$this->createMockParticipantDto(0, 'F'),
|
$this->createMockParticipantDto(0, 'F'),
|
||||||
$this->createMockParticipantDto(1, 'S'), // Canceled
|
$this->createMockParticipantDto(1, 'S'), // Canceled
|
||||||
];
|
];
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithAdditionalServices(): BookingEditDto
|
private function createFormDataWithAdditionalServices(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$participant = $formData->participants[0];
|
$participant = $formData->participants[0];
|
||||||
$participant->courses = [$this->createMockService(1)];
|
$participant->courses = [$this->createMockService(1)];
|
||||||
$participant->additionalServices = [$this->createMockService(2)];
|
$participant->additionalServices = [$this->createMockService(2)];
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithTransportation(): BookingEditDto
|
private function createFormDataWithTransportation(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$participant = $formData->participants[0];
|
$participant = $formData->participants[0];
|
||||||
$participant->transportationServiceTo = $this->createMockService(1);
|
$participant->transportationServiceTo = $this->createMockService(1);
|
||||||
$participant->transportationServiceFro = $this->createMockService(2);
|
$participant->transportationServiceFro = $this->createMockService(2);
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithBusPickup(): BookingEditDto
|
private function createFormDataWithBusPickup(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$participant = $formData->participants[0];
|
$participant = $formData->participants[0];
|
||||||
$busService = $this->createMockService(1);
|
$busService = $this->createMockService(1);
|
||||||
$busService->subType = 'BUS';
|
$busService->subType = 'BUS';
|
||||||
$participant->transportationServiceTo = $busService;
|
$participant->transportationServiceTo = $busService;
|
||||||
$participant->pickup = $this->createMockPickup(1);
|
$participant->pickup = $this->createMockPickup(1);
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithNonBusTransportation(): BookingEditDto
|
private function createFormDataWithNonBusTransportation(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$participant = $formData->participants[0];
|
$participant = $formData->participants[0];
|
||||||
$trainService = $this->createMockService(1);
|
$trainService = $this->createMockService(1);
|
||||||
$trainService->subType = 'TRAIN';
|
$trainService->subType = 'TRAIN';
|
||||||
$participant->transportationServiceTo = $trainService;
|
$participant->transportationServiceTo = $trainService;
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithUnusedServices(): BookingEditDto
|
private function createFormDataWithUnusedServices(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
// Remove all participants so services become unused
|
// Remove all participants so services become unused
|
||||||
$formData->participants = [];
|
$formData->participants = [];
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithUpdatedPersonalData(): BookingEditDto
|
private function createFormDataWithUpdatedPersonalData(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$participant = $formData->participants[0];
|
$participant = $formData->participants[0];
|
||||||
$participant->firstName = 'Updated';
|
$participant->firstName = 'Updated';
|
||||||
$participant->lastName = 'Participant';
|
$participant->lastName = 'Participant';
|
||||||
$participant->email = '[email protected]';
|
$participant->email = '[email protected]';
|
||||||
$participant->mobile = '+49123456789';
|
$participant->mobile = '+49123456789';
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithInactiveParticipant(): BookingEditDto
|
private function createFormDataWithInactiveParticipant(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$participant = $formData->participants[0];
|
$participant = $formData->participants[0];
|
||||||
$participant->status = 'C'; // Inactive status
|
$participant->status = 'C'; // Inactive status
|
||||||
$participant->firstName = 'Updated';
|
$participant->firstName = 'Updated';
|
||||||
$participant->lastName = 'Participant';
|
$participant->lastName = 'Participant';
|
||||||
|
|
||||||
// Ensure original data remains unchanged
|
// Ensure original data remains unchanged
|
||||||
$formData->booking->participants[0]->firstName = 'Original';
|
$formData->booking->participants[0]->firstName = 'Original';
|
||||||
$formData->booking->participants[0]->name = 'Name';
|
$formData->booking->participants[0]->name = 'Name';
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataForApplicantSync(): BookingEditDto
|
private function createFormDataForApplicantSync(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$firstParticipant = $formData->booking->participants[0];
|
$firstParticipant = $formData->booking->participants[0];
|
||||||
$firstParticipant->height = '180';
|
$firstParticipant->height = '180';
|
||||||
$firstParticipant->weight = '75';
|
$firstParticipant->weight = '75';
|
||||||
$firstParticipant->shoeSize = '42';
|
$firstParticipant->shoeSize = '42';
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithBankAccount(): BookingEditDto
|
private function createFormDataWithBankAccount(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$bankAccount = new BankAccount();
|
$bankAccount = new BankAccount();
|
||||||
$bankAccount->bankName = 'Test Bank';
|
$bankAccount->bankName = 'Test Bank';
|
||||||
$bankAccount->iban = 'DE89370400440532013000';
|
$bankAccount->iban = 'DE89370400440532013000';
|
||||||
$bankAccount->bic = 'COBADEFFXXX';
|
$bankAccount->bic = 'COBADEFFXXX';
|
||||||
$bankAccount->holder = 'Test Holder';
|
$bankAccount->holder = 'Test Holder';
|
||||||
|
|
||||||
$formData->booking->bankAccount = $bankAccount;
|
$formData->booking->bankAccount = $bankAccount;
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,14 +349,14 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
$formData->booking->bankAccount = null;
|
$formData->booking->bankAccount = null;
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithRooms(): BookingEditDto
|
private function createFormDataWithRooms(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$room = new Room();
|
$room = new Room();
|
||||||
$room->id = 1;
|
$room->id = 1;
|
||||||
$room->category = 'DOUBLE';
|
$room->category = 'DOUBLE';
|
||||||
@@ -371,22 +365,22 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
$room->dateTo = new \DateTimeImmutable('2024-01-07');
|
$room->dateTo = new \DateTimeImmutable('2024-01-07');
|
||||||
$room->totalCount = 2;
|
$room->totalCount = 2;
|
||||||
$room->mapping = [0, 1];
|
$room->mapping = [0, 1];
|
||||||
|
|
||||||
$formData->booking->rooms = [$room];
|
$formData->booking->rooms = [$room];
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createFormDataWithoutExistingCommunication(): BookingEditDto
|
private function createFormDataWithoutExistingCommunication(): BookingEditDto
|
||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
|
|
||||||
$participant = $formData->participants[0];
|
$participant = $formData->participants[0];
|
||||||
$participant->email = '[email protected]';
|
$participant->email = '[email protected]';
|
||||||
$participant->mobile = '+49987654321';
|
$participant->mobile = '+49987654321';
|
||||||
|
|
||||||
$formData->booking->participants[0]->communication = new Communication();
|
$formData->booking->participants[0]->communication = new Communication();
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +388,7 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
{
|
{
|
||||||
$formData = $this->createCompleteFormData();
|
$formData = $this->createCompleteFormData();
|
||||||
$formData->booking->pickupsTo = [];
|
$formData->booking->pickupsTo = [];
|
||||||
|
|
||||||
return $formData;
|
return $formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +415,7 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
$booking->applicant = $this->createMockPersonalData('Applicant');
|
$booking->applicant = $this->createMockPersonalData('Applicant');
|
||||||
$booking->bankAccount = null;
|
$booking->bankAccount = null;
|
||||||
$booking->rooms = [];
|
$booking->rooms = [];
|
||||||
|
|
||||||
return $booking;
|
return $booking;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,7 +430,7 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
1 => $this->createMockService(1),
|
1 => $this->createMockService(1),
|
||||||
2 => $this->createMockService(2),
|
2 => $this->createMockService(2),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $travel;
|
return $travel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,7 +457,7 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
$participant->transportationServiceTo = $this->createMockService(1);
|
$participant->transportationServiceTo = $this->createMockService(1);
|
||||||
$participant->transportationServiceFro = $this->createMockService(2);
|
$participant->transportationServiceFro = $this->createMockService(2);
|
||||||
$participant->pickup = null;
|
$participant->pickup = null;
|
||||||
|
|
||||||
return $participant;
|
return $participant;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,7 +474,7 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
$personalData->shoeSize = '40';
|
$personalData->shoeSize = '40';
|
||||||
$personalData->address = new Address();
|
$personalData->address = new Address();
|
||||||
$personalData->communication = new Communication();
|
$personalData->communication = new Communication();
|
||||||
|
|
||||||
return $personalData;
|
return $personalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +486,7 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
$service->mapping = [];
|
$service->mapping = [];
|
||||||
$service->individualPrice = [];
|
$service->individualPrice = [];
|
||||||
$service->subType = 'STANDARD';
|
$service->subType = 'STANDARD';
|
||||||
|
|
||||||
return $service;
|
return $service;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,7 +495,7 @@ class BookingDataProcessorTest extends TestCase
|
|||||||
$pickup = new Pickup();
|
$pickup = new Pickup();
|
||||||
$pickup->id = $id;
|
$pickup->id = $id;
|
||||||
$pickup->mapping = [];
|
$pickup->mapping = [];
|
||||||
|
|
||||||
return $pickup;
|
return $pickup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user