fix(intake): keep barcode print as reserved entry
This commit is contained in:
parent
90ff111283
commit
2ff31032c6
File diff suppressed because it is too large
Load Diff
@ -1,437 +0,0 @@
|
||||
import type { BarcodePrintRow } from "./barcodePrintRows";
|
||||
|
||||
export type BarcodePrintImportKind = "barcode" | "brandSku";
|
||||
|
||||
type BarcodePrintImportUpdates = Partial<
|
||||
Omit<BarcodePrintRow, "id" | "importRecordIds">
|
||||
> & {
|
||||
skuCode: string;
|
||||
dataSource: string;
|
||||
};
|
||||
|
||||
export type BarcodePrintImportRecord = {
|
||||
id: string;
|
||||
kind: BarcodePrintImportKind;
|
||||
skuCode: string;
|
||||
sourceFileName: string;
|
||||
rowNumber: number;
|
||||
importedAt: number;
|
||||
updates: BarcodePrintImportUpdates;
|
||||
};
|
||||
|
||||
export type BarcodePrintImportResult = {
|
||||
kind: BarcodePrintImportKind;
|
||||
fileName: string;
|
||||
records: BarcodePrintImportRecord[];
|
||||
};
|
||||
|
||||
export const BARCODE_PRINT_IMPORT_ACCEPT =
|
||||
".xls,.xlsx,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
|
||||
const BARCODE_IMPORT_HEADERS = [
|
||||
"SPU",
|
||||
"型号",
|
||||
"货号",
|
||||
"条码",
|
||||
"工厂型号",
|
||||
"规格名称",
|
||||
"商品类目",
|
||||
"尺寸",
|
||||
"颜色",
|
||||
"执行标准",
|
||||
"吊牌材质",
|
||||
"供应商名称",
|
||||
];
|
||||
|
||||
const BRAND_SKU_IMPORT_HEADERS = [
|
||||
"货号",
|
||||
"品牌货号",
|
||||
"品名",
|
||||
"规格(长*宽*高)",
|
||||
"合格证的主材*基材",
|
||||
];
|
||||
|
||||
const IMPORT_LABEL_BY_KIND: Record<BarcodePrintImportKind, string> = {
|
||||
barcode: "条码导入",
|
||||
brandSku: "品牌货号导入",
|
||||
};
|
||||
|
||||
const TEMPLATE_HEADERS_BY_KIND: Record<BarcodePrintImportKind, string[]> = {
|
||||
barcode: BARCODE_IMPORT_HEADERS,
|
||||
brandSku: BRAND_SKU_IMPORT_HEADERS,
|
||||
};
|
||||
|
||||
const TEMPLATE_FILE_NAME_BY_KIND: Record<BarcodePrintImportKind, string> = {
|
||||
barcode: "条码导入模板.xlsx",
|
||||
brandSku: "品牌货号导入模板.xlsx",
|
||||
};
|
||||
|
||||
const EMPTY_STANDALONE_ROW: Omit<BarcodePrintRow, "id"> = {
|
||||
spu: "",
|
||||
model: "",
|
||||
specCode: "",
|
||||
setCount: "",
|
||||
skuCode: "",
|
||||
brandSkuCode: "",
|
||||
productName: "",
|
||||
specification: "",
|
||||
certificateMaterial: "",
|
||||
barcode: "",
|
||||
factoryModel: "",
|
||||
attributeCode: "",
|
||||
drawingId: "",
|
||||
barcodeSpecName: "",
|
||||
productCategory: "",
|
||||
salesCategory: "",
|
||||
materialType: "",
|
||||
size: "",
|
||||
color: "",
|
||||
material: "",
|
||||
executeStandard: "",
|
||||
tagMaterial: "",
|
||||
supplierName: "",
|
||||
warehouse: "",
|
||||
installationService: "",
|
||||
lengthCm: "",
|
||||
widthCm: "",
|
||||
heightCm: "",
|
||||
volumeCm3: "",
|
||||
weightKg: "",
|
||||
goodsShape: "",
|
||||
costPrice: "",
|
||||
productPlanning: "",
|
||||
visualPlanning: "",
|
||||
spuOwner: "",
|
||||
operationsOwner: "",
|
||||
purchaseOwner: "",
|
||||
demandDepartment: "",
|
||||
manualProducer: "",
|
||||
isSelfDevelopedPatentProduct: "",
|
||||
patentOwner: "",
|
||||
profitCenter: "",
|
||||
salesChannel: "",
|
||||
salesPlatform: "",
|
||||
productionType: "",
|
||||
shippingMode: "",
|
||||
supplierCategory: "",
|
||||
purchaseOrderMethod: "",
|
||||
assemblyDetail: "",
|
||||
locked: "",
|
||||
createdAt: "",
|
||||
dataSource: "导入",
|
||||
};
|
||||
|
||||
function normalizeHeader(value: unknown) {
|
||||
return String(value ?? "").replace(/^\uFEFF/, "").trim();
|
||||
}
|
||||
|
||||
function normalizeSkuCode(value: string) {
|
||||
return value.trim().toUpperCase();
|
||||
}
|
||||
|
||||
function cellText(value: unknown) {
|
||||
return String(value ?? "").trim();
|
||||
}
|
||||
|
||||
function importRecordKey(record: BarcodePrintImportRecord) {
|
||||
return `${record.kind}:${normalizeSkuCode(record.skuCode)}`;
|
||||
}
|
||||
|
||||
function buildHeaderIndex(headers: unknown[]) {
|
||||
const headerIndex = new Map<string, number>();
|
||||
headers.forEach((header, index) => {
|
||||
const normalizedHeader = normalizeHeader(header);
|
||||
if (normalizedHeader) {
|
||||
headerIndex.set(normalizedHeader, index);
|
||||
}
|
||||
});
|
||||
return headerIndex;
|
||||
}
|
||||
|
||||
function requireHeaders(
|
||||
headerIndex: Map<string, number>,
|
||||
kind: BarcodePrintImportKind,
|
||||
) {
|
||||
const missingHeaders = TEMPLATE_HEADERS_BY_KIND[kind].filter(
|
||||
(header) => !headerIndex.has(header),
|
||||
);
|
||||
|
||||
if (missingHeaders.length > 0) {
|
||||
throw new Error(
|
||||
`${IMPORT_LABEL_BY_KIND[kind]}模板缺少字段:${missingHeaders.join("、")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getCell(
|
||||
row: unknown[],
|
||||
headerIndex: Map<string, number>,
|
||||
headerName: string,
|
||||
) {
|
||||
const index = headerIndex.get(headerName);
|
||||
return typeof index === "number" ? cellText(row[index]) : "";
|
||||
}
|
||||
|
||||
function setIfPresent<
|
||||
K extends keyof Omit<BarcodePrintRow, "id" | "importRecordIds">,
|
||||
>(
|
||||
updates: Partial<Record<K, string>>,
|
||||
key: K,
|
||||
value: string,
|
||||
) {
|
||||
if (value.trim()) {
|
||||
updates[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function createRecordId(
|
||||
kind: BarcodePrintImportKind,
|
||||
skuCode: string,
|
||||
rowNumber: number,
|
||||
) {
|
||||
const normalizedSkuCode = normalizeSkuCode(skuCode).replace(
|
||||
/[^A-Z0-9_-]/g,
|
||||
"_",
|
||||
);
|
||||
return `barcode-print-import:${kind}:${Date.now()}:${rowNumber}:${normalizedSkuCode || "row"}`;
|
||||
}
|
||||
|
||||
function buildBarcodeUpdates(
|
||||
row: unknown[],
|
||||
headerIndex: Map<string, number>,
|
||||
) {
|
||||
const skuCode = getCell(row, headerIndex, "货号");
|
||||
const updates: BarcodePrintImportRecord["updates"] = {
|
||||
skuCode,
|
||||
dataSource: "导入",
|
||||
};
|
||||
|
||||
setIfPresent(updates, "spu", getCell(row, headerIndex, "SPU"));
|
||||
setIfPresent(updates, "model", getCell(row, headerIndex, "型号"));
|
||||
setIfPresent(updates, "barcode", getCell(row, headerIndex, "条码"));
|
||||
setIfPresent(updates, "factoryModel", getCell(row, headerIndex, "工厂型号"));
|
||||
setIfPresent(
|
||||
updates,
|
||||
"barcodeSpecName",
|
||||
getCell(row, headerIndex, "规格名称"),
|
||||
);
|
||||
setIfPresent(
|
||||
updates,
|
||||
"productCategory",
|
||||
getCell(row, headerIndex, "商品类目"),
|
||||
);
|
||||
setIfPresent(updates, "size", getCell(row, headerIndex, "尺寸"));
|
||||
setIfPresent(updates, "color", getCell(row, headerIndex, "颜色"));
|
||||
setIfPresent(
|
||||
updates,
|
||||
"executeStandard",
|
||||
getCell(row, headerIndex, "执行标准"),
|
||||
);
|
||||
setIfPresent(updates, "tagMaterial", getCell(row, headerIndex, "吊牌材质"));
|
||||
setIfPresent(
|
||||
updates,
|
||||
"supplierName",
|
||||
getCell(row, headerIndex, "供应商名称"),
|
||||
);
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
function buildBrandSkuUpdates(
|
||||
row: unknown[],
|
||||
headerIndex: Map<string, number>,
|
||||
) {
|
||||
const skuCode = getCell(row, headerIndex, "货号");
|
||||
const updates: BarcodePrintImportRecord["updates"] = {
|
||||
skuCode,
|
||||
dataSource: "导入",
|
||||
};
|
||||
|
||||
setIfPresent(
|
||||
updates,
|
||||
"brandSkuCode",
|
||||
getCell(row, headerIndex, "品牌货号"),
|
||||
);
|
||||
setIfPresent(updates, "productName", getCell(row, headerIndex, "品名"));
|
||||
setIfPresent(
|
||||
updates,
|
||||
"specification",
|
||||
getCell(row, headerIndex, "规格(长*宽*高)"),
|
||||
);
|
||||
setIfPresent(
|
||||
updates,
|
||||
"certificateMaterial",
|
||||
getCell(row, headerIndex, "合格证的主材*基材"),
|
||||
);
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
function buildImportRecord({
|
||||
row,
|
||||
headerIndex,
|
||||
kind,
|
||||
fileName,
|
||||
rowNumber,
|
||||
}: {
|
||||
row: unknown[];
|
||||
headerIndex: Map<string, number>;
|
||||
kind: BarcodePrintImportKind;
|
||||
fileName: string;
|
||||
rowNumber: number;
|
||||
}) {
|
||||
const updates =
|
||||
kind === "barcode"
|
||||
? buildBarcodeUpdates(row, headerIndex)
|
||||
: buildBrandSkuUpdates(row, headerIndex);
|
||||
|
||||
if (!updates.skuCode.trim()) {
|
||||
throw new Error(`第 ${rowNumber} 行缺少货号。`);
|
||||
}
|
||||
|
||||
return {
|
||||
id: createRecordId(kind, updates.skuCode, rowNumber),
|
||||
kind,
|
||||
skuCode: updates.skuCode,
|
||||
sourceFileName: fileName,
|
||||
rowNumber,
|
||||
importedAt: Date.now(),
|
||||
updates,
|
||||
};
|
||||
}
|
||||
|
||||
export async function parseBarcodePrintImportFile(
|
||||
file: File,
|
||||
kind: BarcodePrintImportKind,
|
||||
): Promise<BarcodePrintImportResult> {
|
||||
const XLSX = await import("xlsx");
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const workbook = XLSX.read(arrayBuffer, { type: "array", cellDates: false });
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
|
||||
if (!sheetName) {
|
||||
throw new Error("导入文件里没有可读取的工作表。");
|
||||
}
|
||||
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
const rows = XLSX.utils.sheet_to_json(worksheet, {
|
||||
header: 1,
|
||||
defval: "",
|
||||
raw: false,
|
||||
blankrows: false,
|
||||
}) as unknown[][];
|
||||
const headerRow = rows[0] ?? [];
|
||||
const headerIndex = buildHeaderIndex(headerRow);
|
||||
|
||||
requireHeaders(headerIndex, kind);
|
||||
|
||||
const dataRows = rows
|
||||
.slice(1)
|
||||
.map((row, index) => ({ row, rowNumber: index + 2 }))
|
||||
.filter(({ row }) => row.some((cell) => cellText(cell)));
|
||||
|
||||
if (dataRows.length === 0) {
|
||||
throw new Error(`${IMPORT_LABEL_BY_KIND[kind]}模板里没有可导入的数据。`);
|
||||
}
|
||||
|
||||
return {
|
||||
kind,
|
||||
fileName: file.name,
|
||||
records: dataRows.map(({ row, rowNumber }) =>
|
||||
buildImportRecord({
|
||||
row,
|
||||
headerIndex,
|
||||
kind,
|
||||
fileName: file.name,
|
||||
rowNumber,
|
||||
}),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function upsertBarcodePrintImportRecords(
|
||||
currentRecords: BarcodePrintImportRecord[],
|
||||
incomingRecords: BarcodePrintImportRecord[],
|
||||
) {
|
||||
const incomingKeys = new Set(incomingRecords.map(importRecordKey));
|
||||
|
||||
return [
|
||||
...currentRecords.filter(
|
||||
(record) => !incomingKeys.has(importRecordKey(record)),
|
||||
),
|
||||
...incomingRecords,
|
||||
];
|
||||
}
|
||||
|
||||
export function mergeBarcodePrintImportRows(
|
||||
baseRows: BarcodePrintRow[],
|
||||
importRecords: BarcodePrintImportRecord[],
|
||||
): BarcodePrintRow[] {
|
||||
const recordsBySkuCode = new Map<string, BarcodePrintImportRecord[]>();
|
||||
|
||||
importRecords.forEach((record) => {
|
||||
const skuCodeKey = normalizeSkuCode(record.skuCode);
|
||||
if (!skuCodeKey) return;
|
||||
const records = recordsBySkuCode.get(skuCodeKey) ?? [];
|
||||
records.push(record);
|
||||
recordsBySkuCode.set(skuCodeKey, records);
|
||||
});
|
||||
|
||||
const matchedSkuCodes = new Set<string>();
|
||||
const mergedRows = baseRows.map((row) => {
|
||||
const skuCodeKey = normalizeSkuCode(row.skuCode);
|
||||
const records = skuCodeKey ? recordsBySkuCode.get(skuCodeKey) : undefined;
|
||||
|
||||
if (!records?.length) return row;
|
||||
|
||||
matchedSkuCodes.add(skuCodeKey);
|
||||
|
||||
const updates = Object.assign({}, ...records.map((record) => record.updates));
|
||||
|
||||
return {
|
||||
...row,
|
||||
...updates,
|
||||
id: row.id,
|
||||
skuCode: row.skuCode || updates.skuCode || "",
|
||||
dataSource: "导入",
|
||||
importRecordIds: records.map((record) => record.id),
|
||||
};
|
||||
});
|
||||
|
||||
const standaloneRows = Array.from(recordsBySkuCode.entries())
|
||||
.filter(([skuCodeKey]) => !matchedSkuCodes.has(skuCodeKey))
|
||||
.map(([skuCodeKey, records]) => {
|
||||
const updates = Object.assign(
|
||||
{},
|
||||
...records.map((record) => record.updates),
|
||||
);
|
||||
|
||||
return {
|
||||
...EMPTY_STANDALONE_ROW,
|
||||
...updates,
|
||||
id: `barcode-print-standalone:${skuCodeKey}`,
|
||||
skuCode: updates.skuCode || records.at(-1)?.skuCode || "",
|
||||
dataSource: "导入",
|
||||
importRecordIds: records.map((record) => record.id),
|
||||
};
|
||||
});
|
||||
|
||||
return [...mergedRows, ...standaloneRows];
|
||||
}
|
||||
|
||||
export async function downloadBarcodePrintImportTemplate(
|
||||
kind: BarcodePrintImportKind,
|
||||
) {
|
||||
const XLSX = await import("xlsx");
|
||||
const headers = TEMPLATE_HEADERS_BY_KIND[kind];
|
||||
const workbook = XLSX.utils.book_new();
|
||||
const worksheet = XLSX.utils.aoa_to_sheet([headers]);
|
||||
worksheet["!cols"] = headers.map((header) => ({
|
||||
wch: Math.max(12, header.length + 4),
|
||||
}));
|
||||
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "模板");
|
||||
XLSX.writeFile(workbook, TEMPLATE_FILE_NAME_BY_KIND[kind], {
|
||||
bookType: "xlsx",
|
||||
});
|
||||
}
|
||||
@ -1,168 +0,0 @@
|
||||
import type { NonIntegratedProductRecord } from "@/app/intake/non-integrated/nonIntegratedStorage";
|
||||
|
||||
export type BarcodePrintRow = {
|
||||
id: string;
|
||||
importRecordIds?: string[];
|
||||
spu: string;
|
||||
model: string;
|
||||
specCode: string;
|
||||
setCount: string;
|
||||
skuCode: string;
|
||||
brandSkuCode: string;
|
||||
productName: string;
|
||||
specification: string;
|
||||
certificateMaterial: string;
|
||||
barcode: string;
|
||||
factoryModel: string;
|
||||
attributeCode: string;
|
||||
drawingId: string;
|
||||
barcodeSpecName: string;
|
||||
productCategory: string;
|
||||
salesCategory: string;
|
||||
materialType: string;
|
||||
size: string;
|
||||
color: string;
|
||||
material: string;
|
||||
executeStandard: string;
|
||||
tagMaterial: string;
|
||||
supplierName: string;
|
||||
warehouse: string;
|
||||
installationService: string;
|
||||
lengthCm: string;
|
||||
widthCm: string;
|
||||
heightCm: string;
|
||||
volumeCm3: string;
|
||||
weightKg: string;
|
||||
goodsShape: string;
|
||||
costPrice: string;
|
||||
productPlanning: string;
|
||||
visualPlanning: string;
|
||||
spuOwner: string;
|
||||
operationsOwner: string;
|
||||
purchaseOwner: string;
|
||||
demandDepartment: string;
|
||||
manualProducer: string;
|
||||
isSelfDevelopedPatentProduct: string;
|
||||
patentOwner: string;
|
||||
profitCenter: string;
|
||||
salesChannel: string;
|
||||
salesPlatform: string;
|
||||
productionType: string;
|
||||
shippingMode: string;
|
||||
supplierCategory: string;
|
||||
purchaseOrderMethod: string;
|
||||
assemblyDetail: string;
|
||||
locked: string;
|
||||
createdAt: string;
|
||||
dataSource: string;
|
||||
};
|
||||
|
||||
function firstText(...values: Array<string | undefined | null>) {
|
||||
return values.find((value) => value?.trim())?.trim() ?? "";
|
||||
}
|
||||
|
||||
function calculateVolumeCm3(lengthCm: string, widthCm: string, heightCm: string) {
|
||||
const length = Number.parseFloat(lengthCm);
|
||||
const width = Number.parseFloat(widthCm);
|
||||
const height = Number.parseFloat(heightCm);
|
||||
|
||||
if (!Number.isFinite(length) || !Number.isFinite(width) || !Number.isFinite(height)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const volume = length * width * height;
|
||||
if (!Number.isFinite(volume)) return "";
|
||||
|
||||
return Number.isInteger(volume) ? String(volume) : volume.toFixed(2);
|
||||
}
|
||||
|
||||
function normalizeCreatedAt(value: string) {
|
||||
if (!value.trim()) return "";
|
||||
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) return value;
|
||||
|
||||
return parsed.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
export function buildBarcodePrintRows(
|
||||
products: NonIntegratedProductRecord[],
|
||||
): BarcodePrintRow[] {
|
||||
return products.flatMap((product) => {
|
||||
const skuItems = product.skuItems.length > 0 ? product.skuItems : [null];
|
||||
|
||||
return skuItems.map((sku, index) => {
|
||||
const skuId = sku?.id ?? `${product.id}-empty-sku`;
|
||||
const factoryRow = product.factoryRows.find((row) => row.skuId === skuId);
|
||||
const measurementRow = product.sampleMeasurementRows.find(
|
||||
(row) => row.skuId === skuId,
|
||||
);
|
||||
const pricingRow = product.pricingRows.find((row) => row.skuId === skuId);
|
||||
const lengthCm = measurementRow?.lengthCm?.trim() ?? "";
|
||||
const widthCm = measurementRow?.widthCm?.trim() ?? "";
|
||||
const heightCm = measurementRow?.heightCm?.trim() ?? "";
|
||||
const barcode = firstText(factoryRow?.barcode, sku?.barcode);
|
||||
const skuCode = firstText(factoryRow?.skuCode, sku?.skuCode);
|
||||
|
||||
return {
|
||||
id: `${product.id}:${skuId}:${index}`,
|
||||
spu: product.spu,
|
||||
model: firstText(sku?.model, factoryRow?.model, product.productCode),
|
||||
specCode: firstText(sku?.specName, skuCode),
|
||||
setCount: firstText(factoryRow?.setCount, sku?.setCount),
|
||||
skuCode,
|
||||
brandSkuCode: firstText(product.itemId, product.productCode),
|
||||
productName: firstText(
|
||||
product.openingReference.productName,
|
||||
product.productTag,
|
||||
product.spu,
|
||||
),
|
||||
specification: firstText(sku?.size, product.size),
|
||||
certificateMaterial: product.packageDetail.trim(),
|
||||
barcode,
|
||||
factoryModel: firstText(factoryRow?.model, sku?.model),
|
||||
attributeCode: sku?.attributeCode?.trim() ?? "",
|
||||
drawingId: sku?.drawingId?.trim() ?? "",
|
||||
barcodeSpecName: firstText(factoryRow?.specName, sku?.specName),
|
||||
productCategory: product.productCategory,
|
||||
salesCategory: product.salesCategory,
|
||||
materialType: firstText(product.materialType, sku?.boardMaterialType),
|
||||
size: firstText(sku?.size, product.size),
|
||||
color: firstText(sku?.color, product.color),
|
||||
material: firstText(sku?.material, product.openingReference.material),
|
||||
executeStandard: firstText(factoryRow?.executeStandard, sku?.executeStandard),
|
||||
tagMaterial: firstText(sku?.tagMaterial, product.tagMaterial),
|
||||
supplierName: firstText(factoryRow?.supplierName, sku?.manufacturer),
|
||||
warehouse: factoryRow?.warehouse?.trim() ?? "",
|
||||
installationService: factoryRow?.installationService?.trim() ?? "",
|
||||
lengthCm,
|
||||
widthCm,
|
||||
heightCm,
|
||||
volumeCm3: calculateVolumeCm3(lengthCm, widthCm, heightCm),
|
||||
weightKg: measurementRow?.weightKg?.trim() ?? "",
|
||||
goodsShape: product.goodsType.trim(),
|
||||
costPrice: pricingRow?.costPrice?.trim() ?? "",
|
||||
productPlanning: product.productPlanning,
|
||||
visualPlanning: product.visualPlanning,
|
||||
spuOwner: product.spuOwner,
|
||||
operationsOwner: product.operationsOwner,
|
||||
purchaseOwner: product.purchaseOwner,
|
||||
demandDepartment: product.demandDepartment,
|
||||
manualProducer: product.manualProducer,
|
||||
isSelfDevelopedPatentProduct: product.isSelfDevelopedPatentProduct,
|
||||
patentOwner: product.patentOwner,
|
||||
profitCenter: product.profitCenter,
|
||||
salesChannel: product.salesChannel,
|
||||
salesPlatform: product.salesPlatform,
|
||||
productionType: firstText(factoryRow?.productionType, product.productionType),
|
||||
shippingMode: firstText(factoryRow?.shippingMethod, product.shippingMode),
|
||||
supplierCategory: factoryRow?.supplierCategory?.trim() ?? "",
|
||||
purchaseOrderMethod: factoryRow?.purchaseOrderMethod?.trim() ?? "",
|
||||
assemblyDetail: product.installSteps.trim(),
|
||||
locked: sku?.contentLocked ? "是" : "否",
|
||||
createdAt: normalizeCreatedAt(product.createdAt),
|
||||
dataSource: firstText(sku?.sourceTable, sku?.source, "流程"),
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user