feat: replace custom discount with absolute value in favor of percentage

This commit is contained in:
fromme committed 2026-09-08 17:11:00 +02:00
1 parent d6c2cfaf1c
commit f8ff4b82a3
15 files changed
+105 -50

No files matched your search

@@ -91,6 +91,7 @@ class EditController extends AbstractController
'current_board_service' => $currentBoardService,
'current_additional_services' => $currentAdditionalServices,
'assignable_managers' => $assignableManagers,
'currency' => $booking->getPricingCurrency() ?? $accommodation?->getCurrency() ?? 'EUR',
]);
$form->handleRequest($request);
+8 -7
View File
@@ -156,13 +156,14 @@ class AccommodationBooking implements BlameableEntityInterface, TimestampableEnt
#[Assert\Range(min: 1, max: 100)]
private ?int $additionalServicesDiscount = null;
/** Absolute discount on the total, in minor units. Applied after the three section discounts. */
#[ORM\Column(nullable: true)]
#[Assert\Range(min: 1, max: 100)]
private ?int $totalDiscount = null;
#[Assert\Positive]
private ?int $totalDiscountAmount = null;
#[ORM\Column(length: 255, nullable: true)]
#[Assert\Expression(
'null === this.getTotalDiscount() or (null !== value and "" !== value)',
'null === this.getTotalDiscountAmount() or (null !== value and "" !== value)',
message: 'Bitte eine Bezeichnung für den Rabatt angeben.',
)]
private ?string $totalDiscountLabel = null;
@@ -649,14 +650,14 @@ class AccommodationBooking implements BlameableEntityInterface, TimestampableEnt
return $this;
}
public function getTotalDiscount(): ?int
public function getTotalDiscountAmount(): ?int
{
return $this->totalDiscount;
return $this->totalDiscountAmount;
}
public function setTotalDiscount(?int $totalDiscount): self
public function setTotalDiscountAmount(?int $totalDiscountAmount): self
{
$this->totalDiscount = $totalDiscount;
$this->totalDiscountAmount = $totalDiscountAmount;
return $this;
}
@@ -14,11 +14,13 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Positive;
use Symfony\Component\Validator\Constraints\Range;
/** @extends AbstractType<AccommodationBooking> */
@@ -158,14 +160,17 @@ class AccommodationBookingType extends AbstractType
new Range(min: 1, max: 100),
],
])
// Applies to the total after the three section discounts. The label is not marked
// required here — a percentage may legitimately be absent — but the entity refuses
// a percentage without one, so no unnamed discount can reach the customer.
->add('totalDiscount', IntegerType::class, [
'label' => 'Rabatt Gesamtpreis (%)',
// A fixed sum, not a percentage, applied to the total after the three section
// discounts. The label is not marked required here — an amount may legitimately be
// absent — but the entity refuses an amount without one, so no unnamed discount can
// reach the customer.
->add('totalDiscountAmount', MoneyType::class, [
'label' => 'Rabatt Gesamtpreis',
'currency' => $options['currency'],
'divisor' => 100,
'required' => false,
'constraints' => [
new Range(min: 1, max: 100),
new Positive(),
],
])
->add('totalDiscountLabel', TextType::class, [
@@ -201,6 +206,7 @@ class AccommodationBookingType extends AbstractType
'current_board_service' => null,
'current_additional_services' => [],
'assignable_managers' => [],
'currency' => 'EUR',
]);
$resolver->setAllowedTypes('max_adolescent_age', 'int');
$resolver->setAllowedTypes('board_services', 'array');
@@ -208,5 +214,6 @@ class AccommodationBookingType extends AbstractType
$resolver->setAllowedTypes('current_board_service', ['null', BoardService::class]);
$resolver->setAllowedTypes('current_additional_services', 'array');
$resolver->setAllowedTypes('assignable_managers', 'array');
$resolver->setAllowedTypes('currency', 'string');
}
}
@@ -73,9 +73,9 @@ final readonly class AccommodationBookingApiResponse
#[Groups(['api:single'])]
public ?int $additionalServicesDiscount;
/** Applies to the total after the three section discounts above, not to the gross total. */
/** An absolute amount in minor units, subtracted after the three section discounts above. */
#[Groups(['api:single'])]
public ?int $totalDiscount;
public ?int $totalDiscountAmount;
#[Groups(['api:single'])]
public ?string $totalDiscountLabel;
@@ -122,7 +122,7 @@ final readonly class AccommodationBookingApiResponse
$this->accommodationDiscount = $booking->getAccommodationDiscount();
$this->boardServiceDiscount = $booking->getBoardServiceDiscount();
$this->additionalServicesDiscount = $booking->getAdditionalServicesDiscount();
$this->totalDiscount = $booking->getTotalDiscount();
$this->totalDiscountAmount = $booking->getTotalDiscountAmount();
$this->totalDiscountLabel = $booking->getTotalDiscountLabel();
$this->totalPrice = $booking->getTotalPrice();
$this->pricingCurrency = $booking->getPricingCurrency();
@@ -43,7 +43,8 @@ class AccommodationBookingBreakdownCalculator
public function withDiscounts(AccommodationBooking $booking, array $breakdown): array
{
// The labels carry their own "Rabatt" prefix so that every row renders as plain
// {label} {percent} %, including the freely named discount on the total below.
// {label} {percent} %, while the freely named discount on the total below is a fixed
// amount and renders as its label alone.
$buckets = [
['Rabatt Unterkunft', $booking->getAccommodationDiscount(), (int) ($breakdown['basePrice'] ?? 0) + (int) ($breakdown['additionalPersonsPrice'] ?? 0)],
['Rabatt Verpflegung', $booking->getBoardServiceDiscount(), (int) ($breakdown['boardPrice'] ?? 0)],
@@ -70,15 +71,15 @@ class AccommodationBookingBreakdownCalculator
// computing the new total, where the stored one is still the outdated value.
$subtotal = (int) ($breakdown['total'] ?? 0) - $discountSum;
// Compounds on the already-reduced subtotal rather than on the gross total: the discount
// is granted on what the customer would otherwise pay, not on a figure no longer asked.
$totalDiscountAmount = self::discountAmount($subtotal, $booking->getTotalDiscount());
// A fixed sum rather than a percentage, subtracted after the section discounts. Clamped to
// what is left of the subtotal so that a discount larger than the price cannot produce a
// negative total, the same guard BookingSummaryAssembler applies to vouchers.
$totalDiscountAmount = min($booking->getTotalDiscountAmount() ?? 0, max(0, $subtotal));
$breakdown['discounts'] = $discounts;
$breakdown['totalDiscountDetails'] = 0 !== $totalDiscountAmount
? [
'label' => (string) $booking->getTotalDiscountLabel(),
'percent' => $booking->getTotalDiscount(),
'amount' => $totalDiscountAmount,
]
: null;