changes export flow

This commit is contained in:
Marshal
2026-07-30 11:30:34 +00:00
parent b671f07ce6
commit 2ca04b8f98
47 changed files with 1195 additions and 74 deletions

View File

@@ -213,6 +213,7 @@ export function effectiveWindowConfig(
ruleWindowCloseHour?: number | null;
ruleWindowDurationHours?: number | null;
ruleReopenDelayMinutes?: number | null;
rulePaymentWindowMinutes?: number | null;
ruleImportWindowLeadDays?: number | null;
ruleExportBookingLeadHours?: number | null;
ruleImportCloseOffsetMinutes?: number | null;
@@ -232,7 +233,14 @@ export function effectiveWindowConfig(
? Number(schedule.ruleWindowDurationHours)
: liveCfg.windowDurationHours,
docReviewMinutes: liveCfg.docReviewMinutes,
paymentWindowMinutes: liveCfg.paymentWindowMinutes,
// Pay windows read live values unless staff explicitly overrode this ONE
// schedule (rule_payment_window_minutes is only ever written by that
// override, never stamped at creation). The override wins for whichever
// direction the schedule runs.
paymentWindowMinutes:
schedule.rulePaymentWindowMinutes ?? liveCfg.paymentWindowMinutes,
exportPaymentWindowMinutes:
schedule.rulePaymentWindowMinutes ?? liveCfg.exportPaymentWindowMinutes,
// The close offset is frozen per-schedule: a snapshot value of null means
// "created with no offset" and must NOT inherit a later live offset (that
// would retro-shrink an open train's window). Only a truly legacy row that
@@ -696,6 +704,8 @@ export class TrainSchedulingService {
if (dto.windowDurationHours != null) row.windowDurationHours = dto.windowDurationHours;
if (dto.docReviewMinutes != null) row.docReviewMinutes = dto.docReviewMinutes;
if (dto.paymentWindowMinutes != null) row.paymentWindowMinutes = dto.paymentWindowMinutes;
if (dto.exportPaymentWindowMinutes != null)
row.exportPaymentWindowMinutes = dto.exportPaymentWindowMinutes;
// Store 0 as null so "no offset" is a single canonical value.
if (dto.importCloseOffsetMinutes !== undefined)
row.importCloseOffsetMinutes = dto.importCloseOffsetMinutes || null;
@@ -790,7 +800,14 @@ export class TrainSchedulingService {
// The reopen gap is doc review + payment; keep the config values unless the
// override changes them, so the derived snapshot delay stays consistent.
docReviewMinutes: dto.docReviewMinutes ?? liveCfg.docReviewMinutes,
paymentWindowMinutes: dto.paymentWindowMinutes ?? liveCfg.paymentWindowMinutes,
paymentWindowMinutes:
dto.paymentWindowMinutes ??
schedule.rulePaymentWindowMinutes ??
liveCfg.paymentWindowMinutes,
exportPaymentWindowMinutes:
dto.paymentWindowMinutes ??
schedule.rulePaymentWindowMinutes ??
liveCfg.exportPaymentWindowMinutes,
// A per-schedule override isn't a close-offset control, so inherit the
// offset already frozen on the schedule (null = none), or the live one for
// legacy rows — the override must not silently drop the global offset.
@@ -853,11 +870,17 @@ export class TrainSchedulingService {
}
}
// The pay-window override persists only when staff actually sent it (or the
// schedule already had one) — windowRuleSnapshot never stamps it, so NULL
// keeps meaning "follow the live global value for my direction".
const rulePaymentWindowMinutes =
dto.paymentWindowMinutes ?? schedule.rulePaymentWindowMinutes ?? null;
for (const t of targets) {
await repo.update(t.id, {
windowOpensAt: cap(times.windowOpensAt, t.departure),
windowClosesAt: cap(times.windowClosesAt, t.departure),
...ruleFields,
rulePaymentWindowMinutes,
});
}
this.logger.log(
@@ -1199,6 +1222,7 @@ export class TrainSchedulingService {
windowDurationHours: num(row?.windowDurationHours, 3),
docReviewMinutes: num(row?.docReviewMinutes, 30),
paymentWindowMinutes: num(row?.paymentWindowMinutes, 60),
exportPaymentWindowMinutes: num(row?.exportPaymentWindowMinutes, 60),
importCloseOffsetMinutes: offset(row?.importCloseOffsetMinutes),
exportCloseOffsetMinutes: offset(row?.exportCloseOffsetMinutes),
};
@@ -1931,8 +1955,14 @@ export class TrainSchedulingService {
removedAt: new Date(),
});
console.log(
`[NOTIFY] Booking ${bookingReference} removed from schedule ${scheduleId} by user ${userId ?? 'unknown'} — customer should be notified to reschedule or cancel.`,
// Ops decision, so the customer hears about it: SMS/email + inbox telling
// them to rebook or pick a new schedule (the removal log above is the record).
const removedBooking = await this.dataSource
.getRepository(Booking)
.findOne({ where: { id: bookingId }, relations: { company: true } });
if (removedBooking) this.bookingNotifier.removedFromTrain(removedBooking);
this.logger.log(
`Booking ${bookingReference} removed from schedule ${scheduleId} by user ${userId ?? 'unknown'} — customer notified to reschedule or cancel.`,
);
return this.getTrainScheduleById(scheduleId);
@@ -6778,7 +6808,14 @@ export class TrainSchedulingService {
importWindowLeadDays: schedule.ruleImportWindowLeadDays ?? null,
exportBookingLeadHours: schedule.ruleExportBookingLeadHours ?? null,
docReviewMinutes: windowCfg.docReviewMinutes,
paymentWindowMinutes: windowCfg.paymentWindowMinutes,
// Editor prefill: this schedule's own override when staff set one,
// else the live global for the schedule's direction (import/export
// pay windows are tuned separately).
paymentWindowMinutes:
schedule.rulePaymentWindowMinutes ??
(schedule.direction === 'EXPORT'
? windowCfg.exportPaymentWindowMinutes
: windowCfg.paymentWindowMinutes),
},
route: schedule.route
? { id: schedule.route.id, name: formatRouteLabel(schedule.route) }