feat(tahesab): PDF invoice export for asnad cards

Add per-document PDF print from account details, plus org invoice settings for logo and texts separate from CRM invoices.
This commit is contained in:
2026-10-24 12:34:02 +03:30
parent 7d9fc8447f
commit f50246dc46
11 changed files with 871 additions and 11 deletions
+36
View File
@@ -449,6 +449,42 @@ export class SyncAsnadReportDto {
to!: string;
}
export class UpdateTahesabInvoiceSettingsDto {
/** null = حذف لوگو */
@IsOptional()
logoDataUrl?: string | null;
@IsOptional()
@IsString()
@MaxLength(200)
companyName?: string;
@IsOptional()
@IsString()
@MaxLength(200)
companySubtitle?: string;
@IsOptional()
@IsString()
@MaxLength(500)
address?: string;
@IsOptional()
@IsString()
@MaxLength(80)
phone?: string;
@IsOptional()
@IsString()
@MaxLength(1000)
headerText?: string;
@IsOptional()
@IsString()
@MaxLength(1000)
footerText?: string;
}
// silence unused import warnings for ValidateNested/Type if unused
void ValidateNested;
void Type;
@@ -0,0 +1,111 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import type {
TahesabInvoiceSettingsDto,
UpdateTahesabInvoiceSettingsRequest,
} from '@mento/shared';
import { PrismaService } from '../../prisma/prisma.service';
const MAX_LOGO_CHARS = 700_000; // ~500KB data URL
function toDto(row: {
id: string;
organizationId: string;
logoDataUrl: string | null;
companyName: string;
companySubtitle: string;
address: string;
phone: string;
headerText: string;
footerText: string;
updatedAt: Date;
}): TahesabInvoiceSettingsDto {
return {
id: row.id,
organizationId: row.organizationId,
logoDataUrl: row.logoDataUrl,
companyName: row.companyName,
companySubtitle: row.companySubtitle,
address: row.address,
phone: row.phone,
headerText: row.headerText,
footerText: row.footerText,
updatedAt: row.updatedAt.toISOString(),
};
}
@Injectable()
export class TahesabInvoiceSettingsService {
constructor(private readonly prisma: PrismaService) {}
async getOrCreate(organizationId: string): Promise<TahesabInvoiceSettingsDto> {
const existing = await this.prisma.tahesabInvoiceSettings.findUnique({
where: { organizationId },
});
if (existing) return toDto(existing);
const org = await this.prisma.organization.findUnique({
where: { id: organizationId },
select: { name: true },
});
const created = await this.prisma.tahesabInvoiceSettings.create({
data: {
organizationId,
companyName: org?.name ?? '',
},
});
return toDto(created);
}
async update(
organizationId: string,
dto: UpdateTahesabInvoiceSettingsRequest,
): Promise<TahesabInvoiceSettingsDto> {
if (dto.logoDataUrl != null && dto.logoDataUrl.length > MAX_LOGO_CHARS) {
throw new BadRequestException(
'حجم لوگو زیاد است؛ تصویر کوچک‌تری انتخاب کنید.',
);
}
if (
dto.logoDataUrl != null &&
dto.logoDataUrl !== '' &&
!/^data:image\/(png|jpe?g|webp|gif);base64,/i.test(dto.logoDataUrl)
) {
throw new BadRequestException('لوگو باید تصویر با فرمت data URL باشد.');
}
const data = {
...(dto.logoDataUrl !== undefined
? { logoDataUrl: dto.logoDataUrl?.trim() || null }
: {}),
...(dto.companyName !== undefined
? { companyName: dto.companyName.trim().slice(0, 200) }
: {}),
...(dto.companySubtitle !== undefined
? { companySubtitle: dto.companySubtitle.trim().slice(0, 200) }
: {}),
...(dto.address !== undefined
? { address: dto.address.trim().slice(0, 500) }
: {}),
...(dto.phone !== undefined
? { phone: dto.phone.trim().slice(0, 80) }
: {}),
...(dto.headerText !== undefined
? { headerText: dto.headerText.trim().slice(0, 1000) }
: {}),
...(dto.footerText !== undefined
? { footerText: dto.footerText.trim().slice(0, 1000) }
: {}),
};
const row = await this.prisma.tahesabInvoiceSettings.upsert({
where: { organizationId },
create: {
organizationId,
companyName: '',
...data,
},
update: data,
});
return toDto(row);
}
}
@@ -22,6 +22,7 @@ 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,
@@ -38,6 +39,7 @@ import {
SyncAsnadReportDto,
SyncGroupsPageDto,
UpdateTahesabConnectionDto,
UpdateTahesabInvoiceSettingsDto,
} from './dto';
@Controller('integrations/tahesab')
@@ -51,6 +53,7 @@ export class TahesabController {
private readonly balance: TahesabBalanceService,
private readonly documents: TahesabDocumentsService,
private readonly asnadReport: TahesabAsnadReportService,
private readonly invoiceSettings: TahesabInvoiceSettingsService,
) {}
// —— Connections ——
@@ -279,6 +282,21 @@ export class TahesabController {
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')
@@ -11,6 +11,7 @@ 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 { TahesabController } from './tahesab.controller';
import { CustomerGroupsModule } from '../../customer-groups/customer-groups.module';
@@ -27,6 +28,7 @@ import { CustomerGroupsModule } from '../../customer-groups/customer-groups.modu
TahesabBalanceService,
TahesabDocumentsService,
TahesabAsnadReportService,
TahesabInvoiceSettingsService,
],
exports: [
TahesabCryptoService,
@@ -36,6 +38,7 @@ import { CustomerGroupsModule } from '../../customer-groups/customer-groups.modu
TahesabBalanceService,
TahesabDocumentsService,
TahesabAsnadReportService,
TahesabInvoiceSettingsService,
],
})
export class TahesabModule {}