Files
myep/tests/Service/AccommodationBookingBreakdownCalculatorTest.php
T

148 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Service;
use App\Entity\Groups\Accommodation;
use App\Entity\Groups\AccommodationBooking;
use App\Repository\Groups\AccommodationPriceRepository;
use App\Service\AccommodationBookingBreakdownCalculator;
use App\Service\GroupsPriceCalculator;
use App\Service\PriceTimelineBuilder;
use PHPUnit\Framework\TestCase;
class AccommodationBookingBreakdownCalculatorTest extends TestCase
{
public function testComputeReturnsStoredSnapshotWithoutLoadingCurrentPrices(): void
{
$snapshot = ['total' => 12345, 'currency' => 'EUR'];
$booking = new AccommodationBooking();
$booking->setPriceSnapshot($snapshot, 12345, 'EUR', 1);
$priceRepository = $this->createMock(AccommodationPriceRepository::class);
$priceRepository->expects(self::never())->method('findByHotelCodeAndDateRange');
$breakdown = $this->createCalculator($priceRepository)->compute($booking);
self::assertSame(12345, $breakdown['total']);
self::assertSame('EUR', $breakdown['currency']);
}
public function testComputeEnrichesTheStoredSnapshotWithDiscountRowsAndTotal(): void
{
$booking = new AccommodationBooking();
$booking->setPriceSnapshot($this->breakdown(), 12345, 'CHF', 1);
$booking->setAccommodationDiscount(10);
$breakdown = $this->createCalculator()->compute($booking);
self::assertSame([['label' => 'Unterkunft', 'percent' => 10, 'amount' => 1000]], $breakdown['discounts']);
self::assertSame(12345 - 1000, $breakdown['discountedTotal']);
}
public function testWithDiscountsListsOneRowPerConfiguredDiscount(): void
{
$booking = new AccommodationBooking();
$booking->setAccommodationDiscount(10);
$booking->setBoardServiceDiscount(20);
$booking->setAdditionalServicesDiscount(50);
$breakdown = $this->createCalculator()->withDiscounts($booking, $this->breakdown());
// accommodation: (8000+2000)*10% = 1000, board: 1000*20% = 200, services: 500*50% = 250
self::assertSame([
['label' => 'Unterkunft', 'percent' => 10, 'amount' => 1000],
['label' => 'Verpflegung', 'percent' => 20, 'amount' => 200],
['label' => 'Zusatzleistungen', 'percent' => 50, 'amount' => 250],
], $breakdown['discounts']);
self::assertSame(12345 - 1000 - 200 - 250, $breakdown['discountedTotal']);
}
public function testWithDiscountsRoundsToWholeCents(): void
{
$booking = new AccommodationBooking();
$booking->setBoardServiceDiscount(15);
// 333 * 15% = 49.95 — the amount has to land on whole cents, and the discounted total
// has to be reduced by the very number that is shown on the row.
$breakdown = $this->createCalculator()->withDiscounts($booking, ['total' => 12345, 'boardPrice' => 333]);
self::assertSame(50, $breakdown['discounts'][0]['amount']);
self::assertSame(12345 - 50, $breakdown['discountedTotal']);
}
public function testWithDiscountsSkipsDiscountsThatAmountToNothing(): void
{
$booking = new AccommodationBooking();
$booking->setAccommodationDiscount(0);
// A discount on a bucket this booking has no price in.
$booking->setAdditionalServicesDiscount(20);
$breakdown = $this->createCalculator()->withDiscounts($booking, [
'total' => 12345,
'basePrice' => 8000,
'servicesPrice' => 0,
]);
self::assertSame([], $breakdown['discounts']);
self::assertSame(12345, $breakdown['discountedTotal']);
}
public function testWithoutDiscountsTheDiscountedTotalIsTheTotal(): void
{
$breakdown = $this->createCalculator()->withDiscounts(new AccommodationBooking(), $this->breakdown());
self::assertSame([], $breakdown['discounts']);
self::assertSame(12345, $breakdown['discountedTotal']);
}
/**
* The derived keys must never reach the database: refreshPriceSnapshot() freezes exactly
* what computeCurrent() returns.
*/
public function testComputeCurrentReturnsTheRawBreakdown(): void
{
$booking = new AccommodationBooking();
$booking->setAccommodationDiscount(10);
$booking->setAccommodation((new Accommodation())->setCalendarCode('HOTEL')->setCurrency('CHF'));
$booking->setDateFrom(new \DateTimeImmutable('2026-08-01'));
$booking->setDateTo(new \DateTimeImmutable('2026-08-03'));
$breakdown = $this->createCalculator()->computeCurrent($booking);
self::assertArrayNotHasKey('discounts', $breakdown);
self::assertArrayNotHasKey('discountedTotal', $breakdown);
}
/**
* @return array<string, mixed>
*/
private function breakdown(): array
{
return [
'total' => 12345,
'currency' => 'CHF',
'basePrice' => 8000,
'additionalPersonsPrice' => 2000,
'boardPrice' => 1000,
'servicesPrice' => 500,
];
}
private function createCalculator(?AccommodationPriceRepository $priceRepository = null): AccommodationBookingBreakdownCalculator
{
return new AccommodationBookingBreakdownCalculator(
new GroupsPriceCalculator(new PriceTimelineBuilder(), [
'runningCostsEur' => 0,
'runningCostsChf' => 0,
'undersubscription30Eur' => 0,
'undersubscription30Chf' => 0,
'undersubscription40Eur' => 0,
'undersubscription40Chf' => 0,
]),
$priceRepository ?? $this->createMock(AccommodationPriceRepository::class),
);
}
}