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); } }