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.

115 lines
3.7 KiB

/**
* MotherShip fill-only 结账:选承运商 + 保障 → 入队 RPA
*/
import { prisma } from "@/lib/prisma";
import {
validateMsCheckoutSelection,
type MsCoverageKind,
} from "@/lib/mothership/ms-checkout-selection";
import { saveMsCheckoutStatus, readMsCheckoutStatus } from "@/lib/mothership/ms-checkout-store";
import { enqueueMsCheckoutFillJob } from "@/modules/mothership/checkout-queue";
import { validateQuoteInput } from "@/modules/quote/validation";
import type { QuoteRequestBody } from "@/lib/frontend/types";
export async function submitMsCheckoutFill(input: {
customerId: string;
quoteId: string;
preferredCarrier: string;
coverage: MsCoverageKind;
cargoValueUsd?: number;
mothershipDetails?: QuoteRequestBody["mothership_details"];
pickupAccessorials?: string[];
deliveryAccessorials?: string[];
}): Promise<{ quote_id: string; status: "processing" }> {
const err = validateMsCheckoutSelection({
preferredCarrier: input.preferredCarrier,
coverage: input.coverage,
cargoValueUsd: input.cargoValueUsd,
});
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 readMsCheckoutStatus(input.quoteId);
if (existing?.status === "processing") {
throw new Error("结账同步进行中,请稍候");
}
const pickup =
record.pickupJson as unknown as QuoteRequestBody["pickup_address"];
const delivery =
record.deliveryJson as unknown as QuoteRequestBody["delivery_address"];
const body = {
request_id: record.requestId,
customer_id: input.customerId,
pickup_address: pickup,
delivery_address: delivery,
weight: { value: Number(record.weightLb), unit: "lb" as const },
dimensions: {
length: Number(record.dimLIn),
width: Number(record.dimWIn),
height: Number(record.dimHIn),
unit: "in" as const,
},
pallet_count: record.palletCount,
cargo_type: record.cargoType as QuoteRequestBody["cargo_type"],
mothership_details: input.mothershipDetails,
pickup_accessorials: input.pickupAccessorials,
delivery_accessorials: input.deliveryAccessorials,
preferred_carrier: input.preferredCarrier,
coverage: input.coverage,
cargo_value_usd: input.cargoValueUsd,
};
const cargo = validateQuoteInput(body);
cargo.preferredCarrier = input.preferredCarrier;
cargo.coverage = input.coverage;
cargo.cargoValueUsd = input.cargoValueUsd;
await saveMsCheckoutStatus({
quote_id: input.quoteId,
status: "processing",
coverage: input.coverage,
cargo_value_usd: input.cargoValueUsd ?? null,
message: "正在官网选择承运商与保障并填齐详情…",
updated_at_ms: Date.now(),
});
try {
await enqueueMsCheckoutFillJob({
quoteId: input.quoteId,
requestId: record.requestId,
customerId: input.customerId,
cargo,
preferredCarrier: input.preferredCarrier,
coverage: input.coverage,
cargoValueUsd: input.cargoValueUsd,
});
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (/already exists|JobId/i.test(msg)) {
throw new Error("结账同步进行中,请稍候");
}
await saveMsCheckoutStatus({
quote_id: input.quoteId,
status: "failed",
coverage: input.coverage,
cargo_value_usd: input.cargoValueUsd ?? null,
message: "官网同步失败,请稍后重试",
updated_at_ms: Date.now(),
});
throw err;
}
return { quote_id: input.quoteId, status: "processing" };
}