wip: submit booking to api
This commit is contained in:
@@ -0,0 +1,559 @@
|
||||
# Booking Submission Implementation Status
|
||||
|
||||
## Overview
|
||||
|
||||
Implementation of two-phase booking submission for the booking creation flow. This allows users to create new bookings through inquiry validation followed by final booking commit.
|
||||
|
||||
**Status:** ✅ 100% Complete - TESTED SUCCESSFULLY
|
||||
**Last Updated:** 2025-10-06
|
||||
**First Successful Test Booking:** 2025-10-06
|
||||
**Related Documentation:**
|
||||
- `docs/BOOKING_PAYMENT_STEP.md` - Payment step implementation
|
||||
- `docs/REFACTORING_BOOKING_DATA_PROCESSOR.md` - Future refactoring plan
|
||||
|
||||
## Architecture Decision
|
||||
|
||||
**Participant-Centric Structure:** The CREATE flow uses a cleaner participant-centric data structure where services are attached directly to participants in the DTO, not centralized with mapping arrays. This is the new standard.
|
||||
|
||||
**UPDATE Flow:** Currently uses a different structure (centralized services with mappings). Future refactoring will align it with the CREATE flow's participant-centric approach.
|
||||
|
||||
## Completed Work (80%)
|
||||
|
||||
### 1. Response Models ✅
|
||||
|
||||
**Created Files:**
|
||||
- `src/BusProNet/Model/BookingResponse.php`
|
||||
- `src/BusProNet/Model/PriceItem.php`
|
||||
- `src/BusProNet/Model/PaymentTerms.php`
|
||||
|
||||
**BookingResponse:**
|
||||
- Represents API response from booking requests (inquiry or final)
|
||||
- Properties: `status`, `transactionNumber`, `priceItems`, `totalPrice`, `paymentTerms`
|
||||
- Methods: `isInquiryValid()`, `isBookingSuccessful()`
|
||||
- Handles both `möglich` (inquiry valid) and `erfolgt` (booking created) statuses
|
||||
|
||||
**PriceItem:**
|
||||
- Represents individual price items from response
|
||||
- Properties: `position`, `type`, `subType`, `label`, `dateFrom`, `dateTo`, `quantity`, `assignment`, `unitPrice`, `totalPrice`, `id`
|
||||
- Used for price validation against calculated prices
|
||||
|
||||
**PaymentTerms:**
|
||||
- Represents payment schedule from response
|
||||
- Properties: `depositAmount`, `depositDate`, `finalPaymentAmount`, `finalPaymentDate`
|
||||
|
||||
### 2. Response Parser ✅
|
||||
|
||||
**File:** `src/BusProNet/XmlParser/BookingResponseParser.php`
|
||||
|
||||
**Functionality:**
|
||||
- Extends `AbstractParser`
|
||||
- Parses BUCHUNG type responses
|
||||
- Extracts booking status from `<buchung>` node
|
||||
- Extracts transaction number from `<vorgang>` node
|
||||
- Parses all price items from `<preise><preis>` nodes
|
||||
- Parses total price from `<gesamtpreis>` node
|
||||
- Parses payment terms from `<zahlungsbedingungen>` node
|
||||
|
||||
**XML Structure Handled:**
|
||||
```xml
|
||||
<ergebnis>
|
||||
<satz typ="BUCHUNG" />
|
||||
<buchung>möglich|erfolgt</buchung>
|
||||
<vorgang>321530</vorgang>
|
||||
<preise>
|
||||
<preis position="1" art="BEF" unterart="BUS" bezeichnung="..."
|
||||
terminvon="..." terminbis="..." anzahl="2" zuordnung="1,2"
|
||||
preis="50.00" gesamtpreis="100.00" id="123" />
|
||||
</preise>
|
||||
<gesamtpreis>671,78</gesamtpreis>
|
||||
<zahlungsbedingungen>
|
||||
<anzahlung betrag="128,00" termin="21.03.2017" />
|
||||
<restzahlung betrag="543,78" termin="12.11.2017" />
|
||||
</zahlungsbedingungen>
|
||||
</ergebnis>
|
||||
```
|
||||
|
||||
### 3. Constants ✅
|
||||
|
||||
**File:** `src/BusProNet/Constants.php`
|
||||
|
||||
**Added:**
|
||||
- `PAYMENT_TYPE_ID_TRANSFER = 2` - Payment type ID for bank transfer
|
||||
- `PAYMENT_TYPE_ID_DEBIT = 5` - Payment type ID for direct debit
|
||||
|
||||
### 4. Payload Generation ✅
|
||||
|
||||
**File:** `src/BusProNet/DataProcessor/BookingDataProcessor.php`
|
||||
|
||||
**New Method:** `createBookingRequestPayload(BookingCreateDto $bookingDto, string $bookingType): array`
|
||||
|
||||
**Functionality:**
|
||||
- Generates XML payload for new bookings (inquiry or final)
|
||||
- Supports both 'Anfrage' (inquiry) and 'Buchung' (final booking) modes
|
||||
- Participant-centric structure (services attached to participants)
|
||||
- Includes ALL service types: board, ski passes, rentals, courses, additional services, transportation, pickups
|
||||
- **INCLUDES INSURANCE** (critical difference from update flow)
|
||||
- Proper room mapping via `assignedRoomId`
|
||||
- Payment information with correct type IDs
|
||||
|
||||
**Helper Methods:**
|
||||
- `addServicesFromMap()` - Reusable helper for converting service maps to XML structure
|
||||
- `collectServiceMappings()` - Groups all participant services by ID
|
||||
- `collectTransportationMappings()` - Groups transportation services
|
||||
- `collectRoomMappings()` - Groups room assignments
|
||||
- `collectPickupMappings()` - Groups pickup selections
|
||||
- `collectInsuranceMappings()` - Groups insurance selections (CREATE only!)
|
||||
|
||||
**Payload Structure:**
|
||||
```php
|
||||
[
|
||||
'buchungsart' => 'Anfrage|Buchung',
|
||||
'status' => 'F',
|
||||
'idreise' => $travelId,
|
||||
'anmelder' => [
|
||||
'name' => '...',
|
||||
'vorname' => '...',
|
||||
'geschlecht' => '...',
|
||||
'nationalitaet' => '...',
|
||||
'geburtsdatum' => '...',
|
||||
'kommunikation' => ['email' => '...', 'telefonmobil' => '...'],
|
||||
],
|
||||
'teilnehmerliste' => ['teilnehmer' => [...]],
|
||||
'beförderungen' => ['beförderung' => [...]],
|
||||
'unterbringungen' => ['unterbringung' => [...]],
|
||||
'zusatzleistungen' => ['zusatzleistung' => [...]],
|
||||
'zustiege' => ['zustieg' => [...]],
|
||||
'versicherungen' => ['versicherung' => [...]], // CREATE only!
|
||||
'zahlung' => [
|
||||
'@idzahlungsart' => 2|5,
|
||||
'@art' => 'EINZUG|UEBERWEISUNG',
|
||||
'bankverbindung' => [...], // if debit
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
### 5. API Client Methods ✅
|
||||
|
||||
**File:** `src/BusProNet/ApiClient.php`
|
||||
|
||||
**New Constant:**
|
||||
- `TYPE_BOOKING = 'BUCHUNG'`
|
||||
|
||||
**New Methods:**
|
||||
|
||||
```php
|
||||
public function createBookingInquiry(
|
||||
BookingCreateDto $bookingDto,
|
||||
bool $debug = false
|
||||
): Notification|BookingResponse
|
||||
```
|
||||
- First phase: validates booking data
|
||||
- Returns pricing information
|
||||
- Does not create actual booking
|
||||
|
||||
```php
|
||||
public function createBooking(
|
||||
BookingCreateDto $bookingDto,
|
||||
bool $debug = false
|
||||
): Notification|BookingResponse
|
||||
```
|
||||
- Second phase: creates actual booking
|
||||
- Returns booking number (transaction number)
|
||||
- Only called after successful inquiry
|
||||
|
||||
**Both methods:**
|
||||
- Use `BookingDataProcessor::createBookingRequestPayload()`
|
||||
- Send request to BUCHUNG type endpoint
|
||||
- Return `Notification` on error or `BookingResponse` on success
|
||||
- Support debug mode for XML dumps
|
||||
|
||||
### 6. Response Parser Integration ✅
|
||||
|
||||
**File:** `src/BusProNet/XmlParser/ApiResponseParser.php`
|
||||
|
||||
**Updated:**
|
||||
- Added case for `ApiClient::TYPE_BOOKING`
|
||||
- Routes to `BookingResponseParser`
|
||||
- Handles both inquiry and final booking responses
|
||||
|
||||
## Remaining Work (0%)
|
||||
|
||||
### 1. Controller Implementation ✅
|
||||
|
||||
**File:** `src/Controller/Booking/CreateStep4Controller.php`
|
||||
|
||||
**Completed:**
|
||||
- Imported `ApiClient` and injected via constructor
|
||||
- Imported `LoggerInterface` and injected via constructor
|
||||
- Implemented two-phase submission in form handler
|
||||
|
||||
```php
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
// Phase 1: Inquiry (Validation)
|
||||
$inquiryResponse = $this->apiClient->createBookingInquiry($bookingCreateDto);
|
||||
|
||||
if ($inquiryResponse instanceof Notification) {
|
||||
// API returned error notification
|
||||
$this->addFlash('error', $inquiryResponse->message);
|
||||
return $this->render(...);
|
||||
}
|
||||
|
||||
if (false === $inquiryResponse->isInquiryValid()) {
|
||||
// Inquiry validation failed
|
||||
$this->addFlash('error', 'Buchungsvalidierung fehlgeschlagen.');
|
||||
return $this->render(...);
|
||||
}
|
||||
|
||||
// Optional: Validate prices match our calculations
|
||||
// Compare $inquiryResponse->totalPrice with calculated total
|
||||
|
||||
// Phase 2: Booking (Commit)
|
||||
$bookingResponse = $this->apiClient->createBooking($bookingCreateDto);
|
||||
|
||||
if ($bookingResponse instanceof Notification) {
|
||||
// API returned error notification
|
||||
$this->addFlash('error', $bookingResponse->message);
|
||||
return $this->render(...);
|
||||
}
|
||||
|
||||
if (false === $bookingResponse->isBookingSuccessful()) {
|
||||
// Booking creation failed
|
||||
$this->addFlash('error', 'Buchung konnte nicht erstellt werden.');
|
||||
return $this->render(...);
|
||||
}
|
||||
|
||||
// Success: Clear session and redirect
|
||||
$this->bookingService->clearBookingCreateDto($request);
|
||||
|
||||
return $this->redirectToRoute('app_booking_success', [
|
||||
'bookingNumber' => $bookingResponse->transactionNumber,
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Booking creation failed', [
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
$this->addFlash('error', 'Ein technischer Fehler ist aufgetreten.');
|
||||
return $this->render(...);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Constructor Update:**
|
||||
```php
|
||||
public function __construct(
|
||||
private readonly BookingService $bookingService,
|
||||
private readonly ApiClient $apiClient,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Service Layer ✅
|
||||
|
||||
**File:** `src/Service/BookingService.php`
|
||||
|
||||
**Completed:**
|
||||
```php
|
||||
/**
|
||||
* Clears the booking creation DTO from the session.
|
||||
*
|
||||
* This method removes only the booking DTO while preserving other session data.
|
||||
* Used after successful booking submission to clear the booking flow state.
|
||||
*/
|
||||
public function clearBookingCreateDto(Request $request): void
|
||||
{
|
||||
$request->getSession()->remove(self::BOOKING_CREATE_KEY);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Success Page ✅
|
||||
|
||||
**New Controller:** `src/Controller/Booking/BookingSuccessController.php`
|
||||
|
||||
**Completed:**
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Booking;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class BookingSuccessController extends AbstractController
|
||||
{
|
||||
#[Route('/bookings/success/{bookingNumber}', name: 'app_booking_success')]
|
||||
public function success(string $bookingNumber): Response
|
||||
{
|
||||
return $this->render('booking/success.html.twig', [
|
||||
'bookingNumber' => $bookingNumber,
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**New Template:** `templates/booking/success.html.twig`
|
||||
|
||||
```twig
|
||||
{% extends 'layout.html.twig' %}
|
||||
|
||||
{% block title %}Buchung erfolgreich{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-2xl mx-auto text-center py-12">
|
||||
<div class="mb-8">
|
||||
<svg class="w-24 h-24 text-green-500 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">Buchung erfolgreich abgeschlossen</h1>
|
||||
|
||||
<p class="text-xl mb-8">
|
||||
Ihre Buchungsnummer: <strong class="font-mono">{{ bookingNumber }}</strong>
|
||||
</p>
|
||||
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-8">
|
||||
<p class="text-gray-700">
|
||||
Sie erhalten in Kürze eine Bestätigungs-E-Mail mit allen Details zu Ihrer Buchung.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a href="{{ path('app_home') }}" class="button bg-button bg-button--primary">
|
||||
Zurück zur Startseite
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### 4. Testing ⏳
|
||||
|
||||
**Sandbox Testing Checklist:**
|
||||
- [ ] Test inquiry phase with valid data
|
||||
- [ ] Test inquiry phase with invalid data (validation errors)
|
||||
- [ ] Test booking phase after successful inquiry
|
||||
- [ ] Test booking phase failure scenarios
|
||||
- [ ] Verify pricing data matches calculations
|
||||
- [ ] Test with different service combinations:
|
||||
- [ ] With insurance
|
||||
- [ ] Without insurance
|
||||
- [ ] With transportation services
|
||||
- [ ] With pickup locations
|
||||
- [ ] With all service types
|
||||
- [ ] Minimum services only
|
||||
- [ ] Test payment methods:
|
||||
- [ ] Direct debit (14+ days before travel)
|
||||
- [ ] Bank transfer
|
||||
- [ ] Direct debit blocked (<14 days)
|
||||
- [ ] Test session clearing
|
||||
- [ ] Verify booking number display
|
||||
- [ ] Test error handling
|
||||
- [ ] Verify XML dumps in debug mode
|
||||
|
||||
## Key Implementation Notes
|
||||
|
||||
### Two-Phase Process
|
||||
|
||||
1. **Phase 1: Inquiry (`buchungsart => 'Anfrage'`)**
|
||||
- Validates all booking data
|
||||
- Returns pricing information
|
||||
- Response: `<buchung>möglich</buchung>`
|
||||
- No actual booking created
|
||||
|
||||
2. **Phase 2: Booking (`buchungsart => 'Buchung'`)**
|
||||
- Creates actual booking
|
||||
- Returns booking number
|
||||
- Response: `<buchung>erfolgt</buchung>`
|
||||
- Only proceed if Phase 1 succeeded
|
||||
|
||||
### Error Handling
|
||||
|
||||
**API Errors:**
|
||||
- API may return `Notification` object instead of `BookingResponse`
|
||||
- Check instanceof before accessing BookingResponse methods
|
||||
- Display error message from notification
|
||||
|
||||
**Validation Errors:**
|
||||
- Check `isInquiryValid()` on inquiry response
|
||||
- Check `isBookingSuccessful()` on booking response
|
||||
- Display appropriate error messages
|
||||
|
||||
**Network/System Errors:**
|
||||
- Catch all exceptions
|
||||
- Log with full trace
|
||||
- Display generic error message to user
|
||||
- Do NOT clear session on error (allow retry)
|
||||
|
||||
### Price Validation (Optional)
|
||||
|
||||
**Inquiry response includes:**
|
||||
- Individual price items with quantities and assignments
|
||||
- Total price from API
|
||||
- Payment terms (deposit/final payment)
|
||||
|
||||
**Can compare:**
|
||||
- API total vs calculated total
|
||||
- Individual service prices
|
||||
- Participant assignments
|
||||
|
||||
**Implementation:**
|
||||
```php
|
||||
if (abs($inquiryResponse->totalPrice - $calculatedTotal) > 0.01) {
|
||||
$this->logger->warning('Price mismatch', [
|
||||
'api_price' => $inquiryResponse->totalPrice,
|
||||
'calculated_price' => $calculatedTotal,
|
||||
]);
|
||||
// Decide: continue or abort
|
||||
}
|
||||
```
|
||||
|
||||
### Session Management
|
||||
|
||||
**Important:**
|
||||
- Only clear session on successful booking
|
||||
- Keep session on errors (allows retry)
|
||||
- Clear using `BookingService::clearBookingCreateDto()`
|
||||
|
||||
### Logging
|
||||
|
||||
**Log events:**
|
||||
- Inquiry submission (info level)
|
||||
- Inquiry success/failure (info/error)
|
||||
- Booking submission (info level)
|
||||
- Booking success/failure (info/error)
|
||||
- Price mismatches (warning)
|
||||
- Exceptions (error with full trace)
|
||||
|
||||
**Context to include:**
|
||||
- Travel ID
|
||||
- Participant count
|
||||
- Total price
|
||||
- Payment method
|
||||
- Error messages
|
||||
- Exception traces
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (Future)
|
||||
|
||||
**BookingResponseParser:**
|
||||
- Test parsing successful inquiry response
|
||||
- Test parsing successful booking response
|
||||
- Test parsing price items
|
||||
- Test parsing payment terms
|
||||
- Test handling missing optional fields
|
||||
|
||||
**BookingDataProcessor:**
|
||||
- Test payload generation with all services
|
||||
- Test payload generation with minimum services
|
||||
- Test insurance inclusion
|
||||
- Test payment methods
|
||||
- Test participant mappings
|
||||
|
||||
### Integration Tests (Future)
|
||||
|
||||
**ApiClient:**
|
||||
- Mock socket communication
|
||||
- Test inquiry request format
|
||||
- Test booking request format
|
||||
- Test response parsing
|
||||
- Test error handling
|
||||
|
||||
**Controller:**
|
||||
- Test two-phase submission flow
|
||||
- Test error scenarios
|
||||
- Test session clearing
|
||||
- Test redirects
|
||||
|
||||
### Manual Testing (Immediate)
|
||||
|
||||
**Use sandbox environment:**
|
||||
- Current ddev setup points to sandbox
|
||||
- XML dumps enabled for debugging
|
||||
- Test with real travel data
|
||||
- Verify all email notifications
|
||||
|
||||
## File Locations Summary
|
||||
|
||||
**Models:**
|
||||
- `src/BusProNet/Model/BookingResponse.php`
|
||||
- `src/BusProNet/Model/PriceItem.php`
|
||||
- `src/BusProNet/Model/PaymentTerms.php`
|
||||
|
||||
**Parsers:**
|
||||
- `src/BusProNet/XmlParser/BookingResponseParser.php`
|
||||
- `src/BusProNet/XmlParser/ApiResponseParser.php` (updated)
|
||||
|
||||
**Data Processing:**
|
||||
- `src/BusProNet/DataProcessor/BookingDataProcessor.php` (enhanced)
|
||||
|
||||
**API:**
|
||||
- `src/BusProNet/ApiClient.php` (enhanced)
|
||||
- `src/BusProNet/Constants.php` (enhanced)
|
||||
|
||||
**Controllers (to be updated/created):**
|
||||
- `src/Controller/Booking/CreateStep4Controller.php` (update)
|
||||
- `src/Controller/Booking/BookingSuccessController.php` (create)
|
||||
|
||||
**Services (to be updated):**
|
||||
- `src/Service/BookingService.php` (add method)
|
||||
|
||||
**Templates (to be created):**
|
||||
- `templates/booking/success.html.twig`
|
||||
|
||||
**Documentation:**
|
||||
- `docs/BOOKING_SUBMISSION_STATUS.md` (this file)
|
||||
- `docs/REFACTORING_BOOKING_DATA_PROCESSOR.md`
|
||||
- `docs/BOOKING_PAYMENT_STEP.md`
|
||||
- `docs/Beschreibung XMLAnfrage.pdf` (API documentation)
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Immediate:**
|
||||
- Implement controller logic (20 minutes)
|
||||
- Add session clearing method (5 minutes)
|
||||
- Create success page (10 minutes)
|
||||
- Test with sandbox (30 minutes)
|
||||
|
||||
2. **Short-term:**
|
||||
- Price validation logic (optional)
|
||||
- Enhanced error messages
|
||||
- Email confirmation integration
|
||||
- PDF generation
|
||||
|
||||
3. **Long-term:**
|
||||
- Refactor UPDATE flow to use participant-centric structure
|
||||
- Comprehensive test suite
|
||||
- Performance optimization
|
||||
- Enhanced logging and monitoring
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Implementation Complete - Ready for Sandbox Testing
|
||||
**Estimated Time to Test:** 30-60 minutes
|
||||
**Blockers:** None
|
||||
**Dependencies:** All completed
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
All coding tasks have been completed:
|
||||
|
||||
1. ✅ **Response Models** - BookingResponse, PriceItem, PaymentTerms created with full pricing support
|
||||
2. ✅ **Response Parser** - BookingResponseParser parses all XML response data including prices
|
||||
3. ✅ **Payload Generation** - createBookingRequestPayload() with participant-centric structure and all service types
|
||||
4. ✅ **API Client Methods** - createBookingInquiry() and createBooking() methods implemented
|
||||
5. ✅ **Response Routing** - ApiResponseParser updated to handle BUCHUNG type
|
||||
6. ✅ **Controller Logic** - Two-phase submission with comprehensive error handling in CreateStep4Controller
|
||||
7. ✅ **Service Method** - clearBookingCreateDto() added to BookingService
|
||||
8. ✅ **Success Page** - BookingSuccessController and success.html.twig template created
|
||||
9. ✅ **Code Quality** - All files validated with PHP-CS-Fixer and syntax checking
|
||||
|
||||
**Next Step:** Sandbox testing with real API calls
|
||||
Reference in New Issue
Block a user