mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 14:15:44 +00:00
149 lines
5.3 KiB
TypeScript
149 lines
5.3 KiB
TypeScript
import { BookingJourneyService } from './booking-journey.service';
|
|
|
|
/**
|
|
* autoPlaceOnFreedWagons: intercity cargo boards the wagons freed by earlier
|
|
* unloads. Exercised directly with a stubbed EntityManager — the surrounding
|
|
* loadBooking flow is integration-tested through the running app.
|
|
*/
|
|
describe('BookingJourneyService.autoPlaceOnFreedWagons', () => {
|
|
const service = new BookingJourneyService(
|
|
{} as never, // dataSource
|
|
{} as never, // yardFacilities
|
|
{} as never, // facilityHandling
|
|
{ emit: jest.fn() } as never, // events
|
|
{} as never, // notifications
|
|
{} as never, // inbox
|
|
{ record: jest.fn() } as never, // wagonHistory
|
|
);
|
|
|
|
const schedule = { id: 'sched-1', trainSetId: 'ts-1' };
|
|
const booking = {
|
|
id: 'booking-1',
|
|
reference: 'BK-1',
|
|
cargoTotalWeightVgm: 50,
|
|
freightType: 'CONTAINER',
|
|
};
|
|
|
|
const makeManager = (slots: unknown[], existingAllocs: unknown[] = []) => {
|
|
const savedAllocs: Array<Record<string, unknown>> = [];
|
|
const savedItems: Array<Record<string, unknown>> = [];
|
|
const allocQb = {
|
|
innerJoinAndSelect: jest.fn().mockReturnThis(),
|
|
innerJoin: jest.fn().mockReturnThis(),
|
|
where: jest.fn().mockReturnThis(),
|
|
getMany: jest.fn().mockResolvedValue(existingAllocs),
|
|
};
|
|
const slotQb = {
|
|
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
|
innerJoin: jest.fn().mockReturnThis(),
|
|
where: jest.fn().mockReturnThis(),
|
|
orderBy: jest.fn().mockReturnThis(),
|
|
getMany: jest.fn().mockResolvedValue(slots),
|
|
};
|
|
let allocId = 0;
|
|
const manager = {
|
|
getRepository: jest.fn((entity: { name?: string }) => {
|
|
const name = entity?.name;
|
|
if (name === 'WagonBookingAllocation') {
|
|
return {
|
|
createQueryBuilder: jest.fn(() => allocQb),
|
|
create: jest.fn((v: Record<string, unknown>) => v),
|
|
save: jest.fn(async (v: Record<string, unknown>) => {
|
|
const row = { ...v, id: `alloc-${++allocId}` };
|
|
savedAllocs.push(row);
|
|
return row;
|
|
}),
|
|
update: jest.fn(),
|
|
};
|
|
}
|
|
if (name === 'TrainSetWagon') {
|
|
return { createQueryBuilder: jest.fn(() => slotQb) };
|
|
}
|
|
if (name === 'BookingContainer') {
|
|
return {
|
|
find: jest.fn().mockResolvedValue([
|
|
{
|
|
id: 'line-1',
|
|
containerNumber: 'LINE-001',
|
|
containerTypeId: 'ct-20',
|
|
units: [{ containerNumber: 'UNIT-001' }, { containerNumber: 'UNIT-002' }],
|
|
},
|
|
]),
|
|
};
|
|
}
|
|
if (name === 'WagonAllocationContainerItem') {
|
|
return {
|
|
create: jest.fn((v: Record<string, unknown>) => v),
|
|
save: jest.fn(async (v: Record<string, unknown>) => {
|
|
savedItems.push(v);
|
|
return v;
|
|
}),
|
|
};
|
|
}
|
|
throw new Error(`Unexpected repository ${name}`);
|
|
}),
|
|
};
|
|
return { manager, savedAllocs, savedItems };
|
|
};
|
|
|
|
const call = (manager: unknown) =>
|
|
(service as never as {
|
|
autoPlaceOnFreedWagons: (m: unknown, s: unknown, b: unknown) => Promise<void>;
|
|
}).autoPlaceOnFreedWagons(manager, schedule, booking);
|
|
|
|
it('places the booking on freed slots in consist order, with container items', async () => {
|
|
const slots = [
|
|
// Active cargo still riding — NOT freed.
|
|
{ id: 'slot-1', sequenceNo: 1, capacityTons: 60, allocations: [{ status: 'LOADED' }] },
|
|
// Freed by an earlier unload.
|
|
{ id: 'slot-2', sequenceNo: 2, capacityTons: 60, allocations: [{ status: 'DEPARTED' }] },
|
|
{ id: 'slot-3', sequenceNo: 3, capacityTons: 60, allocations: [] },
|
|
];
|
|
const { manager, savedAllocs, savedItems } = makeManager(slots);
|
|
|
|
await call(manager);
|
|
|
|
// 50 t fits on the first freed slot alone.
|
|
expect(savedAllocs).toHaveLength(1);
|
|
expect(savedAllocs[0]).toMatchObject({
|
|
trainSetWagonId: 'slot-2',
|
|
bookingId: 'booking-1',
|
|
allocatedWeightTons: 50,
|
|
status: 'LOADED',
|
|
});
|
|
// One item per physical unit, on the first allocation.
|
|
expect(savedItems.map((i) => i.containerNumber)).toEqual(['UNIT-001', 'UNIT-002']);
|
|
expect(savedItems.every((i) => i.wagonBookingAllocationId === 'alloc-1')).toBe(true);
|
|
});
|
|
|
|
it('spills over onto the next freed slot when one is not enough', async () => {
|
|
const slots = [
|
|
{ id: 'slot-2', sequenceNo: 2, capacityTons: 30, allocations: [{ status: 'DEPARTED' }] },
|
|
{ id: 'slot-3', sequenceNo: 3, capacityTons: 30, allocations: [] },
|
|
];
|
|
const { manager, savedAllocs } = makeManager(slots);
|
|
|
|
await call(manager);
|
|
|
|
expect(savedAllocs.map((a) => [a.trainSetWagonId, a.allocatedWeightTons])).toEqual([
|
|
['slot-2', 30],
|
|
['slot-3', 20],
|
|
]);
|
|
});
|
|
|
|
it('does nothing when the booking already has allocations', async () => {
|
|
const { manager, savedAllocs } = makeManager([], [{ id: 'existing' }]);
|
|
await call(manager);
|
|
expect(savedAllocs).toHaveLength(0);
|
|
});
|
|
|
|
it('loads without allocation when no wagon is free', async () => {
|
|
const slots = [
|
|
{ id: 'slot-1', sequenceNo: 1, capacityTons: 60, allocations: [{ status: 'LOADED' }] },
|
|
];
|
|
const { manager, savedAllocs } = makeManager(slots);
|
|
await expect(call(manager)).resolves.toBeUndefined();
|
|
expect(savedAllocs).toHaveLength(0);
|
|
});
|
|
});
|