feat: additional custom discount applied to total price
This commit is contained in:
@@ -56,6 +56,8 @@ class AccommodationBookingControllerTest extends TestCase
|
||||
$booking->setAccommodationDiscount(10);
|
||||
$booking->setBoardServiceDiscount(20);
|
||||
$booking->setAdditionalServicesDiscount(30);
|
||||
$booking->setTotalDiscount(5);
|
||||
$booking->setTotalDiscountLabel('Treuerabatt');
|
||||
$booking->setStatus(AccommodationBookingStatus::Confirmed);
|
||||
$booking->setOrigin(AccommodationBookingOrigin::Direct);
|
||||
$booking->setAcceptedAt(new \DateTimeImmutable('2026-07-15T10:00:00+00:00'));
|
||||
@@ -127,6 +129,8 @@ class AccommodationBookingControllerTest extends TestCase
|
||||
'accommodationDiscount' => 10,
|
||||
'boardServiceDiscount' => 20,
|
||||
'additionalServicesDiscount' => 30,
|
||||
'totalDiscount' => 5,
|
||||
'totalDiscountLabel' => 'Treuerabatt',
|
||||
'totalPrice' => 11111,
|
||||
'pricingCurrency' => 'EUR',
|
||||
'pricingVersion' => 1,
|
||||
|
||||
@@ -73,6 +73,42 @@ class AccommodationBookingTypeTest extends TestCase
|
||||
self::assertSame(['email', 'firstName', 'groupName', 'lastName'], $properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* The label is printed verbatim on the customer's PDF, so a percentage without one is
|
||||
* refused — and in the Default group, so that even a draft cannot store the pair half-set.
|
||||
*/
|
||||
public function testTotalDiscountRequiresALabel(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setTotalDiscount(5);
|
||||
|
||||
$violations = $this->validate($booking, ['Default']);
|
||||
|
||||
self::assertCount(1, $violations);
|
||||
self::assertSame('totalDiscountLabel', $violations[0]->getPropertyPath());
|
||||
}
|
||||
|
||||
public function testALabelledTotalDiscountPassesValidation(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setTotalDiscount(5);
|
||||
$booking->setTotalDiscountLabel('Treuerabatt');
|
||||
|
||||
self::assertCount(0, $this->validate($booking, ['Default']));
|
||||
}
|
||||
|
||||
/**
|
||||
* The reverse is harmless — without a percentage the label never reaches a breakdown row —
|
||||
* so it is deliberately left valid rather than blocking a half-typed edit.
|
||||
*/
|
||||
public function testALabelWithoutAPercentageIsAccepted(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setTotalDiscountLabel('Treuerabatt');
|
||||
|
||||
self::assertCount(0, $this->validate($booking, ['Default']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,7 @@ class AccommodationBookingBreakdownCalculatorTest extends TestCase
|
||||
|
||||
$breakdown = $this->createCalculator()->compute($booking);
|
||||
|
||||
self::assertSame([['label' => 'Unterkunft', 'percent' => 10, 'amount' => 1000]], $breakdown['discounts']);
|
||||
self::assertSame([['label' => 'Rabatt Unterkunft', 'percent' => 10, 'amount' => 1000]], $breakdown['discounts']);
|
||||
self::assertSame(12345 - 1000, $breakdown['discountedTotal']);
|
||||
}
|
||||
|
||||
@@ -52,9 +52,9 @@ class AccommodationBookingBreakdownCalculatorTest extends TestCase
|
||||
|
||||
// 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],
|
||||
['label' => 'Rabatt Unterkunft', 'percent' => 10, 'amount' => 1000],
|
||||
['label' => 'Rabatt Verpflegung', 'percent' => 20, 'amount' => 200],
|
||||
['label' => 'Rabatt Zusatzleistungen', 'percent' => 50, 'amount' => 250],
|
||||
], $breakdown['discounts']);
|
||||
self::assertSame(12345 - 1000 - 200 - 250, $breakdown['discountedTotal']);
|
||||
}
|
||||
@@ -94,9 +94,67 @@ class AccommodationBookingBreakdownCalculatorTest extends TestCase
|
||||
$breakdown = $this->createCalculator()->withDiscounts(new AccommodationBooking(), $this->breakdown());
|
||||
|
||||
self::assertSame([], $breakdown['discounts']);
|
||||
self::assertNull($breakdown['totalDiscountDetails']);
|
||||
self::assertNull($breakdown['discountSubtotal']);
|
||||
self::assertSame(12345, $breakdown['discountedTotal']);
|
||||
}
|
||||
|
||||
/**
|
||||
* On its own the total discount has nothing to sit below, so no subtotal row is offered and
|
||||
* the percentage applies to the plain total.
|
||||
*/
|
||||
public function testTotalDiscountAloneAppliesToTheTotalWithoutASubtotal(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setTotalDiscount(10);
|
||||
$booking->setTotalDiscountLabel('Treuerabatt');
|
||||
|
||||
$breakdown = $this->createCalculator()->withDiscounts($booking, $this->breakdown());
|
||||
|
||||
self::assertSame([], $breakdown['discounts']);
|
||||
self::assertSame(['label' => 'Treuerabatt', 'percent' => 10, 'amount' => 1235], $breakdown['totalDiscountDetails']);
|
||||
self::assertNull($breakdown['discountSubtotal']);
|
||||
self::assertSame(12345 - 1235, $breakdown['discountedTotal']);
|
||||
}
|
||||
|
||||
/**
|
||||
* The worked example from the specification: the total discount compounds on the subtotal
|
||||
* left by the section discounts, not on the gross total — 5 % of 930,00 € is 46,50 €, where
|
||||
* 5 % of the gross 1.000,00 € would have been 50,00 €.
|
||||
*/
|
||||
public function testTotalDiscountCompoundsOnTheSubtotalAfterSectionDiscounts(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setAccommodationDiscount(10);
|
||||
$booking->setBoardServiceDiscount(5);
|
||||
$booking->setTotalDiscount(5);
|
||||
$booking->setTotalDiscountLabel('Treuerabatt');
|
||||
|
||||
$breakdown = $this->createCalculator()->withDiscounts($booking, [
|
||||
'total' => 100000,
|
||||
'basePrice' => 60000,
|
||||
'boardPrice' => 20000,
|
||||
]);
|
||||
|
||||
// accommodation: 60000*10% = 6000, board: 20000*5% = 1000 → subtotal 93000
|
||||
self::assertSame(93000, $breakdown['discountSubtotal']);
|
||||
self::assertSame(['label' => 'Treuerabatt', 'percent' => 5, 'amount' => 4650], $breakdown['totalDiscountDetails']);
|
||||
self::assertSame(88350, $breakdown['discountedTotal']);
|
||||
}
|
||||
|
||||
public function testTotalDiscountThatAmountsToNothingIsSkipped(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setTotalDiscount(20);
|
||||
$booking->setTotalDiscountLabel('Treuerabatt');
|
||||
|
||||
$breakdown = $this->createCalculator()->withDiscounts($booking, ['total' => 0]);
|
||||
|
||||
self::assertNull($breakdown['totalDiscountDetails']);
|
||||
self::assertNull($breakdown['discountSubtotal']);
|
||||
self::assertSame(0, $breakdown['discountedTotal']);
|
||||
}
|
||||
|
||||
/**
|
||||
* The derived keys must never reach the database: refreshPriceSnapshot() freezes exactly
|
||||
* what computeCurrent() returns.
|
||||
@@ -112,6 +170,8 @@ class AccommodationBookingBreakdownCalculatorTest extends TestCase
|
||||
$breakdown = $this->createCalculator()->computeCurrent($booking);
|
||||
|
||||
self::assertArrayNotHasKey('discounts', $breakdown);
|
||||
self::assertArrayNotHasKey('discountSubtotal', $breakdown);
|
||||
self::assertArrayNotHasKey('totalDiscountDetails', $breakdown);
|
||||
self::assertArrayNotHasKey('discountedTotal', $breakdown);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user