mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 11:08:12 +00:00
Double handling billed every import with a matching rule. Add bookings.double_handling (+ set_at/by), charge only when Yes, expose a PATCH endpoint (import-only, locked after invoicing, audited) and Yes/No items in the inventory row menu.
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { WarehouseFeeService } from './warehouse-fee.service';
|
|
|
|
/**
|
|
* Double handling bills ONLY when warehouse staff answered Yes after
|
|
* unloading. Undecided (null) or No must produce a zero charge even when a
|
|
* matching DOUBLE_HANDLING_FEE rule exists.
|
|
*/
|
|
type Item = Parameters<WarehouseFeeService['previewForInventory']> extends unknown
|
|
? Record<string, unknown>
|
|
: never;
|
|
|
|
const svc = Object.create(WarehouseFeeService.prototype) as {
|
|
computeDoubleHandling: (
|
|
rule: Record<string, unknown> | null,
|
|
item: Item,
|
|
now: Date,
|
|
billingCurrency: string,
|
|
) => Promise<{ amount: number; billableUnits: number }>;
|
|
normalizeCurrency: (c?: string | null) => string;
|
|
convertAmount: (a: number, from: string, to: string) => Promise<number>;
|
|
resolveBulkQuantity: (item: Item) => { quantity: number; unitLabel: string };
|
|
};
|
|
// No exchange service on a bare prototype — bill in the rule's own currency.
|
|
svc.normalizeCurrency = (c) => (c ? String(c).toUpperCase() : 'USD');
|
|
svc.convertAmount = async (a) => a;
|
|
|
|
const rule = { basis: 'PER_CONTAINER', ratePerDay: 100, currency: 'USD', id: 'r1', name: 'DH' };
|
|
const item = (doubleHandling: boolean | null) => ({
|
|
tradeDirection: 'IMPORT',
|
|
freightType: 'CONTAINER',
|
|
inventoryQuantity: 2,
|
|
bookingContainerCount: 3,
|
|
inventoryWeight: 10,
|
|
cargoUnitOfMeasure: 'PER_TON',
|
|
doubleHandling,
|
|
}) as unknown as Item;
|
|
|
|
describe('double handling gate', () => {
|
|
it('bills rate x containers when the booking is flagged Yes', async () => {
|
|
const out = await svc.computeDoubleHandling(rule, item(true), new Date(), 'USD');
|
|
expect(out.billableUnits).toBe(3);
|
|
expect(out.amount).toBe(300);
|
|
});
|
|
|
|
it('charges nothing when the answer is No', async () => {
|
|
const out = await svc.computeDoubleHandling(rule, item(false), new Date(), 'USD');
|
|
expect(out.billableUnits).toBe(0);
|
|
expect(out.amount).toBe(0);
|
|
});
|
|
|
|
it('charges nothing while the answer is undecided', async () => {
|
|
const out = await svc.computeDoubleHandling(rule, item(null), new Date(), 'USD');
|
|
expect(out.amount).toBe(0);
|
|
});
|
|
|
|
it('charges nothing for export even when flagged Yes', async () => {
|
|
const exportItem = { ...(item(true) as Record<string, unknown>), tradeDirection: 'EXPORT' } as Item;
|
|
const out = await svc.computeDoubleHandling(rule, exportItem, new Date(), 'USD');
|
|
expect(out.amount).toBe(0);
|
|
});
|
|
});
|