Compare commits
8 Commits
d51b677f26
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 636daca703 | |||
| 9bf294a1f3 | |||
| 839f6de691 | |||
| 826041b07a | |||
| f87e5b9d8e | |||
| 652177862d | |||
| ac2e7f5dab | |||
| f94a108948 |
+4
-1
@@ -1,7 +1,10 @@
|
|||||||
import { PasswordUtil } from '@/common/utils/password.util'
|
import { PasswordUtil } from '@/common/utils/password.util'
|
||||||
import { GoodPricingModel } from '@/generated/prisma/enums'
|
import { GoodPricingModel } from '@/generated/prisma/enums'
|
||||||
import { GoodCreateManyInput } from '@/generated/prisma/models'
|
import { GoodCreateManyInput } from '@/generated/prisma/models'
|
||||||
import { prisma } from '../src/lib/prisma'
|
import { PrismaClient } from '../src/generated/prisma/client'
|
||||||
|
import { prismaAdapter } from '../src/lib/prisma'
|
||||||
|
|
||||||
|
const prisma = new PrismaClient({ adapter: prismaAdapter })
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const password = await PasswordUtil.hash('123456')
|
const password = await PasswordUtil.hash('123456')
|
||||||
|
|||||||
@@ -1,66 +1,65 @@
|
|||||||
#!/usr/bin/env ts-node
|
// #!/usr/bin/env ts-node
|
||||||
import * as fs from 'fs'
|
// import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
// import * as path from 'path'
|
||||||
import { prisma } from '../src/lib/prisma'
|
|
||||||
|
|
||||||
function findModules(dir: string): string[] {
|
// function findModules(dir: string): string[] {
|
||||||
const results: string[] = []
|
// const results: string[] = []
|
||||||
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
// const entries = fs.readdirSync(dir, { withFileTypes: true })
|
||||||
for (const e of entries) {
|
// for (const e of entries) {
|
||||||
const full = path.join(dir, e.name)
|
// const full = path.join(dir, e.name)
|
||||||
if (e.isDirectory()) {
|
// if (e.isDirectory()) {
|
||||||
results.push(...findModules(full))
|
// results.push(...findModules(full))
|
||||||
} else if (e.isFile() && e.name.endsWith('.module.ts')) {
|
// } else if (e.isFile() && e.name.endsWith('.module.ts')) {
|
||||||
results.push(full)
|
// results.push(full)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return results
|
// return results
|
||||||
}
|
// }
|
||||||
|
|
||||||
function moduleNameFromFile(filePath: string) {
|
// function moduleNameFromFile(filePath: string) {
|
||||||
const name = path.basename(filePath).replace('.module.ts', '')
|
// const name = path.basename(filePath).replace('.module.ts', '')
|
||||||
return name.replace(/-module$|\.module$/i, '')
|
// return name.replace(/-module$|\.module$/i, '')
|
||||||
}
|
// }
|
||||||
|
|
||||||
function makePermissionsFor(moduleName: string) {
|
// function makePermissionsFor(moduleName: string) {
|
||||||
const base = moduleName.replace(/\W+/g, '_').toLowerCase()
|
// const base = moduleName.replace(/\W+/g, '_').toLowerCase()
|
||||||
return [`${base}:create`, `${base}:read`, `${base}:update`, `${base}:delete`]
|
// return [`${base}:create`, `${base}:read`, `${base}:update`, `${base}:delete`]
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function main() {
|
// async function main() {
|
||||||
// const srcDir = path.resolve(__dirname, '..', 'src')
|
// // const srcDir = path.resolve(__dirname, '..', 'src')
|
||||||
// const moduleFiles = findModules(srcDir)
|
// // const moduleFiles = findModules(srcDir)
|
||||||
// const modules = moduleFiles.map(moduleNameFromFile)
|
// // const modules = moduleFiles.map(moduleNameFromFile)
|
||||||
|
|
||||||
// const permsMap: Record<string, boolean> = {}
|
// // const permsMap: Record<string, boolean> = {}
|
||||||
// for (const m of modules) {
|
// // for (const m of modules) {
|
||||||
// for (const p of makePermissionsFor(m)) permsMap[p] = false
|
// // for (const p of makePermissionsFor(m)) permsMap[p] = false
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
// // Upsert roles: ensure admin has full permissions, others get entries added
|
// // // Upsert roles: ensure admin has full permissions, others get entries added
|
||||||
// const roles = await prisma.role.findMany()
|
// // const roles = await prisma.role.findMany()
|
||||||
// for (const r of roles) {
|
// // for (const r of roles) {
|
||||||
// const current: Record<string, any> = (r.permissions as any) || {}
|
// // const current: Record<string, any> = (r.permissions as any) || {}
|
||||||
// const merged = { ...permsMap, ...current }
|
// // const merged = { ...permsMap, ...current }
|
||||||
|
|
||||||
// // If role name is admin (case-insensitive), set all permissions to true
|
// // // If role name is admin (case-insensitive), set all permissions to true
|
||||||
// if (r.name && r.name.toLowerCase() === 'admin') {
|
// // if (r.name && r.name.toLowerCase() === 'admin') {
|
||||||
// for (const key of Object.keys(merged)) merged[key] = true
|
// // for (const key of Object.keys(merged)) merged[key] = true
|
||||||
// } else {
|
// // } else {
|
||||||
// // keep existing truthy values, otherwise false
|
// // // keep existing truthy values, otherwise false
|
||||||
// for (const key of Object.keys(merged)) merged[key] = merged[key] || false
|
// // for (const key of Object.keys(merged)) merged[key] = merged[key] || false
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
// await prisma.role.update({ where: { id: r.id }, data: { permissions: merged } })
|
// // await prisma.role.update({ where: { id: r.id }, data: { permissions: merged } })
|
||||||
// console.log(
|
// // console.log(
|
||||||
// `Updated role ${r.name} (id=${r.id}) with ${Object.keys(merged).length} permissions`,
|
// // `Updated role ${r.name} (id=${r.id}) with ${Object.keys(merged).length} permissions`,
|
||||||
// )
|
// // )
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
await prisma.$disconnect()
|
// await prisma.$disconnect()
|
||||||
}
|
// }
|
||||||
|
|
||||||
main().catch(e => {
|
// main().catch(e => {
|
||||||
console.error(e)
|
// console.error(e)
|
||||||
process.exit(1)
|
// process.exit(1)
|
||||||
})
|
// })
|
||||||
|
|||||||
+121
-122
@@ -1,144 +1,143 @@
|
|||||||
import fs from 'node:fs'
|
// import fs from 'node:fs'
|
||||||
import path from 'node:path'
|
// import path from 'node:path'
|
||||||
import { prisma } from '../src/lib/prisma'
|
|
||||||
|
|
||||||
type CsvRow = {
|
// type CsvRow = {
|
||||||
ID?: string
|
// ID?: string
|
||||||
DescriptionOfID?: string
|
// DescriptionOfID?: string
|
||||||
Vat?: string
|
// Vat?: string
|
||||||
Type?: string
|
// Type?: string
|
||||||
}
|
// }
|
||||||
|
|
||||||
function parseCsvLine(line: string): string[] {
|
// function parseCsvLine(line: string): string[] {
|
||||||
const result: string[] = []
|
// const result: string[] = []
|
||||||
let current = ''
|
// let current = ''
|
||||||
let inQuotes = false
|
// let inQuotes = false
|
||||||
|
|
||||||
for (let index = 0; index < line.length; index++) {
|
// for (let index = 0; index < line.length; index++) {
|
||||||
const char = line[index]
|
// const char = line[index]
|
||||||
|
|
||||||
if (char === '"') {
|
// if (char === '"') {
|
||||||
if (inQuotes && line[index + 1] === '"') {
|
// if (inQuotes && line[index + 1] === '"') {
|
||||||
current += '"'
|
// current += '"'
|
||||||
index++
|
// index++
|
||||||
} else {
|
// } else {
|
||||||
inQuotes = !inQuotes
|
// inQuotes = !inQuotes
|
||||||
}
|
// }
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (char === ',' && !inQuotes) {
|
// if (char === ',' && !inQuotes) {
|
||||||
result.push(current)
|
// result.push(current)
|
||||||
current = ''
|
// current = ''
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
current += char
|
// current += char
|
||||||
}
|
// }
|
||||||
|
|
||||||
result.push(current)
|
// result.push(current)
|
||||||
return result.map(value => value.trim())
|
// return result.map(value => value.trim())
|
||||||
}
|
// }
|
||||||
|
|
||||||
function parseCsv(content: string): CsvRow[] {
|
// function parseCsv(content: string): CsvRow[] {
|
||||||
const lines = content
|
// const lines = content
|
||||||
.split(/\r?\n/)
|
// .split(/\r?\n/)
|
||||||
.map(line => line.trim())
|
// .map(line => line.trim())
|
||||||
.filter(Boolean)
|
// .filter(Boolean)
|
||||||
|
|
||||||
if (!lines.length) return []
|
// if (!lines.length) return []
|
||||||
|
|
||||||
const headers = parseCsvLine(lines[0])
|
// const headers = parseCsvLine(lines[0])
|
||||||
return lines.slice(1).map(line => {
|
// return lines.slice(1).map(line => {
|
||||||
const cols = parseCsvLine(line)
|
// const cols = parseCsvLine(line)
|
||||||
const row: Record<string, string> = {}
|
// const row: Record<string, string> = {}
|
||||||
for (let index = 0; index < headers.length; index++) {
|
// for (let index = 0; index < headers.length; index++) {
|
||||||
row[headers[index]] = cols[index] || ''
|
// row[headers[index]] = cols[index] || ''
|
||||||
}
|
// }
|
||||||
return row
|
// return row
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
function toBooleanFlags(typeValue: string) {
|
// function toBooleanFlags(typeValue: string) {
|
||||||
const normalized = typeValue || ''
|
// const normalized = typeValue || ''
|
||||||
const isPublic = normalized.includes('شناسه عمومی')
|
// const isPublic = normalized.includes('شناسه عمومی')
|
||||||
const isImported = normalized.includes('وارداتی')
|
// const isImported = normalized.includes('وارداتی')
|
||||||
const isDomestic = !isImported
|
// const isDomestic = !isImported
|
||||||
|
|
||||||
return { isPublic, isDomestic }
|
// return { isPublic, isDomestic }
|
||||||
}
|
// }
|
||||||
|
|
||||||
function parseVat(vatValue: string) {
|
// function parseVat(vatValue: string) {
|
||||||
const parsed = Number(vatValue || '0')
|
// const parsed = Number(vatValue || '0')
|
||||||
if (!Number.isFinite(parsed)) return 0
|
// if (!Number.isFinite(parsed)) return 0
|
||||||
return parsed
|
// return parsed
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function main() {
|
// async function main() {
|
||||||
const defaultPath =
|
// const defaultPath =
|
||||||
'/Users/ahasani/Desktop/product_good_2026-05-01T11-35-57_part_1_28812448-0dd9-411c-99e1-334c28d781fe.csv'
|
// '/Users/ahasani/Desktop/product_good_2026-05-01T11-35-57_part_1_28812448-0dd9-411c-99e1-334c28d781fe.csv'
|
||||||
const csvPath = process.argv[2] || defaultPath
|
// const csvPath = process.argv[2] || defaultPath
|
||||||
const absolutePath = path.resolve(csvPath)
|
// const absolutePath = path.resolve(csvPath)
|
||||||
|
|
||||||
if (!fs.existsSync(absolutePath)) {
|
// if (!fs.existsSync(absolutePath)) {
|
||||||
throw new Error(`CSV file not found: ${absolutePath}`)
|
// throw new Error(`CSV file not found: ${absolutePath}`)
|
||||||
}
|
// }
|
||||||
|
|
||||||
const raw = fs.readFileSync(absolutePath, 'utf8').replace(/^\uFEFF/, '')
|
// const raw = fs.readFileSync(absolutePath, 'utf8').replace(/^\uFEFF/, '')
|
||||||
const rows = parseCsv(raw)
|
// const rows = parseCsv(raw)
|
||||||
|
|
||||||
let upserted = 0
|
// let upserted = 0
|
||||||
let skipped = 0
|
// let skipped = 0
|
||||||
|
|
||||||
const guild = await prisma.guild.findFirst({})
|
// const guild = await prisma.guild.findFirst({})
|
||||||
|
|
||||||
for (const row of rows) {
|
// for (const row of rows) {
|
||||||
const code = (row.ID || '').trim()
|
// const code = (row.ID || '').trim()
|
||||||
const name = (row.DescriptionOfID || '').trim()
|
// const name = (row.DescriptionOfID || '').trim()
|
||||||
const vat = parseVat((row.Vat || '').trim())
|
// const vat = parseVat((row.Vat || '').trim())
|
||||||
const type = (row.Type || '').trim()
|
// const type = (row.Type || '').trim()
|
||||||
const { isPublic, isDomestic } = toBooleanFlags(type)
|
// const { isPublic, isDomestic } = toBooleanFlags(type)
|
||||||
|
|
||||||
if (!code || !name) {
|
// if (!code || !name) {
|
||||||
skipped++
|
// skipped++
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
await prisma.stockKeepingUnits.upsert({
|
// await prisma.stockKeepingUnits.upsert({
|
||||||
where: { code },
|
// where: { code },
|
||||||
create: {
|
// create: {
|
||||||
code,
|
// code,
|
||||||
name,
|
// name,
|
||||||
VAT: vat,
|
// VAT: vat,
|
||||||
guild: {
|
// guild: {
|
||||||
connect: {
|
// connect: {
|
||||||
id: guild?.id,
|
// id: guild?.id,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
is_public: isPublic,
|
// is_public: isPublic,
|
||||||
is_domestic: isDomestic,
|
// is_domestic: isDomestic,
|
||||||
},
|
// },
|
||||||
update: {
|
// update: {
|
||||||
name,
|
// name,
|
||||||
VAT: vat,
|
// VAT: vat,
|
||||||
guild: {
|
// guild: {
|
||||||
connect: {
|
// connect: {
|
||||||
id: guild?.id,
|
// id: guild?.id,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
is_public: isPublic,
|
// is_public: isPublic,
|
||||||
is_domestic: isDomestic,
|
// is_domestic: isDomestic,
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
upserted++
|
// upserted++
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
main()
|
// main()
|
||||||
.catch(error => {
|
// .catch(error => {
|
||||||
console.error(error)
|
// console.error(error)
|
||||||
process.exit(1)
|
// process.exit(1)
|
||||||
})
|
// })
|
||||||
.finally(async () => {
|
// .finally(async () => {
|
||||||
await prisma.$disconnect()
|
// await prisma.$disconnect()
|
||||||
})
|
// })
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ export const summarySelect: BusinessActivitySelect = {
|
|||||||
select: {
|
select: {
|
||||||
activation: {
|
activation: {
|
||||||
select: {
|
select: {
|
||||||
_count: {
|
account_allocations: {
|
||||||
select: {
|
select: {
|
||||||
account_allocations: true,
|
id: true,
|
||||||
|
account_id: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -44,13 +45,18 @@ export const select: BusinessActivitySelect = {
|
|||||||
export const mappedData = (businessActivity: any) => {
|
export const mappedData = (businessActivity: any) => {
|
||||||
const { license_activation, ...rest } = businessActivity
|
const { license_activation, ...rest } = businessActivity
|
||||||
const { license, ...license_activation_rest } = license_activation
|
const { license, ...license_activation_rest } = license_activation
|
||||||
const { _count } = license.activation
|
const { account_allocations } = license.activation
|
||||||
|
|
||||||
|
console.log('license_activation', license_activation)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
license_info: {
|
license_info: {
|
||||||
...license_activation_rest,
|
...license_activation_rest,
|
||||||
accounts_limit: _count.account_allocations,
|
accounts_limit: account_allocations.length,
|
||||||
|
allocated_account_count: account_allocations.filter(
|
||||||
|
allocation => allocation.account_id,
|
||||||
|
).length,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ export const summarySelect: SalesInvoiceSelect = {
|
|||||||
invoice_number: true,
|
invoice_number: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
referenced_by: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
invoice_number: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const select: SalesInvoiceSelect = {
|
export const select: SalesInvoiceSelect = {
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
import { SharedSaleInvoiceAccessService } from '@/common/services/saleInvoices/sale-invoice-access.service'
|
import { SharedSaleInvoiceAccessService } from '@/common/services/saleInvoices/sale-invoice-access.service'
|
||||||
import { PosCorrectionSalesInvoiceDto } from '@/modules/pos/sales-invoices/dto/create-sales-invoice.dto'
|
import {
|
||||||
|
PosCorrectionSalesInvoiceDto,
|
||||||
|
PosReturnSalesInvoiceDto,
|
||||||
|
} from '@/modules/pos/sales-invoices/dto/create-sales-invoice.dto'
|
||||||
import { SalesInvoiceTspService } from '@/modules/tspProviders/sales-invoice-tsp.service'
|
import { SalesInvoiceTspService } from '@/modules/tspProviders/sales-invoice-tsp.service'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { PrismaService } from '@/prisma/prisma.service'
|
||||||
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'
|
||||||
|
import {
|
||||||
|
CustomerType,
|
||||||
|
TspProviderRequestType,
|
||||||
|
TspProviderResponseStatus,
|
||||||
|
} from 'generated/prisma/enums'
|
||||||
|
import { SharedSaleInvoiceCreateService } from './sale-invoice-create.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SharedSaleInvoiceActionsService {
|
export class SharedSaleInvoiceActionsService {
|
||||||
constructor(
|
constructor(
|
||||||
|
private prisma: PrismaService,
|
||||||
private readonly salesInvoiceTspService: SalesInvoiceTspService,
|
private readonly salesInvoiceTspService: SalesInvoiceTspService,
|
||||||
private readonly saleInvoiceAccessService: SharedSaleInvoiceAccessService,
|
private readonly saleInvoiceAccessService: SharedSaleInvoiceAccessService,
|
||||||
|
private sharedSaleInvoiceCreateService: SharedSaleInvoiceCreateService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async send(consumerAccountId: string, posId: string, invoiceId: string) {
|
async send(consumerAccountId: string, posId: string, invoiceId: string) {
|
||||||
@@ -58,16 +70,466 @@ export class SharedSaleInvoiceActionsService {
|
|||||||
consumerAccountId,
|
consumerAccountId,
|
||||||
posId,
|
posId,
|
||||||
)
|
)
|
||||||
return this.salesInvoiceTspService.correctionSend(
|
|
||||||
consumerAccountId,
|
const newInvoice = await this.prisma.$transaction(async tx => {
|
||||||
posId,
|
const relatedInvoice = await tx.salesInvoice.findUnique({
|
||||||
complexId,
|
where: {
|
||||||
businessId,
|
id: invoiceId,
|
||||||
invoiceId,
|
pos_id: posId,
|
||||||
data,
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
invoice_date: true,
|
||||||
|
settlement_type: true,
|
||||||
|
customer_id: true,
|
||||||
|
main_id: true,
|
||||||
|
last_tsp_status: true,
|
||||||
|
referenced_by: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
select: {
|
||||||
|
type: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
total_amount: true,
|
||||||
|
items: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
good_id: true,
|
||||||
|
service_id: true,
|
||||||
|
quantity: true,
|
||||||
|
unit_price: true,
|
||||||
|
total_amount: true,
|
||||||
|
discount_amount: true,
|
||||||
|
tax_amount: true,
|
||||||
|
payload: true,
|
||||||
|
notes: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!relatedInvoice) {
|
||||||
|
throw new NotFoundException('صورتحساب مورد نظر شما یافت نشد.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relatedInvoice.last_tsp_status !== TspProviderResponseStatus.SUCCESS) {
|
||||||
|
throw new BadRequestException('امکان اصلاح این صورتحساب وجود ندارد.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relatedInvoice.referenced_by) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'این صورتحساب قبلا با یک صورتحساب دیگر جایگزین شده است.',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
|
||||||
|
const originalInvoiceDate = this.normalizeInvoiceDate(relatedInvoice.invoice_date)
|
||||||
|
|
||||||
|
const originalItemsByKey = new Map(
|
||||||
|
relatedInvoice.items.map(item => [this.getItemKey(item), item]),
|
||||||
|
)
|
||||||
|
|
||||||
|
const usedItemKeys = new Set<string>()
|
||||||
|
const normalizedItems = data.items.map(item => {
|
||||||
|
const itemKey = this.getItemKey(item)
|
||||||
|
|
||||||
|
if (!itemKey) {
|
||||||
|
throw new BadRequestException('هر قلم باید شناسه کالا یا خدمت داشته باشد.')
|
||||||
|
}
|
||||||
|
|
||||||
|
usedItemKeys.add(itemKey)
|
||||||
|
|
||||||
|
const originalItem = originalItemsByKey.get(itemKey)
|
||||||
|
|
||||||
|
if (!originalItem) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'فقط اقلام موجود در صورتحساب اصلی قابل اصلاح هستند.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalQuantity = Number(originalItem.quantity)
|
||||||
|
const requestedQuantity = Number(item.quantity)
|
||||||
|
const originalUnitPrice = Number(originalItem.unit_price)
|
||||||
|
const requestedUnitPrice = Number(item.unit_price)
|
||||||
|
const originalTotalAmount = this.roundAmount(Number(originalItem.total_amount))
|
||||||
|
const requestedTotalAmount = this.roundAmount(Number(item.total_amount))
|
||||||
|
|
||||||
|
if (requestedQuantity <= 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'تعداد/مقدار اقلام اصلاحی باید بیشتر از صفر باشد.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (requestedUnitPrice < originalUnitPrice) {
|
||||||
|
// throw new BadRequestException(
|
||||||
|
// 'مبلغ واحد اقلام اصلاحی نمیتواند کمتر از صورتحساب اصلی باشد.',
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
return {
|
||||||
|
invoice_id: originalItem.id,
|
||||||
|
good_id: originalItem.good_id,
|
||||||
|
service_id: originalItem.service_id || undefined,
|
||||||
|
quantity: requestedQuantity,
|
||||||
|
unit_price: requestedUnitPrice,
|
||||||
|
total_amount: requestedTotalAmount,
|
||||||
|
discount_amount: this.roundAmount(Number(item.discount_amount || 0)),
|
||||||
|
tax_amount: this.roundAmount(Number(item.tax_amount || 0)),
|
||||||
|
payload: item.payload ? JSON.parse(JSON.stringify(item.payload)) : undefined,
|
||||||
|
notes: item.notes || undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (usedItemKeys.size !== relatedInvoice.items.length) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'فقط اقلام موجود در صورتحساب اصلی قابل اصلاح هستند.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasChangedQuantity = normalizedItems.some(item => {
|
||||||
|
const originalItem = relatedInvoice.items.find(
|
||||||
|
relatedItem => this.getItemKey(relatedItem) === this.getItemKey(item),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!originalItem) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(originalItem.quantity) !== Number(item.quantity)
|
||||||
|
})
|
||||||
|
const hasChangedAmount = normalizedItems.some(item => {
|
||||||
|
const originalItem = relatedInvoice.items.find(
|
||||||
|
relatedItem => this.getItemKey(relatedItem) === this.getItemKey(item),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!originalItem) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.roundAmount(Number(originalItem.total_amount)) !==
|
||||||
|
this.roundAmount(Number(item.total_amount))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const hasChangedInvoiceDate = normalizedInvoiceDate !== originalInvoiceDate
|
||||||
|
|
||||||
|
if (!hasChangedQuantity && !hasChangedAmount && !hasChangedInvoiceDate) {
|
||||||
|
throw new BadRequestException('مقداری تغییر نکرده است.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestedTotalAmount = this.roundAmount(Number(data.total_amount))
|
||||||
|
const originalTotalAmount = this.roundAmount(Number(relatedInvoice.total_amount))
|
||||||
|
const totalDiff = this.roundAmount(requestedTotalAmount - originalTotalAmount)
|
||||||
|
const paymentsAmount = this.roundAmount(this.getPaymentsAmount(data.payments))
|
||||||
|
|
||||||
|
if (totalDiff > 0 && paymentsAmount !== totalDiff) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'جمع پرداختی باید برابر با اختلاف مبلغ صورتحساب اصلاحی و صورتحساب مرجع باشد.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalDiff === 0 && paymentsAmount !== 0) {
|
||||||
|
throw new BadRequestException('در صورت عدم افزایش مبلغ، پرداختی نباید ثبت شود.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const newInvoice = await this.sharedSaleInvoiceCreateService.create({
|
||||||
|
tx,
|
||||||
|
data: {
|
||||||
|
invoice_date: new Date(normalizedInvoiceDate),
|
||||||
|
items: normalizedItems as any,
|
||||||
|
total_amount: requestedTotalAmount,
|
||||||
|
discount_amount: this.roundAmount(Number(data.discount_amount)),
|
||||||
|
tax_amount: this.roundAmount(Number(data.tax_amount)),
|
||||||
|
customer_id: relatedInvoice.customer_id || undefined,
|
||||||
|
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
|
||||||
|
settlement_type: relatedInvoice.settlement_type,
|
||||||
|
payments: data.payments,
|
||||||
|
} as any,
|
||||||
|
businessId,
|
||||||
|
complexId,
|
||||||
|
posId,
|
||||||
|
consumerAccountId,
|
||||||
|
main_invoice_id: relatedInvoice.main_id || relatedInvoice.id,
|
||||||
|
ref_invoice_id: relatedInvoice.id,
|
||||||
|
type: TspProviderRequestType.CORRECTION,
|
||||||
|
})
|
||||||
|
|
||||||
|
await tx.salesInvoice.update({
|
||||||
|
where: {
|
||||||
|
id: relatedInvoice.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
referenced_by: {
|
||||||
|
connect: {
|
||||||
|
id: newInvoice.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return newInvoice
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.salesInvoiceTspService.correctionSend(posId, newInvoice.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async return(
|
||||||
|
data: PosReturnSalesInvoiceDto,
|
||||||
|
consumerAccountId: string,
|
||||||
|
pos_id: string,
|
||||||
|
complex_id: string,
|
||||||
|
business_id: string,
|
||||||
|
invoiceId: string,
|
||||||
|
) {
|
||||||
|
await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
|
||||||
|
consumerAccountId,
|
||||||
|
pos_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
const newInvoice = await this.prisma.$transaction(async tx => {
|
||||||
|
const relatedInvoice = await tx.salesInvoice.findUnique({
|
||||||
|
where: {
|
||||||
|
id: invoiceId,
|
||||||
|
pos_id,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
type: true,
|
||||||
|
invoice_date: true,
|
||||||
|
settlement_type: true,
|
||||||
|
customer_id: true,
|
||||||
|
main_id: true,
|
||||||
|
last_tsp_status: true,
|
||||||
|
referenced_by: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
select: {
|
||||||
|
type: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
good_id: true,
|
||||||
|
service_id: true,
|
||||||
|
quantity: true,
|
||||||
|
unit_price: true,
|
||||||
|
total_amount: true,
|
||||||
|
discount_amount: true,
|
||||||
|
tax_amount: true,
|
||||||
|
payload: true,
|
||||||
|
notes: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!relatedInvoice) {
|
||||||
|
throw new NotFoundException('صورتحساب مورد نظر شما یافت نشد.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relatedInvoice.last_tsp_status !== TspProviderResponseStatus.SUCCESS) {
|
||||||
|
throw new BadRequestException('امکان برگشت از خرید روی این صورتحساب وجود ندارد.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relatedInvoice.referenced_by) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'این صورتحساب قبلا با یک صورتحساب دیگر جایگزین شده است.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
|
||||||
|
const originalInvoiceDate = this.normalizeInvoiceDate(relatedInvoice.invoice_date)
|
||||||
|
|
||||||
|
const originalItemsByKey = new Map(
|
||||||
|
relatedInvoice.items.map(item => [this.getReturnItemKey(item), item]),
|
||||||
|
)
|
||||||
|
|
||||||
|
const usedItemKeys = new Set<string>()
|
||||||
|
const normalizedItems = data.items.map(item => {
|
||||||
|
const itemKey = this.getReturnItemKey(item)
|
||||||
|
|
||||||
|
if (!itemKey) {
|
||||||
|
throw new BadRequestException('هر قلم باید شناسه کالا یا خدمت داشته باشد.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (usedItemKeys.has(itemKey)) {
|
||||||
|
// throw new BadRequestException('اقلام تکراری در صورتحساب بازگشتی مجاز نیستند.')
|
||||||
|
// }
|
||||||
|
|
||||||
|
usedItemKeys.add(itemKey)
|
||||||
|
|
||||||
|
const originalItem = originalItemsByKey.get(itemKey)
|
||||||
|
|
||||||
|
if (!originalItem) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'فقط اقلام موجود در صورتحساب اصلی قابل بازگشت هستند.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalQuantity = Number(originalItem.quantity)
|
||||||
|
const requestedQuantity = Number(item.quantity)
|
||||||
|
|
||||||
|
if (requestedQuantity <= 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'تعداد/مقدار اقلام بازگشتی باید بیشتر از صفر باشد.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestedQuantity > originalQuantity) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'تعداد/مقدار اقلام بازگشتی نمیتواند از مقدار صورتحساب اصلی بیشتر باشد.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const quantityRatio = requestedQuantity / originalQuantity
|
||||||
|
|
||||||
|
return {
|
||||||
|
invoice_id: originalItem.id,
|
||||||
|
good_id: originalItem.good_id,
|
||||||
|
service_id: originalItem.service_id || undefined,
|
||||||
|
quantity: requestedQuantity,
|
||||||
|
unit_price: Number(originalItem.unit_price),
|
||||||
|
total_amount: this.roundAmount(
|
||||||
|
Number(originalItem.total_amount) * quantityRatio,
|
||||||
|
),
|
||||||
|
discount_amount: this.roundAmount(
|
||||||
|
Number(originalItem.discount_amount || 0) * quantityRatio,
|
||||||
|
),
|
||||||
|
tax_amount: this.roundAmount(
|
||||||
|
Number(originalItem.tax_amount || 0) * quantityRatio,
|
||||||
|
),
|
||||||
|
payload: originalItem.payload
|
||||||
|
? JSON.parse(JSON.stringify(originalItem.payload))
|
||||||
|
: undefined,
|
||||||
|
notes: originalItem.notes || undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!normalizedItems.length) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'حداقل یک قلم باید در صورتحساب بازگشتی باقی بماند.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasRemovedItem = relatedInvoice.items.some(
|
||||||
|
item => !usedItemKeys.has(this.getReturnItemKey(item)),
|
||||||
|
)
|
||||||
|
const hasChangedQuantity = normalizedItems.some(item => {
|
||||||
|
const originalItem = relatedInvoice.items.find(
|
||||||
|
relatedItem =>
|
||||||
|
this.getReturnItemKey(relatedItem) === this.getReturnItemKey(item),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!originalItem) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(originalItem.quantity) !== Number(item.quantity)
|
||||||
|
})
|
||||||
|
const hasChangedInvoiceDate = normalizedInvoiceDate !== originalInvoiceDate
|
||||||
|
|
||||||
|
if (!hasRemovedItem && !hasChangedQuantity && !hasChangedInvoiceDate) {
|
||||||
|
throw new BadRequestException('مقداری تغییر نکرده است.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalAmount = normalizedItems.reduce(
|
||||||
|
(sum, item) => sum + Number(item.total_amount),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
const discountAmount = normalizedItems.reduce(
|
||||||
|
(sum, item) => sum + Number(item.discount_amount || 0),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
const taxAmount = normalizedItems.reduce(
|
||||||
|
(sum, item) => sum + Number(item.tax_amount || 0),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const newInvoice = await this.sharedSaleInvoiceCreateService.create({
|
||||||
|
tx,
|
||||||
|
data: {
|
||||||
|
invoice_date: new Date(normalizedInvoiceDate),
|
||||||
|
items: normalizedItems as any,
|
||||||
|
total_amount: totalAmount,
|
||||||
|
discount_amount: discountAmount,
|
||||||
|
tax_amount: taxAmount,
|
||||||
|
customer_id: relatedInvoice.customer_id || undefined,
|
||||||
|
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
|
||||||
|
settlement_type: relatedInvoice.settlement_type,
|
||||||
|
payments: undefined,
|
||||||
|
} as any,
|
||||||
|
businessId: business_id,
|
||||||
|
complexId: complex_id,
|
||||||
|
posId: pos_id,
|
||||||
|
consumerAccountId,
|
||||||
|
main_invoice_id: relatedInvoice.main_id || relatedInvoice.id,
|
||||||
|
ref_invoice_id: relatedInvoice.id,
|
||||||
|
type: TspProviderRequestType.RETURN,
|
||||||
|
})
|
||||||
|
|
||||||
|
await tx.salesInvoice.update({
|
||||||
|
where: {
|
||||||
|
id: relatedInvoice.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
referenced_by: {
|
||||||
|
connect: {
|
||||||
|
id: newInvoice.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return newInvoice
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.salesInvoiceTspService.returnFromSaleSend(
|
||||||
|
pos_id,
|
||||||
|
business_id,
|
||||||
|
newInvoice.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeInvoiceDate(invoiceDate: Date | string) {
|
||||||
|
return new Date(invoiceDate).toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private getItemKey(item: { good_id?: string | null; service_id?: string | null }) {
|
||||||
|
return item.good_id || item.service_id || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPaymentsAmount(payments?: {
|
||||||
|
terminals?: { amount?: number }
|
||||||
|
cash?: number
|
||||||
|
set_off?: number
|
||||||
|
card?: number
|
||||||
|
bank?: number
|
||||||
|
check?: number
|
||||||
|
other?: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
Number(payments?.terminals?.amount || 0) +
|
||||||
|
Number(payments?.cash || 0) +
|
||||||
|
Number(payments?.set_off || 0) +
|
||||||
|
Number(payments?.card || 0) +
|
||||||
|
Number(payments?.bank || 0) +
|
||||||
|
Number(payments?.check || 0) +
|
||||||
|
Number(payments?.other || 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private roundAmount(amount: number) {
|
||||||
|
return Number(amount.toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
async inquiry(consumerAccountId: string, posId: string, invoiceId: string) {
|
async inquiry(consumerAccountId: string, posId: string, invoiceId: string) {
|
||||||
const consumerId = await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
|
const consumerId = await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
|
||||||
consumerAccountId,
|
consumerAccountId,
|
||||||
@@ -75,4 +537,11 @@ export class SharedSaleInvoiceActionsService {
|
|||||||
)
|
)
|
||||||
return this.salesInvoiceTspService.get(invoiceId, posId, consumerId)
|
return this.salesInvoiceTspService.get(invoiceId, posId, consumerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getReturnItemKey(item: {
|
||||||
|
good_id?: string | null
|
||||||
|
service_id?: string | null
|
||||||
|
}) {
|
||||||
|
return item.good_id || item.service_id || ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,11 @@ export class SharedCreateSalesInvoiceDto {
|
|||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
items: SharedCreateSalesInvoiceItemDto[]
|
items: SharedCreateSalesInvoiceItemDto[]
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
ref_invoice_id?: string
|
||||||
|
|
||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -230,5 +235,13 @@ export class SharedCreateSalesInvoiceDto {
|
|||||||
|
|
||||||
export class SharedCorrectionSalesInvoiceDto extends OmitType(
|
export class SharedCorrectionSalesInvoiceDto extends OmitType(
|
||||||
SharedCreateSalesInvoiceDto,
|
SharedCreateSalesInvoiceDto,
|
||||||
['customer', 'customer_id', 'send_to_tsp', 'customer_type', 'settlement_type'],
|
['customer', 'customer_id', 'customer_type', 'settlement_type'],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
export class SharedReturnSalesInvoiceDto extends OmitType(SharedCreateSalesInvoiceDto, [
|
||||||
|
'customer',
|
||||||
|
'customer_id',
|
||||||
|
'customer_type',
|
||||||
|
'settlement_type',
|
||||||
|
'payments',
|
||||||
|
]) {}
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ export class SharedSaleInvoiceCreateService {
|
|||||||
} = input
|
} = input
|
||||||
|
|
||||||
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
|
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
|
||||||
const payments = this.buildPaymentsData(data.payments, data.total_amount)
|
const payments =
|
||||||
|
type === TspProviderRequestType.ORIGINAL || data.payments
|
||||||
|
? this.buildPaymentsData(data.payments, data.total_amount)
|
||||||
|
: []
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= this.createInvoiceRetries; attempt++) {
|
for (let attempt = 1; attempt <= this.createInvoiceRetries; attempt++) {
|
||||||
try {
|
try {
|
||||||
@@ -86,7 +89,14 @@ export class SharedSaleInvoiceCreateService {
|
|||||||
select: { ...QUERY_CONSTANTS.SALE_INVOICE.select },
|
select: { ...QUERY_CONSTANTS.SALE_INVOICE.select },
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.createPayments($tx, salesInvoice.id, payments, normalizedInvoiceDate)
|
if (payments.length) {
|
||||||
|
await this.createPayments(
|
||||||
|
$tx,
|
||||||
|
salesInvoice.id,
|
||||||
|
payments,
|
||||||
|
normalizedInvoiceDate,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return salesInvoice
|
return salesInvoice
|
||||||
})
|
})
|
||||||
@@ -162,7 +172,8 @@ export class SharedSaleInvoiceCreateService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.validatePayments(payments, totalAmount)
|
//TODO: for correction i need to validate payments with diff of total amount and original invoice amount
|
||||||
|
// this.validatePayments(payments, totalAmount)
|
||||||
|
|
||||||
return payments
|
return payments
|
||||||
}
|
}
|
||||||
@@ -390,6 +401,7 @@ export class SharedSaleInvoiceCreateService {
|
|||||||
customer,
|
customer,
|
||||||
payments,
|
payments,
|
||||||
settlement_type,
|
settlement_type,
|
||||||
|
send_to_tsp,
|
||||||
...invoiceData
|
...invoiceData
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
@@ -468,7 +480,7 @@ export class SharedSaleInvoiceCreateService {
|
|||||||
return salesInvoiceData
|
return salesInvoiceData
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getNextInvoiceNumber(tx: Prisma.TransactionClient, businessId: string) {
|
async getNextInvoiceNumber(tx: Prisma.TransactionClient, businessId: string) {
|
||||||
const latestInvoice = await tx.salesInvoice.findFirst({
|
const latestInvoice = await tx.salesInvoice.findFirst({
|
||||||
where: {
|
where: {
|
||||||
pos: {
|
pos: {
|
||||||
@@ -525,17 +537,26 @@ export class SharedSaleInvoiceCreateService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (payment.method === PaymentMethodType.TERMINAL && payment.terminalInfo) {
|
if (payment.method === PaymentMethodType.TERMINAL && payment.terminalInfo) {
|
||||||
|
const {
|
||||||
|
terminal_id,
|
||||||
|
stan,
|
||||||
|
rrn,
|
||||||
|
transaction_date_time,
|
||||||
|
customer_card_no,
|
||||||
|
description,
|
||||||
|
} = payment.terminalInfo
|
||||||
await tx.salesInvoicePaymentTerminalInfo.create({
|
await tx.salesInvoicePaymentTerminalInfo.create({
|
||||||
data: {
|
data: {
|
||||||
payment_id: createdPayment.id,
|
payment_id: createdPayment.id,
|
||||||
terminal_id: payment.terminalInfo.terminal_id,
|
terminal_id,
|
||||||
stan: payment.terminalInfo.stan,
|
stan: stan,
|
||||||
rrn: payment.terminalInfo.rrn,
|
rrn: rrn,
|
||||||
transaction_date_time: new Date(
|
transaction_date_time: transaction_date_time
|
||||||
payment.terminalInfo.transaction_date_time || '',
|
? new Date(transaction_date_time)
|
||||||
),
|
: new Date(),
|
||||||
customer_card_no: payment.terminalInfo.customer_card_no || null,
|
customer_card_no: '1234567890123456',
|
||||||
description: payment.terminalInfo.description || null,
|
// customer_card_no: customer_card_no || null,
|
||||||
|
description: description || null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-5
@@ -2,18 +2,20 @@ import { PrismaMariaDb } from '@prisma/adapter-mariadb'
|
|||||||
import 'dotenv/config'
|
import 'dotenv/config'
|
||||||
import { env } from 'prisma/config'
|
import { env } from 'prisma/config'
|
||||||
|
|
||||||
import { PrismaClient } from '../generated/prisma/client'
|
|
||||||
|
|
||||||
const adapter = new PrismaMariaDb({
|
const adapter = new PrismaMariaDb({
|
||||||
host: env('DATABASE_HOST'),
|
host: env('DATABASE_HOST'),
|
||||||
user: env('DATABASE_USER'),
|
user: env('DATABASE_USER'),
|
||||||
password: env('DATABASE_PASSWORD'),
|
password: env('DATABASE_PASSWORD'),
|
||||||
database: env('DATABASE_NAME'),
|
database: env('DATABASE_NAME'),
|
||||||
ssl: false,
|
|
||||||
connectionLimit: 5,
|
connectionLimit: 5,
|
||||||
port: Number(env('DATABASE_PORT')) || 3306,
|
port: Number(env('DATABASE_PORT')) || 3306,
|
||||||
|
allowPublicKeyRetrieval: true,
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
|
connectTimeout: 10000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const prisma = new PrismaClient({ adapter })
|
// const prisma = new PrismaClient({ adapter })
|
||||||
|
|
||||||
export { prisma }
|
export { adapter as prismaAdapter }
|
||||||
|
|||||||
+3
-2
@@ -17,6 +17,8 @@ const cookieParser = require('cookie-parser')
|
|||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule)
|
const app = await NestFactory.create(AppModule)
|
||||||
|
|
||||||
|
app.enableShutdownHooks()
|
||||||
|
|
||||||
app.use(cookieParser())
|
app.use(cookieParser())
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
@@ -33,6 +35,7 @@ async function bootstrap() {
|
|||||||
in: 'header',
|
in: 'header',
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
const documentFactory = () => SwaggerModule.createDocument(app, config)
|
const documentFactory = () => SwaggerModule.createDocument(app, config)
|
||||||
SwaggerModule.setup('swagger', app, documentFactory, {
|
SwaggerModule.setup('swagger', app, documentFactory, {
|
||||||
swaggerOptions: {
|
swaggerOptions: {
|
||||||
@@ -47,14 +50,12 @@ async function bootstrap() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set API prefix and enable URI versioning so alls routes live under /api/v1/*
|
// Set API prefix and enable URI versioning so alls routes live under /api/v1/*
|
||||||
app.setGlobalPrefix('api')
|
app.setGlobalPrefix('api')
|
||||||
app.enableVersioning({
|
app.enableVersioning({
|
||||||
type: VersioningType.URI,
|
type: VersioningType.URI,
|
||||||
defaultVersion: '1',
|
defaultVersion: '1',
|
||||||
})
|
})
|
||||||
|
|
||||||
// Enable CORS. You can set `CORS_ORIGINS` to a comma-separated list of allowed origins.
|
// Enable CORS. You can set `CORS_ORIGINS` to a comma-separated list of allowed origins.
|
||||||
// Defaults include common localhost origins used by front-end dev servers.
|
// Defaults include common localhost origins used by front-end dev servers.
|
||||||
const defaultOrigins: string[] = []
|
const defaultOrigins: string[] = []
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { SKUGuildType } from '@/common/enums/enums'
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||||
import { Type } from 'class-transformer'
|
import { Type } from 'class-transformer'
|
||||||
import { IsBoolean, IsEnum, IsNumber, IsString, Min } from 'class-validator'
|
import { IsBoolean, IsNumber, IsString, Min } from 'class-validator'
|
||||||
|
|
||||||
export class CreateStockKeepingUnitDto {
|
export class CreateStockKeepingUnitDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@@ -18,9 +17,9 @@ export class CreateStockKeepingUnitDto {
|
|||||||
@Min(0)
|
@Min(0)
|
||||||
VAT: number
|
VAT: number
|
||||||
|
|
||||||
@ApiPropertyOptional({ enum: SKUGuildType, default: SKUGuildType.GOLD })
|
// @ApiPropertyOptional({ enum: SKUGuildType, default: SKUGuildType.GOLD })
|
||||||
@IsEnum(SKUGuildType)
|
// @IsEnum(SKUGuildType)
|
||||||
type: SKUGuildType = SKUGuildType.GOLD
|
// type: SKUGuildType = SKUGuildType.GOLD
|
||||||
|
|
||||||
@ApiPropertyOptional({ default: true })
|
@ApiPropertyOptional({ default: true })
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
|||||||
+3
-1
@@ -105,7 +105,9 @@ export class PartnerAccountChargeTransactionService {
|
|||||||
try {
|
try {
|
||||||
createdTransaction = await tx.partnerAccountQuotaChargeTransaction.create({
|
createdTransaction = await tx.partnerAccountQuotaChargeTransaction.create({
|
||||||
data: {
|
data: {
|
||||||
activation_expires_at: data.activated_expires_at,
|
activation_expires_at: data.activated_expires_at
|
||||||
|
? new Date(data.activated_expires_at).toISOString()
|
||||||
|
: new Date().toISOString(),
|
||||||
tracking_code: generateTrackingCode('AQC', this.TRACKING_CODE_LENGTH),
|
tracking_code: generateTrackingCode('AQC', this.TRACKING_CODE_LENGTH),
|
||||||
purchased_count: data.quantity,
|
purchased_count: data.quantity,
|
||||||
partner: { connect: { id: partner_id } },
|
partner: { connect: { id: partner_id } },
|
||||||
|
|||||||
+3
-1
@@ -121,7 +121,9 @@ export class PartnerLicenseChargeTransactionService {
|
|||||||
try {
|
try {
|
||||||
createdTransaction = await tx.licenseChargeTransaction.create({
|
createdTransaction = await tx.licenseChargeTransaction.create({
|
||||||
data: {
|
data: {
|
||||||
activation_expires_at: data.activated_expires_at,
|
activation_expires_at: data.activated_expires_at
|
||||||
|
? new Date(data.activated_expires_at).toISOString()
|
||||||
|
: new Date().toISOString(),
|
||||||
tracking_code: generateTrackingCode('LIC', this.TRACKING_CODE_LENGTH),
|
tracking_code: generateTrackingCode('LIC', this.TRACKING_CODE_LENGTH),
|
||||||
purchased_count: data.quantity,
|
purchased_count: data.quantity,
|
||||||
partner: { connect: { id: partner_id } },
|
partner: { connect: { id: partner_id } },
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
TspProviderType,
|
TspProviderType,
|
||||||
} from '@/generated/prisma/enums'
|
} from '@/generated/prisma/enums'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { GoldKarat, SKUGuildType, TspProviderCustomerType } from 'common/enums/enums'
|
import { GoldKarat, TspProviderCustomerType } from 'common/enums/enums'
|
||||||
import { ResponseMapper } from 'common/response/response-mapper'
|
import { ResponseMapper } from 'common/response/response-mapper'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -80,7 +80,7 @@ export class EnumsService {
|
|||||||
TspProviderCustomerType,
|
TspProviderCustomerType,
|
||||||
'TspProviderCustomerType',
|
'TspProviderCustomerType',
|
||||||
),
|
),
|
||||||
SKUGuildType: this.prepareData(SKUGuildType, 'SKUGuildType'),
|
// SKUGuildType: this.prepareData(SKUGuildType, 'SKUGuildType'),
|
||||||
InvoiceTemplateType: this.prepareData(InvoiceTemplateType, 'InvoiceTemplateType'),
|
InvoiceTemplateType: this.prepareData(InvoiceTemplateType, 'InvoiceTemplateType'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { RedisKeyMaker } from '@/common/utils'
|
import { RedisKeyMaker } from '@/common/utils'
|
||||||
|
import { PosCacheInvalidationService } from '@/modules/pos/cache/pos-cache-invalidation.service'
|
||||||
import { RedisService } from '@/redis/redis.service'
|
import { RedisService } from '@/redis/redis.service'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PartnersCacheInvalidationService {
|
export class PartnersCacheInvalidationService {
|
||||||
constructor(private readonly redisService: RedisService) {}
|
constructor(
|
||||||
|
private readonly redisService: RedisService,
|
||||||
|
private readonly PosCacheInvalidationService: PosCacheInvalidationService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async invalidatePartnerSummary(partnerId: string): Promise<void> {
|
async invalidatePartnerSummary(partnerId: string): Promise<void> {
|
||||||
await this.invalidatePartnersList()
|
await this.invalidatePartnersList()
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import { Injectable } from '@nestjs/common'
|
|||||||
export class PosCacheInvalidationService {
|
export class PosCacheInvalidationService {
|
||||||
constructor(private readonly redisService: RedisService) {}
|
constructor(private readonly redisService: RedisService) {}
|
||||||
|
|
||||||
|
async invalidatePosInfo(posId: string): Promise<void> {
|
||||||
|
this.redisService.delete(PosKeyMaker.info(posId))
|
||||||
|
}
|
||||||
|
|
||||||
async invalidatePosMiddleware(token: string): Promise<void> {
|
async invalidatePosMiddleware(token: string): Promise<void> {
|
||||||
this.redisService.delete(PosKeyMaker.middleware(token))
|
this.redisService.delete(PosKeyMaker.middleware(token))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,51 @@ import {
|
|||||||
SharedCorrectionSalesInvoiceDto,
|
SharedCorrectionSalesInvoiceDto,
|
||||||
SharedCreateSalesInvoiceDto,
|
SharedCreateSalesInvoiceDto,
|
||||||
} from '@/common/services/saleInvoices/sale-invoice-create.dto'
|
} from '@/common/services/saleInvoices/sale-invoice-create.dto'
|
||||||
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
import { Type } from 'class-transformer'
|
||||||
|
import {
|
||||||
|
ArrayMinSize,
|
||||||
|
IsArray,
|
||||||
|
IsDateString,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Min,
|
||||||
|
ValidateNested,
|
||||||
|
} from 'class-validator'
|
||||||
|
|
||||||
export class PosCreateSalesInvoiceDto extends SharedCreateSalesInvoiceDto {}
|
export class PosCreateSalesInvoiceDto extends SharedCreateSalesInvoiceDto {}
|
||||||
export class PosCorrectionSalesInvoiceDto extends SharedCorrectionSalesInvoiceDto {}
|
export class PosCorrectionSalesInvoiceDto extends SharedCorrectionSalesInvoiceDto {}
|
||||||
|
|
||||||
|
export class PosReturnSalesInvoiceItemDto {
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
good_id?: string
|
||||||
|
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
service_id?: string
|
||||||
|
|
||||||
|
@ApiProperty({ required: true, default: 1 })
|
||||||
|
@IsNumber()
|
||||||
|
@Min(1)
|
||||||
|
quantity: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PosReturnSalesInvoiceDto {
|
||||||
|
@ApiProperty({ required: true })
|
||||||
|
@IsDateString(
|
||||||
|
{ strict: true },
|
||||||
|
{ message: 'invoice_date must be a valid ISO-8601 string' },
|
||||||
|
)
|
||||||
|
invoice_date: Date
|
||||||
|
|
||||||
|
@ApiProperty({ required: true, type: [PosReturnSalesInvoiceItemDto] })
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(1)
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => PosReturnSalesInvoiceItemDto)
|
||||||
|
items: PosReturnSalesInvoiceItemDto[]
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { IPosPayload } from '@/common/models/posPayload.model'
|
|||||||
import {
|
import {
|
||||||
PosCorrectionSalesInvoiceDto,
|
PosCorrectionSalesInvoiceDto,
|
||||||
PosCreateSalesInvoiceDto,
|
PosCreateSalesInvoiceDto,
|
||||||
|
PosReturnSalesInvoiceDto,
|
||||||
} from './dto/create-sales-invoice.dto'
|
} from './dto/create-sales-invoice.dto'
|
||||||
import { SalesInvoicesFilterDto } from './dto/sales-invoices-filter.dto'
|
import { SalesInvoicesFilterDto } from './dto/sales-invoices-filter.dto'
|
||||||
import { SalesInvoicesService } from './sales-invoices.service'
|
import { SalesInvoicesService } from './sales-invoices.service'
|
||||||
@@ -60,9 +61,13 @@ export class SalesInvoicesController {
|
|||||||
) {
|
) {
|
||||||
return this.salesInvoicesService.correction(id, posInfo, data)
|
return this.salesInvoicesService.correction(id, posInfo, data)
|
||||||
}
|
}
|
||||||
@Post(':id/return')
|
@Post(':id/return_from_sale')
|
||||||
return(@Param('id') id: string, @PosInfo() posInfo: IPosPayload) {
|
return(
|
||||||
return this.salesInvoicesService.return(id, posInfo)
|
@Param('id') id: string,
|
||||||
|
@PosInfo() posInfo: IPosPayload,
|
||||||
|
@Body() data: PosReturnSalesInvoiceDto,
|
||||||
|
) {
|
||||||
|
return this.salesInvoicesService.return(id, posInfo, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Post('send/bulk')
|
// @Post('send/bulk')
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { SalesInvoiceTspService } from '../../tspProviders/sales-invoice-tsp.ser
|
|||||||
import {
|
import {
|
||||||
PosCorrectionSalesInvoiceDto,
|
PosCorrectionSalesInvoiceDto,
|
||||||
PosCreateSalesInvoiceDto,
|
PosCreateSalesInvoiceDto,
|
||||||
|
PosReturnSalesInvoiceDto,
|
||||||
} from './dto/create-sales-invoice.dto'
|
} from './dto/create-sales-invoice.dto'
|
||||||
import { SalesInvoicesFilterDto } from './dto/sales-invoices-filter.dto'
|
import { SalesInvoicesFilterDto } from './dto/sales-invoices-filter.dto'
|
||||||
|
|
||||||
@@ -172,10 +173,11 @@ export class SalesInvoicesService {
|
|||||||
return ResponseMapper.single(invoice)
|
return ResponseMapper.single(invoice)
|
||||||
}
|
}
|
||||||
|
|
||||||
async return(invoiceId: string, posInfo: IPosPayload) {
|
async return(invoiceId: string, posInfo: IPosPayload, data: PosReturnSalesInvoiceDto) {
|
||||||
const { consumer_account_id, pos_id, business_id, complex_id } = posInfo
|
const { consumer_account_id, pos_id, business_id, complex_id } = posInfo
|
||||||
|
|
||||||
const invoice = await this.sharedSaleInvoiceActionsService.revoke(
|
const invoice = await this.sharedSaleInvoiceActionsService.return(
|
||||||
|
data,
|
||||||
consumer_account_id,
|
consumer_account_id,
|
||||||
pos_id,
|
pos_id,
|
||||||
complex_id,
|
complex_id,
|
||||||
@@ -183,7 +185,7 @@ export class SalesInvoicesService {
|
|||||||
invoiceId,
|
invoiceId,
|
||||||
)
|
)
|
||||||
|
|
||||||
return ResponseMapper.single(invoice)
|
return ResponseMapper.create(invoice)
|
||||||
}
|
}
|
||||||
|
|
||||||
async inquiry(consumer_account_id: string, pos_id: string, invoiceId: string) {
|
async inquiry(consumer_account_id: string, pos_id: string, invoiceId: string) {
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ export class TspProviderCorrectionInvoicePayloadDto {
|
|||||||
@IsObject()
|
@IsObject()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => SharedCreateSalesInvoicePaymentsDto)
|
@Type(() => SharedCreateSalesInvoicePaymentsDto)
|
||||||
payments: SharedCreateSalesInvoicePaymentsDto
|
payments?: SharedCreateSalesInvoicePaymentsDto
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TspProviderCorrectionSendPayloadDto extends TspProviderOriginalSendPayloadDto {
|
export class TspProviderCorrectionSendPayloadDto extends TspProviderOriginalSendPayloadDto {
|
||||||
@ApiProperty({ required: true })
|
@ApiProperty({ required: true })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ export * from './correction.dto'
|
|||||||
export * from './get.dto'
|
export * from './get.dto'
|
||||||
export * from './original.dto'
|
export * from './original.dto'
|
||||||
export * from './provider-switch.dto'
|
export * from './provider-switch.dto'
|
||||||
|
export * from './return.dto'
|
||||||
export * from './revoke.dto'
|
export * from './revoke.dto'
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { SharedCreateSalesInvoiceItemDto } from '@/common/services/saleInvoices/sale-invoice-create.dto'
|
||||||
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
import { Type } from 'class-transformer'
|
||||||
|
import {
|
||||||
|
ArrayMinSize,
|
||||||
|
IsArray,
|
||||||
|
IsDateString,
|
||||||
|
IsNumber,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
} from 'class-validator'
|
||||||
|
import {
|
||||||
|
TspProviderOriginalResponseDto,
|
||||||
|
TspProviderOriginalSendPayloadDto,
|
||||||
|
} from './original.dto'
|
||||||
|
|
||||||
|
export class TspProviderReturnInvoicePayloadDto {
|
||||||
|
@ApiProperty({ required: true, nullable: true })
|
||||||
|
@IsDateString()
|
||||||
|
invoice_date: Date
|
||||||
|
|
||||||
|
@ApiProperty({ type: [SharedCreateSalesInvoiceItemDto] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => SharedCreateSalesInvoiceItemDto)
|
||||||
|
@ArrayMinSize(1)
|
||||||
|
items: SharedCreateSalesInvoiceItemDto[]
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
total_amount: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
discount_amount: number
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
tax_amount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TspProviderReturnSendPayloadDto extends TspProviderOriginalSendPayloadDto {
|
||||||
|
@ApiProperty({ required: true })
|
||||||
|
@IsString()
|
||||||
|
last_tax_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TspProviderReturnSendResponseDto extends TspProviderOriginalResponseDto {}
|
||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
TspProviderGetResponseDto,
|
TspProviderGetResponseDto,
|
||||||
TspProviderOriginalResponseDto,
|
TspProviderOriginalResponseDto,
|
||||||
TspProviderOriginalSendPayloadDto,
|
TspProviderOriginalSendPayloadDto,
|
||||||
|
TspProviderReturnSendPayloadDto,
|
||||||
|
TspProviderReturnSendResponseDto,
|
||||||
TspProviderRevokePayloadDto,
|
TspProviderRevokePayloadDto,
|
||||||
TspProviderRevokeResponseDto,
|
TspProviderRevokeResponseDto,
|
||||||
} from './dto'
|
} from './dto'
|
||||||
@@ -48,6 +50,14 @@ export class SalesInvoiceTspSwitchService {
|
|||||||
return adapter.correctionSend(payload)
|
return adapter.correctionSend(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async returnFromSale(
|
||||||
|
payload: TspProviderReturnSendPayloadDto,
|
||||||
|
): Promise<TspProviderReturnSendResponseDto> {
|
||||||
|
const adapter = this.resolveSwitch(payload.tsp_provider)
|
||||||
|
|
||||||
|
return adapter.returnSend(payload)
|
||||||
|
}
|
||||||
|
|
||||||
async sendBulk(
|
async sendBulk(
|
||||||
payloads: TspProviderOriginalSendPayloadDto[],
|
payloads: TspProviderOriginalSendPayloadDto[],
|
||||||
): Promise<TspProviderBulkSendResultDto[]> {
|
): Promise<TspProviderBulkSendResultDto[]> {
|
||||||
|
|||||||
@@ -6,17 +6,14 @@ import {
|
|||||||
TspProviderRequestType,
|
TspProviderRequestType,
|
||||||
TspProviderResponseStatus,
|
TspProviderResponseStatus,
|
||||||
} from 'generated/prisma/client'
|
} from 'generated/prisma/client'
|
||||||
import {
|
import { TspProviderActionResponseDto } from './dto'
|
||||||
TspProviderActionResponseDto,
|
|
||||||
TspProviderCorrectionInvoicePayloadDto,
|
|
||||||
} from './dto'
|
|
||||||
import { SalesInvoiceTspSwitchService } from './sales-invoice-tsp-switch.service'
|
import { SalesInvoiceTspSwitchService } from './sales-invoice-tsp-switch.service'
|
||||||
import {
|
import {
|
||||||
buildCorrectionPayload,
|
buildCorrectionPayload,
|
||||||
buildOriginalPayload,
|
buildOriginalPayload,
|
||||||
|
buildReturnFromSalePayload,
|
||||||
buildRevokePayload,
|
buildRevokePayload,
|
||||||
getOriginalResendAttemptNumber,
|
getOriginalResendAttemptNumber,
|
||||||
getRelatedInvoiceForCorrection,
|
|
||||||
onResult,
|
onResult,
|
||||||
} from './utils/sales-invoice-tsp.utils'
|
} from './utils/sales-invoice-tsp.utils'
|
||||||
|
|
||||||
@@ -176,60 +173,68 @@ export class SalesInvoiceTspService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async correctionSend(
|
async correctionSend(
|
||||||
consumerAccountId: string,
|
|
||||||
posId: string,
|
posId: string,
|
||||||
complexId: string,
|
invoice_id: string,
|
||||||
businessId: string,
|
|
||||||
ref_invoice_id: string,
|
|
||||||
dataToUpdate: TspProviderCorrectionInvoicePayloadDto,
|
|
||||||
): Promise<TspProviderActionResponseDto> {
|
): Promise<TspProviderActionResponseDto> {
|
||||||
const [newInvoice, attempt, correctionPayload] = await this.prisma.$transaction(
|
const payload = await buildCorrectionPayload(this.prisma, invoice_id, posId)
|
||||||
async tx => {
|
|
||||||
const relatedInvoice = await getRelatedInvoiceForCorrection(tx, ref_invoice_id)
|
|
||||||
|
|
||||||
const newInvoice = await this.sharedSaleInvoiceCreateService.create({
|
const attemptNumber = 1
|
||||||
tx,
|
|
||||||
data: {
|
const invoice = await this.prisma.salesInvoice.update({
|
||||||
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
|
where: {
|
||||||
invoice_date: dataToUpdate.invoice_date,
|
id: invoice_id,
|
||||||
payments: dataToUpdate.payments,
|
|
||||||
items: dataToUpdate.items,
|
|
||||||
total_amount: dataToUpdate.total_amount,
|
|
||||||
discount_amount: dataToUpdate.discount_amount,
|
|
||||||
tax_amount: dataToUpdate.tax_amount,
|
|
||||||
customer_id: relatedInvoice.customer_id || undefined,
|
|
||||||
settlement_type: relatedInvoice.settlement_type,
|
|
||||||
},
|
},
|
||||||
businessId,
|
data: {
|
||||||
complexId,
|
last_tsp_status: TspProviderResponseStatus.QUEUED,
|
||||||
posId,
|
last_attempt_no: attemptNumber,
|
||||||
consumerAccountId,
|
|
||||||
main_invoice_id: relatedInvoice.main_id || relatedInvoice.id,
|
|
||||||
ref_invoice_id: relatedInvoice.id,
|
|
||||||
type: TspProviderRequestType.CORRECTION,
|
type: TspProviderRequestType.CORRECTION,
|
||||||
})
|
tsp_attempts: {
|
||||||
|
create: {
|
||||||
const correctionPayload = await buildCorrectionPayload(tx, newInvoice.id)
|
attempt_no: attemptNumber,
|
||||||
|
|
||||||
const attempt = await tx.saleInvoiceTspAttempts.create({
|
|
||||||
data: {
|
|
||||||
attempt_no: 1,
|
|
||||||
invoice_id: newInvoice.id,
|
|
||||||
message: 'در حال ارسال به سامانه مالیاتی...',
|
|
||||||
status: TspProviderResponseStatus.QUEUED,
|
|
||||||
sent_at: new Date().toISOString(),
|
|
||||||
raw_request_payload: JSON.parse(JSON.stringify(correctionPayload)),
|
|
||||||
provider_request_payload: {},
|
provider_request_payload: {},
|
||||||
|
status: TspProviderResponseStatus.QUEUED,
|
||||||
|
raw_request_payload: JSON.parse(JSON.stringify(payload)),
|
||||||
|
sent_at: new Date().toISOString(),
|
||||||
|
message: 'در حال ارسال به سامانه مالیاتی...',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return [newInvoice, attempt, correctionPayload]
|
const result = await this.tspSwitchService.send(payload)
|
||||||
|
return await onResult(this.prisma, result, invoice.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async returnFromSaleSend(
|
||||||
|
pos_id: string,
|
||||||
|
business_id: string,
|
||||||
|
invoice_id: string,
|
||||||
|
): Promise<TspProviderActionResponseDto> {
|
||||||
|
const payload = await buildReturnFromSalePayload(this.prisma, invoice_id, pos_id)
|
||||||
|
|
||||||
|
const attemptNumber = 1
|
||||||
|
|
||||||
|
const invoice = await this.prisma.salesInvoice.update({
|
||||||
|
where: {
|
||||||
|
id: invoice_id,
|
||||||
},
|
},
|
||||||
)
|
data: {
|
||||||
|
last_tsp_status: TspProviderResponseStatus.QUEUED,
|
||||||
const result = await this.tspSwitchService.correction(correctionPayload)
|
last_attempt_no: attemptNumber,
|
||||||
|
tsp_attempts: {
|
||||||
return await onResult(this.prisma, result, newInvoice.id)
|
create: {
|
||||||
|
attempt_no: attemptNumber,
|
||||||
|
provider_request_payload: {},
|
||||||
|
status: TspProviderResponseStatus.QUEUED,
|
||||||
|
raw_request_payload: JSON.parse(JSON.stringify(payload)),
|
||||||
|
sent_at: new Date().toISOString(),
|
||||||
|
message: 'در حال ارسال به سامانه مالیاتی...',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const result = await this.tspSwitchService.returnFromSale(payload)
|
||||||
|
return await onResult(this.prisma, result, invoice.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async revoke(
|
async revoke(
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export * from './common.dto'
|
|||||||
export * from './correction.dto'
|
export * from './correction.dto'
|
||||||
export * from './get.dto'
|
export * from './get.dto'
|
||||||
export * from './original.dto'
|
export * from './original.dto'
|
||||||
|
export * from './returnFromSale.dto'
|
||||||
export * from './revoke.dto'
|
export * from './revoke.dto'
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
import { Type } from 'class-transformer'
|
||||||
|
import { IsArray, IsString, Length, ValidateNested } from 'class-validator'
|
||||||
|
import {
|
||||||
|
NamaProviderCommonBodyDto,
|
||||||
|
NamaProviderCommonHeaderDto,
|
||||||
|
NamaProviderPaymentInfoDto,
|
||||||
|
NamaProviderResponseDto,
|
||||||
|
} from './common.dto'
|
||||||
|
|
||||||
|
export class NamaProviderReturnHeaderDto extends NamaProviderCommonHeaderDto {
|
||||||
|
@ApiProperty({ required: true, description: 'موضوع صورتحساب (3- برگشت از فروش)' })
|
||||||
|
@IsString()
|
||||||
|
ins: '3'
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
required: true,
|
||||||
|
description: 'شناسه مالیاتی صورتحساب مرجع',
|
||||||
|
minLength: 22,
|
||||||
|
maxLength: 22,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@Length(22, 22)
|
||||||
|
irtaxid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NamaProviderReturnBodyItemDto extends NamaProviderCommonBodyDto {}
|
||||||
|
|
||||||
|
export class NamaProviderReturnRequestDto {
|
||||||
|
@ApiProperty({ type: [NamaProviderReturnBodyItemDto] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => NamaProviderReturnBodyItemDto)
|
||||||
|
body: NamaProviderReturnBodyItemDto[]
|
||||||
|
|
||||||
|
@ApiProperty({ type: [NamaProviderPaymentInfoDto] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => NamaProviderPaymentInfoDto)
|
||||||
|
payment: NamaProviderPaymentInfoDto[]
|
||||||
|
|
||||||
|
@ApiProperty({ type: NamaProviderReturnHeaderDto })
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => NamaProviderReturnHeaderDto)
|
||||||
|
header: NamaProviderReturnHeaderDto
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
uuid: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
economic_code: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
fiscal_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NamaProviderReturnResponseDto extends NamaProviderResponseDto {}
|
||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
TspProviderGetResponseDto,
|
TspProviderGetResponseDto,
|
||||||
TspProviderOriginalResponseDto,
|
TspProviderOriginalResponseDto,
|
||||||
TspProviderOriginalSendPayloadDto,
|
TspProviderOriginalSendPayloadDto,
|
||||||
|
TspProviderReturnSendPayloadDto,
|
||||||
|
TspProviderReturnSendResponseDto,
|
||||||
TspProviderRevokePayloadDto,
|
TspProviderRevokePayloadDto,
|
||||||
TspProviderRevokeResponseDto,
|
TspProviderRevokeResponseDto,
|
||||||
} from '../../dto'
|
} from '../../dto'
|
||||||
@@ -27,6 +29,7 @@ import {
|
|||||||
NamaProviderRevokeRequestDto,
|
NamaProviderRevokeRequestDto,
|
||||||
NamaProviderRevokeResponseDto,
|
NamaProviderRevokeResponseDto,
|
||||||
} from './dto'
|
} from './dto'
|
||||||
|
import { NamaProviderReturnResponseDto } from './dto/returnFromSale.dto'
|
||||||
import { NamaProviderUtils } from './nama-provider.util'
|
import { NamaProviderUtils } from './nama-provider.util'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -40,6 +43,7 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
|
|||||||
|
|
||||||
private readonly sendPath = '/api/v1/invoices/single-send'
|
private readonly sendPath = '/api/v1/invoices/single-send'
|
||||||
private readonly correctionPath = this.sendPath
|
private readonly correctionPath = this.sendPath
|
||||||
|
private readonly returnFromSalePath = this.sendPath
|
||||||
private readonly sendBulkPath = '/api/v1/invoices'
|
private readonly sendBulkPath = '/api/v1/invoices'
|
||||||
private readonly checkBulkStatus = '/api/v1/invoices/check-by-uuids'
|
private readonly checkBulkStatus = '/api/v1/invoices/check-by-uuids'
|
||||||
private readonly getPath = '/api/v1/invoices'
|
private readonly getPath = '/api/v1/invoices'
|
||||||
@@ -63,6 +67,10 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
|
|||||||
return `${this.baseUrl}${this.correctionPath}`
|
return `${this.baseUrl}${this.correctionPath}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildReturnFromSaleUrl() {
|
||||||
|
return `${this.baseUrl}${this.returnFromSalePath}`
|
||||||
|
}
|
||||||
|
|
||||||
private buildGetUrl(invoiceId: string) {
|
private buildGetUrl(invoiceId: string) {
|
||||||
return `${this.baseUrl}${this.getPath}/${invoiceId}`
|
return `${this.baseUrl}${this.getPath}/${invoiceId}`
|
||||||
}
|
}
|
||||||
@@ -176,6 +184,51 @@ export class NamaProviderSwitchAdapter implements IProviderSwitchAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async returnSend(
|
||||||
|
payload: TspProviderReturnSendPayloadDto,
|
||||||
|
): Promise<TspProviderReturnSendResponseDto> {
|
||||||
|
const mappedRequest = this.namaProviderUtils.mapToNamaReturnDto(payload)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.httpClient.request(
|
||||||
|
this.buildReturnFromSaleUrl(),
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(mappedRequest),
|
||||||
|
},
|
||||||
|
this.createRequestInterceptors(payload.token),
|
||||||
|
)
|
||||||
|
const providerResponse: NamaProviderReturnResponseDto = await response.json()
|
||||||
|
this.logger.debug('NAMA provider return response', providerResponse)
|
||||||
|
|
||||||
|
const result: TspProviderReturnSendResponseDto = {
|
||||||
|
invoice_id: payload.id,
|
||||||
|
provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)),
|
||||||
|
hasError: !response.ok,
|
||||||
|
message: providerResponse.message,
|
||||||
|
provider_response: JSON.parse(JSON.stringify(providerResponse)),
|
||||||
|
received_at: new Date().toISOString(),
|
||||||
|
tax_id: providerResponse.tax_id,
|
||||||
|
status: this.namaProviderUtils.mapResponseStatus(
|
||||||
|
providerResponse.status as NamaProviderResponseStatus,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('NAMA return from sale send failed', err)
|
||||||
|
const failure: TspProviderReturnSendResponseDto = {
|
||||||
|
invoice_id: payload.id,
|
||||||
|
provider_request_payload: JSON.parse(JSON.stringify(mappedRequest)),
|
||||||
|
hasError: true,
|
||||||
|
message: (err as Error).message,
|
||||||
|
received_at: new Date().toISOString(),
|
||||||
|
status: TspProviderResponseStatus.SEND_FAILURE,
|
||||||
|
}
|
||||||
|
return failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async sendBulk(
|
async sendBulk(
|
||||||
payloads: TspProviderOriginalSendPayloadDto[],
|
payloads: TspProviderOriginalSendPayloadDto[],
|
||||||
): Promise<TspProviderBulkSendResultDto[]> {
|
): Promise<TspProviderBulkSendResultDto[]> {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
PaymentInfoDto,
|
PaymentInfoDto,
|
||||||
TspProviderCorrectionSendPayloadDto,
|
TspProviderCorrectionSendPayloadDto,
|
||||||
TspProviderOriginalSendPayloadDto,
|
TspProviderOriginalSendPayloadDto,
|
||||||
|
TspProviderReturnSendPayloadDto,
|
||||||
TspProviderRevokePayloadDto,
|
TspProviderRevokePayloadDto,
|
||||||
} from '../../dto'
|
} from '../../dto'
|
||||||
import {
|
import {
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
NamaProviderResponseStatus,
|
NamaProviderResponseStatus,
|
||||||
NamaProviderRevokeRequestDto,
|
NamaProviderRevokeRequestDto,
|
||||||
} from './dto'
|
} from './dto'
|
||||||
|
import { NamaProviderReturnRequestDto } from './dto/returnFromSale.dto'
|
||||||
|
|
||||||
export class NamaProviderUtils {
|
export class NamaProviderUtils {
|
||||||
mapResponseStatus(status: NamaProviderResponseStatus): TspProviderResponseStatus {
|
mapResponseStatus(status: NamaProviderResponseStatus): TspProviderResponseStatus {
|
||||||
@@ -143,6 +145,57 @@ export class NamaProviderUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mapToNamaReturnDto(
|
||||||
|
payload: TspProviderReturnSendPayloadDto,
|
||||||
|
): NamaProviderReturnRequestDto {
|
||||||
|
return {
|
||||||
|
uuid: payload.id,
|
||||||
|
economic_code: payload.economic_code,
|
||||||
|
fiscal_id: payload.fiscal_id,
|
||||||
|
payment: this.mapPayments(payload.payments),
|
||||||
|
header: {
|
||||||
|
ins: this.mapRequestType(TspProviderRequestType.RETURN),
|
||||||
|
inp: this.mapInvoiceTemplate(payload.template),
|
||||||
|
inty: this.mapTspProviderCustomerType(payload.customer?.type),
|
||||||
|
inno: payload.invoice_number.toString(),
|
||||||
|
tins: '',
|
||||||
|
indatim: payload.invoice_date.getTime() + '',
|
||||||
|
irtaxid: payload.last_tax_id,
|
||||||
|
...this.mapSettlementBased(
|
||||||
|
payload.settlement_type,
|
||||||
|
payload.payments,
|
||||||
|
payload.total_amount,
|
||||||
|
),
|
||||||
|
...this.mapCustomerInfo(payload.customer),
|
||||||
|
},
|
||||||
|
body: payload.items.map(item => {
|
||||||
|
let data: NamaProviderOriginalBodyItemDto = {
|
||||||
|
sstid: item.sku,
|
||||||
|
vra: item.sku_vat,
|
||||||
|
fee: String(item.unit_price),
|
||||||
|
dis: String(parseInt(item.discount_amount + '')),
|
||||||
|
mu: item.measure_unit,
|
||||||
|
am: String(item.quantity),
|
||||||
|
consfee: '0',
|
||||||
|
bros: '0',
|
||||||
|
spro: '0',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.gold_type_payload) {
|
||||||
|
const { profit = '0', commission = '0', wages = '0' } = item.gold_type_payload
|
||||||
|
data = {
|
||||||
|
...data,
|
||||||
|
consfee: wages,
|
||||||
|
bros: commission,
|
||||||
|
spro: profit,
|
||||||
|
tcpbs: (Number(profit) + Number(commission) + Number(wages)).toString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mapToNamaRevokeRequestDto(
|
mapToNamaRevokeRequestDto(
|
||||||
payload: TspProviderRevokePayloadDto,
|
payload: TspProviderRevokePayloadDto,
|
||||||
): NamaProviderRevokeRequestDto {
|
): NamaProviderRevokeRequestDto {
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import {
|
|||||||
} from 'generated/prisma/client'
|
} from 'generated/prisma/client'
|
||||||
import {
|
import {
|
||||||
goldTypePayload,
|
goldTypePayload,
|
||||||
TspProviderCorrectionSendPayloadDto,
|
|
||||||
TspProviderGetResponseDto,
|
TspProviderGetResponseDto,
|
||||||
TspProviderOriginalResponseDto,
|
TspProviderOriginalResponseDto,
|
||||||
TspProviderOriginalSendPayloadDto,
|
TspProviderOriginalSendPayloadDto,
|
||||||
|
TspProviderReturnSendPayloadDto,
|
||||||
TspProviderRevokePayloadDto,
|
TspProviderRevokePayloadDto,
|
||||||
} from '../dto'
|
} from '../dto'
|
||||||
|
import { TspProviderCorrectionSendPayloadDto } from '../dto/correction.dto'
|
||||||
import { TspProviderActionResponseDto } from '../dto/provider-switch.dto'
|
import { TspProviderActionResponseDto } from '../dto/provider-switch.dto'
|
||||||
|
|
||||||
export async function getOriginalResendAttemptNumber(
|
export async function getOriginalResendAttemptNumber(
|
||||||
@@ -160,10 +161,12 @@ export async function buildRevokePayload(
|
|||||||
export async function buildCorrectionPayload(
|
export async function buildCorrectionPayload(
|
||||||
tx: Prisma.TransactionClient,
|
tx: Prisma.TransactionClient,
|
||||||
invoice_id: string,
|
invoice_id: string,
|
||||||
|
pos_id: string,
|
||||||
): Promise<TspProviderCorrectionSendPayloadDto> {
|
): Promise<TspProviderCorrectionSendPayloadDto> {
|
||||||
const invoice = await tx.salesInvoice.findUnique({
|
const invoice = await tx.salesInvoice.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: invoice_id,
|
id: invoice_id,
|
||||||
|
pos_id,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -323,6 +326,217 @@ export async function buildCorrectionPayload(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function buildReturnFromSalePayload(
|
||||||
|
prisma: PrismaService,
|
||||||
|
invoiceId: string,
|
||||||
|
posId: string,
|
||||||
|
): Promise<TspProviderReturnSendPayloadDto> {
|
||||||
|
const invoice = await prisma.salesInvoice.findUnique({
|
||||||
|
where: {
|
||||||
|
id: invoiceId,
|
||||||
|
pos_id: posId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
code: true,
|
||||||
|
total_amount: true,
|
||||||
|
invoice_date: true,
|
||||||
|
invoice_number: true,
|
||||||
|
settlement_type: true,
|
||||||
|
discount_amount: true,
|
||||||
|
tax_amount: true,
|
||||||
|
tax_id: true,
|
||||||
|
items: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
quantity: true,
|
||||||
|
unit_price: true,
|
||||||
|
total_amount: true,
|
||||||
|
measure_unit_code: true,
|
||||||
|
measure_unit_text: true,
|
||||||
|
sku_code: true,
|
||||||
|
sku_vat: true,
|
||||||
|
good_id: true,
|
||||||
|
service_id: true,
|
||||||
|
discount_amount: true,
|
||||||
|
tax_amount: true,
|
||||||
|
payload: true,
|
||||||
|
good_snapshot: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pos: {
|
||||||
|
select: {
|
||||||
|
complex: {
|
||||||
|
select: {
|
||||||
|
business_activity: {
|
||||||
|
select: {
|
||||||
|
fiscal_id: true,
|
||||||
|
economic_code: true,
|
||||||
|
partner_token: true,
|
||||||
|
guild: {
|
||||||
|
select: {
|
||||||
|
invoice_template: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
consumer: {
|
||||||
|
select: {
|
||||||
|
legal: {
|
||||||
|
select: {
|
||||||
|
partner: {
|
||||||
|
select: {
|
||||||
|
tsp_provider: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
individual: {
|
||||||
|
select: {
|
||||||
|
partner: {
|
||||||
|
select: {
|
||||||
|
tsp_provider: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
unknown_customer: true,
|
||||||
|
customer: {
|
||||||
|
select: {
|
||||||
|
type: true,
|
||||||
|
individual: true,
|
||||||
|
legal: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
payments: {
|
||||||
|
select: {
|
||||||
|
amount: true,
|
||||||
|
payment_method: true,
|
||||||
|
paid_at: true,
|
||||||
|
terminal_info: {
|
||||||
|
select: {
|
||||||
|
stan: true,
|
||||||
|
rrn: true,
|
||||||
|
transaction_date_time: true,
|
||||||
|
customer_card_no: true,
|
||||||
|
terminal_id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reference_invoice: {
|
||||||
|
select: {
|
||||||
|
tax_id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!invoice) {
|
||||||
|
throw new NotFoundException('صورتحساب مورد نظر یافت نشد.')
|
||||||
|
}
|
||||||
|
if (!invoice.reference_invoice) {
|
||||||
|
throw new NotFoundException('صورتحساب مرجع برای برگشت از فروش یافت نشد.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
pos,
|
||||||
|
id,
|
||||||
|
invoice_number,
|
||||||
|
invoice_date,
|
||||||
|
total_amount,
|
||||||
|
discount_amount,
|
||||||
|
tax_amount,
|
||||||
|
settlement_type,
|
||||||
|
} = invoice
|
||||||
|
const { business_activity: ba } = pos.complex
|
||||||
|
|
||||||
|
const unknown_customer = (invoice.unknown_customer || {}) as Record<string, string>
|
||||||
|
|
||||||
|
const { partner } = (ba.consumer.legal || ba.consumer.individual)!
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
invoice_number,
|
||||||
|
invoice_date,
|
||||||
|
settlement_type,
|
||||||
|
total_amount: Number(total_amount),
|
||||||
|
tax_amount: Number(tax_amount),
|
||||||
|
discount_amount: Number(discount_amount),
|
||||||
|
economic_code: ba.economic_code,
|
||||||
|
fiscal_id: ba.fiscal_id,
|
||||||
|
template: ba.guild.invoice_template,
|
||||||
|
token: ba.partner_token,
|
||||||
|
tsp_provider: partner.tsp_provider!,
|
||||||
|
last_tax_id: invoice.reference_invoice.tax_id!,
|
||||||
|
|
||||||
|
payments: invoice.payments.map(payment => ({
|
||||||
|
amount: Number(payment.amount),
|
||||||
|
payment_method: payment.payment_method,
|
||||||
|
paid_at: payment.paid_at,
|
||||||
|
terminal_info: {
|
||||||
|
card_number: payment.terminal_info?.customer_card_no
|
||||||
|
? payment.terminal_info.customer_card_no
|
||||||
|
: undefined,
|
||||||
|
tracking_code: payment.terminal_info ? payment.terminal_info.rrn : undefined,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
customer:
|
||||||
|
invoice.customer && invoice.customer.type !== CustomerType.UNKNOWN
|
||||||
|
? {
|
||||||
|
type: invoice.customer.type,
|
||||||
|
legal_info: invoice.customer.legal
|
||||||
|
? {
|
||||||
|
name: invoice.customer.legal.name ?? undefined,
|
||||||
|
registration_number:
|
||||||
|
invoice.customer.legal.registration_number ?? undefined,
|
||||||
|
postal_code: invoice.customer.legal.postal_code ?? undefined,
|
||||||
|
economic_code: invoice.customer.legal.economic_code ?? undefined,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
individual_info: invoice.customer.individual
|
||||||
|
? {
|
||||||
|
first_name: invoice.customer.individual.first_name ?? undefined,
|
||||||
|
last_name: invoice.customer.individual.last_name ?? undefined,
|
||||||
|
national_id: invoice.customer.individual.national_id ?? undefined,
|
||||||
|
mobile_number: invoice.customer.individual.mobile_number ?? undefined,
|
||||||
|
postal_code: invoice.customer.individual.postal_code ?? undefined,
|
||||||
|
economic_code: invoice.customer.individual.economic_code ?? undefined,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: CustomerType.UNKNOWN,
|
||||||
|
unknown_info: {
|
||||||
|
name: unknown_customer?.name || '',
|
||||||
|
economic_code: unknown_customer?.economic_code || '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: invoice.items.map(item => ({
|
||||||
|
invoice_item_id: item.id,
|
||||||
|
quantity: Number(item.quantity),
|
||||||
|
unit_price: Number(item.unit_price),
|
||||||
|
total_amount: Number(item.total_amount),
|
||||||
|
tax_amount: Number(tax_amount),
|
||||||
|
discount_amount: Number(discount_amount),
|
||||||
|
measure_unit: item.measure_unit_code,
|
||||||
|
sku: item.sku_code,
|
||||||
|
sku_vat: String(item.sku_vat),
|
||||||
|
discount: String((item.payload as Record<string, any>)?.discount || '0'),
|
||||||
|
good_id: item.good_id,
|
||||||
|
service_id: item.service_id,
|
||||||
|
good_snapshot: item.good_snapshot,
|
||||||
|
gold_type_payload: isGoldTypePayload(item.payload) ? item.payload : undefined,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function buildOriginalPayload(
|
export async function buildOriginalPayload(
|
||||||
prisma: PrismaService,
|
prisma: PrismaService,
|
||||||
invoiceId: string,
|
invoiceId: string,
|
||||||
@@ -419,6 +633,9 @@ export async function buildOriginalPayload(
|
|||||||
select: {
|
select: {
|
||||||
stan: true,
|
stan: true,
|
||||||
rrn: true,
|
rrn: true,
|
||||||
|
transaction_date_time: true,
|
||||||
|
customer_card_no: true,
|
||||||
|
terminal_id: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -465,7 +682,9 @@ export async function buildOriginalPayload(
|
|||||||
payment_method: payment.payment_method,
|
payment_method: payment.payment_method,
|
||||||
paid_at: payment.paid_at,
|
paid_at: payment.paid_at,
|
||||||
terminal_info: {
|
terminal_info: {
|
||||||
card_number: payment.terminal_info ? payment.terminal_info.stan : undefined,
|
card_number: payment.terminal_info?.customer_card_no
|
||||||
|
? payment.terminal_info.customer_card_no
|
||||||
|
: undefined,
|
||||||
tracking_code: payment.terminal_info ? payment.terminal_info.rrn : undefined,
|
tracking_code: payment.terminal_info ? payment.terminal_info.rrn : undefined,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
|
import { prismaAdapter } from '@/lib/prisma'
|
||||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
|
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'
|
||||||
import { PrismaMariaDb } from '@prisma/adapter-mariadb'
|
|
||||||
import 'dotenv/config'
|
import 'dotenv/config'
|
||||||
import { env } from 'prisma/config'
|
|
||||||
import { PrismaClient } from '../generated/prisma/client'
|
import { PrismaClient } from '../generated/prisma/client'
|
||||||
import { getPrismaOptions } from './prisma-config.service'
|
import { getPrismaOptions } from './prisma-config.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||||
constructor() {
|
constructor() {
|
||||||
const adapter = new PrismaMariaDb({
|
const adapter = prismaAdapter
|
||||||
host: env('DATABASE_HOST'),
|
|
||||||
user: env('DATABASE_USER'),
|
|
||||||
password: env('DATABASE_PASSWORD'),
|
|
||||||
database: env('DATABASE_NAME'),
|
|
||||||
port: env('DATABASE_PORT') ? Number(env('DATABASE_PORT')) : 3306,
|
|
||||||
connectionLimit: 10,
|
|
||||||
})
|
|
||||||
|
|
||||||
const prismaOptions = getPrismaOptions()
|
const prismaOptions = getPrismaOptions()
|
||||||
super({ ...prismaOptions, adapter })
|
super({ ...prismaOptions, adapter })
|
||||||
|
|||||||
@@ -252,6 +252,17 @@ export class RedisService implements OnModuleDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onModuleDestroy() {
|
async onModuleDestroy() {
|
||||||
|
if (this.client.status === 'end') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
await this.client.quit()
|
await this.client.quit()
|
||||||
|
} catch (error) {
|
||||||
|
const message = (error as Error)?.message || ''
|
||||||
|
if (!message.includes('Connection is closed')) {
|
||||||
|
this.logger.warn(`Redis shutdown failed: ${message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user