feat: enhance CustomerIndividual and CustomerLegal models with new unique input types

feat: add cache invalidation method for POS middleware

feat: implement Redis caching in POS middleware for improved performance

feat: refactor sales invoice DTOs for POS to include correction functionality

feat: extend SalesInvoicesController to handle invoice corrections and returns

feat: update SalesInvoicesService to support invoice correction and return operations

feat: enhance TSP provider correction functionality with new DTOs and utility methods

fix: improve error handling and logging in Nama provider switch adapter

refactor: streamline invoice payload building in TSP provider utilities
This commit is contained in:
2026-06-06 19:53:00 +03:30
parent 25e589551b
commit f61100bf25
21 changed files with 302 additions and 113 deletions
+5 -4
View File
@@ -1,5 +1,6 @@
import {
InvoiceSettlementType,
TspProviderRequestType,
TspProviderResponseStatus,
} from '@/generated/prisma/enums'
@@ -153,10 +154,10 @@ export default {
[InvoiceSettlementType.MIXED]: 'نقدی / نسیه',
},
TspProviderRequestType: {
ORIGINAL: 'اصلی',
CORRECTION: 'اصلاح',
REVOKE: 'ابطال',
REMOVE: 'حذف',
[TspProviderRequestType.ORIGINAL]: 'اصلی',
[TspProviderRequestType.CORRECTION]: 'اصلاح',
[TspProviderRequestType.REVOKE]: 'ابطال',
[TspProviderRequestType.RETURN]: 'برگشت از فروش',
},
TspProviderCustomerType: {
Unknown: 'ناشناس',
@@ -1,6 +1,7 @@
import { SharedSaleInvoiceAccessService } from '@/common/services/saleInvoices/sale-invoice-access.service'
import { Injectable } from '@nestjs/common'
import { PosCorrectionSalesInvoiceDto } from '@/modules/pos/sales-invoices/dto/create-sales-invoice.dto'
import { SalesInvoiceTspService } from '@/modules/tspProviders/sales-invoice-tsp.service'
import { Injectable } from '@nestjs/common'
@Injectable()
export class SharedSaleInvoiceActionsService {
@@ -45,6 +46,28 @@ export class SharedSaleInvoiceActionsService {
)
}
async correction(
data: PosCorrectionSalesInvoiceDto,
consumerAccountId: string,
posId: string,
complexId: string,
businessId: string,
invoiceId: string,
) {
await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
consumerAccountId,
posId,
)
return this.salesInvoiceTspService.correctionSend(
consumerAccountId,
posId,
complexId,
businessId,
invoiceId,
data,
)
}
async inquiry(consumerAccountId: string, posId: string, invoiceId: string) {
const consumerId = await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
consumerAccountId,
@@ -1,5 +1,5 @@
import type { SaleInvoiceType } from '@/common/interfaces/sale-invoice-payload'
import { ApiProperty } from '@nestjs/swagger'
import { ApiProperty, OmitType } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import {
ArrayMinSize,
@@ -206,8 +206,6 @@ export class SharedCreateSalesInvoiceDto {
@IsBoolean()
send_to_tsp?: boolean
@ApiProperty({ required: true, default: CustomerType.UNKNOWN })
@IsEnum(CustomerType)
customer_type: CustomerType
@@ -229,3 +227,8 @@ export class SharedCreateSalesInvoiceDto {
}
}
}
export class SharedCorrectionSalesInvoiceDto extends OmitType(
SharedCreateSalesInvoiceDto,
['customer', 'customer_id', 'send_to_tsp', 'customer_type', 'settlement_type'],
) {}
+2 -2
View File
@@ -18,13 +18,13 @@ export function checkAndDecodeJwtToken(
token = authHeader.slice(7)
}
}
if (!token) throw new UnauthorizedException('Missing accessToken cookie')
if (!token) throw new UnauthorizedException('توکن احراز هویت ارسال نشده است')
try {
const payload = jwtService.decode(token) as AccessTokenPayload
return payload as ITokenPayload
} catch {
return null
throw new UnauthorizedException('توکن احراز هویت نامعتبر است')
}
}
+1
View File
@@ -20,6 +20,7 @@ export class RedisKeyMaker extends PartnerKeyMaker {
static guildStockKeepingUnitsList = GuildKeyMaker.guildStockKeepingUnitsList
static guildGoodsList = GuildKeyMaker.guildGoodsList
static posMiddleware = PosKeyMaker.middleware
static posInfo = PosKeyMaker.info
static posMe = PosKeyMaker.me
static posGoodsList = PosKeyMaker.goodList
+4
View File
@@ -1,4 +1,8 @@
export class PosKeyMaker {
static middleware(token: string): string {
return `pos:${token}:middleware`
}
static info(posId: string): string {
return `pos:${posId}:info`
}
File diff suppressed because one or more lines are too long
@@ -227,6 +227,7 @@ export type CustomerIndividualOrderByWithRelationInput = {
export type CustomerIndividualWhereUniqueInput = Prisma.AtLeast<{
customer_id?: string
business_activity_id_national_id?: Prisma.CustomerIndividualBusiness_activity_idNational_idCompoundUniqueInput
business_activity_id_postal_code_national_id?: Prisma.CustomerIndividualBusiness_activity_idPostal_codeNational_idCompoundUniqueInput
AND?: Prisma.CustomerIndividualWhereInput | Prisma.CustomerIndividualWhereInput[]
OR?: Prisma.CustomerIndividualWhereInput[]
NOT?: Prisma.CustomerIndividualWhereInput | Prisma.CustomerIndividualWhereInput[]
@@ -239,7 +240,7 @@ export type CustomerIndividualWhereUniqueInput = Prisma.AtLeast<{
business_activity_id?: Prisma.StringFilter<"CustomerIndividual"> | string
business_activity?: Prisma.XOR<Prisma.BusinessActivityScalarRelationFilter, Prisma.BusinessActivityWhereInput>
customer?: Prisma.XOR<Prisma.CustomerScalarRelationFilter, Prisma.CustomerWhereInput>
}, "customer_id" | "business_activity_id_national_id">
}, "customer_id" | "business_activity_id_national_id" | "business_activity_id_postal_code_national_id">
export type CustomerIndividualOrderByWithAggregationInput = {
first_name?: Prisma.SortOrderInput | Prisma.SortOrder
@@ -370,6 +371,12 @@ export type CustomerIndividualBusiness_activity_idNational_idCompoundUniqueInput
national_id: string
}
export type CustomerIndividualBusiness_activity_idPostal_codeNational_idCompoundUniqueInput = {
business_activity_id: string
postal_code: string
national_id: string
}
export type CustomerIndividualCountOrderByAggregateInput = {
first_name?: Prisma.SortOrder
last_name?: Prisma.SortOrder
+8 -1
View File
@@ -209,6 +209,7 @@ export type CustomerLegalOrderByWithRelationInput = {
export type CustomerLegalWhereUniqueInput = Prisma.AtLeast<{
customer_id?: string
business_activity_id_economic_code?: Prisma.CustomerLegalBusiness_activity_idEconomic_codeCompoundUniqueInput
business_activity_id_postal_code_registration_number?: Prisma.CustomerLegalBusiness_activity_idPostal_codeRegistration_numberCompoundUniqueInput
AND?: Prisma.CustomerLegalWhereInput | Prisma.CustomerLegalWhereInput[]
OR?: Prisma.CustomerLegalWhereInput[]
NOT?: Prisma.CustomerLegalWhereInput | Prisma.CustomerLegalWhereInput[]
@@ -219,7 +220,7 @@ export type CustomerLegalWhereUniqueInput = Prisma.AtLeast<{
business_activity_id?: Prisma.StringFilter<"CustomerLegal"> | string
business_activity?: Prisma.XOR<Prisma.BusinessActivityScalarRelationFilter, Prisma.BusinessActivityWhereInput>
customer?: Prisma.XOR<Prisma.CustomerScalarRelationFilter, Prisma.CustomerWhereInput>
}, "customer_id" | "business_activity_id_economic_code">
}, "customer_id" | "business_activity_id_economic_code" | "business_activity_id_postal_code_registration_number">
export type CustomerLegalOrderByWithAggregationInput = {
name?: Prisma.SortOrderInput | Prisma.SortOrder
@@ -332,6 +333,12 @@ export type CustomerLegalBusiness_activity_idEconomic_codeCompoundUniqueInput =
economic_code: string
}
export type CustomerLegalBusiness_activity_idPostal_codeRegistration_numberCompoundUniqueInput = {
business_activity_id: string
postal_code: string
registration_number: string
}
export type CustomerLegalCountOrderByAggregateInput = {
name?: Prisma.SortOrder
economic_code?: Prisma.SortOrder
@@ -6,6 +6,10 @@ import { Injectable } from '@nestjs/common'
export class PosCacheInvalidationService {
constructor(private readonly redisService: RedisService) {}
async invalidatePosMiddleware(token: string): Promise<void> {
this.redisService.delete(PosKeyMaker.middleware(token))
}
async invalidatePosGoodsList(
guildId: string,
businessActivityId: string,
+40 -2
View File
@@ -1,7 +1,9 @@
import { RedisKeyMaker } from '@/common/utils'
import { checkAndDecodeJwtToken } from '@/common/utils/jwt-user.util'
import { ConsumerRole, POSStatus } from '@/generated/prisma/enums'
import { PosSelect, PosWhereInput } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { RedisService } from '@/redis/redis.service'
import {
ForbiddenException,
Injectable,
@@ -16,15 +18,38 @@ export class PosMiddleware implements NestMiddleware {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
private redisService: RedisService,
) {}
async use(req: Request, _res: Response, next: NextFunction) {
const doForbidden = () => {
throw new ForbiddenException('شما دسترسی لازم را ندارید')
}
const token = req.cookies?.accessToken
const cacheKey = RedisKeyMaker.posMiddleware(token)
let cashedPosData: unknown
try {
cashedPosData = await this.redisService.getJson(cacheKey)
} catch (_) {}
const tokenAccount = checkAndDecodeJwtToken(req, this.jwtService)
req.decodedToken = tokenAccount!
if (
cashedPosData &&
typeof cashedPosData === 'object' &&
'pos_id' in cashedPosData &&
'complex_id' in cashedPosData &&
'business_id' in cashedPosData &&
'guild_id' in cashedPosData &&
'consumer_account_id' in cashedPosData
) {
req.posData = cashedPosData as any
return next()
}
return this.prisma.$transaction(async tx => {
try {
const tokenAccount = checkAndDecodeJwtToken(req, this.jwtService)
const { type, role, account_id } = tokenAccount!
const posSelect: PosSelect = {
id: true,
@@ -103,6 +128,20 @@ export class PosMiddleware implements NestMiddleware {
return doForbidden()
}
try {
await this.redisService.setJson(
cacheKey,
{
pos_id: posId,
complex_id: pos.complex.id,
business_id: pos.complex.business_activity.id,
guild_id: pos.complex.business_activity.guild_id,
consumer_account_id: account_id,
},
60 * 60,
)
} catch (_) {}
req.posData = {
pos_id: posId,
complex_id: pos.complex.id,
@@ -110,7 +149,6 @@ export class PosMiddleware implements NestMiddleware {
guild_id: pos.complex.business_activity.guild_id,
consumer_account_id: account_id,
}
req.decodedToken = tokenAccount!
return next()
} catch (error) {
@@ -1,6 +1,7 @@
import { SharedCreateSalesInvoiceDto } from '@/common/services/saleInvoices/sale-invoice-create.dto'
import { PartialType } from '@nestjs/swagger'
import {
SharedCorrectionSalesInvoiceDto,
SharedCreateSalesInvoiceDto,
} from '@/common/services/saleInvoices/sale-invoice-create.dto'
export class CreateSalesInvoiceDto extends SharedCreateSalesInvoiceDto {}
export class UpdateSalesInvoiceDto extends PartialType(CreateSalesInvoiceDto) {}
export class PosCreateSalesInvoiceDto extends SharedCreateSalesInvoiceDto {}
export class PosCorrectionSalesInvoiceDto extends SharedCorrectionSalesInvoiceDto {}
@@ -2,7 +2,10 @@ import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'
import { PosInfo } from '@/common/decorators/posInfo.decorator'
import type { IPosPayload } from '@/common/models/posPayload.model'
import { CreateSalesInvoiceDto } from './dto/create-sales-invoice.dto'
import {
PosCorrectionSalesInvoiceDto,
PosCreateSalesInvoiceDto,
} from './dto/create-sales-invoice.dto'
import { SalesInvoicesFilterDto } from './dto/sales-invoices-filter.dto'
import { SalesInvoicesService } from './sales-invoices.service'
@@ -30,7 +33,7 @@ export class SalesInvoicesController {
}
@Post()
create(@Body() data: CreateSalesInvoiceDto, @PosInfo() posInfo: IPosPayload) {
create(@Body() data: PosCreateSalesInvoiceDto, @PosInfo() posInfo: IPosPayload) {
return this.salesInvoicesService.create(data, posInfo)
}
@@ -49,6 +52,19 @@ export class SalesInvoicesController {
return this.salesInvoicesService.revoke(id, posInfo)
}
@Post(':id/correction')
correction(
@Param('id') id: string,
@PosInfo() posInfo: IPosPayload,
@Body() data: PosCorrectionSalesInvoiceDto,
) {
return this.salesInvoicesService.correction(id, posInfo, data)
}
@Post(':id/return')
return(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) {
return this.salesInvoicesService.return(id, posInfo)
}
// @Post('send/bulk')
// sendBulk(@Body() data: SendBulkSalesInvoicesDto, @PosInfo() posInfo: IPosPayload) {
// return this.salesInvoicesService.sendBulk(data.invoice_ids, posInfo)
@@ -9,7 +9,10 @@ import { ResponseMapper } from 'common/response/response-mapper'
import { Prisma } from 'generated/prisma/client'
import { TspProviderRequestType, TspProviderResponseStatus } from 'generated/prisma/enums'
import { SalesInvoiceTspService } from '../../tspProviders/sales-invoice-tsp.service'
import { CreateSalesInvoiceDto } from './dto/create-sales-invoice.dto'
import {
PosCorrectionSalesInvoiceDto,
PosCreateSalesInvoiceDto,
} from './dto/create-sales-invoice.dto'
import { SalesInvoicesFilterDto } from './dto/sales-invoices-filter.dto'
@Injectable()
@@ -91,7 +94,7 @@ export class SalesInvoicesService {
} else throw new NotFoundException('فاکتور مورد نظر شما یافت نشد.')
}
async create(data: CreateSalesInvoiceDto, posInfo: IPosPayload) {
async create(data: PosCreateSalesInvoiceDto, posInfo: IPosPayload) {
let salesInvoice = await this.sharedSaleInvoiceCreateService.create({
data,
businessId: posInfo.business_id,
@@ -148,6 +151,39 @@ export class SalesInvoicesService {
return ResponseMapper.single(invoice)
}
async correction(
invoiceId: string,
posInfo: IPosPayload,
data: PosCorrectionSalesInvoiceDto,
) {
const { consumer_account_id, pos_id, business_id, complex_id } = posInfo
const invoice = await this.sharedSaleInvoiceActionsService.correction(
data,
consumer_account_id,
pos_id,
complex_id,
business_id,
invoiceId,
)
return ResponseMapper.single(invoice)
}
async return(invoiceId: string, posInfo: IPosPayload) {
const { consumer_account_id, pos_id, business_id, complex_id } = posInfo
const invoice = await this.sharedSaleInvoiceActionsService.revoke(
consumer_account_id,
pos_id,
complex_id,
business_id,
invoiceId,
)
return ResponseMapper.single(invoice)
}
async inquiry(consumer_account_id: string, pos_id: string, invoiceId: string) {
const invoice = await this.sharedSaleInvoiceActionsService.inquiry(
consumer_account_id,
+10 -2
View File
@@ -7,14 +7,22 @@ import { Type } from 'class-transformer'
import {
ArrayMinSize,
IsArray,
IsDateString,
IsNumber,
IsObject,
IsString,
ValidateNested,
} from 'class-validator'
import { TspProviderOriginalSendPayloadDto } from './original.dto'
import {
TspProviderOriginalResponseDto,
TspProviderOriginalSendPayloadDto,
} from './original.dto'
export class TspProviderCorrectionInvoicePayloadDto {
@ApiProperty({ required: true, nullable: true })
@IsDateString()
invoice_date: Date
@ApiProperty({ type: [SharedCreateSalesInvoiceItemDto] })
@IsArray()
@ValidateNested({ each: true })
@@ -46,4 +54,4 @@ export class TspProviderCorrectionSendPayloadDto extends TspProviderOriginalSend
last_tax_id: string
}
export class TspProviderCorrectionSendResponseDto {}
export class TspProviderCorrectionSendResponseDto extends TspProviderOriginalResponseDto {}
+1 -1
View File
@@ -14,7 +14,7 @@ import {
IsOptional,
IsString,
Min,
ValidateNested
ValidateNested,
} from 'class-validator'
import {
CustomerInfoDto,
@@ -16,6 +16,7 @@ import {
buildOriginalPayload,
buildRevokePayload,
getOriginalResendAttemptNumber,
getRelatedInvoiceForCorrection,
onResult,
trySend,
} from './utils/sales-invoice-tsp.utils'
@@ -165,45 +166,13 @@ export class SalesInvoiceTspService {
): Promise<TspProviderActionResponseDto> {
const [newInvoice, attempt, correctionPayload] = await this.prisma.$transaction(
async tx => {
const relatedInvoice = await tx.salesInvoice.findUnique({
where: {
id: ref_invoice_id,
},
include: {
customer: {
select: {
type: true,
},
},
tsp_attempts: {
orderBy: {
attempt_no: 'desc',
},
take: 1,
},
},
})
if (!relatedInvoice) {
throw new NotFoundException('فاکتور مورد نظر یافت نشد.')
}
if (relatedInvoice.type === TspProviderRequestType.REVOKE) {
throw new BadRequestException(
'فاکتور ارسالی قبلا ابطال شده است و امکان ویرایش آن وجود ندارد.',
)
}
if (!relatedInvoice.tax_id) {
throw new BadRequestException(
'فاکتور قبلی همچنان در حال بررسی است و امکان ویرایش آن وجود ندارد.',
)
}
const relatedInvoice = await getRelatedInvoiceForCorrection(tx, ref_invoice_id)
const newInvoice = await this.sharedSaleInvoiceCreateService.create({
tx,
data: {
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
invoice_date: new Date(),
invoice_date: dataToUpdate.invoice_date,
payments: dataToUpdate.payments,
items: dataToUpdate.items,
total_amount: dataToUpdate.total_amount,
@@ -40,7 +40,7 @@ export class NamaProviderCorrectionRequestDto {
payment: NamaProviderPaymentInfoDto[]
@ApiProperty({ type: NamaProviderCorrectionHeaderDto })
@ValidateNested()
@ValidateNested({ each: true })
@Type(() => NamaProviderCorrectionHeaderDto)
header: NamaProviderCorrectionHeaderDto
@@ -20,6 +20,7 @@ import {
TspProviderBulkSendResultDto,
} from '../../dto/provider-switch.dto'
import {
NamaProviderCorrectionResponseDto,
NamaProviderGetResponseDto,
NamaProviderOriginalResponseDto,
NamaProviderResponseStatus,
@@ -38,6 +39,7 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
process.env.NAMA_PROVIDER_PRODUCTION_URL || 'https://api.nama.ir'
private readonly sendPath = '/api/v1/invoices/single-send'
private readonly correctionPath = this.sendPath
private readonly sendBulkPath = '/api/v1/invoices'
private readonly checkBulkStatus = '/api/v1/invoices/check-by-uuids'
private readonly getPath = '/api/v1/invoices'
@@ -57,6 +59,10 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
return `${this.baseUrl}${this.sendPath}`
}
private buildCorrectionUrl() {
return `${this.baseUrl}${this.correctionPath}`
}
private buildGetUrl(invoiceId: string) {
return `${this.baseUrl}${this.getPath}/${invoiceId}`
}
@@ -110,9 +116,8 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
return result
} catch (err) {
console.log('err@@@@@@@@@@@@@@@@@@', err);
console.log(JSON.parse(JSON.stringify(mappedRequest)));
console.log('err@@@@@@@@@@@@@@@@@@', err)
console.log(JSON.parse(JSON.stringify(mappedRequest)))
const failure: TspProviderOriginalResponseDto = {
invoice_id: payload.id,
@@ -129,21 +134,21 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
async correctionSend(
payload: TspProviderCorrectionSendPayloadDto,
): Promise<TspProviderCorrectionSendResponseDto> {
const mappedRequest = this.namaProviderUtils.mapToNamaOriginalRequestDto(payload)
const mappedRequest = this.namaProviderUtils.mapToNamaCorrectionDto(payload)
try {
const response = await this.httpClient.request(
this.buildSendUrl(),
this.buildCorrectionUrl(),
{
method: 'POST',
body: JSON.stringify(mappedRequest),
},
this.createRequestInterceptors(payload.token),
)
const providerResponse: NamaProviderOriginalResponseDto = await response.json()
this.logger.debug('NAMA provider response', providerResponse)
const providerResponse: NamaProviderCorrectionResponseDto = await response.json()
this.logger.debug('NAMA provider correction response', providerResponse)
const result: TspProviderOriginalResponseDto = {
const result: TspProviderCorrectionSendResponseDto = {
invoice_id: payload.id,
provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)),
hasError: !response.ok,
@@ -158,15 +163,14 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
return result
} catch (err) {
this.logger.error('NAMA send failed', err)
const failure: TspProviderOriginalResponseDto = {
this.logger.error('NAMA correction send failed', err)
const failure: TspProviderCorrectionSendResponseDto = {
invoice_id: payload.id,
provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)),
hasError: true,
message: (err as Error).message,
received_at: new Date().toISOString(),
tax_id: null,
status: TspProviderResponseStatus.NOT_SEND,
status: TspProviderResponseStatus.SEND_FAILURE,
}
return failure
}
@@ -12,7 +12,7 @@ import {
PaymentInfoDto,
TspProviderCorrectionSendPayloadDto,
TspProviderOriginalSendPayloadDto,
TspProviderRevokePayloadDto
TspProviderRevokePayloadDto,
} from '../../dto'
import {
NamaProviderCorrectionRequestDto,
@@ -76,11 +76,8 @@ export class NamaProviderUtils {
spro: '0',
}
console.log('item.gold_type_payload', item.gold_type_payload);
if (item.gold_type_payload) {
const { profit='0', commission = '0', wages = "0" } = item.gold_type_payload
const { profit = '0', commission = '0', wages = '0' } = item.gold_type_payload
data = {
...data,
consfee: wages,
@@ -110,21 +107,39 @@ export class NamaProviderUtils {
inno: payload.invoice_number.toString(),
tins: '',
indatim: payload.invoice_date.getTime() + '',
setm: '1',
irtaxid: payload.last_tax_id,
...this.mapSettlementBased(
payload.settlement_type,
payload.payments,
payload.total_amount,
),
...this.mapCustomerInfo(payload.customer),
},
body: payload.items.map(item => ({
sstid: item.sku,
vra: item.sku_vat,
// sstt: item.unit_type,
fee: String(item.unit_price),
dis: String(item.discount_amount),
mu: item.measure_unit,
am: String(item.quantity),
consfee: '0',
bros: '0',
spro: '0',
})),
body: payload.items.map(item => {
let data: NamaProviderOriginalBodyItemDto = {
sstid: item.sku,
vra: item.sku_vat,
fee: String(item.unit_price),
dis: String(item.discount_amount),
mu: item.measure_unit,
am: String(item.quantity),
consfee: '0',
bros: '0',
spro: '0',
}
if (item.gold_type_payload) {
const { profit = '0', commission = '0', wages = '0' } = item.gold_type_payload
data = {
...data,
consfee: wages,
bros: commission,
spro: profit,
tcpbs: (Number(profit) + Number(commission) + Number(wages)).toString(),
}
}
return data
}),
}
}
@@ -1,9 +1,7 @@
import { CustomerType } from '@/generated/prisma/enums'
import { SaleInvoiceTspAttemptsUpdateInput } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, NotFoundException } from '@nestjs/common'
import {
CustomerType,
} from '@/generated/prisma/enums'
import {
Prisma,
TspProviderRequestType,
@@ -172,7 +170,6 @@ export async function buildCorrectionPayload(
invoice_date: true,
invoice_number: true,
settlement_type: true,
tax_id: true,
discount_amount: true,
tax_amount: true,
items: true,
@@ -226,16 +223,17 @@ export async function buildCorrectionPayload(
legal: true,
},
},
reference_invoice: {
select: {
tax_id: true,
},
},
payments: {
include: {
terminal_info: true,
},
},
reference_invoice: {
select: {
tax_id: true,
id: true,
},
},
},
})
@@ -284,7 +282,9 @@ export async function buildCorrectionPayload(
token: invoice.pos.complex.business_activity.partner_token,
tsp_provider: partner.tsp_provider!,
template: invoice.pos.complex.business_activity.guild.invoice_template,
invoice_date: new Date(),
invoice_date: invoice?.invoice_date
? new Date(invoice.invoice_date)
: invoice.invoice_date,
settlement_type: invoice.settlement_type,
customer:
@@ -305,11 +305,9 @@ export async function buildCorrectionPayload(
first_name: invoice.customer.individual.first_name ?? undefined,
last_name: invoice.customer.individual.last_name ?? undefined,
national_id: invoice.customer.individual.national_id ?? undefined,
mobile_number:
invoice.customer.individual.mobile_number ?? undefined,
mobile_number: invoice.customer.individual.mobile_number ?? undefined,
postal_code: invoice.customer.individual.postal_code ?? undefined,
economic_code:
invoice.customer.individual.economic_code ?? undefined,
economic_code: invoice.customer.individual.economic_code ?? undefined,
}
: undefined,
}
@@ -487,11 +485,9 @@ export async function buildOriginalPayload(
first_name: invoice.customer.individual.first_name ?? undefined,
last_name: invoice.customer.individual.last_name ?? undefined,
national_id: invoice.customer.individual.national_id ?? undefined,
mobile_number:
invoice.customer.individual.mobile_number ?? undefined,
mobile_number: invoice.customer.individual.mobile_number ?? undefined,
postal_code: invoice.customer.individual.postal_code ?? undefined,
economic_code:
invoice.customer.individual.economic_code ?? undefined,
economic_code: invoice.customer.individual.economic_code ?? undefined,
}
: undefined,
}
@@ -521,6 +517,62 @@ export async function buildOriginalPayload(
}
}
export async function getRelatedInvoiceForCorrection(
tx: Prisma.TransactionClient,
ref_invoice_id: string,
) {
const relatedInvoice = await getRelatedInvoiceForModification(tx, ref_invoice_id)
return relatedInvoice
}
export async function getRelatedInvoiceForModification(
tx: Prisma.TransactionClient,
ref_invoice_id: string,
) {
const relatedInvoice = await tx.salesInvoice.findUnique({
where: {
id: ref_invoice_id,
},
include: {
customer: {
select: {
type: true,
},
},
payments: {
select: {},
},
tsp_attempts: {
orderBy: {
attempt_no: 'desc',
},
take: 1,
},
},
})
if (!relatedInvoice) {
throw new NotFoundException('فاکتور مورد نظر یافت نشد.')
}
if (relatedInvoice.type === TspProviderRequestType.REVOKE) {
throw new BadRequestException(
'فاکتور ارسالی قبلا ابطال شده است و امکان ویرایش آن وجود ندارد.',
)
}
if (
!relatedInvoice.tax_id ||
relatedInvoice.tsp_attempts?.[0].status !== TspProviderResponseStatus.SUCCESS
) {
throw new BadRequestException(
'فاکتور قبلی همچنان در حال بررسی است و امکان اصلاح آن وجود ندارد.',
)
}
return relatedInvoice
}
export async function trySend(
tspSwitchService: {
send(