# Booking DTO Unification Plan ## Problem Statement The current implementation uses two separate DTOs (`BookingCreateDto` and `BookingEditDto`) with fundamentally different data structures: - **Create mode**: Services stored in participant DTOs (e.g., `$participant->insurance`, `$participant->courses`) - **Edit mode**: Services stored in the booking object (e.g., `$booking->insurances`, `$booking->additionalServices`) This divergence causes multiple issues: 1. Pricing and summary calculations fail in edit mode 2. Field handlers need complex mode-specific logic 3. Data processor needs separate handling for create vs edit 4. Code duplication and increased complexity 5. Bugs due to assumptions about data structure ## Solution: Unified BookingDto Create a single `BookingDto` class that stores all service selections in participant DTOs for BOTH create and edit modes. Different modes are handled through different instantiation methods. ## Implementation Plan ### Phase 1: Create Unified BookingDto Class **File**: `src/Form/Model/BookingDto.php` **Changes**: - Merge `BookingCreateDto` and `BookingEditDto` into single `BookingDto` class - Keep all participant-based service storage (insurance, courses, skiPass, rentals, transportation, etc.) - Add `mode` property (MODE_CREATE or MODE_EDIT) - Add `booking` property (null in create mode, Booking object in edit mode for metadata only) - Implement `BookingDtoInterface` interface **Constructor signatures**: ```php // Create mode public function __construct(Travel $travel, int $agencyId) // Edit mode (static factory) public static function fromBooking(Booking $booking, Travel $travel): static ``` **Key method**: ```php public function getMode(): string { return null !== $this->booking ? self::MODE_EDIT : self::MODE_CREATE; } ``` ### Phase 2: Update BookingDto::fromBooking() for Edit Mode **File**: `src/Form/Model/BookingDto.php` **Responsibilities**: 1. Extract services from booking object and assign to participant DTOs 2. Map insurances: `$participant->insurance = $booking->getInsuranceForParticipant($index)` 3. Map services: `$participant->courses = $booking->getAdditionalServicesForParticipantByGroup($index, 'COURSES')` 4. Map transportation: `$participant->transportationOutbound = $booking->getTransportationForParticipant($index, 'OUTBOUND')` 5. Map pickup locations 6. Map room assignments 7. Set body dimensions from applicant for participant 0 8. Set parking from form data (need to check if stored in booking) **Data extraction methods needed**: - `Booking::getInsuranceForParticipant(int $index): ?Insurance` - Existing methods for other services can be reused ### Phase 3: Update ParticipantDto **File**: `src/Form/Model/ParticipantDto.php` **Changes**: - Already has all necessary service properties - Ensure `fromPersonalData()` copies body dimensions correctly - No structural changes needed ### Phase 4: Update Form Types **Files to update**: - `src/Form/BookingType.php` → Rename to `BookingType` (generic) - `src/Form/BookingEditType.php` → Delete (use unified BookingType) - `src/Form/BookingParticipantType.php` → Already works with ParticipantDto, should work unchanged **Changes**: - Update `BookingType` to use `BookingDto::class` as data_class - Remove mode-specific form type distinction - Pass `edit_mode` option to child forms for field state provider selection ### Phase 5: Update Controllers #### CreateStep2Controller **File**: `src/Controller/Booking/CreateStep2Controller.php` **Changes**: - Change `BookingCreateDto` → `BookingDto` - Constructor instantiation remains same - All logic should work unchanged (services already in participant DTOs) #### EditController **File**: `src/Controller/Booking/EditController.php` **Changes**: - Change `BookingEditDto` → `BookingDto` - Change `BookingEditDto::fromBooking()` → `BookingDto::fromBooking()` - Form type: change `BookingEditType` → `BookingType` with `['edit_mode' => true]` option - All pricing and summary calculations should now work (same DTO structure as create) ### Phase 6: Update BookingDataProcessor **File**: `src/BusProNet/DataProcessor/BookingDataProcessor.php` **Major simplification**: #### createBookingRequestPayload() (Create flow) - Already works with participant DTOs - Change signature: `BookingDto` instead of `BookingCreateDto` - No other changes needed #### createUpdateRequestPayload() (Edit flow) **Current problems**: - Tries to read services from `$bookingDto->booking` object - Complex mapping and resetting logic - `processParticipantServices()` needs to add services to booking data from travel data **New approach**: - Services already in participant DTOs (populated by `fromBooking()`) - Can reuse create flow logic almost entirely - Only difference: include `idbuchung` and participant `idadresseperson` in payload **Unified approach**: ```php public function createPayload(BookingDto $bookingDto, string $type): array { // Apply bulk insurance if enabled $this->applyBulkInsuranceIfActive($bookingDto); // Collect service mappings (works same for both modes) $serviceMap = $this->collectServiceMappings($bookingDto); $transportationMap = $this->collectTransportationMappings($bookingDto); $roomMap = $this->collectRoomMappings($bookingDto); $pickupMap = $this->collectPickupMappings($bookingDto); $insuranceMap = $this->collectInsuranceMappings($bookingDto); // Build payload based on mode if (BookingDtoInterface::MODE_EDIT === $bookingDto->getMode()) { return $this->buildUpdatePayload($bookingDto, ...maps); } else { return $this->buildCreatePayload($bookingDto, $type, ...maps); } } ``` **Simplifications**: - Remove `processParticipantServices()` complexity - Remove `resetServiceMappings()` - Remove `removeUnusedServices()` - Direct mapping from participant DTOs to payload ### Phase 7: Update Field Handlers **Files**: All handlers in `src/Form/Service/` **Changes needed**: - Handlers already work with participant DTOs - No changes needed (they don't care about mode) - Registry already handles both modes ### Phase 8: Update Service Layer #### BookingService **File**: `src/Service/BookingService.php` **Changes**: - Update type hints: `BookingCreateDto|BookingEditDto` → `BookingDto` - `getRoomSummaryAndParticipantCount()` should now work for both modes (same DTO structure) - No logic changes needed #### PriceCalculator **File**: `src/Service/PriceCalculator.php` **Changes**: - Update type hints to use `BookingDto` - All calculations work with participant DTOs, should work unchanged - Mode detection: `$bookingDto->getMode()` instead of `instanceof` checks ### Phase 9: Update Field State Providers **Files**: - `src/Form/Service/CreateFieldStateProvider.php` - `src/Form/Service/EditFieldStateProvider.php` **Changes**: - Already use `BookingDtoInterface`, no changes needed - Continue to be selected based on `edit_mode` form option ### Phase 10: Update Templates **Files**: - `templates/booking/create_step_2.html.twig` - `templates/booking/edit.html.twig` **Changes**: - Variable naming: `bookingCreateDto` → `bookingDto`, `bookingEditDto` → `bookingDto` - All logic should work unchanged (both render participant forms) ### Phase 11: Critical New Method in Booking Model **File**: `src/BusProNet/Model/Booking.php` **Add method**: ```php public function getInsuranceForParticipant(int $index): ?Insurance { foreach ($this->insurances as $insurance) { if (in_array($index, $insurance->mapping)) { return $insurance; } } return null; } ``` Similar methods may be needed for other services if not already present. ## Migration Strategy ### Step 1: Create new BookingDto (keep old DTOs) - Create `src/Form/Model/BookingDto.php` - Implement both constructor and `fromBooking()` - Keep `BookingCreateDto` and `BookingEditDto` temporarily ### Step 2: Update edit flow to use new DTO - Update `EditController` to use `BookingDto` - Update `BookingDataProcessor::createUpdateRequestPayload()` to accept `BookingDto` - Test edit flow thoroughly ### Step 3: Update create flow to use new DTO - Update `CreateStep2Controller` to use `BookingDto` - Test create flow thoroughly ### Step 4: Cleanup - Delete `BookingCreateDto.php` - Delete `BookingEditDto.php` - Delete `BookingEditType.php` - Update all remaining type hints ## Testing Checklist ### Edit Flow - [ ] Load existing booking with all service types - [ ] Form displays all current selections correctly - [ ] Summary sidebar shows all services and pricing - [ ] Body dimensions show for applicant - [ ] Change insurance (individual and bulk) - [ ] Change services (courses, skiPass, rentals, board) - [ ] Change transportation - [ ] Submit changes successfully - [ ] API receives correct payload with insurances - [ ] After redirect, summary shows correctly ### Create Flow - [ ] Select rooms - [ ] Add participants - [ ] Select services for participants - [ ] Select insurances (individual and bulk) - [ ] Pricing calculates correctly - [ ] Summary shows all selections - [ ] Submit creates booking successfully ### Field State System - [ ] Conditional fields show/hide correctly in both modes - [ ] Readonly fields work in edit mode - [ ] Age-dependent fields work in both modes - [ ] Bulk insurance checkbox works ### Data Integrity - [ ] No service data loss during mode transitions - [ ] Insurance IDs match correctly - [ ] Participant indices correct in both modes - [ ] Room assignments preserved ## Benefits of Unified DTO 1. **Single source of truth**: All service selections in one place (participant DTOs) 2. **Simplified calculations**: Pricing, summary, and totals work identically for both modes 3. **Reduced complexity**: No mode-specific logic in services and calculators 4. **Easier testing**: One DTO structure to test 5. **Better maintainability**: Changes to service structure only need updating in one place 6. **Consistent field handlers**: Handlers work with same data structure regardless of mode 7. **Cleaner templates**: Same rendering logic for both modes ## Risks and Mitigation ### Risk: Breaking existing create flow **Mitigation**: Migrate edit flow first, test thoroughly, then migrate create flow ### Risk: Data loss during form processing **Mitigation**: Extensive logging during migration, compare payloads before/after ### Risk: Field handler incompatibility **Mitigation**: Field handlers already work with ParticipantDto, minimal changes needed ### Risk: Performance impact from data extraction **Mitigation**: `fromBooking()` runs once per request, acceptable overhead ## Timeline Estimate - **Phase 1-3** (DTO creation): 2 hours - **Phase 4-5** (Forms & Controllers): 2 hours - **Phase 6** (DataProcessor refactor): 3 hours - **Phase 7-10** (Service layer & templates): 2 hours - **Phase 11** (Booking model methods): 1 hour - **Testing & Fixes**: 3 hours **Total**: ~13 hours ## Notes - Current code has accumulated technical debt from multiple iterations - Clean refactor will improve long-term maintainability - Most existing logic can be reused (field handlers, conditions, validators) - Main work is in `fromBooking()` extraction logic and DataProcessor simplification