|
|
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),
|
|
|
});
|
|
|
}
|