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:
@@ -1,3 +1,4 @@
|
||||
import { translateEnumValue } from '@/common/utils'
|
||||
import { PasswordUtil } from '@/common/utils/password.util'
|
||||
import { AccountStatus, AccountType } from '@/generated/prisma/enums'
|
||||
import { PrismaService } from '@/prisma/prisma.service'
|
||||
@@ -16,6 +17,22 @@ export class AccountsService {
|
||||
id: true,
|
||||
role: true,
|
||||
created_at: true,
|
||||
pos: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
complex: {
|
||||
select: {
|
||||
name: true,
|
||||
business_activity: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
account: {
|
||||
select: {
|
||||
username: true,
|
||||
@@ -24,7 +41,15 @@ export class AccountsService {
|
||||
},
|
||||
},
|
||||
})
|
||||
return ResponseMapper.list(accounts)
|
||||
|
||||
const mappedAccounts = accounts.map(account => {
|
||||
return {
|
||||
...account,
|
||||
role: translateEnumValue('ConsumerRole', account.role),
|
||||
status: translateEnumValue('AccountStatus', account.account.status),
|
||||
}
|
||||
})
|
||||
return ResponseMapper.list(mappedAccounts)
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
|
||||
@@ -17,6 +17,9 @@ export class BusinessActivitiesService {
|
||||
},
|
||||
id: true,
|
||||
name: true,
|
||||
economic_code: true,
|
||||
fiscal_id: true,
|
||||
partner_token: true,
|
||||
created_at: true,
|
||||
} as BusinessActivitySelect
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PartnerStatus } from '@/generated/prisma/enums'
|
||||
import { PartnerFiscalSwitchType, PartnerStatus } from '@/generated/prisma/enums'
|
||||
import { ApiProperty, PartialType } from '@nestjs/swagger'
|
||||
import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
@@ -11,10 +11,14 @@ export class CreatePartnerDto {
|
||||
@ApiProperty({ required: true })
|
||||
code: string
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@IsEnum(PartnerFiscalSwitchType)
|
||||
@ApiProperty({ required: true })
|
||||
license_quota?: number
|
||||
fiscal_switch_type: PartnerFiscalSwitchType
|
||||
|
||||
// @IsOptional()
|
||||
// @IsNumber()
|
||||
// @ApiProperty({ required: true })
|
||||
// license_quota?: number
|
||||
|
||||
@IsString()
|
||||
@ApiProperty({ required: true })
|
||||
|
||||
@@ -27,6 +27,7 @@ export class PartnersService {
|
||||
code: true,
|
||||
status: true,
|
||||
logo_url: true,
|
||||
fiscal_switch_type: true,
|
||||
created_at: true,
|
||||
license_charge_transactions: {
|
||||
select: {
|
||||
|
||||
+12
-10
@@ -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)
|
||||
+4
-4
@@ -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 {
|
||||
+1
-1
@@ -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 = {
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
import translates from '@/common/constants/translates/translates'
|
||||
import { translateEnumValue } from '@/common/utils/enum-translator.util'
|
||||
import {
|
||||
AccountRole,
|
||||
AccountStatus,
|
||||
ApplicationPlatform,
|
||||
ApplicationPublisher,
|
||||
ApplicationReleaseType,
|
||||
BusinessRole,
|
||||
ConsumerRole,
|
||||
ConsumerStatus,
|
||||
ConsumerType,
|
||||
CustomerType,
|
||||
FiscalResponseStatus,
|
||||
GoodPricingModel,
|
||||
LicenseStatus,
|
||||
LicenseType,
|
||||
PartnerFiscalSwitchType,
|
||||
PartnerRole,
|
||||
PartnerStatus,
|
||||
PaymentMethodType,
|
||||
POSRole,
|
||||
POSStatus,
|
||||
POSType,
|
||||
ProviderRole,
|
||||
ProviderStatus,
|
||||
UnitType,
|
||||
UserStatus,
|
||||
UserType,
|
||||
@@ -22,33 +34,62 @@ import { ResponseMapper } from 'common/response/response-mapper'
|
||||
|
||||
@Injectable()
|
||||
export class EnumsService {
|
||||
private prepareData(items: Record<string, string>) {
|
||||
return Object.values(items).map(item => ({
|
||||
name: translates.enums[item] || item,
|
||||
value: item,
|
||||
}))
|
||||
private prepareData(
|
||||
items: Record<string, string>,
|
||||
enumName: keyof typeof translates.enums,
|
||||
) {
|
||||
return Object.values(items).map(item => {
|
||||
const translated = translateEnumValue(enumName, item)
|
||||
return {
|
||||
name: translated.translate || item,
|
||||
value: translated.value,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getAllEnums() {
|
||||
return {
|
||||
GoldKarat: this.prepareData(GoldKarat),
|
||||
UserStatus: this.prepareData(UserStatus),
|
||||
AccountRole: this.prepareData(AccountRole),
|
||||
AccountStatus: this.prepareData(AccountStatus),
|
||||
POSStatus: this.prepareData(POSStatus),
|
||||
POSRole: this.prepareData(POSRole),
|
||||
LicenseType: this.prepareData(LicenseType),
|
||||
LicenseStatus: this.prepareData(LicenseStatus),
|
||||
POSType: this.prepareData(POSType),
|
||||
UserType: this.prepareData(UserType),
|
||||
AccountType: this.prepareData(AccountType),
|
||||
PartnerRole: this.prepareData(PartnerRole),
|
||||
BusinessRole: this.prepareData(BusinessRole),
|
||||
ProviderRole: this.prepareData(ProviderRole),
|
||||
TokenType: this.prepareData(TokenType),
|
||||
UnitType: this.prepareData(UnitType),
|
||||
GoodPricingModel: this.prepareData(GoodPricingModel),
|
||||
ConsumerRole: this.prepareData(ConsumerRole),
|
||||
PaymentMethodType: this.prepareData(PaymentMethodType, 'PaymentMethodType'),
|
||||
GoldKarat: this.prepareData(GoldKarat, 'GoldKarat'),
|
||||
UserStatus: this.prepareData(UserStatus, 'UserStatus'),
|
||||
AccountRole: this.prepareData(AccountRole, 'AccountRole'),
|
||||
AccountStatus: this.prepareData(AccountStatus, 'AccountStatus'),
|
||||
POSStatus: this.prepareData(POSStatus, 'POSStatus'),
|
||||
POSRole: this.prepareData(POSRole, 'POSRole'),
|
||||
LicenseType: this.prepareData(LicenseType, 'LicenseType'),
|
||||
LicenseStatus: this.prepareData(LicenseStatus, 'LicenseStatus'),
|
||||
POSType: this.prepareData(POSType, 'POSType'),
|
||||
UserType: this.prepareData(UserType, 'UserType'),
|
||||
AccountType: this.prepareData(AccountType, 'AccountType'),
|
||||
PartnerRole: this.prepareData(PartnerRole, 'PartnerRole'),
|
||||
BusinessRole: this.prepareData(BusinessRole, 'BusinessRole'),
|
||||
ProviderRole: this.prepareData(ProviderRole, 'ProviderRole'),
|
||||
ProviderStatus: this.prepareData(ProviderStatus, 'ProviderStatus'),
|
||||
TokenType: this.prepareData(TokenType, 'TokenType'),
|
||||
UnitType: this.prepareData(UnitType, 'UnitType'),
|
||||
GoodPricingModel: this.prepareData(GoodPricingModel, 'GoodPricingModel'),
|
||||
ConsumerRole: this.prepareData(ConsumerRole, 'ConsumerRole'),
|
||||
ConsumerStatus: this.prepareData(ConsumerStatus, 'ConsumerStatus'),
|
||||
ConsumerType: this.prepareData(ConsumerType, 'ConsumerType'),
|
||||
PartnerStatus: this.prepareData(PartnerStatus, 'PartnerStatus'),
|
||||
PartnerFiscalSwitchType: this.prepareData(
|
||||
PartnerFiscalSwitchType,
|
||||
'PartnerFiscalSwitchType',
|
||||
),
|
||||
ApplicationPlatform: this.prepareData(ApplicationPlatform, 'ApplicationPlatform'),
|
||||
ApplicationReleaseType: this.prepareData(
|
||||
ApplicationReleaseType,
|
||||
'ApplicationReleaseType',
|
||||
),
|
||||
ApplicationPublisher: this.prepareData(
|
||||
ApplicationPublisher,
|
||||
'ApplicationPublisher',
|
||||
),
|
||||
CustomerType: this.prepareData(CustomerType, 'CustomerType'),
|
||||
FiscalResponseStatus: this.prepareData(
|
||||
FiscalResponseStatus,
|
||||
'FiscalResponseStatus',
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { checkAndDecodeJwtToken } from '@/common/utils/jwt-user.util'
|
||||
import { POSStatus } from '@/generated/prisma/enums'
|
||||
import { PosSelect } from '@/generated/prisma/models'
|
||||
import { ConsumerRole, POSStatus } from '@/generated/prisma/enums'
|
||||
import { PosSelect, PosWhereInput } from '@/generated/prisma/models'
|
||||
import { PrismaService } from '@/prisma/prisma.service'
|
||||
import {
|
||||
ForbiddenException,
|
||||
@@ -55,20 +55,26 @@ export class PosMiddleware implements NestMiddleware {
|
||||
let pos!: any
|
||||
|
||||
if (!posId || typeof posId !== 'string') {
|
||||
const poses = await tx.pos.findMany({
|
||||
where: {
|
||||
complex: {
|
||||
business_activity: {
|
||||
consumer: {
|
||||
accounts: {
|
||||
some: {
|
||||
id: account_id,
|
||||
},
|
||||
const defaultWhere: PosWhereInput = {
|
||||
complex: {
|
||||
business_activity: {
|
||||
consumer: {
|
||||
accounts: {
|
||||
some: {
|
||||
id: account_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
account_id,
|
||||
},
|
||||
}
|
||||
|
||||
if (role === ConsumerRole.OPERATOR) {
|
||||
defaultWhere.account_id = account_id
|
||||
}
|
||||
const poses = await tx.pos.findMany({
|
||||
where: {
|
||||
...defaultWhere,
|
||||
},
|
||||
select: posSelect,
|
||||
})
|
||||
@@ -137,7 +143,7 @@ export class PosMiddleware implements NestMiddleware {
|
||||
req.posData = {
|
||||
pos_id: posId,
|
||||
complex_id: pos.complex.id,
|
||||
business_id: pos.complex.id,
|
||||
business_id: pos.complex.business_activity.id,
|
||||
guild_id: pos.complex.business_activity.guild_id,
|
||||
consumer_account_id: account_id,
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export class PosService {
|
||||
id: true,
|
||||
name: true,
|
||||
code: true,
|
||||
logo_url: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,18 +1,110 @@
|
||||
import { ApiProperty, PartialType } from '@nestjs/swagger'
|
||||
import { Type } from 'class-transformer'
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Min,
|
||||
ValidateNested,
|
||||
} from 'class-validator'
|
||||
import type { CustomerIndividual, CustomerLegal } from 'generated/prisma/client'
|
||||
import { CustomerType } from 'generated/prisma/enums'
|
||||
import { CreateSalesInvoiceItemDto } from '../sales-invoice-items/dto/create-sales-invoice-item.dto'
|
||||
|
||||
export class CreateTerminalPayment {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@ApiProperty({ required: false, default: 0 })
|
||||
amount?: number
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ required: true })
|
||||
terminalId: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ required: true })
|
||||
stan: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ required: true })
|
||||
rrn: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({ required: true })
|
||||
response_code?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({ required: true })
|
||||
customer_card_no?: string
|
||||
|
||||
@Type(() => Date)
|
||||
@IsDate()
|
||||
@ApiProperty({ required: true })
|
||||
transaction_date_time: Date
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiProperty({ required: false })
|
||||
description?: string
|
||||
}
|
||||
|
||||
export class CreateSalesInvoicePaymentsDto {
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => CreateTerminalPayment)
|
||||
@ApiProperty({ required: false, type: () => CreateTerminalPayment })
|
||||
terminals?: CreateTerminalPayment
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@ApiProperty({ required: false, default: 0 })
|
||||
cash?: number
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@ApiProperty({ required: false, default: 0 })
|
||||
set_off?: number
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@ApiProperty({ required: false, default: 0 })
|
||||
card?: number
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@ApiProperty({ required: false, default: 0 })
|
||||
bank?: number
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@ApiProperty({ required: false, default: 0 })
|
||||
check?: number
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@ApiProperty({ required: false, default: 0 })
|
||||
other?: number
|
||||
}
|
||||
|
||||
export class CreateSalesInvoiceDto {
|
||||
// @TODO: totalAmount must calculated instead of get from api
|
||||
@IsNumber()
|
||||
@@ -28,11 +120,9 @@ export class CreateSalesInvoiceDto {
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
@IsObject()
|
||||
payments: {
|
||||
terminal: number
|
||||
cash: number
|
||||
set_off: number
|
||||
}
|
||||
@ValidateNested()
|
||||
@Type(() => CreateSalesInvoicePaymentsDto)
|
||||
payments: CreateSalesInvoicePaymentsDto
|
||||
|
||||
@ApiProperty()
|
||||
@ArrayMinSize(1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { SalesInvoiceTaxSwitchService } from '../../consumer/saleInvoices/tax/sales-invoice-tax-switch.service'
|
||||
import { SalesInvoiceTaxService } from '../../consumer/saleInvoices/tax/sales-invoice-tax.service'
|
||||
import { NamaTaxSwitchAdapter } from '../../consumer/saleInvoices/tax/switch/nama-tax-switch.adapter'
|
||||
import { SalesInvoiceFiscalSwitchService } from '../../consumer/saleInvoices/fiscal/sales-invoice-fiscal-switch.service'
|
||||
import { SalesInvoiceFiscalService } from '../../consumer/saleInvoices/fiscal/sales-invoice-fiscal.service'
|
||||
import { NamaTaxSwitchAdapter } from '../../consumer/saleInvoices/fiscal/switch/nama/nama-fiscal-switch.adapter'
|
||||
import { SalesInvoicesController } from './sales-invoices.controller'
|
||||
import { SalesInvoicesService } from './sales-invoices.service'
|
||||
|
||||
@@ -9,8 +9,8 @@ import { SalesInvoicesService } from './sales-invoices.service'
|
||||
controllers: [SalesInvoicesController],
|
||||
providers: [
|
||||
SalesInvoicesService,
|
||||
SalesInvoiceTaxService,
|
||||
SalesInvoiceTaxSwitchService,
|
||||
SalesInvoiceFiscalService,
|
||||
SalesInvoiceFiscalSwitchService,
|
||||
NamaTaxSwitchAdapter,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -3,8 +3,9 @@ import { PrismaService } from '@/prisma/prisma.service'
|
||||
import { BadRequestException, Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from 'common/response/response-mapper'
|
||||
import type { CustomerIndividual, CustomerLegal } from 'generated/prisma/client'
|
||||
import { Prisma } from 'generated/prisma/client'
|
||||
import { CustomerType, PaymentMethodType } from 'generated/prisma/enums'
|
||||
import { SalesInvoiceTaxService } from '../../consumer/saleInvoices/tax/sales-invoice-tax.service'
|
||||
import { SalesInvoiceFiscalService } from '../../consumer/saleInvoices/fiscal/sales-invoice-fiscal.service'
|
||||
import { CreateSalesInvoiceDto } from './dto/create-sales-invoice.dto'
|
||||
|
||||
// Define type guard for CustomerIndividual
|
||||
@@ -25,11 +26,28 @@ function isCustomerLegal(customer: any): customer is CustomerLegal {
|
||||
)
|
||||
}
|
||||
|
||||
interface TerminalPaymentInfo {
|
||||
terminalId: string
|
||||
stan: string
|
||||
rrn: string
|
||||
transactionDateTime: string | Date
|
||||
customerCardNO?: string
|
||||
description?: string
|
||||
amount?: number
|
||||
}
|
||||
|
||||
interface NormalizedPayment {
|
||||
method: PaymentMethodType
|
||||
amount: number
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SalesInvoicesService {
|
||||
private readonly createInvoiceRetries = 3
|
||||
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private salesInvoiceTaxService: SalesInvoiceTaxService,
|
||||
private salesInvoiceTaxService: SalesInvoiceFiscalService,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
@@ -65,202 +83,404 @@ export class SalesInvoicesService {
|
||||
// }
|
||||
|
||||
async create(data: CreateSalesInvoiceDto, posInfo: IPosPayload) {
|
||||
data.invoice_date = new Date(data.invoice_date).toISOString() as any
|
||||
|
||||
const { business_id, pos_id, consumer_account_id } = posInfo
|
||||
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
|
||||
const { payments, terminalInfo } = this.buildPaymentsData(
|
||||
data.payments,
|
||||
data.total_amount,
|
||||
)
|
||||
|
||||
const salesInvoice = await this.prisma.$transaction(async $tx => {
|
||||
const payments = Object.entries(data.payments)
|
||||
.filter(([_, value]) => value > 0 && PaymentMethodType[_.toUpperCase()])
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
amount: value,
|
||||
payment_method: key.toLocaleUpperCase() as PaymentMethodType,
|
||||
paid_at: data.invoice_date,
|
||||
}
|
||||
})
|
||||
|
||||
const totalPayments = payments.reduce((sum, payment) => sum + payment.amount, 0)
|
||||
|
||||
if (totalPayments !== data.total_amount) {
|
||||
throw new Error('مبلغ پرداختی باید برابر با مبلغ کل فاکتور باشد.')
|
||||
}
|
||||
|
||||
const { customer_id, customer_type, customer, ...invoiceData } = data
|
||||
let newCustomerId: string | null = customer_id || null
|
||||
if (customer_id) {
|
||||
} else if (
|
||||
customer_type === CustomerType.INDIVIDUAL &&
|
||||
customer?.customer_individual
|
||||
) {
|
||||
const customer_individual = customer.customer_individual
|
||||
const customerIndividual = await $tx.customerIndividual.upsert({
|
||||
where: {
|
||||
business_activity_id_national_id: {
|
||||
business_activity_id: business_id,
|
||||
national_id: customer_individual.national_id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
...customer_individual,
|
||||
customer: {
|
||||
create: {
|
||||
type: CustomerType.INDIVIDUAL,
|
||||
},
|
||||
},
|
||||
business_activity: {
|
||||
connect: {
|
||||
id: business_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
select: {
|
||||
customer_id: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!customerIndividual) {
|
||||
throw new BadRequestException('متاسفانه مشکلی در اطلاعات مشتری وجود دارد.')
|
||||
}
|
||||
newCustomerId = customerIndividual.customer_id
|
||||
} else if (data.customer_type === CustomerType.LEGAL && customer?.customer_legal) {
|
||||
const customer_legal = customer.customer_legal
|
||||
|
||||
const customerLegal = await $tx.customerLegal.upsert({
|
||||
where: {
|
||||
business_activity_id_economic_code: {
|
||||
business_activity_id: business_id,
|
||||
economic_code: customer_legal.economic_code,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
...customer_legal,
|
||||
customer: {
|
||||
create: {
|
||||
type: CustomerType.LEGAL,
|
||||
},
|
||||
},
|
||||
business_activity: {
|
||||
connect: {
|
||||
id: business_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
select: {
|
||||
customer_id: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!customerLegal) {
|
||||
throw new BadRequestException('متاسفانه مشکلی در اطلاعات مشتری وجود دارد.')
|
||||
}
|
||||
newCustomerId = customerLegal.customer_id
|
||||
} else if (data.customer_type === CustomerType.UNKNOWN) {
|
||||
}
|
||||
|
||||
const itemGoodIds = data.items
|
||||
.map(item => item.good_id)
|
||||
.filter((goodId): goodId is string => Boolean(goodId))
|
||||
|
||||
const goods = itemGoodIds.length
|
||||
? await $tx.good.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: itemGoodIds,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sku: true,
|
||||
local_sku: true,
|
||||
barcode: true,
|
||||
pricing_model: true,
|
||||
unit_type: true,
|
||||
base_sale_price: true,
|
||||
image_url: true,
|
||||
category: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
for (let attempt = 1; attempt <= this.createInvoiceRetries; attempt++) {
|
||||
try {
|
||||
const salesInvoice = await this.prisma.$transaction(async $tx => {
|
||||
const invoiceNumber = await this.getNextInvoiceNumber($tx, business_id)
|
||||
const newCustomerId = await this.resolveCustomerId($tx, data, business_id)
|
||||
const goodsById = await this.getGoodsById($tx, data)
|
||||
const salesInvoiceData = this.buildSalesInvoiceData({
|
||||
data,
|
||||
normalizedInvoiceDate,
|
||||
invoiceNumber,
|
||||
consumer_account_id,
|
||||
pos_id,
|
||||
goodsById,
|
||||
customerId: newCustomerId,
|
||||
})
|
||||
: []
|
||||
|
||||
const goodsById = new Map(goods.map(good => [good.id, good]))
|
||||
const salesInvoice = await $tx.salesInvoice.create({
|
||||
data: salesInvoiceData,
|
||||
})
|
||||
|
||||
const salesInvoiceData: any = {
|
||||
...invoiceData,
|
||||
total_amount: data.total_amount,
|
||||
code: 'INV-' + Date.now(),
|
||||
await this.createPayments(
|
||||
$tx,
|
||||
salesInvoice.id,
|
||||
payments,
|
||||
terminalInfo,
|
||||
normalizedInvoiceDate,
|
||||
)
|
||||
|
||||
items: {
|
||||
createMany: {
|
||||
data: data.items.map(item => ({
|
||||
good_id: item.good_id,
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price,
|
||||
total_amount: item.total_amount,
|
||||
payload: item.payload
|
||||
? JSON.parse(JSON.stringify(item.payload))
|
||||
: undefined,
|
||||
good_snapshot: item.good_id
|
||||
? JSON.parse(
|
||||
JSON.stringify({
|
||||
good: goodsById.get(item.good_id) || null,
|
||||
}),
|
||||
)
|
||||
: undefined,
|
||||
unit_type: item.unit_type,
|
||||
// pricing_model: item.pricingModel,
|
||||
})),
|
||||
},
|
||||
},
|
||||
unknown_customer: {},
|
||||
payments: {
|
||||
createMany: {
|
||||
data: payments,
|
||||
},
|
||||
},
|
||||
// customer: {
|
||||
// connect: {
|
||||
// id: newCustomerId || undefined,
|
||||
// },
|
||||
// },
|
||||
consumer_account: {
|
||||
connect: {
|
||||
id: consumer_account_id,
|
||||
},
|
||||
},
|
||||
pos: {
|
||||
connect: {
|
||||
id: pos_id,
|
||||
},
|
||||
},
|
||||
}
|
||||
return salesInvoice
|
||||
})
|
||||
|
||||
if (newCustomerId) {
|
||||
salesInvoiceData.customer = {
|
||||
connect: {
|
||||
id: newCustomerId || undefined,
|
||||
},
|
||||
if (data.send_to_tax) {
|
||||
await this.salesInvoiceTaxService.send(salesInvoice.id, pos_id)
|
||||
}
|
||||
|
||||
return ResponseMapper.create(salesInvoice)
|
||||
} catch (error) {
|
||||
if (
|
||||
this.isRetryableInvoiceConflict(error) &&
|
||||
attempt < this.createInvoiceRetries
|
||||
) {
|
||||
continue
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
const salesInvoice = await $tx.salesInvoice.create({
|
||||
data: salesInvoiceData,
|
||||
})
|
||||
|
||||
return salesInvoice
|
||||
})
|
||||
if (data.send_to_tax) {
|
||||
await this.salesInvoiceTaxService.send(salesInvoice.id, pos_id)
|
||||
}
|
||||
|
||||
return ResponseMapper.create(salesInvoice)
|
||||
throw new BadRequestException('ایجاد فاکتور با خطا مواجه شد.')
|
||||
}
|
||||
|
||||
private isRetryableInvoiceConflict(error: unknown) {
|
||||
if (!(error instanceof Prisma.PrismaClientKnownRequestError)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (error.code !== 'P2002') {
|
||||
return false
|
||||
}
|
||||
|
||||
const target = (error.meta?.target as string[]) || []
|
||||
return (
|
||||
target.includes('invoice_number') ||
|
||||
target.includes('sales_invoices_invoice_number_pos_id_key')
|
||||
)
|
||||
}
|
||||
|
||||
private normalizeInvoiceDate(invoiceDate: Date | string) {
|
||||
return new Date(invoiceDate).toISOString() as any
|
||||
}
|
||||
|
||||
private buildPaymentsData(
|
||||
paymentsData: CreateSalesInvoiceDto['payments'],
|
||||
totalAmount: number,
|
||||
) {
|
||||
const paymentMethodMap: Record<string, PaymentMethodType> = {
|
||||
cash: PaymentMethodType.CASH,
|
||||
set_off: PaymentMethodType.SET_OFF,
|
||||
card: PaymentMethodType.CARD,
|
||||
bank: PaymentMethodType.BANK,
|
||||
check: PaymentMethodType.CHECK,
|
||||
other: PaymentMethodType.OTHER,
|
||||
terminal: PaymentMethodType.TERMINAL,
|
||||
}
|
||||
|
||||
const rawPayments = (paymentsData || {}) as Record<string, unknown>
|
||||
const terminalInfo = rawPayments.terminals as TerminalPaymentInfo | undefined
|
||||
|
||||
const payments: NormalizedPayment[] = Object.entries(rawPayments)
|
||||
.filter(([key]) => key !== 'terminals')
|
||||
.map(([key, value]) => ({
|
||||
method: paymentMethodMap[key.toLowerCase()],
|
||||
amount: typeof value === 'number' ? value : Number(value || 0),
|
||||
}))
|
||||
.filter(payment => payment.method && payment.amount > 0) as NormalizedPayment[]
|
||||
|
||||
const hasTerminalPayment = payments.some(
|
||||
payment => payment.method === PaymentMethodType.TERMINAL,
|
||||
)
|
||||
const nonTerminalTotal = payments
|
||||
.filter(payment => payment.method !== PaymentMethodType.TERMINAL)
|
||||
.reduce((sum, payment) => sum + payment.amount, 0)
|
||||
|
||||
if (!hasTerminalPayment && terminalInfo) {
|
||||
const terminalAmount =
|
||||
typeof terminalInfo.amount === 'number'
|
||||
? terminalInfo.amount
|
||||
: Math.max(0, Number(totalAmount) - nonTerminalTotal)
|
||||
|
||||
if (terminalAmount > 0) {
|
||||
payments.push({
|
||||
method: PaymentMethodType.TERMINAL,
|
||||
amount: terminalAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.validatePayments(payments, totalAmount, terminalInfo)
|
||||
|
||||
return {
|
||||
payments,
|
||||
terminalInfo,
|
||||
}
|
||||
}
|
||||
|
||||
private validatePayments(
|
||||
payments: NormalizedPayment[],
|
||||
totalAmount: number,
|
||||
terminalInfo?: TerminalPaymentInfo,
|
||||
) {
|
||||
const totalPayments = payments.reduce((sum, payment) => sum + payment.amount, 0)
|
||||
const roundedTotalPayments = Number(totalPayments.toFixed(2))
|
||||
const roundedTotalAmount = Number(Number(totalAmount).toFixed(2))
|
||||
|
||||
if (roundedTotalPayments !== roundedTotalAmount) {
|
||||
throw new BadRequestException('مبلغ پرداختی باید برابر با مبلغ کل فاکتور باشد.')
|
||||
}
|
||||
|
||||
const terminalPayments = payments.filter(
|
||||
payment => payment.method === PaymentMethodType.TERMINAL,
|
||||
)
|
||||
|
||||
if (terminalPayments.length > 0 && !terminalInfo) {
|
||||
throw new BadRequestException('برای پرداخت ترمینال اطلاعات ترمینال الزامی است.')
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveCustomerId(
|
||||
tx: Prisma.TransactionClient,
|
||||
data: CreateSalesInvoiceDto,
|
||||
businessId: string,
|
||||
) {
|
||||
if (data.customer_id) {
|
||||
return data.customer_id
|
||||
}
|
||||
|
||||
if (
|
||||
data.customer_type === CustomerType.INDIVIDUAL &&
|
||||
data.customer?.customer_individual
|
||||
) {
|
||||
const { national_id, mobile_number, ...rest } = data.customer.customer_individual
|
||||
|
||||
const customerIndividual = await tx.customerIndividual.upsert({
|
||||
where: {
|
||||
business_activity_id_national_id: {
|
||||
business_activity_id: businessId,
|
||||
national_id: data.customer.customer_individual.national_id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
...data.customer.customer_individual,
|
||||
customer: {
|
||||
create: {
|
||||
type: CustomerType.INDIVIDUAL,
|
||||
},
|
||||
},
|
||||
business_activity: {
|
||||
connect: {
|
||||
id: businessId,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: { ...rest },
|
||||
select: {
|
||||
customer_id: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!customerIndividual) {
|
||||
throw new BadRequestException('متاسفانه مشکلی در اطلاعات مشتری وجود دارد.')
|
||||
}
|
||||
|
||||
return customerIndividual.customer_id
|
||||
}
|
||||
|
||||
if (data.customer_type === CustomerType.LEGAL && data.customer?.customer_legal) {
|
||||
const { registration_number, ...rest } = data.customer.customer_legal
|
||||
const customerLegal = await tx.customerLegal.upsert({
|
||||
where: {
|
||||
business_activity_id_economic_code: {
|
||||
business_activity_id: businessId,
|
||||
economic_code: data.customer.customer_legal.economic_code,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
...data.customer.customer_legal,
|
||||
customer: {
|
||||
create: {
|
||||
type: CustomerType.LEGAL,
|
||||
},
|
||||
},
|
||||
business_activity: {
|
||||
connect: {
|
||||
id: businessId,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
...rest,
|
||||
},
|
||||
select: {
|
||||
customer_id: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!customerLegal) {
|
||||
throw new BadRequestException('متاسفانه مشکلی در اطلاعات مشتری وجود دارد.')
|
||||
}
|
||||
|
||||
return customerLegal.customer_id
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private async getGoodsById(tx: Prisma.TransactionClient, data: CreateSalesInvoiceDto) {
|
||||
const itemGoodIds = data.items
|
||||
.map(item => item.good_id)
|
||||
.filter((goodId): goodId is string => Boolean(goodId))
|
||||
|
||||
const goods = itemGoodIds.length
|
||||
? await tx.good.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: itemGoodIds,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sku: true,
|
||||
local_sku: true,
|
||||
barcode: true,
|
||||
pricing_model: true,
|
||||
unit_type: true,
|
||||
base_sale_price: true,
|
||||
image_url: true,
|
||||
category: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
: []
|
||||
|
||||
return new Map(goods.map(good => [good.id, good]))
|
||||
}
|
||||
|
||||
private buildSalesInvoiceData(params: {
|
||||
data: CreateSalesInvoiceDto
|
||||
normalizedInvoiceDate: Date
|
||||
invoiceNumber: number
|
||||
consumer_account_id: string
|
||||
pos_id: string
|
||||
goodsById: Map<string, any>
|
||||
customerId: string | null
|
||||
}) {
|
||||
const {
|
||||
data,
|
||||
normalizedInvoiceDate,
|
||||
invoiceNumber,
|
||||
consumer_account_id,
|
||||
pos_id,
|
||||
goodsById,
|
||||
customerId,
|
||||
} = params
|
||||
const { customer_id, customer_type, customer, payments, ...invoiceData } = data
|
||||
|
||||
const salesInvoiceData: any = {
|
||||
...invoiceData,
|
||||
invoice_date: normalizedInvoiceDate,
|
||||
invoice_number: invoiceNumber,
|
||||
total_amount: data.total_amount,
|
||||
code: 'INV-' + Date.now(),
|
||||
items: {
|
||||
createMany: {
|
||||
data: data.items.map(item => ({
|
||||
good_id: item.good_id,
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price,
|
||||
total_amount: item.total_amount,
|
||||
payload: item.payload ? JSON.parse(JSON.stringify(item.payload)) : undefined,
|
||||
good_snapshot: item.good_id
|
||||
? JSON.parse(
|
||||
JSON.stringify({
|
||||
good: goodsById.get(item.good_id) || null,
|
||||
}),
|
||||
)
|
||||
: undefined,
|
||||
unit_type: item.unit_type,
|
||||
})),
|
||||
},
|
||||
},
|
||||
unknown_customer: {},
|
||||
consumer_account: {
|
||||
connect: {
|
||||
id: consumer_account_id,
|
||||
},
|
||||
},
|
||||
pos: {
|
||||
connect: {
|
||||
id: pos_id,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (customerId) {
|
||||
salesInvoiceData.customer = {
|
||||
connect: {
|
||||
id: customerId,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return salesInvoiceData
|
||||
}
|
||||
|
||||
private async getNextInvoiceNumber(tx: Prisma.TransactionClient, businessId: string) {
|
||||
const latestInvoice = await tx.salesInvoice.findFirst({
|
||||
where: {
|
||||
pos: {
|
||||
complex: {
|
||||
business_activity_id: businessId,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
invoice_number: 'desc',
|
||||
},
|
||||
select: {
|
||||
invoice_number: true,
|
||||
},
|
||||
})
|
||||
|
||||
return (latestInvoice?.invoice_number || 0) + 1
|
||||
}
|
||||
|
||||
private async createPayments(
|
||||
tx: Prisma.TransactionClient,
|
||||
invoiceId: string,
|
||||
payments: NormalizedPayment[],
|
||||
terminalInfo: TerminalPaymentInfo | undefined,
|
||||
paidAt: Date,
|
||||
) {
|
||||
for (const payment of payments) {
|
||||
if (payment.amount <= 0) {
|
||||
continue
|
||||
}
|
||||
const createdPayment = await tx.salesInvoicePayment.create({
|
||||
data: {
|
||||
invoice_id: invoiceId,
|
||||
amount: payment.amount,
|
||||
payment_method: payment.method,
|
||||
paid_at: paidAt,
|
||||
},
|
||||
})
|
||||
|
||||
if (payment.method === PaymentMethodType.TERMINAL && terminalInfo) {
|
||||
await tx.salesInvoicePaymentTerminalInfo.create({
|
||||
data: {
|
||||
payment_id: createdPayment.id,
|
||||
terminal_id: terminalInfo.terminalId,
|
||||
stan: terminalInfo.stan,
|
||||
rrn: terminalInfo.rrn,
|
||||
transaction_date_time: new Date(terminalInfo.transactionDateTime),
|
||||
customer_card_no: terminalInfo.customerCardNO || null,
|
||||
description: terminalInfo.description || null,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user