/** * MotherShip 登录态官网硬约束(与 dashboard Create shipment 对齐) * * - 单件平均重量(Weight each)> 5000 lb → 官网不再提供报价 * - 整票总重 > 53 英尺干货厢式挂车最大载重(45000 lb)→ 不可询价 * - 不以托盘数量 1–25 为硬拒(各路径均取消该上限) */ export const MOTHERSHIP_MAX_AVG_WEIGHT_EACH_LB = 5000; /** 53 英尺干货厢式挂车最大载重(lb) */ export const MOTHERSHIP_MAX_TOTAL_WEIGHT_LB = 45_000; /** @deprecated 使用 MOTHERSHIP_MAX_AVG_WEIGHT_EACH_LB */ export const MOTHERSHIP_MAX_WEIGHT_EACH_LB = MOTHERSHIP_MAX_AVG_WEIGHT_EACH_LB; export const MOTHERSHIP_AVG_WEIGHT_BLOCK_MESSAGE = `单件平均重量超过 ${MOTHERSHIP_MAX_AVG_WEIGHT_EACH_LB} 磅时,无法提供报价`; export const MOTHERSHIP_TOTAL_WEIGHT_BLOCK_MESSAGE = `整票总重超过 53 英尺货车最大载重(${MOTHERSHIP_MAX_TOTAL_WEIGHT_LB.toLocaleString("en-US")} 磅),无法提供报价`; /** 重量/尺寸仅接受整数;有小数须向上取整 */ export const MOTHERSHIP_INTEGER_HINT = "重量与尺寸仅支持整数。有小数时系统将自动向上取整(进一)"; /** 一级「货物」标题旁录入限制提示(提前告知,勿等到查价页) */ export const MOTHERSHIP_CARGO_ENTRY_HINT_TITLE = "录入提示 (提前告知,勿等到查价页)"; export const MOTHERSHIP_CARGO_ENTRY_HINT_ITEMS = [ "重量/尺寸支持小数录入 (lb / inch);点「开始询价」时自动向上取整 (进一) 再提交,例 25.68→26", `「单件重」= Weight each,须 ≤ ${MOTHERSHIP_MAX_AVG_WEIGHT_EACH_LB} lb,超出不可询价`, `整票总重 = Σ(单件重×数量) 须 ≤ ${MOTHERSHIP_MAX_TOTAL_WEIGHT_LB.toLocaleString("en-US")} lb (53 英尺干货厢式挂车最大载重),超出不可询价`, ] as const; /** * 官网不接受小数:正数向上取整(ceil)。 * 例:25.68 → 26,40.1 → 41,25 → 25 */ export function ceilMothershipNumeric(value: number): number { if (!Number.isFinite(value) || value <= 0) return value; return Math.ceil(value); } export function hasMothershipFractionalPart(value: number): boolean { return Number.isFinite(value) && value > 0 && !Number.isInteger(value); } /** YYYY-MM-DD → 本地 noon 解析,避免 UTC 偏移改星期 */ export function parseIsoDateLocal(iso: string): Date | null { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso.trim()); if (!m) return null; const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), 12, 0, 0); return Number.isNaN(d.getTime()) ? null : d; } export function toIsoDateLocal(d: Date): string { const yyyy = d.getFullYear(); const mm = String(d.getMonth() + 1).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0"); return `${yyyy}-${mm}-${dd}`; } /** 周六=6 / 周日=0 不可选 */ export function isMothershipWeekendIso(iso: string): boolean { const d = parseIsoDateLocal(iso); if (!d) return false; const day = d.getDay(); return day === 0 || day === 6; } /** 若落在周末,拨到下一工作日(周一) */ export function snapMothershipReadyDateToWeekday(iso: string): string { const d = parseIsoDateLocal(iso); if (!d) return iso; const day = d.getDay(); if (day === 6) d.setDate(d.getDate() + 2); else if (day === 0) d.setDate(d.getDate() + 1); return toIsoDateLocal(d); } /** 今天 YYYY-MM-DD(本地) */ export function todayIsoDateLocal(): string { return toIsoDateLocal(new Date()); } /** 默认可提货日:今天若为周末则拨到周一 */ export function defaultMothershipReadyDateIso(): string { return snapMothershipReadyDateToWeekday(todayIsoDateLocal()); } /** date input min:与官网一致,不可早于最近工作日 */ export function minMothershipReadyDateIso(): string { return defaultMothershipReadyDateIso(); } /** 所有 MotherShip 日期字段统一规范化 */ export function normalizeMothershipReadyDateIso( iso: string | undefined, ): string | undefined { const trimmed = iso?.trim(); if (!trimmed) return undefined; return snapMothershipReadyDateToWeekday(trimmed); } /** * 单件平均重量(lb)。 * 登录态表单「单件重量 / Weight each」即均重;quantity 仅表示件数,不参与均重计算。 */ export function resolveMothershipAverageWeightEachLb( weightEachLb: number, _quantity?: number, ): number { return weightEachLb; } /** 单件平均重量 ≤ 5000 lb 才允许询价 */ export function isMothershipAverageWeightEachAllowed( weightEachLb: number, ): boolean { return ( Number.isFinite(weightEachLb) && weightEachLb > 0 && weightEachLb <= MOTHERSHIP_MAX_AVG_WEIGHT_EACH_LB ); } /** @deprecated 使用 isMothershipAverageWeightEachAllowed */ export function isMothershipWeightEachAllowed(weightEachLb: number): boolean { return isMothershipAverageWeightEachAllowed(weightEachLb); } export type MothershipCargoWeightLine = { weightLb: number; quantity?: number; }; /** 整票总重 = Σ(单件重量 × 数量) */ export function sumMothershipTotalWeightLb( lines: MothershipCargoWeightLine[], ): number { return lines.reduce((acc, line) => { const qty = Math.max(1, Math.round(line.quantity ?? 1)); const wt = line.weightLb; if (!Number.isFinite(wt) || wt <= 0) return acc; return acc + wt * qty; }, 0); } export function isMothershipTotalWeightAllowed(totalLb: number): boolean { return ( Number.isFinite(totalLb) && totalLb > 0 && totalLb <= MOTHERSHIP_MAX_TOTAL_WEIGHT_LB ); } /** * 校验多行货物:任一单件平均重量超限则返回阻断文案,否则 null。 */ export function findMothershipAvgWeightBlockMessage( lines: MothershipCargoWeightLine[], ): string | null { for (const line of lines) { const avg = resolveMothershipAverageWeightEachLb( line.weightLb, line.quantity, ); if (!isMothershipAverageWeightEachAllowed(avg)) { return MOTHERSHIP_AVG_WEIGHT_BLOCK_MESSAGE; } } return null; } /** 整票总重超限则返回阻断文案,否则 null */ export function findMothershipTotalWeightBlockMessage( lines: MothershipCargoWeightLine[], ): string | null { const total = sumMothershipTotalWeightLb(lines); if (!isMothershipTotalWeightAllowed(total)) { return MOTHERSHIP_TOTAL_WEIGHT_BLOCK_MESSAGE; } return null; } /** 均重或总重任一超限则返回阻断文案 */ export function findMothershipCargoWeightBlockMessage( lines: MothershipCargoWeightLine[], ): string | null { return ( findMothershipAvgWeightBlockMessage(lines) ?? findMothershipTotalWeightBlockMessage(lines) ); }