Import Tahesab customers in batches with progress overlay and prefetched rows

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-05 21:08:38 +03:30
co-authored by Cursor
parent 46dcf296d3
commit f88786ef61
2 changed files with 125 additions and 27 deletions
@@ -465,6 +465,7 @@ export class TahesabPersonLinkService {
async importFromTahesab(
organizationId: string,
dto: ImportFromTahesabDto,
prefetched?: { api: TahesabApi; row?: Record<string, unknown> },
) {
const clash = await this.prisma.personAccountingLink.findFirst({
where: {
@@ -479,16 +480,19 @@ export class TahesabPersonLinkService {
);
}
const { api } = await this.connections.getApi(
organizationId,
dto.connectionId,
);
const raw = await api.doListMoshtariByCode(
dto.moshtariCode,
dto.moshtariCode,
);
const rows = asMoshtariList(raw);
const row = rows[0] ?? {};
let row: Record<string, unknown>;
if (prefetched?.row) {
row = prefetched.row;
} else {
const api =
prefetched?.api ??
(await this.connections.getApi(organizationId, dto.connectionId)).api;
const raw = await api.doListMoshtariByCode(
dto.moshtariCode,
dto.moshtariCode,
);
row = (asMoshtariList(raw)[0] ?? {}) as Record<string, unknown>;
}
const name = pickName(row) || `مشتری ته‌حساب ${dto.moshtariCode}`;
const tel = pickTel(row);
const parts = name.trim().split(/\s+/);
@@ -668,24 +672,49 @@ export class TahesabPersonLinkService {
error?: string;
}> = [];
const existingLinks = await this.prisma.personAccountingLink.findMany({
where: { connectionId: dto.connectionId, moshtariCode: { in: codes } },
select: { moshtariCode: true, personId: true },
});
const linkedByCode = new Map(existingLinks.map((l) => [l.moshtariCode, l.personId]));
const pending = codes.filter((c) => !linkedByCode.has(c));
let bulkApi: TahesabApi | undefined;
const rowsByCode = new Map<number, Record<string, unknown>>();
if (pending.length) {
bulkApi = (await this.connections.getApi(organizationId, dto.connectionId)).api;
const minCode = Math.min(...pending);
const maxCode = Math.max(...pending);
if (maxCode - minCode <= 2000) {
try {
const raw = await bulkApi.doListMoshtariByCode(minCode, maxCode);
for (const row of asMoshtariList(raw)) {
const code = pickCode(row);
if (code != null) rowsByCode.set(code, row);
}
} catch {
/* fallback: per-code fetch inside importFromTahesab */
}
}
}
for (const moshtariCode of codes) {
try {
const existing = await this.prisma.personAccountingLink.findFirst({
where: { connectionId: dto.connectionId, moshtariCode },
});
if (existing) {
const linkedPersonId = linkedByCode.get(moshtariCode);
if (linkedPersonId) {
items.push({
moshtariCode,
ok: true,
skipped: true,
personId: existing.personId,
personId: linkedPersonId,
});
continue;
}
const r = await this.importFromTahesab(organizationId, {
connectionId: dto.connectionId,
moshtariCode,
});
const r = await this.importFromTahesab(
organizationId,
{ connectionId: dto.connectionId, moshtariCode },
bulkApi ? { api: bulkApi, row: rowsByCode.get(moshtariCode) } : undefined,
);
items.push({ moshtariCode, ok: true, personId: r.personId });
} catch (e) {
const message =