From 9170d8cd5af8eb3a70dda84a4c00f5773d1aedaa Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Mon, 15 Jun 2026 17:14:54 +0330 Subject: [PATCH] feat: enhance sales invoice processing with correction and return functionalities --- .../saleInvoices/sale-invoice-create.dto.ts | 2 +- .../sale-invoice-create.service.ts | 73 +++----- .../saleInvoices/saleInvoices.controller.ts | 15 ++ .../saleInvoices/saleInvoices.service.ts | 34 ++++ .../tspProviders/sales-invoice-tsp.service.ts | 160 +++++++----------- .../switch/nama/nama-provider.util.ts | 7 +- .../utils/sales-invoice-tsp.utils.ts | 2 + 7 files changed, 141 insertions(+), 152 deletions(-) diff --git a/src/common/services/saleInvoices/sale-invoice-create.dto.ts b/src/common/services/saleInvoices/sale-invoice-create.dto.ts index 0348467..23fae7d 100644 --- a/src/common/services/saleInvoices/sale-invoice-create.dto.ts +++ b/src/common/services/saleInvoices/sale-invoice-create.dto.ts @@ -83,7 +83,7 @@ export class SharedCreateTerminalPayment { @IsString() @IsNotEmpty() @ApiProperty({ required: true }) - terminalId: string + terminal_id: string @IsString() @IsNotEmpty() diff --git a/src/common/services/saleInvoices/sale-invoice-create.service.ts b/src/common/services/saleInvoices/sale-invoice-create.service.ts index ebadcfa..51ed7c9 100644 --- a/src/common/services/saleInvoices/sale-invoice-create.service.ts +++ b/src/common/services/saleInvoices/sale-invoice-create.service.ts @@ -23,6 +23,7 @@ interface TerminalPaymentInfo { interface NormalizedPayment { method: PaymentMethodType amount: number + terminalInfo?: TerminalPaymentInfo } interface CreateSharedSaleInvoiceInput { @@ -57,10 +58,7 @@ export class SharedSaleInvoiceCreateService { } = input const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date) - const { payments, terminalInfo } = this.buildPaymentsData( - data.payments, - data.total_amount, - ) + const payments = this.buildPaymentsData(data.payments, data.total_amount) for (let attempt = 1; attempt <= this.createInvoiceRetries; attempt++) { try { @@ -88,13 +86,7 @@ export class SharedSaleInvoiceCreateService { select: { ...QUERY_CONSTANTS.SALE_INVOICE.select }, }) - await this.createPayments( - $tx, - salesInvoice.id, - payments, - terminalInfo, - normalizedInvoiceDate, - ) + await this.createPayments($tx, salesInvoice.id, payments, normalizedInvoiceDate) return salesInvoice }) @@ -148,7 +140,7 @@ export class SharedSaleInvoiceCreateService { } const rawPayments = (paymentsData || {}) as Record - const terminalInfo = rawPayments.terminals as TerminalPaymentInfo | undefined + const terminalPayments = rawPayments.terminals as TerminalPaymentInfo[] | undefined const payments: NormalizedPayment[] = Object.entries(rawPayments) .filter(([key, value]) => key !== 'terminals' && value && Number(value) > 0) @@ -158,40 +150,24 @@ export class SharedSaleInvoiceCreateService { })) .filter(payment => payment.method && payment.amount > 0) as NormalizedPayment[] - const hasTerminalPayment = payments.some( - payment => payment.method === PaymentMethodType.TERMINAL, - ) - const nonTerminalTotal = payments - .filter(payment => payment.method !== PaymentMethodType.TERMINAL) - .reduce((sum, payment) => sum + payment.amount, 0) + const hasTerminalPayment = terminalPayments && terminalPayments.length - if (!hasTerminalPayment && terminalInfo) { - const terminalAmount = - typeof terminalInfo.amount === 'number' - ? terminalInfo.amount - : Math.max(0, Number(totalAmount) - nonTerminalTotal) - - if (terminalAmount > 0) { + if (hasTerminalPayment) { + for (const terminal of terminalPayments) { payments.push({ method: PaymentMethodType.TERMINAL, - amount: terminalAmount, + amount: terminal.amount, + terminalInfo: terminal, }) } } - this.validatePayments(payments, totalAmount, terminalInfo) + this.validatePayments(payments, totalAmount) - return { - payments, - terminalInfo, - } + return payments } - private validatePayments( - payments: NormalizedPayment[], - totalAmount: number, - terminalInfo?: TerminalPaymentInfo, - ) { + private validatePayments(payments: NormalizedPayment[], totalAmount: number) { const totalPayments = payments.reduce((sum, payment) => sum + payment.amount, 0) const roundedTotalPayments = Number(totalPayments.toFixed(2)) const roundedTotalAmount = Number(Number(totalAmount).toFixed(2)) @@ -199,14 +175,6 @@ export class SharedSaleInvoiceCreateService { if (roundedTotalPayments !== roundedTotalAmount) { throw new BadRequestException('مبلغ پرداختی باید برابر با مبلغ کل صورت‌حساب باشد.') } - - const terminalPayments = payments.filter( - payment => payment.method === PaymentMethodType.TERMINAL, - ) - - if (terminalPayments.length > 0 && !terminalInfo) { - throw new BadRequestException('برای پرداخت ترمینال اطلاعات ترمینال الزامی است.') - } } private async resolveCustomerId( @@ -541,7 +509,6 @@ export class SharedSaleInvoiceCreateService { tx: Prisma.TransactionClient, invoiceId: string, payments: NormalizedPayment[], - terminalInfo: TerminalPaymentInfo | undefined, paidAt: Date, ) { for (const payment of payments) { @@ -557,16 +524,18 @@ export class SharedSaleInvoiceCreateService { }, }) - if (payment.method === PaymentMethodType.TERMINAL && terminalInfo) { + if (payment.method === PaymentMethodType.TERMINAL && payment.terminalInfo) { await tx.salesInvoicePaymentTerminalInfo.create({ data: { payment_id: createdPayment.id, - terminal_id: terminalInfo.terminal_id, - stan: terminalInfo.stan, - rrn: terminalInfo.rrn, - transaction_date_time: new Date(terminalInfo.transaction_date_time || ''), - customer_card_no: terminalInfo.customer_card_no || null, - description: terminalInfo.description || null, + terminal_id: payment.terminalInfo.terminal_id, + stan: payment.terminalInfo.stan, + rrn: payment.terminalInfo.rrn, + transaction_date_time: new Date( + payment.terminalInfo.transaction_date_time || '', + ), + customer_card_no: payment.terminalInfo.customer_card_no || null, + description: payment.terminalInfo.description || null, }, }) } diff --git a/src/modules/consumer/saleInvoices/saleInvoices.controller.ts b/src/modules/consumer/saleInvoices/saleInvoices.controller.ts index e6f56ff..d6ce14e 100644 --- a/src/modules/consumer/saleInvoices/saleInvoices.controller.ts +++ b/src/modules/consumer/saleInvoices/saleInvoices.controller.ts @@ -4,6 +4,7 @@ import type { IPosPayload } from '@/common/models' import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common' import { ApiTags } from '@nestjs/swagger' import { ConsumerSaleInvoicesFilterDto } from './dto/saleInvoices-filter.dto' +import { PosCorrectionSalesInvoiceDto } from '../../pos/sales-invoices/dto/create-sales-invoice.dto' import type { SaleInvoicesServiceFindAllResponseDto, SaleInvoicesServiceFindOneResponseDto, @@ -59,4 +60,18 @@ export class StatisticsController { revoke(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) { return this.service.revoke(id, posInfo) } + + @Post(':id/correction') + correction( + @Param('id') id: string, + @PosInfo() posInfo: IPosPayload, + @Body() data: PosCorrectionSalesInvoiceDto, + ) { + return this.service.correction(id, posInfo, data) + } + + @Post(':id/return') + return(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) { + return this.service.return(id, posInfo) + } } diff --git a/src/modules/consumer/saleInvoices/saleInvoices.service.ts b/src/modules/consumer/saleInvoices/saleInvoices.service.ts index 447b1dc..70cf2bd 100644 --- a/src/modules/consumer/saleInvoices/saleInvoices.service.ts +++ b/src/modules/consumer/saleInvoices/saleInvoices.service.ts @@ -13,6 +13,7 @@ import { import { PrismaService } from '@/prisma/prisma.service' import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common' import { ResponseMapper } from 'common/response/response-mapper' +import { PosCorrectionSalesInvoiceDto } from '../../pos/sales-invoices/dto/create-sales-invoice.dto' import { SalesInvoiceTspService } from '../../tspProviders/sales-invoice-tsp.service' import { ConsumerSaleInvoicesFilterDto } from './dto/saleInvoices-filter.dto' @@ -177,6 +178,39 @@ export class SaleInvoicesService { 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, diff --git a/src/modules/tspProviders/sales-invoice-tsp.service.ts b/src/modules/tspProviders/sales-invoice-tsp.service.ts index 2e42cdc..77a3940 100644 --- a/src/modules/tspProviders/sales-invoice-tsp.service.ts +++ b/src/modules/tspProviders/sales-invoice-tsp.service.ts @@ -18,7 +18,6 @@ import { getOriginalResendAttemptNumber, getRelatedInvoiceForCorrection, onResult, - trySend, } from './utils/sales-invoice-tsp.utils' type ItemTspRow = { @@ -228,43 +227,9 @@ export class SalesInvoiceTspService { }, ) - const result = await this.runProviderCall(() => - trySend(this.tspSwitchService, correctionPayload), - ) - if (result?.hasError) { - result.provider_request_payload = - result.provider_request_payload || JSON.parse(JSON.stringify(correctionPayload)) - result.sent_at = result.sent_at || new Date().toISOString() - result.received_at = result.received_at || new Date().toISOString() - } + const result = await this.tspSwitchService.correction(correctionPayload) - return onResult(this.prisma, result, attempt.id) - - // const countByGoodId = (goodIds: string[]): Map => { - // const counts = new Map() - // 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 - // } - // } - // } + return await onResult(this.prisma, result, newInvoice.id) } async revoke( @@ -317,71 +282,72 @@ export class SalesInvoiceTspService { ) } - 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 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, + // terminal_id: 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, + // @ts-ignore + payments: undefined, settlement_type: relatedInvoice.settlement_type, items: relatedInvoice.items.map(item => ({ unit_price: Number(item.unit_price), diff --git a/src/modules/tspProviders/switch/nama/nama-provider.util.ts b/src/modules/tspProviders/switch/nama/nama-provider.util.ts index 352e457..49832c9 100644 --- a/src/modules/tspProviders/switch/nama/nama-provider.util.ts +++ b/src/modules/tspProviders/switch/nama/nama-provider.util.ts @@ -68,7 +68,7 @@ export class NamaProviderUtils { sstid: item.sku, vra: item.sku_vat, fee: String(item.unit_price), - dis: String(item.discount_amount), + dis: String(parseInt(item.discount_amount + '')), mu: item.measure_unit, am: String(item.quantity), consfee: '0', @@ -120,7 +120,7 @@ export class NamaProviderUtils { sstid: item.sku, vra: item.sku_vat, fee: String(item.unit_price), - dis: String(item.discount_amount), + dis: String(parseInt(item.discount_amount + '')), mu: item.measure_unit, am: String(item.quantity), consfee: '0', @@ -297,6 +297,9 @@ export class NamaProviderUtils { } private mapPayments(payments: PaymentInfoDto[]): NamaProviderPaymentInfoDto[] { + if (!payments) { + return [] + } return payments.map(payment => ({ pmt: this.mapPaymentMethod(payment.payment_method), pv: payment.amount, diff --git a/src/modules/tspProviders/utils/sales-invoice-tsp.utils.ts b/src/modules/tspProviders/utils/sales-invoice-tsp.utils.ts index 70d2f58..54280c6 100644 --- a/src/modules/tspProviders/utils/sales-invoice-tsp.utils.ts +++ b/src/modules/tspProviders/utils/sales-invoice-tsp.utils.ts @@ -653,6 +653,8 @@ export async function onResult( data: attemptUpdatedData, }) + console.log('attemptUpdatedData', attemptUpdatedData) + const updatedInvoice = await tx.salesInvoice.update({ where: { id: invoice_id }, data: {