From 0802c2f43690deaa444e9382051ae9815d62851f Mon Sep 17 00:00:00 2001 From: Mahdi Date: Sat, 10 Oct 2026 16:56:58 +0330 Subject: [PATCH] Montify sync: save progress every 5 customers; skip endpoints returning 404, summarize instead of per-customer errors --- .../src/integrations/montify/sync.service.ts | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/api/src/integrations/montify/sync.service.ts b/apps/api/src/integrations/montify/sync.service.ts index dc6fe1f..21d6dc7 100644 --- a/apps/api/src/integrations/montify/sync.service.ts +++ b/apps/api/src/integrations/montify/sync.service.ts @@ -37,6 +37,10 @@ import { const CUSTOMER_PAGE_SIZE = 50; const ENGAGEMENT_KEEP = 50; +/** مسیری که پشت‌سرهم 404 بدهد (پیاده نشده) تا پایان همان دور رد می‌شود */ +const SKIP_AFTER_NOT_FOUND = 3; + +type EndpointStats = Map; /** پروفایل فروشگاهی مشتری (MontifyLink.data) — نام‌ها طبق جدول نگاشت مستند */ type CustomerProfile = { @@ -221,6 +225,7 @@ export class MontifySyncService { onStarted?.(); } + const endpoints: EndpointStats = new Map(); let cursor = row.customersCursor; let fatal: string | null = null; try { @@ -234,10 +239,10 @@ export class MontifySyncService { do { const res = await client.listCustomers({ page, limit: CUSTOMER_PAGE_SIZE, updatedAfter }); lastPage = res.meta.last_page; - result.customersFetched += res.data.length; for (const customer of res.data) { + result.customersFetched += 1; try { - const outcome = await this.syncCustomerDeep(row, client, customer, mode, result); + const outcome = await this.syncCustomerDeep(row, client, customer, mode, result, endpoints); if (outcome === 'created') result.customersCreated += 1; else result.customersUpdated += 1; const updatedAt = parseDate(customer.updated_at); @@ -248,6 +253,7 @@ export class MontifySyncService { result.errors.push(`مشتری ${customer.id}: ${msg}`); this.logger.warn(`Montify ${row.id} customer ${customer.id}: ${msg}`); } + if (result.customersFetched % 5 === 0) await saveProgress(); } await saveProgress(); if (!res.data.length) break; @@ -261,8 +267,15 @@ export class MontifySyncService { this.running.delete(row.id); } + const missing = [...endpoints.entries()] + .filter(([, s]) => s.notFound > 0) + .map( + ([label, s]) => + `مسیر ${label}: ${s.notFound} بار 404 داد` + + (s.ok ? '' : ' (احتمالاً در این فروشگاه پیاده نشده)'), + ); result.finishedAt = new Date().toISOString(); - result.errors = result.errors.slice(0, 50); + result.errors = [...missing, ...result.errors].slice(0, 50); await this.prisma.montifyConnection.update({ where: { id: row.id }, data: { @@ -291,13 +304,23 @@ export class MontifySyncService { customer: MontifyCustomer, mode: 'incremental' | 'full', result: MontifySyncResultDto | null, + endpoints: EndpointStats = new Map(), ): Promise<'created' | 'updated'> { const externalId = String(customer.id); const step = async (label: string, fn: () => Promise): Promise => { + const stats = endpoints.get(label) ?? { ok: 0, notFound: 0 }; + endpoints.set(label, stats); + if (stats.ok === 0 && stats.notFound >= SKIP_AFTER_NOT_FOUND) return null; try { - return await fn(); + const value = await fn(); + stats.ok += 1; + return value; } catch (e) { if (e instanceof MontifyHttpError && e.status === 401) throw e; + if (e instanceof MontifyHttpError && e.status === 404) { + stats.notFound += 1; + return null; + } const msg = e instanceof Error ? e.message : String(e); result?.errors.push(`مشتری ${externalId} (${label}): ${msg}`); return null;