Montify sync: save progress every 5 customers; skip endpoints returning 404, summarize instead of per-customer errors

This commit is contained in:
2026-10-10 16:56:58 +03:30
parent 37157620e2
commit 0802c2f436
@@ -37,6 +37,10 @@ import {
const CUSTOMER_PAGE_SIZE = 50;
const ENGAGEMENT_KEEP = 50;
/** مسیری که پشت‌سرهم 404 بدهد (پیاده نشده) تا پایان همان دور رد می‌شود */
const SKIP_AFTER_NOT_FOUND = 3;
type EndpointStats = Map<string, { ok: number; notFound: number }>;
/** پروفایل فروشگاهی مشتری (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 <T>(label: string, fn: () => Promise<T>): Promise<T | null> => {
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;