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:
@@ -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'],
|
||||
) {}
|
||||
|
||||
@@ -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('توکن احراز هویت نامعتبر است')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,13 +107,18 @@ 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 => ({
|
||||
body: payload.items.map(item => {
|
||||
let data: NamaProviderOriginalBodyItemDto = {
|
||||
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,
|
||||
@@ -124,7 +126,20 @@ export class NamaProviderUtils {
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user