Generate missing ticket and payment report currency updates

This commit is contained in:
Stephanos A
2026-07-23 19:39:39 +03:00
parent e2bdc21c95
commit 84346d77ac
5 changed files with 69 additions and 45 deletions

View File

@@ -599,32 +599,31 @@ export class ReportsService {
sortBy?: string;
search?: string;
}) {
// Load exchange rates once — we need DJF→ETB (and any other non-ETB currencies).
// Keep only the most-recent rate per pair (rates are ordered desc by effectiveDate).
// Load all exchange rates once — we need conversions in both directions.
const rateRows = await this.prisma.currencyExchangeRate.findMany({
where: { toCurrency: 'ETB' as any },
orderBy: { effectiveDate: 'desc' },
});
const rateToEtb = new Map<string, number>();
// Most-recent rate for each fromCurrency→toCurrency pair
const rateMap = new Map<string, number>();
for (const r of rateRows) {
if (!rateToEtb.has(r.fromCurrency)) {
rateToEtb.set(r.fromCurrency, Number(r.rate));
}
const key = `${r.fromCurrency}${r.toCurrency}`;
if (!rateMap.has(key)) rateMap.set(key, Number(r.rate));
}
// Convert any minor amount to its ETB equivalent using stored exchange rates.
// b.totalMinor is the booking's canonical ETB amount (always stored in ETB),
// so callers should pass that directly rather than converting displayTotalMinor.
const toEtbMinor = (minor: number, currency: string): number => {
if (currency === 'ETB') return minor;
const rate = rateToEtb.get(currency);
// If no rate is on file fall back to the raw value (avoids silently hiding
// cross-currency bookings, at the cost of an approximate comparison).
return rate ? Math.round(minor * rate) : minor;
// Convert minor amount from one currency to another.
const convertMinor = (minor: number, from: string, to: string): number => {
if (from === to) return minor;
const direct = rateMap.get(`${from}${to}`);
if (direct) return Math.round(minor * direct);
// Try via ETB as pivot
const toEtb = rateMap.get(`${from}→ETB`);
const fromEtb = rateMap.get(`ETB→${to}`);
if (toEtb && fromEtb) return Math.round(minor * toEtb * fromEtb);
return minor; // fallback: no rate on file
};
if (params.search?.trim()) {
return this.getDiscrepancyForRef(params.search.trim(), toEtbMinor);
return this.getDiscrepancyForRef(params.search.trim(), convertMinor);
}
const dateFilter: Record<string, Date> = {};
@@ -679,20 +678,18 @@ export class ReportsService {
.map(b => {
const pi = b.paymentIntent!;
// Display amounts shown to the passenger (may be in DJF).
// Display amounts shown to the passenger (may be in DJF/USD).
const actualMinor = b.displayTotalMinor ?? b.totalMinor;
const actualCurrency = (b.displayCurrency as string | null) ?? b.currency;
const paidMinor = pi.amountMinor;
const paidCurrency = pi.currency;
// b.totalMinor is always in ETB minor. pi.amountMinor is the charge MAJOR amount
// (the gateway receives major units — displayMinorToChargeMajor divides by 100 before
// sending). Multiply by 100 to convert back to minor before the ETB comparison.
const owedEtb = b.totalMinor;
const paidEtb = toEtbMinor(paidMinor * 100, paidCurrency);
const balanceMinor = owedEtb - paidEtb;
const balanceCurrency = 'ETB';
// Balance in the booking's display currency:
// convert paid (major units from gateway) to display currency minor, then subtract.
const paidInDisplayMinor = convertMinor(paidMinor * 100, paidCurrency, actualCurrency);
const balanceMinor = actualMinor - paidInDisplayMinor;
const balanceCurrency = actualCurrency;
const firstSeat = b.seats[0];
const passengers = this.buildSeatPassengers(b.seats, actualCurrency);
@@ -731,7 +728,7 @@ export class ReportsService {
private async getDiscrepancyForRef(
search: string,
toEtbMinor: (minor: number, currency: string) => number,
convertMinor: (minor: number, from: string, to: string) => number,
) {
let bookingId: string | null = null;
const byPnr = await this.prisma.booking.findUnique({
@@ -797,10 +794,9 @@ export class ReportsService {
const paidMinor = pi?.amountMinor ?? 0;
const paidCurrency = pi?.currency ?? b.currency;
const owedEtb = b.totalMinor;
const paidEtb = toEtbMinor(paidMinor * 100, paidCurrency);
const balanceMinor = owedEtb - paidEtb;
const balanceCurrency = 'ETB';
const paidInDisplayMinor = convertMinor(paidMinor * 100, paidCurrency, actualCurrency);
const balanceMinor = actualMinor - paidInDisplayMinor;
const balanceCurrency = actualCurrency;
const firstSeat = b.seats[0];
const passengers = this.buildSeatPassengers(b.seats, actualCurrency);