import { getHostQuotePollIntervalMs } from "@/lib/constants/host-public-api"; import { QUOTE_TIMEOUT_MS } from "@/lib/constants/quote"; import { prisma } from "@/lib/prisma"; import { serializeQuoteDetail, type QuoteDetailResponse, } from "@/modules/quote/quote-serializer"; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } export class HostQuoteWaitError extends Error { constructor( message: string, readonly code: "QUOTE_NOT_FOUND" | "QUOTE_TIMEOUT" | "QUOTE_FAILED", readonly detail?: QuoteDetailResponse, ) { super(message); this.name = "HostQuoteWaitError"; } } /** 服务端轮询 DB,直至报价完成或超时(供宿主同步接口使用) */ export async function waitForQuoteDetail( quoteId: string, customerId: string, options?: { timeoutMs?: number }, ): Promise { const timeoutMs = options?.timeoutMs ?? QUOTE_TIMEOUT_MS; const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const record = await prisma.quoteRecord.findFirst({ where: { quoteId, customerId, isDeleted: false }, }); if (!record) { throw new HostQuoteWaitError("报价不存在", "QUOTE_NOT_FOUND"); } const detail = serializeQuoteDetail(record); if (detail.status === "processing") { const remaining = deadline - Date.now(); if (remaining <= 0) { break; } await sleep(Math.min(getHostQuotePollIntervalMs(), remaining)); continue; } if (detail.status === "failed") { throw new HostQuoteWaitError( detail.error_message ?? "询价失败", "QUOTE_FAILED", detail, ); } if (detail.status === "expired") { throw new HostQuoteWaitError("报价已过期", "QUOTE_FAILED", detail); } return detail; } throw new HostQuoteWaitError("询价超时,请稍后重试", "QUOTE_TIMEOUT"); }