giveme git commit message 50 char

This commit is contained in:
Marshal
2026-08-06 23:48:06 +00:00
parent 29f7d05800
commit 0f4aa1128f
12 changed files with 147 additions and 33 deletions

View File

@@ -31,6 +31,19 @@ export function paymentDrainMs(): number {
);
}
/**
* ISO timestamp of the end of a pay window's drain tail, for client display
* (the "payment processing" countdown). Null in ⇒ null out.
*/
export function paymentDrainEndsAtIso(
deadline: Date | string | null | undefined,
): string | null {
if (deadline == null) return null;
const ms = new Date(deadline).getTime();
if (!Number.isFinite(ms)) return null;
return new Date(ms + paymentDrainMs()).toISOString();
}
/**
* A pay window AND its drain tail have closed.
*

View File

@@ -14,6 +14,7 @@ import { Server, Socket } from 'socket.io';
import { WsAuthService } from '../notification-inbox/ws-auth.service';
import { TrainSchedule } from '../train-schedules/entities/train-schedule.entity';
import { paymentDrainEndsAtIso } from './booking-batch.constants';
/**
* Server → client push for booking-window state changes. Same handshake model
@@ -61,6 +62,7 @@ export class BookingWindowGateway implements OnGatewayConnection {
windowClosesAt: schedule.windowClosesAt?.toISOString() ?? null,
docReviewEndsAt: schedule.docReviewEndsAt?.toISOString() ?? null,
paymentPhaseEndsAt: schedule.paymentPhaseEndsAt?.toISOString() ?? null,
paymentDrainEndsAt: paymentDrainEndsAtIso(schedule.paymentPhaseEndsAt),
scheduledDepartureDate: schedule.scheduledDepartureDate?.toISOString() ?? null,
};
this.server.emit(BOOKING_WINDOW_WS_EVENTS.PHASE, payload);

View File

@@ -1,5 +1,6 @@
import {
DEFAULT_PAYMENT_DRAIN_MINUTES,
paymentDrainEndsAtIso,
paymentDrainMs,
payWindowLapsed,
} from "./booking-batch.constants";
@@ -64,4 +65,16 @@ describe("payWindowLapsed — pay-window drain tail", () => {
expect(paymentDrainMs()).toBe(DEFAULT_PAYMENT_DRAIN_MINUTES * MIN);
}
});
it("paymentDrainEndsAtIso reports deadline + drain, null/garbage-safe", () => {
expect(paymentDrainEndsAtIso(deadline)).toBe(
new Date(at(DEFAULT_PAYMENT_DRAIN_MINUTES * MIN)).toISOString(),
);
expect(paymentDrainEndsAtIso(deadline.toISOString())).toBe(
new Date(at(DEFAULT_PAYMENT_DRAIN_MINUTES * MIN)).toISOString(),
);
expect(paymentDrainEndsAtIso(null)).toBeNull();
expect(paymentDrainEndsAtIso(undefined)).toBeNull();
expect(paymentDrainEndsAtIso("not-a-date")).toBeNull();
});
});

View File

@@ -153,6 +153,7 @@ import {
DEFAULT_CONTAINER_WAGON_CAPACITY_TONS,
DEFAULT_CONTAINER_WAGON_LENGTH_METERS,
DEFAULT_CONTAINER_WAGON_TARE_TONS,
paymentDrainEndsAtIso,
} from './booking-batch.constants';
import { orderConsistWagons } from './consist-order.util';
import {
@@ -6940,6 +6941,7 @@ export class TrainSchedulingService {
windowClosesAt: r.window_closes_at,
docReviewEndsAt: r.doc_review_ends_at,
paymentPhaseEndsAt: r.payment_phase_ends_at,
paymentDrainEndsAt: paymentDrainEndsAtIso(r.payment_phase_ends_at),
bookingWindowStatus: r.booking_window_status,
bookingCycleNo: r.booking_cycle_no,
departureDate: r.scheduled_departure_date,

View File

@@ -39,6 +39,8 @@ interface WindowRow {
windowClosesAt: string | null;
docReviewEndsAt: string | null;
paymentPhaseEndsAt: string | null;
/** End of the payment drain tail — pending payments may settle until then. */
paymentDrainEndsAt?: string | null;
bookingWindowStatus: string;
bookingCycleNo: number;
departureDate: string;
@@ -94,16 +96,29 @@ const COUNTDOWN_TEXT: Partial<
PRE_WINDOW: { label: "Opens in", expiredText: "Opening now…" },
OPEN: { label: "Closes in", expiredText: "Review starting…" },
DOC_REVIEW: { label: "Doc review ends in", expiredText: "Payment starting…" },
PAYMENT: { label: "Payment ends in", expiredText: "Closing…" },
PAYMENT: { label: "Payment ends in", expiredText: "Finalizing…" },
};
function phaseCountdown(
w: WindowRow,
): { label: string; deadline: string; expiredText: string } | null {
function phaseCountdown(w: WindowRow): {
label: string;
deadline: string;
expiredText: string;
graceDeadline?: string | null;
graceLabel?: string;
} | null {
const state = bookingWindowUiState(w);
const text = COUNTDOWN_TEXT[state.kind];
if (!state.countdownTo || !text) return null;
return { ...text, deadline: state.countdownTo };
// Once the pay deadline lapses, pending payments still settle during the
// drain tail — count it down as "processing" instead of a stale "closing".
const grace =
state.kind === "PAYMENT" && w.paymentDrainEndsAt
? {
graceDeadline: w.paymentDrainEndsAt,
graceLabel: "Processing payments — closes in",
}
: undefined;
return { ...text, deadline: state.countdownTo, ...grace };
}
/** Badge label + Mantine color per UI state — same state the countdown uses. */
@@ -225,6 +240,8 @@ function WindowCard({ w }: { w: WindowRow }) {
deadline={cd.deadline}
label={cd.label}
expiredText={cd.expiredText}
graceDeadline={cd.graceDeadline}
graceLabel={cd.graceLabel}
size="xs"
/>
</Box>

View File

@@ -30,6 +30,7 @@ interface WindowRow {
windowClosesAt: string | null;
docReviewEndsAt: string | null;
paymentPhaseEndsAt: string | null;
paymentDrainEndsAt?: string | null;
bookingWindowStatus: string;
bookingCycleNo: number;
departureDate: string;
@@ -55,6 +56,7 @@ function applyEvent<T extends WindowRow>(row: T, event: BookingWindowPhaseEvent)
windowClosesAt: event.windowClosesAt,
docReviewEndsAt: event.docReviewEndsAt,
paymentPhaseEndsAt: event.paymentPhaseEndsAt,
paymentDrainEndsAt: event.paymentDrainEndsAt,
departureDate: event.scheduledDepartureDate ?? row.departureDate,
};
}

View File

@@ -47,6 +47,7 @@ function applyEvent(
windowClosesAt: event.windowClosesAt,
docReviewEndsAt: event.docReviewEndsAt,
paymentPhaseEndsAt: event.paymentPhaseEndsAt,
paymentDrainEndsAt: event.paymentDrainEndsAt,
departureDate: event.scheduledDepartureDate ?? row.departureDate,
};
}

