mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
Fix roundtrip in voucher
This commit is contained in:
@@ -1734,6 +1734,7 @@ export class BookingsService {
|
|||||||
where: isUuid ? { id: bookingRefOrId } : { bookingRef: bookingRefOrId },
|
where: isUuid ? { id: bookingRefOrId } : { bookingRef: bookingRefOrId },
|
||||||
include: {
|
include: {
|
||||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||||
|
returnSchedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||||
seats: { include: { seat: { include: { coach: { include: { coachType: { include: { seatClasses: true } } } } } } } },
|
seats: { include: { seat: { include: { coach: { include: { coachType: { include: { seatClasses: true } } } } } } } },
|
||||||
paymentIntent: true, tickets: true,
|
paymentIntent: true, tickets: true,
|
||||||
priceTier: { select: { priceMinor: true } },
|
priceTier: { select: { priceMinor: true } },
|
||||||
@@ -1822,6 +1823,16 @@ export class BookingsService {
|
|||||||
destination: { id: (booking as any).schedule.destinationStation.id, name: (booking as any).schedule.destinationStation.name, code: (booking as any).schedule.destinationStation.code, city: (booking as any).schedule.destinationStation.city },
|
destination: { id: (booking as any).schedule.destinationStation.id, name: (booking as any).schedule.destinationStation.name, code: (booking as any).schedule.destinationStation.code, city: (booking as any).schedule.destinationStation.city },
|
||||||
departureAt: (booking as any).schedule.departureAt, arrivalAt: (booking as any).schedule.arrivalAt,
|
departureAt: (booking as any).schedule.departureAt, arrivalAt: (booking as any).schedule.arrivalAt,
|
||||||
},
|
},
|
||||||
|
returnSchedule: (booking as any).returnSchedule
|
||||||
|
? {
|
||||||
|
id: (booking as any).returnSchedule.id,
|
||||||
|
trainNumber: (booking as any).returnSchedule.train.number,
|
||||||
|
trainName: (booking as any).returnSchedule.train.name,
|
||||||
|
origin: { id: (booking as any).returnSchedule.originStation.id, name: (booking as any).returnSchedule.originStation.name, code: (booking as any).returnSchedule.originStation.code, city: (booking as any).returnSchedule.originStation.city },
|
||||||
|
destination: { id: (booking as any).returnSchedule.destinationStation.id, name: (booking as any).returnSchedule.destinationStation.name, code: (booking as any).returnSchedule.destinationStation.code, city: (booking as any).returnSchedule.destinationStation.city },
|
||||||
|
departureAt: (booking as any).returnSchedule.departureAt, arrivalAt: (booking as any).returnSchedule.arrivalAt,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
passengers: (booking as any).seats?.map((bs: any) => ({
|
passengers: (booking as any).seats?.map((bs: any) => ({
|
||||||
fullName: bs.passengerName,
|
fullName: bs.passengerName,
|
||||||
category: bs.passengerCategory,
|
category: bs.passengerCategory,
|
||||||
@@ -1844,11 +1855,14 @@ export class BookingsService {
|
|||||||
currency: (booking as any).paymentIntent.currency,
|
currency: (booking as any).paymentIntent.currency,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
// One ticket per passenger — matched on the frontend by passengerName, not array
|
// One ticket per passenger per leg (round trips have a separate ticket — and
|
||||||
// position, since tickets are grouped/created independently of the passengers array.
|
// barcode — for the return leg) — matched on the frontend by passengerName +
|
||||||
|
// leg, not array position, since tickets are grouped/created independently of
|
||||||
|
// the passengers array.
|
||||||
tickets: (booking as any).tickets?.map((t: any) => ({
|
tickets: (booking as any).tickets?.map((t: any) => ({
|
||||||
id: t.id,
|
id: t.id,
|
||||||
passengerName: t.passengerName,
|
passengerName: t.passengerName,
|
||||||
|
leg: t.leg ?? 1,
|
||||||
qrPayload: t.qrPayload,
|
qrPayload: t.qrPayload,
|
||||||
barcodePayload: t.barcodePayload,
|
barcodePayload: t.barcodePayload,
|
||||||
status: t.status,
|
status: t.status,
|
||||||
|
|||||||
@@ -39,6 +39,35 @@ const getIconForMethod = (methodType: string) => {
|
|||||||
return Smartphone;
|
return Smartphone;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function SeatDetailsGrid({
|
||||||
|
seat,
|
||||||
|
}: {
|
||||||
|
seat?: { coach?: string; number?: string; seatClass?: string };
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-500 dark:text-gray-400">Coach:</span>
|
||||||
|
<div className="font-mono font-semibold text-gray-900 dark:text-white">
|
||||||
|
{seat?.coach || "N/A"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-500 dark:text-gray-400">Seat Number:</span>
|
||||||
|
<div className="font-semibold text-gray-900 dark:text-white">
|
||||||
|
{seat?.number || "N/A"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<span className="text-gray-500 dark:text-gray-400">Class:</span>
|
||||||
|
<div className="font-medium text-gray-900 dark:text-white">
|
||||||
|
{seat?.seatClass || "N/A"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function BookingDetailContent() {
|
function BookingDetailContent() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -311,18 +340,22 @@ function BookingDetailContent() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// /bookings/:ref returns one row per passenger PER LEG for round trips (leg 1 =
|
// /bookings/:ref returns one row per passenger PER LEG for round trips (leg 1 =
|
||||||
// outbound, leg 2 = return) — /booking/payment's fare breakdown, by contrast, shows one
|
// outbound, leg 2 = return), each carrying that leg's own seat assignment —
|
||||||
// combined row per passenger with an Outbound/Return sub-split. Group leg rows back
|
// /booking/payment's fare breakdown, by contrast, shows one combined row per
|
||||||
// together here so both pages present the same per-passenger total, not a doubled list
|
// passenger with an Outbound/Return sub-split. Group leg rows back together here —
|
||||||
// of half-fare rows.
|
// by identity, not by row — so every consumer (fare breakdown, passenger/seat list)
|
||||||
|
// sees one entry per real passenger with both legs' seats attached, not a doubled
|
||||||
|
// list of half-passenger rows.
|
||||||
const isRoundTripBooking = booking.bookingType === "ROUND_TRIP";
|
const isRoundTripBooking = booking.bookingType === "ROUND_TRIP";
|
||||||
const farePassengers = (() => {
|
const groupedPassengers = (() => {
|
||||||
const rows: any[] = booking.passengers || [];
|
const rows: any[] = booking.passengers || [];
|
||||||
if (!isRoundTripBooking) {
|
if (!isRoundTripBooking) {
|
||||||
return rows.map((p) => ({
|
return rows.map((p) => ({
|
||||||
fullName: p.fullName,
|
fullName: p.fullName,
|
||||||
category: p.category,
|
category: p.category,
|
||||||
fareMinor: p.fareMinor ?? 0,
|
fareMinor: p.fareMinor ?? 0,
|
||||||
|
outboundSeat: p.seat,
|
||||||
|
returnSeat: undefined as any,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
const grouped = new Map<
|
const grouped = new Map<
|
||||||
@@ -332,6 +365,8 @@ function BookingDetailContent() {
|
|||||||
category: string;
|
category: string;
|
||||||
outboundFareMinor: number;
|
outboundFareMinor: number;
|
||||||
returnFareMinor: number;
|
returnFareMinor: number;
|
||||||
|
outboundSeat?: any;
|
||||||
|
returnSeat?: any;
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
rows.forEach((p) => {
|
rows.forEach((p) => {
|
||||||
@@ -341,9 +376,16 @@ function BookingDetailContent() {
|
|||||||
category: p.category,
|
category: p.category,
|
||||||
outboundFareMinor: 0,
|
outboundFareMinor: 0,
|
||||||
returnFareMinor: 0,
|
returnFareMinor: 0,
|
||||||
|
outboundSeat: undefined,
|
||||||
|
returnSeat: undefined,
|
||||||
};
|
};
|
||||||
if (p.leg === 2) entry.returnFareMinor = p.fareMinor ?? 0;
|
if (p.leg === 2) {
|
||||||
else entry.outboundFareMinor = p.fareMinor ?? 0;
|
entry.returnFareMinor = p.fareMinor ?? 0;
|
||||||
|
entry.returnSeat = p.seat;
|
||||||
|
} else {
|
||||||
|
entry.outboundFareMinor = p.fareMinor ?? 0;
|
||||||
|
entry.outboundSeat = p.seat;
|
||||||
|
}
|
||||||
grouped.set(key, entry);
|
grouped.set(key, entry);
|
||||||
});
|
});
|
||||||
return Array.from(grouped.values()).map((p) => ({
|
return Array.from(grouped.values()).map((p) => ({
|
||||||
@@ -352,6 +394,8 @@ function BookingDetailContent() {
|
|||||||
fareMinor: p.outboundFareMinor + p.returnFareMinor,
|
fareMinor: p.outboundFareMinor + p.returnFareMinor,
|
||||||
outboundFareMinor: p.outboundFareMinor,
|
outboundFareMinor: p.outboundFareMinor,
|
||||||
returnFareMinor: p.returnFareMinor,
|
returnFareMinor: p.returnFareMinor,
|
||||||
|
outboundSeat: p.outboundSeat,
|
||||||
|
returnSeat: p.returnSeat,
|
||||||
}));
|
}));
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -374,7 +418,7 @@ function BookingDetailContent() {
|
|||||||
<h3 className="text-sm font-bold text-gray-900 dark:text-gray-100">
|
<h3 className="text-sm font-bold text-gray-900 dark:text-gray-100">
|
||||||
Fare breakdown
|
Fare breakdown
|
||||||
</h3>
|
</h3>
|
||||||
{farePassengers.map((passenger: any, idx: number) => {
|
{groupedPassengers.map((passenger: any, idx: number) => {
|
||||||
const isChildPassenger = passenger.category === "CHILD";
|
const isChildPassenger = passenger.category === "CHILD";
|
||||||
const isFreeChild =
|
const isFreeChild =
|
||||||
isChildPassenger && (passenger.fareMinor ?? 0) === 0;
|
isChildPassenger && (passenger.fareMinor ?? 0) === 0;
|
||||||
@@ -1035,69 +1079,77 @@ function BookingDetailContent() {
|
|||||||
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-2xl p-6 border border-gray-200 dark:border-gray-700">
|
<div className="bg-white dark:bg-gray-800 rounded-2xl p-6 border border-gray-200 dark:border-gray-700">
|
||||||
<h2 className="text-lg font-bold text-gray-900 dark:text-white mb-4">
|
<h2 className="text-lg font-bold text-gray-900 dark:text-white mb-4">
|
||||||
Passenger Details ({booking.passengers?.length || 0})
|
Passenger Details ({groupedPassengers.length})
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{booking.passengers?.map((passenger: any, idx: number) => (
|
{groupedPassengers.map((passenger: any, idx: number) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="border border-gray-200 dark:border-gray-700 rounded-xl p-4"
|
className="border border-gray-200 dark:border-gray-700 rounded-xl p-4 space-y-4"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col md:flex-row md:items-center gap-4">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex-1">
|
<span className="w-6 h-6 bg-primary text-white rounded-full flex items-center justify-center text-xs font-bold">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
{idx + 1}
|
||||||
<span className="w-6 h-6 bg-primary text-white rounded-full flex items-center justify-center text-xs font-bold">
|
</span>
|
||||||
{idx + 1}
|
<h3 className="font-bold text-gray-900 dark:text-white">
|
||||||
</span>
|
{passenger.fullName}
|
||||||
<h3 className="font-bold text-gray-900 dark:text-white">
|
</h3>
|
||||||
{passenger.fullName}
|
<span className="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-full">
|
||||||
</h3>
|
{passenger.category}
|
||||||
<span className="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-full">
|
</span>
|
||||||
{passenger.category}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
||||||
<div>
|
|
||||||
<span className="text-gray-500 dark:text-gray-400">
|
|
||||||
Coach:
|
|
||||||
</span>
|
|
||||||
<div className="font-mono font-semibold text-gray-900 dark:text-white">
|
|
||||||
{passenger.seat?.coach || "N/A"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-gray-500 dark:text-gray-400">
|
|
||||||
Seat Number:
|
|
||||||
</span>
|
|
||||||
<div className="font-semibold text-gray-900 dark:text-white">
|
|
||||||
{passenger.seat?.number || "N/A"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-2">
|
|
||||||
<span className="text-gray-500 dark:text-gray-400">
|
|
||||||
Class:
|
|
||||||
</span>
|
|
||||||
<div className="font-medium text-gray-900 dark:text-white">
|
|
||||||
{passenger.seat?.seatClass || "N/A"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isConfirmed && (
|
|
||||||
<div className="flex-shrink-0">
|
|
||||||
<div className="bg-white p-3 rounded-lg border-2 border-gray-200">
|
|
||||||
<QRCode
|
|
||||||
value={`TICKET:${booking.bookingRef}-${passenger.seat?.id || idx}`}
|
|
||||||
size={80}
|
|
||||||
level="M"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isRoundTripBooking ? (
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
{ legLabel: "Outbound", seat: passenger.outboundSeat },
|
||||||
|
{ legLabel: "Return", seat: passenger.returnSeat },
|
||||||
|
] as const
|
||||||
|
).map(({ legLabel, seat }) => (
|
||||||
|
<div
|
||||||
|
key={legLabel}
|
||||||
|
className="flex items-center gap-4 pt-3 border-t border-gray-100 dark:border-gray-800 first:border-t-0 first:pt-0 md:border-t-0 md:pt-0"
|
||||||
|
>
|
||||||
|
<div className="flex-1">
|
||||||
|
<span className="inline-block mb-2 text-xs font-semibold px-2 py-0.5 rounded-full bg-primary/10 text-primary">
|
||||||
|
{legLabel}
|
||||||
|
</span>
|
||||||
|
<SeatDetailsGrid seat={seat} />
|
||||||
|
</div>
|
||||||
|
{isConfirmed && (
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<div className="bg-white p-3 rounded-lg border-2 border-gray-200">
|
||||||
|
<QRCode
|
||||||
|
value={`TICKET:${booking.bookingRef}-${seat?.id || `${idx}-${legLabel}`}`}
|
||||||
|
size={72}
|
||||||
|
level="M"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<SeatDetailsGrid seat={passenger.outboundSeat} />
|
||||||
|
</div>
|
||||||
|
{isConfirmed && (
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<div className="bg-white p-3 rounded-lg border-2 border-gray-200">
|
||||||
|
<QRCode
|
||||||
|
value={`TICKET:${booking.bookingRef}-${passenger.outboundSeat?.id || idx}`}
|
||||||
|
size={80}
|
||||||
|
level="M"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -419,18 +419,33 @@ export const generatePassengerVoucherPDF = async (data: PassengerVoucherData): P
|
|||||||
|
|
||||||
// ─── legacy combined voucher (kept for backward compat) ──────────────────────
|
// ─── legacy combined voucher (kept for backward compat) ──────────────────────
|
||||||
|
|
||||||
|
interface VoucherSchedule {
|
||||||
|
trainNumber: string;
|
||||||
|
trainName?: string;
|
||||||
|
origin: { name: string; code: string; city: string };
|
||||||
|
destination: { name: string; code: string; city: string };
|
||||||
|
departureAt: string;
|
||||||
|
arrivalAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface VoucherData {
|
interface VoucherData {
|
||||||
bookingRef: string;
|
bookingRef: string;
|
||||||
status: string;
|
status: string;
|
||||||
passengers: Array<{ fullName: string; category: string; seat?: { number: string; coach: string; seatClass: string } }>;
|
// /bookings/:ref returns one row per passenger PER LEG for round trips (leg 1 =
|
||||||
schedule: { trainNumber: string; trainName?: string; origin: { name: string; code: string; city: string }; destination: { name: string; code: string; city: string }; departureAt: string; arrivalAt: string };
|
// outbound, leg 2 = return), each with that leg's own seat — see bookings.service.ts's
|
||||||
|
// getByRef(). dateOfBirth is included purely to disambiguate same-name passengers when
|
||||||
|
// grouping leg rows back into one passenger below.
|
||||||
|
passengers: Array<{ fullName: string; dateOfBirth?: string; category: string; leg?: number; seat?: { number: string; coach: string; seatClass: string } }>;
|
||||||
|
schedule: VoucherSchedule;
|
||||||
|
returnSchedule?: VoucherSchedule | null;
|
||||||
totalMinor: number;
|
totalMinor: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
bookingType: string;
|
bookingType: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
// One ticket per passenger, matched below by passengerName — see bookings.service.ts's
|
// One ticket per passenger per leg (round trips have a separate ticket/barcode for the
|
||||||
// getByRef(). Optional/absent falls back to a client-generated placeholder number.
|
// return leg) — matched below by passengerName + leg. Optional/absent falls back to a
|
||||||
tickets?: Array<{ passengerName?: string; barcodePayload?: string }>;
|
// client-generated placeholder number.
|
||||||
|
tickets?: Array<{ passengerName?: string; leg?: number; barcodePayload?: string }>;
|
||||||
// The actual settled amount/currency for this booking's payment — preferred over the
|
// 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.
|
// ETB booking total once available, since it reflects what was really charged.
|
||||||
payment?: { amountMinor?: number; currency?: string };
|
payment?: { amountMinor?: number; currency?: string };
|
||||||
@@ -447,31 +462,54 @@ export const generateVoucherPDF = async (booking: VoucherData): Promise<void> =>
|
|||||||
const useSettledAmount = settledAmountMinor != null && !!settledCurrency;
|
const useSettledAmount = settledAmountMinor != null && !!settledCurrency;
|
||||||
const voucherCurrency = useSettledAmount ? settledCurrency! : booking.currency;
|
const voucherCurrency = useSettledAmount ? settledCurrency! : booking.currency;
|
||||||
|
|
||||||
|
const isRoundTrip = booking.bookingType === 'ROUND_TRIP' && !!booking.returnSchedule;
|
||||||
|
|
||||||
|
// Group leg rows back into one entry per real passenger — without this, a round trip
|
||||||
|
// produced two half-passenger vouchers (one per leg, each showing only its own leg's
|
||||||
|
// seat) instead of one voucher per passenger covering both legs.
|
||||||
|
const grouped = new Map<
|
||||||
|
string,
|
||||||
|
{ fullName: string; category: string; outboundSeat?: VoucherData['passengers'][number]['seat']; returnSeat?: VoucherData['passengers'][number]['seat'] }
|
||||||
|
>();
|
||||||
|
booking.passengers.forEach((p) => {
|
||||||
|
const key = `${p.fullName}|${p.dateOfBirth}|${p.category}`;
|
||||||
|
const entry = grouped.get(key) || { fullName: p.fullName, category: p.category, outboundSeat: undefined, returnSeat: undefined };
|
||||||
|
if (p.leg === 2) entry.returnSeat = p.seat;
|
||||||
|
else entry.outboundSeat = p.seat;
|
||||||
|
grouped.set(key, entry);
|
||||||
|
});
|
||||||
|
|
||||||
// Separate file per passenger, saved back-to-back with no macrotask (setTimeout) between
|
// Separate file per passenger, saved back-to-back with no macrotask (setTimeout) between
|
||||||
// them — a setTimeout delay here would push later saves outside the click's synchronous
|
// them — a setTimeout delay here would push later saves outside the click's synchronous
|
||||||
// user-activation window and risk iOS Safari silently blocking them. The awaited work
|
// user-activation window and risk iOS Safari silently blocking them. The awaited work
|
||||||
// inside generatePassengerVoucherPDF is itself just microtasks (cached logo, QR encode),
|
// inside generatePassengerVoucherPDF is itself just microtasks (cached logo, QR encode),
|
||||||
// which doesn't have that effect.
|
// which doesn't have that effect.
|
||||||
for (let i = 0; i < booking.passengers.length; i++) {
|
for (const p of grouped.values()) {
|
||||||
const p = booking.passengers[i];
|
// The displayed ticket number is always the outbound leg's — matched by leg, not just
|
||||||
|
// name, so a round trip doesn't end up showing whichever ticket happens to sort first.
|
||||||
const matchedTicket =
|
const matchedTicket =
|
||||||
booking.tickets?.find((t) => t.passengerName === p.fullName) ?? booking.tickets?.[i] ?? null;
|
booking.tickets?.find((t) => t.passengerName === p.fullName && (t.leg ?? 1) === 1) ??
|
||||||
|
booking.tickets?.find((t) => t.passengerName === p.fullName) ??
|
||||||
|
null;
|
||||||
// No fabricated placeholder — a made-up TKT-... number reads as real and is misleading
|
// No fabricated placeholder — a made-up TKT-... number reads as real and is misleading
|
||||||
// if it doesn't match what's actually on file.
|
// if it doesn't match what's actually on file.
|
||||||
const ticketNumber = matchedTicket?.barcodePayload || 'Not yet issued';
|
const ticketNumber = matchedTicket?.barcodePayload || 'Not yet issued';
|
||||||
|
|
||||||
await generatePassengerVoucherPDF({
|
await generatePassengerVoucherPDF({
|
||||||
bookingRef: booking.bookingRef,
|
bookingRef: booking.bookingRef,
|
||||||
ticketNumber,
|
ticketNumber,
|
||||||
passengerName: p.fullName,
|
passengerName: p.fullName,
|
||||||
seatNumber: p.seat?.number,
|
status: booking.status,
|
||||||
status: booking.status,
|
outboundSchedule: { ...booking.schedule, seatClass: p.outboundSeat?.seatClass },
|
||||||
outboundSchedule: { ...booking.schedule, seatClass: p.seat?.seatClass },
|
inboundSchedule: isRoundTrip ? { ...booking.returnSchedule!, seatClass: p.returnSeat?.seatClass } : undefined,
|
||||||
isRoundTrip: false,
|
isRoundTrip,
|
||||||
fareMinor: useSettledAmount ? settledAmountMinor! : booking.totalMinor,
|
seatNumber: isRoundTrip ? undefined : p.outboundSeat?.number,
|
||||||
currency: voucherCurrency,
|
outboundSeatNumber: isRoundTrip ? p.outboundSeat?.number : undefined,
|
||||||
fareIsMajorUnits: useSettledAmount,
|
inboundSeatNumber: isRoundTrip ? p.returnSeat?.number : undefined,
|
||||||
createdAt: booking.createdAt,
|
fareMinor: useSettledAmount ? settledAmountMinor! : booking.totalMinor,
|
||||||
|
currency: voucherCurrency,
|
||||||
|
fareIsMajorUnits: useSettledAmount,
|
||||||
|
createdAt: booking.createdAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user