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
+77 -8
View File
@@ -10,8 +10,10 @@ import type {
UpdateTahesabConnectionRequest,
} from '@mento/shared';
import { brand } from '@mento/shared';
import { toFaDigits } from '@/lib/jalali';
import {
UiBadge,
UiBusyOverlay,
UiButton,
UiCard,
UiField,
@@ -48,6 +50,13 @@ const sync = reactive({
toCode: '',
});
const syncBusy = ref(false);
/** تعداد مشتری در هر درخواست ورود دسته‌ای (برای جلوگیری از تایم‌اوت و نمایش درصد) */
const IMPORT_CHUNK = 25;
const importOverlay = reactive<{ open: boolean; message: string; progress: number | null }>({
open: false,
message: '',
progress: null,
});
type SyncResult = {
moshtariCode: number | null;
name: string;
@@ -475,18 +484,72 @@ async function importBulk(mode: 'results' | 'range') {
body.toCode = toCode;
}
}
syncBusy.value = true;
importOverlay.open = true;
importOverlay.progress = 0;
importOverlay.message = 'در حال آماده‌سازی فهرست مشتریان…';
const totals = { imported: 0, skipped: 0, failed: 0 };
let firstError = '';
try {
syncBusy.value = true;
const { data } = await api.post<{
imported: number;
skipped: number;
failed: number;
}>('/integrations/tahesab/sync/import-bulk', body);
ok.value = `وارد شد: ${data.imported} · قبلاً بود: ${data.skipped} · ناموفق: ${data.failed}`;
await searchTahesab(mode === 'range' ? 'range' : 'filter');
let codes: number[];
if (Array.isArray(body.moshtariCodes)) {
codes = body.moshtariCodes as number[];
} else {
const { data } = await api.post<{ results: SyncResult[] }>(
'/integrations/tahesab/sync/search',
body,
{ timeout: 90_000 },
);
const found = data.results ?? [];
totals.skipped += found.filter((r) => r.alreadyLinked).length;
codes = found
.filter((r) => r.moshtariCode != null && !r.alreadyLinked)
.map((r) => r.moshtariCode as number);
}
const total = codes.length;
for (let i = 0; i < total; i += IMPORT_CHUNK) {
const chunk = codes.slice(i, i + IMPORT_CHUNK);
importOverlay.message = `در حال وارد کردن مشتریان… ${toFaDigits(Math.min(i + chunk.length, total))} از ${toFaDigits(total)}`;
try {
const { data } = await api.post<{
imported: number;
skipped: number;
failed: number;
items?: Array<{ moshtariCode: number; ok: boolean; personId?: string; error?: string }>;
}>(
'/integrations/tahesab/sync/import-bulk',
{ connectionId: sync.connectionId, moshtariCodes: chunk },
{ timeout: 120_000 },
);
totals.imported += data.imported;
totals.skipped += data.skipped;
totals.failed += data.failed;
for (const item of data.items ?? []) {
if (!item.ok) {
if (!firstError && item.error) firstError = item.error;
continue;
}
const row = syncResults.value.find((r) => r.moshtariCode === item.moshtariCode);
if (row) {
row.alreadyLinked = true;
if (item.personId) row.personId = item.personId;
}
}
} catch (e) {
totals.failed += chunk.length;
if (!firstError) firstError = apiErr(e, 'خطا در ارسال');
}
importOverlay.progress = total ? ((i + chunk.length) / total) * 100 : 100;
}
ok.value = `وارد شد: ${toFaDigits(totals.imported)} · قبلاً بود: ${toFaDigits(totals.skipped)} · ناموفق: ${toFaDigits(totals.failed)}`;
if (firstError) error.value = `برخی موارد وارد نشدند: ${firstError}`;
} catch (e) {
error.value = apiErr(e, 'وارد کردن دسته‌ای ناموفق');
} finally {
importOverlay.open = false;
importOverlay.progress = null;
syncBusy.value = false;
}
}
@@ -980,6 +1043,12 @@ onMounted(load);
</div>
</div>
</UiCard>
<UiBusyOverlay
:open="importOverlay.open"
:message="importOverlay.message"
:progress="importOverlay.progress"
/>
</section>
</template>