This commit is contained in:
Marshal
2026-07-14 11:06:49 +00:00
parent 957a185a4d
commit 6d0cf50b4d
64 changed files with 4896 additions and 404 deletions

View File

@@ -72,7 +72,7 @@ const makeBooking = (
wagonsRequired,
vgmPerUnitTons: weight / quantity,
isOverweight: false,
containerType: { code: containerCode, label: containerCode, wagonTypeId: nw5.id },
containerType: { id: 'ct-1', code: containerCode, label: containerCode, wagonTypes: [nw5] },
},
],
...extra,
@@ -80,7 +80,7 @@ const makeBooking = (
describe('TrainSchedulingService', () => {
let service: TrainSchedulingService;
let dataSource: { getRepository: jest.Mock; transaction: jest.Mock };
let dataSource: { getRepository: jest.Mock; transaction: jest.Mock; query: jest.Mock };
let bookingsRepository: Record<string, jest.Mock>;
let locomotivesRepository: { findById: jest.Mock; findAll: jest.Mock };
let wagonTypesRepository: { findAll: jest.Mock };
@@ -91,7 +91,12 @@ describe('TrainSchedulingService', () => {
let wagonAllocationBulkLoadsRepository: Record<string, jest.Mock>;
beforeEach(() => {
dataSource = { getRepository: jest.fn(), transaction: jest.fn() };
dataSource = {
getRepository: jest.fn(),
transaction: jest.fn(),
// Raw-SQL helper lookups (e.g. builtTrainIdOfSchedule) default to "no rows".
query: jest.fn().mockResolvedValue([]),
};
bookingsRepository = {
findEligibleForScheduling: jest.fn(),
findByIdsForScheduling: jest.fn(),
@@ -259,8 +264,10 @@ describe('TrainSchedulingService', () => {
expect(result.valid).toBe(true);
expect(result.violations).toEqual([]);
expect(result.summary.wagonsNeeded).toBe(45);
expect(result.wagonPlan).toHaveLength(45);
// TEU packing: 20 + 15 wagons of 40ft plus 10×20ft at two per wagon (5) —
// the planner packs by container size, not the stored per-line fallback.
expect(result.summary.wagonsNeeded).toBe(40);
expect(result.wagonPlan).toHaveLength(40);
});
it('returns soft hold warnings without forceAssign', async () => {
@@ -293,7 +300,7 @@ describe('TrainSchedulingService', () => {
wagonsRequired: 80,
vgmPerUnitTons: 45,
isOverweight: true,
containerType: { code: '40FT', label: '40FT', wagonTypeId: nw5.id },
containerType: { id: 'ct-1', code: '40FT', label: '40FT', wagonTypes: [nw5] },
},
],
}),
@@ -655,10 +662,12 @@ describe('TrainSchedulingService', () => {
destinationStationId: 'yard-djibouti',
});
expect(result.valid).toBe(false);
expect(
result.violations.some((v) => v.includes('available at yard') && v.includes('NW5')),
).toBe(true);
// List-based planner: a booking with no plannable wagon at the yard is
// DEFERRED with the wagon-type reason (assign still hard-fails when no
// booking fits), instead of surfacing a phantom-slot violation.
expect(result.valid).toBe(true);
expect(result.wagonPlan).toHaveLength(0);
expect(result.deferredBookings.some((d) => d.reason.includes('NW5'))).toBe(true);
});
it('assignBookingsToSchedule rejects when physical wagons cannot be pinned', async () => {