Files
edr-platform/apps/edr-freight-api/src/modules/contracts/shipment-currency.spec.ts

86 lines
3.3 KiB
TypeScript

import { ContractBookingService } from './contract-booking.service';
import { BookingPricingService } from '../bookings/booking-pricing.service';
import type { Contract } from './entities/contract.entity';
import type { ContractRateSnapshot } from './entities/contract-rate-snapshot.entity';
const contract = (over: Partial<Contract>): Contract =>
({ tradeDirection: 'IMPORT', paymentCurrency: 'USD', ...over }) as Contract;
/** The private resolver, reached without standing up the whole Nest graph. */
const resolveCurrency = (c: Contract, requested?: string | null): string =>
(
ContractBookingService.prototype as unknown as {
resolveShipmentCurrency: (c: Contract, r?: string | null) => string;
}
).resolveShipmentCurrency(c, requested);
const snapshot = (currency: string, unitPrice: number): ContractRateSnapshot =>
({ rateCode: 'CONTAINER_20FT', currency, unitPrice }) as ContractRateSnapshot;
const frozenByCode = (
snap: ContractRateSnapshot | null,
bookingCurrency: string,
usdToEtb: number,
): ContractRateSnapshot | null =>
(
BookingPricingService.prototype as unknown as {
frozenRateByCode: (
m: Map<string, ContractRateSnapshot> | null,
code: string,
bookingCurrency: string,
usdToEtb: number,
) => ContractRateSnapshot | null;
}
).frozenRateByCode(
snap ? new Map([['CONTAINER_20FT', snap]]) : null,
'CONTAINER_20FT',
bookingCurrency,
usdToEtb,
);
describe('per-shipment billing currency', () => {
it('takes the customer choice over the contract', () => {
expect(resolveCurrency(contract({}), 'ETB')).toBe('ETB');
expect(resolveCurrency(contract({}), 'USD')).toBe('USD');
});
it('falls back to the contract currency when none is chosen', () => {
// Grandfathered ETB contract with no explicit choice.
expect(resolveCurrency(contract({ paymentCurrency: 'ETB' }))).toBe('ETB');
expect(resolveCurrency(contract({}), ' ')).toBe('USD');
});
it('forces ETB on intercity whatever was requested', () => {
const domestic = contract({ tradeDirection: 'DOMESTIC' });
expect(resolveCurrency(domestic, 'USD')).toBe('ETB');
expect(resolveCurrency(domestic)).toBe('ETB');
});
});
describe('frozen contract rate in the booking currency', () => {
it('converts a USD snapshot for an ETB booking instead of dropping it', () => {
// The old behaviour returned null here, which silently re-priced the
// booking at live rates and lost the agreed contract price.
expect(frozenByCode(snapshot('USD', 400), 'ETB', 150)?.unitPrice).toBe(60_000);
});
it('converts a grandfathered ETB snapshot back for a USD booking', () => {
expect(frozenByCode(snapshot('ETB', 60_000), 'USD', 150)?.unitPrice).toBe(400);
});
it('passes a matching-currency snapshot through untouched', () => {
const snap = snapshot('USD', 400);
expect(frozenByCode(snap, 'USD', 1)).toBe(snap);
});
it('refuses to price off an unusable exchange rate', () => {
// Converting with 0 would zero the whole line.
expect(frozenByCode(snapshot('USD', 400), 'ETB', 0)).toBeNull();
expect(frozenByCode(snapshot('USD', 400), 'ETB', Number.NaN)).toBeNull();
});
it('returns null when there is no snapshot', () => {
expect(frozenByCode(null, 'ETB', 150)).toBeNull();
});
});