/** 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}`; }