enhance booking window logic to support overnight desk configurations and update related UI components

This commit is contained in:
Marshal
2026-07-05 21:29:09 +00:00
parent 3485a7d63d
commit caa8397563
5 changed files with 83 additions and 36 deletions

View File

@@ -295,16 +295,10 @@ 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.`,
);
}
// The booking desk supports three shapes: a same-day range
// (closeHour > openHour), a 24-hour desk (openHour === closeHour), and an
// overnight range that wraps past midnight (openHour > closeHour, e.g.
// 08:00 → 07:00). officeHoursOpen handles all three, so no ordering guard.
// 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
@@ -389,12 +383,8 @@ export class TrainSchedulingService {
reopenDelayMinutes: liveCfg.reopenDelayMinutes,
};
if (merged.windowCloseHour < merged.windowOpenHour) {
throw new BadRequestException(
`Window close hour (${merged.windowCloseHour}) must be on or after the open hour ` +
`(${merged.windowOpenHour}); set them equal for a 24-hour desk.`,
);
}
// Same-day, 24-hour, and overnight (openHour > closeHour) desks are all valid
// — officeHoursOpen resolves each, so no close-vs-open ordering guard here.
const times =
schedule.direction === 'EXPORT'
@@ -1527,9 +1517,21 @@ export class TrainSchedulingService {
if (!schedule) {
throw new NotFoundException(`Train schedule ${scheduleId} not found`);
}
// Import-Djibouti trains gate dispatch on the operation's loadedOnTrainAt.
if (this.isImportDjiboutiSchedule(schedule)) {
await this.confirmImportLoadedOnTrain(scheduleId, dto);
}
// Confirming loading also marks every wagon-assigned booking LOADED, so the
// per-booking loading flag and the dispatch gate agree (otherwise the
// dispatch pre-check keeps reporting these bookings as unloaded).
const wagonAssignedIds = await this.getWagonAssignedBookingIds(scheduleId);
if (wagonAssignedIds.size) {
await this.trainScheduleBookingsRepository.updateLoadingStatusMany(
scheduleId,
[...wagonAssignedIds],
LoadingStatus.Loaded,
);
}
return this.getTrainScheduleById(scheduleId);
}