2026-03-29 18:06:41 +03:30
|
|
|
import { IPosPayload } from '@/common/models/posPayload.model'
|
2026-05-05 22:42:09 +03:30
|
|
|
import { SharedSaleInvoiceCreateService } from '@/common/services/saleInvoices/sale-invoice-create.service'
|
2026-05-10 14:14:01 +03:30
|
|
|
import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service'
|
2026-05-01 19:43:59 +03:30
|
|
|
import { translateEnumValue } from '@/common/utils'
|
2026-03-07 11:25:11 +03:30
|
|
|
import { PrismaService } from '@/prisma/prisma.service'
|
2026-05-10 14:14:01 +03:30
|
|
|
import { Injectable, NotFoundException } from '@nestjs/common'
|
2026-03-07 11:25:11 +03:30
|
|
|
import { ResponseMapper } from 'common/response/response-mapper'
|
2026-04-30 16:27:46 +03:30
|
|
|
import { Prisma } from 'generated/prisma/client'
|
2026-05-10 14:14:01 +03:30
|
|
|
import { TspProviderRequestType, TspProviderResponseStatus } from 'generated/prisma/enums'
|
2026-05-03 16:23:17 +03:30
|
|
|
import { SalesInvoiceTspService } from '../../tspProviders/sales-invoice-tsp.service'
|
2026-02-24 12:42:10 +03:30
|
|
|
import { CreateSalesInvoiceDto } from './dto/create-sales-invoice.dto'
|
2026-05-01 19:43:59 +03:30
|
|
|
import { SalesInvoicesFilterDto } from './dto/sales-invoices-filter.dto'
|
2026-02-24 12:42:10 +03:30
|
|
|
|
2026-01-07 15:25:59 +03:30
|
|
|
@Injectable()
|
|
|
|
|
export class SalesInvoicesService {
|
2026-04-27 22:11:05 +03:30
|
|
|
constructor(
|
|
|
|
|
private prisma: PrismaService,
|
2026-05-03 16:23:17 +03:30
|
|
|
private salesInvoiceTaxService: SalesInvoiceTspService,
|
2026-05-05 22:42:09 +03:30
|
|
|
private sharedSaleInvoiceCreateService: SharedSaleInvoiceCreateService,
|
2026-05-10 14:14:01 +03:30
|
|
|
private sharedSaleInvoiceActionsService: SharedSaleInvoiceActionsService,
|
2026-04-27 22:11:05 +03:30
|
|
|
) {}
|
2026-02-24 12:42:10 +03:30
|
|
|
|
2026-05-01 19:43:59 +03:30
|
|
|
async findAll(posInfo: IPosPayload, query: SalesInvoicesFilterDto = {}) {
|
|
|
|
|
const page = query.page || 1
|
|
|
|
|
const perPage = Math.min(query.perPage || 10, 100)
|
|
|
|
|
|
|
|
|
|
const where = this.buildFindAllWhere(posInfo, query)
|
|
|
|
|
const [items, total] = await this.prisma.$transaction(async tx => [
|
|
|
|
|
await tx.salesInvoice.findMany({
|
|
|
|
|
where,
|
|
|
|
|
orderBy: {
|
|
|
|
|
created_at: 'desc',
|
|
|
|
|
},
|
|
|
|
|
skip: (page - 1) * perPage,
|
|
|
|
|
take: perPage,
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
code: true,
|
|
|
|
|
invoice_number: true,
|
|
|
|
|
invoice_date: true,
|
|
|
|
|
created_at: true,
|
|
|
|
|
total_amount: true,
|
|
|
|
|
customer: {
|
|
|
|
|
select: {
|
|
|
|
|
type: true,
|
|
|
|
|
individual: {
|
|
|
|
|
select: {
|
|
|
|
|
first_name: true,
|
|
|
|
|
last_name: true,
|
|
|
|
|
mobile_number: true,
|
|
|
|
|
national_id: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
legal: {
|
|
|
|
|
select: {
|
|
|
|
|
name: true,
|
|
|
|
|
economic_code: true,
|
|
|
|
|
registration_number: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-05-03 16:23:17 +03:30
|
|
|
tsp_attempts: {
|
|
|
|
|
orderBy: {
|
|
|
|
|
created_at: 'desc',
|
|
|
|
|
},
|
|
|
|
|
take: 1,
|
2026-05-01 19:43:59 +03:30
|
|
|
select: {
|
2026-05-03 16:23:17 +03:30
|
|
|
status: true,
|
2026-05-01 19:43:59 +03:30
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
await tx.salesInvoice.count({ where }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const summaryItems = items.map(invoice => ({
|
|
|
|
|
...invoice,
|
|
|
|
|
status: translateEnumValue(
|
2026-05-03 16:23:17 +03:30
|
|
|
'TspProviderResponseStatus',
|
|
|
|
|
invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND,
|
2026-05-01 19:43:59 +03:30
|
|
|
),
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
return ResponseMapper.paginate(summaryItems, { total, page, perPage })
|
2026-01-07 15:25:59 +03:30
|
|
|
}
|
|
|
|
|
|
2026-05-01 19:43:59 +03:30
|
|
|
async findOne(posInfo: IPosPayload, id: string) {
|
2026-05-03 16:23:17 +03:30
|
|
|
const invoice = await this.prisma.salesInvoice.findUnique({
|
2026-05-01 19:43:59 +03:30
|
|
|
where: {
|
|
|
|
|
id,
|
|
|
|
|
pos: {
|
2026-05-03 16:23:17 +03:30
|
|
|
id: posInfo.pos_id,
|
2026-05-01 19:43:59 +03:30
|
|
|
complex: {
|
|
|
|
|
business_activity_id: posInfo.business_id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
code: true,
|
|
|
|
|
invoice_number: true,
|
|
|
|
|
invoice_date: true,
|
|
|
|
|
created_at: true,
|
|
|
|
|
updated_at: true,
|
|
|
|
|
notes: true,
|
|
|
|
|
total_amount: true,
|
|
|
|
|
unknown_customer: true,
|
2026-05-05 22:42:09 +03:30
|
|
|
tax_id: true,
|
2026-05-01 19:43:59 +03:30
|
|
|
customer: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
type: true,
|
|
|
|
|
individual: {
|
|
|
|
|
select: {
|
|
|
|
|
first_name: true,
|
|
|
|
|
last_name: true,
|
|
|
|
|
mobile_number: true,
|
|
|
|
|
national_id: true,
|
|
|
|
|
postal_code: true,
|
|
|
|
|
economic_code: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
legal: {
|
|
|
|
|
select: {
|
|
|
|
|
name: true,
|
|
|
|
|
economic_code: true,
|
|
|
|
|
registration_number: true,
|
|
|
|
|
postal_code: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
pos: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
name: true,
|
|
|
|
|
complex: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
name: true,
|
|
|
|
|
business_activity: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
name: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
consumer_account: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
role: true,
|
|
|
|
|
account: {
|
|
|
|
|
select: {
|
|
|
|
|
username: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
items: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
good_id: true,
|
|
|
|
|
service_id: true,
|
|
|
|
|
quantity: true,
|
|
|
|
|
measure_unit_code: true,
|
|
|
|
|
measure_unit_text: true,
|
|
|
|
|
sku_code: true,
|
|
|
|
|
unit_price: true,
|
|
|
|
|
discount: true,
|
|
|
|
|
total_amount: true,
|
|
|
|
|
notes: true,
|
|
|
|
|
payload: true,
|
|
|
|
|
good_snapshot: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
payments: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
amount: true,
|
|
|
|
|
payment_method: true,
|
|
|
|
|
paid_at: true,
|
|
|
|
|
created_at: true,
|
|
|
|
|
terminal_info: {
|
|
|
|
|
select: {
|
|
|
|
|
terminal_id: true,
|
|
|
|
|
stan: true,
|
|
|
|
|
rrn: true,
|
|
|
|
|
transaction_date_time: true,
|
|
|
|
|
customer_card_no: true,
|
|
|
|
|
description: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-05-03 16:23:17 +03:30
|
|
|
tsp_attempts: {
|
|
|
|
|
orderBy: {
|
|
|
|
|
created_at: 'desc',
|
|
|
|
|
},
|
2026-05-01 19:43:59 +03:30
|
|
|
select: {
|
|
|
|
|
id: true,
|
2026-05-03 16:23:17 +03:30
|
|
|
attempt_no: true,
|
|
|
|
|
status: true,
|
2026-05-05 22:42:09 +03:30
|
|
|
message: true,
|
2026-05-03 16:23:17 +03:30
|
|
|
sent_at: true,
|
|
|
|
|
received_at: true,
|
|
|
|
|
created_at: true,
|
2026-05-01 19:43:59 +03:30
|
|
|
},
|
2026-05-03 16:23:17 +03:30
|
|
|
take: 1,
|
2026-05-01 19:43:59 +03:30
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
2026-05-03 16:23:17 +03:30
|
|
|
if (invoice) {
|
|
|
|
|
const { tsp_attempts, ...rest } = invoice || {}
|
|
|
|
|
|
|
|
|
|
return ResponseMapper.single({
|
|
|
|
|
...rest,
|
2026-05-05 10:14:48 +03:30
|
|
|
status: translateEnumValue(
|
|
|
|
|
'TspProviderResponseStatus',
|
|
|
|
|
invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND,
|
|
|
|
|
),
|
2026-05-03 16:23:17 +03:30
|
|
|
})
|
|
|
|
|
} else throw new NotFoundException('فاکتور مورد نظر شما یافت نشد.')
|
2026-01-07 15:25:59 +03:30
|
|
|
}
|
|
|
|
|
|
2026-03-29 18:06:41 +03:30
|
|
|
async create(data: CreateSalesInvoiceDto, posInfo: IPosPayload) {
|
2026-05-08 18:09:13 +03:30
|
|
|
let salesInvoice = await this.sharedSaleInvoiceCreateService.create({
|
2026-05-05 22:42:09 +03:30
|
|
|
data,
|
|
|
|
|
businessId: posInfo.business_id,
|
|
|
|
|
complexId: posInfo.complex_id,
|
|
|
|
|
posId: posInfo.pos_id,
|
|
|
|
|
consumerAccountId: posInfo.consumer_account_id,
|
|
|
|
|
type: TspProviderRequestType.ORIGINAL,
|
|
|
|
|
})
|
2026-02-24 12:42:10 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
if (data.send_to_tsp) {
|
2026-05-08 18:09:13 +03:30
|
|
|
const providerResponse = await this.salesInvoiceTaxService.originalSend(
|
|
|
|
|
salesInvoice.id,
|
|
|
|
|
posInfo.pos_id,
|
|
|
|
|
)
|
|
|
|
|
if (providerResponse?.invoice) {
|
|
|
|
|
salesInvoice = providerResponse?.invoice as any
|
|
|
|
|
}
|
2026-04-30 16:27:46 +03:30
|
|
|
}
|
2026-02-24 12:42:10 +03:30
|
|
|
|
2026-05-05 22:42:09 +03:30
|
|
|
return ResponseMapper.create(salesInvoice)
|
2026-04-30 16:27:46 +03:30
|
|
|
}
|
|
|
|
|
|
2026-05-05 10:14:48 +03:30
|
|
|
async send(invoiceId: string, posInfo: IPosPayload) {
|
2026-05-10 14:14:01 +03:30
|
|
|
const invoice = await this.sharedSaleInvoiceActionsService.send(
|
|
|
|
|
posInfo.consumer_account_id,
|
2026-05-10 09:44:49 +03:30
|
|
|
posInfo.pos_id,
|
|
|
|
|
invoiceId,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return ResponseMapper.single(invoice)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async retry(invoiceId: string, posInfo: IPosPayload) {
|
2026-05-10 14:14:01 +03:30
|
|
|
const invoice = await this.sharedSaleInvoiceActionsService.retry(
|
|
|
|
|
posInfo.consumer_account_id,
|
2026-05-05 10:14:48 +03:30
|
|
|
posInfo.pos_id,
|
2026-05-05 22:42:09 +03:30
|
|
|
invoiceId,
|
2026-05-05 10:14:48 +03:30
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return ResponseMapper.single(invoice)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async revoke(invoiceId: string, posInfo: IPosPayload) {
|
2026-05-05 22:42:09 +03:30
|
|
|
const { consumer_account_id, pos_id, business_id, complex_id } = posInfo
|
|
|
|
|
|
2026-05-10 14:14:01 +03:30
|
|
|
const invoice = await this.sharedSaleInvoiceActionsService.revoke(
|
2026-05-05 22:42:09 +03:30
|
|
|
consumer_account_id,
|
|
|
|
|
pos_id,
|
|
|
|
|
complex_id,
|
|
|
|
|
business_id,
|
|
|
|
|
invoiceId,
|
|
|
|
|
)
|
2026-05-05 10:14:48 +03:30
|
|
|
|
|
|
|
|
return ResponseMapper.single(invoice)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async inquiry(consumer_account_id: string, pos_id: string, invoiceId: string) {
|
2026-05-10 14:14:01 +03:30
|
|
|
const invoice = await this.sharedSaleInvoiceActionsService.inquiry(
|
|
|
|
|
consumer_account_id,
|
|
|
|
|
pos_id,
|
|
|
|
|
invoiceId,
|
|
|
|
|
)
|
2026-05-05 10:14:48 +03:30
|
|
|
return ResponseMapper.single(invoice)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 19:43:59 +03:30
|
|
|
private buildFindAllWhere(posInfo: IPosPayload, query: SalesInvoicesFilterDto) {
|
|
|
|
|
const where: Prisma.SalesInvoiceWhereInput = {
|
|
|
|
|
pos: {
|
2026-05-05 22:42:09 +03:30
|
|
|
id: posInfo.pos_id,
|
2026-05-01 19:43:59 +03:30
|
|
|
complex: {
|
|
|
|
|
business_activity_id: posInfo.business_id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (query.code?.trim()) {
|
|
|
|
|
where.code = {
|
|
|
|
|
contains: query.code.trim(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (query.invoice_date_from || query.invoice_date_to) {
|
|
|
|
|
where.invoice_date = {
|
|
|
|
|
...(query.invoice_date_from ? { gte: new Date(query.invoice_date_from) } : {}),
|
|
|
|
|
...(query.invoice_date_to ? { lte: new Date(query.invoice_date_to) } : {}),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (query.created_at_from || query.created_at_to) {
|
|
|
|
|
where.created_at = {
|
|
|
|
|
...(query.created_at_from ? { gte: new Date(query.created_at_from) } : {}),
|
|
|
|
|
...(query.created_at_to ? { lte: new Date(query.created_at_to) } : {}),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
query.total_amount !== undefined ||
|
|
|
|
|
query.total_amount_from !== undefined ||
|
|
|
|
|
query.total_amount_to !== undefined
|
|
|
|
|
) {
|
|
|
|
|
where.total_amount = {
|
|
|
|
|
...(query.total_amount !== undefined ? { equals: query.total_amount } : {}),
|
|
|
|
|
...(query.total_amount_from !== undefined
|
|
|
|
|
? { gte: query.total_amount_from }
|
|
|
|
|
: {}),
|
|
|
|
|
...(query.total_amount_to !== undefined ? { lte: query.total_amount_to } : {}),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
query.customer_name?.trim() ||
|
|
|
|
|
query.customer_mobile?.trim() ||
|
|
|
|
|
query.customer_national_id?.trim() ||
|
|
|
|
|
query.customer_economic_code?.trim()
|
|
|
|
|
) {
|
|
|
|
|
where.customer = {
|
|
|
|
|
is: {
|
|
|
|
|
OR: [
|
|
|
|
|
...(query.customer_name?.trim()
|
|
|
|
|
? [
|
|
|
|
|
{
|
|
|
|
|
individual: {
|
|
|
|
|
is: {
|
|
|
|
|
OR: [
|
|
|
|
|
{
|
|
|
|
|
first_name: {
|
|
|
|
|
contains: query.customer_name.trim(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
last_name: {
|
|
|
|
|
contains: query.customer_name.trim(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
legal: {
|
|
|
|
|
is: {
|
|
|
|
|
name: {
|
|
|
|
|
contains: query.customer_name.trim(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
: []),
|
|
|
|
|
...(query.customer_mobile?.trim()
|
|
|
|
|
? [
|
|
|
|
|
{
|
|
|
|
|
individual: {
|
|
|
|
|
is: {
|
|
|
|
|
mobile_number: {
|
|
|
|
|
contains: query.customer_mobile.trim(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
: []),
|
|
|
|
|
...(query.customer_national_id?.trim()
|
|
|
|
|
? [
|
|
|
|
|
{
|
|
|
|
|
individual: {
|
|
|
|
|
is: {
|
|
|
|
|
national_id: {
|
|
|
|
|
contains: query.customer_national_id.trim(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
: []),
|
|
|
|
|
...(query.customer_economic_code?.trim()
|
|
|
|
|
? [
|
|
|
|
|
{
|
|
|
|
|
legal: {
|
|
|
|
|
is: {
|
|
|
|
|
OR: [
|
|
|
|
|
{
|
|
|
|
|
economic_code: {
|
|
|
|
|
contains: query.customer_economic_code.trim(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
registration_number: {
|
|
|
|
|
contains: query.customer_economic_code.trim(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
: []),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (query.status) {
|
2026-05-03 16:23:17 +03:30
|
|
|
if (query.status === TspProviderResponseStatus.NOT_SEND) {
|
|
|
|
|
where.tsp_attempts = undefined
|
2026-05-01 19:43:59 +03:30
|
|
|
} else {
|
2026-05-03 16:23:17 +03:30
|
|
|
where.tsp_attempts = {
|
|
|
|
|
some: {
|
|
|
|
|
status: query.status,
|
2026-05-01 19:43:59 +03:30
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return where
|
|
|
|
|
}
|
2026-01-07 15:25:59 +03:30
|
|
|
}
|