Files
myep/docs/booking-dto-unification-plan.md
T

323 lines
11 KiB
Markdown

# 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