mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 21:50:57 +00:00
123 lines
3.9 KiB
TypeScript
123 lines
3.9 KiB
TypeScript
import { UnprocessableEntityException } from '@nestjs/common';
|
|
|
|
import { ContractPricingService } from './contract-pricing.service';
|
|
import type { Contract } from './entities/contract.entity';
|
|
import type { Rate } from '../rule-engine/entities/rate.entity';
|
|
|
|
const CT20 = 'ct-20';
|
|
const CT40 = 'ct-40';
|
|
const DCT = 'yard-dct';
|
|
const SEBETA = 'yard-sebeta';
|
|
const GMP = 'yard-gmp';
|
|
|
|
const rate = (over: Partial<Rate>): Rate =>
|
|
({
|
|
rateType: 'CONTAINER_IMPORT',
|
|
currency: 'USD',
|
|
rateValue: 1000,
|
|
rateUnit: 'PER_CONTAINER',
|
|
containerTypeId: null,
|
|
cargoTypeId: null,
|
|
originYardId: DCT,
|
|
destinationYardId: SEBETA,
|
|
...over,
|
|
}) as Rate;
|
|
|
|
const contract = (over: Partial<Contract>): Contract =>
|
|
({
|
|
freightType: 'CONTAINER',
|
|
tradeDirection: 'IMPORT',
|
|
paymentCurrency: 'USD',
|
|
customsClearingEnabled: false,
|
|
isHazardous: false,
|
|
isReefer: false,
|
|
routes: [{ originYardId: DCT, destinationYardId: SEBETA, sortOrder: 0 }],
|
|
cargoScope: [{ containerSize: '20ft' }],
|
|
...over,
|
|
}) as Contract;
|
|
|
|
const service = (liveRates: Rate[]): ContractPricingService =>
|
|
new ContractPricingService(
|
|
{} as never,
|
|
{ findLiveRates: async () => liveRates } as never,
|
|
{
|
|
findAll: async () => ({
|
|
items: [
|
|
{ id: CT20, sizeFt: 20 },
|
|
{ id: CT40, sizeFt: 40 },
|
|
],
|
|
}),
|
|
} as never,
|
|
{ getRate: async () => 1 } as never,
|
|
);
|
|
|
|
describe('contract base freight is priced on the contract lane only', () => {
|
|
it('prices from the contract route, never another lane (CTR-2026-00065)', async () => {
|
|
const breakdown = await service([
|
|
// Same size, other lane — the leak that priced DCT → Sebeta at GMP rates.
|
|
rate({ containerTypeId: CT20, destinationYardId: GMP, rateValue: 1690 }),
|
|
rate({ containerTypeId: CT20, rateValue: 750 }),
|
|
]).buildBreakdown(contract({}));
|
|
expect(breakdown.lineItems).toEqual([
|
|
expect.objectContaining({ code: 'CONTAINER_20FT', unitPrice: 750 }),
|
|
]);
|
|
});
|
|
|
|
it('blocks the contract when its lane has no container rate', async () => {
|
|
await expect(
|
|
service([
|
|
rate({ containerTypeId: CT20, destinationYardId: GMP, rateValue: 1690 }),
|
|
]).buildBreakdown(contract({})),
|
|
).rejects.toThrow(UnprocessableEntityException);
|
|
});
|
|
|
|
it('freezes the OVERWEIGHT_PER_TON surcharge on export contracts only', async () => {
|
|
const overweight = rate({
|
|
rateType: 'OVERWEIGHT_PER_TON',
|
|
trigger: 'OVERWEIGHT',
|
|
rateUnit: 'PER_TON',
|
|
rateValue: 25,
|
|
originYardId: null,
|
|
destinationYardId: null,
|
|
} as Partial<Rate>);
|
|
|
|
const exported = await service([
|
|
rate({ rateType: 'CONTAINER_EXPORT', containerTypeId: CT20, rateValue: 900 }),
|
|
overweight,
|
|
]).buildBreakdown(contract({ tradeDirection: 'EXPORT' }));
|
|
expect(exported.lineItems).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ code: 'OVERWEIGHT_PER_TON', unitPrice: 25 }),
|
|
]),
|
|
);
|
|
|
|
// Import derives overweight from the route's base freight — never frozen.
|
|
const imported = await service([
|
|
rate({ containerTypeId: CT20, rateValue: 750 }),
|
|
overweight,
|
|
]).buildBreakdown(contract({}));
|
|
expect(
|
|
imported.lineItems.some((li) => li.code === 'OVERWEIGHT_PER_TON'),
|
|
).toBe(false);
|
|
});
|
|
|
|
it('blocks bulk contracts too instead of borrowing an arbitrary rate', async () => {
|
|
const bulk = contract({ freightType: 'BULK', cargoScope: [] });
|
|
await expect(
|
|
service([
|
|
rate({
|
|
rateType: 'BULK_IMPORT',
|
|
rateUnit: 'PER_TON',
|
|
destinationYardId: GMP,
|
|
}),
|
|
]).buildBreakdown(bulk),
|
|
).rejects.toThrow(UnprocessableEntityException);
|
|
const priced = await service([
|
|
rate({ rateType: 'BULK_IMPORT', rateUnit: 'PER_TON', rateValue: 32 }),
|
|
]).buildBreakdown(bulk);
|
|
expect(priced.lineItems).toEqual([
|
|
expect.objectContaining({ code: 'BULK_FREIGHT', unitPrice: 32 }),
|
|
]);
|
|
});
|
|
});
|