mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 10:58:14 +00:00
fix: ( bookings ) report the full amount paid after a reschedule
This commit is contained in:
@@ -40,6 +40,8 @@ interface PassengerVoucherData {
|
||||
// payment.amountMinor straight from the API) and must NOT be divided by 100 — as
|
||||
// opposed to the normal case where fareMinor is genuine minor units (cents).
|
||||
fareIsMajorUnits?: boolean;
|
||||
/** Itemises the total when a booking was paid more than once (e.g. after a reschedule). */
|
||||
paymentLines?: Array<{ label: string; amountMinor: number; currency: string; status: string; settled: boolean }>;
|
||||
}
|
||||
|
||||
// ─── palette ───────────────────────────────────────────────────────────────
|
||||
@@ -365,6 +367,43 @@ function drawFareSummary(doc: jsPDF, fareMinor: number, currency: string, y: num
|
||||
|
||||
return y + cardH + 6;
|
||||
}
|
||||
function drawPaymentBreakdown(
|
||||
doc: jsPDF,
|
||||
lines: Array<{ label: string; amountMinor: number; currency: string; status: string; settled: boolean }>,
|
||||
y: number,
|
||||
margin: number,
|
||||
pageWidth: number,
|
||||
): number {
|
||||
if (lines.length < 2) return y;
|
||||
|
||||
const rowH = 6;
|
||||
const padX = 7;
|
||||
const headerH = 7;
|
||||
const cardH = headerH + lines.length * rowH + 3;
|
||||
|
||||
doc.setDrawColor(...HAIRLINE);
|
||||
doc.setLineWidth(0.2);
|
||||
doc.roundedRect(margin, y, pageWidth - margin * 2, cardH, 3, 3, 'S');
|
||||
|
||||
doc.setFontSize(7.5); doc.setFont('helvetica', 'bold'); doc.setTextColor(...MUTED);
|
||||
doc.text('PAYMENT BREAKDOWN', margin + padX, y + 5, { charSpace: 0.2 });
|
||||
|
||||
lines.forEach((line, i) => {
|
||||
const rowY = y + headerH + i * rowH;
|
||||
doc.setFontSize(8.5); doc.setFont('helvetica', 'normal'); doc.setTextColor(...BODY);
|
||||
// No status suffix: the caller passes settled lines only, so every row here is paid.
|
||||
doc.text(line.label, margin + padX, rowY + 4);
|
||||
doc.setFont('helvetica', 'bold'); doc.setTextColor(...INK);
|
||||
doc.text(
|
||||
`${line.currency} ${(line.amountMinor / 100).toFixed(2)}`,
|
||||
pageWidth - margin - padX,
|
||||
rowY + 4,
|
||||
{ align: 'right' },
|
||||
);
|
||||
});
|
||||
|
||||
return y + cardH + 6;
|
||||
}
|
||||
|
||||
// ─── instructions ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -425,6 +464,7 @@ async function drawPassengerVoucherPage(doc: jsPDF, data: PassengerVoucherData):
|
||||
|
||||
y = drawPassengerDetails(doc, data, y, margin, pageW);
|
||||
y = drawFareSummary(doc, data.fareMinor, data.currency, y, margin, pageW, data.fareIsMajorUnits);
|
||||
y = drawPaymentBreakdown(doc, data.paymentLines ?? [], y, margin, pageW);
|
||||
y = drawInstructions(doc, y, margin, pageW);
|
||||
drawFooter(doc, data.createdAt, y);
|
||||
}
|
||||
@@ -474,15 +514,32 @@ interface VoucherData {
|
||||
tickets?: Array<{ passengerName?: string; leg?: number; barcodePayload?: string }>;
|
||||
// The actual settled amount/currency for this booking's payment — preferred over the
|
||||
// ETB booking total once available, since it reflects what was really charged.
|
||||
payment?: { amountMinor?: number; currency?: string };
|
||||
payment?: {
|
||||
amountMinor?: number;
|
||||
currency?: string;
|
||||
/** Every line collected for this booking — see the API's payment-breakdown.util.ts. */
|
||||
breakdown?: {
|
||||
lines: Array<{ label: string; amountMinor: number; currency: string; status: string; settled: boolean }>;
|
||||
totalPaidMinor: number | null;
|
||||
totalPaidCurrency: string | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const generateVoucherPDF = async (booking: VoucherData): Promise<void> => {
|
||||
// Currency always comes straight from the booking/payment data, never hardcoded — the
|
||||
// settled payment currency when a payment has settled, otherwise the booking's own
|
||||
// display currency (falling back to the internal ETB currency field).
|
||||
const settledAmountMinor = booking.payment?.amountMinor;
|
||||
const settledCurrency = booking.payment?.currency;
|
||||
// `payment.amountMinor` is the ORIGINAL intent only — it never moves when a booking is
|
||||
// rescheduled, so a voucher built from it reports the pre-reschedule amount forever (a 51.85
|
||||
// booking moved to a 1752.34 journey still printed 51.85). `payment.breakdown` counts every
|
||||
// line collected, including the reschedule fee and fare difference. It arrives in true minor
|
||||
// units, so it is divided down to the major-unit basis the rest of this function expects.
|
||||
const breakdown = booking.payment?.breakdown;
|
||||
const settledAmountMinor =
|
||||
breakdown?.totalPaidMinor != null ? breakdown.totalPaidMinor / 100 : booking.payment?.amountMinor;
|
||||
const settledCurrency =
|
||||
(breakdown?.totalPaidMinor != null ? breakdown.totalPaidCurrency : booking.payment?.currency) ?? undefined;
|
||||
const useSettledAmount = settledAmountMinor != null && !!settledCurrency;
|
||||
// Prefer displayCurrency (passenger's home currency) over the internal ETB currency field.
|
||||
const voucherCurrency = useSettledAmount ? settledCurrency! : (booking.displayCurrency || booking.currency || 'ETB');
|
||||
@@ -541,7 +598,10 @@ export const generateVoucherPDF = async (booking: VoucherData): Promise<void> =>
|
||||
|
||||
// This passenger's own fare, scaled onto the same currency/amount basis as the rest of
|
||||
// the voucher — not the booking's overall total, and not an equal share of it.
|
||||
const passengerFareMinor = Math.round(p.fareMinor * fareScaleFactor);
|
||||
const scaledFare = p.fareMinor * fareScaleFactor;
|
||||
const passengerFareMinor = useSettledAmount
|
||||
? Math.round(scaledFare * 100) / 100
|
||||
: Math.round(scaledFare);
|
||||
|
||||
await generatePassengerVoucherPDF({
|
||||
bookingRef: booking.bookingRef,
|
||||
@@ -560,6 +620,7 @@ export const generateVoucherPDF = async (booking: VoucherData): Promise<void> =>
|
||||
fareMinor: passengerFareMinor,
|
||||
currency: voucherCurrency,
|
||||
fareIsMajorUnits: useSettledAmount,
|
||||
paymentLines: grouped.size === 1 ? breakdown?.lines.filter((l) => l.settled) : undefined,
|
||||
createdAt: booking.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user