2026-05-05 22:42:09 +03:30
|
|
|
import { SharedSaleInvoiceCreateService } from '@/common/services/saleInvoices/sale-invoice-create.service'
|
2026-05-03 16:23:17 +03:30
|
|
|
import { PrismaService } from '@/prisma/prisma.service'
|
|
|
|
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'
|
|
|
|
|
import {
|
2026-05-05 22:42:09 +03:30
|
|
|
CustomerType,
|
2026-05-03 16:23:17 +03:30
|
|
|
TspProviderRequestType,
|
|
|
|
|
TspProviderResponseStatus,
|
|
|
|
|
} from 'generated/prisma/client'
|
|
|
|
|
import {
|
2026-05-05 22:42:09 +03:30
|
|
|
TspProviderActionResponseDto,
|
|
|
|
|
TspProviderCorrectionInvoicePayloadDto,
|
2026-05-03 16:23:17 +03:30
|
|
|
} from './dto/provider-switch.dto'
|
|
|
|
|
import { SalesInvoiceTspSwitchService } from './sales-invoice-tsp-switch.service'
|
2026-05-08 18:09:13 +03:30
|
|
|
import {
|
|
|
|
|
buildCorrectionPayload,
|
|
|
|
|
buildPayload,
|
|
|
|
|
buildRevokePayload,
|
|
|
|
|
getOriginalResendAttemptNumber,
|
|
|
|
|
onResult,
|
|
|
|
|
trySend,
|
|
|
|
|
} from './utils/sales-invoice-tsp.utils'
|
2026-05-03 16:23:17 +03:30
|
|
|
|
|
|
|
|
type ItemTspRow = {
|
|
|
|
|
tsp_provider: string | null
|
|
|
|
|
tax_id: string | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class SalesInvoiceTspService {
|
|
|
|
|
constructor(
|
|
|
|
|
private readonly prisma: PrismaService,
|
|
|
|
|
private readonly tspSwitchService: SalesInvoiceTspSwitchService,
|
2026-05-05 22:42:09 +03:30
|
|
|
private sharedSaleInvoiceCreateService: SharedSaleInvoiceCreateService,
|
2026-05-03 16:23:17 +03:30
|
|
|
) {}
|
|
|
|
|
|
2026-05-10 09:44:49 +03:30
|
|
|
private mapProviderError(error: any) {
|
|
|
|
|
const isProviderFailureObject =
|
|
|
|
|
error &&
|
|
|
|
|
typeof error === 'object' &&
|
|
|
|
|
('provider_request_payload' in error ||
|
|
|
|
|
'provider_response_payload' in error ||
|
|
|
|
|
'sent_at' in error ||
|
|
|
|
|
'received_at' in error)
|
|
|
|
|
|
|
|
|
|
if (isProviderFailureObject) {
|
|
|
|
|
return {
|
|
|
|
|
hasError: true,
|
|
|
|
|
status: error.status || TspProviderResponseStatus.FAILURE,
|
|
|
|
|
message:
|
|
|
|
|
error.message?.toString() ||
|
|
|
|
|
'خطا در ارتباط با سامانه مالیاتی. لطفا مجددا تلاش کنید.',
|
|
|
|
|
provider_request_payload: error.provider_request_payload,
|
|
|
|
|
provider_response_payload: error.provider_response_payload,
|
|
|
|
|
sent_at: error.sent_at || new Date().toISOString(),
|
|
|
|
|
received_at: error.received_at || new Date().toISOString(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
hasError: true,
|
|
|
|
|
status: TspProviderResponseStatus.FAILURE,
|
|
|
|
|
message:
|
|
|
|
|
error?.message?.toString() ||
|
|
|
|
|
'خطا در ارتباط با سامانه مالیاتی. لطفا مجددا تلاش کنید.',
|
|
|
|
|
provider_response_payload: {
|
|
|
|
|
error: error?.message || 'fetch failed',
|
|
|
|
|
cause: error?.cause || null,
|
|
|
|
|
},
|
|
|
|
|
sent_at: new Date().toISOString(),
|
|
|
|
|
received_at: new Date().toISOString(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async runProviderCall(fn: () => Promise<any>) {
|
|
|
|
|
try {
|
|
|
|
|
return await fn()
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
return this.mapProviderError(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
async originalSend(
|
|
|
|
|
posId: string,
|
|
|
|
|
invoice_id: string,
|
|
|
|
|
): Promise<TspProviderActionResponseDto> {
|
2026-05-08 18:09:13 +03:30
|
|
|
const payload = await buildPayload(this.prisma, invoice_id, posId)
|
2026-05-07 20:30:24 +03:30
|
|
|
|
2026-05-08 18:09:13 +03:30
|
|
|
const attemptNumber = await getOriginalResendAttemptNumber(this.prisma, invoice_id)
|
2026-05-10 09:44:49 +03:30
|
|
|
console.log('attemptNumber', attemptNumber)
|
2026-05-07 20:30:24 +03:30
|
|
|
|
2026-05-03 16:23:17 +03:30
|
|
|
const attempt = await this.prisma.saleInvoiceTspAttempts.create({
|
|
|
|
|
data: {
|
2026-05-07 20:30:24 +03:30
|
|
|
attempt_no: attemptNumber,
|
2026-05-03 16:23:17 +03:30
|
|
|
invoice_id,
|
|
|
|
|
status: TspProviderResponseStatus.QUEUED,
|
2026-05-08 18:09:13 +03:30
|
|
|
raw_request_payload: JSON.parse(JSON.stringify(payload)),
|
2026-05-05 22:42:09 +03:30
|
|
|
message: 'در حال ارسال به سامانه مالیاتی...',
|
2026-05-03 16:23:17 +03:30
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2026-05-10 09:44:49 +03:30
|
|
|
const result = await this.runProviderCall(() =>
|
|
|
|
|
trySend(this.tspSwitchService, payload),
|
|
|
|
|
)
|
|
|
|
|
if (result?.hasError) {
|
|
|
|
|
result.provider_request_payload =
|
|
|
|
|
result.provider_request_payload || JSON.parse(JSON.stringify(payload))
|
|
|
|
|
result.sent_at = result.sent_at || new Date().toISOString()
|
|
|
|
|
result.received_at = result.received_at || new Date().toISOString()
|
|
|
|
|
}
|
2026-05-03 16:23:17 +03:30
|
|
|
|
2026-05-08 18:09:13 +03:30
|
|
|
return await onResult(this.prisma, result, attempt.id)
|
2026-05-03 16:23:17 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async sendBulk(consumer_id: string, invoice_ids: string[]): Promise<void> {
|
2026-05-05 22:42:09 +03:30
|
|
|
// if (!invoice_ids.length) return
|
|
|
|
|
// const payloads: TspProviderOriginalSendPayloadDto[] = []
|
|
|
|
|
// for (const invoiceId of invoice_ids) {
|
|
|
|
|
// const posId = await this.getPosId(consumer_id, invoiceId)
|
|
|
|
|
// payloads.push(await this.buildPayload(invoiceId, posId))
|
|
|
|
|
// }
|
|
|
|
|
// const bulkResult = await this.tspSwitchService.sendBulk(payloads)
|
|
|
|
|
// const allItemResults = bulkResult.flatMap(result => result.items)
|
|
|
|
|
// await this.persistAttemptResults(allItemResults)
|
2026-05-03 16:23:17 +03:30
|
|
|
}
|
|
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
async get(
|
|
|
|
|
invoice_id: string,
|
|
|
|
|
pos_id: string,
|
|
|
|
|
consumer_id: string,
|
|
|
|
|
): Promise<TspProviderActionResponseDto> {
|
2026-05-03 16:23:17 +03:30
|
|
|
const [attempt, pos] = await this.prisma.$transaction(async tx => [
|
|
|
|
|
await tx.saleInvoiceTspAttempts.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
invoice_id,
|
|
|
|
|
invoice: {
|
|
|
|
|
pos: {
|
|
|
|
|
id: pos_id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: {
|
|
|
|
|
attempt_no: 'desc',
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
await tx.pos.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
id: pos_id,
|
|
|
|
|
complex: {
|
|
|
|
|
business_activity: {
|
|
|
|
|
consumer_id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
select: {
|
|
|
|
|
complex: {
|
|
|
|
|
select: {
|
|
|
|
|
business_activity: {
|
|
|
|
|
select: {
|
|
|
|
|
partner_token: true,
|
|
|
|
|
consumer: {
|
|
|
|
|
select: {
|
|
|
|
|
legal: {
|
|
|
|
|
select: {
|
|
|
|
|
partner: {
|
|
|
|
|
select: {
|
|
|
|
|
tsp_provider: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
individual: {
|
|
|
|
|
select: {
|
|
|
|
|
partner: {
|
|
|
|
|
select: {
|
|
|
|
|
tsp_provider: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
if (!attempt) {
|
|
|
|
|
throw new NotFoundException('صورتحساب ارسالی فاکتور شما به سامانه یافت نشد.')
|
|
|
|
|
}
|
|
|
|
|
if (!pos) {
|
|
|
|
|
throw new NotFoundException('مشکلی در ساختار اطلاعات ورودی شما وجود دارد.')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { business_activity } = pos.complex
|
|
|
|
|
|
|
|
|
|
const { partner } = (business_activity.consumer.individual ||
|
|
|
|
|
business_activity.consumer.legal)!
|
|
|
|
|
|
|
|
|
|
const result = await this.tspSwitchService.get(
|
|
|
|
|
partner.tsp_provider,
|
|
|
|
|
invoice_id,
|
|
|
|
|
business_activity.partner_token,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
console.log('getResult', result)
|
|
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
|
throw new NotFoundException('نتیجه ارسال فاکتور یافت نشد.')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedAttempt = await this.prisma.saleInvoiceTspAttempts.update({
|
|
|
|
|
where: {
|
|
|
|
|
id: attempt.id,
|
|
|
|
|
},
|
|
|
|
|
data: {
|
2026-05-08 18:09:13 +03:30
|
|
|
provider_response_payload: JSON.parse(JSON.stringify(result)),
|
2026-05-03 16:23:17 +03:30
|
|
|
status: result.status,
|
|
|
|
|
received_at: result.received_at,
|
|
|
|
|
},
|
|
|
|
|
select: {
|
|
|
|
|
status: true,
|
|
|
|
|
invoice: true,
|
2026-05-05 22:42:09 +03:30
|
|
|
message: true,
|
2026-05-03 16:23:17 +03:30
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
2026-05-05 22:42:09 +03:30
|
|
|
invoice: updatedAttempt.invoice,
|
2026-05-03 16:23:17 +03:30
|
|
|
status: updatedAttempt.status,
|
2026-05-05 22:42:09 +03:30
|
|
|
message: updatedAttempt.message,
|
2026-05-03 16:23:17 +03:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
async correctionSend(
|
|
|
|
|
consumerAccountId: string,
|
|
|
|
|
posId: string,
|
|
|
|
|
complexId: string,
|
|
|
|
|
businessId: string,
|
|
|
|
|
ref_invoice_id: string,
|
|
|
|
|
dataToUpdate: TspProviderCorrectionInvoicePayloadDto,
|
|
|
|
|
): Promise<TspProviderActionResponseDto> {
|
|
|
|
|
const [newInvoice, attempt, correctionPayload] = await this.prisma.$transaction(
|
|
|
|
|
async tx => {
|
|
|
|
|
const relatedInvoice = await tx.salesInvoice.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
id: ref_invoice_id,
|
2026-05-03 16:23:17 +03:30
|
|
|
},
|
|
|
|
|
include: {
|
2026-05-05 22:42:09 +03:30
|
|
|
customer: {
|
|
|
|
|
select: {
|
|
|
|
|
type: true,
|
2026-05-03 16:23:17 +03:30
|
|
|
},
|
|
|
|
|
},
|
2026-05-05 22:42:09 +03:30
|
|
|
tsp_attempts: {
|
|
|
|
|
orderBy: {
|
|
|
|
|
attempt_no: 'desc',
|
2026-05-03 16:23:17 +03:30
|
|
|
},
|
2026-05-05 22:42:09 +03:30
|
|
|
take: 1,
|
2026-05-03 16:23:17 +03:30
|
|
|
},
|
|
|
|
|
},
|
2026-05-05 22:42:09 +03:30
|
|
|
})
|
2026-05-03 16:23:17 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
if (!relatedInvoice) {
|
|
|
|
|
throw new NotFoundException('فاکتور مورد نظر یافت نشد.')
|
|
|
|
|
}
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
if (relatedInvoice.type === TspProviderRequestType.REVOKE) {
|
|
|
|
|
throw new BadRequestException(
|
|
|
|
|
'فاکتور ارسالی قبلا ابطال شده است و امکان ویرایش آن وجود ندارد.',
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-05-03 16:23:17 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
if (!relatedInvoice.tax_id) {
|
|
|
|
|
throw new BadRequestException(
|
|
|
|
|
'فاکتور قبلی همچنان در حال بررسی است و امکان ویرایش آن وجود ندارد.',
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
const newInvoice = await this.sharedSaleInvoiceCreateService.create({
|
|
|
|
|
data: {
|
|
|
|
|
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
|
|
|
|
|
invoice_date: new Date(),
|
|
|
|
|
payments: dataToUpdate.payments,
|
|
|
|
|
items: dataToUpdate.items,
|
|
|
|
|
total_amount: dataToUpdate.total_amount,
|
|
|
|
|
customer_id: relatedInvoice.customer_id || undefined,
|
2026-05-24 19:40:04 +03:30
|
|
|
settlement_type: relatedInvoice.settlement_type,
|
2026-05-05 22:42:09 +03:30
|
|
|
},
|
|
|
|
|
businessId,
|
|
|
|
|
complexId,
|
|
|
|
|
posId,
|
|
|
|
|
consumerAccountId,
|
|
|
|
|
main_invoice_id: relatedInvoice.main_id || relatedInvoice.id,
|
|
|
|
|
ref_invoice_id: relatedInvoice.id,
|
|
|
|
|
type: TspProviderRequestType.CORRECTION,
|
|
|
|
|
})
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-08 18:09:13 +03:30
|
|
|
const correctionPayload = await buildCorrectionPayload(tx, newInvoice.id)
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
const attempt = await tx.saleInvoiceTspAttempts.create({
|
|
|
|
|
data: {
|
|
|
|
|
attempt_no: 1,
|
|
|
|
|
invoice_id: newInvoice.id,
|
|
|
|
|
message: 'در حال ارسال به سامانه مالیاتی...',
|
|
|
|
|
status: TspProviderResponseStatus.QUEUED,
|
2026-05-08 18:09:13 +03:30
|
|
|
sent_at: new Date().toISOString(),
|
|
|
|
|
raw_request_payload: JSON.parse(JSON.stringify(correctionPayload)),
|
2026-05-05 22:42:09 +03:30
|
|
|
},
|
|
|
|
|
})
|
2026-05-03 16:23:17 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
return [newInvoice, attempt, correctionPayload]
|
2026-05-03 16:23:17 +03:30
|
|
|
},
|
2026-05-05 22:42:09 +03:30
|
|
|
)
|
2026-05-03 16:23:17 +03:30
|
|
|
|
2026-05-10 09:44:49 +03:30
|
|
|
const result = await this.runProviderCall(() =>
|
|
|
|
|
trySend(this.tspSwitchService, correctionPayload),
|
|
|
|
|
)
|
|
|
|
|
if (result?.hasError) {
|
|
|
|
|
result.provider_request_payload =
|
2026-05-24 19:40:04 +03:30
|
|
|
result.provider_request_payload || JSON.parse(JSON.stringify(correctionPayload))
|
2026-05-10 09:44:49 +03:30
|
|
|
result.sent_at = result.sent_at || new Date().toISOString()
|
|
|
|
|
result.received_at = result.received_at || new Date().toISOString()
|
|
|
|
|
}
|
2026-05-03 16:23:17 +03:30
|
|
|
|
|
|
|
|
console.log('sendResult')
|
2026-05-05 22:42:09 +03:30
|
|
|
console.log(result)
|
2026-05-08 18:09:13 +03:30
|
|
|
return onResult(this.prisma, result, attempt.id)
|
2026-05-05 22:42:09 +03:30
|
|
|
|
|
|
|
|
// const countByGoodId = (goodIds: string[]): Map<string, number> => {
|
|
|
|
|
// const counts = new Map<string, number>()
|
|
|
|
|
// for (const goodId of goodIds) {
|
|
|
|
|
// counts.set(goodId, (counts.get(goodId) ?? 0) + 1)
|
|
|
|
|
// }
|
|
|
|
|
// return counts
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// const updatedGoodIds = dataToUpdate.items.map(item => item.good_id!)
|
|
|
|
|
// const previousGoodIds = lastAttempt.invoice.items.map(item => item.good_id)
|
|
|
|
|
|
|
|
|
|
// const updatedCounts = countByGoodId(updatedGoodIds)
|
|
|
|
|
// const previousCounts = countByGoodId(previousGoodIds)
|
|
|
|
|
|
|
|
|
|
// let isBackFromSale = false
|
|
|
|
|
// if (updatedCounts.size !== previousCounts.size) {
|
|
|
|
|
// isBackFromSale = true
|
|
|
|
|
// } else {
|
|
|
|
|
// for (const [goodId, count] of updatedCounts) {
|
|
|
|
|
// if (previousCounts.get(goodId) !== count) {
|
|
|
|
|
// isBackFromSale = true
|
|
|
|
|
// break
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
2026-05-03 16:23:17 +03:30
|
|
|
}
|
|
|
|
|
|
2026-05-04 11:21:49 +03:30
|
|
|
async revoke(
|
2026-05-05 22:42:09 +03:30
|
|
|
consumerAccountId: string,
|
|
|
|
|
posId: string,
|
|
|
|
|
complexId: string,
|
|
|
|
|
businessId: string,
|
|
|
|
|
ref_invoice_id: string,
|
|
|
|
|
): Promise<TspProviderActionResponseDto> {
|
|
|
|
|
const [newInvoice, attempt, revokePayload] = await this.prisma.$transaction(
|
|
|
|
|
async tx => {
|
|
|
|
|
const relatedInvoice = await tx.salesInvoice.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
id: ref_invoice_id,
|
2026-05-04 11:21:49 +03:30
|
|
|
},
|
2026-05-05 22:42:09 +03:30
|
|
|
include: {
|
|
|
|
|
customer: {
|
|
|
|
|
select: {
|
|
|
|
|
type: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
tsp_attempts: {
|
|
|
|
|
orderBy: {
|
|
|
|
|
attempt_no: 'desc',
|
|
|
|
|
},
|
|
|
|
|
take: 1,
|
|
|
|
|
},
|
|
|
|
|
payments: {
|
|
|
|
|
include: {
|
|
|
|
|
terminal_info: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
items: true,
|
|
|
|
|
},
|
|
|
|
|
})
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
if (!relatedInvoice) {
|
|
|
|
|
throw new NotFoundException('فاکتور مورد نظر یافت نشد.')
|
|
|
|
|
}
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
if (relatedInvoice.type === TspProviderRequestType.REVOKE) {
|
|
|
|
|
throw new BadRequestException(
|
|
|
|
|
'فاکتور ارسالی قبلا ابطال شده است و امکان ویرایش آن وجود ندارد.',
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
if (!relatedInvoice.tax_id) {
|
|
|
|
|
throw new BadRequestException(
|
|
|
|
|
'فاکتور قبلی همچنان در حال بررسی است و امکان ویرایش آن وجود ندارد.',
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
const payments = relatedInvoice.payments.reduce(
|
|
|
|
|
(acc, payment) => {
|
|
|
|
|
const amount = Number(payment.amount)
|
|
|
|
|
switch (payment.payment_method) {
|
|
|
|
|
case 'CASH':
|
|
|
|
|
acc.cash = (acc.cash || 0) + amount
|
|
|
|
|
break
|
|
|
|
|
case 'SET_OFF':
|
|
|
|
|
acc.set_off = (acc.set_off || 0) + amount
|
|
|
|
|
break
|
|
|
|
|
case 'CARD':
|
|
|
|
|
acc.card = (acc.card || 0) + amount
|
|
|
|
|
break
|
|
|
|
|
case 'BANK':
|
|
|
|
|
acc.bank = (acc.bank || 0) + amount
|
|
|
|
|
break
|
|
|
|
|
case 'CHEQUE':
|
|
|
|
|
acc.check = (acc.check || 0) + amount
|
|
|
|
|
break
|
|
|
|
|
case 'OTHER':
|
|
|
|
|
acc.other = (acc.other || 0) + amount
|
|
|
|
|
break
|
|
|
|
|
case 'TERMINAL':
|
|
|
|
|
acc.terminals = payment.terminal_info
|
|
|
|
|
? {
|
|
|
|
|
amount,
|
|
|
|
|
terminalId: payment.terminal_info.terminal_id,
|
|
|
|
|
stan: payment.terminal_info.stan,
|
|
|
|
|
rrn: payment.terminal_info.rrn,
|
|
|
|
|
customer_card_no:
|
|
|
|
|
payment.terminal_info.customer_card_no || undefined,
|
|
|
|
|
transaction_date_time: payment.terminal_info.transaction_date_time,
|
|
|
|
|
description: payment.terminal_info.description || undefined,
|
|
|
|
|
}
|
|
|
|
|
: acc.terminals
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
return acc
|
|
|
|
|
},
|
|
|
|
|
{} as {
|
|
|
|
|
terminals?: {
|
|
|
|
|
amount?: number
|
|
|
|
|
terminalId: string
|
|
|
|
|
stan: string
|
|
|
|
|
rrn: string
|
|
|
|
|
customer_card_no?: string
|
|
|
|
|
transaction_date_time: Date
|
|
|
|
|
description?: string
|
|
|
|
|
}
|
|
|
|
|
cash?: number
|
|
|
|
|
set_off?: number
|
|
|
|
|
card?: number
|
|
|
|
|
bank?: number
|
|
|
|
|
check?: number
|
|
|
|
|
other?: number
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const newInvoice = await this.sharedSaleInvoiceCreateService.create({
|
|
|
|
|
data: {
|
|
|
|
|
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
|
|
|
|
|
invoice_date: new Date(),
|
|
|
|
|
payments,
|
2026-05-24 19:40:04 +03:30
|
|
|
settlement_type: relatedInvoice.settlement_type,
|
2026-05-05 22:42:09 +03:30
|
|
|
items: relatedInvoice.items.map(item => ({
|
|
|
|
|
unit_price: Number(item.unit_price),
|
|
|
|
|
quantity: Number(item.quantity),
|
|
|
|
|
total_amount: Number(item.total_amount),
|
|
|
|
|
discount_amount: Number(item.discount || 0),
|
|
|
|
|
invoice_id: '',
|
|
|
|
|
good_id: item.good_id,
|
|
|
|
|
service_id: item.service_id || undefined,
|
|
|
|
|
payload: item.payload as any,
|
|
|
|
|
notes: item.notes || '',
|
|
|
|
|
})),
|
|
|
|
|
total_amount: Number(relatedInvoice.total_amount),
|
|
|
|
|
customer_id: relatedInvoice.customer_id || undefined,
|
|
|
|
|
},
|
|
|
|
|
businessId,
|
|
|
|
|
complexId,
|
|
|
|
|
posId,
|
|
|
|
|
consumerAccountId,
|
|
|
|
|
main_invoice_id: relatedInvoice.main_id || relatedInvoice.id,
|
|
|
|
|
ref_invoice_id: relatedInvoice.id,
|
2026-05-04 11:21:49 +03:30
|
|
|
type: TspProviderRequestType.REVOKE,
|
2026-05-05 22:42:09 +03:30
|
|
|
})
|
2026-05-04 11:21:49 +03:30
|
|
|
|
2026-05-08 18:09:13 +03:30
|
|
|
const payload = await buildRevokePayload(this.prisma, newInvoice.id, posId)
|
2026-05-05 22:42:09 +03:30
|
|
|
|
|
|
|
|
const attempt = await tx.saleInvoiceTspAttempts.create({
|
|
|
|
|
data: {
|
|
|
|
|
attempt_no: 1,
|
|
|
|
|
invoice_id: newInvoice.id,
|
|
|
|
|
message: 'در حال ارسال به سامانه مالیاتی...',
|
|
|
|
|
status: TspProviderResponseStatus.QUEUED,
|
2026-05-08 18:09:13 +03:30
|
|
|
raw_request_payload: JSON.parse(JSON.stringify(payload)),
|
2026-05-05 22:42:09 +03:30
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return [newInvoice, attempt, payload]
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-10 09:44:49 +03:30
|
|
|
const result = await this.runProviderCall(() =>
|
|
|
|
|
this.tspSwitchService.revoke(revokePayload),
|
|
|
|
|
)
|
|
|
|
|
if (result?.hasError) {
|
|
|
|
|
result.provider_request_payload =
|
|
|
|
|
result.provider_request_payload || JSON.parse(JSON.stringify(revokePayload))
|
|
|
|
|
result.sent_at = result.sent_at || new Date().toISOString()
|
|
|
|
|
result.received_at = result.received_at || new Date().toISOString()
|
|
|
|
|
}
|
2026-05-05 22:42:09 +03:30
|
|
|
|
2026-05-08 18:09:13 +03:30
|
|
|
return await onResult(this.prisma, result, attempt.id)
|
2026-05-03 16:23:17 +03:30
|
|
|
}
|
|
|
|
|
}
|