fix(freight): guard booking-desk hours against overnight ranges

Review follow-ups on the window-close-hour feature:

- Reject windowCloseHour < windowOpenHour when saving global rules. The
  reopen engine (nextCycleOpensAt) assumes the daily desk runs within one
  EAT day; an overnight range would misroute a ready-time inside the span
  to the next morning. openHour === closeHour stays valid (24-hour desk).
- Derive the board projection's runaway cap from the real first-open →
  departure span over the minimum per-cycle advance, so a legitimate
  long-lead config is never silently truncated (was a flat 200).
- Document the open <= close precondition on nextCycleOpensAt and clarify
  the ready-before-open comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marshal
2026-07-05 09:07:21 +00:00
parent 93b8b69464
commit dfdf7a025d
3 changed files with 31 additions and 8 deletions

View File

@@ -293,6 +293,17 @@ export class TrainSchedulingService {
if (dto.paymentWindowMinutes != null) row.paymentWindowMinutes = dto.paymentWindowMinutes;
if (dto.reopenDelayMinutes != null) row.reopenDelayMinutes = dto.reopenDelayMinutes;
// The daily booking desk runs [openHour, closeHour) within one EAT day, so
// the desk must not wrap past midnight. openHour === closeHour is the 24-hour
// desk; openHour > closeHour (an overnight range) is rejected — the reopen
// engine has no notion of a window that spans midnight.
if (row.windowCloseHour < row.windowOpenHour) {
throw new BadRequestException(
`Window close hour (${row.windowCloseHour}) must be on or after the open hour ` +
`(${row.windowOpenHour}); set them equal for a 24-hour desk.`,
);
}
// Fields that change the STAMPED open/close times of a schedule. docReview/
// payment/reopen are read live by the cron each tick, so they need no
// re-stamp; only the four below feed computeImport/ExportWindowTimes.