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/lib/frontend/mothership-logged-in-quote-...

143 lines
5.5 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.

/**
* 登录后一级 payload → 现有 QuoteRequestBody(复用匿名询价 API)
*/
import { applyMothershipCandidate } from "@/lib/frontend/mothership-address";
import { uuidV4 } from "@/lib/frontend/format";
import type {
AddressInput,
MothershipAddressCandidate,
QuoteRequestBody,
} from "@/lib/frontend/types";
import type { MothershipLoggedInShipmentPayload } from "@/components/mothership/mothership-logged-in-shipment-form";
import type { MothershipLoggedInDetailsPayload } from "@/components/mothership/mothership-logged-in-details-form";
import { normalizeMothershipReadyDateIso } from "@/lib/mothership/logged-in-constraints";
function candidateToAddress(
candidate: MothershipAddressCandidate,
): AddressInput {
return applyMothershipCandidate(
{
street: candidate.street,
city: candidate.city,
state: candidate.state,
zip: candidate.zip || "",
place_id: candidate.option_id,
formatted_address:
candidate.formatted_address || candidate.display_label,
selected_from_suggestions: true,
},
candidate,
);
}
/** 托盘数:仅合计 cargoType=pallet 的 quantity(纸箱等不计入) */
export function resolveLoggedInPalletCount(
payload: MothershipLoggedInShipmentPayload,
): number {
const palletSum = payload.cargo
.filter((row) => row.cargoType === "pallet")
.reduce((acc, row) => acc + row.quantity, 0);
if (palletSum > 0) {
return Math.max(1, Math.round(palletSum));
}
// 无托盘行(纯纸箱等):退回各行 quantity 合计,供兼容字段
const sum = payload.cargo.reduce((acc, row) => acc + row.quantity, 0);
return Math.max(1, Math.round(sum));
}
/**
* 用一级货件 + 已确认联想地址组询价请求。
* 托盘数仅合计 pallet 行;重量/尺寸保留首行供匿名路径兼容;
* cargo_lines 供登录态 RPA 按行填官网(含纸箱等非托盘行)。
*/
export function buildQuoteRequestBodyFromLoggedIn(
payload: MothershipLoggedInShipmentPayload,
customerId: string,
details?: MothershipLoggedInDetailsPayload,
): QuoteRequestBody {
const primary = payload.cargo[0];
if (!primary) {
throw new Error("货物明细不能为空");
}
return {
request_id: uuidV4(),
customer_id: customerId,
business_customer_id: payload.businessCustomerId || undefined,
business_user_account: payload.businessUserAccount || undefined,
quote_session_id: uuidV4(),
pickup_address: candidateToAddress(payload.pickupConfirmed),
delivery_address: candidateToAddress(payload.deliveryConfirmed),
weight: { value: primary.weightLb, unit: "lb" },
dimensions: {
length: primary.lengthIn,
width: primary.widthIn,
height: primary.heightIn,
unit: "in",
},
pallet_count: resolveLoggedInPalletCount(payload),
cargo_type: "general_freight",
service_level: "standard",
rate_option: "lowest",
display_unit: "imperial",
pickup_accessorials: details
? [...(details.pickup.accessorials ?? [])]
: [...payload.pickupAccessorials],
delivery_accessorials: details
? [...(details.delivery.accessorials ?? [])]
: [...payload.deliveryAccessorials],
ready_date: normalizeMothershipReadyDateIso(payload.readyDate),
ready_time: payload.readyTime || undefined,
cargo_lines: payload.cargo.map((row) => ({
cargo_type: row.cargoType,
quantity: row.quantity,
weight_lb: row.weightLb,
length_in: row.lengthIn,
width_in: row.widthIn,
height_in: row.heightIn,
})),
mothership_details: details
? {
pickup: {
company_name: details.pickup.companyName || undefined,
suite: details.pickup.suite || undefined,
contact_first: details.pickup.contactFirst || undefined,
contact_last: details.pickup.contactLast || undefined,
contact_email: details.pickup.contactEmail || undefined,
contact_phone: details.pickup.contactPhone || undefined,
reference: details.pickup.reference || undefined,
notes: details.pickup.notes || undefined,
opens_at: details.pickup.opensAt || undefined,
closes_at: details.pickup.closesAt || undefined,
},
delivery: {
company_name: details.delivery.companyName || undefined,
suite: details.delivery.suite || undefined,
contact_first: details.delivery.contactFirst || undefined,
contact_last: details.delivery.contactLast || undefined,
contact_email: details.delivery.contactEmail || undefined,
contact_phone: details.delivery.contactPhone || undefined,
reference: details.delivery.reference || undefined,
notes: details.delivery.notes || undefined,
opens_at: details.delivery.opensAt || undefined,
closes_at: details.delivery.closesAt || undefined,
},
request_delivery_appointment: details.requestDeliveryAppointment,
fba_number: details.fbaNumber || undefined,
fba_po_number: details.fbaPoNumber || undefined,
cargo: details.cargo.map((line) => ({
piece_count_type: line.pieceCountType || undefined,
piece_count_qty: Number.isFinite(line.pieceCountQty)
? line.pieceCountQty
: undefined,
description: line.description || undefined,
nmfc: line.nmfc || undefined,
hazmat: line.hazmat,
alcohol: line.alcohol,
tobacco: line.tobacco,
})),
}
: undefined,
};
}