Merge pull request #1459 from Tria-plc/Wrehousechanges

fix(train-scheduling): scope numbered marshalling docs to their own s…
This commit is contained in:
Hagernesh Tadesse
2026-08-29 12:45:10 +03:00
committed by GitHub
2 changed files with 64 additions and 1 deletions

View File

@@ -1406,6 +1406,30 @@ describe('TrainSchedulingService', () => {
expect(numbers).toEqual(['W-LEG2']);
});
it('drops a leg slot LOADED by generation time but not yet coupled as of this stop', () => {
// Both W-DIRE (coupled+loaded at Dire Dawa) and W-ADAMA (coupled+loaded
// at Adama, a LATER stop) read identically to intercityOnBoardView by
// the time this runs — both LOADED right now. Only the adjustment log
// knows W-ADAMA hadn't coupled yet as of Dire Dawa's own timestamp.
const wholeRoute = makeWagon(1, 'W-001', [allocWith({ status: 'LOADED' })]);
const legDireDawa = { ...makeWagon(2, 'W-DIRE', [allocWith({ status: 'LOADED' })]), boardYardId: 'yard-dire' };
const legAdama = { ...makeWagon(3, 'W-ADAMA', [allocWith({ status: 'LOADED' })]), boardYardId: 'yard-adama' };
const schedule = { trainSet: { wagons: [wholeRoute, legDireDawa, legAdama] }, scheduleBookings: [] };
const { wagons } = onBoardView(schedule);
const boardedByDireDawa = new Set(['W-DIRE']); // logged ADD only up to Dire Dawa's stop
const wagonsAsOfStop = (service as never as {
wagonsAsOfStop: (w: unknown, s: Set<string>) => Array<{ physicalWagon: { wagonNumber: string } }>;
}).wagonsAsOfStop.bind(service);
const asOfDireDawa = wagonsAsOfStop(wagons, boardedByDireDawa);
expect(asOfDireDawa.map((w) => w.physicalWagon.wagonNumber)).toEqual(['W-001', 'W-DIRE']);
const boardedByAdama = new Set(['W-DIRE', 'W-ADAMA']); // both stops have now happened
const asOfAdama = wagonsAsOfStop(wagons, boardedByAdama);
expect(asOfAdama.map((w) => w.physicalWagon.wagonNumber)).toEqual(['W-001', 'W-DIRE', 'W-ADAMA']);
});
it('lists an IN_TRANSIT booking with no wagon allocation in the unassigned section', () => {
const rider = {
id: 'booking-9',

View File

@@ -28,6 +28,7 @@ import {
ILike,
In,
IsNull,
LessThanOrEqual,
Not,
QueryFailedError,
Raw,
@@ -3623,6 +3624,24 @@ export class TrainSchedulingService {
return { wagons, unassignedBookings };
}
/**
* Corrects intercityOnBoardView's CURRENT-state wagon list against a
* specific stop's document. intercityOnBoardView's "boardYardId == null ||
* hasLoaded" test reads whatever is true RIGHT NOW — it can't distinguish
* "this leg slot coupled at THIS stop" from "it coupled at a LATER stop
* that has, by generation time, also already happened" (both look LOADED).
* Reprinting an earlier stop's document after a later one has run would
* otherwise leak the later stop's wagons in. `boardedWagonNumbers` is the
* set of physical wagon numbers with a logged ADD at or before this stop
* (see marshallingDocumentAt) — the ground truth a real-time heuristic
* can't provide once multiple stops have already happened.
*/
private wagonsAsOfStop(wagons: TrainSetWagon[], boardedWagonNumbers: Set<string>): TrainSetWagon[] {
return wagons.filter(
(wagon) => wagon.boardYardId == null || boardedWagonNumbers.has(wagon.physicalWagon?.wagonNumber ?? ''),
);
}
/**
* Every corridor stop where the consist actually changed for this schedule
* (coupled, uncoupled, or switched — any flavor), in the order the train
@@ -3717,7 +3736,27 @@ export class TrainSchedulingService {
);
}
const { wagons, unassignedBookings } = this.intercityOnBoardView(schedule);
const { wagons: currentWagons, unassignedBookings } = this.intercityOnBoardView(schedule);
// intercityOnBoardView's "boardYardId == null || hasLoaded" test reads
// CURRENT state — it can't tell "coupled here" from "coupled at a LATER
// stop that has since also happened" (both look LOADED by generation
// time once the trip has moved past this stop). Reprinting Marshalling 2
// after Marshalling 3's stop already ran would otherwise show Marshalling
// 3's coupled wagons too. Correct it against the log: a leg-slot wagon
// belongs on THIS stop's document only if it actually has a logged ADD
// at or before THIS stop's own timestamp.
const boardedByThisStop = new Set(
(
await this.dataSource.getRepository(ScheduleWagonAdjustmentLog).find({
where: {
trainScheduleId: scheduleId,
action: 'ADD',
occurredAt: LessThanOrEqual(new Date(stop.firstOccurredAt)),
},
})
).map((row) => row.wagonNumber),
);
const wagons = this.wagonsAsOfStop(currentWagons, boardedByThisStop);
const logRows = await this.dataSource.getRepository(ScheduleWagonAdjustmentLog).find({
where: { trainScheduleId: scheduleId, yardId: stop.yardId },
order: { occurredAt: 'ASC' },