Compare commits

...

2 Commits

10 changed files with 152 additions and 163 deletions
@@ -83,7 +83,7 @@ export class SharedCreateTerminalPayment {
@IsString()
@IsNotEmpty()
@ApiProperty({ required: true })
terminalId: string
terminal_id: string
@IsString()
@IsNotEmpty()
@@ -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<string, unknown>
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(
@@ -277,9 +245,7 @@ export class SharedSaleInvoiceCreateService {
}
return customerIndividualId
}
if (customer_type === CustomerType.LEGAL && customer?.customer_legal) {
} else if (customer_type === CustomerType.LEGAL && customer?.customer_legal) {
const { registration_number, economic_code, postal_code } = customer.customer_legal
const foundedCustomer = await tx.customerLegal.findFirst({
where: {
@@ -486,6 +452,8 @@ export class SharedSaleInvoiceCreateService {
id: customerId,
},
}
} else if (data.customer?.customer_unknown) {
salesInvoiceData.unknown_customer = data.customer.customer_unknown
}
if (type !== TspProviderRequestType.ORIGINAL) {
@@ -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,
},
})
}
@@ -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)
}
}
@@ -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,
@@ -10,13 +10,13 @@ export class StatisticsService {
constructor(private readonly prisma: PrismaService) {}
private readonly invoiceMapper = (invoice: any) => {
const { tsp_attempts, ...rest } = invoice || {}
const { last_tsp_status, ...rest } = invoice || {}
return {
...rest,
status: translateEnumValue(
'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 invoiceMapper = (invoice: any) => {
const { tsp_attempts, type, ...rest } = invoice || {}
const { last_tsp_status, type, ...rest } = invoice || {}
return {
...rest,
type: translateEnumValue('TspProviderRequestType', type),
status: translateEnumValue(
'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 { tsp_attempts, type, ...rest } = invoice
const { last_tsp_status, type, ...rest } = invoice
return {
...rest,
type: translateEnumValue('TspProviderRequestType', type),
status: translateEnumValue(
'TspProviderResponseStatus',
invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND,
last_tsp_status || TspProviderResponseStatus.NOT_SEND,
),
}
})
@@ -78,13 +78,13 @@ export class SalesInvoicesService {
})
if (invoice) {
const { tsp_attempts, type, ...rest } = invoice
const { last_tsp_status, type, ...rest } = invoice
const mappedInvoice = {
...rest,
type: translateEnumValue('TspProviderRequestType', type),
status: translateEnumValue(
'TspProviderResponseStatus',
invoice.tsp_attempts?.[0]?.status || TspProviderResponseStatus.NOT_SEND,
last_tsp_status || TspProviderResponseStatus.NOT_SEND,
),
settlement_type: translateEnumValue(
'InvoiceSettlementType',
@@ -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<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
// }
// }
// }
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),
@@ -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,
@@ -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: {