feat: 积分商城-供应商-对账-发票4

This commit is contained in:
2026-09-08 20:12:40 +08:00
parent f42ea5b270
commit ff77ba16cc
39 changed files with 705 additions and 136 deletions
+46 -10
View File
@@ -324,6 +324,32 @@ export const generateMockInvoicePdf = (invoiceInput, outputPath, _options = {})
const invoiceDate = dateValue.replace(/^(\d{4})-(\d{2})-(\d{2}).*$/, '$1年$2月$3日')
const title = Number(invoice.invoiceType) === 9 ? '电子发票(增值税专用发票)' : '电子发票(普通发票)'
const products = Array.isArray(invoice.orderProducts) && invoice.orderProducts.length
? invoice.orderProducts
: [{ name: invoice.invoiceContent, number: invoice.orderNum, price: invoice.total / invoice.orderNum, unit: '项' }]
const weights = products.map(product => Math.max(Number(product.price || 0) * Number(product.number || 1), 0))
const totalWeight = weights.reduce((sum, value) => sum + value, 0)
const nakedCents = Math.round(invoice.naked * 100)
const taxCents = Math.round(invoice.tax * 100)
let accumulatedWeight = 0
let allocatedNaked = 0
let allocatedTax = 0
// 按商品金额分摊票面金额,累计取整以消除逐行计算产生的分位尾差。
const invoiceItems = products.map((product, index) => {
accumulatedWeight += weights[index]
const ratio = totalWeight > 0 ? accumulatedWeight / totalWeight : (index + 1) / products.length
const nextNaked = index === products.length - 1 ? nakedCents : Math.round(nakedCents * ratio)
const nextTax = index === products.length - 1 ? taxCents : Math.round(taxCents * ratio)
const item = { ...product, number: Number(product.number || 1), naked: (nextNaked - allocatedNaked) / 100, tax: (nextTax - allocatedTax) / 100 }
allocatedNaked = nextNaked
allocatedTax = nextTax
return item
})
const pages = []
const itemsPerPage = 5
for (let itemOffset = 0; itemOffset < invoiceItems.length; itemOffset += itemsPerPage) {
commands = []
const lastItemPage = itemOffset + itemsPerPage >= invoiceItems.length
commands.push('1 1 1 rg 0 0 842 595 re f')
text(title, (PAGE_WIDTH - title.length * 21) / 2, 546, 21, labelColor)
line(245, 531, 590, 531, 0.8, labelColor)
@@ -358,22 +384,30 @@ export const generateMockInvoicePdf = (invoiceInput, outputPath, _options = {})
const x = (columns[index] + columns[index + 1] - label.length * 9) / 2
text(label, x, 382, 9, labelColor)
})
wrappedText(`*商品*${String(invoice.invoiceContent).length > 50 ? '订单商品汇总(详见后附明细)' : invoice.invoiceContent}`, 50, 360, 20, 9, 14)
text('项', 344, 360, 9)
fitText(invoice.orderNum, 398, 360, 32, 9)
fitText(money(invoice.naked / invoice.orderNum), 445, 360, 60, 9)
fitText(money(invoice.naked), 526, 360, 73, 9)
fitText(`${Number((invoice.taxRate * 100).toFixed(4))}%`, 627, 360, 60, 9)
fitText(money(invoice.tax), 722, 360, 70, 9)
invoiceItems.slice(itemOffset, itemOffset + itemsPerPage).forEach((item, index) => {
const y = 360 - index * 23
fitText(`*商品*${item.name || item.sku || '商品'}`, 50, y, 184, 9)
fitText(item.spec || item.skuText || '', 247, y, 69, 8)
fitText(item.unit || '件', 337, y, 40, 9)
fitText(item.number, 391, y, 40, 9)
fitText(Number((item.naked / item.number).toFixed(6)).toString(), 445, y, 60, 9)
fitText(money(item.naked), 526, y, 73, 9)
fitText(`${Number((invoice.taxRate * 100).toFixed(4))}%`, 627, y, 60, 9)
fitText(money(item.tax), 722, y, 70, 9)
})
if (lastItemPage) {
text('合 计', 92, 230, 10, labelColor)
fitText(`¥${money(invoice.naked)}`, 526, 230, 73, 10)
fitText(`¥${money(invoice.tax)}`, 722, 230, 70, 10)
} else {
text('项目明细续下页,合计见末页', 92, 230, 10, labelColor)
}
line(235, 183, 235, 217, 0.7, labelColor)
text('价税合计(大写)', 88, 196, 10, labelColor)
fitText(chineseAmount(invoice.total), 245, 196, 340, 11)
fitText(lastItemPage ? chineseAmount(invoice.total) : '详见项目明细末页', 245, 196, 340, 11)
text('(小写)', 603, 196, 10, labelColor)
fitText(`¥${money(invoice.total)}`, 657, 196, 133, 11)
if (lastItemPage) fitText(`¥${money(invoice.total)}`, 657, 196, 133, 11)
line(70, 100, 70, 183, 0.7, labelColor)
text('备', 51, 153, 10, labelColor)
@@ -384,7 +418,9 @@ export const generateMockInvoicePdf = (invoiceInput, outputPath, _options = {})
text('开票人:', 90, 73, 10, labelColor)
fitText(invoice.biller || '模拟开票员', 140, 73, 250, 10)
const pages = [Buffer.from(commands.join('\n'), 'ascii')]
text(`项目明细第 ${pages.length + 1} / ${Math.ceil(invoiceItems.length / itemsPerPage)} 页`, 620, 73, 9, labelColor)
pages.push(Buffer.from(commands.join('\n'), 'ascii'))
}
const rows = (Array.isArray(invoice.orderProducts) ? invoice.orderProducts : []).flatMap(product => {
const name = Array.from(String(product.name || product.sku || '商品'))
const lines = []