feat: implement tax switch functionality with Nama adapter

- Add DTOs for tax switch operations including payloads and results.
- Create SalesInvoiceFiscalSwitchService to handle sending and retrieving tax data.
- Implement SalesInvoiceFiscalService for managing invoice tax submissions and results persistence.
- Develop NamaTaxSwitchAdapter for interfacing with the external tax service.
- Introduce NamaTaxRequestDto and related classes for structured tax requests.
This commit is contained in:
2026-04-30 16:27:46 +03:30
parent 58a7c359d8
commit a68a7f594d
38 changed files with 2678 additions and 287 deletions
@@ -1,14 +1,14 @@
import { Injectable } from '@nestjs/common'
import {
TaxSwitchBulkSendResultDto,
TaxSwitchBulkSendResultDto as FiscalSwitchBulkSendResultDto,
TaxSwitchSendItemResultDto as FiscalSwitchSendItemResultDto,
TaxSwitchSendPayloadDto as FiscalSwitchSendPayloadDto,
TaxSwitchGetResultDto,
TaxSwitchSendItemResultDto,
TaxSwitchSendPayloadDto,
} from './dto/tax-switch.dto'
import { NamaTaxSwitchAdapter } from './switch/nama-tax-switch.adapter'
import { NamaTaxSwitchAdapter } from './switch/nama/nama-fiscal-switch.adapter'
@Injectable()
export class SalesInvoiceTaxSwitchService {
export class SalesInvoiceFiscalSwitchService {
constructor(private readonly namaAdapter: NamaTaxSwitchAdapter) {}
private resolveSwitch(providerCode?: string | null) {
@@ -21,15 +21,17 @@ export class SalesInvoiceTaxSwitchService {
}
}
async send(payload: TaxSwitchSendPayloadDto): Promise<TaxSwitchSendItemResultDto[]> {
async send(
payload: FiscalSwitchSendPayloadDto,
): Promise<FiscalSwitchSendItemResultDto[]> {
const adapter = this.resolveSwitch(payload.provider_code)
return adapter.send(payload)
}
async sendBulk(
payloads: TaxSwitchSendPayloadDto[],
): Promise<TaxSwitchBulkSendResultDto[]> {
const groupedByProvider = new Map<string, TaxSwitchSendPayloadDto[]>()
payloads: FiscalSwitchSendPayloadDto[],
): Promise<FiscalSwitchBulkSendResultDto[]> {
const groupedByProvider = new Map<string, FiscalSwitchSendPayloadDto[]>()
for (const payload of payloads) {
const key = payload.provider_code?.trim().toUpperCase() || 'NAMA'
@@ -38,7 +40,7 @@ export class SalesInvoiceTaxSwitchService {
groupedByProvider.set(key, currentGroup)
}
const result: TaxSwitchBulkSendResultDto[] = []
const result: FiscalSwitchBulkSendResultDto[] = []
for (const [providerCode, groupedPayloads] of groupedByProvider.entries()) {
const adapter = this.resolveSwitch(providerCode)
const providerResult = await adapter.sendBulk(groupedPayloads)
@@ -7,7 +7,7 @@ import {
TaxSwitchSendItemResultDto,
TaxSwitchSendPayloadDto,
} from './dto/tax-switch.dto'
import { SalesInvoiceTaxSwitchService } from './sales-invoice-tax-switch.service'
import { SalesInvoiceFiscalSwitchService } from './sales-invoice-fiscal-switch.service'
type TaxRow = {
id: string
@@ -20,10 +20,10 @@ type ItemTaxProviderRow = {
}
@Injectable()
export class SalesInvoiceTaxService {
export class SalesInvoiceFiscalService {
constructor(
private readonly prisma: PrismaService,
private readonly taxSwitchService: SalesInvoiceTaxSwitchService,
private readonly taxSwitchService: SalesInvoiceFiscalSwitchService,
) {}
async send(consumer_id: string, invoice_id: string): Promise<void> {
@@ -173,7 +173,7 @@ export class SalesInvoiceTaxService {
})
if (!invoice) {
throw new NotFoundException('Sales invoice was not found.')
throw new NotFoundException('فاکتور مورد نظر یافت نشد.')
}
return {
@@ -6,7 +6,7 @@ import {
TaxSwitchGetResultDto,
TaxSwitchSendItemResultDto,
TaxSwitchSendPayloadDto,
} from '../dto/tax-switch.dto'
} from '../../dto/tax-switch.dto'
@Injectable()
export class NamaTaxSwitchAdapter implements ITaxSwitchAdapter {
@@ -0,0 +1,124 @@
import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import { IsArray, IsString, ValidateNested } from 'class-validator'
export class NamaTaxBodyItemDto {
@ApiProperty()
@IsString()
sstid: string
@ApiProperty()
@IsString()
vra: string
@ApiProperty()
@IsString()
sstt: string
@ApiProperty()
@IsString()
fee: string
@ApiProperty()
@IsString()
dis: string
@ApiProperty()
@IsString()
mu: string
@ApiProperty()
@IsString()
am: string
@ApiProperty()
@IsString()
consfee: string
@ApiProperty()
@IsString()
bros: string
@ApiProperty()
@IsString()
spro: string
}
export class NamaTaxHeaderDto {
@ApiProperty()
@IsString()
ins: string
@ApiProperty()
@IsString()
inp: string
@ApiProperty()
@IsString()
inty: string
@ApiProperty()
@IsString()
unique_tax_code: string
@ApiProperty()
@IsString()
inno: string
@ApiProperty()
@IsString()
tins: string
@ApiProperty()
@IsString()
indatim: string
@ApiProperty()
@IsString()
name: string
@ApiProperty()
@IsString()
tob: string
@ApiProperty()
@IsString()
address: string
@ApiProperty()
@IsString()
mobile: string
@ApiProperty()
@IsString()
bid: string
}
export class NamaTaxRequestDto {
@ApiProperty({ type: [NamaTaxBodyItemDto] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => NamaTaxBodyItemDto)
body: NamaTaxBodyItemDto[]
@ApiProperty({ type: [Object] })
@IsArray()
payment: unknown[]
@ApiProperty({ type: NamaTaxHeaderDto })
@ValidateNested()
@Type(() => NamaTaxHeaderDto)
header: NamaTaxHeaderDto
@ApiProperty()
@IsString()
uuid: string
@ApiProperty()
@IsString()
economic_code: string
@ApiProperty()
@IsString()
fiscal_id: string
}
@@ -1,18 +1,18 @@
import { PrismaModule } from '@/prisma/prisma.module'
import { Module } from '@nestjs/common'
import { SalesInvoiceFiscalSwitchService } from './fiscal/sales-invoice-fiscal-switch.service'
import { SalesInvoiceFiscalService } from './fiscal/sales-invoice-fiscal.service'
import { NamaTaxSwitchAdapter } from './fiscal/switch/nama/nama-fiscal-switch.adapter'
import { StatisticsController } from './saleInvoices.controller'
import { SaleInvoicesService } from './saleInvoices.service'
import { SalesInvoiceTaxSwitchService } from './tax/sales-invoice-tax-switch.service'
import { SalesInvoiceTaxService } from './tax/sales-invoice-tax.service'
import { NamaTaxSwitchAdapter } from './tax/switch/nama-tax-switch.adapter'
@Module({
imports: [PrismaModule],
controllers: [StatisticsController],
providers: [
SaleInvoicesService,
SalesInvoiceTaxService,
SalesInvoiceTaxSwitchService,
SalesInvoiceFiscalService,
SalesInvoiceFiscalSwitchService,
NamaTaxSwitchAdapter,
],
exports: [SaleInvoicesService],
@@ -6,13 +6,13 @@ import {
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
import { SalesInvoiceTaxService } from './tax/sales-invoice-tax.service'
import { SalesInvoiceFiscalService } from './fiscal/sales-invoice-fiscal.service'
@Injectable()
export class SaleInvoicesService {
constructor(
private readonly prisma: PrismaService,
private salesInvoiceTaxService: SalesInvoiceTaxService,
private salesInvoiceTaxService: SalesInvoiceFiscalService,
) {}
private readonly defaultSelect: SalesInvoiceSelect = {