Files
inquiry_robot/inquiry-backend/src/views/inquiry/template/previewSample.ts
T

254 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 报价模板预览:Excel 样式网格(上传原文),辅以映射摘要。
* 主数据来自 /previewSheet;mappingJson 用于已映射/未映射清单。
*/
/** 系统字段 code → 中文标签 */
export const FIELD_LABELS: Record<string, string> = {
customer_name: "客户名称",
contact_name: "联系人",
contact_phone: "电话",
quote_no: "报价单编号",
quote_date: "报价日期",
valid_until: "有效期至",
currency: "币别",
sales_name: "业务员",
pol: "起运港",
pod: "目的港",
origin: "始发地",
destination: "目的地",
commodity: "品名",
quantity: "货物数量",
container_type_qty: "箱型箱量",
fcl_or_lcl: "整柜或拼柜",
trade_terms: "贸易条款",
cargo_ready_date: "货好时间",
pieces: "件数",
weight_kg: "重量",
volume_cbm: "体积",
total_amount: "报价合计",
chargeable_weight_kg: "计费重",
delivery_address: "送货地址",
pickup_address: "提货地址",
transit_time: "运输时效",
cargo_value: "货值",
hs_code: "HS编码",
oil_elec_mag: "油电磁",
has_battery: "是否带电",
on_board_date: "开船日",
special_remarks: "特殊备注",
eta_request: "要求到港",
sailing_schedule: "船期",
shipping_line: "船公司",
flight_no: "航班号",
transfer_airport: "中转机场",
effective_date: "生效日期",
fuel_surcharge_rate: "燃油附加费率",
tax_rate: "税率",
has_oil: "是否含油",
has_magnet: "是否含磁",
vehicle_qty: "车型/数量",
};
/** 解析结果摘要:供预览弹窗展示「已解析 / 未解析」 */
export type MappingFieldItem = {
code: string;
label: string;
cell: string;
};
export type MappingRegionItem = {
kind: "fee" | "route";
id: string;
title: string;
};
export type MappingParseSummary = {
fields: MappingFieldItem[];
regions: MappingRegionItem[];
gaps: string[];
unmappedRequired: string[];
unmappedLabels: string[];
pointCount: number;
};
function asMappingObject(mappingJson: unknown): Record<string, any> | null {
if (!mappingJson) return null;
if (typeof mappingJson === "object") return mappingJson as Record<string, any>;
if (typeof mappingJson === "string") {
try {
const obj = JSON.parse(mappingJson);
return obj && typeof obj === "object" ? obj : null;
} catch {
return null;
}
}
return null;
}
/**
* 从 mappingJson 拆出运营可读的已映射 / 未映射清单。
* 不打接口;mappingGaps 为空时(旧版解析)仍可看 fields 与 unmapped*。
*/
export function summarizeMappingParse(mappingJson: unknown): MappingParseSummary {
const obj = asMappingObject(mappingJson);
const empty: MappingParseSummary = {
fields: [],
regions: [],
gaps: [],
unmappedRequired: [],
unmappedLabels: [],
pointCount: 0,
};
if (!obj) return empty;
const fieldsObj = obj.fields && typeof obj.fields === "object" ? obj.fields : {};
const fields: MappingFieldItem[] = Object.keys(fieldsObj)
.filter((k) => fieldsObj[k])
.map((code) => ({
code,
label: FIELD_LABELS[code] || code,
cell: String(fieldsObj[code] || ""),
}))
.sort((a, b) => a.label.localeCompare(b.label, "zh"));
const regions: MappingRegionItem[] = [];
const feeRegions = Array.isArray(obj.feeRegions) ? obj.feeRegions : [];
for (const r of feeRegions) {
if (!r || typeof r !== "object") continue;
regions.push({
kind: "fee",
id: String(r.id || ""),
title: String(r.title || r.id || "费用区"),
});
}
const routeRegions = Array.isArray(obj.routeRegions) ? obj.routeRegions : [];
for (const r of routeRegions) {
if (!r || typeof r !== "object") continue;
regions.push({
kind: "route",
id: String(r.id || ""),
title: String(r.title || r.id || "线路区"),
});
}
const gaps = Array.isArray(obj.mappingGaps)
? obj.mappingGaps.map((x: any) => String(x)).filter(Boolean)
: [];
const unmappedRequired = Array.isArray(obj.unmappedRequired)
? obj.unmappedRequired.map((x: any) => String(x)).filter(Boolean)
: [];
const unmappedLabels = Array.isArray(obj.unmappedLabels)
? obj.unmappedLabels.map((x: any) => String(x)).filter(Boolean)
: [];
return {
fields,
regions,
gaps,
unmappedRequired,
unmappedLabels,
pointCount: fields.length + regions.length,
};
}
export type SheetPreviewCell = {
addr: string;
text: string;
yellow: boolean;
mappedCode?: string;
mappedLabel?: string;
};
export type SheetPreviewRow = {
rowNo: number;
cells: SheetPreviewCell[];
};
export type PreviewViewModel = {
loading: boolean;
error: string;
sheet: string;
fileName: string;
colHeaders: string[];
rows: SheetPreviewRow[];
mappedCount: number;
yellowCount: number;
unmappedLabels: string[];
unmappedRequired: string[];
};
function parseUnmapped(mappingJson: unknown): string[] {
if (!mappingJson) return [];
let obj: any = mappingJson;
if (typeof mappingJson === "string") {
try {
obj = JSON.parse(mappingJson);
} catch {
return [];
}
}
return Array.isArray(obj?.unmappedRequired) ? obj.unmappedRequired.map((x: any) => String(x)) : [];
}
/** 把主账 previewSheet 结果转成弹窗视图 */
export function buildSheetPreviewViewModel(sheetRes: any, mappingJson?: unknown): PreviewViewModel {
const colHeaders = Array.isArray(sheetRes?.colHeaders)
? sheetRes.colHeaders.map((x: any) => String(x))
: [];
const rowsRaw = Array.isArray(sheetRes?.rows) ? sheetRes.rows : [];
const rows: SheetPreviewRow[] = rowsRaw.map((r: any) => ({
rowNo: Number(r.rowNo || 0),
cells: Array.isArray(r.cells)
? r.cells.map((c: any) => ({
addr: String(c.addr || ""),
text: String(c.text || ""),
yellow: Boolean(c.yellow),
mappedCode: c.mappedCode ? String(c.mappedCode) : undefined,
mappedLabel: c.mappedLabel ? String(c.mappedLabel) : undefined,
}))
: [],
}));
let yellowCount = 0;
let mappedHit = 0;
for (const row of rows) {
for (const c of row.cells) {
if (c.yellow) yellowCount += 1;
if (c.mappedCode) mappedHit += 1;
}
}
const unmapped = parseUnmapped(mappingJson);
return {
loading: false,
error: String(sheetRes?.error || ""),
sheet: String(sheetRes?.sheet || ""),
fileName: String(sheetRes?.fileName || ""),
colHeaders,
rows,
mappedCount: Number(sheetRes?.mappedCount || mappedHit || 0),
yellowCount,
unmappedLabels: unmapped.map((c) => FIELD_LABELS[c] || c),
unmappedRequired: unmapped,
};
}
export function emptyPreviewViewModel(): PreviewViewModel {
return {
loading: false,
error: "",
sheet: "",
fileName: "",
colHeaders: [],
rows: [],
mappedCount: 0,
yellowCount: 0,
unmappedLabels: [],
unmappedRequired: [],
};
}
/** @deprecated 兼容旧名:请用 buildSheetPreviewViewModel */
export function buildPreviewViewModel(_mappingJson: unknown, _pointCountHint = 0): PreviewViewModel {
return emptyPreviewViewModel();
}