323 lines
11 KiB
Markdown
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 |