mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 16:28:12 +00:00
remove reopen delay minutes from global rules and update related types
- Removed the field from and related components. - Updated to reflect the removal of the reopen delay input field. - Modified to include new train number fields: and . - Added interface to manage active schedules with trade direction. - Introduced interface to track wagon shortages in bookings. - Updated logic to ensure consistent UI state representation. - Created migrations to drop the column and add and columns to the table. - Added tests for the new booking window display logic and wagon planning functionality.
This commit is contained in:
@@ -17,7 +17,12 @@ import { BookingBatchService } from './booking-batch.service';
|
||||
import { BookingWindowGateway } from './booking-window.gateway';
|
||||
import { TrainSchedulingService, effectiveWindowConfig } from './train-scheduling.service';
|
||||
import { BATCH_TIMEZONE } from './booking-batch.constants';
|
||||
import { eatDay, nextCycleOpensAt, type OfficeHours } from './batch-window.util';
|
||||
import {
|
||||
clampCloseToOfficeHours,
|
||||
eatDay,
|
||||
nextCycleOpensAt,
|
||||
type OfficeHours,
|
||||
} from './batch-window.util';
|
||||
import { type BookingWindowConfig } from './booking-window.config';
|
||||
|
||||
/**
|
||||
@@ -297,6 +302,17 @@ export class BookingWindowService implements OnModuleInit {
|
||||
// (or allocating government) — skipped automatically for everyone who fits
|
||||
// is handled inside the fill (all fit → all reserved → all notified).
|
||||
await this.bookingBatchService.processRouteDay(routeDay);
|
||||
// Batch reserved nobody (empty pool, or it allocated without pay windows):
|
||||
// a PAYMENT phase with nobody to pay is a dead hour with the window shut.
|
||||
// Conclude straight away — full → DONE, otherwise reopen per office hours.
|
||||
if (!(await this.bookingBatchService.hasLiveReservations(schedule.id))) {
|
||||
this.logger.log(
|
||||
`[WINDOW] ${schedule.id} DOC_REVIEW→PAYMENT — batch reserved nothing; ` +
|
||||
`skipping the empty payment phase and concluding the cycle`,
|
||||
);
|
||||
await this.concludeCycle(schedule, cfg, now);
|
||||
return true;
|
||||
}
|
||||
this.logger.log(
|
||||
`[WINDOW] ${schedule.id} DOC_REVIEW→PAYMENT — batch ran; payment phase ` +
|
||||
`until ${paymentPhaseEndsAt.toISOString()}`,
|
||||
@@ -353,7 +369,10 @@ export class BookingWindowService implements OnModuleInit {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** After settle: full → finalize + DONE; space left → reopen same day or close for the day. */
|
||||
/**
|
||||
* After settle: full → finalize + DONE; waiting bookings still fit → fresh pay
|
||||
* window, back to PAYMENT; otherwise reopen (office hours decide when) or DONE.
|
||||
*/
|
||||
private async concludeCycle(
|
||||
schedule: TrainSchedule,
|
||||
cfg: BookingWindowConfig,
|
||||
@@ -386,6 +405,31 @@ export class BookingWindowService implements OnModuleInit {
|
||||
if (fresh) schedule.bookingWindowStatus = fresh.bookingWindowStatus;
|
||||
}
|
||||
|
||||
// The window reopens only once the waiting list is exhausted: a booking can
|
||||
// still reach the pool mid-payment (late doc accept, consolidation partner),
|
||||
// so retry the batch before reopening. Anything that fits gets a fresh pay
|
||||
// window and the cycle stays in PAYMENT; check live reservations on THIS
|
||||
// schedule because the day-level fill may have reserved onto a sibling.
|
||||
// Waiting bookings that fit no train stay pooled and the window reopens.
|
||||
const promoted = await this.bookingBatchService.fillFromWaitingList(schedule.id);
|
||||
if (
|
||||
promoted > 0 &&
|
||||
(await this.bookingBatchService.hasLiveReservations(schedule.id))
|
||||
) {
|
||||
let paymentPhaseEndsAt = new Date(
|
||||
now.getTime() + cfg.paymentWindowMinutes * 60_000,
|
||||
);
|
||||
if (paymentPhaseEndsAt > schedule.scheduledDepartureDate) {
|
||||
paymentPhaseEndsAt = schedule.scheduledDepartureDate;
|
||||
}
|
||||
await this.setPhase(schedule, { windowPhase: 'PAYMENT', paymentPhaseEndsAt });
|
||||
this.logger.log(
|
||||
`[WINDOW] ${schedule.id} conclude → waiting list still had bookings that ` +
|
||||
`fit — back in PAYMENT until ${paymentPhaseEndsAt.toISOString()}, no reopen yet`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -413,6 +457,9 @@ export class BookingWindowService implements OnModuleInit {
|
||||
let nextClosesAt = new Date(
|
||||
nextOpensAt.getTime() + cfg.windowDurationHours * 3_600_000,
|
||||
);
|
||||
// Office hours end a running window early: never let the duration outlive
|
||||
// the desk close (open 16:00, 3h, desk 8–17 → closes 17:00).
|
||||
nextClosesAt = clampCloseToOfficeHours(nextOpensAt, nextClosesAt, officeHours);
|
||||
if (nextClosesAt > schedule.scheduledDepartureDate) {
|
||||
nextClosesAt = schedule.scheduledDepartureDate;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user