mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-04 18:23:40 +00:00
The OCC report publishes total loading and unloading time and the other activity left over from a station stay, but nothing recorded when handling started or ended — the July 2026 seed had to write the figure into a checkpoint note. Four nullable stamps now ride the stop's arrival row, which is the row the staying-time report builds a stay from (a turnaround's departure belongs to a different schedule). Handling is unloading start to loading end, so a container stop reads as one window and a bulk station that only loads or only unloads still reports its half; other activity is the rest of the stay. Both stay NULL where nothing was logged rather than collapsing to zero. - station-staying-time: + loading/unloading and other activity per stop - turnaround-cycle: + the same, summed over the cycle's stops - loading-unloading (new): per train per station per period, so a week or month view is that train's average over its stops - the stop/stay query moves to operations-classification, shared by both
70 lines
2.6 KiB
TypeScript
70 lines
2.6 KiB
TypeScript
import { TrainSchedulingService } from './services/train-scheduling.service';
|
|
import { TrainCheckpointEvent } from './entities/train-checkpoint-event.entity';
|
|
|
|
/**
|
|
* Loading and unloading stamps decide two published figures — total handling
|
|
* (unloading start to loading end) and the other activity left over from the
|
|
* stay. A crossed or future pair would publish negative hours, so the guard is
|
|
* the thing worth pinning down.
|
|
*/
|
|
const svc = Object.create(TrainSchedulingService.prototype) as {
|
|
handlingPatch: (
|
|
dto: Record<string, string | null | undefined>,
|
|
existing?: TrainCheckpointEvent | null,
|
|
) => Record<string, Date | null>;
|
|
};
|
|
|
|
const iso = (h: number): string => new Date(Date.UTC(2026, 6, 3, h)).toISOString();
|
|
const stop = (fields: Partial<TrainCheckpointEvent>) => fields as TrainCheckpointEvent;
|
|
|
|
describe('checkpoint handling times', () => {
|
|
it('takes a sane handling window', () => {
|
|
const patch = svc.handlingPatch({
|
|
unloadingStartedAt: iso(4),
|
|
loadingCompletedAt: iso(9),
|
|
});
|
|
|
|
expect(patch.unloadingStartedAt).toEqual(new Date(iso(4)));
|
|
expect(patch.loadingCompletedAt).toEqual(new Date(iso(9)));
|
|
});
|
|
|
|
it('rejects loading finishing before unloading started', () => {
|
|
expect(() =>
|
|
svc.handlingPatch({ unloadingStartedAt: iso(9), loadingCompletedAt: iso(4) }),
|
|
).toThrow('Loading cannot finish before unloading started');
|
|
});
|
|
|
|
it('rejects a window that runs backwards', () => {
|
|
expect(() =>
|
|
svc.handlingPatch({ loadingStartedAt: iso(9), loadingCompletedAt: iso(8) }),
|
|
).toThrow('Loading cannot finish before it started');
|
|
});
|
|
|
|
it('rejects a stamp in the future', () => {
|
|
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
expect(() => svc.handlingPatch({ unloadingStartedAt: tomorrow })).toThrow(
|
|
'Unloading start cannot be in the future',
|
|
);
|
|
});
|
|
|
|
// A body that moves one end of a window is still checked against the end
|
|
// already stored, or a two-step edit could walk the stop into a crossed pair.
|
|
it('checks a one-sided edit against the stored stop', () => {
|
|
expect(() =>
|
|
svc.handlingPatch(
|
|
{ loadingCompletedAt: iso(4) },
|
|
stop({ unloadingStartedAt: new Date(iso(9)) }),
|
|
),
|
|
).toThrow('Loading cannot finish before unloading started');
|
|
});
|
|
|
|
it('clears a stamp on null and leaves an untouched one alone', () => {
|
|
const patch = svc.handlingPatch(
|
|
{ unloadingStartedAt: null },
|
|
stop({ unloadingStartedAt: new Date(iso(4)), loadingCompletedAt: new Date(iso(9)) }),
|
|
);
|
|
|
|
expect(patch).toEqual({ unloadingStartedAt: null });
|
|
});
|
|
});
|