import { Prisma } from "@prisma/client"; import { parseAdminAuth } from "@/lib/api/admin-auth-context"; import { QUOTE_VALIDITY_MS } from "@/lib/constants/quote"; import { prisma } from "@/lib/prisma"; import { fail, ok } from "@/lib/response"; import { writeAlert } from "@/modules/alert/service"; import { writeAudit } from "@/modules/audit/service"; import { AuthError } from "@/modules/auth/errors"; import { requirePermission } from "@/modules/auth/rbac"; import { applyMarkupToQuotes, getMarkupRule, type RawQuoteTier, } from "@/modules/pricing/engine"; import { getConfidenceScore } from "@/modules/quote/confidence"; import { prepareMotherShipStorageQuotes } from "@/modules/quote/quote-completeness"; import { overwriteL1, setL3 } from "@/modules/cache/redis-cache"; type TierInput = { service_level?: unknown; rate_option?: unknown; carrier?: unknown; transit_days?: unknown; transit_description?: unknown; raw_freight?: unknown; surcharges?: unknown; raw_total?: unknown; }; type ManualFallbackBody = { quote_id?: unknown; quotes?: unknown; }; function parseTier(raw: TierInput): RawQuoteTier | null { if ( typeof raw.service_level !== "string" || typeof raw.rate_option !== "string" || typeof raw.carrier !== "string" || typeof raw.transit_days !== "string" || typeof raw.transit_description !== "string" || typeof raw.raw_freight !== "number" || typeof raw.surcharges !== "number" || typeof raw.raw_total !== "number" ) { return null; } if (raw.raw_freight <= 0 || raw.raw_total <= 0) { return null; } return { service_level: raw.service_level, rate_option: raw.rate_option, carrier: raw.carrier, transit_days: raw.transit_days, transit_description: raw.transit_description, raw_freight: raw.raw_freight, surcharges: raw.surcharges, raw_total: raw.raw_total, }; } export async function POST(request: Request) { let auth; try { auth = parseAdminAuth(request); requirePermission(auth, "rpa:operate"); } catch (error) { if (error instanceof AuthError) { return fail(error.code, error.message, error.httpStatus); } throw error; } let body: ManualFallbackBody; try { body = (await request.json()) as ManualFallbackBody; } catch { return fail("VALIDATION_FAILED", "请求体格式无效", 400); } const quoteId = typeof body.quote_id === "string" ? body.quote_id.trim() : ""; if (!quoteId) { return fail("VALIDATION_FAILED", "请填写 quote_id", 400); } if (!Array.isArray(body.quotes) || body.quotes.length !== 4) { return fail("VALIDATION_FAILED", "请提供完整四档报价", 400); } const rawQuotes: RawQuoteTier[] = []; for (const item of body.quotes) { const tier = parseTier(item as TierInput); if (!tier) { return fail("VALIDATION_FAILED", "报价格式无效或金额须大于 0", 400); } rawQuotes.push(tier); } let uiQuotes: RawQuoteTier[]; try { uiQuotes = prepareMotherShipStorageQuotes(rawQuotes); } catch (error) { const message = error instanceof Error ? error.message : "四档报价不完整"; return fail("VALIDATION_FAILED", message, 400); } const record = await prisma.quoteRecord.findUnique({ where: { quoteId }, select: { quoteId: true, requestId: true, customerId: true, cargoHash: true, status: true, }, }); if (!record) { return fail("QUOTE_NOT_FOUND", "询价记录不存在", 404); } const markupRule = await getMarkupRule(record.customerId); const markedQuotes = applyMarkupToQuotes(uiQuotes, markupRule); const validUntil = new Date(Date.now() + QUOTE_VALIDITY_MS); const cachePayload = { quotes: uiQuotes }; await setL3(record.cargoHash, cachePayload); await prisma.quoteRecord.update({ where: { quoteId }, data: { status: "done", sourceType: "stale", isRealtime: false, confidenceScore: getConfidenceScore("stale"), markupPercent: markupRule.type === "percent" ? markupRule.percent : 0, quotesJson: markedQuotes as unknown as Prisma.InputJsonValue, validUntil, errorCode: null, }, }); await overwriteL1(record.requestId, { quoteId: record.quoteId, response: { quote_id: record.quoteId, status: "done", source_type: "stale", is_realtime: false, }, }); await writeAudit("quotes:manual_fallback", auth.userId, quoteId, { cargo_hash: record.cargoHash, customer_id: record.customerId, }); await writeAlert("MANUAL_FALLBACK", { quoteId, cargoHash: record.cargoHash, detail: { operator_id: auth.userId, previous_status: record.status, }, }); return ok({ quote_id: quoteId, status: "done", source_type: "stale", is_realtime: false, }); }