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

11 KiB

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:

// Create mode
public function __construct(Travel $travel, int $agencyId)

// Edit mode (static factory)
public static function fromBooking(Booking $booking, Travel $travel): static

Key method:

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 BookingCreateDtoBookingDto
  • Constructor instantiation remains same
  • All logic should work unchanged (services already in participant DTOs)

EditController

File: src/Controller/Booking/EditController.php

Changes:

  • Change BookingEditDtoBookingDto
  • Change BookingEditDto::fromBooking()BookingDto::fromBooking()
  • Form type: change BookingEditTypeBookingType 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:

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|BookingEditDtoBookingDto
  • 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: bookingCreateDtobookingDto, bookingEditDtobookingDto
  • 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:

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