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>
449 lines
13 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|