Split large activity Excel prepare from chunk import and clarify errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-03 17:58:41 +03:30
co-authored by Cursor
parent 07bdc4a3b6
commit d3fb92a5e2
4 changed files with 116 additions and 4 deletions
@@ -18,6 +18,7 @@ import {
} from '../common/excel';
import {
chunkImportMeta,
createExcelImportSession,
destroyExcelImportSession,
getExcelImportSessionCaches,
resolveExcelImportChunk,
@@ -160,6 +161,49 @@ export class ActivitiesExcelService {
return previewExcel(file, ACTIVITY_ALIASES, excelFieldsFromColumns(ACTIVITY_COLUMNS, ['title']));
}
/** فقط خواندن و جلسه؛ بدون درج — برای فایل‌های خیلی بزرگ قبل از تکه‌تکه */
async prepare(
actor: Actor,
file: Express.Multer.File,
mappingRaw?: string,
): Promise<ImportContactsResult> {
const result: ImportContactsResult = { created: 0, skipped: 0, errors: [] };
let rows: Record<string, string>[];
try {
rows = await readMappedRows(
file,
ACTIVITY_ALIASES,
parseExcelMapping(mappingRaw),
);
} catch (e) {
if (e instanceof BadRequestException) throw e;
result.errors.push({
row: 1,
message: e instanceof Error ? e.message : 'خواندن فایل اکسل ناموفق بود',
});
return result;
}
const MAX_ROWS = 100_000;
if (rows.length > MAX_ROWS) {
result.errors.push({
row: 1,
message: `فایل بیش از ${MAX_ROWS} ردیف دارد. فایل را تقسیم کنید.`,
});
return { ...result, total: rows.length, processed: 0, hasMore: false };
}
const sessionId = createExcelImportSession(actor.organizationId, rows);
return {
...result,
sessionId,
total: rows.length,
processed: 0,
hasMore: rows.length > 0,
nextOffset: 0,
};
}
async import(
actor: Actor,
file: Express.Multer.File | undefined,
@@ -100,6 +100,19 @@ export class ActivitiesController {
return this.excelService.preview(file);
}
@Post('import/prepare')
@UseInterceptors(excelUpload)
importPrepare(
@CurrentUser() user: Actor,
@UploadedFile() file?: Express.Multer.File,
@Body('mapping') mapping?: string,
) {
if (!file?.buffer?.length) {
throw new BadRequestException('فایل ارسال نشده');
}
return this.excelService.prepare(user, file, mapping);
}
@Post('import')
@UseInterceptors(excelUpload)
import(