fix(train-scheduling): marshalling docs respect mid-corridor wagon coupling

Origin marshalling (import + export) now prints leg slots as
TO BE LOADED AT <board yard> with board/alight stations and keeps
their containers out of the loaded tallies. Intercity Marshalling 2 no
longer hides whole-route cargo whose allocation never left PLANNED
(import flow confirms loading at schedule level), so the on-board view
matches the physical consist before and after mid-corridor coupling.
This commit is contained in:
Hagernesh
2026-08-26 08:27:05 +00:00
parent 1205e2cdc1
commit 69062389c7
25 changed files with 502 additions and 12 deletions

View File

@@ -1134,6 +1134,81 @@ describe('TrainSchedulingService', () => {
expect(html).toContain('2 (1 empty)');
});
it('marks a leg slot on the import document as TO BE LOADED and keeps it out of the loaded tallies', () => {
const loadList = {
generatedAt: '2026-07-17T08:00:00.000Z',
trainScheduleId: 'schedule-1',
trainNumber: '7002',
route: 'DCT/SGTD → GMP',
origin: 'DCT/SGTD',
destination: 'GMP',
totalBookings: 2,
wagons: [
{
sequenceNo: 1,
wagonNumber: 'W-ICY',
boardYard: 'Dire Dawa Port',
alightYard: null,
allocations: [
{
...loadedAllocation,
containerItems: [{ containerNumber: 'ICY-001' }],
},
],
},
{
sequenceNo: 2,
wagonNumber: 'W-IMP',
boardYard: null,
alightYard: null,
allocations: [
{
...loadedAllocation,
containerItems: [{ containerNumber: 'CONT-001', containerType: { sizeFt: 20 } }],
},
],
},
],
operation: { status: {} },
};
const html = (service as never as {
buildImportLoadListHtml: (l: unknown) => string;
}).buildImportLoadListHtml(loadList);
expect(html).toContain('TO BE LOADED AT DIRE DAWA PORT');
// Departure station of the leg slot is its board yard, not the origin.
expect(html).toContain('<td>Dire Dawa Port</td>');
// Only the origin-loaded container counts; the leg slot's tallies separately.
expect(html).toContain('<span>Total containers</span><strong>1</strong>');
expect(html).toContain('<span>To load en route</span><strong>1 containers</strong>');
});
it('marks a leg slot on the export document as TO LOAD AT its board yard and keeps it out of the tallies', () => {
const sizedAllocation = {
...loadedAllocation,
containerItems: [{ containerNumber: 'CONT-001', containerType: { sizeFt: 20 } }],
};
const legWagon = { ...makeWagon(2, 'W-LEG', [sizedAllocation]), id: 'slot-leg' };
const schedule = {
id: 'schedule-1',
trainNumber: '8302',
direction: 'EXPORT',
trainSet: { wagons: [{ ...makeWagon(1, 'W-001', [sizedAllocation]), id: 'slot-1' }, legWagon] },
scheduleBookings: [],
};
const html = (service as never as {
buildExportLoadListHtml: (s: unknown, o?: unknown) => string;
}).buildExportLoadListHtml(schedule, {
pendingBoardYardLabelBySlot: new Map([['slot-leg', 'Dire Dawa Port']]),
});
expect(html).toContain('TO LOAD AT DIRE DAWA PORT');
expect(html).toContain('<span>Total containers</span><strong>1</strong>');
expect(html).toContain('<span>To load en route</span><strong>1 containers</strong>');
});
it('lists loaded empty containers by number and states they are empty', () => {
const schedule = {
id: 'schedule-1',
@@ -1264,6 +1339,25 @@ describe('TrainSchedulingService', () => {
expect(html).toContain('2 (1 empty)');
});
it('keeps whole-route cargo whose allocation never left PLANNED (import flow) on board', () => {
// The import flow confirms loading at schedule level and never flips the
// allocation to LOADED — the cargo is still on the train until DEPARTED.
const schedule = {
trainSet: {
wagons: [
{ ...makeWagon(1, 'W-IMP', [allocWith({ status: 'PLANNED' })]), status: 'RESERVED' },
{ ...makeWagon(2, 'W-ICY', [allocWith({ status: 'LOADED', bookingId: 'booking-2' })]), status: 'RESERVED', boardYardId: 'yard-mid' },
],
},
scheduleBookings: [],
};
const { wagons } = onBoardView(schedule);
const byNumber = wagons as Array<{ physicalWagon: { wagonNumber: string }; allocations: unknown[] }>;
expect(byNumber.map((w) => w.physicalWagon.wagonNumber)).toEqual(['W-IMP', 'W-ICY']);
expect(byNumber[0].allocations).toHaveLength(1);
});
it('hides a leg slot (boardYardId set) until it has confirmed LOADED cargo', () => {
const legWagonEmpty = { ...makeWagon(2, 'W-LEG', [allocWith({ status: 'RESERVED' })]), status: 'RESERVED', boardYardId: 'yard-mid' };
const legWagonLoaded = { ...makeWagon(3, 'W-LEG2', [allocWith({ status: 'LOADED' })]), status: 'RESERVED', boardYardId: 'yard-mid' };