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/mothership/logged-in-constraints.ts

196 lines
6.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.

/**
* 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)
);
}