fix: streamlined discount display in all breakdowns
This commit is contained in:
@@ -25,6 +25,7 @@ use App\Service\BookingEditPreFlightChecker;
|
||||
use App\Service\BookingEditSubmitter;
|
||||
use App\Service\BookingSessionManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -816,6 +817,7 @@ final class TestableIndexController extends IndexController
|
||||
$editContextFactory,
|
||||
$preFlightChecker,
|
||||
$formSubmitter,
|
||||
new NullLogger(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use App\Security\Crypt;
|
||||
use Carbon\CarbonImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
@@ -329,6 +330,7 @@ final class TestableParticipantController extends ParticipantController
|
||||
$bookingSessionService,
|
||||
$prepopulationService,
|
||||
$participantFormSupportService,
|
||||
new NullLogger(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
@@ -22,7 +23,99 @@ class AccommodationBookingBreakdownCalculatorTest extends TestCase
|
||||
$priceRepository = $this->createMock(AccommodationPriceRepository::class);
|
||||
$priceRepository->expects(self::never())->method('findByHotelCodeAndDateRange');
|
||||
|
||||
$calculator = new AccommodationBookingBreakdownCalculator(
|
||||
$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 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,
|
||||
@@ -31,9 +124,7 @@ class AccommodationBookingBreakdownCalculatorTest extends TestCase
|
||||
'undersubscription40Eur' => 0,
|
||||
'undersubscription40Chf' => 0,
|
||||
]),
|
||||
$priceRepository,
|
||||
$priceRepository ?? $this->createMock(AccommodationPriceRepository::class),
|
||||
);
|
||||
|
||||
self::assertSame($snapshot, $calculator->compute($booking));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,21 +522,16 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
$service->sendBookingConfirmedCustomerEmail($booking);
|
||||
}
|
||||
|
||||
public function testRefreshPriceSnapshotStoresDiscountedFinalTotal(): void
|
||||
/**
|
||||
* The discount arithmetic itself lives in — and is tested with —
|
||||
* AccommodationBookingBreakdownCalculator; what matters here is that the raw breakdown is
|
||||
* frozen and the stored total is the calculator's discounted one.
|
||||
*/
|
||||
public function testRefreshPriceSnapshotStoresTheRawBreakdownAndTheDiscountedTotal(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setAccommodationDiscount(10);
|
||||
$booking->setBoardServiceDiscount(20);
|
||||
$booking->setAdditionalServicesDiscount(50);
|
||||
|
||||
$breakdown = [
|
||||
'total' => 12345,
|
||||
'currency' => 'CHF',
|
||||
'basePrice' => 8000,
|
||||
'additionalPersonsPrice' => 2000,
|
||||
'boardPrice' => 1000,
|
||||
'servicesPrice' => 500,
|
||||
];
|
||||
$breakdown = ['total' => 12345, 'currency' => 'CHF'];
|
||||
|
||||
$breakdownCalculator = $this->createMock(AccommodationBookingBreakdownCalculator::class);
|
||||
$breakdownCalculator
|
||||
@@ -544,13 +539,16 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
->method('computeCurrent')
|
||||
->with($booking)
|
||||
->willReturn($breakdown);
|
||||
$breakdownCalculator
|
||||
->method('withDiscounts')
|
||||
->with($booking, $breakdown)
|
||||
->willReturn($breakdown + ['discounts' => [], 'discountedTotal' => 10895]);
|
||||
|
||||
$service = $this->createServiceWithAccommodation(breakdownCalculator: $breakdownCalculator);
|
||||
$service->refreshPriceSnapshot($booking);
|
||||
|
||||
// accommodation: (8000+2000)*10% = 1000, board: 1000*20% = 200, services: 500*50% = 250
|
||||
self::assertSame($breakdown, $booking->getPriceBreakdown());
|
||||
self::assertSame(12345 - 1000 - 200 - 250, $booking->getTotalPrice());
|
||||
self::assertSame($breakdown, $booking->getPriceBreakdown(), 'the derived keys must not be persisted');
|
||||
self::assertSame(10895, $booking->getTotalPrice());
|
||||
self::assertSame('CHF', $booking->getPricingCurrency());
|
||||
self::assertSame(1, $booking->getPricingVersion());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user