mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 11:21:18 +00:00
Add window close hour to booking desk configuration
This commit is contained in:
@@ -137,6 +137,62 @@ export function shiftEatDay(day: string, deltaDays: number): string {
|
||||
).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The daily office window `[openHour, closeHour)` in EAT: after `closeHour` the
|
||||
* booking desk is shut and reopens `openHour` the next morning. `openHour ===
|
||||
* closeHour` means a 24-hour desk that never breaks for the day.
|
||||
*/
|
||||
export interface OfficeHours {
|
||||
windowOpenHour: number;
|
||||
windowCloseHour: number;
|
||||
}
|
||||
|
||||
/** True when the desk runs round the clock (open hour equals close hour). */
|
||||
export function isRoundTheClock(hours: OfficeHours): boolean {
|
||||
return hours.windowOpenHour === hours.windowCloseHour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the NEXT booking cycle opens after a cycle closes at `closedAt`, given a
|
||||
* not-yet-full train and a daily office window. `earliestNextOpen` is the raw
|
||||
* ready time (close + doc-review + payment); the desk honours it only while
|
||||
* inside office hours:
|
||||
*
|
||||
* • round-the-clock desk → opens at `earliestNextOpen` (no day break)
|
||||
* • ready time before closeHour → opens at `earliestNextOpen`, same day
|
||||
* • ready time at/after closeHour → desk shut; opens next morning at openHour
|
||||
*
|
||||
* Returns `null` when the next open would fall on/after `departure` — the train
|
||||
* leaves before another cycle could run, so the window is done.
|
||||
*/
|
||||
export function nextCycleOpensAt(
|
||||
earliestNextOpen: Date,
|
||||
hours: OfficeHours,
|
||||
departure: Date,
|
||||
): Date | null {
|
||||
let opensAt: Date;
|
||||
if (isRoundTheClock(hours)) {
|
||||
opensAt = earliestNextOpen;
|
||||
} else {
|
||||
const { hour, minute } = eatParts(earliestNextOpen);
|
||||
const readyMinutes = hour * 60 + minute;
|
||||
const openMinutes = hours.windowOpenHour * 60;
|
||||
const closeMinutes = hours.windowCloseHour * 60;
|
||||
if (readyMinutes < openMinutes) {
|
||||
// Desk not open yet today (ready before opening) → open this morning.
|
||||
opensAt = eatDayToUtc(eatDay(earliestNextOpen), hours.windowOpenHour);
|
||||
} else if (readyMinutes < closeMinutes) {
|
||||
// Inside office hours → open as soon as ready.
|
||||
opensAt = earliestNextOpen;
|
||||
} else {
|
||||
// Desk shut for the day → open tomorrow morning.
|
||||
const tomorrow = shiftEatDay(eatDay(earliestNextOpen), 1);
|
||||
opensAt = eatDayToUtc(tomorrow, hours.windowOpenHour);
|
||||
}
|
||||
}
|
||||
return opensAt.getTime() < departure.getTime() ? opensAt : null;
|
||||
}
|
||||
|
||||
export interface InitialWindowTimes {
|
||||
windowOpensAt: Date;
|
||||
windowClosesAt: Date;
|
||||
@@ -269,7 +325,10 @@ export interface BoardWindow extends BatchWindow {
|
||||
export interface BoardWindowConfig {
|
||||
importWindowLeadDays: number;
|
||||
windowOpenHour: number;
|
||||
/** EAT hour the daily booking desk shuts; equals windowOpenHour for a 24h desk. */
|
||||
windowCloseHour: number;
|
||||
windowDurationHours: number;
|
||||
/** Gap between a cycle's close and its reopen (doc review + payment minutes). */
|
||||
reopenDelayMinutes: number;
|
||||
exportBookingLeadHours: number;
|
||||
}
|
||||
@@ -328,25 +387,27 @@ export function listConfigBookingWindows(
|
||||
|
||||
const windows: BoardWindow[] = [];
|
||||
const durationMs = cfg.windowDurationHours * 3_600_000;
|
||||
// Post-close gap before the next cycle opens (doc review + payment), subject
|
||||
// to office hours below.
|
||||
const reopenMs = cfg.reopenDelayMinutes * 60_000;
|
||||
const officeHours: OfficeHours = {
|
||||
windowOpenHour: cfg.windowOpenHour,
|
||||
windowCloseHour: cfg.windowCloseHour,
|
||||
};
|
||||
const windowDay = shiftEatDay(eatDay(departure), -cfg.importWindowLeadDays);
|
||||
|
||||
let opensAt = anchorOpensAt ?? eatDayToUtc(windowDay, cfg.windowOpenHour);
|
||||
// Reopen stays on the same EAT booking day and before departure; cap at 12 cycles.
|
||||
for (let cycle = 0; cycle < 12; cycle += 1) {
|
||||
let opensAt: Date | null = anchorOpensAt ?? eatDayToUtc(windowDay, cfg.windowOpenHour);
|
||||
// Reopen daily until the train departs; cap the projection so a tiny duration
|
||||
// can't run away (spanning up to importWindowLeadDays of office days).
|
||||
for (let cycle = 0; cycle < 200; cycle += 1) {
|
||||
if (opensAt.getTime() >= departure.getTime()) break;
|
||||
let closesAt = new Date(opensAt.getTime() + durationMs);
|
||||
if (closesAt.getTime() > departure.getTime()) closesAt = departure;
|
||||
windows.push(boardWindowFromInterval(opensAt, closesAt));
|
||||
|
||||
const nextOpensAt = new Date(closesAt.getTime() + reopenMs);
|
||||
if (
|
||||
nextOpensAt.getTime() >= departure.getTime() ||
|
||||
eatDay(nextOpensAt) !== eatDay(opensAt)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
opensAt = nextOpensAt;
|
||||
const earliestNextOpen = new Date(closesAt.getTime() + reopenMs);
|
||||
opensAt = nextCycleOpensAt(earliestNextOpen, officeHours, departure);
|
||||
if (opensAt == null) break;
|
||||
}
|
||||
|
||||
// Degenerate config (no window before departure) — surface a single window
|
||||
|
||||
Reference in New Issue
Block a user