|
|
import { QUOTE_TIMEOUT_MS } from "@/lib/constants/quote";
|
|
|
import type { RpaErrorCode } from "@/modules/rpa/errors";
|
|
|
import { RpaError } from "@/modules/rpa/errors";
|
|
|
|
|
|
const QUOTE_TIMEOUT_SEC = Math.round(QUOTE_TIMEOUT_MS / 1_000);
|
|
|
|
|
|
/** 对外展示的询价失败 error_code(domain-rules §10 + RPA 细分) */
|
|
|
export type QuoteFailureCode =
|
|
|
| "QUOTE_UNAVAILABLE"
|
|
|
| "QUOTE_TIMEOUT"
|
|
|
| "QUOTE_ENTRY_UNAVAILABLE"
|
|
|
| "ADDRESS_SUGGESTION_NOT_FOUND"
|
|
|
| "CARRIER_NO_CAPACITY"
|
|
|
| "ADDRESS_NOT_SUPPORTED"
|
|
|
| "RPA_CAPTCHA"
|
|
|
| "SESSION_EXPIRED"
|
|
|
| "RPA_DATA_INVALID"
|
|
|
| "PAGE_LOAD_TIMEOUT";
|
|
|
|
|
|
const DEFAULT_MESSAGES: Record<QuoteFailureCode, string> = {
|
|
|
QUOTE_UNAVAILABLE: "暂时无法获取报价,请稍后重试",
|
|
|
QUOTE_TIMEOUT: `查询超时(超过 ${QUOTE_TIMEOUT_SEC} 秒),请重试`,
|
|
|
QUOTE_ENTRY_UNAVAILABLE:
|
|
|
"MotherShip 报价入口打不开(网络超时或被拦截)。请检查 VPN、将 RPA_HEADLESS=false 后重录 .rpa/mothership-storage.json,或增大 RPA_PAGE_GOTO_TIMEOUT_MS",
|
|
|
ADDRESS_SUGGESTION_NOT_FOUND:
|
|
|
"无法在 MotherShip 页面匹配您已确认的地址,请重新选择提货/派送地址",
|
|
|
CARRIER_NO_CAPACITY: "该线路暂无可用运力,请调整货物或地址后重试",
|
|
|
ADDRESS_NOT_SUPPORTED: "该地址不在 MotherShip 服务范围内",
|
|
|
RPA_CAPTCHA: "MotherShip 触发验证码,RPA 已暂停,请稍后重试或联系管理员",
|
|
|
SESSION_EXPIRED: "MotherShip 登录会话已过期,请配置凭据后重试",
|
|
|
RPA_DATA_INVALID: "未能从 MotherShip 页面解析完整四档报价,请稍后重试",
|
|
|
PAGE_LOAD_TIMEOUT: "MotherShip 页面加载超时,请稍后重试",
|
|
|
};
|
|
|
|
|
|
const RPA_CODE_MAP: Partial<Record<RpaErrorCode, QuoteFailureCode>> = {
|
|
|
QUOTE_ENTRY_UNAVAILABLE: "QUOTE_ENTRY_UNAVAILABLE",
|
|
|
ADDRESS_SUGGESTION_NOT_FOUND: "ADDRESS_SUGGESTION_NOT_FOUND",
|
|
|
CARRIER_NO_CAPACITY: "CARRIER_NO_CAPACITY",
|
|
|
ADDRESS_NOT_SUPPORTED: "ADDRESS_NOT_SUPPORTED",
|
|
|
RPA_CAPTCHA: "RPA_CAPTCHA",
|
|
|
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
|
RPA_DATA_INVALID: "RPA_DATA_INVALID",
|
|
|
PAGE_LOAD_TIMEOUT: "PAGE_LOAD_TIMEOUT",
|
|
|
STRUCT_CHANGE: "QUOTE_ENTRY_UNAVAILABLE",
|
|
|
};
|
|
|
|
|
|
export function resolveQuoteFailureCode(error: unknown): QuoteFailureCode {
|
|
|
if (error instanceof RpaError) {
|
|
|
return RPA_CODE_MAP[error.code] ?? "QUOTE_UNAVAILABLE";
|
|
|
}
|
|
|
return "QUOTE_UNAVAILABLE";
|
|
|
}
|
|
|
|
|
|
export function resolveQuoteFailureMessage(
|
|
|
error: unknown,
|
|
|
errorCode: QuoteFailureCode,
|
|
|
): string {
|
|
|
if (error instanceof RpaError && error.message.trim()) {
|
|
|
return error.message.trim();
|
|
|
}
|
|
|
if (error instanceof Error && error.message.trim()) {
|
|
|
return error.message.trim();
|
|
|
}
|
|
|
return DEFAULT_MESSAGES[errorCode];
|
|
|
}
|
|
|
|
|
|
/** GET /quotes/{id} 展示文案:优先库内明细,否则按 error_code 映射 */
|
|
|
export function formatQuoteErrorMessage(
|
|
|
errorCode: string | null | undefined,
|
|
|
storedMessage?: string | null,
|
|
|
): string {
|
|
|
const trimmed = storedMessage?.trim();
|
|
|
if (trimmed) {
|
|
|
return trimmed;
|
|
|
}
|
|
|
const code = (errorCode ?? "QUOTE_UNAVAILABLE") as QuoteFailureCode;
|
|
|
return DEFAULT_MESSAGES[code] ?? DEFAULT_MESSAGES.QUOTE_UNAVAILABLE;
|
|
|
}
|