mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 08:20:58 +00:00
86 lines
3.3 KiB
TypeScript
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();
|
|
});
|
|
});
|