/** * Flock:选档后入队 RPA 拉取三档灵活性价(不点 Yes / 不填表) */ import { prisma } from "@/lib/prisma"; import type { FlockCheckoutTier } from "@/lib/flock/flock-checkout-selection"; import { saveFlockPricingOptions, readFlockPricingOptions, } from "@/lib/flock/flock-pricing-options-store"; import { readFlockQuoteHoldByQuoteId } from "@/lib/flock/flock-quote-hold-store"; import { enqueueFlockPricingOptionsJob } from "@/modules/flock/pricing-options-queue"; import { parseFlockStoredQuotes } from "@/modules/flock/quote-storage"; import type { FlockQuoteInput } from "@/workers/rpa/flock/types"; import { isFlockQuoteHoldExpired } from "@/lib/constants/flock-quote-hold"; function assertPricingOptionsTier(tier: FlockCheckoutTier): void { if (tier !== "flock_direct" && tier !== "standard") { throw new Error("请选择 FlockDirect® 或 Standard 报价档"); } } function tomorrowIso(): string { const d = new Date(); d.setDate(d.getDate() + 1); while (d.getDay() === 0 || d.getDay() === 6) { d.setDate(d.getDate() + 1); } const yyyy = d.getFullYear(); const mm = String(d.getMonth() + 1).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0"); return `${yyyy}-${mm}-${dd}`; } function reconstructFlockInput(record: { pickupJson: unknown; deliveryJson: unknown; weightLb: unknown; dimLIn: unknown; dimWIn: unknown; dimHIn: unknown; palletCount: number; }): FlockQuoteInput { const pickup = record.pickupJson && typeof record.pickupJson === "object" ? (record.pickupJson as Record) : {}; const delivery = record.deliveryJson && typeof record.deliveryJson === "object" ? (record.deliveryJson as Record) : {}; const pickupDate = typeof pickup.flock_pickup_date === "string" && pickup.flock_pickup_date ? pickup.flock_pickup_date : tomorrowIso(); return { pickupDate, pickupZip: String(pickup.zip ?? ""), deliveryZip: String(delivery.zip ?? ""), palletCount: record.palletCount, totalWeightLb: Number(record.weightLb) || 0, lengthIn: Number(record.dimLIn) || 48, widthIn: Number(record.dimWIn) || 40, heightIn: Number(record.dimHIn) || 48, formMode: "logged_in_quick", }; } export async function submitFlockPricingOptions(input: { customerId: string; quoteId: string; preferredTier: FlockCheckoutTier; }): Promise<{ quote_id: string; status: "processing" }> { // 为何:此处是去官网拉灵活价,不能要求已选 preferred_flexibility(结账才校验) assertPricingOptionsTier(input.preferredTier); const record = await prisma.quoteRecord.findUnique({ where: { quoteId: input.quoteId }, }); if (!record || record.customerId !== input.customerId) { throw new Error("报价单不存在"); } if (record.status !== "done") { throw new Error("请等待初步报价完成后再选档"); } const existing = await readFlockPricingOptions(input.quoteId); if ( existing?.status === "processing" && existing.preferred_tier === input.preferredTier ) { throw new Error("正在拉取档内报价,请稍候"); } const stored = parseFlockStoredQuotes(record.quotesJson); const reference = stored?.reference ?? null; const hold = await readFlockQuoteHoldByQuoteId(input.quoteId); const quoteSessionId = hold && hold.customer_id === input.customerId && !isFlockQuoteHoldExpired(hold) && (hold.status === "awaiting_decision" || hold.status === "filling" || hold.status === "checking_out") ? hold.quote_session_id : undefined; const flockInput = reconstructFlockInput(record); if (!flockInput.pickupZip || !flockInput.deliveryZip) { throw new Error("报价缺少邮编信息,无法拉取档内报价"); } await saveFlockPricingOptions({ quote_id: input.quoteId, status: "processing", preferred_tier: input.preferredTier, options: [], message: "正在官网拉取灵活性报价…", reference, updated_at_ms: Date.now(), }); try { await enqueueFlockPricingOptionsJob({ quoteId: input.quoteId, requestId: record.requestId, customerId: input.customerId, preferredTier: input.preferredTier, reference, input: flockInput, quoteSessionId, }); } catch (enqueueErr) { const msg = enqueueErr instanceof Error ? enqueueErr.message : String(enqueueErr); if (/already exists|JobId/i.test(msg)) { throw new Error("正在拉取档内报价,请稍候"); } await saveFlockPricingOptions({ quote_id: input.quoteId, status: "failed", preferred_tier: input.preferredTier, options: [], message: "拉取档内报价失败,请重试", updated_at_ms: Date.now(), }); throw enqueueErr; } return { quote_id: input.quoteId, status: "processing" }; }