+```
+
+## Implementation Status
+
+### ✅ COMPLETED PHASE 1: XML Parsing & Age Constraints
+1. **✅ Extended Service Model** - Added age constraint properties (`ageFrom`, `ageTo`, `birthYearFrom`, `birthYearTo`, `ageConstraintType`)
+2. **✅ Created AgeConstraintResult DTO** - Structured data for parsed age constraints
+3. **✅ Created AgeConstraintParserInterface** - Extensible parser contract for future constraint types
+4. **✅ Implemented BirthYearConstraintParser** - Parses `JG:YYYY-YYYY` format constraints from XML
+5. **✅ Created AgeConstraintParserRegistry** - Manages multiple parsers, handles semicolon-separated constraints
+6. **✅ Enhanced TravelParser** - Integrated age constraint parsing for both absolute age and extensible formats
+7. **✅ Successfully tested XML parsing** - Confirmed correct extraction of age constraints from real BPN data
+
+### ✅ COMPLETED PHASE 2: Form Integration & Age-Based Filtering
+8. **✅ Created DateOfBirthProvidedCondition** - Checks if participant has provided birth date
+9. **✅ Created ServiceAgeEvaluator** - Evaluates service availability for participant age
+10. **✅ Enhanced CreateFieldStateProvider** - Added field visibility rules to hide age-dependent fields
+11. **✅ Enhanced ParticipantFieldOptionsProvider** - Integrated age-aware service filtering
+12. **✅ Enhanced hasValidFieldOptions method** - Handles empty choices from age filtering
+
+### ✅ RESOLVED: Age Filtering Implementation Complete
+**Resolution:** Age-based service filtering is now working correctly - services with age constraints are properly filtered based on participant birth date.
+
+### ✅ COMPLETED PHASE 3: Form Field Management & Room-Based Fields
+13. **✅ Age filtering system** - Age-based service filtering working correctly
+14. **✅ Service configuration** - ServiceAgeEvaluator instantiated directly (no DI needed)
+15. **✅ Skipass field implementation** - Added single-selection skipass field with age/date validation
+16. **✅ Service selection bug fixes** - Fixed form submission issues with service ID/object handling
+17. **✅ Room-based conditional field system** - Added remarksRoom field for specific room types
+18. **✅ Field visibility improvements** - Fields no longer just hidden but excluded from form entirely
+19. **✅ Dynamic field type system** - Simplified field configuration with type mapping
+20. **✅ Room code normalization** - Case-insensitive room code matching (mbz/MBZ/Mbz)
+
+### 📋 REMAINING IMPLEMENTATION STEPS
+21. **⏳ Field placeholder system** - Add explanatory content for missing age
+22. **⏳ Enhanced date of birth field** - Smart guidance styling
+23. **⏳ HTMX transitions** - Smooth field additions
+24. **⏳ Stimulus controllers** - (`age-dependent-fields`, `field-transitions`, `participant-hints`)
+25. **⏳ Template enhancements** - UX improvements and placeholder system
+26. **⏳ Service model validation** - Ensure all constraint types properly handled
+27. **⏳ Comprehensive testing** - UX flows and edge cases
+
+### ✅ NEW FEATURES ADDED
+
+#### Skipass Field System
+- **Field Type**: Single-selection radio buttons (mandatory when available)
+- **Age Constraints**: Automatic filtering via existing ServiceAgeEvaluator
+- **Date Validation**: Travel date range validation for service availability
+- **Form Integration**: Proper ID/object handling with choice_value configuration
+- **Files Modified**:
+ - `ParticipantFieldOptionsProvider.php` - Added skipass field provider
+ - `ParticipantSkiPassFieldHandler.php` - Created field handler
+ - `ParticipantDto.php` - Changed from array to single Service object
+ - `BookingEditDto.php` - Conversion logic for existing bookings
+ - `BookingCreateParticipantType.php` - Added to dynamic fields
+ - `services.yaml` - Registered handler
+
+#### Room-Based Conditional Fields
+- **remarksRoom Field**: Textarea field for room-specific comments
+- **Conditional Logic**: Only appears when room with code 'mbz' is selected
+- **Case-Insensitive**: Room code matching normalized to lowercase
+- **Form Integration**: Uses TextareaType with XSS protection
+- **Files Created**:
+ - `RoomSelectionCondition.php` - Flexible room-based condition
+ - `ParticipantRemarksRoomFieldHandler.php` - Field processing
+- **Files Modified**:
+ - `CreateFieldStateProvider.php` - Added room selection condition
+ - `ParticipantDto.php` - Added remarksRoom property
+
+#### Improved Form Field Management
+- **Problem**: Fields were added to form but hidden with CSS
+- **Solution**: Fields now excluded from form entirely when conditions not met
+- **Benefits**: Cleaner HTML, better performance, no hidden field clutter
+- **Logic**: Check field state conditions BEFORE adding fields to form
+- **Choice Field Optimization**: Only validate choices for ChoiceType fields
+- **Field Type System**: Simple field name → form type mapping
+
+#### Service Selection Bug Fixes & HTMX Improvements
+- **HTMX Trigger Issue**: HTMX requests were not triggered for service fields
+ - **Root Cause**: HTMX attributes placed on container elements instead of individual form inputs
+ - **Solution**: Moved HTMX triggers to individual checkbox/radio inputs for expanded choice fields
+ - **Affected Fields**: board, skipass, courses, additionalServices, rentals
+ - **Result**: Real-time dynamic updates now work reliably for all service fields
+
+- **Field Handler Data Issue**: Field handlers were storing service IDs instead of Service objects
+ - **Root Cause**: Form submission processing returned IDs rather than full Service entities
+ - **Solution**: Updated all field handlers to retrieve and store complete Service objects
+ - **Impact**: Pricing calculator now has access to service price data
+ - **Affected Handlers**: ParticipantBoardFieldHandler, ParticipantSkipassFieldHandler, etc.
+
+- **Form Submission Reset Issue**: Service selections were lost on form submission
+ - **Root Cause**: Inconsistent choice_value configuration between service types
+ - **Solution**: Added 'choice_value' => 'id' to all service field providers
+ - **Affected Fields**: courses, additionalServices, board, rentals, skipass
+ - **Result**: Consistent form submission behavior across all service types
+
+**Legend:** ✅ Completed | 🔄 In Progress | 🔍 Investigating | ⏳ Pending
+
+## Key Benefits
+
+### Technical Benefits
+- **Leverages existing architecture** - no major refactoring needed
+- **Consistent patterns** - follows current field state and options provider patterns
+- **Dynamic updates** - automatically works with existing HTMX integration
+- **Flexible configuration** - easy to add new age-based rules
+- **Performance optimized** - fields not computed when not visible
+
+### UX Benefits
+- **Clear Expectations** - Users know what to expect after providing age
+- **Progressive Disclosure** - Information revealed at the right time
+- **Visual Continuity** - Placeholders maintain form layout consistency
+- **Contextual Help** - Specific guidance for different age scenarios
+- **Smooth Interactions** - Animated transitions for dynamic changes
+- **Accessibility** - Screen reader friendly field announcements
+- **Mobile Optimized** - Touch-friendly interactions and responsive design
+
+## UX Components to Add
+
+1. **Field Hint Cards** - Informational cards explaining missing fields
+2. **Age Requirement Badges** - Visual indicators on age-dependent sections
+3. **Progress Indicators** - Show completion status including hidden fields
+4. **Interactive Placeholders** - Clickable areas that focus date field
+5. **Transition Animations** - Smooth field appearance/disappearance
+6. **Contextual Tooltips** - Explain specific age requirements
+7. **Smart Form Validation** - Guide users to complete prerequisites first
+
+## Testing Scenarios
+
+### Functional Testing
+1. **Initial Load** - Proper placeholders and guidance shown when no date of birth
+2. **Progressive Disclosure** - Fields appear correctly as age is entered
+3. **Age Restrictions** - Correct fields hidden/shown based on age ranges
+4. **Dynamic Updates** - HTMX updates work seamlessly with field state changes
+5. **Form Validation** - Proper validation with conditional field visibility
+6. **Field Dependencies** - Complex conditions work correctly
+
+### UX Testing
+1. **Visual Feedback** - Clear indication of why fields are missing
+2. **Interaction Flow** - Smooth user journey through form completion
+3. **Accessibility** - Screen readers announce field changes properly
+4. **Mobile Experience** - Touch interactions work on mobile devices
+5. **Performance** - No lag during dynamic field updates
+6. **Error Handling** - Graceful handling of invalid dates and edge cases
+
+## Configuration Examples
+
+### Service Age Restrictions
+```php
+// Example: Add age restrictions to Service model or metadata
+$alcoholicBeverages = [
+ 'minAge' => 18,
+ 'description' => 'Nur für Erwachsene verfügbar'
+];
+
+$youthActivities = [
+ 'maxAge' => 15,
+ 'description' => 'Nur für Jugendliche unter 16'
+];
+
+$seniorServices = [
+ 'minAge' => 65,
+ 'description' => 'Spezielle Services für Senioren'
+];
+```
+
+### Field Age Requirements
+```php
+// Configuration for which fields require age verification
+private const AGE_RESTRICTED_FIELDS = [
+ 'courses' => ['requireAge' => true, 'reason' => 'Altersgerechte Kursauswahl'],
+ 'additionalServices' => ['requireAge' => true, 'reason' => 'Altersspezifische Services'],
+ 'alcoholicBeverages' => ['minAge' => 18, 'reason' => 'Jugendschutz'],
+ 'youthActivities' => ['maxAge' => 15, 'reason' => 'Nur für Jugendliche'],
+ 'seniorServices' => ['minAge' => 65, 'reason' => 'Seniorenspezifisch'],
+];
+```
+
+This comprehensive plan provides both robust technical implementation and excellent user experience for age-based field management, building seamlessly on the existing sophisticated form architecture.
\ No newline at end of file
diff --git a/docs/AGE_CONSTRAINTS_MODEL_EXTENSION_PLAN.md b/docs/AGE_CONSTRAINTS_MODEL_EXTENSION_PLAN.md
new file mode 100644
index 0000000..07296b4
--- /dev/null
+++ b/docs/AGE_CONSTRAINTS_MODEL_EXTENSION_PLAN.md
@@ -0,0 +1,641 @@
+# Age Constraints Model Extension Plan
+
+## Current Situation Analysis
+
+**XML Data Contains Two Age Constraint Formats:**
+1. **Absolute Age**: `614` (current age 6-14)
+2. **Birth Year Ranges**: `JG:2007-2009` (birth years 2007-2009)
+
+**Future Considerations:**
+- `hinweis_stamm` may contain additional constraint types beyond `JG:` (birth year)
+- Need extensible parsing system for future constraint formats
+- Maintain English naming conventions throughout
+
+**Current State:**
+- `Service` model has `ageFrom`/`ageTo` properties but they're not populated
+- `TravelParser` doesn't parse age-related XML nodes
+- Form processing doesn't consider age constraints
+
+## Required Changes
+
+### 1. Extend Service Model with Extensible Age Constraints
+
+**Add New Properties for Flexible Age Constraints:**
+```php
+// Add to Service class (src/BusProNet/Model/Service.php)
+
+#[Groups(['api:single', 'api:list'])]
+public ?int $birthYearFrom = null;
+
+#[Groups(['api:single', 'api:list'])]
+public ?int $birthYearTo = null;
+
+#[Groups(['api:single', 'api:list'])]
+public ?string $ageConstraintType = null; // 'absolute_age', 'birth_year', 'mixed'
+
+#[Groups(['api:single'])]
+public ?array $ageConstraintMetadata = null; // Extensible metadata for future constraint types
+
+#[Groups(['api:single'])]
+public ?string $rawAgeConstraintData = null; // Store original XML data for debugging/future parsing
+```
+
+### 2. Create Extensible Age Constraint Parser System
+
+**A. Create Age Constraint Parser Interface:**
+```php
+// src/BusProNet/XmlParser/Contract/AgeConstraintParserInterface.php
+interface AgeConstraintParserInterface
+{
+ public function canParse(string $constraintData): bool;
+ public function parse(string $constraintData): AgeConstraintResult;
+ public function getConstraintType(): string;
+}
+```
+
+**B. Create Age Constraint Result DTO:**
+```php
+// src/BusProNet/XmlParser/Model/AgeConstraintResult.php
+class AgeConstraintResult
+{
+ public function __construct(
+ public readonly string $type,
+ public readonly ?int $ageFrom = null,
+ public readonly ?int $ageTo = null,
+ public readonly ?int $birthYearFrom = null,
+ public readonly ?int $birthYearTo = null,
+ public readonly array $metadata = [],
+ public readonly ?string $rawData = null
+ ) {}
+
+ public function hasAgeConstraints(): bool
+ {
+ return null !== $this->ageFrom || null !== $this->ageTo;
+ }
+
+ public function hasBirthYearConstraints(): bool
+ {
+ return null !== $this->birthYearFrom || null !== $this->birthYearTo;
+ }
+
+ public function isEmpty(): bool
+ {
+ return !$this->hasAgeConstraints() && !$this->hasBirthYearConstraints();
+ }
+}
+```
+
+**C. Create Birth Year Constraint Parser:**
+```php
+// src/BusProNet/XmlParser/AgeConstraint/BirthYearConstraintParser.php
+class BirthYearConstraintParser implements AgeConstraintParserInterface
+{
+ private const BIRTH_YEAR_PREFIX = 'JG:';
+
+ public function canParse(string $constraintData): bool
+ {
+ return str_starts_with($constraintData, self::BIRTH_YEAR_PREFIX);
+ }
+
+ public function parse(string $constraintData): AgeConstraintResult
+ {
+ if (!$this->canParse($constraintData)) {
+ throw new InvalidArgumentException('Cannot parse constraint data: ' . $constraintData);
+ }
+
+ $yearData = substr($constraintData, strlen(self::BIRTH_YEAR_PREFIX));
+
+ // Parse range format "2007-2009"
+ if (str_contains($yearData, '-')) {
+ [$fromYear, $toYear] = explode('-', $yearData, 2);
+
+ return new AgeConstraintResult(
+ type: 'birth_year',
+ birthYearFrom: (int) trim($fromYear),
+ birthYearTo: (int) trim($toYear),
+ metadata: [
+ 'range_type' => 'birth_year_range',
+ 'original_format' => $yearData
+ ],
+ rawData: $constraintData
+ );
+ }
+
+ // Parse single year format "2007"
+ $year = (int) trim($yearData);
+ return new AgeConstraintResult(
+ type: 'birth_year',
+ birthYearFrom: $year,
+ birthYearTo: $year,
+ metadata: [
+ 'range_type' => 'birth_year_single',
+ 'original_format' => $yearData
+ ],
+ rawData: $constraintData
+ );
+ }
+
+ public function getConstraintType(): string
+ {
+ return 'birth_year';
+ }
+}
+```
+
+**D. Create Age Constraint Parser Registry:**
+```php
+// src/BusProNet/XmlParser/AgeConstraint/AgeConstraintParserRegistry.php
+class AgeConstraintParserRegistry
+{
+ /** @var AgeConstraintParserInterface[] */
+ private array $parsers = [];
+
+ public function __construct()
+ {
+ // Register built-in parsers
+ $this->addParser(new BirthYearConstraintParser());
+ }
+
+ public function addParser(AgeConstraintParserInterface $parser): void
+ {
+ $this->parsers[] = $parser;
+ }
+
+ public function parseConstraints(string $constraintData): AgeConstraintResult
+ {
+ // Try multiple constraint types (semicolon-separated, e.g., 'JG:2007-2009;GL:5-8')
+ $constraints = array_map('trim', explode(';', $constraintData));
+ $results = [];
+
+ foreach ($constraints as $constraint) {
+ if (empty($constraint)) {
+ continue;
+ }
+
+ foreach ($this->parsers as $parser) {
+ if ($parser->canParse($constraint)) {
+ $results[] = $parser->parse($constraint);
+ break; // First matching parser wins
+ }
+ }
+ }
+
+ // Merge results if multiple constraints found
+ return $this->mergeConstraintResults($results, $constraintData);
+ }
+
+ private function mergeConstraintResults(array $results, string $rawData): AgeConstraintResult
+ {
+ if (empty($results)) {
+ return new AgeConstraintResult(type: 'unknown', rawData: $rawData);
+ }
+
+ if (count($results) === 1) {
+ return $results[0];
+ }
+
+ // Merge multiple constraint results
+ $type = 'mixed';
+ $ageFrom = null;
+ $ageTo = null;
+ $birthYearFrom = null;
+ $birthYearTo = null;
+ $metadata = ['merged_from' => []];
+
+ foreach ($results as $result) {
+ $ageFrom = $this->mergeMinValue($ageFrom, $result->ageFrom);
+ $ageTo = $this->mergeMaxValue($ageTo, $result->ageTo);
+ $birthYearFrom = $this->mergeMinValue($birthYearFrom, $result->birthYearFrom);
+ $birthYearTo = $this->mergeMaxValue($birthYearTo, $result->birthYearTo);
+ $metadata['merged_from'][] = $result->type;
+ }
+
+ return new AgeConstraintResult(
+ type: $type,
+ ageFrom: $ageFrom,
+ ageTo: $ageTo,
+ birthYearFrom: $birthYearFrom,
+ birthYearTo: $birthYearTo,
+ metadata: $metadata,
+ rawData: $rawData
+ );
+ }
+
+ private function mergeMinValue(?int $current, ?int $new): ?int
+ {
+ if (null === $current) return $new;
+ if (null === $new) return $current;
+ return max($current, $new); // Most restrictive minimum
+ }
+
+ private function mergeMaxValue(?int $current, ?int $new): ?int
+ {
+ if (null === $current) return $new;
+ if (null === $new) return $current;
+ return min($current, $new); // Most restrictive maximum
+ }
+}
+```
+
+### 3. Enhance TravelParser with Extensible Constraint Parsing
+
+**Add Age Constraint Parsing to Service Methods:**
+```php
+// Add to TravelParser class
+private AgeConstraintParserRegistry $ageConstraintRegistry;
+
+public function __construct()
+{
+ $this->ageConstraintRegistry = new AgeConstraintParserRegistry();
+ // Future: inject via DI for custom parsers
+}
+
+// Update getAdditionalServices() method:
+private function parseServiceAgeConstraints(Crawler $serviceNode, Service $service): void
+{
+ // Parse absolute age constraints (altervon/alterbis)
+ $ageFrom = $this->getIntOrNullValue($serviceNode->filterXPath('.//altervon'));
+ $ageTo = $this->getIntOrNullValue($serviceNode->filterXPath('.//alterbis'));
+
+ // Parse extensible constraint data (hinweis_stamm -> ageConstraintData)
+ $constraintData = $this->getStringOrNullValue($serviceNode->filterXPath('.//hinweis_stamm'));
+
+ $constraintResult = null;
+ if (null !== $constraintData && !empty(trim($constraintData))) {
+ $constraintResult = $this->ageConstraintRegistry->parseConstraints($constraintData);
+ }
+
+ // Apply absolute age constraints
+ if (null !== $ageFrom || null !== $ageTo) {
+ $service->ageFrom = $ageFrom;
+ $service->ageTo = $ageTo;
+
+ if (null !== $constraintResult && !$constraintResult->isEmpty()) {
+ // Mixed constraints scenario
+ $service->ageConstraintType = 'mixed';
+ $service->birthYearFrom = $constraintResult->birthYearFrom;
+ $service->birthYearTo = $constraintResult->birthYearTo;
+ $service->ageConstraintMetadata = array_merge(
+ $constraintResult->metadata,
+ ['has_absolute_age' => true, 'has_birth_year' => true]
+ );
+ } else {
+ $service->ageConstraintType = 'absolute_age';
+ }
+ } elseif (null !== $constraintResult && !$constraintResult->isEmpty()) {
+ // Only constraint data (birth year, etc.)
+ $service->ageConstraintType = $constraintResult->type;
+ $service->birthYearFrom = $constraintResult->birthYearFrom;
+ $service->birthYearTo = $constraintResult->birthYearTo;
+ $service->ageConstraintMetadata = $constraintResult->metadata;
+ }
+
+ // Always store raw data for debugging/future parsing
+ if (null !== $constraintData) {
+ $service->rawAgeConstraintData = $constraintData;
+ }
+}
+
+// Update getAdditionalServices() method:
+public function getAdditionalServices(Crawler $node): array
+{
+ $additionalServices = [];
+
+ $node->each(function (Crawler $serviceNode) use (&$additionalServices) {
+ $serviceId = (int) $serviceNode->attr('idbuspro');
+
+ $service = new Service();
+ $service->source = Constants::SOURCE_TRAVEL;
+ $service->category = Constants::CATEGORY_ADDITIONAL;
+ $service->id = $serviceId;
+ $service->subType = $serviceNode->attr('unterart');
+ $service->mandatory = $this->stringToBool($serviceNode->attr('pflicht'));
+ $service->dateFrom = $this->stringToDate($serviceNode->attr('termin'));
+ $service->dateTo = $this->stringToDate($serviceNode->attr('bis'));
+ $service->label = $this->getStringOrNullValue($serviceNode->filterXPath('.//text'));
+ $service->price = $this->stringToFloat($this->getStringOrNullValue($serviceNode->filterXPath('.//preis')));
+ $service->status = $this->getStringOrNullValue($serviceNode->filterXPath('.//status'));
+
+ // Parse age constraints
+ $this->parseServiceAgeConstraints($serviceNode, $service);
+
+ $additionalServices[$serviceId] = $service;
+ });
+
+ return $additionalServices;
+}
+```
+
+### 4. Create Extensible Age Evaluation System
+
+**A. Enhanced Age Evaluation Interface:**
+```php
+// src/Form/Service/Contract/AgeEvaluatorInterface.php
+interface AgeEvaluatorInterface
+{
+ public function canEvaluate(Service $service): bool;
+ public function isServiceAvailableForParticipant(Service $service, BookingDtoInterface $bookingDto, int $participantIndex): bool;
+ public function getConstraintDescription(Service $service): string;
+}
+```
+
+**B. Create Service Age Evaluator:**
+```php
+// src/Form/Service/AgeEvaluator/ServiceAgeEvaluator.php
+class ServiceAgeEvaluator implements AgeEvaluatorInterface
+{
+ public function canEvaluate(Service $service): bool
+ {
+ return null !== $service->ageConstraintType;
+ }
+
+ public function isServiceAvailableForParticipant(Service $service, BookingDtoInterface $bookingDto, int $participantIndex): bool
+ {
+ $participant = $bookingDto->getParticipant($participantIndex);
+
+ if (null === $participant || null === $participant->dateOfBirth) {
+ return false; // Cannot evaluate without birth date
+ }
+
+ return match($service->ageConstraintType) {
+ 'absolute_age' => $this->evaluateAbsoluteAge($service, $participant->dateOfBirth),
+ 'birth_year' => $this->evaluateBirthYear($service, $participant->dateOfBirth),
+ 'mixed' => $this->evaluateAbsoluteAge($service, $participant->dateOfBirth)
+ && $this->evaluateBirthYear($service, $participant->dateOfBirth),
+ default => true // No constraints or unknown type
+ };
+ }
+
+ private function evaluateAbsoluteAge(Service $service, \DateTimeImmutable $dateOfBirth): bool
+ {
+ $age = $this->calculateAge($dateOfBirth);
+
+ if (null !== $service->ageFrom && $age < $service->ageFrom) {
+ return false;
+ }
+
+ if (null !== $service->ageTo && $age > $service->ageTo) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function evaluateBirthYear(Service $service, \DateTimeImmutable $dateOfBirth): bool
+ {
+ $birthYear = (int) $dateOfBirth->format('Y');
+
+ if (null !== $service->birthYearFrom && $birthYear < $service->birthYearFrom) {
+ return false;
+ }
+
+ if (null !== $service->birthYearTo && $birthYear > $service->birthYearTo) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function calculateAge(\DateTimeImmutable $dateOfBirth): int
+ {
+ $today = new \DateTimeImmutable();
+ return (int) $dateOfBirth->diff($today)->y;
+ }
+
+ public function getConstraintDescription(Service $service): string
+ {
+ return match($service->ageConstraintType) {
+ 'absolute_age' => $this->getAbsoluteAgeDescription($service),
+ 'birth_year' => $this->getBirthYearDescription($service),
+ 'mixed' => sprintf('%s and %s',
+ $this->getAbsoluteAgeDescription($service),
+ $this->getBirthYearDescription($service)),
+ default => 'No age restrictions'
+ };
+ }
+
+ private function getAbsoluteAgeDescription(Service $service): string
+ {
+ if (null !== $service->ageFrom && null !== $service->ageTo) {
+ return sprintf('Ages %d-%d', $service->ageFrom, $service->ageTo);
+ }
+
+ if (null !== $service->ageFrom) {
+ return sprintf('Age %d+', $service->ageFrom);
+ }
+
+ if (null !== $service->ageTo) {
+ return sprintf('Age up to %d', $service->ageTo);
+ }
+
+ return '';
+ }
+
+ private function getBirthYearDescription(Service $service): string
+ {
+ if (null !== $service->birthYearFrom && null !== $service->birthYearTo) {
+ if ($service->birthYearFrom === $service->birthYearTo) {
+ return sprintf('Born in %d', $service->birthYearFrom);
+ }
+ return sprintf('Born %d-%d', $service->birthYearFrom, $service->birthYearTo);
+ }
+
+ if (null !== $service->birthYearFrom) {
+ return sprintf('Born %d or later', $service->birthYearFrom);
+ }
+
+ if (null !== $service->birthYearTo) {
+ return sprintf('Born up to %d', $service->birthYearTo);
+ }
+
+ return '';
+ }
+}
+```
+
+### 5. Enhanced Form Field Options Provider
+
+**Update with Age-Aware Service Filtering:**
+```php
+// Add to ParticipantFieldOptionsProvider (simplified approach)
+// ServiceAgeEvaluator is instantiated directly when needed
+
+protected function registerFieldOptionProviders(): void
+{
+ // Enhanced field providers with age-aware filtering
+ $this->fieldOptionProviders['courses'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
+ 'label' => 'Kurse',
+ 'multiple' => true,
+ 'expanded' => true,
+ 'required' => false,
+ 'choices' => $this->filterServicesByAgeConstraints(
+ $bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_COURSES),
+ $bookingDto,
+ $participantIndex
+ ),
+ 'choice_label' => 'label',
+ ];
+
+ // Similar updates for additionalServices, rentals, board, etc.
+}
+
+private function filterServicesByAgeConstraints(array $services, BookingDtoInterface $bookingDto, int $participantIndex): array
+{
+ $participant = $bookingDto->getParticipant($participantIndex);
+
+ // If no birth date provided, return empty array (handled by DateOfBirthProvidedCondition)
+ if (null === $participant || null === $participant->dateOfBirth) {
+ return [];
+ }
+
+ return array_filter($services, function (Service $service) use ($bookingDto, $participantIndex) {
+ // No age constraints = available to all
+ $ageEvaluator = new ServiceAgeEvaluator();
+ if (!$ageEvaluator->canEvaluate($service)) {
+ return true;
+ }
+
+ return $ageEvaluator->isServiceAvailableForParticipant($service, $bookingDto, $participantIndex);
+ });
+}
+```
+
+### 6. Future Extension Examples
+
+**A. Example: Adding Grade Level Constraints (GL:5-8)**
+```php
+class GradeLevelConstraintParser implements AgeConstraintParserInterface
+{
+ private const GRADE_PREFIX = 'GL:';
+
+ public function canParse(string $constraintData): bool
+ {
+ return str_starts_with($constraintData, self::GRADE_PREFIX);
+ }
+
+ public function parse(string $constraintData): AgeConstraintResult
+ {
+ $gradeData = substr($constraintData, strlen(self::GRADE_PREFIX));
+
+ if (str_contains($gradeData, '-')) {
+ [$fromGrade, $toGrade] = explode('-', $gradeData, 2);
+
+ return new AgeConstraintResult(
+ type: 'grade_level',
+ metadata: [
+ 'grade_from' => (int) trim($fromGrade),
+ 'grade_to' => (int) trim($toGrade),
+ 'constraint_type' => 'grade_range'
+ ],
+ rawData: $constraintData
+ );
+ }
+
+ // Single grade
+ return new AgeConstraintResult(
+ type: 'grade_level',
+ metadata: [
+ 'grade' => (int) trim($gradeData),
+ 'constraint_type' => 'grade_single'
+ ],
+ rawData: $constraintData
+ );
+ }
+
+ public function getConstraintType(): string
+ {
+ return 'grade_level';
+ }
+}
+
+// Register in registry constructor:
+$this->addParser(new GradeLevelConstraintParser());
+```
+
+**B. Example: Complex Mixed Constraints (JG:2007-2009;GL:5-8)**
+- Registry automatically handles semicolon-separated constraints
+- Merges results into mixed constraint type
+- Evaluator can handle multiple constraint types
+
+### 7. Add Helper Methods
+
+**Add to AbstractParser:**
+```php
+protected function getIntOrNullValue(Crawler $node): ?int
+{
+ $value = $this->getStringOrNullValue($node);
+
+ if (null === $value || '' === trim($value)) {
+ return null;
+ }
+
+ return (int) $value;
+}
+```
+
+## Implementation Order
+
+1. **Create extensible constraint parser system** (interfaces, registry, birth year parser)
+2. **Extend Service model** with new age constraint properties
+3. **Update TravelParser** with extensible constraint parsing
+4. **Create age evaluator system** for service filtering
+5. **Update field options provider** with age-aware filtering
+6. **Add comprehensive tests** for parsing and evaluation
+7. **Add helper methods** to parser base class
+8. **Update documentation** with extensible patterns
+
+## Key Benefits
+
+### Technical Benefits
+- **Fully extensible** - easy to add new constraint types (grade level, membership status, etc.)
+- **Backward compatible** - existing absolute age constraints continue working
+- **English naming** - all properties and methods use clear English names
+- **Robust parsing** - handles malformed data gracefully
+- **Debuggable** - stores raw constraint data for troubleshooting
+- **Testable** - clear separation of parsing, evaluation, and filtering concerns
+
+### Future Extensibility
+- **Plugin architecture** - new constraint parsers can be added via DI
+- **Mixed constraints** - supports multiple constraint types per service
+- **Metadata storage** - extensible metadata for complex constraint types
+- **Version resilient** - unknown constraint types don't break existing functionality
+
+### Business Benefits
+- **Accurate service filtering** - services only shown to eligible participants
+- **Clear constraint communication** - descriptive messages for age restrictions
+- **Flexible business rules** - supports complex eligibility scenarios
+
+## Edge Cases Handled
+
+- **Invalid constraint formats** - graceful handling with fallback to 'unknown' type
+- **Mixed constraint scenarios** - services with both absolute age and birth year requirements
+- **Empty/null constraint data** - treated as no constraints (available to all)
+- **Future constraint types** - unknown parsers don't break existing functionality
+- **Malformed date ranges** - validation and error handling in parsers
+- **Single vs range values** - supports both `JG:2007` and `JG:2007-2009` formats
+
+## Testing Strategy
+
+### Unit Tests
+- **Constraint parsing** for all supported formats and edge cases
+- **Age evaluation** for different constraint types and participant scenarios
+- **Service filtering** with mixed constraint types
+- **Registry behavior** with multiple parsers and constraint merging
+
+### Integration Tests
+- **XML parsing** with real BPN export data containing age constraints
+- **Form field generation** with age-restricted services
+- **HTMX updates** when birth date changes affect service availability
+- **End-to-end booking flow** with age-restricted services
+
+## Related Completed Improvements
+
+**✅ Form Processing System Enhancements** (complementary to age constraints):
+- **Service Field HTMX Integration**: Fixed HTMX triggers for service fields (board, skipass, courses, etc.) to enable real-time updates for age-based field filtering
+- **Field Handler Data Storage**: Updated all service field handlers to store complete Service objects instead of IDs, enabling access to age constraint data
+- **Service Label Formatting**: Implemented smart service label formatting with pricing integration and quantity display
+- **Pricing Integration**: Service selections now properly integrate with pricing calculations, supporting age-restricted service pricing
+
+These improvements provide the foundation for implementing age constraint filtering once the XML parsing and model extensions described in this plan are completed.
+
+This plan provides a robust, extensible foundation for handling current age constraints while being prepared for future constraint types that may emerge from the XML data.
\ No newline at end of file
diff --git a/docs/DOCUMENTATION_UPDATES_2025-09-02.md b/docs/DOCUMENTATION_UPDATES_2025-09-02.md
new file mode 100644
index 0000000..40c52ab
--- /dev/null
+++ b/docs/DOCUMENTATION_UPDATES_2025-09-02.md
@@ -0,0 +1,168 @@
+# Documentation Updates - September 2, 2025
+
+## Overview
+
+This document summarizes the comprehensive documentation updates made to reflect the booking flow debugging and enhancements completed during the HTMX Service Selection Bug Investigation.
+
+## Updated Documentation Files
+
+### 1. AGE_BASED_FIELDS_PLAN.md
+
+**Section Updated**: Service Selection Bug Fixes & HTMX Improvements (Lines 425-442)
+
+**Key Changes**:
+- **HTMX Trigger Issue**: Added detailed explanation of HTMX attribute placement fix
+ - Root cause: HTMX attributes on container elements instead of individual inputs
+ - Solution: Moved HTMX triggers to individual checkbox/radio inputs for expanded choice fields
+ - Impact: Real-time dynamic updates now work reliably for all service fields
+
+- **Field Handler Data Issue**: Documented the Service object storage improvement
+ - Root cause: Field handlers storing service IDs instead of complete Service objects
+ - Solution: Updated all field handlers to retrieve and store complete Service entities
+ - Impact: Pricing calculator now has access to service price data
+
+- **Form Submission Reset Issue**: Clarified the choice_value configuration fix
+ - Root cause: Inconsistent choice_value configuration between service types
+ - Solution: Added 'choice_value' => 'id' to all service field providers
+ - Result: Consistent form submission behavior across all service types
+
+### 2. FORM_PROCESSING.md
+
+**Section Updated**: Field Handlers Architecture (Lines 235-251)
+
+**Key Additions**:
+- **Data Storage Strategy**: Added explanation of why Service objects are stored instead of IDs
+ - Enables pricing calculations to access service price data
+ - Eliminates need for additional database lookups during price calculation
+ - Provides immediate access to all service metadata
+
+**Section Updated**: HTMX Dynamic Updates (Lines 349-372)
+
+**Key Additions**:
+- **Service Field HTMX Integration**: Comprehensive explanation of expanded choice field HTMX handling
+ - Issue identification: Container-level HTMX attributes don't work for checkboxes/radios
+ - Solution implementation: Individual input-level HTMX triggers
+ - Code examples showing correct vs incorrect attribute placement
+ - Result: Real-time updates work reliably for all service selections
+
+### 3. PRICING_DISPLAY_IMPLEMENTATION.md
+
+**Status**: Completely rewritten to reflect completed implementation
+
+**Major Updates**:
+- **Implementation Status**: Changed from "In Progress" to "✅ Implementation completed successfully"
+- **Service Label Formatting**: Updated to reflect actual implementation with smart zero-price handling
+ - Zero-priced services display without price suffix
+ - Priced services show with formatted German pricing
+ - Consistent quantity display (1x, 2x) for all services
+
+- **Unified Summary Component**: Documented the single cohesive sticky sidebar approach
+ - Eliminated separate pricing summary template
+ - Integrated pricing into main booking summary
+ - Improved UX with unified layout
+
+- **Service Layer Integration**: Updated with actual implemented methods
+ - `calculateServiceTotal()` method with bug fix documentation
+ - `getServiceGroups()` method with corrected key usage (groupTotal vs totalPrice)
+
+- **Controller Integration**: Documented completed pricing integration
+ - Step 1 and Step 2 controller enhancements
+ - HTMX service field integration fixes
+
+- **Technical Considerations**: Updated HTMX integration section
+ - Fixed service field triggers for expanded choice types
+ - Real-time pricing updates working reliably
+ - Improved reliability documentation
+
+### 4. FIELD_STATE_SYSTEM.md
+
+**Section Added**: Service Field HTMX Integration Improvements (Lines 181-216)
+
+**New Content**:
+- **Critical Fix Documentation**: Detailed explanation of expanded choice field HTMX handling
+- **Issue Resolution**: Clear before/after comparison of HTMX attribute placement
+- **Implementation Example**: Code snippets showing correct `attr` vs incorrect `row_attr` usage
+- **Benefits Documentation**: List of improvements from the fix
+- **Affected Field Types**: Comprehensive list of service fields that benefited from the fix
+
+### 5. AGE_CONSTRAINTS_MODEL_EXTENSION_PLAN.md
+
+**Section Added**: Related Completed Improvements (Lines 631-642)
+
+**New Content**:
+- **Complementary Enhancements**: Documented how the completed form processing improvements support future age constraints implementation
+- **Service Field HTMX Integration**: Reference to completed fixes
+- **Field Handler Data Storage**: How Service object storage supports age constraint data access
+- **Service Label Formatting**: Integration with age-restricted service pricing
+- **Foundation Documentation**: How these improvements prepare for age constraint filtering
+
+## Summary of Issues Resolved
+
+### 🐞 HTMX Service Selection Issues
+- **Problem**: HTMX requests not triggered for service fields
+- **Root Cause**: Incorrect HTMX attribute placement on containers
+- **Solution**: Individual input-level HTMX triggers for expanded choice fields
+- **Documentation**: Updated in FORM_PROCESSING.md, FIELD_STATE_SYSTEM.md, AGE_BASED_FIELDS_PLAN.md
+
+### 🧠 Field Handler Logic
+- **Problem**: Service IDs stored instead of Service objects
+- **Impact**: Pricing calculator couldn't access service price data
+- **Solution**: Updated all field handlers to store complete Service entities
+- **Documentation**: Updated in FORM_PROCESSING.md, AGE_BASED_FIELDS_PLAN.md
+
+### 💰 Pricing Calculation Bug
+- **Problem**: Selected services not included in total calculation
+- **Root Cause**: Incorrect array key usage in `calculateServiceTotal()`
+- **Solution**: Fixed to use 'groupTotal' instead of 'totalPrice'
+- **Documentation**: Detailed in PRICING_DISPLAY_IMPLEMENTATION.md
+
+### 💡 UX & UI Enhancements
+- **Improvement**: Unified booking summary with integrated pricing
+- **Changes**: Eliminated separate pricing sidebar, improved layout
+- **Service Labels**: Smart zero-price handling and consistent quantity display
+- **Documentation**: Comprehensive updates in PRICING_DISPLAY_IMPLEMENTATION.md
+
+### 🧼 Code Quality Fixes
+- **Issue**: Linter warnings on dynamic property access
+- **Solution**: Proper type annotations and parameter typing
+- **Documentation**: Noted in implementation completion status
+
+## Benefits Achieved
+
+### Technical Benefits
+- ✅ Reliable HTMX dynamic updates for all service fields
+- ✅ Consistent data flow from form submission to pricing calculation
+- ✅ Clean separation of concerns with Service object storage
+- ✅ Improved code quality with proper type annotations
+
+### UX Benefits
+- ✅ Real-time pricing updates for all service selections
+- ✅ Unified, sticky booking summary with clear pricing breakdown
+- ✅ Smart service label formatting (no €0,00 for free services)
+- ✅ Consistent quantity display across all services
+
+### Documentation Benefits
+- ✅ Accurate reflection of current system behavior
+- ✅ Clear troubleshooting information for similar issues
+- ✅ Complete implementation status tracking
+- ✅ Foundation documentation for future enhancements
+
+## Next Steps
+
+### 🔧 Future Enhancements (Optional)
+- Add contextual tooltips to service options
+- Implement Stimulus controllers for smooth field transitions
+- Add placeholder cards for age-restricted fields
+- Enhance pricing with discounts, tax breakdowns, or multi-currency support
+
+### 📚 Documentation Maintenance
+- Ensure future updates to service field logic are reflected in updated docs
+- Maintain accuracy of implementation status as system evolves
+- Update troubleshooting sections based on any new issues discovered
+
+---
+
+**Documentation Update Date**: September 2, 2025
+**Updated By**: Claude Code Assistant
+**Status**: ✅ All critical documentation updates completed
+**Impact**: Documentation now accurately reflects current system behavior and resolved issues
\ No newline at end of file
diff --git a/docs/FIELD_STATE_SYSTEM.md b/docs/FIELD_STATE_SYSTEM.md
index 81f0c77..5bc4871 100644
--- a/docs/FIELD_STATE_SYSTEM.md
+++ b/docs/FIELD_STATE_SYSTEM.md
@@ -180,6 +180,41 @@ The system supports real-time field state updates:
3. HTMX can update field states without full page refresh
4. Dependency tracking ensures only affected fields are updated
+### Service Field HTMX Integration Improvements
+
+**Critical Fix for Expanded Choice Fields**: Service fields (board, skipass, courses, etc.) use expanded choice types (checkboxes/radios) which required special HTMX trigger handling:
+
+**Issue Resolved**: HTMX attributes were originally placed on container elements (`row_attr`) which don't capture individual input changes for expanded choice fields.
+
+**Solution Implemented**: HTMX triggers must be placed directly on each individual checkbox/radio input via the `attr` configuration:
+
+```php
+// Correct implementation for service fields
+'attr' => [
+ 'hx-post' => $this->urlGenerator->generate('booking_create_step_2_refresh'),
+ 'hx-target' => '#booking-summary',
+ 'hx-trigger' => 'change',
+],
+
+// Incorrect approach (doesn't work for expanded choices)
+// 'row_attr' => ['hx-post' => '...']
+```
+
+**Benefits of the Fix**:
+- Real-time updates now work reliably for all service field selections
+- Service pricing calculations update immediately when selections change
+- Booking summary reflects service changes without manual form submission
+- Consistent HTMX behavior across all form field types
+
+**Affected Field Types**:
+- Board/meal selection fields
+- Ski pass selection fields
+- Course selection fields
+- Additional services selection fields
+- Rental equipment selection fields
+
+This improvement ensures that the conditional field state system works seamlessly with HTMX for all field types, providing users with immediate feedback on their selections.
+
## Extending the System
### Custom Conditions
diff --git a/docs/FORM_PROCESSING.md b/docs/FORM_PROCESSING.md
new file mode 100644
index 0000000..a337fd0
--- /dev/null
+++ b/docs/FORM_PROCESSING.md
@@ -0,0 +1,518 @@
+# Form Processing System Documentation
+
+## Overview
+
+The MyEP Next Booking application implements a sophisticated form processing system designed to handle complex multi-step booking workflows with dynamic participant forms and conditional field logic. The system integrates with the Bus Pro Net (BPN) XML API for travel management and provides real-time field updates through HTMX integration.
+
+## Architecture Components
+
+### 1. Multi-Step Booking Flow
+
+The booking process consists of three main steps:
+
+1. **Step 1 (`CreateStep1Controller`)**: Room selection with quantities and dates
+2. **Step 2 (`CreateStep2Controller`)**: Participant details with conditional fields
+3. **Step 3**: Final confirmation and submission to BPN API
+
+### 2. Data Transfer Objects (DTOs)
+
+#### BookingCreateDto (`src/Form/Model/BookingCreateDto.php`)
+- Main container for the entire booking process
+- Contains travel data, hotel ID, room selections, and participants
+- Implements `BookingDtoInterface` for polymorphic handling
+
+```php
+class BookingCreateDto implements BookingDtoInterface
+{
+ public int $currentStep = 1;
+ public array $roomSelections = []; // RoomSelectionDto[]
+ public array $participants = []; // ParticipantDto[]
+ public Travel $travel;
+ public int $hotelId;
+}
+```
+
+#### ParticipantDto (`src/Form/Model/ParticipantDto.php`)
+- Individual participant data container
+- Includes personal data, body dimensions, and service selections
+- Custom validation for body dimensions when rental services are selected
+
+```php
+class ParticipantDto
+{
+ // Personal data
+ public ?string $firstName = null;
+ public ?string $lastName = null;
+ public ?\DateTimeImmutable $dateOfBirth = null;
+ public ?string $email = null;
+
+ // Body dimensions (conditional)
+ public ?string $height = null;
+ public ?string $weight = null;
+ public ?string $shoeSize = null;
+
+ // Service selections
+ public ?int $assignedRoomId = null;
+ public array $courses = [];
+ public array $additionalServices = [];
+ public array $rentals = [];
+ // ... other service arrays
+}
+```
+
+### 3. Dynamic Field Options System
+
+#### ParticipantFieldOptionsProvider (`src/Form/Service/ParticipantFieldOptionsProvider.php`)
+
+Central registry for dynamic field configurations using a provider pattern with lazy evaluation.
+
+**Registered Field Providers:**
+
+- **`assignedRoomId`**: Context-aware room selection
+ - Shows only available rooms for the participant
+ - Excludes rooms already assigned to other participants
+ - Respects room capacity and booking constraints
+
+- **`courses`**: Available courses from travel data
+ - Multiple selection with checkboxes
+ - Populated from `travel.additionalServices` with `TOKEN_COURSES` subtype
+
+- **`additionalServices`**: Additional services with mandatory logic
+ - Mandatory services are pre-selected and readonly
+ - Choice attributes include visual indicators for mandatory items
+
+- **`board`**: Board/meal options
+ - Multiple selection from travel data
+ - Populated from `TOKEN_BOARD` subtype services
+
+- **`rentals`**: Rental equipment options
+ - Date-filtered rental services
+ - Triggers body dimension requirements when selected
+ - Populated from `TOKEN_RENTALS` subtype services
+
+**Provider Pattern Implementation:**
+```php
+protected function registerFieldOptionProviders(): void
+{
+ $this->fieldOptionProviders['fieldName'] = fn (BookingDtoInterface $bookingDto, int $participantIndex) => [
+ 'label' => 'Field Label',
+ 'choices' => $this->generateChoicesFor($bookingDto, $participantIndex),
+ // ... other Symfony form options
+ ];
+}
+```
+
+### 4. Conditional Field State System
+
+#### Field State Providers
+
+**CreateFieldStateProvider (`src/Form/Service/CreateFieldStateProvider.php`)**
+- Manages field states for the booking creation workflow
+- Currently implements body dimension requirements for rental services
+
+**Field State Types:**
+- `readonly`: Field is visible but not editable
+- `disabled`: Field interaction is disabled
+- `required`: Field becomes mandatory
+- `hidden`: Field is not displayed
+
+**Current Implementation:**
+```php
+protected function registerFieldStateConditions(): void
+{
+ $rentalCondition = new RentalSelectionCondition();
+
+ // Body dimensions become required when rentals are selected
+ $this->fieldStateConditions['height'] = ['required' => $rentalCondition];
+ $this->fieldStateConditions['weight'] = ['required' => $rentalCondition];
+ $this->fieldStateConditions['shoeSize'] = ['required' => $rentalCondition];
+}
+```
+
+#### Field Conditions
+
+**Available Condition Types:**
+
+1. **`RentalSelectionCondition`**: Evaluates rental service selections
+2. **`AgeRangeCondition`**: Age-based conditions
+3. **`FieldValueCondition`**: Field interdependency conditions (equals, in, empty, etc.)
+4. **`CompositeCondition`**: Complex logic with AND/OR/NOT operators
+5. **`ApplicantCondition`**: Applicant-specific conditions
+6. **`MutabilityCondition`**: Mutability-based conditions
+
+**Condition Interface:**
+```php
+interface FieldConditionInterface
+{
+ public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool;
+ public function getDependentFields(): array;
+ public function getDescription(): string;
+}
+```
+
+### 5. Participant Field Processing
+
+#### ParticipantFieldHandlerRegistry (`src/Form/Service/ParticipantFieldHandlerRegistry.php`)
+
+Manages field handlers in dependency order using topological sorting to ensure proper processing sequence.
+
+**Key Features:**
+- Dependency resolution using Kahn's algorithm
+- Circular dependency detection
+- Support for both simple and complex handler instantiation
+- Batch processing of all participants
+
+**Processing Flow:**
+```php
+public function processFieldsAndSync(array $submittedData, BookingDtoInterface $bookingDto): array
+{
+ // Process all field handlers to clean the DTO
+ $this->processFields($submittedData, $bookingDto);
+
+ // Synchronize submitted data with the cleaned DTO state
+ return $this->syncSubmittedDataWithDto($submittedData, $bookingDto);
+}
+
+public function processFields(array $submittedData, BookingDtoInterface $bookingDto): void
+{
+ foreach ($submittedData['participants'] as $participantIndex => $participantData) {
+ foreach ($this->getSortedHandlers() as $handlerName) {
+ $handler = $this->handlers[$handlerName];
+ if ($handler->shouldProcess($participantData, $participantIndex)) {
+ $handler->processField($participantData, $bookingDto, $participantIndex);
+ }
+ }
+ }
+}
+```
+
+**Data Synchronization:**
+The registry includes a critical synchronization feature to maintain consistency between DTO state and form submitted data:
+
+```php
+private function syncSubmittedDataWithDto(array $submittedData, BookingDtoInterface $bookingDto): array
+{
+ // Updates submitted data to match cleaned DTO state
+ // Converts DTO objects back to form-expected formats
+ // Ensures form rendering shows valid selections only
+}
+```
+
+This prevents validation errors when field handlers remove invalid selections from DTOs but the original submitted data still contains those invalid choices.
+
+#### Field Handlers
+
+**AbstractParticipantFieldHandler (`src/Form/Service/Abstract/AbstractParticipantFieldHandler.php`)**
+
+Base class providing common functionality:
+- Default dependency resolution
+- Safe participant data access
+- Field value extraction utilities
+- Value normalization methods
+
+**Concrete Implementations:**
+
+1. **`ParticipantDateOfBirthFieldHandler`**: Processes date of birth field
+ - Converts submitted date strings to `DateTimeImmutable` objects
+ - Normalizes various date formats
+ - No dependencies (foundation field for age-based logic)
+
+2. **`ParticipantAssignedRoomFieldHandler`**: Processes room assignments
+ - Converts form strings to integers
+ - Handles empty selections as null values
+ - No dependencies (base field)
+
+3. **Service-Based Handlers** (age-aware filtering):
+ - **`ParticipantAdditionalServicesFieldHandler`**: Additional services filtering
+ - **`ParticipantCoursesFieldHandler`**: Course selections filtering
+ - **`ParticipantBoardFieldHandler`**: Board/meal options filtering
+ - **`ParticipantRentalsFieldHandler`**: Rental equipment filtering
+
+ All service handlers share these characteristics:
+ - Depend on `dateOfBirth` field (processed first)
+ - Filter selections based on age constraints
+ - Instantiate `ServiceAgeEvaluator` directly when needed
+ - Remove invalid selections to prevent form validation errors
+ - **Store complete Service objects** in ParticipantDto (not just IDs) for pricing calculations
+
+**Key Architecture Decisions**:
+
+1. **Service Evaluator Instantiation**: Service handlers instantiate `ServiceAgeEvaluator` directly rather than using dependency injection because:
+ - `ServiceAgeEvaluator` has no dependencies itself
+ - Handlers are registered as simple class names in service configuration
+ - Avoids complex service wiring for lightweight utility classes
+ - Maintains clean separation between handlers and evaluator logic
+
+2. **Data Storage Strategy**: Service handlers store complete Service objects in ParticipantDto rather than just IDs because:
+ - Pricing calculations require access to service price data
+ - Eliminates need for additional database lookups during price calculation
+ - Provides immediate access to all service metadata (labels, descriptions, etc.)
+ - Maintains data consistency throughout the booking flow
+
+**Handler Interface:**
+```php
+interface ParticipantFieldHandlerInterface
+{
+ public function getFieldName(): string;
+ public function getDependencies(): array;
+ public function shouldProcess(array $submittedData, int $participantIndex): bool;
+ public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void;
+}
+```
+
+### 6. Form Type Integration
+
+#### BookingCreateStep2Type (`src/Form/BookingCreateStep2Type.php`)
+
+Main form type for participant data collection with event-driven processing.
+
+**Form Events:**
+- **`PRE_SET_DATA`**: Initial form setup with participants collection
+- **`PRE_SUBMIT`**: Dynamic field updates and DTO synchronization
+
+**Event Processing:**
+```php
+public function onPreSubmit(FormEvent $event): void
+{
+ $form = $event->getForm();
+ $submittedData = $event->getData();
+ $bookingDto = $form->getData();
+
+ // Process field handlers and synchronize submitted data with cleaned DTO state
+ $cleanedSubmittedData = $this->participantFieldHandlerRegistry->processFieldsAndSync($submittedData, $bookingDto);
+ $event->setData($cleanedSubmittedData);
+
+ // Rebuild the 'participants' field with the updated DTO
+ $this->addParticipantsField($form);
+}
+```
+
+#### BookingCreateParticipantType (`src/Form/BookingCreateParticipantType.php`)
+
+Individual participant form with dynamic field management.
+
+**Static Fields:**
+- Personal data (name, email, birth date, etc.)
+- Body dimensions (embedded `BodyDimensionsType`)
+- Contact information
+
+**Dynamic Fields:**
+- Room assignment (`assignedRoomId`)
+- Service selections (courses, additional services, board, rentals)
+
+**Dynamic State Application:**
+```php
+private function applyFieldStates(FormInterface $form, BookingDtoInterface $bookingDto, int $participantIndex, array $formData = []): void
+{
+ $allFieldStates = $this->fieldStateProvider->getAllFieldStates($bookingDto, $participantIndex, $formData);
+
+ foreach ($allFieldStates as $fieldName => $fieldState) {
+ // Apply state modifications to form fields
+ // Handle nested body dimension fields specially
+ }
+}
+```
+
+## Form Processing Pipeline
+
+### 1. Initial Form Rendering
+
+1. **Controller** creates `BookingCreateDto` with travel data
+2. **Step2Type** `PRE_SET_DATA` event fires:
+ - Adds participants collection field
+ - Each participant triggers `BookingCreateParticipantType` creation
+3. **ParticipantType** `PRE_SET_DATA` event fires:
+ - Adds dynamic fields using `FieldOptionsProvider`
+ - Applies initial field states using `FieldStateProvider`
+4. **Form rendered** with proper field options and states
+
+### 2. Form Submission Processing
+
+1. **Form submission** received by controller
+2. **Step2Type** `PRE_SUBMIT` event fires:
+ - `ParticipantFieldHandlerRegistry` processes all submitted data to update DTOs
+ - Registry synchronizes submitted data with cleaned DTO state
+ - Event data updated with cleaned submitted data
+ - Form rebuilt with updated DTO state
+3. **ParticipantType** `PRE_SUBMIT` event fires:
+ - Field states recalculated based on submitted data
+ - Form fields updated with new states
+4. **Validation** runs on updated DTO with cleaned data
+5. **Controller** handles successful submission or re-renders with errors
+
+### 3. HTMX Dynamic Updates
+
+For real-time field updates without full form submission:
+
+1. **HTMX request** sent with partial form data
+2. **Same pipeline** executes as form submission
+3. **Partial response** returned with updated field states
+4. **Frontend** updates only changed form sections
+
+**Service Field HTMX Integration:**
+
+Service fields (board, skipass, courses, etc.) use expanded choice types (checkboxes/radios) which require special HTMX trigger handling:
+
+- **Issue**: HTMX attributes on container elements don't capture individual input changes
+- **Solution**: HTMX triggers must be placed on each individual checkbox/radio input
+- **Implementation**: Field handlers ensure `hx-post`, `hx-target`, and `hx-trigger` attributes are applied to each choice input
+- **Result**: Real-time updates work reliably for all service selections
+
+**HTMX Attribute Placement:**
+```php
+// Correct: Individual input triggers
+'attr' => [
+ 'hx-post' => $this->urlGenerator->generate('booking_create_step_2_refresh'),
+ 'hx-target' => '#booking-summary',
+ 'hx-trigger' => 'change',
+],
+
+// Incorrect: Container-level triggers (doesn't work for expanded choices)
+// 'row_attr' => ['hx-post' => '...']
+```
+
+## Validation System
+
+### DTO-Level Validation
+
+**ParticipantDto Validation:**
+- Symfony validation constraints on properties
+- Custom callback validation for body dimensions when rentals selected
+
+```php
+#[Assert\Callback('validateBodyDimensionsForRentals', groups: ['booking_create_step_2'])]
+public function validateBodyDimensionsForRentals(ExecutionContextInterface $context): void
+{
+ if (!empty($this->rentals)) {
+ // Validate height, weight, shoeSize are provided
+ }
+}
+```
+
+### Form-Level Validation
+
+**Validation Groups:**
+- `booking_create_step_2`: Step 2 specific validations
+- `booking_edit`: Edit workflow validations
+
+## Extension Points
+
+### Adding New Dynamic Fields
+
+1. **Register field options** in `ParticipantFieldOptionsProvider`:
+```php
+$this->fieldOptionProviders['newField'] = fn($bookingDto, $participantIndex) => [
+ 'label' => 'New Field Label',
+ 'choices' => $this->generateChoicesFor($bookingDto, $participantIndex),
+];
+```
+
+2. **Add to dynamic fields list** in `BookingCreateParticipantType`:
+```php
+$dynamicFields = ['assignedRoomId', 'courses', 'newField']; // Add 'newField'
+```
+
+### Adding New Field Conditions
+
+1. **Implement condition class**:
+```php
+class NewCondition implements FieldConditionInterface
+{
+ public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
+ {
+ // Condition logic
+ }
+}
+```
+
+2. **Register in field state provider**:
+```php
+$this->fieldStateConditions['fieldName'] = [
+ 'required' => new NewCondition(),
+];
+```
+
+### Adding New Field Handlers
+
+1. **Implement handler class**:
+```php
+class NewFieldHandler extends AbstractParticipantFieldHandler
+{
+ public function getFieldName(): string { return 'newField'; }
+ public function getDependencies(): array { return ['dependentField']; }
+ public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void
+ {
+ // Processing logic
+ }
+}
+```
+
+2. **Register in service configuration** (services.yaml or through DI)
+
+## Integration with BPN API
+
+The form system is designed to prepare data for submission to the Bus Pro Net XML API:
+
+1. **Field handlers** transform form data into BPN-compatible format
+2. **Service selections** map to BPN service IDs
+3. **Room assignments** align with BPN room availability
+4. **Validation rules** ensure data meets BPN requirements
+
+## Performance Considerations
+
+### Optimization Strategies
+
+1. **Lazy evaluation** in field option providers
+2. **Caching** in field state providers
+3. **Dependency sorting** cached until handlers change
+4. **Minimal form rebuilding** only when necessary
+
+### Memory Management
+
+- DTOs use typed properties to minimize memory footprint
+- Field handlers process data in-place where possible
+- Form events only rebuild changed portions
+
+## Security Considerations
+
+### XSS Protection
+
+- Custom `XssCleanTransformer` applied to text inputs
+- `clean_xss: true` option on relevant form fields
+
+### Data Validation
+
+- Strict type declarations throughout
+- Yoda conditions for safety
+- Explicit validation constraints on all user inputs
+
+## Testing Strategy
+
+### Unit Testing Focus Areas
+
+1. **Field option providers** with various travel data scenarios
+2. **Field conditions** with different participant states
+3. **Field handlers** with edge cases and dependencies
+4. **Validation logic** for body dimensions and rental services
+
+### Integration Testing
+
+1. **Form submission workflows** end-to-end
+2. **HTMX dynamic updates** with state changes
+3. **Multi-participant scenarios** with interdependencies
+
+## Future Enhancements
+
+### Planned Features
+
+1. **Step 3 implementation** for booking confirmation
+2. **Additional field conditions** for complex business rules
+3. **Enhanced validation** for service compatibility
+4. **Performance optimizations** for large participant counts
+
+### Architectural Improvements
+
+1. **Event system** for field state change notifications
+2. **Caching layer** for expensive field option calculations
+3. **Async processing** for complex form submissions
+4. **Enhanced error handling** with user-friendly messages
\ No newline at end of file
diff --git a/docs/PRICING_DISPLAY_IMPLEMENTATION.md b/docs/PRICING_DISPLAY_IMPLEMENTATION.md
new file mode 100644
index 0000000..a2eff72
--- /dev/null
+++ b/docs/PRICING_DISPLAY_IMPLEMENTATION.md
@@ -0,0 +1,274 @@
+# Pricing Display Implementation Plan
+
+## Overview
+Implement comprehensive pricing display functionality that shows costs both inline in form options and in a detailed sidebar summary with real-time updates via HTMX.
+
+## Goals
+1. **Inline pricing** in form options (rooms, services) so users see costs while selecting
+2. **Sidebar pricing summary** with itemized breakdown and grand total
+3. **Real-time updates** via existing HTMX integration
+4. **Professional UX** with clear pricing integrated into the booking flow
+
+## Implementation Strategy
+
+### 1. Core Pricing Service (`src/Service/BookingPriceCalculatorService.php`)
+
+**Purpose**: Central service for all pricing calculations
+
+**Key Methods**:
+- `calculateRoomPricing(BookingCreateDto $bookingDto): array`
+- `calculateServicePricing(BookingCreateDto $bookingDto): array`
+- `calculateGrandTotal(BookingCreateDto $bookingDto): float`
+- `getPricingBreakdown(BookingCreateDto $bookingDto): array`
+
+**Room Pricing Logic**:
+```php
+// For each selected room: quantity × room.price
+foreach ($bookingDto->getSelectedRooms() as $roomSelection) {
+ $room = $bookingDto->travel->rooms[$roomSelection->roomId];
+ $totalPrice = $roomSelection->quantity * $room->price;
+}
+```
+
+**Service Pricing Logic**:
+```php
+// For each participant's selected services
+foreach ($bookingDto->getParticipants() as $participant) {
+ // Handle different service types (single vs multiple selection)
+ $serviceTotal += $participant->skiPass?->price ?? 0;
+ $serviceTotal += array_sum(array_map(fn($s) => $s->price, $participant->courses));
+ // etc.
+}
+```
+
+### 2. Enhanced Form Field Options with Pricing ✅ COMPLETED
+
+#### A. Room Selection Forms ✅ COMPLETED
+**File**: `src/Form/RoomSelectType.php`
+
+**Enhancement**: Choice labels include pricing information
+```php
+'choice_label' => function (Room $room) {
+ $priceText = $room->price ? sprintf(' (€%.2f pro Nacht)', $room->price) : '';
+ return $room->label . $priceText;
+}
+```
+
+#### B. Service Selection Forms ✅ COMPLETED
+**File**: `src/Form/Service/ParticipantFieldOptionsProvider.php`
+
+**Enhancement**: Service labels include pricing via `formatServiceLabelWithPrice()` method
+- Zero-priced services display without price suffix (e.g., "Vollpension" instead of "Vollpension (€0,00)")
+- Priced services show with formatted price (e.g., "Halbpension (€45,00)")
+- All services consistently show quantity prefix (e.g., "1x", "2x")
+
+**Service Label Formatting Logic**:
+```php
+private function formatServiceLabelWithPrice(Service $service): string
+{
+ $label = $service->label;
+
+ // Add price only if service has a cost
+ if ($service->price > 0) {
+ $label .= sprintf(' (€%.2f)', $service->price);
+ }
+
+ return $label;
+}
+```
+
+**Affected Service Types**:
+- Courses: `"Skikurs Anfänger (€25,00)"`
+- Additional Services: `"Versicherung (€15,00)"`
+- Ski Pass: `"5-Tage Skipass (€120,00)"`
+- Rentals: `"Ski-Set (€30,00)"`
+- Board: `"Halbpension (€45,00)"` or `"Vollpension"` (if €0,00)
+
+### 3. Enhanced Unified Booking Summary ✅ COMPLETED
+
+#### A. Unified Summary Component ✅ COMPLETED
+**File**: `templates/booking/_summary.html.twig`
+
+The pricing summary has been integrated into the main booking summary, creating a single cohesive sticky sidebar that displays:
+
+**Travel Information Section**:
+- Travel details (destination, dates, duration)
+- Room selections with quantities
+- Participant count summary
+
+**Pricing Summary Section** (integrated):
+- Room pricing breakdown with quantities (e.g., "2x Doppelzimmer")
+- Service selections by participant with pricing
+- Grand total calculation
+
+**Key UX Improvements**:
+- Single sticky summary box (eliminated separate pricing sidebar)
+- Clean, unified layout with consistent typography
+- Service labels show quantity and pricing appropriately
+- Zero-priced services display without price suffix
+- Real-time updates via HTMX for all pricing changes
+
+#### B. Deprecated Separate Pricing Template ✅ COMPLETED
+**File**: `templates/booking/_pricing_summary.html.twig` - REMOVED
+
+The separate pricing summary template was removed in favor of the integrated approach within the main summary template for better UX.
+
+### 4. Service Layer Integration ✅ COMPLETED
+
+#### A. Enhanced BookingService ✅ COMPLETED
+**File**: `src/Service/BookingService.php`
+
+**Implemented Pricing Methods**:
+```php
+public function calculateServiceTotal(BookingCreateDto $bookingDto): float
+{
+ // Groups services by type and calculates total pricing
+ $serviceGroups = $this->getServiceGroups($bookingDto);
+ return array_sum(array_column($serviceGroups, 'groupTotal'));
+}
+
+private function getServiceGroups(BookingCreateDto $bookingDto): array
+{
+ // Groups selected services and calculates totals for pricing display
+ // Fixed bug: Uses 'groupTotal' instead of incorrect 'totalPrice' key
+}
+```
+
+**Key Bug Fix**: The `calculateServiceTotal()` method was corrected to use `groupTotal` instead of `totalPrice` from the grouped array structure, ensuring selected services are properly included in the grand total calculation.
+
+### 5. Controller Integration ✅ COMPLETED
+
+#### A. Step 1 Controller Updates ✅ COMPLETED
+**File**: `src/Controller/Booking/CreateStep1Controller.php`
+
+**Enhancement**: Integrated pricing calculations into existing summary methods
+```php
+// In index() method - pricing data included in summary
+$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
+// Pricing calculated and passed to template
+
+// In roomSummary() method - HTMX endpoint includes pricing updates
+$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
+return $this->render('booking/_summary.html.twig', [
+ // existing data with integrated pricing
+]);
+```
+
+#### B. Step 2 Controller Updates ✅ COMPLETED
+**File**: `src/Controller/Booking/CreateStep2Controller.php`
+
+**Enhancement**: Service pricing calculations integrated into form refresh
+```php
+// In refresh() method - service selections update pricing in real-time
+$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
+// Service total calculation includes selected service pricing
+```
+
+#### C. HTMX Service Field Integration ✅ COMPLETED
+**Critical Fix**: Service field HTMX triggers were moved from container elements to individual form inputs (checkboxes/radios) to ensure real-time updates work properly for expanded choice fields.
+
+### 6. Error Handling & Edge Cases ✅ COMPLETED
+
+**Price Data Handling** ✅ IMPLEMENTED:
+- Zero prices handled gracefully (services show without price suffix)
+- All prices rounded to 2 decimal places
+- German number formatting implemented (comma as decimal separator)
+- Null price handling via conditional display logic
+
+**Service Quantity Logic** ✅ IMPLEMENTED:
+- Single services (skiPass): price × 1 per participant
+- Multiple services (courses, rentals): sum of all selected service prices per participant
+- Service quantity consistently displayed (1x, 2x) in labels
+- Field handlers store complete Service objects (not just IDs) for pricing access
+
+**Field Handler Data Fix** ✅ COMPLETED:
+- All service field handlers updated to store Service objects instead of service IDs
+- Enables pricing calculator to access service price data
+- Resolves issue where selected services were ignored in total calculations
+
+## Implementation Progress
+
+### ✅ Completed
+- [x] Created implementation plan documentation
+- [x] Enhanced BookingService with pricing calculation methods (`calculateServiceTotal`, `getServiceGroups`)
+- [x] Updated ParticipantFieldOptionsProvider to add prices to service labels with smart formatting
+- [x] Updated RoomSelectType to add prices to room labels
+- [x] Integrated pricing into main summary template (unified approach)
+- [x] Updated controllers to include pricing data in summary calculations
+- [x] Fixed HTMX integration for service field real-time updates
+- [x] Fixed field handler data storage (Service objects vs IDs)
+- [x] Fixed pricing calculation bug (groupTotal vs totalPrice key)
+- [x] Implemented smart service label formatting (zero-price handling)
+- [x] Added consistent quantity display for all services
+- [x] Completed comprehensive testing of pricing display and HTMX integration
+
+### ✅ Additional Improvements Completed
+- [x] Unified booking summary layout (eliminated separate pricing sidebar)
+- [x] Enhanced UX with sticky summary positioning
+- [x] Fixed service field HTMX triggers for expanded choice types
+- [x] Resolved linter warnings with proper type annotations
+- [x] Streamlined service label formatting logic
+
+## Technical Considerations
+
+### Performance
+- All pricing calculations done in-memory (no database queries)
+- Calculations triggered only on form changes via HTMX
+- Efficient array operations for service aggregation
+
+### HTMX Integration ✅ COMPLETED
+- Uses existing `#booking-summary` target for seamless updates
+- **Fixed service field triggers**: HTMX attributes moved to individual form inputs for expanded choice fields
+- Real-time pricing updates work reliably for all service selections
+- Maintains current real-time update behavior with improved reliability
+
+### Styling
+- CSS classes for pricing components:
+ - `.pricing-summary` - Overall container
+ - `.pricing-section` - Room/service sections
+ - `.pricing-item` - Individual line items
+ - `.pricing-total` - Grand total display
+
+### Accessibility
+- Proper semantic HTML structure
+- ARIA labels for pricing information
+- Screen reader friendly number formatting
+
+## Testing Strategy
+
+### Unit Tests
+- Test pricing calculations with various room/service combinations
+- Test edge cases (null prices, zero quantities)
+- Test German number formatting
+
+### Integration Tests
+- Test HTMX updates with pricing changes
+- Test form submission with pricing data
+- Test step navigation with pricing persistence
+
+### Manual Testing Scenarios
+1. Select rooms and verify inline pricing appears
+2. Change room quantities and verify total updates
+3. Add/remove services and verify pricing updates
+4. Navigate between steps and verify pricing persistence
+5. Test with services that have null/zero prices
+
+## Future Enhancements
+
+### Potential Additions
+- **Discounts/Promotions**: Add discount calculation logic
+- **Currency Selection**: Support multiple currencies
+- **Price History**: Track pricing changes during booking session
+- **Export Pricing**: Add pricing breakdown to booking confirmations
+- **Tax Calculations**: Add VAT/tax breakdown if needed
+
+### Performance Optimizations
+- Cache frequently calculated pricing data
+- Optimize service aggregation algorithms
+- Consider lazy loading for complex pricing scenarios
+
+---
+
+**Last Updated**: 2025-09-02
+**Status**: ✅ Implementation completed successfully
+**Completed Features**: Inline pricing, unified summary, HTMX integration, service field fixes, and comprehensive UX improvements
\ No newline at end of file