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.

164 lines
4.2 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 登录态附加服务组合约束
* 依据官网门户拒价文案住宅提货、API accessorial 语义、实测易无价组合
*/
export type MsCompatIssue = {
severity: "block" | "warn";
code: string;
message: string;
};
/** 提货侧不可选的附加服务(官网承运商拒价) */
export const MS_PICKUP_BLOCKED_ACCESSORIALS: Readonly<
Record<string, string>
> = {
residential:
"承运商不支持住宅地址上门取件,请改选商业提货地址,或取消「住宅」",
};
/** 同侧互斥:勾选后者会取消前者并提示 */
export const MS_ACCESSORIAL_EXCLUSIVE_PAIRS: ReadonlyArray<{
a: string;
b: string;
message: string;
}> = [
{
a: "appointment",
b: "fbaAppointment",
message: "「预约」与「Amazon 预约」不能同时选择,请只保留一项",
},
{
a: "residential",
b: "tradeshow",
message: "「住宅」与「展会」不宜同时勾选,请按实际地点类型只选一项",
},
{
a: "cfs",
b: "residential",
message: "CFS 与「住宅」不能同时选择,请按实际地点只选一项",
},
];
export function evaluateMothershipAccessorialCompat(input: {
side: "pickup" | "delivery";
selected: readonly string[];
}): MsCompatIssue[] {
const selected = [...input.selected];
const issues: MsCompatIssue[] = [];
if (input.side === "pickup" && selected.includes("residential")) {
issues.push({
severity: "block",
code: "ms_pickup_residential",
message: MS_PICKUP_BLOCKED_ACCESSORIALS.residential!,
});
}
for (const pair of MS_ACCESSORIAL_EXCLUSIVE_PAIRS) {
if (selected.includes(pair.a) && selected.includes(pair.b)) {
// 预约互斥仅派送侧有这两项
if (
(pair.a === "appointment" || pair.b === "appointment") &&
input.side === "pickup"
) {
continue;
}
issues.push({
severity: "block",
code: `ms_exclusive_${pair.a}_${pair.b}`,
message: pair.message,
});
}
}
if (
input.side === "delivery" &&
selected.includes("residential") &&
!selected.includes("liftgate")
) {
issues.push({
severity: "warn",
code: "ms_residential_need_liftgate",
message: "住宅派送通常需要尾板建议同时勾选「尾板Liftgate」以免追收",
});
}
if (
selected.includes("limitedAccess") &&
!selected.includes("liftgate") &&
!selected.includes("inside")
) {
issues.push({
severity: "warn",
code: "ms_limited_suggest_liftgate",
message: "受限通道地点常需尾板或室内服务,建议按现场条件勾选以免无法卸货",
});
}
return issues;
}
/**
* 尝试勾选/取消某附加服务。
* - 提货「住宅」:禁止勾选
* - 互斥对:勾选新项时自动去掉旧项
*/
export function toggleMothershipAccessorial(input: {
side: "pickup" | "delivery";
selected: readonly string[];
id: string;
}): {
next: string[];
applied: boolean;
message?: string;
issues: MsCompatIssue[];
} {
const { side, id } = input;
const selected = [...input.selected];
const currentlyOn = selected.includes(id);
if (currentlyOn) {
const next = selected.filter((x) => x !== id);
return {
next,
applied: true,
issues: evaluateMothershipAccessorialCompat({ side, selected: next }),
};
}
if (side === "pickup" && id === "residential") {
return {
next: selected,
applied: false,
message: MS_PICKUP_BLOCKED_ACCESSORIALS.residential,
issues: evaluateMothershipAccessorialCompat({ side, selected }),
};
}
let next = [...selected, id];
let message: string | undefined;
for (const pair of MS_ACCESSORIAL_EXCLUSIVE_PAIRS) {
if (id !== pair.a && id !== pair.b) continue;
const other = id === pair.a ? pair.b : pair.a;
if (next.includes(other)) {
next = next.filter((x) => x !== other);
message = pair.message;
}
}
return {
next,
applied: true,
message,
issues: evaluateMothershipAccessorialCompat({ side, selected: next }),
};
}
export function mothershipAccessorialHasBlock(
issues: readonly MsCompatIssue[],
): boolean {
return issues.some((i) => i.severity === "block");
}