|
|
/**
|
|
|
* 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<string, unknown>)
|
|
|
: {};
|
|
|
const delivery =
|
|
|
record.deliveryJson && typeof record.deliveryJson === "object"
|
|
|
? (record.deliveryJson as Record<string, unknown>)
|
|
|
: {};
|
|
|
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" };
|
|
|
}
|