Deals Excel import/export aligned with Didar: browser-side import, new deal fields (code, probability, closedAt, creator, lostNote, referrer), Didar custom fields

This commit is contained in:
2026-10-06 16:36:04 +03:30
parent e08f1452ef
commit 2a2728e5ee
11 changed files with 1032 additions and 210 deletions
+23 -6
View File
@@ -39,17 +39,24 @@ export type ClientExcelPositionalColumn = { key: string; label: string };
export type ClientExcelReadOptions = {
positionalColumns?: ClientExcelPositionalColumn[];
detectHeaderless?: (firstRowCells: string[]) => boolean;
/**
* مقدار خام سلول به‌جای متن قالب‌بندی‌شده؛ خروجی دیدار به کدهای عددی قالب تاریخ می‌دهد
* و بدون این گزینه «4231» به‌صورت «8/1/11» خوانده می‌شود.
*/
rawValues?: boolean;
/** ستون‌های ناشناخته با کلید `${prefix}${عنوان ستون}` ارسال می‌شوند (فیلد سفارشی) */
customFieldPrefix?: string;
};
function readMatrix(fileBuffer: ArrayBuffer) {
const wb = XLSX.read(fileBuffer, { type: 'array', cellDates: true });
function readMatrix(fileBuffer: ArrayBuffer, rawValues = false) {
const wb = XLSX.read(fileBuffer, { type: 'array', cellDates: !rawValues });
const sheetName = wb.SheetNames[0];
if (!sheetName) throw new Error('شیت اکسل پیدا نشد');
const sheet = wb.Sheets[sheetName]!;
return XLSX.utils.sheet_to_json<unknown[]>(sheet, {
header: 1,
defval: '',
raw: false,
raw: rawValues,
}) as unknown[][];
}
@@ -60,11 +67,13 @@ export async function previewExcelClient(
fields: ExcelFieldOption[],
options?: ClientExcelReadOptions,
): Promise<ExcelPreviewResult> {
const matrix = readMatrix(await file.arrayBuffer());
const matrix = readMatrix(await file.arrayBuffer(), options?.rawValues);
const headerRow = (matrix[0] ?? []).map((c) => cellToString(c));
const headerless =
Boolean(options?.positionalColumns?.length) &&
Boolean(options?.detectHeaderless?.(headerRow));
const prefix = options?.customFieldPrefix;
const allFields = [...fields];
const columns: ExcelPreviewResult['columns'] = [];
const suggested: Record<string, string> = {};
@@ -96,6 +105,11 @@ export async function previewExcelClient(
if (key && !used.has(key)) {
suggested[header] = key;
used.add(key);
} else if (!key && prefix) {
const cfKey = `${prefix}${header}`;
allFields.push({ key: cfKey, label: `فیلد سفارشی: ${header}` });
suggested[header] = cfKey;
used.add(cfKey);
}
});
}
@@ -116,7 +130,7 @@ export async function previewExcelClient(
return {
columns,
fields,
fields: allFields,
suggested,
needsMapping: headerless
? false
@@ -132,7 +146,7 @@ export async function readMappedRowsClient(
mapping?: Record<string, string>,
options?: ClientExcelReadOptions,
): Promise<Record<string, string>[]> {
const matrix = readMatrix(await file.arrayBuffer());
const matrix = readMatrix(await file.arrayBuffer(), options?.rawValues);
const headerRow = (matrix[0] ?? []).map((c) => cellToString(c));
const headerless =
!mapping &&
@@ -152,6 +166,9 @@ export async function readMappedRowsClient(
if (!header) return;
const key = resolveFieldKey(header, aliases, mapping);
if (key) colMap.set(idx, key);
else if (!mapping && options?.customFieldPrefix) {
colMap.set(idx, `${options.customFieldPrefix}${header}`);
}
});
}