feat: finalize voucher handling and discount display in summary and overview
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
/**
|
||||
* Represents a voucher discount accepted by the BPN API.
|
||||
*
|
||||
* This is the base class for all voucher types (promotional, purchase, goodwill).
|
||||
* Instances are created from API responses and stored in BookingDto for display.
|
||||
*/
|
||||
class AcceptedVoucherDto
|
||||
{
|
||||
public const TYPE_PROMOTIONAL = 'promotional';
|
||||
public const TYPE_PURCHASE = 'purchase';
|
||||
public const TYPE_GOODWILL = 'goodwill';
|
||||
|
||||
public function __construct(
|
||||
public readonly string $type,
|
||||
public readonly string $code,
|
||||
public readonly float $amount,
|
||||
public readonly ?string $description = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isPromotional(): bool
|
||||
{
|
||||
return self::TYPE_PROMOTIONAL === $this->type;
|
||||
}
|
||||
|
||||
public function isPurchase(): bool
|
||||
{
|
||||
return self::TYPE_PURCHASE === $this->type;
|
||||
}
|
||||
|
||||
public function isGoodwill(): bool
|
||||
{
|
||||
return self::TYPE_GOODWILL === $this->type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
/**
|
||||
* Collection of accepted vouchers from BPN API response.
|
||||
*
|
||||
* Stores voucher discounts parsed from booking inquiry/creation responses.
|
||||
* Used to display voucher discounts in booking summaries and for price validation.
|
||||
*/
|
||||
class AcceptedVouchersDto
|
||||
{
|
||||
/** @var array<AcceptedVoucherDto> */
|
||||
private array $vouchers = [];
|
||||
|
||||
private float $totalDiscount = 0.0;
|
||||
|
||||
public function addVoucher(AcceptedVoucherDto $voucher): void
|
||||
{
|
||||
$this->vouchers[] = $voucher;
|
||||
$this->totalDiscount = round($this->totalDiscount + $voucher->amount, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<AcceptedVoucherDto>
|
||||
*/
|
||||
public function getVouchers(): array
|
||||
{
|
||||
return $this->vouchers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<AcceptedVoucherDto>
|
||||
*/
|
||||
public function getPromotionalVouchers(): array
|
||||
{
|
||||
return array_filter($this->vouchers, fn (AcceptedVoucherDto $v) => $v->isPromotional());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<AcceptedVoucherDto>
|
||||
*/
|
||||
public function getPurchaseVouchers(): array
|
||||
{
|
||||
return array_filter($this->vouchers, fn (AcceptedVoucherDto $v) => $v->isPurchase());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<AcceptedVoucherDto>
|
||||
*/
|
||||
public function getGoodwillVouchers(): array
|
||||
{
|
||||
return array_filter($this->vouchers, fn (AcceptedVoucherDto $v) => $v->isGoodwill());
|
||||
}
|
||||
|
||||
public function getTotalDiscount(): float
|
||||
{
|
||||
return $this->totalDiscount;
|
||||
}
|
||||
|
||||
public function hasVouchers(): bool
|
||||
{
|
||||
return count($this->vouchers) > 0;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->vouchers);
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,65 @@ class BookingDto
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds AcceptedVouchers from validated participant voucher data.
|
||||
*
|
||||
* Computes voucher discounts from validated vouchers stored on participants.
|
||||
* This allows displaying voucher savings as soon as vouchers are validated
|
||||
* in Step 2, rather than waiting for Step 3 API confirmation.
|
||||
*
|
||||
* For promo vouchers with percentage discounts, we need the participant price
|
||||
* to calculate the actual discount amount.
|
||||
*
|
||||
* @param array<int, float>|null $participantPrices Prices per participant index for percentage calculation
|
||||
*/
|
||||
public function getAcceptedVouchers(?array $participantPrices = null): ?AcceptedVouchersDto
|
||||
{
|
||||
$acceptedVouchers = new AcceptedVouchersDto();
|
||||
$processedPromoCodes = [];
|
||||
|
||||
foreach ($this->participants as $index => $participant) {
|
||||
// Process purchase voucher
|
||||
if (null !== $participant->validatedPurchaseVoucher) {
|
||||
$voucher = $participant->validatedPurchaseVoucher;
|
||||
$acceptedVouchers->addVoucher(new AcceptedVoucherDto(
|
||||
type: $voucher->isGoodwill() ? AcceptedVoucherDto::TYPE_GOODWILL : AcceptedVoucherDto::TYPE_PURCHASE,
|
||||
code: $voucher->voucherNumber,
|
||||
amount: $voucher->remainingBalance,
|
||||
description: $voucher->isGoodwill() ? 'Kulanzgutschein' : 'Kaufgutschein',
|
||||
));
|
||||
}
|
||||
|
||||
// Process promo voucher
|
||||
if (null !== $participant->validatedPromoVoucher) {
|
||||
$voucher = $participant->validatedPromoVoucher;
|
||||
|
||||
// For per-booking vouchers, only count once
|
||||
if ($voucher->isPerBooking()) {
|
||||
if (isset($processedPromoCodes[$voucher->code])) {
|
||||
continue;
|
||||
}
|
||||
$processedPromoCodes[$voucher->code] = true;
|
||||
}
|
||||
|
||||
// Calculate discount amount
|
||||
$discountAmount = $voucher->discountAmount;
|
||||
if ($voucher->discountPercentage > 0 && null !== $participantPrices && isset($participantPrices[$index])) {
|
||||
$discountAmount = round($participantPrices[$index] * ($voucher->discountPercentage / 100), 2);
|
||||
}
|
||||
|
||||
$acceptedVouchers->addVoucher(new AcceptedVoucherDto(
|
||||
type: AcceptedVoucherDto::TYPE_PROMOTIONAL,
|
||||
code: $voucher->code,
|
||||
amount: $discountAmount,
|
||||
description: $voucher->description,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $acceptedVouchers->hasVouchers() ? $acceptedVouchers : null;
|
||||
}
|
||||
|
||||
public function getMode(): string
|
||||
{
|
||||
return null !== $this->booking ? self::MODE_EDIT : self::MODE_CREATE;
|
||||
@@ -251,7 +310,7 @@ class BookingDto
|
||||
public function hasGoodwillVouchers(): bool
|
||||
{
|
||||
foreach ($this->participants as $participant) {
|
||||
if (true === $participant->hasGoodwillVoucher) {
|
||||
if ($participant->hasGoodwillVoucher()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ use App\BusProNet\Model\Address;
|
||||
use App\BusProNet\Model\Insurance;
|
||||
use App\BusProNet\Model\PersonalData;
|
||||
use App\BusProNet\Model\Pickup;
|
||||
use App\BusProNet\Model\PromoVoucher;
|
||||
use App\BusProNet\Model\PurchaseVoucher;
|
||||
use App\BusProNet\Model\Service;
|
||||
use App\Validator\Constraints as AppAssert;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
@@ -129,13 +131,11 @@ class ParticipantDto
|
||||
public ?string $purchaseVoucherCode = null;
|
||||
|
||||
/**
|
||||
* Flag indicating if the purchase voucher is a goodwill (Kulanz) voucher.
|
||||
*
|
||||
* Goodwill vouchers are treated as promotional vouchers in BPN XML
|
||||
* (sent as <aktionscode> per participant, not in <gutscheine> collection).
|
||||
* Validated purchase voucher from API.
|
||||
* Set by ParticipantPurchaseVoucherFieldHandler during form processing.
|
||||
* Contains voucher number, remaining balance, and type (Kauf/Kulanz).
|
||||
*/
|
||||
public bool $hasGoodwillVoucher = false;
|
||||
public ?PurchaseVoucher $validatedPurchaseVoucher = null;
|
||||
|
||||
/**
|
||||
* Promo voucher code.
|
||||
@@ -151,6 +151,13 @@ class ParticipantDto
|
||||
)]
|
||||
public ?string $promoVoucherCode = null;
|
||||
|
||||
/**
|
||||
* Validated promo voucher from API.
|
||||
* Set by ParticipantPromoVoucherFieldHandler during form processing.
|
||||
* Contains discount amount/percentage and applicability (per person/booking).
|
||||
*/
|
||||
public ?PromoVoucher $validatedPromoVoucher = null;
|
||||
|
||||
/**
|
||||
* @var array<array{type: string, message: string}> Notification messages for user feedback
|
||||
*/
|
||||
@@ -279,6 +286,18 @@ class ParticipantDto
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this participant has a goodwill (Kulanz) voucher.
|
||||
*
|
||||
* Goodwill vouchers are treated as promotional vouchers in BPN XML
|
||||
* (sent as <aktionscode> per participant, not in <gutscheine> collection).
|
||||
*/
|
||||
public function hasGoodwillVoucher(): bool
|
||||
{
|
||||
return null !== $this->validatedPurchaseVoucher
|
||||
&& $this->validatedPurchaseVoucher->isGoodwill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the participant is a child based on age at current date.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user