möglich = inquiry validation successful
* - erfolgt = booking creation successful
*
* Error responses return Notification objects instead (typ="HINWEIS").
*/
class BookingResponse
{
/**
* @param string $status Booking status (möglich|erfolgt)
* @param int|null $bookingNumber Booking number (BPN XML: vorgang)
* @param array $priceItems Individual price items from response
* @param float|null $totalPrice Total price (gesamtpreis)
* @param PaymentTerms|null $paymentTerms Payment terms (anzahlung/restzahlung)
*/
public function __construct(
public readonly string $status,
public readonly ?int $bookingNumber = null,
public readonly array $priceItems = [],
public readonly ?float $totalPrice = null,
public readonly ?PaymentTerms $paymentTerms = null,
public readonly ?string $message = null,
) {
}
/**
* Returns true if the inquiry validation was successful.
*/
public function isInquiryValid(): bool
{
return 'möglich' === $this->status;
}
/**
* Returns true if the booking was successfully created.
*/
public function isBookingSuccessful(): bool
{
return 'erfolgt' === $this->status;
}
/**
* Gets voucher discounts from price items (AKTION and KULANZGUTSCHEIN types).
*
* Promotional vouchers appear as negative price items with art="AKTION".
* Goodwill vouchers appear as negative price items with art="KULANZGUTSCHEIN".
*
* @return array Price items representing voucher discounts
*/
public function getVoucherDiscountsFromPrices(): array
{
return array_filter(
$this->priceItems,
fn (PriceItem $item) => \in_array($item->type, ['AKTION', 'KULANZGUTSCHEIN'], true)
);
}
/**
* Gets total discount from price-based vouchers (promotional and goodwill).
*
* Returns the absolute sum of negative price items with AKTION or KULANZGUTSCHEIN types.
*/
public function getVoucherDiscountFromPrices(): float
{
$discount = 0.0;
foreach ($this->getVoucherDiscountsFromPrices() as $item) {
// Price items for vouchers are negative, so we take absolute value
$discount += abs($item->totalPrice);
}
return round($discount, 2);
}
/**
* Gets total discount from purchase vouchers in payment terms.
*/
public function getPurchaseVoucherDiscount(): float
{
return $this->paymentTerms?->getPurchaseVoucherDiscount() ?? 0.0;
}
/**
* Gets total voucher discount from all sources.
*
* Combines discounts from:
* - Price items (art="AKTION" for promotional, art="KULANZGUTSCHEIN" for goodwill)
* - Payment terms (kaufgutschein elements for purchase vouchers)
*/
public function getTotalVoucherDiscount(): float
{
return round(
$this->getVoucherDiscountFromPrices() + $this->getPurchaseVoucherDiscount(),
2
);
}
/**
* Gets API-applied discounts that cannot be predicted by the local price calculator.
*
* The BusProNet API may apply automatic discounts based on business rules that
* are not known to the local application, such as:
* - ERM/GRU: Gruppenrabatt (group discount for large bookings)
* - Other ERM subtypes: Various automatic discounts
*
* These discounts appear as negative price items with type "ERM" and must be
* accounted for when comparing the API total price against the locally calculated price.
*
* Important: Only ERM type discounts are considered API-applied. Other negative price
* items (like BEF transportation discounts) are already handled by the local price
* calculator and should not be subtracted again.
*
* Note: Voucher discounts (AKTION, KULANZGUTSCHEIN) are handled separately via
* getVoucherDiscountFromPrices() as they are user-initiated, not automatic.
*
* @return array Price items representing API-applied automatic discounts
*/
public function getApiAppliedDiscounts(): array
{
return array_filter(
$this->priceItems,
fn (PriceItem $item) => $item->totalPrice < 0 && 'ERM' === $item->type
);
}
/**
* Gets total discount from API-applied automatic discounts.
*
* Returns the absolute sum of negative price items that represent automatic
* discounts applied by the API (excluding voucher discounts which are handled separately).
*/
public function getApiAppliedDiscountTotal(): float
{
$discount = 0.0;
foreach ($this->getApiAppliedDiscounts() as $item) {
$discount += abs($item->totalPrice);
}
return round($discount, 2);
}
}