import { createHash } from "crypto"; import type { NormalizedCargo } from "@/modules/quote/types"; export type CargoHashInput = { pickupSerialized: string; deliverySerialized: string; weightLb: number; dimLIn: number; dimWIn: number; dimHIn: number; palletCount: number; cargoType: string; /** 登录态附加服务 / ready 纳入 hash,避免错价缓存 */ loggedInExtras?: string; }; /** 地址序列化为含 place_id 的标准 JSON(BR-4.4) */ export function serializeAddress(addr: { street: string; city: string; state: string; zip: string; place_id: string; mothership_option_id?: string; }): string { return JSON.stringify({ street: addr.street.trim(), city: addr.city.trim(), state: addr.state.trim().toUpperCase(), zip: addr.zip.trim(), place_id: addr.mothership_option_id?.trim() || addr.place_id, }); } /** cargo_hash = MD5(pickup+delivery+weight_lb+dims+pallet_count+cargo_type[+loggedInExtras]) */ export function buildCargoHash(input: CargoHashInput): string { const parts = [ input.pickupSerialized, input.deliverySerialized, input.weightLb.toFixed(2), input.dimLIn.toFixed(2), input.dimWIn.toFixed(2), input.dimHIn.toFixed(2), String(input.palletCount), input.cargoType, ]; // 仅登录态附加字段存在时追加,保持匿名 hash 兼容 if (input.loggedInExtras) { parts.push(input.loggedInExtras); } return createHash("md5").update(parts.join("|")).digest("hex"); } export function buildLoggedInCargoHashExtras(input: { pickupAccessorials?: string[]; deliveryAccessorials?: string[]; readyDate?: string; readyTime?: string; cargoLines?: Array<{ cargoType: string; quantity: number; weightLb: number; lengthIn: number; widthIn: number; heightIn: number; }>; }): string | undefined { const pickup = [...(input.pickupAccessorials ?? [])] .map((s) => s.trim()) .filter(Boolean) .sort(); const delivery = [...(input.deliveryAccessorials ?? [])] .map((s) => s.trim()) .filter(Boolean) .sort(); const readyDate = input.readyDate?.trim() ?? ""; const readyTime = input.readyTime?.trim() ?? ""; const lines = (input.cargoLines ?? []) .map( (r) => `${r.cargoType}:${r.quantity}:${r.weightLb}:${r.lengthIn}x${r.widthIn}x${r.heightIn}`, ) .join(";"); if ( pickup.length === 0 && delivery.length === 0 && !readyDate && !readyTime && !lines ) { return undefined; } return `pa=${pickup.join(",")}|da=${delivery.join(",")}|rd=${readyDate}|rt=${readyTime}|cl=${lines}`; } export function buildCargoHashFromNormalized(cargo: NormalizedCargo): string { return buildCargoHash({ pickupSerialized: cargo.pickupSerialized, deliverySerialized: cargo.deliverySerialized, weightLb: cargo.weightLb, dimLIn: cargo.dimLIn, dimWIn: cargo.dimWIn, dimHIn: cargo.dimHIn, palletCount: cargo.palletCount, cargoType: cargo.cargoType, loggedInExtras: buildLoggedInCargoHashExtras(cargo), }); }