From 619e2ba1ee1690d4f30ae0968d52730a4df045a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Sat, 18 Oct 2025 17:59:42 +0200 Subject: [PATCH] feat: extended pricing breakdown on confirmation page --- docs/PROJECT_OVERVIEW.md | 27 ++- .../Booking/Create/Step4Controller.php | 3 + templates/booking/create/step_4.html.twig | 176 +++++++++++++++--- 3 files changed, 176 insertions(+), 30 deletions(-) diff --git a/docs/PROJECT_OVERVIEW.md b/docs/PROJECT_OVERVIEW.md index c6e67df..0ab8510 100644 --- a/docs/PROJECT_OVERVIEW.md +++ b/docs/PROJECT_OVERVIEW.md @@ -8,7 +8,11 @@ 1. **Step 1**: Room selection and dates (`Create\Step1Controller`) 2. **Step 2**: Participant details with card-based UI (`Create\Step2Controller`) 3. **Step 3**: Payment method selection (`Create\Step3Controller`) -4. **Step 4**: Final confirmation and submission to BPN API (`Create\Step4Controller`) +4. **Step 4**: Final confirmation with comprehensive pricing breakdown and submission to BPN API (`Create\Step4Controller`) + - Displays complete pricing breakdown by category (rooms, services grouped by type) + - Shows per-participant total pricing in card headers + - Displays individual service prices inline with each booked service + - Three-level pricing transparency: aggregate, participant, and service-level 5. **Edit Flow**: Similar card-based UI for existing bookings (`Edit\IndexController`) ### Card-Based UI Pattern (Production) @@ -46,7 +50,12 @@ ### Service Layer (`src/Service/`) - `BookingService` - Core booking workflow -- `BookingPriceCalculatorService` - Real-time pricing +- `BookingPriceCalculatorService` - Comprehensive pricing calculations + - `getPricingBreakdown()` - Complete pricing breakdown with rooms and services grouped by type + - `calculateAllParticipantIndividualPrices()` - Per-participant total pricing + - `calculateIndividualParticipantPrice()` - Single participant total (room + all services) + - `calculateServicePricing()` - Service aggregation with grouping by subtype + - Powers sidebar summary and Step 4 confirmation pricing display - `BookingFingerprintService` - Dirty state detection for edit mode - `TravelDataService` - API integration and caching - `ParticipantCardDataService` - Card display data @@ -285,7 +294,12 @@ Edit mode session requires proper cleanup to prevent dirty state persistence: - `Step1Controller.php` - Room selection and dates - `Step2Controller.php` - Participant details with card-based UI - `Step3Controller.php` - Payment method selection -- `Step4Controller.php` - Final confirmation and API submission +- `Step4Controller.php` - Final confirmation with comprehensive pricing display and API submission + - Integrates `BookingPriceCalculatorService` for complete pricing breakdown + - Calculates per-participant individual prices via `calculateAllParticipantIndividualPrices()` + - Template displays three-level pricing: aggregate breakdown, per-participant totals, and inline service prices + - All service prices shown in blue (text-blue-700) with German formatting + - Zero prices hidden per project standards - `SuccessController.php` - Success page after booking completion **Edit Namespace** (`src/Controller/Booking/Edit/`): @@ -312,7 +326,12 @@ Edit mode session requires proper cleanup to prevent dirty state persistence: - `templates/booking/create/step_1.html.twig` - Room selection - `templates/booking/create/step_2.html.twig` - Participant cards - `templates/booking/create/step_3.html.twig` - Payment method -- `templates/booking/create/step_4.html.twig` - Confirmation +- `templates/booking/create/step_4.html.twig` - Final confirmation with comprehensive pricing display + - **Aggregate pricing breakdown**: Rooms and services grouped by type with totals + - **Per-participant cards**: Individual total price in header, all personal details and service selections + - **Inline service pricing**: Each service shows price in blue (€X,XX format) next to label + - **Payment summary**: Selected payment method and bank details (if debit) + - **Confirmation checkbox**: Final acceptance before API submission - `templates/booking/create/success.html.twig` - Success page - `templates/booking/create/error.html.twig` - Error page diff --git a/src/Controller/Booking/Create/Step4Controller.php b/src/Controller/Booking/Create/Step4Controller.php index 04bbecb..678ac50 100644 --- a/src/Controller/Booking/Create/Step4Controller.php +++ b/src/Controller/Booking/Create/Step4Controller.php @@ -11,6 +11,7 @@ use App\Controller\Booking\Traits\BookingExceptionHandlerTrait; use App\Form\BookingCreateStep4Type; use App\Form\Model\BookingDto; use App\Htmx\HxTrait; +use App\Service\BookingPriceCalculatorService; use App\Service\BookingService; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -30,6 +31,7 @@ class Step4Controller extends AbstractController public function __construct( private readonly BookingService $bookingService, + private readonly BookingPriceCalculatorService $priceCalculator, private readonly ApiClient $apiClient, private readonly LoggerInterface $logger, ) { @@ -118,6 +120,7 @@ class Step4Controller extends AbstractController 'bookingCreateDto' => $bookingCreateDto, 'form' => $form->createView(), ...$this->getSummaryVariables($bookingCreateDto), + 'participantPrices' => $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingCreateDto), ]); } } diff --git a/templates/booking/create/step_4.html.twig b/templates/booking/create/step_4.html.twig index 4bdaa4e..4069e8f 100644 --- a/templates/booking/create/step_4.html.twig +++ b/templates/booking/create/step_4.html.twig @@ -25,18 +25,74 @@ - {# Rooms Summary #} -
-

