mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
139 lines
4.8 KiB
TypeScript
139 lines
4.8 KiB
TypeScript
import {
|
||
autoFillPlacements,
|
||
findMissingContainerNumberIssues,
|
||
occupiedTeuPerEdgeBySlot,
|
||
type ContainerUnitForPlacement,
|
||
} from './container-placement.util';
|
||
|
||
describe('container-placement.util', () => {
|
||
const units: ContainerUnitForPlacement[] = [
|
||
{
|
||
bookingId: 'b1',
|
||
bookingContainerId: 'c1',
|
||
unitIndex: 0,
|
||
label: 'REF · 1/1 · 20GP',
|
||
teuSlots: 1,
|
||
sizeFt: 20,
|
||
containerNumber: 'ABCD1234567',
|
||
},
|
||
{
|
||
bookingId: 'b2',
|
||
bookingContainerId: 'c2',
|
||
unitIndex: 0,
|
||
label: 'REF2 · 1/1 · 40GP',
|
||
teuSlots: 2,
|
||
sizeFt: 40,
|
||
containerNumber: null,
|
||
},
|
||
];
|
||
|
||
it('auto-fills placements across slots', () => {
|
||
const { placements, overflow } = autoFillPlacements(units, [1, 2]);
|
||
expect(placements).toHaveLength(2);
|
||
expect(overflow).toHaveLength(0);
|
||
expect(placements[0].sequenceNo).toBe(1);
|
||
expect(placements[1].sequenceNo).toBe(2);
|
||
});
|
||
|
||
it('reports missing container numbers only when placement is empty', () => {
|
||
const { placements } = autoFillPlacements(units, [1, 2]);
|
||
const issues = findMissingContainerNumberIssues(units, placements);
|
||
expect(issues).toHaveLength(0);
|
||
expect(placements[1].containerNumber).toMatch(/^TBD-/);
|
||
});
|
||
|
||
it('generates TBD placeholder for missing container numbers', () => {
|
||
const single: ContainerUnitForPlacement[] = [
|
||
{
|
||
bookingId: 'b2',
|
||
bookingReference: 'BK-2026-000033',
|
||
bookingContainerId: 'c2',
|
||
unitIndex: 0,
|
||
label: 'REF2 · 1/1 · 40GP',
|
||
teuSlots: 2,
|
||
sizeFt: 40,
|
||
containerNumber: null,
|
||
},
|
||
];
|
||
const { placements } = autoFillPlacements(single, [1]);
|
||
expect(placements[0].containerNumber).toBe('TBD-BK-2026-000033-1');
|
||
});
|
||
|
||
const ft40 = (
|
||
bookingId: string,
|
||
i: number,
|
||
leg?: { from: number; to: number },
|
||
): ContainerUnitForPlacement => ({
|
||
bookingId,
|
||
bookingReference: bookingId,
|
||
bookingContainerId: `${bookingId}-line`,
|
||
unitIndex: i,
|
||
label: `${bookingId} · ${i + 1} · 40GP`,
|
||
teuSlots: 2,
|
||
sizeFt: 40,
|
||
containerNumber: `CNT${bookingId}${i}`,
|
||
leg,
|
||
});
|
||
|
||
it('never clamps overflow onto the last slot — returns it instead', () => {
|
||
// 3 × 40ft, 2 slots. The old walk piled unit 3 onto slot #2 and let the
|
||
// validator reject it once per container ("Wagon #42…", the reported bug).
|
||
const three = [ft40('A', 0), ft40('A', 1), ft40('A', 2)];
|
||
const { placements, overflow } = autoFillPlacements(three, [1, 2]);
|
||
expect(placements).toHaveLength(2);
|
||
expect(overflow).toHaveLength(1);
|
||
expect(placements.every((p) => p.sequenceNo === 1 || p.sequenceNo === 2)).toBe(true);
|
||
});
|
||
|
||
it('leg-aware: disjoint-leg 40fts share one wagon (the staging case)', () => {
|
||
// 2 slots riding the whole 2-edge route. Leg-blind fill fits only two of
|
||
// these four 40fts; per-edge TEU fits all four — two per wagon, one per leg.
|
||
const slots = [
|
||
{ sequenceNo: 1, from: 0, to: 2 },
|
||
{ sequenceNo: 2, from: 0, to: 2 },
|
||
];
|
||
const four = [
|
||
ft40('LEG1', 0, { from: 0, to: 1 }),
|
||
ft40('LEG1', 1, { from: 0, to: 1 }),
|
||
ft40('LEG2', 0, { from: 1, to: 2 }),
|
||
ft40('LEG2', 1, { from: 1, to: 2 }),
|
||
];
|
||
const { placements, overflow } = autoFillPlacements(four, slots, new Map(), 2);
|
||
expect(overflow).toHaveLength(0);
|
||
expect(placements).toHaveLength(4);
|
||
});
|
||
|
||
it('same-leg 40fts still never share a wagon', () => {
|
||
const slots = [{ sequenceNo: 1, from: 0, to: 2 }];
|
||
const two = [ft40('X', 0, { from: 0, to: 1 }), ft40('X', 1, { from: 0, to: 1 })];
|
||
const { placements, overflow } = autoFillPlacements(two, slots, new Map(), 2);
|
||
expect(placements).toHaveLength(1);
|
||
expect(overflow).toHaveLength(1);
|
||
});
|
||
|
||
it('respects per-edge occupied TEU from caller-provided placements', () => {
|
||
const slots = [{ sequenceNo: 1, from: 0, to: 2 }];
|
||
const provided = [{ bookingContainerId: 'P-line', unitIndex: 0, sequenceNo: 1 }];
|
||
const providedUnit = ft40('P', 0, { from: 0, to: 1 });
|
||
const occupied = occupiedTeuPerEdgeBySlot(provided, [providedUnit], 2);
|
||
// Edge 0 is full on slot 1; an edge-0 unit overflows, an edge-1 unit fits.
|
||
const edge0 = autoFillPlacements([ft40('Q', 0, { from: 0, to: 1 })], slots, occupied, 2);
|
||
expect(edge0.overflow).toHaveLength(1);
|
||
const edge1 = autoFillPlacements([ft40('Q', 0, { from: 1, to: 2 })], slots, occupied, 2);
|
||
expect(edge1.overflow).toHaveLength(0);
|
||
expect(edge1.placements[0].sequenceNo).toBe(1);
|
||
});
|
||
|
||
it('a unit never lands on a slot that does not ride its leg', () => {
|
||
const slots = [{ sequenceNo: 1, from: 0, to: 1 }]; // alights at stop 1
|
||
const { placements, overflow } = autoFillPlacements(
|
||
[ft40('Y', 0, { from: 1, to: 2 })],
|
||
slots,
|
||
new Map(),
|
||
2,
|
||
);
|
||
expect(placements).toHaveLength(0);
|
||
expect(overflow).toHaveLength(1);
|
||
});
|
||
});
|