Files
edr-platform/apps/edr-freight-api/src/modules/train-scheduling/fleet-plan.util.spec.ts
Marshal ddddcfb71f Refactor wagon type handling and container wagon calculations
- Removed maxWagonsPerTrain from WagonType entity and related DTOs.
- Updated containerWagonsForLines function to calculate required wagons based on container lines more accurately.
- Added unit tests for containerWagonsForLines to ensure correct calculations.
- Adjusted related services and scripts to reflect the removal of maxWagonsPerTrain.
- Enhanced booking and contract components to use new status labels for better user experience.
- Implemented validation for unique container numbers in shipment forms.
2026-07-09 03:36:35 +00:00

127 lines
3.8 KiB
TypeScript

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('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);
});
});