feat(tahesab): add manual sync to refresh linked customer profiles

Expose sync/customers-now and a settings button so existing linked people get national id, address, city, and rasteh without waiting for the schedule.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-11 18:54:59 +03:30
co-authored by Cursor
parent d23dd20237
commit de77066cb0
4 changed files with 74 additions and 6 deletions
+6
View File
@@ -205,6 +205,12 @@ export class SyncGroupsPageDto {
pageSize?: number;
}
/** اجرای دستی همگام‌سازی همهٔ مشتریان (وارد + به‌روز کردن لینک‌شده‌ها) */
export class SyncAllCustomersDto {
@IsString()
connectionId!: string;
}
export class ExportPeopleToTahesabDto {
@IsString()
connectionId!: string;
@@ -969,6 +969,7 @@ export class TahesabPersonLinkService {
...(duplicate ? [`${duplicate} شماره تکراری`] : []),
`${failed} خطا`,
].join('، ');
const error = errors.length ? errors.join('؛ ') : null;
await this.prisma.accountingSyncJob.create({
data: {
organizationId,
@@ -978,6 +979,14 @@ export class TahesabPersonLinkService {
result: { imported, updated, duplicate, failed, pages: page - 1 } as Prisma.InputJsonValue,
},
});
await this.prisma.accountingConnection.update({
where: { id: connectionId },
data: {
customerSyncLastRunAt: new Date(),
customerSyncLastSummary: summary,
customerSyncLastError: error,
},
});
this.logger.log(`همگام‌سازی مشتریان ته‌حساب ${connectionId}: ${summary}`);
return {
imported,
@@ -985,7 +994,7 @@ export class TahesabPersonLinkService {
duplicate,
failed,
summary,
error: errors.length ? errors.join('؛ ') : null,
error,
};
}
@@ -37,6 +37,7 @@ import {
SearchTahesabDto,
SearchTahesabProductsDto,
SyncAsnadReportDto,
SyncAllCustomersDto,
SyncGroupsPageDto,
UpdateTahesabConnectionDto,
UpdateTahesabInvoiceSettingsDto,
@@ -237,6 +238,16 @@ export class TahesabController {
return this.links.syncGroupsPage(user.organizationId, dto);
}
/** همهٔ مشتریان ته‌حساب: تازه‌ها وارد، لینک‌شده‌ها (نام/موبایل/کدملی/آدرس/شهر/رسته/گروه) به‌روز */
@Post('sync/customers-now')
@Roles('owner', 'admin')
syncCustomersNow(
@CurrentUser() user: { organizationId: string },
@Body() dto: SyncAllCustomersDto,
) {
return this.links.syncAllCustomers(user.organizationId, dto.connectionId);
}
@Get('sync/unlinked-people')
@Roles('owner', 'admin')
unlinkedPeople(
+47 -5
View File
@@ -148,6 +148,41 @@ async function saveAutoSync() {
autoSyncSaving.value = false;
}
}
/** همهٔ مشتریان ته‌حساب را می‌خواند: تازه‌ها وارد، لینک‌شده‌ها پروفایل‌شان به‌روز می‌شود */
async function syncCustomersNow() {
if (!sync.connectionId) {
error.value = 'اتصال را انتخاب کنید.';
return;
}
syncBusy.value = true;
importOverlay.open = true;
importOverlay.progress = null;
importOverlay.message =
'در حال همگام‌سازی همهٔ مشتریان… نام، موبایل، کد ملی، آدرس، شهر، رسته و گروه به‌روز می‌شود.';
error.value = '';
ok.value = '';
try {
const { data } = await api.post<{
imported: number;
updated: number;
duplicate: number;
failed: number;
summary: string;
error: string | null;
}>('/integrations/tahesab/sync/customers-now', {
connectionId: sync.connectionId,
});
ok.value = data.summary;
if (data.error) error.value = data.error;
await load();
} catch (e) {
error.value = apiErr(e, 'همگام‌سازی مشتریان ناموفق بود');
} finally {
syncBusy.value = false;
importOverlay.open = false;
}
}
/** همان قاعدهٔ سرور: موبایل ایران 09… است، بقیهٔ شماره‌ها فقط رقم‌اند. */
function customerPhoneKey(value?: string | null): string {
const fa = '۰۱۲۳۴۵۶۷۸۹';
@@ -1008,10 +1043,9 @@ onMounted(load);
<div class="sync-sub-section">
<h4 class="sync-subhead">بروزرسانی خودکار</h4>
<p class="sync-sub-desc">
تعداد دفعات در روز و ساعت هر بار را برای همین اتصال مشخص کنید. سرور در آن ساعت‌ها، به وقت تهران،
مشتریان تازه را وارد می‌کند و نام مشتری‌های قبلی را هم با ته‌حساب تازه می‌کند. اگر شماره یکی باشد
همان مشتری می‌ماند و کد دیگر به‌عنوان مشتری تازه وارد نمی‌شود. شماره به شکل ۰۹۱۲ ذخیره می‌شود.
خاموش یعنی فقط ورود دستی.
«همگام‌سازی الان» همهٔ مشتریان ته‌حساب را می‌خواند: تازه‌ها را وارد می‌کند و برای مشتری‌های از قبل
لینک‌شده نام، موبایل، کد ملی، آدرس، شهر، رسته و گروه را از ته‌حساب تازه می‌کند. زمان‌بندی زیر
همان کار را در ساعت‌های انتخابی (به وقت تهران) تکرار می‌کند. خاموش یعنی فقط ورود دستی.
</p>
<UiField label="تعداد در روز">
<UiSelect
@@ -1044,8 +1078,16 @@ onMounted(load);
</div>
<div class="sync-buttons">
<UiButton
:loading="syncBusy"
:disabled="!sync.connectionId || syncBusy || autoSyncSaving"
@click="syncCustomersNow"
>
همگام‌سازی الان (وارد + به‌روز کردن قبلی‌ها)
</UiButton>
<UiButton
variant="secondary"
:loading="autoSyncSaving"
:disabled="!sync.connectionId"
:disabled="!sync.connectionId || syncBusy"
@click="saveAutoSync"
>
ذخیره زمان‌بندی