Files
CRM_Montzar/apps/api/src/integrations/tahesab/tahesab.controller.ts
T
MahdiandCursor de77066cb0 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>
2026-11-11 18:54:59 +03:30

449 lines
13 KiB
TypeScript

import {
BadRequestException,
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from '../../auth/jwt-auth.guard';
import { CurrentUser } from '../../auth/current-user.decorator';
import { Roles } from '../../auth/roles.decorator';
import { RolesGuard } from '../../auth/roles.guard';
import { OrgModuleGuard } from '../../billing/org-module.guard';
import { RequireOrgModule } from '../../billing/org-module.decorator';
import { TahesabConnectionsService } from './connections.service';
import { TahesabPersonLinkService } from './person-link.service';
import { TahesabProductLinkService } from './product-link.service';
import { TahesabBalanceService } from './balance.service';
import { TahesabDocumentsService } from './documents.service';
import { TahesabAsnadReportService } from './asnad-report.service';
import { TahesabInvoiceSettingsService } from './invoice-settings.service';
import {
CreateInTahesabDto,
CreateTahesabConnectionDto,
ExportPeopleToTahesabDto,
ImportBulkFromTahesabDto,
ImportBulkProductsFromTahesabDto,
ImportFromTahesabDto,
ImportProductFromTahesabDto,
ManualLinkDto,
MatchPersonDto,
PushSanadDto,
SearchTahesabDto,
SearchTahesabProductsDto,
SyncAsnadReportDto,
SyncAllCustomersDto,
SyncGroupsPageDto,
UpdateTahesabConnectionDto,
UpdateTahesabInvoiceSettingsDto,
} from './dto';
@Controller('integrations/tahesab')
@UseGuards(JwtAuthGuard, RolesGuard, OrgModuleGuard)
@RequireOrgModule('tahesab')
export class TahesabController {
constructor(
private readonly connections: TahesabConnectionsService,
private readonly links: TahesabPersonLinkService,
private readonly products: TahesabProductLinkService,
private readonly balance: TahesabBalanceService,
private readonly documents: TahesabDocumentsService,
private readonly asnadReport: TahesabAsnadReportService,
private readonly invoiceSettings: TahesabInvoiceSettingsService,
) {}
// —— Connections ——
/** لیست اتصالات برای انتخاب در پروفایل (بدون توکن خام) */
@Get('connections/public')
listPublicConnections(@CurrentUser() user: { organizationId: string }) {
return this.connections.list(user.organizationId);
}
@Get('connections')
@Roles('owner', 'admin')
listConnections(@CurrentUser() user: { organizationId: string }) {
return this.connections.list(user.organizationId);
}
@Post('connections')
@Roles('owner', 'admin')
createConnection(
@CurrentUser() user: { organizationId: string },
@Body() dto: CreateTahesabConnectionDto,
) {
return this.connections.create(user.organizationId, dto);
}
@Patch('connections/:id')
@Roles('owner', 'admin')
updateConnection(
@CurrentUser() user: { organizationId: string },
@Param('id') id: string,
@Body() dto: UpdateTahesabConnectionDto,
) {
return this.connections.update(user.organizationId, id, dto);
}
@Delete('connections/:id')
@Roles('owner', 'admin')
removeConnection(
@CurrentUser() user: { organizationId: string },
@Param('id') id: string,
) {
return this.connections.remove(user.organizationId, id);
}
@Post('connections/:id/test')
@Roles('owner', 'admin')
testConnection(
@CurrentUser() user: { organizationId: string },
@Param('id') id: string,
) {
return this.connections.test(user.organizationId, id);
}
// —— Person links (all authenticated CRM users in org) ——
@Get('people/:personId/links')
listLinks(
@CurrentUser() user: { organizationId: string },
@Param('personId') personId: string,
) {
return this.links.listLinks(user.organizationId, personId);
}
@Post('people/:personId/links')
manualLink(
@CurrentUser() user: { organizationId: string },
@Param('personId') personId: string,
@Body() dto: ManualLinkDto,
) {
return this.links.manualLink(user.organizationId, personId, dto);
}
@Delete('people/:personId/links/:connectionId')
unlink(
@CurrentUser() user: { organizationId: string },
@Param('personId') personId: string,
@Param('connectionId') connectionId: string,
) {
return this.links.unlink(user.organizationId, personId, connectionId);
}
@Post('people/:personId/match')
match(
@CurrentUser() user: { organizationId: string },
@Param('personId') personId: string,
@Body() dto: MatchPersonDto,
) {
return this.links.match(user.organizationId, personId, dto);
}
@Post('people/:personId/create-in-tahesab')
createInTahesab(
@CurrentUser() user: { organizationId: string },
@Param('personId') personId: string,
@Body() dto: CreateInTahesabDto,
) {
return this.links.createInTahesab(user.organizationId, personId, dto);
}
@Get('people/:personId/assets')
getAssets(
@CurrentUser() user: { organizationId: string },
@Param('personId') personId: string,
@Query('connectionId') connectionId?: string,
@Query('refresh') refresh?: string,
) {
return this.balance.getCachedOrFetch(
user.organizationId,
personId,
connectionId,
refresh !== '0',
);
}
@Get('people/:personId/asnad')
listAsnad(
@CurrentUser() user: { organizationId: string },
@Param('personId') personId: string,
@Query('from') from: string,
@Query('to') to: string,
@Query('connectionId') connectionId?: string,
) {
return this.documents.listAsnad(
user.organizationId,
personId,
from,
to,
connectionId,
);
}
@Get('lookups')
lookups(
@CurrentUser() user: { organizationId: string },
@Query('connectionId') connectionId?: string,
) {
return this.documents.listLookups(user.organizationId, connectionId);
}
@Post('people/:personId/sanad')
@Roles('owner', 'admin', 'sales')
pushSanad(
@CurrentUser() user: { organizationId: string },
@Param('personId') personId: string,
@Body() dto: PushSanadDto,
) {
return this.documents.pushSanad(user.organizationId, personId, dto);
}
@Post('sync/search')
@Roles('owner', 'admin', 'sales')
search(
@CurrentUser() user: { organizationId: string },
@Body() dto: SearchTahesabDto,
) {
return this.links.search(user.organizationId, dto);
}
@Post('sync/import')
@Roles('owner', 'admin', 'sales')
importFromTahesab(
@CurrentUser() user: { organizationId: string },
@Body() dto: ImportFromTahesabDto,
) {
return this.links.importFromTahesab(user.organizationId, dto);
}
@Post('sync/import-bulk')
@Roles('owner', 'admin')
importBulk(
@CurrentUser() user: { organizationId: string },
@Body() dto: ImportBulkFromTahesabDto,
) {
return this.links.importBulk(user.organizationId, dto);
}
@Post('sync/groups')
@Roles('owner', 'admin')
syncGroups(
@CurrentUser() user: { organizationId: string },
@Body() dto: SyncGroupsPageDto,
) {
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(
@CurrentUser() user: { organizationId: string },
@Query('connectionId') connectionId: string,
) {
return this.links.unlinkedPeople(user.organizationId, connectionId);
}
@Post('sync/export-people')
@Roles('owner', 'admin')
exportPeople(
@CurrentUser() user: { organizationId: string },
@Body() dto: ExportPeopleToTahesabDto,
) {
return this.links.exportPeople(user.organizationId, dto);
}
@Post('sync/products/search')
@Roles('owner', 'admin', 'sales')
searchProducts(
@CurrentUser() user: { organizationId: string },
@Body() dto: SearchTahesabProductsDto,
) {
return this.products.search(user.organizationId, dto);
}
@Post('sync/products/import')
@Roles('owner', 'admin', 'sales')
importProduct(
@CurrentUser() user: { organizationId: string },
@Body() dto: ImportProductFromTahesabDto,
) {
return this.products.importOne(user.organizationId, dto);
}
@Post('sync/products/import-bulk')
@Roles('owner', 'admin')
importProductsBulk(
@CurrentUser() user: { organizationId: string },
@Body() dto: ImportBulkProductsFromTahesabDto,
) {
return this.products.importBulk(user.organizationId, dto);
}
// —— تنظیمات فاکتور PDF اسناد ته‌حساب ——
@Get('invoice-settings')
getInvoiceSettings(@CurrentUser() user: { organizationId: string }) {
return this.invoiceSettings.getOrCreate(user.organizationId);
}
@Patch('invoice-settings')
@Roles('owner', 'admin')
updateInvoiceSettings(
@CurrentUser() user: { organizationId: string },
@Body() dto: UpdateTahesabInvoiceSettingsDto,
) {
return this.invoiceSettings.update(user.organizationId, dto);
}
// —— گزارش فروش اسناد (اسنپ‌شات DoListAsnad) ——
@Post('reports/asnad/sync')
@Roles('owner', 'admin', 'sales')
syncAsnadReport(
@CurrentUser() user: { organizationId: string },
@Body() dto: SyncAsnadReportDto,
) {
return this.asnadReport.startSync(
user.organizationId,
dto.connectionId,
dto.from,
dto.to,
{ personId: dto.personId, groupIds: dto.groupIds },
);
}
@Get('reports/asnad/customers')
searchAsnadCustomers(
@CurrentUser() user: { organizationId: string },
@Query('connectionId') connectionId?: string,
@Query('q') q?: string,
) {
return this.asnadReport.searchCustomers(
user.organizationId,
connectionId,
q ?? '',
);
}
@Get('reports/asnad/sync/:jobId')
syncAsnadReportStatus(
@CurrentUser() user: { organizationId: string },
@Param('jobId') jobId: string,
) {
return this.asnadReport.getSyncJob(user.organizationId, jobId);
}
@Get('reports/asnad/overview')
asnadReportOverview(
@CurrentUser() user: { organizationId: string },
@Query('connectionId') connectionId?: string,
@Query('from') from?: string,
@Query('to') to?: string,
@Query('groupIds') groupIds?: string | string[],
@Query('personId') personId?: string,
@Query('jensFelez') jensFelez?: string,
) {
if (!from || !to) {
throw new BadRequestException('پارامتر from و to الزامی است.');
}
return this.asnadReport.overview(user.organizationId, {
connectionId,
from,
to,
...this.parseAsnadFilters(groupIds, personId, undefined, jensFelez),
});
}
@Get('reports/asnad/summary')
asnadReportSummary(
@CurrentUser() user: { organizationId: string },
@Query('connectionId') connectionId?: string,
@Query('from') from?: string,
@Query('to') to?: string,
@Query('groupIds') groupIds?: string | string[],
@Query('personId') personId?: string,
@Query('kinds') kinds?: string | string[],
@Query('jensFelez') jensFelez?: string,
) {
if (!from || !to) {
throw new BadRequestException('پارامتر from و to الزامی است.');
}
return this.asnadReport.summary(user.organizationId, {
connectionId,
from,
to,
...this.parseAsnadFilters(groupIds, personId, kinds, jensFelez),
});
}
@Get('reports/asnad')
listAsnadReport(
@CurrentUser() user: { organizationId: string },
@Query('connectionId') connectionId?: string,
@Query('from') from?: string,
@Query('to') to?: string,
@Query('groupIds') groupIds?: string | string[],
@Query('personId') personId?: string,
@Query('kinds') kinds?: string | string[],
@Query('jensFelez') jensFelez?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
if (!from || !to) {
throw new BadRequestException('پارامتر from و to الزامی است.');
}
return this.asnadReport.query(user.organizationId, {
connectionId,
from,
to,
...this.parseAsnadFilters(groupIds, personId, kinds, jensFelez),
page: page ? Number(page) : undefined,
pageSize: pageSize ? Number(pageSize) : undefined,
});
}
private parseAsnadFilters(
groupIds?: string | string[],
personId?: string,
kinds?: string | string[],
jensFelez?: string,
) {
const groups = Array.isArray(groupIds)
? groupIds
: groupIds
? groupIds
.split(',')
.map((s) => s.trim())
.filter(Boolean)
: undefined;
const kindList = Array.isArray(kinds)
? kinds
: kinds
? kinds
.split(',')
.map((s) => s.trim())
.filter(Boolean)
: undefined;
const jf =
jensFelez != null && jensFelez !== '' ? Number(jensFelez) : undefined;
return {
groupIds: groups,
personId,
kinds: kindList,
jensFelez: jf != null && Number.isFinite(jf) ? jf : undefined,
};
}
}