mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 12:58:13 +00:00
changes
This commit is contained in:
@@ -308,70 +308,43 @@ describe('RuleEngineService — empty-container return per route + container typ
|
||||
});
|
||||
});
|
||||
|
||||
describe('RuleEngineService — lashing per cargo kind', () => {
|
||||
const lashing20: Rate = {
|
||||
id: 'rate-lash-20',
|
||||
describe('RuleEngineService — lashing (bulk-only, per direction + commodity)', () => {
|
||||
const lashingBulkImport: Rate = {
|
||||
id: 'rate-lash-bulk',
|
||||
rateType: 'LASHING',
|
||||
trigger: 'LASHING',
|
||||
rateValue: 30,
|
||||
rateUnit: 'PER_CONTAINER',
|
||||
rateValue: 2,
|
||||
rateUnit: 'PER_TON',
|
||||
currency: 'USD',
|
||||
status: 'LIVE',
|
||||
containerTypeId: 'ct-20',
|
||||
containerTypeId: null,
|
||||
cargoTypeId: null,
|
||||
tradeDirection: null,
|
||||
tradeDirection: 'IMPORT',
|
||||
originYardId: null,
|
||||
destinationYardId: null,
|
||||
} as Rate;
|
||||
|
||||
const lashingBulk: Rate = {
|
||||
...lashing20,
|
||||
id: 'rate-lash-bulk',
|
||||
rateValue: 2,
|
||||
rateUnit: 'PER_TON',
|
||||
containerTypeId: null,
|
||||
} as Rate;
|
||||
|
||||
const buildService = (rates: Rate[]): RuleEngineService =>
|
||||
new RuleEngineService(
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never,
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never,
|
||||
{
|
||||
findActiveByContainerTypeId: jest
|
||||
findById: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ id: 'wlr-20', maxVgmTons: 20, maxCapacityTons: null }]),
|
||||
.mockResolvedValue({ hasLashing: true, requiresDirectorApproval: false }),
|
||||
} as never,
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never,
|
||||
{ findActiveByContainerTypeId: jest.fn().mockResolvedValue([]) } as never,
|
||||
{ findAllActive: jest.fn().mockResolvedValue([]) } as never,
|
||||
{ findLiveRates: jest.fn().mockResolvedValue(rates) } as never,
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never,
|
||||
{} as never,
|
||||
);
|
||||
|
||||
const containerInput = (overrides: Partial<BookingEvaluationInput> = {}): BookingEvaluationInput => ({
|
||||
serviceTypeId: 'svc-1',
|
||||
paymentCurrency: 'USD',
|
||||
tradeDirection: 'IMPORT',
|
||||
isHazardous: false,
|
||||
hasLashing: true,
|
||||
totalWagons: 2,
|
||||
containers: [
|
||||
{
|
||||
containerTypeId: 'ct-20',
|
||||
quantity: 4,
|
||||
vgmPerUnitTons: 10,
|
||||
totalVgmTons: 40,
|
||||
wagonsPerUnit: 0.5,
|
||||
},
|
||||
],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const bulkInput = (overrides: Partial<BookingEvaluationInput> = {}): BookingEvaluationInput => ({
|
||||
serviceTypeId: 'svc-1',
|
||||
paymentCurrency: 'USD',
|
||||
tradeDirection: 'IMPORT',
|
||||
isHazardous: false,
|
||||
hasLashing: true,
|
||||
cargoTypeId: 'cargo-sugar',
|
||||
totalWagons: 0,
|
||||
bulkTons: 100,
|
||||
bulkWagons: 3,
|
||||
@@ -382,53 +355,70 @@ describe('RuleEngineService — lashing per cargo kind', () => {
|
||||
const lashingMods = (result: Awaited<ReturnType<RuleEngineService['evaluate']>>) =>
|
||||
result.appliedModifiers.filter((m) => m.surchargeCode === 'LASHING');
|
||||
|
||||
it('bills each container line at its own container type rate', async () => {
|
||||
const result = await buildService([lashing20]).evaluate(containerInput());
|
||||
it('bulk lashing bills per ton on the direction-matched rate', async () => {
|
||||
const result = await buildService([lashingBulkImport]).evaluate(bulkInput());
|
||||
const mods = lashingMods(result);
|
||||
expect(mods).toHaveLength(1);
|
||||
expect(mods[0].triggerValue).toBe(4);
|
||||
expect(mods[0].calculatedAmount).toBe(120);
|
||||
expect(mods[0].billingUnit).toBe('PER_CONTAINER');
|
||||
});
|
||||
|
||||
it('PER_WAGON container lashing bills the wagons the line occupies', async () => {
|
||||
const result = await buildService([
|
||||
{ ...lashing20, rateUnit: 'PER_WAGON' } as Rate,
|
||||
]).evaluate(containerInput());
|
||||
const mods = lashingMods(result);
|
||||
expect(mods[0].triggerValue).toBe(2);
|
||||
expect(mods[0].calculatedAmount).toBe(60);
|
||||
expect(mods[0].billingUnit).toBe('PER_WAGON');
|
||||
});
|
||||
|
||||
it('an unmatched container type simply bills nothing (lenient)', async () => {
|
||||
const result = await buildService([
|
||||
{ ...lashing20, containerTypeId: 'ct-40' } as Rate,
|
||||
]).evaluate(containerInput());
|
||||
expect(lashingMods(result)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('bulk lashing bills per ton on the tonnage', async () => {
|
||||
const result = await buildService([lashingBulk]).evaluate(bulkInput());
|
||||
const mods = lashingMods(result);
|
||||
expect(mods[0].triggerValue).toBe(100);
|
||||
expect(mods[0].calculatedAmount).toBe(200);
|
||||
expect(mods[0].billingUnit).toBe('PER_TON');
|
||||
});
|
||||
|
||||
it('a rate for the other direction never bills', async () => {
|
||||
const result = await buildService([
|
||||
{ ...lashingBulkImport, tradeDirection: 'EXPORT' } as Rate,
|
||||
]).evaluate(bulkInput());
|
||||
expect(lashingMods(result)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('PER_WAGON bulk lashing bills the wagons the bulk occupies', async () => {
|
||||
const result = await buildService([
|
||||
{ ...lashingBulk, rateUnit: 'PER_WAGON', rateValue: 25 } as Rate,
|
||||
{ ...lashingBulkImport, rateUnit: 'PER_WAGON', rateValue: 25 } as Rate,
|
||||
]).evaluate(bulkInput());
|
||||
const mods = lashingMods(result);
|
||||
expect(mods[0].triggerValue).toBe(3);
|
||||
expect(mods[0].calculatedAmount).toBe(75);
|
||||
});
|
||||
|
||||
it('no lashing charge when the cargo does not need lashing', async () => {
|
||||
const result = await buildService([lashing20]).evaluate(
|
||||
containerInput({ hasLashing: false }),
|
||||
it('the commodity-scoped rate wins over the commodity-wide catch-all', async () => {
|
||||
const result = await buildService([
|
||||
lashingBulkImport,
|
||||
{ ...lashingBulkImport, id: 'rate-lash-sugar', rateValue: 7, cargoTypeId: 'cargo-sugar' } as Rate,
|
||||
]).evaluate(bulkInput());
|
||||
const mods = lashingMods(result);
|
||||
expect(mods).toHaveLength(1);
|
||||
expect(mods[0].unitPriceUsd).toBe(7);
|
||||
expect(mods[0].calculatedAmount).toBe(700);
|
||||
});
|
||||
|
||||
it('container bookings never incur lashing (bulk-only service)', async () => {
|
||||
const result = await buildService([lashingBulkImport]).evaluate(
|
||||
bulkInput({
|
||||
cargoTypeId: null,
|
||||
hasLashing: true,
|
||||
containers: [
|
||||
{ containerTypeId: 'ct-20', quantity: 4, vgmPerUnitTons: 10, totalVgmTons: 40 },
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(lashingMods(result)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('no lashing charge when the cargo does not need lashing', async () => {
|
||||
const service = new RuleEngineService(
|
||||
{
|
||||
findById: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ hasLashing: false, requiresDirectorApproval: false }),
|
||||
} as never,
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never,
|
||||
{ findActiveByContainerTypeId: jest.fn().mockResolvedValue([]) } as never,
|
||||
{ findAllActive: jest.fn().mockResolvedValue([]) } as never,
|
||||
{ findLiveRates: jest.fn().mockResolvedValue([lashingBulkImport]) } as never,
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never,
|
||||
{} as never,
|
||||
);
|
||||
const result = await service.evaluate(bulkInput());
|
||||
expect(lashingMods(result)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user