Files
edr-platform/apps/edr-freight-api/src/modules/train-scheduling/wagon-plan-flex.util.spec.ts
2026-07-17 09:16:35 +00:00

133 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Booking } from '../bookings/entities/booking.entity';
import { WagonType } from '../wagon-types/entities/wagon-type.entity';
import { planWagonsWithStock } from './wagon-plan-flex.util';
const nw6: WagonType = {
id: 'wt-nw6',
code: 'NW6',
name: 'Flat Wagon',
capacityTons: 70,
lengthMeters: 14,
supportedLoadTypes: ['CONTAINER'],
isActive: true,
supportsContainer: true,
} as WagonType;
const cw3: WagonType = {
id: 'wt-cw3',
code: 'CW3',
name: 'Covered Wagon',
capacityTons: 60,
lengthMeters: 14,
supportedLoadTypes: ['BULK'],
isActive: true,
supportsContainer: false,
} as WagonType;
const containerBooking = (id: string, quantity: number, wagonsRequired: number): Booking =>
({
id,
reference: id,
freightType: 'CONTAINER',
cargoTotalWeightVgm: quantity * 25,
bookingContainers: [
{
id: `${id}-line-0`,
containerTypeId: 'ct-1',
quantity,
wagonsRequired,
vgmPerUnitTons: 25,
},
],
}) as Booking;
describe('planWagonsWithStock — shortage detail', () => {
it('defers with a structured per-type shortage when container stock runs out', () => {
const result = planWagonsWithStock({
bookings: [containerBooking('BKG-1', 2, 1)],
allowed: {
byContainerTypeId: new Map([['ct-1', [nw6]]]),
byCargoTypeId: new Map(),
},
stock: {
mode: 'YARD',
remainingByTypeId: new Map([[nw6.id, 0]]),
codesByTypeId: new Map([[nw6.id, nw6.code]]),
},
});
expect(result.fitting).toHaveLength(0);
expect(result.deferred).toHaveLength(1);
const row = result.deferred[0]!;
expect(row.reference).toBe('BKG-1');
expect(row.reason).toContain('No available NW6 wagon at the yard');
expect(row.reason).toContain('short 1');
expect(row.shortage).toEqual({
wagonTypeCodes: 'NW6',
wagonsNeeded: 1,
wagonsAvailable: 0,
wagonsShort: 1,
});
});
it('counts the stock the deferred booking actually saw, not its rolled-back usage', () => {
// Two wagons needed (2 × 40ft), one in stock: booking rolls back entirely,
// the shortage reports 1 available / 1 short.
const fortyFooter = containerBooking('BKG-2', 2, 2);
fortyFooter.bookingContainers![0]!.containerType = {
code: '40GP',
sizeFt: 40,
} as never;
const result = planWagonsWithStock({
bookings: [fortyFooter],
allowed: {
byContainerTypeId: new Map([['ct-1', [nw6]]]),
byCargoTypeId: new Map(),
},
stock: {
mode: 'YARD',
remainingByTypeId: new Map([[nw6.id, 1]]),
codesByTypeId: new Map([[nw6.id, nw6.code]]),
},
});
expect(result.deferred).toHaveLength(1);
expect(result.deferred[0]?.shortage).toEqual({
wagonTypeCodes: 'NW6',
wagonsNeeded: 2,
wagonsAvailable: 1,
wagonsShort: 1,
});
// The rolled-back wagon is plannable again for later bookings.
expect(result.plan).toHaveLength(0);
});
it('leaves shortage unset for configuration problems', () => {
const bulkBooking = {
id: 'BKG-3',
reference: 'BKG-3',
freightType: 'BULK',
cargoTotalWeightVgm: 40,
cargoTypeId: 'cargo-1',
cargoType: { id: 'cargo-1', cargoTypeName: 'Fertilizer' },
bookingContainers: [],
} as unknown as Booking;
const result = planWagonsWithStock({
bookings: [bulkBooking],
allowed: {
byContainerTypeId: new Map(),
byCargoTypeId: new Map(), // no wagon types configured → config issue
},
stock: {
mode: 'YARD',
remainingByTypeId: new Map([[cw3.id, 5]]),
codesByTypeId: new Map([[cw3.id, cw3.code]]),
},
});
expect(result.configIssues).toHaveLength(1);
expect(result.deferred[0]?.shortage).toBeNull();
});
});