Add booking window management and locomotive scheduling features

- Implemented migration to release stuck assigned locomotives.
- Added schedule window phases to train schedules.
- Created booking batch offers table for partial capacity bookings.
- Developed BookingSplitService to handle partial booking offers and splits.
- Introduced BookingWindowService to manage booking window lifecycle and transitions.
- Added BookingBatchOffer entity to represent offers made during booking splits.
- Enhanced locomotive options with warnings for scheduling.
- Created UpcomingWindowsSection component to display upcoming booking windows.
This commit is contained in:
Marshal
2026-07-03 03:36:06 +00:00
parent 18c158fb61
commit 56de90892d
41 changed files with 2480 additions and 124 deletions

View File

@@ -83,6 +83,34 @@ export class TrainSchedule extends BaseEntity {
@Column({ name: 'booking_window_status', type: 'varchar', length: 10, default: 'OPEN' })
bookingWindowStatus!: string;
/**
* Booking-window lifecycle for the one-booking-day cycle
* (PRE_WINDOW → OPEN → DOC_REVIEW → PAYMENT → reopen | CLOSED_FOR_DAY | DONE).
* NULL on legacy and DOMESTIC schedules — the window engine ignores those.
*/
@Column({ name: 'window_phase', type: 'varchar', length: 20, nullable: true })
windowPhase?: string | null;
@Column({ name: 'window_opens_at', type: 'timestamptz', nullable: true })
windowOpensAt?: Date | null;
@Column({ name: 'window_closes_at', type: 'timestamptz', nullable: true })
windowClosesAt?: Date | null;
@Column({ name: 'doc_review_ends_at', type: 'timestamptz', nullable: true })
docReviewEndsAt?: Date | null;
/** Staff finished document review early — starts the batch/payment phase immediately. */
@Column({ name: 'doc_review_completed_at', type: 'timestamptz', nullable: true })
docReviewCompletedAt?: Date | null;
@Column({ name: 'payment_phase_ends_at', type: 'timestamptz', nullable: true })
paymentPhaseEndsAt?: Date | null;
/** 1-based count of open→settle cycles run on the booking day. */
@Column({ name: 'booking_cycle_no', type: 'int', default: 0 })
bookingCycleNo!: number;
@OneToMany(() => TrainScheduleBooking, (scheduleBooking) => scheduleBooking.trainSchedule)
scheduleBookings?: TrainScheduleBooking[];
}