feat: refactor invoice creation logic to improve validation and streamline item processing
This commit is contained in:
parent
2905983bfd
commit
647e1e097a
2484
backend/package-lock.json
generated
2484
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -25,11 +25,14 @@
|
|||
"description": "",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.2.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"debug": "^4.4.0",
|
||||
"decimal.js": "^10.4.3",
|
||||
"dotenv": "^16.4.7",
|
||||
"install": "^0.13.0",
|
||||
"koa": "^2.15.3",
|
||||
"koa-body": "^6.0.1",
|
||||
"koa-route": "^4.0.1"
|
||||
"koa-route": "^4.0.1",
|
||||
"npm": "^11.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import koaBody from 'koa-body'
|
|||
import func from './func'
|
||||
import { ErrorDescEnum, HttpError } from './classes/HttpError'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import InvoiceItem from './classes/InvoiceItem'
|
||||
|
||||
dotenv.config()
|
||||
console.log = Debug('invoiceIssuer:app.ts')
|
||||
|
@ -77,32 +78,37 @@ app.use(route.post('/invoice', async (ctx) => {
|
|||
// TODO: 请求头验证 bearer token
|
||||
|
||||
// 提取字段,并验证必填字段
|
||||
const { payerId, period, items, dueDate, note } = ctx.request.body
|
||||
if (!ctx.request.body) throw new HttpError(ErrorDescEnum.required_fields_missing, 400, ['payerId', 'period', 'items', 'dueDate'])
|
||||
const { payerId, period: periodRaw, items: itemsRaw, dueDate: dueDateRaw, note } = ctx.request.body
|
||||
let emptyFields = []
|
||||
if (!payerId) emptyFields.push('payerId')
|
||||
if (!period) emptyFields.push('period')
|
||||
if (!items) emptyFields.push('items')
|
||||
if (!dueDate) emptyFields.push('dueDate')
|
||||
if (!periodRaw) emptyFields.push('period')
|
||||
if (!itemsRaw) emptyFields.push('items')
|
||||
if (!dueDateRaw) emptyFields.push('dueDate')
|
||||
if (emptyFields.length > 0) throw new HttpError(ErrorDescEnum.required_fields_missing, 400, emptyFields)
|
||||
|
||||
// 验证 payerId 必须是数字
|
||||
if (typeof payerId !== 'number') throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['payerId'])
|
||||
|
||||
// 验证 period 必须是两个日期的数组
|
||||
if (!Array.isArray(period) || period.length !== 2) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['period'])
|
||||
if (!Array.isArray(periodRaw) || periodRaw.length !== 2) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['period'])
|
||||
|
||||
// Period 中的日期格式为 YYYY-MM-DD,且第一个日期早于第二个日期
|
||||
const dateRegex = /^\d{4}-\d{2}-\d{2}$/
|
||||
if (!dateRegex.test(period[0]) || !dateRegex.test(period[1])) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['period'])
|
||||
if (new Date(period[0]) > new Date(period[1])) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['period'])
|
||||
if (!dateRegex.test(periodRaw[0]) || !dateRegex.test(periodRaw[1])) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['period'])
|
||||
if (new Date(periodRaw[0]) > new Date(periodRaw[1])) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['period'])
|
||||
const period = [
|
||||
new Date(periodRaw[0].split('-')[0], periodRaw[0].split('-')[1] - 1, periodRaw[0].split('-')[2]),
|
||||
new Date(periodRaw[1].split('-')[0], periodRaw[1].split('-')[1] - 1, periodRaw[1].split('-')[2])
|
||||
]
|
||||
|
||||
// 验证 items 必须是数组
|
||||
if (!Array.isArray(items)) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['items'])
|
||||
if (!Array.isArray(itemsRaw)) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['items'])
|
||||
|
||||
// 验证 items 中的每一项必须是指定格式的对象
|
||||
// description: string, quantity: number, unit: string, unitPrice: Decimal
|
||||
const itemFields = ['description', 'quantity', 'unit', 'unitPrice']
|
||||
items.forEach((item, index) => {
|
||||
itemsRaw.forEach((item, index) => {
|
||||
itemFields.forEach((field) => {
|
||||
if (!item[field]) throw new HttpError(ErrorDescEnum.required_fields_missing, 400, [`items[${index}].${field}`])
|
||||
})
|
||||
|
@ -113,13 +119,18 @@ app.use(route.post('/invoice', async (ctx) => {
|
|||
})
|
||||
|
||||
// 验证 dueDate 必须是日期格式
|
||||
if (!dateRegex.test(dueDate)) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['dueDate'])
|
||||
if (!dateRegex.test(dueDateRaw)) throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['dueDate'])
|
||||
const dueDate = new Date(dueDateRaw.split('-')[0], dueDateRaw.split('-')[1] - 1, dueDateRaw.split('-')[2])
|
||||
|
||||
// 验证 note 必须是字符串
|
||||
if (note && typeof note !== 'string') throw new HttpError(ErrorDescEnum.invalid_field_format, 400, ['note'])
|
||||
|
||||
// 创建新的收据
|
||||
const items = itemsRaw.map(item => {
|
||||
return new InvoiceItem(0, item['description'], item['quantity'], item['unit'], item['unitPrice'])
|
||||
})
|
||||
await func.issueInvoice(ctx.prisma, payerId, period, items, dueDate, note)
|
||||
ctx.status = 204
|
||||
}))
|
||||
|
||||
const port = parseInt(process.env.PORT ?? '3000')
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { PrismaClient } from "@prisma/client"
|
||||
import InvoiceItem from "./InvoiceItem"
|
||||
import Debug from 'debug'
|
||||
|
||||
console.log = Debug('invoiceIssuer:classes/Invoice.ts')
|
||||
|
||||
class Invoice {
|
||||
/**
|
||||
|
@ -11,11 +14,13 @@ class Invoice {
|
|||
*
|
||||
*/
|
||||
constructor(suffixCode: number, issueDate: Date, period: Date[], items: InvoiceItem[], dueDate: Date, payer: { id: number; name: string; address: string; abn?: string; email?: string }) {
|
||||
console.log("hello")
|
||||
// 设置收据后缀
|
||||
this.suffixCode = suffixCode
|
||||
// 设置签发时间
|
||||
this.issueTime = issueDate
|
||||
// 设置账单周期
|
||||
console.log(period)
|
||||
// 首先验证周期有两个日期,且第一个日期早于第二个日期
|
||||
if (period.length !== 2) throw new Error('Period should have two dates.')
|
||||
if (period[0] > period[1]) throw new Error('The first date should be earlier than the second date.')
|
||||
|
@ -23,6 +28,7 @@ class Invoice {
|
|||
// 设置项目列表
|
||||
this.items = items
|
||||
// 设置收款日期
|
||||
console.log(dueDate)
|
||||
this.dueDate = dueDate
|
||||
// 设置付款人
|
||||
this.payer = payer
|
||||
|
@ -133,6 +139,7 @@ class Invoice {
|
|||
|
||||
// 保存收据项目
|
||||
await Promise.all(this.items.map(async (item) => {
|
||||
const unitPrice = typeof item.unitPrice === 'number' ? item.unitPrice : Number(item.unitPrice);
|
||||
await prisma.invoiceItem.create({
|
||||
data: {
|
||||
invoice_date: this.issueTime,
|
||||
|
@ -141,7 +148,7 @@ class Invoice {
|
|||
item_description: item.description ?? '',
|
||||
item_quantity: item.quantity ?? 0,
|
||||
item_unit: item.unit ?? '',
|
||||
item_unit_price: item.unitPrice ? item.unitPrice.toNumber() : 0,
|
||||
item_unit_price: unitPrice,
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -46,8 +46,7 @@ class InvoiceItem {
|
|||
* 获取项目的小计。
|
||||
*/
|
||||
getSubtotal() {
|
||||
if (!this.quantity || !this.unitPrice) return 0
|
||||
return this.quantity * this.unitPrice.toNumber()
|
||||
return (this.unitPrice ? Number(this.unitPrice) : 0) * (this.quantity ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,26 +24,9 @@ export default async (prisma: PrismaClient, payerId: number, period: Date[], ite
|
|||
if (!payer) throw new HttpError(ErrorDescEnum.related_item_not_found, 400, ['payer_id'])
|
||||
|
||||
// 创建新收据
|
||||
// 获取当天最大收据后缀
|
||||
const today = new Date()
|
||||
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
||||
const todayEnd = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1)
|
||||
const maxSuffix = await prisma.invoice.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{ invoice_date: { gte: todayStart } },
|
||||
{ invoice_date: { lt: todayEnd } }
|
||||
]
|
||||
},
|
||||
orderBy: {
|
||||
invoice_suffix_code: 'desc'
|
||||
}
|
||||
})
|
||||
|
||||
// 创建收据实现
|
||||
const newInvoice = new Invoice(
|
||||
maxSuffix ? maxSuffix.invoice_suffix_code + 1 : 1,
|
||||
today,
|
||||
0,
|
||||
new Date(),
|
||||
period,
|
||||
items,
|
||||
dueDate,
|
||||
|
@ -55,4 +38,6 @@ export default async (prisma: PrismaClient, payerId: number, period: Date[], ite
|
|||
email: payer.payer_email
|
||||
}
|
||||
)
|
||||
if (note) await newInvoice.setNote(note)
|
||||
await newInvoice.save(prisma)
|
||||
}
|
Loading…
Reference in New Issue
Block a user