You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
3.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/** Flock 登录态:首价后二级填写确认窗口(对齐 MS refine-hold) */
/** 用户决定是否继续填二级详情的倒计时 */
export const FLOCK_HOLD_DECISION_MS = 60_000;
/** 自首次驻留起的总硬限(含二级填写与选档结账) */
export const FLOCK_HOLD_TOTAL_MS = 300_000;
export const FLOCK_HOLD_REDIS_PREFIX = "flock_quote_hold:";
export type FlockQuoteHoldStatus =
| "awaiting_decision"
| "filling"
| "checking_out"
| "declined"
| "done"
| "expired";
export type FlockQuoteHoldState = {
quote_id: string;
quote_session_id: string;
customer_id: string;
parked_at_ms: number;
decision_deadline_ms: number;
total_deadline_ms: number;
status: FlockQuoteHoldStatus;
};
export type FlockQuoteHoldPublic = {
available: boolean;
quote_session_id: string;
decision_deadline_ms: number;
total_deadline_ms: number;
decision_remaining_ms: number;
total_remaining_ms: number;
status: FlockQuoteHoldStatus;
};
export function buildFlockQuoteHoldState(input: {
quoteId: string;
sessionId: string;
customerId: string;
nowMs?: number;
}): FlockQuoteHoldState {
const now = input.nowMs ?? Date.now();
return {
quote_id: input.quoteId,
quote_session_id: input.sessionId,
customer_id: input.customerId,
parked_at_ms: now,
decision_deadline_ms: now + FLOCK_HOLD_DECISION_MS,
total_deadline_ms: now + FLOCK_HOLD_TOTAL_MS,
status: "awaiting_decision",
};
}
export function toFlockQuoteHoldPublic(
state: FlockQuoteHoldState,
nowMs = Date.now(),
): FlockQuoteHoldPublic {
const decision_remaining_ms = Math.max(0, state.decision_deadline_ms - nowMs);
const total_remaining_ms = Math.max(0, state.total_deadline_ms - nowMs);
const available =
total_remaining_ms > 0 &&
(state.status === "checking_out" ||
state.status === "filling" ||
(state.status === "awaiting_decision" && decision_remaining_ms > 0));
return {
available,
quote_session_id: state.quote_session_id,
decision_deadline_ms: state.decision_deadline_ms,
total_deadline_ms: state.total_deadline_ms,
decision_remaining_ms,
total_remaining_ms,
status: state.status,
};
}
export function shouldAutoDeclineFlockHold(
state: FlockQuoteHoldState,
nowMs = Date.now(),
): boolean {
if (state.status !== "awaiting_decision") return false;
return nowMs >= state.decision_deadline_ms;
}
export function isFlockQuoteHoldExpired(
state: FlockQuoteHoldState,
nowMs = Date.now(),
): boolean {
return nowMs >= state.total_deadline_ms;
}
export function formatFlockHoldRemainingLabel(
deadlineMs: number,
nowMs = Date.now(),
): string {
const sec = Math.max(0, Math.ceil((deadlineMs - nowMs) / 1000));
const m = Math.floor(sec / 60);
const s = sec % 60;
return m > 0 ? `${m}分${String(s).padStart(2, "0")}秒` : `${s}秒`;
}
export const FLOCK_HOLD_MSG = {
decisionTimeout: "确认窗口已超时,已保留初步报价",
totalTimeout: "二级填写已超时,已保留初步报价",
totalTimeoutApi: "二级填写已超时(超过 5 分钟),请重新询价",
decisionTimeoutApi: "确认窗口已超时,请重新询价",
sessionGone: "询价会话已失效,请重新询价",
} as const;
export function flockHoldSessionId(quoteId: string): string {
return `flock_${quoteId}`;
}