You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chajia/modules/flock/pricing-options-service.ts

150 lines
4.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 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" };
}