feat: split nights off an accommodation booking as separate periods
addresses #869f6z36v
This commit is contained in:
1 parent
972839286c
commit
7001bc0cf2
35 files changed
+2221
-25
No files matched your search
@@ -6,6 +6,9 @@ namespace App\Tests\Service;
|
||||
|
||||
use App\Entity\Groups\Accommodation;
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Entity\Groups\AccommodationBookingPeriod;
|
||||
use App\Entity\Groups\AccommodationPrice;
|
||||
use App\Enum\Groups\AccommodationBookingPeriodPosition;
|
||||
use App\Repository\Groups\AccommodationPriceRepository;
|
||||
use App\Service\AccommodationBookingBreakdownCalculator;
|
||||
use App\Service\GroupsPriceCalculator;
|
||||
@@ -190,6 +193,124 @@ class AccommodationBookingBreakdownCalculatorTest extends TestCase
|
||||
self::assertArrayNotHasKey('discountedTotal', $breakdown);
|
||||
}
|
||||
|
||||
public function testAVersion1SnapshotReadsAsABookingWithoutPeriods(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setPriceSnapshot($this->breakdown(), 12345, 'CHF', 1);
|
||||
|
||||
$breakdown = $this->createCalculator()->compute($booking);
|
||||
|
||||
self::assertSame([], $breakdown['periods']);
|
||||
self::assertSame(0, $breakdown['periodsTotal']);
|
||||
self::assertSame(12345, $breakdown['discountedTotal']);
|
||||
}
|
||||
|
||||
public function testWithoutPeriodsTheCatalogPricesTheWholeStay(): void
|
||||
{
|
||||
// Regression guard: a booking nobody split is priced exactly as before periods existed.
|
||||
$booking = $this->stay();
|
||||
|
||||
$breakdown = $this->createCalculator($this->priceRepository())->computeCurrent($booking);
|
||||
|
||||
self::assertSame(7, $breakdown['nights']);
|
||||
self::assertSame(7 * 100_00, $breakdown['basePrice']);
|
||||
self::assertSame(7 * 100_00, $breakdown['total']);
|
||||
self::assertSame([], $breakdown['periods']);
|
||||
self::assertSame(0, $breakdown['periodsTotal']);
|
||||
}
|
||||
|
||||
public function testTheCatalogPricesOnlyTheMainPeriodAndThePeriodLinesAreAddedOnTop(): void
|
||||
{
|
||||
$booking = $this->stay();
|
||||
$booking->addPeriod($this->period(AccommodationBookingPeriodPosition::Trailing, '2026-07-07', '2026-07-08', [['label' => 'Abbau', 'amount' => 100_00]]));
|
||||
$booking->addPeriod($this->period(AccommodationBookingPeriodPosition::Leading, '2026-07-01', '2026-07-02', [
|
||||
['label' => 'Übernachtung', 'amount' => 250_00],
|
||||
['label' => 'Verpflegung', 'amount' => 50_00],
|
||||
])->setLabel('Vorausteam'));
|
||||
|
||||
$breakdown = $this->createCalculator($this->priceRepository())->computeCurrent($booking);
|
||||
|
||||
self::assertSame(5, $breakdown['nights']);
|
||||
self::assertSame(5 * 100_00, $breakdown['basePrice']);
|
||||
self::assertSame(400_00, $breakdown['periodsTotal']);
|
||||
self::assertSame(5 * 100_00 + 400_00, $breakdown['total']);
|
||||
self::assertSame([
|
||||
[
|
||||
'position' => 'leading',
|
||||
'label' => 'Vorausteam',
|
||||
'dateFrom' => '2026-07-01',
|
||||
'dateTo' => '2026-07-02',
|
||||
'nights' => 1,
|
||||
'lines' => [
|
||||
['label' => 'Übernachtung', 'amount' => 250_00],
|
||||
['label' => 'Verpflegung', 'amount' => 50_00],
|
||||
],
|
||||
'total' => 300_00,
|
||||
],
|
||||
[
|
||||
'position' => 'trailing',
|
||||
'label' => null,
|
||||
'dateFrom' => '2026-07-07',
|
||||
'dateTo' => '2026-07-08',
|
||||
'nights' => 1,
|
||||
'lines' => [['label' => 'Abbau', 'amount' => 100_00]],
|
||||
'total' => 100_00,
|
||||
],
|
||||
], $breakdown['periods']);
|
||||
}
|
||||
|
||||
public function testOnlyTheFixedDiscountReachesThePeriodLines(): void
|
||||
{
|
||||
// The section discounts belong to the catalog-priced main period; the fixed discount
|
||||
// is taken off the grand total, so it may exceed what the main period alone costs.
|
||||
$booking = $this->stay();
|
||||
$booking->addPeriod($this->period(AccommodationBookingPeriodPosition::Leading, '2026-07-01', '2026-07-02', [['label' => 'Vorausteam', 'amount' => 1000_00]]));
|
||||
$booking->setAccommodationDiscount(10);
|
||||
$booking->setTotalDiscountAmount(900_00);
|
||||
$booking->setTotalDiscountLabel('Sonderpreis');
|
||||
$calculator = $this->createCalculator($this->priceRepository());
|
||||
|
||||
$breakdown = $calculator->withDiscounts($booking, $calculator->computeCurrent($booking));
|
||||
|
||||
self::assertSame([['label' => 'Rabatt Unterkunft', 'percent' => 10, 'amount' => 60_00]], $breakdown['discounts']);
|
||||
self::assertSame(600_00 + 1000_00 - 60_00, $breakdown['discountSubtotal']);
|
||||
self::assertSame(600_00 + 1000_00 - 60_00 - 900_00, $breakdown['discountedTotal']);
|
||||
}
|
||||
|
||||
private function stay(): AccommodationBooking
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setAccommodation((new Accommodation())->setCalendarCode('HOTEL')->setCurrency('EUR'));
|
||||
$booking->setDateFrom(new \DateTimeImmutable('2026-07-01'));
|
||||
$booking->setDateTo(new \DateTimeImmutable('2026-07-08'));
|
||||
$booking->setPaxCount(20);
|
||||
|
||||
return $booking;
|
||||
}
|
||||
|
||||
/** @param list<array{label: string, amount: int}> $lines */
|
||||
private function period(AccommodationBookingPeriodPosition $position, string $from, string $to, array $lines): AccommodationBookingPeriod
|
||||
{
|
||||
return (new AccommodationBookingPeriod($position, new \DateTimeImmutable($from), new \DateTimeImmutable($to)))
|
||||
->setLines($lines);
|
||||
}
|
||||
|
||||
private function priceRepository(): AccommodationPriceRepository
|
||||
{
|
||||
$price = (new AccommodationPrice())
|
||||
->setDateFrom(new \DateTimeImmutable('2026-06-01'))
|
||||
->setDateTo(new \DateTimeImmutable('2026-08-31'))
|
||||
->setIncludedPax(20)
|
||||
->setPricePerNight(100_00)
|
||||
->setPriceAdditionalPerson(10_00)
|
||||
->setMinNights(1);
|
||||
|
||||
$priceRepository = $this->createStub(AccommodationPriceRepository::class);
|
||||
$priceRepository->method('findByHotelCodeAndDateRange')->willReturn([$price]);
|
||||
|
||||
return $priceRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user