wip: show pricing
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\BusProNet\Constants;
|
||||
use App\BusProNet\Model\Room;
|
||||
use App\BusProNet\Model\Service;
|
||||
use App\Form\Model\BookingCreateDto;
|
||||
use App\Form\Model\BookingDtoInterface;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
|
||||
/**
|
||||
* Calculates pricing for booking components including rooms and services.
|
||||
*
|
||||
* This service provides comprehensive pricing calculations for the booking system,
|
||||
* handling room pricing based on quantities and service pricing per participant.
|
||||
* It returns structured pricing data for display in forms and summaries.
|
||||
*/
|
||||
class BookingPriceCalculatorService
|
||||
{
|
||||
/**
|
||||
* Calculates comprehensive pricing breakdown for a booking.
|
||||
*
|
||||
* @param BookingDtoInterface $bookingDto The booking data to calculate pricing for
|
||||
*
|
||||
* @return array{rooms: array, services: array, grandTotal: float} Complete pricing breakdown
|
||||
*/
|
||||
public function getPricingBreakdown(BookingDtoInterface $bookingDto): array
|
||||
{
|
||||
$roomPricing = $this->calculateRoomPricing($bookingDto);
|
||||
$servicePricing = $this->calculateServicePricing($bookingDto);
|
||||
$grandTotal = $this->calculateGrandTotal($bookingDto);
|
||||
|
||||
return [
|
||||
'rooms' => $roomPricing,
|
||||
'services' => $servicePricing,
|
||||
'grandTotal' => $grandTotal,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates pricing for all selected rooms.
|
||||
*
|
||||
* @param BookingDtoInterface $bookingDto The booking data containing room selections
|
||||
*
|
||||
* @return array Array of room pricing data with labels, quantities, and totals
|
||||
*/
|
||||
public function calculateRoomPricing(BookingDtoInterface $bookingDto): array
|
||||
{
|
||||
$roomPricing = [];
|
||||
|
||||
if (false === $bookingDto instanceof BookingCreateDto) {
|
||||
return $roomPricing;
|
||||
}
|
||||
|
||||
$selectedRooms = $bookingDto->getSelectedRooms();
|
||||
if (true === empty($selectedRooms)) {
|
||||
return $roomPricing;
|
||||
}
|
||||
|
||||
foreach ($selectedRooms as $roomSelection) {
|
||||
$room = $this->getRoomById($bookingDto, $roomSelection->roomId);
|
||||
if (null === $room || null === $room->price) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalPrice = $roomSelection->quantity * $room->price;
|
||||
|
||||
$roomPricing[] = [
|
||||
'roomId' => $room->id,
|
||||
'label' => $room->label,
|
||||
'quantity' => $roomSelection->quantity,
|
||||
'unitPrice' => $room->price,
|
||||
'totalPrice' => $totalPrice,
|
||||
];
|
||||
}
|
||||
|
||||
return $roomPricing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates pricing for all selected services across all participants, grouped by subtype.
|
||||
*
|
||||
* @param BookingDtoInterface $bookingDto The booking data containing participants and their service selections
|
||||
*
|
||||
* @return array Array of service groups with each group containing services of the same subtype
|
||||
*/
|
||||
public function calculateServicePricing(BookingDtoInterface $bookingDto): array
|
||||
{
|
||||
$participants = $bookingDto->getParticipants();
|
||||
|
||||
if (true === empty($participants)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Aggregate service selections across all participants
|
||||
$serviceAggregation = [];
|
||||
|
||||
foreach ($participants as $participant) {
|
||||
$this->aggregateParticipantServices($participant, $serviceAggregation);
|
||||
}
|
||||
|
||||
// Group services by subtype and convert to pricing format
|
||||
return $this->groupServicesBySubtype($serviceAggregation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the grand total for the entire booking.
|
||||
*
|
||||
* @param BookingDtoInterface $bookingDto The booking data to calculate total for
|
||||
*
|
||||
* @return float The grand total price
|
||||
*/
|
||||
public function calculateGrandTotal(BookingDtoInterface $bookingDto): float
|
||||
{
|
||||
$roomTotal = $this->calculateRoomTotal($bookingDto);
|
||||
$serviceTotal = $this->calculateServiceTotal($bookingDto);
|
||||
|
||||
return $roomTotal + $serviceTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates total price for all rooms.
|
||||
*/
|
||||
public function calculateRoomTotal(BookingDtoInterface $bookingDto): float
|
||||
{
|
||||
$roomPricing = $this->calculateRoomPricing($bookingDto);
|
||||
|
||||
return array_sum(array_column($roomPricing, 'totalPrice'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates total price for all services.
|
||||
*/
|
||||
public function calculateServiceTotal(BookingDtoInterface $bookingDto): float
|
||||
{
|
||||
$servicePricing = $this->calculateServicePricing($bookingDto);
|
||||
|
||||
return array_sum(array_column($servicePricing, 'groupTotal'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a price value for display with proper German formatting.
|
||||
*
|
||||
* @param float $price The price to format
|
||||
*
|
||||
* @return string Formatted price string (e.g., "123,45")
|
||||
*/
|
||||
public function formatPrice(float $price): string
|
||||
{
|
||||
return number_format($price, 2, ',', '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a price with Euro symbol for display.
|
||||
*
|
||||
* @param float $price The price to format
|
||||
*
|
||||
* @return string Formatted price string with Euro symbol (e.g., "€123,45")
|
||||
*/
|
||||
public function formatPriceWithSymbol(float $price): string
|
||||
{
|
||||
return '€'.$this->formatPrice($price);
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups services by their subtypes for display.
|
||||
*
|
||||
* @param array $serviceAggregation Aggregated service data
|
||||
*
|
||||
* @return array Grouped services by subtype
|
||||
*/
|
||||
private function groupServicesBySubtype(array $serviceAggregation): array
|
||||
{
|
||||
$groupedServices = [];
|
||||
|
||||
foreach ($serviceAggregation as $serviceData) {
|
||||
if ($serviceData['totalPrice'] <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subType = $serviceData['subType'] ?? 'other';
|
||||
$groupName = $this->getGroupNameForSubtype($subType);
|
||||
|
||||
if (false === isset($groupedServices[$groupName])) {
|
||||
$groupedServices[$groupName] = [
|
||||
'groupName' => $groupName,
|
||||
'services' => [],
|
||||
'groupTotal' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
$groupedServices[$groupName]['services'][] = $serviceData;
|
||||
$groupedServices[$groupName]['groupTotal'] += $serviceData['totalPrice'];
|
||||
}
|
||||
|
||||
return array_values($groupedServices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps service subtypes to user-friendly group names.
|
||||
*/
|
||||
private function getGroupNameForSubtype(string $subType): string
|
||||
{
|
||||
$groupMapping = [
|
||||
Constants::TOKEN_COURSES => 'Kurse',
|
||||
Constants::TOKEN_SKI_PASS => 'Skipässe',
|
||||
Constants::TOKEN_ADDITIONAL => 'Zusatzleistungen',
|
||||
Constants::TOKEN_BOARD => 'Verpflegung',
|
||||
];
|
||||
|
||||
// Handle rentals array
|
||||
if (true === in_array($subType, Constants::TOKEN_RENTALS, true)) {
|
||||
return 'Leihmaterial';
|
||||
}
|
||||
|
||||
return $groupMapping[$subType] ?? 'Sonstige Leistungen';
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates service selections from a single participant into the service aggregation array.
|
||||
*/
|
||||
private function aggregateParticipantServices(ParticipantDto $participant, array &$serviceAggregation): void
|
||||
{
|
||||
// Handle single service selections (skiPass)
|
||||
if (null !== $participant->skiPass && null !== $participant->skiPass->price) {
|
||||
$this->addToServiceAggregation($serviceAggregation, $participant->skiPass, 1);
|
||||
}
|
||||
|
||||
// Handle multiple service selections
|
||||
$multipleServiceArrays = [
|
||||
'courses' => $participant->courses,
|
||||
'additionalServices' => $participant->additionalServices,
|
||||
'board' => $participant->board,
|
||||
'rentals' => $participant->rentals,
|
||||
];
|
||||
|
||||
foreach ($multipleServiceArrays as $serviceArray) {
|
||||
if (true === is_array($serviceArray)) {
|
||||
foreach ($serviceArray as $service) {
|
||||
if ($service instanceof Service && null !== $service->price) {
|
||||
$this->addToServiceAggregation($serviceAggregation, $service, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a service to the aggregation array, incrementing count and updating total price.
|
||||
*/
|
||||
private function addToServiceAggregation(array &$serviceAggregation, Service $service, int $quantity): void
|
||||
{
|
||||
$serviceKey = $service->id.'_'.$service->label;
|
||||
|
||||
if (false === isset($serviceAggregation[$serviceKey])) {
|
||||
$serviceAggregation[$serviceKey] = [
|
||||
'serviceId' => $service->id,
|
||||
'label' => $service->label,
|
||||
'unitPrice' => $service->price,
|
||||
'participantCount' => 0,
|
||||
'totalPrice' => 0.0,
|
||||
'subType' => $service->subType,
|
||||
];
|
||||
}
|
||||
|
||||
$serviceAggregation[$serviceKey]['participantCount'] += $quantity;
|
||||
$serviceAggregation[$serviceKey]['totalPrice'] += $service->price * $quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a room by ID from the booking's travel data.
|
||||
*/
|
||||
private function getRoomById(BookingCreateDto $bookingDto, ?int $roomId): ?Room
|
||||
{
|
||||
if (null === $roomId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($bookingDto->travel->rooms as $room) {
|
||||
if ($room->id === $roomId) {
|
||||
return $room;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ class BookingService
|
||||
|
||||
public function __construct(
|
||||
private readonly TravelDataService $travelDataService,
|
||||
private readonly BookingPriceCalculatorService $priceCalculator,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -156,18 +157,20 @@ class BookingService
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary of selected rooms and the resulting participant count for a booking.
|
||||
* Returns a summary of selected rooms, participant count, and pricing information for a booking.
|
||||
*
|
||||
* @return array{selectedRooms: array, participantCount: int}
|
||||
* @return array{selectedRooms: array, participantCount: int, pricing: array}
|
||||
*/
|
||||
public function getRoomSummaryAndParticipantCount(BookingCreateDto $bookingCreateDto): array
|
||||
{
|
||||
$selectedRooms = $bookingCreateDto->getSelectedRooms();
|
||||
$participantCount = $this->getParticipantsCount($selectedRooms, $bookingCreateDto->travel);
|
||||
$pricing = $this->priceCalculator->getPricingBreakdown($bookingCreateDto);
|
||||
|
||||
return [
|
||||
'selectedRooms' => $selectedRooms,
|
||||
'participantCount' => $participantCount,
|
||||
'pricing' => $pricing,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user