Compare commits

...

2 Commits

10 changed files with 152 additions and 163 deletions
@@ -83,7 +83,7 @@ export class SharedCreateTerminalPayment {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@ApiProperty({ required: true }) @ApiProperty({ required: true })
terminalId: string terminal_id: string
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@@ -23,6 +23,7 @@ interface TerminalPaymentInfo {
interface NormalizedPayment { interface NormalizedPayment {
method: PaymentMethodType method: PaymentMethodType
amount: number amount: number
terminalInfo?: TerminalPaymentInfo
} }
interface CreateSharedSaleInvoiceInput { interface CreateSharedSaleInvoiceInput {
@@ -57,10 +58,7 @@ export class SharedSaleInvoiceCreateService {
} = input } = input
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date) const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
const { payments, terminalInfo } = this.buildPaymentsData( const payments = this.buildPaymentsData(data.payments, data.total_amount)
data.payments,
data.total_amount,
)
for (let attempt = 1; attempt <= this.createInvoiceRetries; attempt++) { for (let attempt = 1; attempt <= this.createInvoiceRetries; attempt++) {
try { try {
@@ -88,13 +86,7 @@ export class SharedSaleInvoiceCreateService {
select: { ...QUERY_CONSTANTS.SALE_INVOICE.select }, select: { ...QUERY_CONSTANTS.SALE_INVOICE.select },
}) })
await this.createPayments( await this.createPayments($tx, salesInvoice.id, payments, normalizedInvoiceDate)
$tx,
salesInvoice.id,
payments,
terminalInfo,
normalizedInvoiceDate,
)
return salesInvoice return salesInvoice
}) })
@@ -148,7 +140,7 @@ export class SharedSaleInvoiceCreateService {
} }
const rawPayments = (paymentsData || {}) as Record<string, unknown> const rawPayments = (paymentsData || {}) as Record<string, unknown>
const terminalInfo = rawPayments.terminals as TerminalPaymentInfo | undefined const terminalPayments = rawPayments.terminals as TerminalPaymentInfo[] | undefined
const payments: NormalizedPayment[] = Object.entries(rawPayments) const payments: NormalizedPayment[] = Object.entries(rawPayments)
.filter(([key, value]) => key !== 'terminals' && value && Number(value) > 0) .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[] .filter(payment => payment.method && payment.amount > 0) as NormalizedPayment[]
const hasTerminalPayment = payments.some( const hasTerminalPayment = terminalPayments && terminalPayments.length
payment => payment.method === PaymentMethodType.TERMINAL,
)
const nonTerminalTotal = payments
.filter(payment => payment.method !== PaymentMethodType.TERMINAL)
.reduce((sum, payment) => sum + payment.amount, 0)
if (!hasTerminalPayment && terminalInfo) { if (hasTerminalPayment) {
const terminalAmount = for (const terminal of terminalPayments) {
typeof terminalInfo.amount === 'number'
? terminalInfo.amount
: Math.max(0, Number(totalAmount) - nonTerminalTotal)
if (terminalAmount > 0) {
payments.push({ payments.push({
method: PaymentMethodType.TERMINAL, method: PaymentMethodType.TERMINAL,
amount: terminalAmount, amount: terminal.amount,
terminalInfo: terminal,
}) })
} }
} }
this.validatePayments(payments, totalAmount, terminalInfo) this.validatePayments(payments, totalAmount)
return { return payments
payments,
terminalInfo,
}
} }
private validatePayments( private validatePayments(payments: NormalizedPayment[], totalAmount: number) {
payments: NormalizedPayment[],
totalAmount: number,
terminalInfo?: TerminalPaymentInfo,
) {
const totalPayments = payments.reduce((sum, payment) => sum + payment.amount, 0) const totalPayments = payments.reduce((sum, payment) => sum + payment.amount, 0)
const roundedTotalPayments = Number(totalPayments.toFixed(2)) const roundedTotalPayments = Number(totalPayments.toFixed(2))
const roundedTotalAmount = Number(Number(totalAmount).toFixed(2)) const roundedTotalAmount = Number(Number(totalAmount).toFixed(2))
@@ -199,14 +175,6 @@ export class SharedSaleInvoiceCreateService {
if (roundedTotalPayments !== roundedTotalAmount) { if (roundedTotalPayments !== roundedTotalAmount) {
throw new BadRequestException('مبلغ پرداختی باید برابر با مبلغ کل صورت‌حساب باشد.') throw new BadRequestException('مبلغ پرداختی باید برابر با مبلغ کل صورت‌حساب باشد.')
} }
const terminalPayments = payments.filter(
payment => payment.method === PaymentMethodType.TERMINAL,
)
if (terminalPayments.length > 0 && !terminalInfo) {
throw new BadRequestException('برای پرداخت ترمینال اطلاعات ترمینال الزامی است.')
}
} }
private async resolveCustomerId( private async resolveCustomerId(
@@ -277,9 +245,7 @@ export class SharedSaleInvoiceCreateService {
} }
return customerIndividualId return customerIndividualId
} } else if (customer_type === CustomerType.LEGAL && customer?.customer_legal) {
if (customer_type === CustomerType.LEGAL && customer?.customer_legal) {
const { registration_number, economic_code, postal_code } = customer.customer_legal const { registration_number, economic_code, postal_code } = customer.customer_legal
const foundedCustomer = await tx.customerLegal.findFirst({ const foundedCustomer = await tx.customerLegal.findFirst({
where: { where: {
@@ -486,6 +452,8 @@ export class SharedSaleInvoiceCreateService {
id: customerId, id: customerId,
}, },
} }
} else if (data.customer?.customer_unknown) {
salesInvoiceData.unknown_customer = data.customer.customer_unknown
} }
if (type !== TspProviderRequestType.ORIGINAL) { if (type !== TspProviderRequestType.ORIGINAL) {
@@ -541,7 +509,6 @@ export class SharedSaleInvoiceCreateService {
tx: Prisma.TransactionClient, tx: Prisma.TransactionClient,
invoiceId: string, invoiceId: string,
payments: NormalizedPayment[], payments: NormalizedPayment[],
terminalInfo: TerminalPaymentInfo | undefined,
paidAt: Date, paidAt: Date,
) { ) {
for (const payment of payments) { 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({ await tx.salesInvoicePaymentTerminalInfo.create({
data: { data: {
payment_id: createdPayment.id, payment_id: createdPayment.id,
terminal_id: terminalInfo.terminal_id, terminal_id: payment.terminalInfo.terminal_id,
stan: terminalInfo.stan, stan: payment.terminalInfo.stan,
rrn: terminalInfo.rrn, rrn: payment.terminalInfo.rrn,
transaction_date_time: new Date(terminalInfo.transaction_date_time || ''), transaction_date_time: new Date(
customer_card_no: terminalInfo.customer_card_no || null, payment.terminalInfo.transaction_date_time || '',
description: terminalInfo.description || null, ),
customer_card_no: payment.terminalInfo.customer_card_no || null,
description: payment.terminalInfo.description || null,
}, },
}) })
} }
@@ -4,6 +4,7 @@ import type { IPosPayload } from '@/common/models'
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common' import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger' import { ApiTags } from '@nestjs/swagger'
import { ConsumerSaleInvoicesFilterDto } from './dto/saleInvoices-filter.dto' import { ConsumerSaleInvoicesFilterDto } from './dto/saleInvoices-filter.dto'
import { PosCorrectionSalesInvoiceDto } from '../../pos/sales-invoices/dto/create-sales-invoice.dto'
import type { import type {
SaleInvoicesServiceFindAllResponseDto, SaleInvoicesServiceFindAllResponseDto,
SaleInvoicesServiceFindOneResponseDto, SaleInvoicesServiceFindOneResponseDto,
@@ -59,4 +60,18 @@ export class StatisticsController {
revoke(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) { revoke(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) {
return this.service.revoke(id, posInfo) 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)
}
} }
@@ -13,6 +13,7 @@ import {
import { PrismaService } from '@/prisma/prisma.service' import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common' import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper' 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 { SalesInvoiceTspService } from '../../tspProviders/sales-invoice-tsp.service'
import { ConsumerSaleInvoicesFilterDto } from './dto/saleInvoices-filter.dto' import { ConsumerSaleInvoicesFilterDto } from './dto/saleInvoices-filter.dto'
@@ -177,6 +178,39 @@ export class SaleInvoicesService {
return ResponseMapper.single(invoice) 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) { async inquiry(consumer_account_id: string, pos_id: string, invoiceId: string) {
const invoice = await this.sharedSaleInvoiceActionsService.inquiry( const invoice = await this.sharedSaleInvoiceActionsService.inquiry(
consumer_account_id, consumer_account_id,
@@ -10,13 +10,13 @@ export class StatisticsService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
private readonly invoiceMapper = (invoice: any) => { private readonly invoiceMapper = (invoice: any) => {
const { tsp_attempts, ...rest } = invoice || {} const { last_tsp_status, ...rest } = invoice || {}
return { return {
...rest, ...rest,
status: translateEnumValue( status: translateEnumValue(
'TspProviderResponseStatus', 'TspProviderResponseStatus',
invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND, last_tsp_status || TspProviderResponseStatus.NOT_SEND,
), ),
} }
} }
+2 -2
View File
@@ -115,14 +115,14 @@ export class InvoicesService {
private readonly where = () => ({}) private readonly where = () => ({})
private readonly invoiceMapper = (invoice: any) => { private readonly invoiceMapper = (invoice: any) => {
const { tsp_attempts, type, ...rest } = invoice || {} const { last_tsp_status, type, ...rest } = invoice || {}
return { return {
...rest, ...rest,
type: translateEnumValue('TspProviderRequestType', type), type: translateEnumValue('TspProviderRequestType', type),
status: translateEnumValue( status: translateEnumValue(
'TspProviderResponseStatus', 'TspProviderResponseStatus',
invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND, last_tsp_status || TspProviderResponseStatus.NOT_SEND,
), ),
} }
} }
@@ -47,13 +47,13 @@ export class SalesInvoicesService {
]) ])
const summaryItems = items.map(invoice => { const summaryItems = items.map(invoice => {
const { tsp_attempts, type, ...rest } = invoice const { last_tsp_status, type, ...rest } = invoice
return { return {
...rest, ...rest,
type: translateEnumValue('TspProviderRequestType', type), type: translateEnumValue('TspProviderRequestType', type),
status: translateEnumValue( status: translateEnumValue(
'TspProviderResponseStatus', 'TspProviderResponseStatus',
invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND, last_tsp_status || TspProviderResponseStatus.NOT_SEND,
), ),
} }
}) })
@@ -78,13 +78,13 @@ export class SalesInvoicesService {
}) })
if (invoice) { if (invoice) {
const { tsp_attempts, type, ...rest } = invoice const { last_tsp_status, type, ...rest } = invoice
const mappedInvoice = { const mappedInvoice = {
...rest, ...rest,
type: translateEnumValue('TspProviderRequestType', type), type: translateEnumValue('TspProviderRequestType', type),
status: translateEnumValue( status: translateEnumValue(
'TspProviderResponseStatus', 'TspProviderResponseStatus',
invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND, last_tsp_status || TspProviderResponseStatus.NOT_SEND,
), ),
settlement_type: translateEnumValue( settlement_type: translateEnumValue(
'InvoiceSettlementType', 'InvoiceSettlementType',
@@ -18,7 +18,6 @@ import {
getOriginalResendAttemptNumber, getOriginalResendAttemptNumber,
getRelatedInvoiceForCorrection, getRelatedInvoiceForCorrection,
onResult, onResult,
trySend,
} from './utils/sales-invoice-tsp.utils' } from './utils/sales-invoice-tsp.utils'
type ItemTspRow = { type ItemTspRow = {
@@ -228,43 +227,9 @@ export class SalesInvoiceTspService {
}, },
) )
const result = await this.runProviderCall(() => const result = await this.tspSwitchService.correction(correctionPayload)
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()
}
return onResult(this.prisma, result, attempt.id) return await onResult(this.prisma, result, newInvoice.id)
// 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
// }
// }
// }
} }
async revoke( async revoke(
@@ -317,71 +282,72 @@ export class SalesInvoiceTspService {
) )
} }
const payments = relatedInvoice.payments.reduce( // const payments = relatedInvoice.payments.reduce(
(acc, payment) => { // (acc, payment) => {
const amount = Number(payment.amount) // const amount = Number(payment.amount)
switch (payment.payment_method) { // switch (payment.payment_method) {
case 'CASH': // case 'CASH':
acc.cash = (acc.cash || 0) + amount // acc.cash = (acc.cash || 0) + amount
break // break
case 'SET_OFF': // case 'SET_OFF':
acc.set_off = (acc.set_off || 0) + amount // acc.set_off = (acc.set_off || 0) + amount
break // break
case 'CARD': // case 'CARD':
acc.card = (acc.card || 0) + amount // acc.card = (acc.card || 0) + amount
break // break
case 'BANK': // case 'BANK':
acc.bank = (acc.bank || 0) + amount // acc.bank = (acc.bank || 0) + amount
break // break
case 'CHEQUE': // case 'CHEQUE':
acc.check = (acc.check || 0) + amount // acc.check = (acc.check || 0) + amount
break // break
case 'OTHER': // case 'OTHER':
acc.other = (acc.other || 0) + amount // acc.other = (acc.other || 0) + amount
break // break
case 'TERMINAL': // case 'TERMINAL':
acc.terminals = payment.terminal_info // acc.terminals = payment.terminal_info
? { // ? {
amount, // amount,
terminalId: payment.terminal_info.terminal_id, // terminal_id: payment.terminal_info.terminal_id,
stan: payment.terminal_info.stan, // stan: payment.terminal_info.stan,
rrn: payment.terminal_info.rrn, // rrn: payment.terminal_info.rrn,
customer_card_no: // customer_card_no:
payment.terminal_info.customer_card_no || undefined, // payment.terminal_info.customer_card_no || undefined,
transaction_date_time: payment.terminal_info.transaction_date_time, // transaction_date_time: payment.terminal_info.transaction_date_time,
description: payment.terminal_info.description || undefined, // description: payment.terminal_info.description || undefined,
} // }
: acc.terminals // : acc.terminals
break // break
default: // default:
break // break
} // }
return acc // return acc
}, // },
{} as { // {} as {
terminals?: { // terminals?: {
amount?: number // amount?: number
terminalId: string // terminalId: string
stan: string // stan: string
rrn: string // rrn: string
customer_card_no?: string // customer_card_no?: string
transaction_date_time: Date // transaction_date_time: Date
description?: string // description?: string
} // }
cash?: number // cash?: number
set_off?: number // set_off?: number
card?: number // card?: number
bank?: number // bank?: number
check?: number // check?: number
other?: number // other?: number
}, // },
) // )
const newInvoice = await this.sharedSaleInvoiceCreateService.create({ const newInvoice = await this.sharedSaleInvoiceCreateService.create({
data: { data: {
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN, customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
invoice_date: new Date(), invoice_date: new Date(),
payments, // @ts-ignore
payments: undefined,
settlement_type: relatedInvoice.settlement_type, settlement_type: relatedInvoice.settlement_type,
items: relatedInvoice.items.map(item => ({ items: relatedInvoice.items.map(item => ({
unit_price: Number(item.unit_price), unit_price: Number(item.unit_price),
@@ -68,7 +68,7 @@ export class NamaProviderUtils {
sstid: item.sku, sstid: item.sku,
vra: item.sku_vat, vra: item.sku_vat,
fee: String(item.unit_price), fee: String(item.unit_price),
dis: String(item.discount_amount), dis: String(parseInt(item.discount_amount + '')),
mu: item.measure_unit, mu: item.measure_unit,
am: String(item.quantity), am: String(item.quantity),
consfee: '0', consfee: '0',
@@ -120,7 +120,7 @@ export class NamaProviderUtils {
sstid: item.sku, sstid: item.sku,
vra: item.sku_vat, vra: item.sku_vat,
fee: String(item.unit_price), fee: String(item.unit_price),
dis: String(item.discount_amount), dis: String(parseInt(item.discount_amount + '')),
mu: item.measure_unit, mu: item.measure_unit,
am: String(item.quantity), am: String(item.quantity),
consfee: '0', consfee: '0',
@@ -297,6 +297,9 @@ export class NamaProviderUtils {
} }
private mapPayments(payments: PaymentInfoDto[]): NamaProviderPaymentInfoDto[] { private mapPayments(payments: PaymentInfoDto[]): NamaProviderPaymentInfoDto[] {
if (!payments) {
return []
}
return payments.map(payment => ({ return payments.map(payment => ({
pmt: this.mapPaymentMethod(payment.payment_method), pmt: this.mapPaymentMethod(payment.payment_method),
pv: payment.amount, pv: payment.amount,
@@ -653,6 +653,8 @@ export async function onResult(
data: attemptUpdatedData, data: attemptUpdatedData,
}) })
console.log('attemptUpdatedData', attemptUpdatedData)
const updatedInvoice = await tx.salesInvoice.update({ const updatedInvoice = await tx.salesInvoice.update({
where: { id: invoice_id }, where: { id: invoice_id },
data: { data: {