mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
Add window close hour to booking desk configuration
This commit is contained in:
@@ -11,7 +11,7 @@ import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { BookingBatchService } from './booking-batch.service';
|
||||
import { TrainSchedulingService } from './train-scheduling.service';
|
||||
import { BATCH_TIMEZONE } from './booking-batch.constants';
|
||||
import { eatDay } from './batch-window.util';
|
||||
import { eatDay, nextCycleOpensAt, type OfficeHours } from './batch-window.util';
|
||||
import { type BookingWindowConfig } from './booking-window.config';
|
||||
|
||||
/**
|
||||
@@ -64,7 +64,9 @@ export class BookingWindowService implements OnModuleInit {
|
||||
],
|
||||
})
|
||||
).filter(
|
||||
(s) => s.windowPhase != null && s.windowPhase !== 'DONE' && s.windowPhase !== 'CLOSED_FOR_DAY',
|
||||
// CLOSED_FOR_DAY is legacy (the daily desk now reopens via PRE_WINDOW):
|
||||
// still pick those rows up so advanceImport can revive them next morning.
|
||||
(s) => s.windowPhase != null && s.windowPhase !== 'DONE',
|
||||
);
|
||||
|
||||
for (const schedule of active) {
|
||||
@@ -185,6 +187,14 @@ export class BookingWindowService implements OnModuleInit {
|
||||
): Promise<boolean> {
|
||||
const { windowPhase, windowOpensAt, windowClosesAt } = schedule;
|
||||
|
||||
// Legacy rows parked at CLOSED_FOR_DAY predate the daily-desk reopen: revive
|
||||
// them through the same not-full conclude path so they resume next morning
|
||||
// (or finalize as DONE if no cycle fits before departure).
|
||||
if (windowPhase === 'CLOSED_FOR_DAY') {
|
||||
await this.concludeCycle(schedule, cfg, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (windowPhase === 'PRE_WINDOW' && windowOpensAt && now >= windowOpensAt) {
|
||||
await this.setPhase(schedule, {
|
||||
windowPhase: 'OPEN',
|
||||
@@ -268,34 +278,46 @@ export class BookingWindowService implements OnModuleInit {
|
||||
return;
|
||||
}
|
||||
|
||||
const closesAt = schedule.windowClosesAt ?? now;
|
||||
const reopenAt = new Date(closesAt.getTime() + cfg.reopenDelayMinutes * 60_000);
|
||||
const nextOpensAt = reopenAt > now ? reopenAt : now;
|
||||
let nextClosesAt = new Date(nextOpensAt.getTime() + cfg.windowDurationHours * 3_600_000);
|
||||
// Doc review + payment have already run, so the desk is ready to reopen NOW —
|
||||
// office hours decide whether that is this afternoon or tomorrow morning. Past
|
||||
// the last cycle before departure, nextCycleOpensAt returns null and we finish.
|
||||
const officeHours: OfficeHours = {
|
||||
windowOpenHour: cfg.windowOpenHour,
|
||||
windowCloseHour: cfg.windowCloseHour,
|
||||
};
|
||||
const nextOpensAt = nextCycleOpensAt(
|
||||
now,
|
||||
officeHours,
|
||||
schedule.scheduledDepartureDate,
|
||||
);
|
||||
if (nextOpensAt == null) {
|
||||
await this.setPhase(schedule, { windowPhase: 'DONE' });
|
||||
this.logger.log(
|
||||
`Schedule ${schedule.id} not full but no cycle fits before departure — window done`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let nextClosesAt = new Date(
|
||||
nextOpensAt.getTime() + cfg.windowDurationHours * 3_600_000,
|
||||
);
|
||||
if (nextClosesAt > schedule.scheduledDepartureDate) {
|
||||
nextClosesAt = schedule.scheduledDepartureDate;
|
||||
}
|
||||
|
||||
const sameBookingDay = eatDay(nextOpensAt) === eatDay(closesAt);
|
||||
const beforeDeparture = nextOpensAt < schedule.scheduledDepartureDate;
|
||||
if (sameBookingDay && beforeDeparture) {
|
||||
await this.setPhase(schedule, {
|
||||
windowPhase: 'PRE_WINDOW',
|
||||
windowOpensAt: nextOpensAt,
|
||||
windowClosesAt: nextClosesAt,
|
||||
docReviewCompletedAt: null,
|
||||
docReviewEndsAt: null,
|
||||
paymentPhaseEndsAt: null,
|
||||
});
|
||||
this.logger.log(
|
||||
`Schedule ${schedule.id} not full — window reopens at ${nextOpensAt.toISOString()}`,
|
||||
);
|
||||
} else {
|
||||
await this.setPhase(schedule, { windowPhase: 'CLOSED_FOR_DAY' });
|
||||
this.logger.log(
|
||||
`Booking day over for schedule ${schedule.id} — remaining capacity is staff-managed`,
|
||||
);
|
||||
}
|
||||
// Stays PRE_WINDOW (not CLOSED_FOR_DAY): the tick reopens it at nextOpensAt,
|
||||
// whether that is later today or next morning after the office-hours break.
|
||||
await this.setPhase(schedule, {
|
||||
windowPhase: 'PRE_WINDOW',
|
||||
windowOpensAt: nextOpensAt,
|
||||
windowClosesAt: nextClosesAt,
|
||||
docReviewCompletedAt: null,
|
||||
docReviewEndsAt: null,
|
||||
paymentPhaseEndsAt: null,
|
||||
});
|
||||
const sameDay = eatDay(nextOpensAt) === eatDay(now);
|
||||
this.logger.log(
|
||||
`Schedule ${schedule.id} not full — window reopens ${sameDay ? 'today' : 'next booking day'} at ${nextOpensAt.toISOString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async tryAutoFinalize(scheduleId: string): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user