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.
62 lines
1.8 KiB
62 lines
1.8 KiB
import { compareUiTierSortOrder } from "@/lib/constants/quote";
|
|
import type { QuoteItem } from "@/modules/providers/quote-provider";
|
|
import { RpaError } from "@/modules/rpa/errors";
|
|
|
|
function tierKey(item: QuoteItem): string {
|
|
const carrier = (item.carrier ?? "").trim().toLowerCase();
|
|
return `${item.serviceLevel}/${item.rateOption}/${carrier}`;
|
|
}
|
|
|
|
/** 保留 MotherShip 实际返回的全部 UI 档位;同档不同承运商不去重挤掉 */
|
|
export function normalizeQuoteItems(items: QuoteItem[]): QuoteItem[] {
|
|
const map = new Map<string, QuoteItem>();
|
|
for (const item of items) {
|
|
map.set(tierKey(item), item);
|
|
}
|
|
|
|
const normalized = [...map.values()].sort((a, b) => {
|
|
const byTier = compareUiTierSortOrder(
|
|
a.serviceLevel,
|
|
a.rateOption,
|
|
b.serviceLevel,
|
|
b.rateOption,
|
|
);
|
|
if (byTier !== 0) return byTier;
|
|
return (a.carrier ?? "").localeCompare(b.carrier ?? "");
|
|
});
|
|
|
|
if (normalized.length === 0) {
|
|
throw new RpaError(
|
|
"RPA_DATA_INVALID",
|
|
"未解析到有效报价档位",
|
|
{ retryable: false },
|
|
);
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
/** 网络层报价严格校验(唯一验收入口) */
|
|
export function validateQuoteSchema(items: QuoteItem[]): QuoteItem[] {
|
|
const normalized = normalizeQuoteItems(items);
|
|
|
|
for (const tier of normalized) {
|
|
if (tier.rawFreight <= 0) {
|
|
throw new RpaError(
|
|
"RPA_DATA_INVALID",
|
|
`档位 ${tier.serviceLevel}/${tier.rateOption} 运费无效`,
|
|
{ retryable: false },
|
|
);
|
|
}
|
|
if (!tier.transitDays?.trim() || tier.transitDays === "—") {
|
|
throw new RpaError(
|
|
"RPA_DATA_INVALID",
|
|
`档位 ${tier.serviceLevel}/${tier.rateOption} 时效为空`,
|
|
{ retryable: false },
|
|
);
|
|
}
|
|
}
|
|
|
|
return normalized;
|
|
}
|