/** * Flock fill-only 结账:选档 → 入队 RPA */ import { prisma } from "@/lib/prisma"; import { validateFlockCheckoutSelection, type FlockCheckoutTier, type FlockFlexibilityKey, } from "@/lib/flock/flock-checkout-selection"; import { saveFlockCheckoutStatus, readFlockCheckoutStatus, } from "@/lib/flock/flock-checkout-store"; import { enqueueFlockCheckoutFillJob } from "@/modules/flock/checkout-queue"; import { parseFlockStoredQuotes } from "@/modules/flock/quote-storage"; import type { FlockCheckoutDetails } from "@/modules/flock/checkout-validation"; import type { FlockQuoteInput } from "@/workers/rpa/flock/types"; import { assertFlockQuoteHoldUsable, clearFlockQuoteHold, readFlockQuoteHoldByQuoteId, updateFlockQuoteHoldStatus, } from "@/lib/flock/flock-quote-hold-store"; import { FLOCK_HOLD_MSG, isFlockQuoteHoldExpired, shouldAutoDeclineFlockHold, } from "@/lib/constants/flock-quote-hold"; import { declineFlockQuoteHold } from "@/modules/flock/hold-service"; 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 submitFlockCheckoutFill(input: { customerId: string; quoteId: string; preferredTier: FlockCheckoutTier; preferredFlexibility?: FlockFlexibilityKey | null; preferredCarrier?: string | null; flockDetails?: FlockCheckoutDetails; }): Promise<{ quote_id: string; status: "processing" }> { const err = validateFlockCheckoutSelection({ preferredTier: input.preferredTier, preferredFlexibility: input.preferredFlexibility, preferredCarrier: input.preferredCarrier, }); if (err) throw new Error(err); 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 readFlockCheckoutStatus(input.quoteId); if (existing?.status === "processing") { throw new Error("结账同步进行中,请稍候"); } const stored = parseFlockStoredQuotes(record.quotesJson); const reference = stored?.reference ?? null; const flockInput = reconstructFlockInput(record); if (!flockInput.pickupZip || !flockInput.deliveryZip) { throw new Error("报价缺少邮编信息,无法同步结账"); } let quoteSessionId: string | null = null; const hold = await readFlockQuoteHoldByQuoteId(input.quoteId); if (hold && hold.customer_id === input.customerId) { if ( (hold.status === "awaiting_decision" && shouldAutoDeclineFlockHold(hold)) || isFlockQuoteHoldExpired(hold) ) { await declineFlockQuoteHold({ customerId: input.customerId, quoteId: input.quoteId, sessionId: hold.quote_session_id, }).catch(() => undefined); throw new Error( isFlockQuoteHoldExpired(hold) ? FLOCK_HOLD_MSG.totalTimeoutApi : FLOCK_HOLD_MSG.decisionTimeoutApi, ); } try { await assertFlockQuoteHoldUsable(hold.quote_session_id); await updateFlockQuoteHoldStatus(hold.quote_session_id, "checking_out"); quoteSessionId = hold.quote_session_id; } catch { quoteSessionId = null; } } await saveFlockCheckoutStatus({ quote_id: input.quoteId, status: "processing", preferred_tier: input.preferredTier, message: "正在官网选择报价档并填齐详情…", updated_at_ms: Date.now(), }); try { await enqueueFlockCheckoutFillJob({ quoteId: input.quoteId, requestId: record.requestId, customerId: input.customerId, preferredTier: input.preferredTier, preferredFlexibility: input.preferredFlexibility, preferredCarrier: input.preferredCarrier, reference, input: flockInput, flockDetails: input.flockDetails, quoteSessionId, }); } catch (enqueueErr) { const msg = enqueueErr instanceof Error ? enqueueErr.message : String(enqueueErr); if (/already exists|JobId/i.test(msg)) { throw new Error("结账同步进行中,请稍候"); } await saveFlockCheckoutStatus({ quote_id: input.quoteId, status: "failed", preferred_tier: input.preferredTier, message: "官网同步失败,请稍后重试", updated_at_ms: Date.now(), }); if (quoteSessionId) { await clearFlockQuoteHold(quoteSessionId).catch(() => undefined); } throw enqueueErr; } return { quote_id: input.quoteId, status: "processing" }; }