Files
edr-platform/apps/edr-freight-api/src/modules/train-scheduling/utils/fleet-plan.util.spec.ts

156 lines
4.5 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 {
computeFleetAvailability,
selectBookingsWithinFleetCap,
sortBookingsForScheduling,
summarizeFleetWarnings,
wagonsRequiredForBooking,
} from './fleet-plan.util';
import { buildContainerWagonPlan, type WagonPlanSlot } from './wagon-plan.util';
const nw5: WagonType = {
id: 'wt-nw5',
code: 'NW5',
name: 'Flat Wagon',
capacityTons: 70,
lengthMeters: 14,
supportedLoadTypes: ['CONTAINER'],
isActive: true,
supportsContainer: true,
} as WagonType;
const makeBooking = (
id: string,
extra: Partial<Booking> = {},
): Booking =>
({
id,
reference: id,
freightType: 'CONTAINER',
isGovernment: false,
priorityScore: 0,
scheduledDate: new Date('2026-06-20T08:00:00.000Z'),
cargoTotalWeightVgm: 50,
bookingContainers: [{ id: `${id}-line`, quantity: 2, wagonsRequired: 1, vgmPerUnitTons: 25 }],
...extra,
}) as Booking;
describe('fleet-plan.util', () => {
it('sorts bookings government first, then priority, then date', () => {
const bookings = [
makeBooking('late', { scheduledDate: new Date('2026-06-22T08:00:00.000Z') }),
makeBooking('gov', { isGovernment: true, priorityScore: 0 }),
makeBooking('prio', { priorityScore: 10 }),
];
const sorted = sortBookingsForScheduling(bookings);
expect(sorted.map((b) => b.id)).toEqual(['gov', 'prio', 'late']);
});
it('computes fleet availability with shortfall', () => {
const plan: WagonPlanSlot[] = buildContainerWagonPlan(
[
makeBooking('b1', {
bookingContainers: [
{ id: 'b1-line', quantity: 4, wagonsRequired: 2, vgmPerUnitTons: 25 } as never,
],
}),
],
nw5,
);
const fleetByTypeId = new Map([[nw5.id, 1]]);
const rows = computeFleetAvailability(plan, fleetByTypeId, new Map([[nw5.id, 'NW5']]));
const nw5Row = rows.find((r) => r.wagonTypeCode === 'NW5');
expect(nw5Row?.needed).toBe(2);
expect(nw5Row?.available).toBe(1);
expect(nw5Row?.shortfall).toBe(1);
});
it('defers lower-priority bookings when fleet is insufficient', () => {
const high = makeBooking('high', {
priorityScore: 100,
bookingContainers: [
{ id: 'high-line', quantity: 2, wagonsRequired: 2, vgmPerUnitTons: 25 } as never,
],
});
const low = makeBooking('low', {
priorityScore: 1,
bookingContainers: [
{ id: 'low-line', quantity: 2, wagonsRequired: 2, vgmPerUnitTons: 25 } as never,
],
});
const fleet = new Map([[nw5.id, 2]]);
const { fitting, deferred } = selectBookingsWithinFleetCap(
[low, high],
fleet,
() => nw5.id,
);
expect(fitting.map((b) => b.id)).toEqual(['high']);
expect(deferred).toHaveLength(1);
expect(deferred[0]?.id).toBe('low');
expect(deferred[0]?.reason).toContain('2');
});
it('summarizes fleet shortage warnings', () => {
const warnings = summarizeFleetWarnings(
[
{
wagonTypeId: nw5.id,
wagonTypeCode: 'NW5',
needed: 5,
available: 2,
shortfall: 3,
},
],
[{ id: 'b1', reference: 'BKG-1', reason: 'No wagons' }],
);
expect(warnings.some((w) => w.includes('Fleet shortage'))).toBe(true);
expect(warnings.some((w) => w.includes('deferred'))).toBe(true);
});
it('names the booking and its per-type shortfall when the deferral carries a shortage', () => {
const warnings = summarizeFleetWarnings(
[],
[
{
id: 'b1',
reference: 'BKG-1',
reason: 'No available NW6 wagon at the yard',
shortage: {
wagonTypeCodes: 'NW6',
wagonsNeeded: 2,
wagonsAvailable: 1,
wagonsShort: 1,
},
},
],
);
expect(
warnings.some(
(w) =>
w.includes('BKG-1') &&
w.includes('2 × NW6') &&
w.includes('only 1 available') &&
w.includes('short 1'),
),
).toBe(true);
});
it('counts wagons required per booking from container lines', () => {
const booking = makeBooking('b1', {
bookingContainers: [
{ id: 'b1-line-0', quantity: 2, wagonsRequired: 1, vgmPerUnitTons: 25 } as never,
{ id: 'b1-line-1', quantity: 1, wagonsRequired: 1, vgmPerUnitTons: 25 } as never,
],
});
expect(wagonsRequiredForBooking(booking)).toBe(2);
});
});