Files
edr-platform/apps/edr-freight-api/src/modules/train-scheduling/wagon-stock-ledger.util.spec.ts
Marshal 2429f6b629 implement contract cancellation feature and update contract statuses
- Added functionality to cancel contracts, allowing users to provide a reason for cancellation.
- Updated contract statuses to include SUSPENDED and changed CLOSED to COMPLETED.
- Enhanced the UI to reflect the new cancellation option and updated messaging for contract statuses.
- Refactored contract booking actions to accommodate changes in booking logic for ONE_TIME and GENERAL contracts.
- Removed clearance document management from the contract detail page, as it is now handled per booking.
- Introduced a SQL script to reset bookings and train schedules for development purposes.
2026-07-28 05:02:58 +00:00

71 lines
2.9 KiB
TypeScript

import { WagonStockLedger } from './wagon-stock-ledger.util';
const WHOLE = { fromEdge: 0, toEdge: 1 };
describe('WagonStockLedger', () => {
it('reports the wagons of a booking\'s OWN types, not the train total', () => {
// The reported case: 20 free wagons on the train, but only 16 of them NW5.
const ledger = new WagonStockLedger(
new Map([
['nw5', 16],
['pw2', 4],
]),
1,
);
expect(ledger.availableFor(['nw5'], WHOLE)).toBe(16);
expect(ledger.availableFor(['pw2'], WHOLE)).toBe(4);
// A cargo type mapped to both may ride either, so they add up.
expect(ledger.availableFor(['nw5', 'pw2'], WHOLE)).toBe(20);
// Duplicates must not double-count.
expect(ledger.availableFor(['nw5', 'nw5'], WHOLE)).toBe(16);
// An unconfigured type has no stock.
expect(ledger.availableFor(['unknown'], WHOLE)).toBe(0);
});
it('consumes what it can and reports the shortfall', () => {
const ledger = new WagonStockLedger(new Map([['nw5', 16]]), 1);
// A 20-wagon booking can only take 16 — the caller splits on that number.
expect(ledger.consume(['nw5'], 20, WHOLE)).toBe(16);
expect(ledger.availableFor(['nw5'], WHOLE)).toBe(0);
expect(ledger.consume(['nw5'], 1, WHOLE)).toBe(0);
});
it('drains the deepest stock first across candidate types', () => {
const ledger = new WagonStockLedger(
new Map([
['nw5', 10],
['nw7', 3],
]),
1,
);
expect(ledger.consume(['nw5', 'nw7'], 12, WHOLE)).toBe(12);
// 10 from NW5 then 2 from NW7 — one NW7 left.
expect(ledger.availableFor(['nw7'], WHOLE)).toBe(1);
expect(ledger.availableFor(['nw5'], WHOLE)).toBe(0);
});
it('frees stock past an alight yard — disjoint legs never compete', () => {
// Three stops (A→B→C) = two edges. An intercity booking riding A→B must
// not consume the wagon on B→C.
const ledger = new WagonStockLedger(new Map([['nw5', 5]]), 2);
const firstLeg = { fromEdge: 0, toEdge: 1 };
const secondLeg = { fromEdge: 1, toEdge: 2 };
ledger.consume(['nw5'], 5, firstLeg);
expect(ledger.availableFor(['nw5'], firstLeg)).toBe(0);
expect(ledger.availableFor(['nw5'], secondLeg)).toBe(5);
// A whole-route booking sees the busiest edge it crosses, so it is blocked.
expect(ledger.availableFor(['nw5'], { fromEdge: 0, toEdge: 2 })).toBe(0);
});
it('counts the busiest edge within a leg, not the sum of edges', () => {
const ledger = new WagonStockLedger(new Map([['nw5', 10]]), 3);
ledger.consume(['nw5'], 4, { fromEdge: 0, toEdge: 1 });
ledger.consume(['nw5'], 6, { fromEdge: 1, toEdge: 2 });
// Edge 0 uses 4, edge 1 uses 6 — a booking over both needs 10 free at once.
expect(ledger.availableFor(['nw5'], { fromEdge: 0, toEdge: 2 })).toBe(4);
expect(ledger.availableFor(['nw5'], { fromEdge: 2, toEdge: 3 })).toBe(10);
});
});