Zimmer / Unterkünfte

+ {# Detailed Pricing Breakdown #} +
+

Preisübersicht

+ + {# Rooms Section #} {% if pricingData.rooms is not empty %} -
    - {% for roomPricing in pricingData.rooms %} -
  • - {{ roomPricing.quantity }}x {{ roomPricing.label }} - {{ roomPricing.totalPrice|number_format(2, ',', '.') }} € -
  • +
    +

    Unterkunft

    +
    + {% for roomPricing in pricingData.rooms %} +
    +
    + {{ roomPricing.quantity }}x {{ roomPricing.label }} + {% if assignmentCounts is defined and assignmentCounts[roomPricing.roomId] is defined %} + {{ assignmentCounts[roomPricing.roomId] }} Person(en) belegt + {% endif %} +
    +
    +
    + €{{ roomPricing.totalPrice|number_format(2, ',', '.') }} +
    +
    + €{{ roomPricing.unitPrice|number_format(2, ',', '.') }} pro Person +
    +
    +
    + {% endfor %} +
    +
    + {% endif %} + + {# Services Section #} + {% if pricingData.services is not empty %} +
    +

    Leistungen

    + {% for serviceGroup in pricingData.services %} +
    +
    {{ serviceGroup.groupName }}
    +
    + {% for servicePricing in serviceGroup.services %} +
    + + {{ servicePricing.participantCount }}x {{ servicePricing.label }} + {% if servicePricing.unitPrice is not null %} + (€{{ servicePricing.unitPrice|number_format(2, ',', '.') }} pro Person) + {% endif %} + + + €{{ servicePricing.totalPrice|number_format(2, ',', '.') }} + +
    + {% endfor %} +
    +
    {% endfor %} -
+
+ {% endif %} + + {# Grand Total #} + {% if pricingData.grandTotal is defined and pricingData.grandTotal > 0 %} +
+
+ Gesamtpreis: + + €{{ pricingData.grandTotal|number_format(2, ',', '.') }} + +
+
{% endif %}
@@ -45,10 +101,18 @@

Teilnehmer ({{ participantsCount }})

{% for participant in bookingCreateDto.participants %}
-

- {{ participant.firstName }} {{ participant.lastName }} - {% if loop.first %}(Anmelder){% endif %} -

+
+

+ {{ participant.firstName }} {{ participant.lastName }} + {% if loop.first %}(Anmelder){% endif %} +

+ {% if participantPrices is defined and participantPrices[loop.index0] is defined %} +
+
Preis
+
€{{ participantPrices[loop.index0]|number_format(2, ',', '.') }}
+
+ {% endif %} +
{# Personal Data #} @@ -102,7 +166,12 @@ {% if assignedRoom %}
Zimmer
-
{{ assignedRoom.label }}
+
+ {{ assignedRoom.label }} + {% if assignedRoom.price is not null and assignedRoom.price != 0 %} + (€{{ assignedRoom.price|number_format(2, ',', '.') }}) + {% endif %} +
{% endif %} @@ -118,13 +187,23 @@ {% if participant.transportationOutbound %}
Anreise
-
{{ participant.transportationOutbound.label }}
+
+ {{ participant.transportationOutbound.label }} + {% if participant.transportationOutbound.price is not null and participant.transportationOutbound.price != 0 %} + (€{{ participant.transportationOutbound.price|number_format(2, ',', '.') }}) + {% endif %} +
{% endif %} {% if participant.transportationInbound %}
Abreise
-
{{ participant.transportationInbound.label }}
+
+ {{ participant.transportationInbound.label }} + {% if participant.transportationInbound.price is not null and participant.transportationInbound.price != 0 %} + (€{{ participant.transportationInbound.price|number_format(2, ',', '.') }}) + {% endif %} +
{% endif %} @@ -132,7 +211,12 @@ {% if participant.pickup %}
Zu- und Ausstieg
-
{{ participant.pickup.label }}
+
+ {{ participant.pickup.label }} + {% if participant.pickup.price is not null and participant.pickup.price != 0 %} + (€{{ participant.pickup.price|number_format(2, ',', '.') }}) + {% endif %} +
{% endif %} @@ -140,7 +224,12 @@ {% if participant.parking %}
Parkplatz
-
Ja
+
+ Ja + {% if participant.parkingService is not null and participant.parkingService.price is not null and participant.parkingService.price != 0 %} + (€{{ participant.parkingService.price|number_format(2, ',', '.') }}) + {% endif %} +
{% endif %} {% if participant.licensePlate %} @@ -157,7 +246,12 @@
    {% for boardItem in participant.board %} -
  • {{ boardItem.label }}
  • +
  • + {{ boardItem.label }} + {% if boardItem.price is not null and boardItem.price != 0 %} + (€{{ boardItem.price|number_format(2, ',', '.') }}) + {% endif %} +
  • {% endfor %}
@@ -168,7 +262,12 @@ {% if participant.skiPass %}
Skipass
-
{{ participant.skiPass.label }}
+
+ {{ participant.skiPass.label }} + {% if participant.skiPass.price is not null and participant.skiPass.price != 0 %} + (€{{ participant.skiPass.price|number_format(2, ',', '.') }}) + {% endif %} +
{% endif %} @@ -179,7 +278,12 @@
    {% for rental in participant.rentals %} -
  • {{ rental.label }}
  • +
  • + {{ rental.label }} + {% if rental.price is not null and rental.price != 0 %} + (€{{ rental.price|number_format(2, ',', '.') }}) + {% endif %} +
  • {% endfor %}
@@ -190,7 +294,12 @@ {% if participant.rentalInsuranceSelected and participant.rentalInsurance %}
Leihmaterial-Versicherung
-
{{ participant.rentalInsurance.label }}
+
+ {{ participant.rentalInsurance.label }} + {% if participant.rentalInsurance.price is not null and participant.rentalInsurance.price != 0 %} + (€{{ participant.rentalInsurance.price|number_format(2, ',', '.') }}) + {% endif %} +
{% endif %} @@ -215,7 +324,12 @@
    {% for course in participant.courses %} -
  • {{ course.label }}
  • +
  • + {{ course.label }} + {% if course.price is not null and course.price != 0 %} + (€{{ course.price|number_format(2, ',', '.') }}) + {% endif %} +
  • {% endfor %}
@@ -229,7 +343,12 @@
    {% for service in participant.additionalServices %} -
  • {{ service.label }}
  • +
  • + {{ service.label }} + {% if service.price is not null and service.price != 0 %} + (€{{ service.price|number_format(2, ',', '.') }}) + {% endif %} +
  • {% endfor %}
@@ -240,7 +359,12 @@ {% if participant.insurance %}
Reiseversicherung
-
{{ participant.insurance.label }}
+
+ {{ participant.insurance.label }} + {% if participant.insurance.price is not null and participant.insurance.price != 0 %} + (€{{ participant.insurance.price|number_format(2, ',', '.') }}) + {% endif %} +
{% endif %}