mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
fix(bookings): apply promo discount to client-subtotal totals in guest/authenticated booking
createGuestBooking (ONE_WAY) and createGuestRoundTripBooking overwrote the authoritative discounted total with an undiscounted client-provided subtotal (per-seat sum or reviewedTotalMinor) whenever one was present, silently dropping a valid promos discount at booking-creation time, even though the same promo was correctly applied in the fare-breakdown/quote step. Only the no-client-subtotal fallback branch subtracted discountMinor. Mirrors the existing usedClientSubtotal fix already present in BookingsService.createOneWayBooking (H-13): subtract discountMinor from the client subtotal before storing it, in ETB and display currency. TRANSIT and ROUND_TRIP_TRANSIT were unaffected since neither overrides with a client subtotal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -294,8 +294,12 @@ export class GuestBookingService {
|
||||
|
||||
let displayTotalMinor: number;
|
||||
let resolvedTotalMinor: number;
|
||||
// True when the total came from a client-summed subtotal (per-seat sum or reviewedTotalMinor),
|
||||
// which the portal computes UNDISCOUNTED — the promo must still be applied to it (H-13).
|
||||
let usedClientSubtotal = false;
|
||||
if (allFaresProvided && !isPackageOneway) {
|
||||
displayTotalMinor = passengersWithFares.reduce((sum, p) => sum + p.fareMinor, 0);
|
||||
usedClientSubtotal = true;
|
||||
} else if (isPackageOneway && dto.reviewedTotalMinor != null) {
|
||||
displayTotalMinor = dto.reviewedTotalMinor;
|
||||
if (seatedPassengers.length > 0) {
|
||||
@@ -306,6 +310,7 @@ export class GuestBookingService {
|
||||
}
|
||||
} else if (dto.reviewedTotalMinor != null && dto.reviewedTotalMinor > 0) {
|
||||
displayTotalMinor = dto.reviewedTotalMinor;
|
||||
usedClientSubtotal = true;
|
||||
} else {
|
||||
// fare engine returns ETB — convert forward to display currency
|
||||
const etbTotal = Math.max(0, totalBaseFareMinor - discountMinor);
|
||||
@@ -313,6 +318,18 @@ export class GuestBookingService {
|
||||
? await this.currencyService.convertAmount(etbTotal, Currency.ETB, displayCurrency)
|
||||
: etbTotal;
|
||||
}
|
||||
|
||||
// H-13: the portal sums UNDISCOUNTED per-passenger fares into a client subtotal, silently
|
||||
// dropping the promo the fare engine recognized. Apply the authoritative discount now so the
|
||||
// customer is charged the discounted price. The non-client-subtotal branch above already
|
||||
// nets the discount out of etbTotal, so it's excluded here to avoid double-subtracting.
|
||||
if (usedClientSubtotal && discountMinor > 0) {
|
||||
const discountDisplayMinor = displayCurrency !== Currency.ETB
|
||||
? await this.currencyService.convertAmount(discountMinor, Currency.ETB, displayCurrency)
|
||||
: discountMinor;
|
||||
displayTotalMinor = Math.max(0, displayTotalMinor - discountDisplayMinor);
|
||||
}
|
||||
|
||||
resolvedTotalMinor = displayCurrency !== Currency.ETB
|
||||
? await this.currencyService.convertAmount(displayTotalMinor, displayCurrency, Currency.ETB)
|
||||
: displayTotalMinor;
|
||||
@@ -834,17 +851,16 @@ export class GuestBookingService {
|
||||
const allRTFaresProvided = rtSeatedPassengers.length > 0 &&
|
||||
rtSeatedPassengers.every(p => p.seatFareMinor != null && p.returnSeatFareMinor != null);
|
||||
|
||||
// True when the total came from a client-summed subtotal (per-seat sum or reviewedTotalMinor),
|
||||
// which the portal computes UNDISCOUNTED — the promo must still be applied to it (H-13).
|
||||
let usedClientSubtotal = false;
|
||||
|
||||
if (allRTFaresProvided && !isPackageRoundTrip) {
|
||||
// Server-computed sum is authoritative — prevents race-condition under-count.
|
||||
displayTotalMinor = passengersWithFares.reduce((sum, p) => sum + p.outboundFareMinor + p.returnFareMinor, 0);
|
||||
totalMinor = displayCurrency !== Currency.ETB
|
||||
? await this.currencyService.convertAmount(displayTotalMinor, displayCurrency, Currency.ETB)
|
||||
: displayTotalMinor;
|
||||
usedClientSubtotal = true;
|
||||
} else if (isPackageRoundTrip && dto.reviewedTotalMinor != null) {
|
||||
displayTotalMinor = dto.reviewedTotalMinor;
|
||||
totalMinor = displayCurrency !== Currency.ETB
|
||||
? await this.currencyService.convertAmount(displayTotalMinor, displayCurrency, Currency.ETB)
|
||||
: displayTotalMinor;
|
||||
const seatedCount = passengersData.filter(p => p.seatId).length;
|
||||
if (seatedCount > 0) {
|
||||
const perSeatPerLeg = Math.round(dto.reviewedTotalMinor / (seatedCount * 2));
|
||||
@@ -855,11 +871,24 @@ export class GuestBookingService {
|
||||
}
|
||||
} else if (dto.reviewedTotalMinor != null && dto.reviewedTotalMinor > 0) {
|
||||
displayTotalMinor = dto.reviewedTotalMinor;
|
||||
totalMinor = displayCurrency !== Currency.ETB
|
||||
? await this.currencyService.convertAmount(displayTotalMinor, displayCurrency, Currency.ETB)
|
||||
: displayTotalMinor;
|
||||
usedClientSubtotal = true;
|
||||
}
|
||||
|
||||
// H-13: the portal sums UNDISCOUNTED per-passenger fares into a client subtotal, silently
|
||||
// dropping the promo the fare engine recognized. Apply the authoritative discount now so the
|
||||
// customer is charged the discounted price. The no-override case above already starts from a
|
||||
// discounted displayTotalMinor, so it's excluded here to avoid double-subtracting.
|
||||
if (usedClientSubtotal && discountMinor > 0) {
|
||||
const discountDisplayMinor = displayCurrency !== Currency.ETB
|
||||
? await this.currencyService.convertAmount(discountMinor, Currency.ETB, displayCurrency)
|
||||
: discountMinor;
|
||||
displayTotalMinor = Math.max(0, displayTotalMinor - discountDisplayMinor);
|
||||
}
|
||||
|
||||
totalMinor = displayCurrency !== Currency.ETB
|
||||
? await this.currencyService.convertAmount(displayTotalMinor, displayCurrency, Currency.ETB)
|
||||
: displayTotalMinor;
|
||||
|
||||
// C-1 guard: never charge less than the server-recomputed authoritative round-trip fare.
|
||||
this.assertTotalNotUnderAuthoritative(totalMinor, authoritativeTotalMinor, 'createGuestRoundTripBooking');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user