/** * 登录后一级 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, ); } /** 托盘数:各行 quantity 合计(登录后官网可多行) */ export function resolveLoggedInPalletCount( payload: MothershipLoggedInShipmentPayload, ): number { const sum = payload.cargo.reduce((acc, row) => acc + row.quantity, 0); return Math.max(1, Math.round(sum)); } /** * 用一级货件 + 已确认联想地址组询价请求。 * 托盘数取 quantity 合计;重量/尺寸保留首行供匿名路径兼容; * cargo_lines 供登录态 RPA 按行填官网(cargo-add-button)。 */ 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, 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: [...payload.pickupAccessorials], 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, }; }