add overnight desk handling and effective window configuration for train schedules

This commit is contained in:
Marshal
2026-07-05 22:22:04 +00:00
parent caa8397563
commit ac7bff8cc8
4 changed files with 89 additions and 24 deletions

View File

@@ -144,6 +144,48 @@ function windowRuleSnapshot(cfg: BookingWindowConfig) {
};
}
/**
* The booking-window config a specific schedule runs under: its frozen rule
* snapshot (open/close hour, duration, lead, reopen gap) overlaid on the live
* config, with the live config filling any snapshot field a legacy row lacks.
*
* The window SHAPE (hours, duration, lead, reopen gap) comes from the snapshot so
* the runtime cycle engine matches exactly what the board drew and the customer
* saw — a later global-rule edit must not retro-change an existing train. The
* doc-review / payment split is an internal process timing (not part of the
* window the customer sees) and is not stored split in the snapshot, so it always
* takes the live values; their sum is only used as a fallback reopen gap when the
* row predates `ruleReopenDelayMinutes`.
*/
export function effectiveWindowConfig(
schedule: {
ruleWindowOpenHour?: number | null;
ruleWindowCloseHour?: number | null;
ruleWindowDurationHours?: number | null;
ruleReopenDelayMinutes?: number | null;
ruleImportWindowLeadDays?: number | null;
ruleExportBookingLeadHours?: number | null;
},
liveCfg: BookingWindowConfig,
): BookingWindowConfig {
return {
importWindowLeadDays:
schedule.ruleImportWindowLeadDays ?? liveCfg.importWindowLeadDays,
exportBookingLeadHours:
schedule.ruleExportBookingLeadHours ?? liveCfg.exportBookingLeadHours,
windowOpenHour: schedule.ruleWindowOpenHour ?? liveCfg.windowOpenHour,
windowCloseHour: schedule.ruleWindowCloseHour ?? liveCfg.windowCloseHour,
windowDurationHours:
schedule.ruleWindowDurationHours != null
? Number(schedule.ruleWindowDurationHours)
: liveCfg.windowDurationHours,
docReviewMinutes: liveCfg.docReviewMinutes,
paymentWindowMinutes: liveCfg.paymentWindowMinutes,
reopenDelayMinutes:
schedule.ruleReopenDelayMinutes ?? liveCfg.reopenDelayMinutes,
};
}
export type BookingWagonAllocationStatus =
| 'NOT_ATTEMPTED'
| 'ASSIGNED'
@@ -456,21 +498,7 @@ export class TrainSchedulingService {
// Re-derive the window from the schedule's own rule snapshot (falling back to
// the live config where a legacy row has no snapshot) against the new date.
const merged: BookingWindowConfig = {
importWindowLeadDays:
schedule.ruleImportWindowLeadDays ?? windowCfg.importWindowLeadDays,
exportBookingLeadHours:
schedule.ruleExportBookingLeadHours ?? windowCfg.exportBookingLeadHours,
windowOpenHour: schedule.ruleWindowOpenHour ?? windowCfg.windowOpenHour,
windowCloseHour: schedule.ruleWindowCloseHour ?? windowCfg.windowCloseHour,
windowDurationHours:
schedule.ruleWindowDurationHours != null
? Number(schedule.ruleWindowDurationHours)
: windowCfg.windowDurationHours,
docReviewMinutes: windowCfg.docReviewMinutes,
paymentWindowMinutes: windowCfg.paymentWindowMinutes,
reopenDelayMinutes: windowCfg.reopenDelayMinutes,
};
const merged = effectiveWindowConfig(schedule, windowCfg);
const times =
schedule.direction === 'EXPORT'