feat: correct calculation and display of individual pricing
This commit is contained in:
@@ -8,6 +8,7 @@ use App\Controller\Traits\HtmxControllerTrait;
|
|||||||
use App\Form\BookingCreateStep2Type;
|
use App\Form\BookingCreateStep2Type;
|
||||||
use App\Form\Model\BookingCreateDto;
|
use App\Form\Model\BookingCreateDto;
|
||||||
use App\Form\Model\ParticipantDto;
|
use App\Form\Model\ParticipantDto;
|
||||||
|
use App\Service\BookingPriceCalculatorService;
|
||||||
use App\Service\BookingService;
|
use App\Service\BookingService;
|
||||||
use App\Service\TravelDataService;
|
use App\Service\TravelDataService;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
@@ -29,6 +30,7 @@ class CreateStep2Controller extends AbstractController
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly BookingService $bookingService,
|
private readonly BookingService $bookingService,
|
||||||
|
private readonly BookingPriceCalculatorService $priceCalculator,
|
||||||
private readonly TravelDataService $travelDataService,
|
private readonly TravelDataService $travelDataService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -80,12 +82,14 @@ class CreateStep2Controller extends AbstractController
|
|||||||
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
|
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
|
||||||
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
|
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
|
||||||
|
$participantPrices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingCreateDto);
|
||||||
|
|
||||||
return $this->render('booking/create_step_2.html.twig', [
|
return $this->render('booking/create_step_2.html.twig', [
|
||||||
'bookingCreateDto' => $bookingCreateDto,
|
'bookingCreateDto' => $bookingCreateDto,
|
||||||
'participantsCount' => $participantsCount,
|
'participantsCount' => $participantsCount,
|
||||||
'pricingData' => $summary['pricing'],
|
'pricingData' => $summary['pricing'],
|
||||||
'assignmentCounts' => $roomAssignmentCounts,
|
'assignmentCounts' => $roomAssignmentCounts,
|
||||||
|
'participantPrices' => $participantPrices,
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
'groupedSelectedRooms' => $groupedSelectedRooms,
|
'groupedSelectedRooms' => $groupedSelectedRooms,
|
||||||
]);
|
]);
|
||||||
@@ -132,6 +136,7 @@ class CreateStep2Controller extends AbstractController
|
|||||||
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
|
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
|
||||||
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
|
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
|
||||||
|
$participantPrices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingCreateDto);
|
||||||
|
|
||||||
// The DTO is now updated with the latest selection and submitted data has been cleaned.
|
// The DTO is now updated with the latest selection and submitted data has been cleaned.
|
||||||
// We can now render the blocks with the fresh data.
|
// We can now render the blocks with the fresh data.
|
||||||
@@ -144,6 +149,7 @@ class CreateStep2Controller extends AbstractController
|
|||||||
'participantsCount' => $participantsCount,
|
'participantsCount' => $participantsCount,
|
||||||
'pricingData' => $summary['pricing'],
|
'pricingData' => $summary['pricing'],
|
||||||
'assignmentCounts' => $roomAssignmentCounts,
|
'assignmentCounts' => $roomAssignmentCounts,
|
||||||
|
'participantPrices' => $participantPrices,
|
||||||
'groupedSelectedRooms' => $groupedSelectedRooms,
|
'groupedSelectedRooms' => $groupedSelectedRooms,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -66,12 +66,17 @@ class BookingPriceCalculatorService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$totalPrice = $roomSelection->quantity * $room->price;
|
// Calculate participant count for this room selection
|
||||||
|
$participantCount = $room->minPax * $roomSelection->quantity;
|
||||||
|
|
||||||
|
// Each participant pays the full room price
|
||||||
|
$totalPrice = $participantCount * $room->price;
|
||||||
|
|
||||||
$roomPricing[] = [
|
$roomPricing[] = [
|
||||||
'roomId' => $room->id,
|
'roomId' => $room->id,
|
||||||
'label' => $room->label,
|
'label' => $room->label,
|
||||||
'quantity' => $roomSelection->quantity,
|
'quantity' => $roomSelection->quantity,
|
||||||
|
'participantCount' => $participantCount,
|
||||||
'unitPrice' => $room->price,
|
'unitPrice' => $room->price,
|
||||||
'totalPrice' => $totalPrice,
|
'totalPrice' => $totalPrice,
|
||||||
];
|
];
|
||||||
@@ -171,6 +176,68 @@ class BookingPriceCalculatorService
|
|||||||
return '€'.$this->formatPrice($price);
|
return '€'.$this->formatPrice($price);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the total price for an individual participant.
|
||||||
|
*
|
||||||
|
* This method calculates the complete price breakdown for a single participant,
|
||||||
|
* including their room allocation (full room price) and all selected services.
|
||||||
|
*
|
||||||
|
* @param BookingDtoInterface $bookingDto The booking data containing all participants
|
||||||
|
* @param int $participantIndex The index of the participant to calculate for
|
||||||
|
*
|
||||||
|
* @return float The total price for the specified participant
|
||||||
|
*/
|
||||||
|
public function calculateIndividualParticipantPrice(BookingDtoInterface $bookingDto, int $participantIndex): float
|
||||||
|
{
|
||||||
|
if (false === $bookingDto instanceof BookingCreateDto) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$participant = $bookingDto->getParticipant($participantIndex);
|
||||||
|
if (null === $participant) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalPrice = 0.0;
|
||||||
|
|
||||||
|
// Add room price if participant is assigned to a room
|
||||||
|
if (null !== $participant->assignedRoomId) {
|
||||||
|
$room = $this->getRoomById($bookingDto, $participant->assignedRoomId);
|
||||||
|
if (null !== $room && null !== $room->price) {
|
||||||
|
// Each participant pays the full room price
|
||||||
|
$totalPrice += $room->price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add service prices for this participant
|
||||||
|
$totalPrice += $this->calculateParticipantServiceTotal($participant);
|
||||||
|
|
||||||
|
return $totalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates individual prices for all participants in a booking.
|
||||||
|
*
|
||||||
|
* @param BookingDtoInterface $bookingDto The booking data containing all participants
|
||||||
|
*
|
||||||
|
* @return array Array indexed by participant index containing individual prices
|
||||||
|
*/
|
||||||
|
public function calculateAllParticipantIndividualPrices(BookingDtoInterface $bookingDto): array
|
||||||
|
{
|
||||||
|
if (false === $bookingDto instanceof BookingCreateDto) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$participantPrices = [];
|
||||||
|
$participants = $bookingDto->getParticipants();
|
||||||
|
|
||||||
|
foreach ($participants as $index => $participant) {
|
||||||
|
$participantPrices[$index] = $this->calculateIndividualParticipantPrice($bookingDto, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $participantPrices;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aggregates all transportation-related services and pricing into separate line items.
|
* Aggregates all transportation-related services and pricing into separate line items.
|
||||||
*
|
*
|
||||||
@@ -405,6 +472,68 @@ class BookingPriceCalculatorService
|
|||||||
$serviceAggregation[$serviceKey]['totalPrice'] += $service->price * $quantity;
|
$serviceAggregation[$serviceKey]['totalPrice'] += $service->price * $quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the total service cost for a single participant.
|
||||||
|
*
|
||||||
|
* @param ParticipantDto $participant The participant to calculate services for
|
||||||
|
*
|
||||||
|
* @return float The total service cost for this participant
|
||||||
|
*/
|
||||||
|
private function calculateParticipantServiceTotal(ParticipantDto $participant): float
|
||||||
|
{
|
||||||
|
$serviceTotal = 0.0;
|
||||||
|
|
||||||
|
// Single service selections
|
||||||
|
if (null !== $participant->skiPass && null !== $participant->skiPass->price) {
|
||||||
|
$serviceTotal += $participant->skiPass->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $participant->rentalInsurance && null !== $participant->rentalInsurance->price) {
|
||||||
|
$serviceTotal += $participant->rentalInsurance->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transportation services
|
||||||
|
if (null !== $participant->transportationOutbound && null !== $participant->transportationOutbound->price) {
|
||||||
|
$serviceTotal += $participant->transportationOutbound->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $participant->transportationInbound && null !== $participant->transportationInbound->price) {
|
||||||
|
$serviceTotal += $participant->transportationInbound->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $participant->pickupOutbound && null !== $participant->pickupOutbound->price) {
|
||||||
|
$serviceTotal += $participant->pickupOutbound->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $participant->pickupInbound && null !== $participant->pickupInbound->price) {
|
||||||
|
$serviceTotal += $participant->pickupInbound->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $participant->parkingService && null !== $participant->parkingService->price) {
|
||||||
|
$serviceTotal += $participant->parkingService->price;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
$serviceTotal += $service->price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $serviceTotal;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a room by ID from the booking's travel data.
|
* Retrieves a room by ID from the booking's travel data.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,7 +16,14 @@
|
|||||||
<div class="{{ html_classes('border rounded px-4 py-2', { 'border-gray-300': participantDataValid, 'border-red-700': not participantDataValid }) }}" {{ stimulus_controller('toggle', {'storageKey': 'participant_' ~ loop.index0, 'open': participant.vars.valid == false}, { 'closed': 'hidden' }) }}>
|
<div class="{{ html_classes('border rounded px-4 py-2', { 'border-gray-300': participantDataValid, 'border-red-700': not participantDataValid }) }}" {{ stimulus_controller('toggle', {'storageKey': 'participant_' ~ loop.index0, 'open': participant.vars.valid == false}, { 'closed': 'hidden' }) }}>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="w-full flex items-center justify-between">
|
<legend class="w-full flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
<span class="font-bold text-xl">Teilnehmer:in {{ loop.index }}</span>
|
<span class="font-bold text-xl">Teilnehmer:in {{ loop.index }}</span>
|
||||||
|
{% if participantPrices is defined and participantPrices[loop.index0] is defined and participantPrices[loop.index0] > 0.0 %}
|
||||||
|
<span class="text-sm font-medium text-gray-600 bg-gray-100 px-2 py-1 rounded">
|
||||||
|
€{{ participantPrices[loop.index0]|number_format(2, ',', '.') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<button type="button" tabindex="0" {{ stimulus_action('toggle', 'toggle') }}>
|
<button type="button" tabindex="0" {{ stimulus_action('toggle', 'toggle') }}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6" {{ stimulus_target('toggle', 'icon') }}>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6" {{ stimulus_target('toggle', 'icon') }}>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Service;
|
||||||
|
|
||||||
|
use App\BusProNet\Model\Room;
|
||||||
|
use App\BusProNet\Model\Service;
|
||||||
|
use App\BusProNet\Model\Travel;
|
||||||
|
use App\Form\Model\BookingCreateDto;
|
||||||
|
use App\Form\Model\ParticipantDto;
|
||||||
|
use App\Form\Model\RoomSelectionDto;
|
||||||
|
use App\Service\BookingPriceCalculatorService;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class BookingPriceCalculatorServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
private BookingPriceCalculatorService $service;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->service = new BookingPriceCalculatorService();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateRoomPricingWithParticipantBasedCalculation(): void
|
||||||
|
{
|
||||||
|
// Create test data: 2 double rooms at €100 each, minPax=2
|
||||||
|
$room = new Room();
|
||||||
|
$room->id = 1;
|
||||||
|
$room->price = 100.0;
|
||||||
|
$room->minPax = 2;
|
||||||
|
$room->label = 'Double Room';
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$travel->rooms = [$room];
|
||||||
|
|
||||||
|
$roomSelection = new RoomSelectionDto();
|
||||||
|
$roomSelection->roomId = 1;
|
||||||
|
$roomSelection->quantity = 2; // 2 rooms selected
|
||||||
|
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->roomSelections = [$roomSelection];
|
||||||
|
|
||||||
|
// Test the calculation
|
||||||
|
$result = $this->service->calculateRoomPricing($bookingDto);
|
||||||
|
|
||||||
|
// Expected: 2 rooms × 2 participants/room × €100 = €400 total
|
||||||
|
$this->assertCount(1, $result);
|
||||||
|
$this->assertEquals(1, $result[0]['roomId']);
|
||||||
|
$this->assertEquals('Double Room', $result[0]['label']);
|
||||||
|
$this->assertEquals(2, $result[0]['quantity']); // 2 rooms
|
||||||
|
$this->assertEquals(4, $result[0]['participantCount']); // 4 participants total
|
||||||
|
$this->assertEquals(100.0, $result[0]['unitPrice']); // €100 per participant
|
||||||
|
$this->assertEquals(400.0, $result[0]['totalPrice']); // €400 total
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateRoomPricingWithSingleRoomSelection(): void
|
||||||
|
{
|
||||||
|
// Create test data: 1 triple room at €150, minPax=3
|
||||||
|
$room = new Room();
|
||||||
|
$room->id = 2;
|
||||||
|
$room->price = 150.0;
|
||||||
|
$room->minPax = 3;
|
||||||
|
$room->label = 'Triple Room';
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$travel->rooms = [$room];
|
||||||
|
|
||||||
|
$roomSelection = new RoomSelectionDto();
|
||||||
|
$roomSelection->roomId = 2;
|
||||||
|
$roomSelection->quantity = 1; // 1 room selected
|
||||||
|
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->roomSelections = [$roomSelection];
|
||||||
|
|
||||||
|
// Test the calculation
|
||||||
|
$result = $this->service->calculateRoomPricing($bookingDto);
|
||||||
|
|
||||||
|
// Expected: 1 room × 3 participants/room × €150 = €450 total
|
||||||
|
$this->assertCount(1, $result);
|
||||||
|
$this->assertEquals(2, $result[0]['roomId']);
|
||||||
|
$this->assertEquals('Triple Room', $result[0]['label']);
|
||||||
|
$this->assertEquals(1, $result[0]['quantity']); // 1 room
|
||||||
|
$this->assertEquals(3, $result[0]['participantCount']); // 3 participants total
|
||||||
|
$this->assertEquals(150.0, $result[0]['unitPrice']); // €150 per participant
|
||||||
|
$this->assertEquals(450.0, $result[0]['totalPrice']); // €450 total
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateRoomPricingWithMultipleRoomTypes(): void
|
||||||
|
{
|
||||||
|
// Create test data: Multiple room types
|
||||||
|
$singleRoom = new Room();
|
||||||
|
$singleRoom->id = 1;
|
||||||
|
$singleRoom->price = 80.0;
|
||||||
|
$singleRoom->minPax = 1;
|
||||||
|
$singleRoom->label = 'Single Room';
|
||||||
|
|
||||||
|
$doubleRoom = new Room();
|
||||||
|
$doubleRoom->id = 2;
|
||||||
|
$doubleRoom->price = 120.0;
|
||||||
|
$doubleRoom->minPax = 2;
|
||||||
|
$doubleRoom->label = 'Double Room';
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$travel->rooms = [$singleRoom, $doubleRoom];
|
||||||
|
|
||||||
|
$singleRoomSelection = new RoomSelectionDto();
|
||||||
|
$singleRoomSelection->roomId = 1;
|
||||||
|
$singleRoomSelection->quantity = 1; // 1 single room
|
||||||
|
|
||||||
|
$doubleRoomSelection = new RoomSelectionDto();
|
||||||
|
$doubleRoomSelection->roomId = 2;
|
||||||
|
$doubleRoomSelection->quantity = 2; // 2 double rooms
|
||||||
|
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->roomSelections = [$singleRoomSelection, $doubleRoomSelection];
|
||||||
|
|
||||||
|
// Test the calculation
|
||||||
|
$result = $this->service->calculateRoomPricing($bookingDto);
|
||||||
|
|
||||||
|
// Expected:
|
||||||
|
// - Single: 1 room × 1 participant × €80 = €80
|
||||||
|
// - Double: 2 rooms × 2 participants × €120 = €480
|
||||||
|
$this->assertCount(2, $result);
|
||||||
|
|
||||||
|
// Single room result
|
||||||
|
$singleResult = $result[0];
|
||||||
|
$this->assertEquals(1, $singleResult['roomId']);
|
||||||
|
$this->assertEquals(1, $singleResult['quantity']);
|
||||||
|
$this->assertEquals(1, $singleResult['participantCount']);
|
||||||
|
$this->assertEquals(80.0, $singleResult['unitPrice']);
|
||||||
|
$this->assertEquals(80.0, $singleResult['totalPrice']);
|
||||||
|
|
||||||
|
// Double room result
|
||||||
|
$doubleResult = $result[1];
|
||||||
|
$this->assertEquals(2, $doubleResult['roomId']);
|
||||||
|
$this->assertEquals(2, $doubleResult['quantity']);
|
||||||
|
$this->assertEquals(4, $doubleResult['participantCount']);
|
||||||
|
$this->assertEquals(120.0, $doubleResult['unitPrice']);
|
||||||
|
$this->assertEquals(480.0, $doubleResult['totalPrice']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateRoomPricingSkipsRoomsWithNullPrice(): void
|
||||||
|
{
|
||||||
|
$room = new Room();
|
||||||
|
$room->id = 1;
|
||||||
|
$room->price = null; // No price set
|
||||||
|
$room->minPax = 2;
|
||||||
|
$room->label = 'Free Room';
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$travel->rooms = [$room];
|
||||||
|
|
||||||
|
$roomSelection = new RoomSelectionDto();
|
||||||
|
$roomSelection->roomId = 1;
|
||||||
|
$roomSelection->quantity = 1;
|
||||||
|
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->roomSelections = [$roomSelection];
|
||||||
|
|
||||||
|
$result = $this->service->calculateRoomPricing($bookingDto);
|
||||||
|
|
||||||
|
// Should skip rooms with null price
|
||||||
|
$this->assertEmpty($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateRoomPricingWithZeroQuantityRooms(): void
|
||||||
|
{
|
||||||
|
$room = new Room();
|
||||||
|
$room->id = 1;
|
||||||
|
$room->price = 100.0;
|
||||||
|
$room->minPax = 2;
|
||||||
|
$room->label = 'Double Room';
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$travel->rooms = [$room];
|
||||||
|
|
||||||
|
$roomSelection = new RoomSelectionDto();
|
||||||
|
$roomSelection->roomId = 1;
|
||||||
|
$roomSelection->quantity = 0; // Zero quantity - not selected
|
||||||
|
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->roomSelections = [$roomSelection];
|
||||||
|
|
||||||
|
$result = $this->service->calculateRoomPricing($bookingDto);
|
||||||
|
|
||||||
|
// Should skip rooms with zero quantity
|
||||||
|
$this->assertEmpty($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateIndividualParticipantPriceWithRoomAndServices(): void
|
||||||
|
{
|
||||||
|
// Create test room
|
||||||
|
$room = new Room();
|
||||||
|
$room->id = 1;
|
||||||
|
$room->price = 100.0;
|
||||||
|
$room->minPax = 2;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$travel->rooms = [$room];
|
||||||
|
|
||||||
|
// Create test services
|
||||||
|
$skiPass = new Service();
|
||||||
|
$skiPass->price = 50.0;
|
||||||
|
|
||||||
|
$course = new Service();
|
||||||
|
$course->price = 30.0;
|
||||||
|
|
||||||
|
// Create participant with room assignment and services
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->assignedRoomId = 1;
|
||||||
|
$participant->skiPass = $skiPass;
|
||||||
|
$participant->courses = [$course];
|
||||||
|
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$participant];
|
||||||
|
|
||||||
|
$result = $this->service->calculateIndividualParticipantPrice($bookingDto, 0);
|
||||||
|
|
||||||
|
// Expected: €100 (room) + €50 (ski pass) + €30 (course) = €180
|
||||||
|
$this->assertEquals(180.0, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateIndividualParticipantPriceWithoutRoomAssignment(): void
|
||||||
|
{
|
||||||
|
$travel = new Travel();
|
||||||
|
|
||||||
|
// Create test services
|
||||||
|
$skiPass = new Service();
|
||||||
|
$skiPass->price = 50.0;
|
||||||
|
|
||||||
|
// Create participant without room assignment
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->assignedRoomId = null; // No room assigned
|
||||||
|
$participant->skiPass = $skiPass;
|
||||||
|
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$participant];
|
||||||
|
|
||||||
|
$result = $this->service->calculateIndividualParticipantPrice($bookingDto, 0);
|
||||||
|
|
||||||
|
// Expected: €0 (no room) + €50 (ski pass) = €50
|
||||||
|
$this->assertEquals(50.0, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateIndividualParticipantPriceForNonExistentParticipant(): void
|
||||||
|
{
|
||||||
|
$travel = new Travel();
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->participants = [];
|
||||||
|
|
||||||
|
$result = $this->service->calculateIndividualParticipantPrice($bookingDto, 0);
|
||||||
|
|
||||||
|
// Expected: €0 for non-existent participant
|
||||||
|
$this->assertEquals(0.0, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateAllParticipantIndividualPricesWithMultipleParticipants(): void
|
||||||
|
{
|
||||||
|
// Create test rooms
|
||||||
|
$singleRoom = new Room();
|
||||||
|
$singleRoom->id = 1;
|
||||||
|
$singleRoom->price = 80.0;
|
||||||
|
$singleRoom->minPax = 1;
|
||||||
|
|
||||||
|
$doubleRoom = new Room();
|
||||||
|
$doubleRoom->id = 2;
|
||||||
|
$doubleRoom->price = 120.0;
|
||||||
|
$doubleRoom->minPax = 2;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$travel->rooms = [$singleRoom, $doubleRoom];
|
||||||
|
|
||||||
|
// Create test services
|
||||||
|
$skiPass = new Service();
|
||||||
|
$skiPass->price = 50.0;
|
||||||
|
|
||||||
|
$course = new Service();
|
||||||
|
$course->price = 30.0;
|
||||||
|
|
||||||
|
// Create participants
|
||||||
|
$participant1 = new ParticipantDto();
|
||||||
|
$participant1->assignedRoomId = 1; // Single room
|
||||||
|
$participant1->skiPass = $skiPass;
|
||||||
|
|
||||||
|
$participant2 = new ParticipantDto();
|
||||||
|
$participant2->assignedRoomId = 2; // Double room
|
||||||
|
$participant2->courses = [$course];
|
||||||
|
|
||||||
|
$participant3 = new ParticipantDto();
|
||||||
|
$participant3->assignedRoomId = null; // No room assigned
|
||||||
|
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$participant1, $participant2, $participant3];
|
||||||
|
|
||||||
|
$result = $this->service->calculateAllParticipantIndividualPrices($bookingDto);
|
||||||
|
|
||||||
|
// Expected results:
|
||||||
|
// Participant 0: €80 (single room) + €50 (ski pass) = €130
|
||||||
|
// Participant 1: €120 (double room) + €30 (course) = €150
|
||||||
|
// Participant 2: €0 (no room) + €0 (no services) = €0
|
||||||
|
$this->assertCount(3, $result);
|
||||||
|
$this->assertEquals(130.0, $result[0]);
|
||||||
|
$this->assertEquals(150.0, $result[1]);
|
||||||
|
$this->assertEquals(0.0, $result[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCalculateAllParticipantIndividualPricesWithEmptyBooking(): void
|
||||||
|
{
|
||||||
|
$travel = new Travel();
|
||||||
|
$bookingDto = new BookingCreateDto($travel, 1);
|
||||||
|
$bookingDto->participants = [];
|
||||||
|
|
||||||
|
$result = $this->service->calculateAllParticipantIndividualPrices($bookingDto);
|
||||||
|
|
||||||
|
$this->assertEmpty($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user