282 lines
11 KiB
Markdown
282 lines
11 KiB
Markdown
# Pricing Display Implementation Plan
|
||
|
||
## Overview
|
||
Implement comprehensive pricing display functionality that shows costs both inline in form options and in a detailed sidebar summary with real-time updates via HTMX.
|
||
|
||
## Goals
|
||
1. **Inline pricing** in form options (rooms, services) so users see costs while selecting
|
||
2. **Sidebar pricing summary** with itemized breakdown and grand total
|
||
3. **Real-time updates** via existing HTMX integration
|
||
4. **Professional UX** with clear pricing integrated into the booking flow
|
||
|
||
## Implementation Strategy
|
||
|
||
### 1. Core Pricing Service (`src/Service/BookingPriceCalculatorService.php`)
|
||
|
||
**Purpose**: Central service for all pricing calculations
|
||
|
||
**Key Methods**:
|
||
- `calculateRoomPricing(BookingCreateDto $bookingDto): array`
|
||
- `calculateServicePricing(BookingCreateDto $bookingDto): array`
|
||
- `calculateGrandTotal(BookingCreateDto $bookingDto): float`
|
||
- `getPricingBreakdown(BookingCreateDto $bookingDto): array`
|
||
|
||
**Room Pricing Logic**:
|
||
```php
|
||
// For each selected room: quantity × room.price
|
||
foreach ($bookingDto->getSelectedRooms() as $roomSelection) {
|
||
$room = $bookingDto->travel->rooms[$roomSelection->roomId];
|
||
$totalPrice = $roomSelection->quantity * $room->price;
|
||
}
|
||
```
|
||
|
||
**Service Pricing Logic**:
|
||
```php
|
||
// For each participant's selected services
|
||
foreach ($bookingDto->getParticipants() as $participant) {
|
||
// Handle different service types (single vs multiple selection)
|
||
$serviceTotal += $participant->skiPass?->price ?? 0;
|
||
$serviceTotal += array_sum(array_map(fn($s) => $s->price, $participant->courses));
|
||
// etc.
|
||
}
|
||
```
|
||
|
||
### 2. Enhanced Form Field Options with Pricing ✅ COMPLETED
|
||
|
||
#### A. Room Selection Forms ✅ COMPLETED
|
||
**File**: `src/Form/RoomSelectType.php`
|
||
|
||
**Enhancement**: Choice labels include pricing information
|
||
```php
|
||
'choice_label' => function (Room $room) {
|
||
$priceText = $room->price ? sprintf(' (€%.2f pro Nacht)', $room->price) : '';
|
||
return $room->label . $priceText;
|
||
}
|
||
```
|
||
|
||
#### B. Service Selection Forms ✅ COMPLETED
|
||
**File**: `src/Form/Service/ParticipantFieldOptionsProvider.php`
|
||
|
||
**Enhancement**: Service labels include pricing via `formatServiceLabelWithPrice()` method
|
||
- Zero-priced services display without price suffix (e.g., "Vollpension" instead of "Vollpension (€0,00)")
|
||
- Priced services show with formatted price (e.g., "Halbpension (€45,00)")
|
||
- Negative prices display as discounts (e.g., "Frühbucher-Bonus (-15,00€ Rabatt)")
|
||
- All services consistently show quantity prefix (e.g., "1x", "2x")
|
||
|
||
**Service Label Formatting Logic**:
|
||
```php
|
||
private function formatServiceLabelWithPrice(Service $service): string
|
||
{
|
||
$label = $service->label;
|
||
|
||
// Handle zero prices (no display)
|
||
if (null === $service->price || 0.0 === $service->price) {
|
||
return $label;
|
||
}
|
||
|
||
// Handle negative prices (discounts)
|
||
if ($service->price < 0) {
|
||
return sprintf('%s (-%s€ Rabatt)', $label, number_format(abs($service->price), 2, ',', '.'));
|
||
}
|
||
|
||
// Handle positive prices (costs)
|
||
return sprintf('%s (€%s)', $label, number_format($service->price, 2, ',', '.'));
|
||
}
|
||
```
|
||
|
||
**Affected Service Types**:
|
||
- Courses: `"Skikurs Anfänger (€25,00)"` or `"Frühbucher-Bonus (-15,00€ Rabatt)"`
|
||
- Additional Services: `"Versicherung (€15,00)"` or `"Stammgast-Vorteil (-5,00€ Rabatt)"`
|
||
- Ski Pass: `"5-Tage Skipass (€120,00)"` or `"Gruppen-Rabatt (-20,00€ Rabatt)"`
|
||
- Rentals: `"Ski-Set (€30,00)"` or `"Eigenes Equipment (-30,00€ Rabatt)"`
|
||
- Board: `"Halbpension (€45,00)"`, `"Vollpension"` (if €0,00), or `"Selbstverpflegung (-25,00€ Rabatt)"`
|
||
- Pickup Services: `"München Hbf (€15,00)"` or `"Nahverkehr (-5,00€ Rabatt)"`
|
||
|
||
### 3. Enhanced Unified Booking Summary ✅ COMPLETED
|
||
|
||
#### A. Unified Summary Component ✅ COMPLETED
|
||
**File**: `templates/booking/_summary.html.twig`
|
||
|
||
The pricing summary has been integrated into the main booking summary, creating a single cohesive sticky sidebar that displays:
|
||
|
||
**Travel Information Section**:
|
||
- Travel details (destination, dates, duration)
|
||
- Room selections with quantities
|
||
- Participant count summary
|
||
|
||
**Pricing Summary Section** (integrated):
|
||
- Room pricing breakdown with quantities (e.g., "2x Doppelzimmer")
|
||
- Service selections by participant with pricing
|
||
- Grand total calculation
|
||
|
||
**Key UX Improvements**:
|
||
- Single sticky summary box (eliminated separate pricing sidebar)
|
||
- Clean, unified layout with consistent typography
|
||
- Service labels show quantity and pricing appropriately
|
||
- Zero-priced services display without price suffix
|
||
- Real-time updates via HTMX for all pricing changes
|
||
|
||
#### B. Deprecated Separate Pricing Template ✅ COMPLETED
|
||
**File**: `templates/booking/_pricing_summary.html.twig` - REMOVED
|
||
|
||
The separate pricing summary template was removed in favor of the integrated approach within the main summary template for better UX.
|
||
|
||
### 4. Service Layer Integration ✅ COMPLETED
|
||
|
||
#### A. Enhanced BookingService ✅ COMPLETED
|
||
**File**: `src/Service/BookingService.php`
|
||
|
||
**Implemented Pricing Methods**:
|
||
```php
|
||
public function calculateServiceTotal(BookingCreateDto $bookingDto): float
|
||
{
|
||
// Groups services by type and calculates total pricing
|
||
$serviceGroups = $this->getServiceGroups($bookingDto);
|
||
return array_sum(array_column($serviceGroups, 'groupTotal'));
|
||
}
|
||
|
||
private function getServiceGroups(BookingCreateDto $bookingDto): array
|
||
{
|
||
// Groups selected services and calculates totals for pricing display
|
||
// Fixed bug: Uses 'groupTotal' instead of incorrect 'totalPrice' key
|
||
}
|
||
```
|
||
|
||
**Key Bug Fix**: The `calculateServiceTotal()` method was corrected to use `groupTotal` instead of `totalPrice` from the grouped array structure, ensuring selected services are properly included in the grand total calculation.
|
||
|
||
### 5. Controller Integration ✅ COMPLETED
|
||
|
||
#### A. Step 1 Controller Updates ✅ COMPLETED
|
||
**File**: `src/Controller/Booking/CreateStep1Controller.php`
|
||
|
||
**Enhancement**: Integrated pricing calculations into existing summary methods
|
||
```php
|
||
// In index() method - pricing data included in summary
|
||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||
// Pricing calculated and passed to template
|
||
|
||
// In roomSummary() method - HTMX endpoint includes pricing updates
|
||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||
return $this->render('booking/_summary.html.twig', [
|
||
// existing data with integrated pricing
|
||
]);
|
||
```
|
||
|
||
#### B. Step 2 Controller Updates ✅ COMPLETED
|
||
**File**: `src/Controller/Booking/CreateStep2Controller.php`
|
||
|
||
**Enhancement**: Service pricing calculations integrated into form refresh
|
||
```php
|
||
// In refresh() method - service selections update pricing in real-time
|
||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||
// Service total calculation includes selected service pricing
|
||
```
|
||
|
||
#### C. HTMX Service Field Integration ✅ COMPLETED
|
||
**Critical Fix**: Service field HTMX triggers were moved from container elements to individual form inputs (checkboxes/radios) to ensure real-time updates work properly for expanded choice fields.
|
||
|
||
### 6. Error Handling & Edge Cases ✅ COMPLETED
|
||
|
||
**Price Data Handling** ✅ IMPLEMENTED:
|
||
- Zero prices handled gracefully (services show without price suffix)
|
||
- All prices rounded to 2 decimal places
|
||
- German number formatting implemented (comma as decimal separator)
|
||
- Null price handling via conditional display logic
|
||
|
||
**Service Quantity Logic** ✅ IMPLEMENTED:
|
||
- Single services (skiPass): price × 1 per participant
|
||
- Multiple services (courses, rentals): sum of all selected service prices per participant
|
||
- Service quantity consistently displayed (1x, 2x) in labels
|
||
- Field handlers store complete Service objects (not just IDs) for pricing access
|
||
|
||
**Field Handler Data Fix** ✅ COMPLETED:
|
||
- All service field handlers updated to store Service objects instead of service IDs
|
||
- Enables pricing calculator to access service price data
|
||
- Resolves issue where selected services were ignored in total calculations
|
||
|
||
## Implementation Progress
|
||
|
||
### ✅ Completed
|
||
- [x] Created implementation plan documentation
|
||
- [x] Enhanced BookingService with pricing calculation methods (`calculateServiceTotal`, `getServiceGroups`)
|
||
- [x] Updated ParticipantFieldOptionsProvider to add prices to service labels with smart formatting
|
||
- [x] Updated RoomSelectType to add prices to room labels
|
||
- [x] Integrated pricing into main summary template (unified approach)
|
||
- [x] Updated controllers to include pricing data in summary calculations
|
||
- [x] Fixed HTMX integration for service field real-time updates
|
||
- [x] Fixed field handler data storage (Service objects vs IDs)
|
||
- [x] Fixed pricing calculation bug (groupTotal vs totalPrice key)
|
||
- [x] Implemented smart service label formatting (zero-price handling)
|
||
- [x] Added consistent quantity display for all services
|
||
- [x] Completed comprehensive testing of pricing display and HTMX integration
|
||
|
||
### ✅ Additional Improvements Completed
|
||
- [x] Unified booking summary layout (eliminated separate pricing sidebar)
|
||
- [x] Enhanced UX with sticky summary positioning
|
||
- [x] Fixed service field HTMX triggers for expanded choice types
|
||
- [x] Resolved linter warnings with proper type annotations
|
||
- [x] Streamlined service label formatting logic
|
||
|
||
## Technical Considerations
|
||
|
||
### Performance
|
||
- All pricing calculations done in-memory (no database queries)
|
||
- Calculations triggered only on form changes via HTMX
|
||
- Efficient array operations for service aggregation
|
||
|
||
### HTMX Integration ✅ COMPLETED
|
||
- Uses existing `#booking-summary` target for seamless updates
|
||
- **Fixed service field triggers**: HTMX attributes moved to individual form inputs for expanded choice fields
|
||
- Real-time pricing updates work reliably for all service selections
|
||
- Maintains current real-time update behavior with improved reliability
|
||
|
||
### Styling
|
||
- CSS classes for pricing components:
|
||
- `.pricing-summary` - Overall container
|
||
- `.pricing-section` - Room/service sections
|
||
- `.pricing-item` - Individual line items
|
||
- `.pricing-total` - Grand total display
|
||
|
||
### Accessibility
|
||
- Proper semantic HTML structure
|
||
- ARIA labels for pricing information
|
||
- Screen reader friendly number formatting
|
||
|
||
## Testing Strategy
|
||
|
||
### Unit Tests
|
||
- Test pricing calculations with various room/service combinations
|
||
- Test edge cases (null prices, zero quantities)
|
||
- Test German number formatting
|
||
|
||
### Integration Tests
|
||
- Test HTMX updates with pricing changes
|
||
- Test form submission with pricing data
|
||
- Test step navigation with pricing persistence
|
||
|
||
### Manual Testing Scenarios
|
||
1. Select rooms and verify inline pricing appears
|
||
2. Change room quantities and verify total updates
|
||
3. Add/remove services and verify pricing updates
|
||
4. Navigate between steps and verify pricing persistence
|
||
5. Test with services that have null/zero prices
|
||
|
||
## Future Enhancements
|
||
|
||
### Potential Additions
|
||
- **Discounts/Promotions**: Add discount calculation logic
|
||
- **Currency Selection**: Support multiple currencies
|
||
- **Price History**: Track pricing changes during booking session
|
||
- **Export Pricing**: Add pricing breakdown to booking confirmations
|
||
- **Tax Calculations**: Add VAT/tax breakdown if needed
|
||
|
||
### Performance Optimizations
|
||
- Cache frequently calculated pricing data
|
||
- Optimize service aggregation algorithms
|
||
- Consider lazy loading for complex pricing scenarios
|
||
|
||
---
|
||
|
||
**Last Updated**: 2025-09-02
|
||
**Status**: ✅ Implementation completed successfully
|
||
**Completed Features**: Inline pricing, unified summary, HTMX integration, service field fixes, and comprehensive UX improvements |