feat: split nights off an accommodation booking as separate periods

addresses #869f6z36v
This commit is contained in:
fromme committed 2026-09-30 18:06:20 +02:00
1 parent 972839286c
commit 7001bc0cf2
35 files changed
+2221 -25

No files matched your search

+21 -6
View File
@@ -86,6 +86,8 @@ class GroupsPriceCalculator
$nights,
$dateFrom,
$dateTo,
$dateFrom,
$nights,
$prices,
$boardService?->getPrice(),
array_map(
@@ -104,7 +106,12 @@ class GroupsPriceCalculator
* Same calculation but using frozen snapshot data from an AccommodationBooking entity
* instead of live catalog entities.
*
* @param AccommodationPrice[] $prices all prices overlapping the booking window
* $dateFrom/$dateTo/$nights are the nights being priced, which is the main period of a booking
* with split-off periods. $stayDateFrom and $stayNights describe the whole stay, which the
* includedPax floor and the short-term surcharge are judged on, so that splitting nights off
* never changes either.
*
* @param AccommodationPrice[] $prices all prices overlapping the whole stay
* @param list<array{label: string, price: int, type: string, originalServiceId: int|null}> $additionalServiceSnapshots
*
* @return array{
@@ -131,6 +138,8 @@ class GroupsPriceCalculator
int $nights,
\DateTimeImmutable $dateFrom,
\DateTimeImmutable $dateTo,
\DateTimeImmutable $stayDateFrom,
int $stayNights,
array $prices,
?int $boardServicePricePerPersonNight,
array $additionalServiceSnapshots,
@@ -151,6 +160,8 @@ class GroupsPriceCalculator
$nights,
$dateFrom,
$dateTo,
$stayDateFrom,
$stayNights,
$prices,
$boardServicePricePerPersonNight,
$items,
@@ -186,15 +197,19 @@ class GroupsPriceCalculator
int $nights,
\DateTimeImmutable $dateFrom,
\DateTimeImmutable $dateTo,
\DateTimeImmutable $stayDateFrom,
int $stayNights,
array $prices,
?int $boardServicePricePerPersonNight,
array $additionalItems,
string $currency,
): array {
// Derive effectivePax: children 0–3 don't count, but never drop below includedPax
// Derive effectivePax: children 0–3 don't count, but never drop below includedPax.
// The floor comes from the stay's first night, not the priced range's, so that splitting
// nights off the start cannot swap in another price row's floor.
$firstCandidates = array_values(array_filter(
$prices,
fn (AccommodationPrice $p) => $p->getDateFrom() <= $dateFrom && $p->getDateTo() >= $dateFrom,
fn (AccommodationPrice $p) => $p->getDateFrom() <= $stayDateFrom && $p->getDateTo() >= $stayDateFrom,
));
$firstWinner = $this->priceTimelineBuilder->resolveWinner($firstCandidates);
$includedPaxFloor = $firstWinner?->getIncludedPax() ?? 1;
@@ -239,10 +254,10 @@ class GroupsPriceCalculator
}
}
// Rule 3: short-term surcharge
// Rule 3: short-term surcharge, judged on the whole stay but charged on the priced nights
$shortTermSurcharge = 0;
if (isset(self::SHORT_TERM_FACTORS[$nights])) {
$shortTermSurcharge = (int) round(($basePrice + $additionalPersonsPrice) * self::SHORT_TERM_FACTORS[$nights] / 100);
if (isset(self::SHORT_TERM_FACTORS[$stayNights])) {
$shortTermSurcharge = (int) round(($basePrice + $additionalPersonsPrice) * self::SHORT_TERM_FACTORS[$stayNights] / 100);
}
// Board is only billable — and only triggers the catering surcharge — when it costs