View File

@@ -68,16 +68,29 @@ const COUNTDOWN_TEXT: Partial<
PRE_WINDOW: { label: "Booking opens in", expiredText: "Booking opening now…" },
OPEN: { label: "Window closes in", expiredText: "Document review starting…" },
DOC_REVIEW: { label: "Document review ends in", expiredText: "Payment starting…" },
PAYMENT: { label: "Payment due in", expiredText: "Payment window closing…" },
PAYMENT: { label: "Payment due in", expiredText: "Finalizing payments…" },
};
function phaseCountdown(
w: MyBookingWindow,
): { label: string; deadline: string; expiredText: string } | null {
function phaseCountdown(w: MyBookingWindow): {
label: string;
deadline: string;
expiredText: string;
graceDeadline?: string | null;
graceLabel?: string;
} | null {
const state = bookingWindowUiState(w);
const text = COUNTDOWN_TEXT[state.kind];
if (!state.countdownTo || !text) return null;
return { ...text, deadline: state.countdownTo };
// Once the pay deadline lapses, pending payments still settle during the
// drain tail — count it down as "processing" instead of a stale "closing".
const grace =
state.kind === "PAYMENT" && w.paymentDrainEndsAt
? {
graceDeadline: w.paymentDrainEndsAt,
graceLabel: "Payment processing — closes in",
}
: undefined;
return { ...text, deadline: state.countdownTo, ...grace };
}
function Pill({
@@ -336,6 +349,8 @@ export const UpcomingWindowsSection = memo(function UpcomingWindowsSection({
deadline={cd.deadline}
label={cd.label}
expiredText={cd.expiredText}
graceDeadline={cd.graceDeadline}
graceLabel={cd.graceLabel}
size="xs"
/>
</Box>

View File

@@ -104,16 +104,29 @@ const COUNTDOWN_TEXT: Partial<
PRE_WINDOW: { label: "Booking opens in", expiredText: "Booking opening now…" },
OPEN: { label: "Window closes in", expiredText: "Document review starting…" },
DOC_REVIEW: { label: "Document review ends in", expiredText: "Payment starting…" },
PAYMENT: { label: "Payment due in", expiredText: "Payment window closing…" },
PAYMENT: { label: "Payment due in", expiredText: "Finalizing payments…" },
};
function phaseCountdown(
w: MyBookingWindow,
): { label: string; deadline: string; expiredText: string } | null {
function phaseCountdown(w: MyBookingWindow): {
label: string;
deadline: string;
expiredText: string;
graceDeadline?: string | null;
graceLabel?: string;
} | null {
const state = bookingWindowUiState(w);
const text = COUNTDOWN_TEXT[state.kind];
if (!state.countdownTo || !text) return null;
return { ...text, deadline: state.countdownTo };
// Once the pay deadline lapses, pending payments still settle during the
// drain tail — count it down as "processing" instead of a stale "closing".
const grace =
state.kind === "PAYMENT" && w.paymentDrainEndsAt
? {
graceDeadline: w.paymentDrainEndsAt,
graceLabel: "Payment processing — closes in",
}
: undefined;
return { ...text, deadline: state.countdownTo, ...grace };
}
/**
@@ -226,6 +239,8 @@ function WindowCard({ w }: { w: MyBookingWindow }) {
deadline={cd.deadline}
label={cd.label}
expiredText={cd.expiredText}
graceDeadline={cd.graceDeadline}
graceLabel={cd.graceLabel}
size="xs"
/>
</Box>

View File

@@ -91,6 +91,8 @@ export interface MyBookingWindow {
windowClosesAt: string | null;
docReviewEndsAt: string | null;
paymentPhaseEndsAt: string | null;
/** End of the payment drain tail — pending payments may settle until then. */
paymentDrainEndsAt: string | null;
bookingWindowStatus: string;
bookingCycleNo: number;
departureDate: string;