feat: implement inquiry, send, retry, and revoke actions for sales invoices; enhance query constants and service structure

This commit is contained in:
2026-05-10 19:56:05 +03:30
parent 9a76880b1d
commit 5e6bd33cdd
8 changed files with 175 additions and 297 deletions
-18
View File
@@ -1,18 +0,0 @@
/*
Warnings:
- Added the required column `raw_request_payload` to the `sale_invoice_tsp_attempts` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE `sale_invoice_tsp_attempts` DROP FOREIGN KEY `sale_invoice_tsp_attempts_invoice_id_fkey`;
-- DropIndex
DROP INDEX `sale_invoice_tsp_attempts_invoice_id_key` ON `sale_invoice_tsp_attempts`;
-- AlterTable
ALTER TABLE `sale_invoice_tsp_attempts` ADD COLUMN `raw_request_payload` JSON NOT NULL,
ADD COLUMN `raw_response_payload` JSON NULL;
-- AddForeignKey
ALTER TABLE `customer_legal` ADD CONSTRAINT `customer_legal_business_activity_id_fkey` FOREIGN KEY (`business_activity_id`) REFERENCES `business_activities`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
View File
+102 -29
View File
@@ -10,16 +10,37 @@ export const summarySelect: SalesInvoiceSelect = {
tax_id: true, tax_id: true,
type: true, type: true,
notes: true, notes: true,
created_at: 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,
},
},
},
},
tsp_attempts: { tsp_attempts: {
orderBy: {
created_at: 'desc',
},
take: 1,
select: { select: {
status: true, status: true,
sent_at: true, sent_at: true,
message: true, message: true,
}, },
orderBy: {
attempt_no: 'desc',
},
take: 1,
}, },
reference_invoice: { reference_invoice: {
select: { select: {
@@ -31,42 +52,94 @@ export const summarySelect: SalesInvoiceSelect = {
export const select: SalesInvoiceSelect = { export const select: SalesInvoiceSelect = {
...summarySelect, ...summarySelect,
payments: { updated_at: true,
select: {
amount: true,
paid_at: true,
payment_method: true,
terminal_info: {
select: {
customer_card_no: true,
terminal_id: true,
rrn: true,
stan: true,
transaction_date_time: true,
},
},
},
},
unknown_customer: true, unknown_customer: true,
customer: { pos: {
select: { select: {
type: true, id: true,
legal: true, name: true,
individual: 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: { items: {
select: { select: {
id: true,
good_id: true,
service_id: true,
quantity: true, quantity: true,
unit_price: true,
discount: true,
total_amount: true,
payload: true,
measure_unit_code: true, measure_unit_code: true,
measure_unit_text: true, measure_unit_text: true,
sku_code: true, sku_code: true,
sku_vat: true, unit_price: true,
discount: true,
total_amount: true,
notes: true,
payload: true,
good_snapshot: true, good_snapshot: true,
good: {
select: {
name: true,
barcode: true,
image_url: true,
category: 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,
},
},
},
},
tsp_attempts: {
orderBy: {
created_at: 'desc',
},
select: {
id: true,
attempt_no: true,
status: true,
message: true,
sent_at: true,
received_at: true,
created_at: true,
},
take: 1,
},
} }
@@ -1,4 +1,6 @@
import { PosInfo } from '@/common/decorators/posInfo.decorator'
import { TokenAccount } from '@/common/decorators/tokenInfo.decorator' import { TokenAccount } from '@/common/decorators/tokenInfo.decorator'
import type { IPosPayload } from '@/common/models'
import { Body, Controller, Get, Param, Post } from '@nestjs/common' import { Body, Controller, Get, Param, Post } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger' import { ApiTags } from '@nestjs/swagger'
import type { import type {
@@ -28,11 +30,6 @@ export class StatisticsController {
return this.service.findOne(consumerId, id) return this.service.findOne(consumerId, id)
} }
@Post(':id/send_tax')
async send(@TokenAccount('userId') consumerId: string, @Param('id') id: string) {
return this.service.send(consumerId, id)
}
@Post('send_tax_bulk') @Post('send_tax_bulk')
async sendBulk( async sendBulk(
@TokenAccount('userId') consumerId: string, @TokenAccount('userId') consumerId: string,
@@ -40,4 +37,24 @@ export class StatisticsController {
) { ) {
return this.service.sendBulk(consumerId, data.invoice_ids) return this.service.sendBulk(consumerId, data.invoice_ids)
} }
@Get(':id/inquiry')
inquiry(@PosInfo() posInfo: IPosPayload, @Param('id') id: string) {
return this.service.inquiry(posInfo.consumer_account_id, posInfo.pos_id, id)
}
@Post(':id/send')
send(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) {
return this.service.send(id, posInfo)
}
@Post(':id/retry')
retry(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) {
return this.service.retry(id, posInfo)
}
@Post(':id/revoke')
revoke(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) {
return this.service.revoke(id, posInfo)
}
} }
@@ -1,3 +1,5 @@
import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service'
import { SharedSaleInvoiceAccessService } from '@/common/services/saleInvoices/sale-invoice-access.service'
import { SaleInvoiceTspModule } from '@/modules/tspProviders/sales-invoice-tsp.module' import { SaleInvoiceTspModule } from '@/modules/tspProviders/sales-invoice-tsp.module'
import { PrismaModule } from '@/prisma/prisma.module' import { PrismaModule } from '@/prisma/prisma.module'
import { Module } from '@nestjs/common' import { Module } from '@nestjs/common'
@@ -7,7 +9,7 @@ import { SaleInvoicesService } from './saleInvoices.service'
@Module({ @Module({
imports: [PrismaModule, SaleInvoiceTspModule], imports: [PrismaModule, SaleInvoiceTspModule],
controllers: [StatisticsController], controllers: [StatisticsController],
providers: [SaleInvoicesService], providers: [SaleInvoicesService, SharedSaleInvoiceActionsService, SharedSaleInvoiceAccessService],
exports: [SaleInvoicesService], exports: [SaleInvoicesService],
}) })
export class ConsumerSaleInvoicesModule {} export class ConsumerSaleInvoicesModule {}
@@ -1,3 +1,6 @@
import { IPosPayload } from '@/common/models'
import { QUERY_CONSTANTS } from '@/common/queryConstants'
import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service'
import { translateEnumValue } from '@/common/utils' import { translateEnumValue } from '@/common/utils'
import { TspProviderResponseStatus } from '@/generated/prisma/enums' import { TspProviderResponseStatus } from '@/generated/prisma/enums'
import { import {
@@ -15,24 +18,10 @@ export class SaleInvoicesService {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private salesInvoiceTaxService: SalesInvoiceTspService, private salesInvoiceTaxService: SalesInvoiceTspService,
private sharedSaleInvoiceActionsService: SharedSaleInvoiceActionsService,
) {} ) {}
private readonly defaultSelect: SalesInvoiceSelect = { private readonly defaultSelect: SalesInvoiceSelect = {
id: true,
code: true,
total_amount: true,
invoice_date: true,
created_at: true,
consumer_account: {
select: {
role: true,
account: {
select: {
username: true,
},
},
},
},
pos: { pos: {
select: { select: {
id: true, id: true,
@@ -51,17 +40,6 @@ export class SaleInvoicesService {
}, },
}, },
}, },
tsp_attempts: {
orderBy: {
created_at: 'asc',
},
take: 1,
select: {
id: true,
status: true,
},
},
} }
private readonly invoiceMapper = (invoice: any) => { private readonly invoiceMapper = (invoice: any) => {
@@ -85,7 +63,10 @@ export class SaleInvoicesService {
const [invoices, total] = await this.prisma.$transaction(async tx => [ const [invoices, total] = await this.prisma.$transaction(async tx => [
await tx.salesInvoice.findMany({ await tx.salesInvoice.findMany({
where: invoicesWhere, where: invoicesWhere,
select: this.defaultSelect, select: {
...QUERY_CONSTANTS.SALE_INVOICE.summarySelect,
...this.defaultSelect,
},
skip: (page - 1) * perPage, skip: (page - 1) * perPage,
take: perPage, take: perPage,
}), }),
@@ -106,71 +87,8 @@ export class SaleInvoicesService {
const invoice = await this.prisma.salesInvoice.findUniqueOrThrow({ const invoice = await this.prisma.salesInvoice.findUniqueOrThrow({
where: invoicesWhere, where: invoicesWhere,
select: { select: {
...QUERY_CONSTANTS.SALE_INVOICE.select,
...this.defaultSelect, ...this.defaultSelect,
notes: true,
created_at: true,
payments: {
select: {
amount: true,
payment_method: true,
},
},
items: {
select: {
id: true,
notes: true,
unit_price: true,
discount: true,
quantity: true,
total_amount: true,
payload: true,
good: {
select: {
id: true,
name: true,
pricing_model: true,
measure_unit: {
select: {
id: true,
name: true,
},
},
category: {
select: {
id: true,
name: true,
image_url: true,
},
},
},
},
},
},
customer: {
select: {
id: true,
type: true,
legal: {
select: {
name: true,
economic_code: true,
registration_number: true,
postal_code: true,
},
},
individual: {
select: {
first_name: true,
last_name: true,
economic_code: true,
national_id: true,
postal_code: true,
},
},
},
},
unknown_customer: true,
}, },
}) })
@@ -180,15 +98,47 @@ export class SaleInvoicesService {
throw new NotFoundException('فاکتور مورد نظر شما یافت نشد.') throw new NotFoundException('فاکتور مورد نظر شما یافت نشد.')
} }
async send(consumer_id: string, invoice_id: string) { async send(invoiceId: string, posInfo: IPosPayload) {
const tspProviderResult = await this.salesInvoiceTaxService.originalSend( const invoice = await this.sharedSaleInvoiceActionsService.send(
consumer_id, posInfo.consumer_account_id,
invoice_id, posInfo.pos_id,
invoiceId,
) )
return ResponseMapper.single({ return ResponseMapper.single(invoice)
...tspProviderResult, }
})
async retry(invoiceId: string, posInfo: IPosPayload) {
const invoice = await this.sharedSaleInvoiceActionsService.retry(
posInfo.consumer_account_id,
posInfo.pos_id,
invoiceId,
)
return ResponseMapper.single(invoice)
}
async revoke(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,
pos_id,
invoiceId,
)
return ResponseMapper.single(invoice)
} }
async sendBulk(consumer_id: string, invoiceIds: string[]) { async sendBulk(consumer_id: string, invoiceIds: string[]) {
@@ -1,6 +1,7 @@
import { IPosPayload } from '@/common/models/posPayload.model' import { IPosPayload } from '@/common/models/posPayload.model'
import { SharedSaleInvoiceCreateService } from '@/common/services/saleInvoices/sale-invoice-create.service' import { QUERY_CONSTANTS } from '@/common/queryConstants'
import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service' import { SharedSaleInvoiceActionsService } from '@/common/services/saleInvoices/sale-invoice-actions.service'
import { SharedSaleInvoiceCreateService } from '@/common/services/saleInvoices/sale-invoice-create.service'
import { translateEnumValue } from '@/common/utils' import { translateEnumValue } from '@/common/utils'
import { PrismaService } from '@/prisma/prisma.service' import { PrismaService } from '@/prisma/prisma.service'
import { Injectable, NotFoundException } from '@nestjs/common' import { Injectable, NotFoundException } from '@nestjs/common'
@@ -34,41 +35,7 @@ export class SalesInvoicesService {
skip: (page - 1) * perPage, skip: (page - 1) * perPage,
take: perPage, take: perPage,
select: { select: {
id: true, ...QUERY_CONSTANTS.SALE_INVOICE.summarySelect,
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,
},
},
},
},
tsp_attempts: {
orderBy: {
created_at: 'desc',
},
take: 1,
select: {
status: true,
},
},
}, },
}), }),
await tx.salesInvoice.count({ where }), await tx.salesInvoice.count({ where }),
@@ -97,120 +64,7 @@ export class SalesInvoicesService {
}, },
}, },
select: { select: {
id: true, ...QUERY_CONSTANTS.SALE_INVOICE.select,
code: true,
invoice_number: true,
invoice_date: true,
created_at: true,
updated_at: true,
notes: true,
total_amount: true,
unknown_customer: true,
tax_id: true,
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,
},
},
},
},
tsp_attempts: {
orderBy: {
created_at: 'desc',
},
select: {
id: true,
attempt_no: true,
status: true,
message: true,
sent_at: true,
received_at: true,
created_at: true,
},
take: 1,
},
}, },
}) })
+1 -1
View File
@@ -25,7 +25,7 @@ export function getPrismaOptions(): any {
} else { } else {
options.log = [ options.log = [
{ emit: 'stdout', level: 'query' }, { emit: 'stdout', level: 'query' },
{ emit: 'stdout', level: 'info' }, // { emit: 'stdout', level: 'info' },
{ emit: 'stdout', level: 'warn' }, { emit: 'stdout', level: 'warn' },
{ emit: 'stdout', level: 'error' }, { emit: 'stdout', level: 'error' },
] ]