feat(报销/拨款): 发票明细与附件双向联动
- 移除手动添加条目按钮,条目只能通过上传文件产生 - 删除附件时同步移除该文件的所有明细条目 - 删除条目时同步从附件列表移除对应文件(含该文件下全部条目) - 无明细时隐藏明细表格区域 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -54,8 +54,9 @@
|
|||||||
<!-- 拨款单据附件(OCR触发区) -->
|
<!-- 拨款单据附件(OCR触发区) -->
|
||||||
<el-form-item label="拨款单据附件" prop="accessoryApplyIds">
|
<el-form-item label="拨款单据附件" prop="accessoryApplyIds">
|
||||||
<file-upload v-model="form.accessoryApplyIds" :limit="50" :file-size="50"
|
<file-upload v-model="form.accessoryApplyIds" :limit="50" :file-size="50"
|
||||||
:file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']" multiple />
|
:file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']" multiple
|
||||||
<div class="hint-text">上传发票、收据、付款截图等(支持 PDF/图片,上传后自动识别)</div>
|
@delete="onFileDelete" />
|
||||||
|
<div class="hint-text">上传发票、收据、付款截图等(支持 PDF/图片,上传后自动识别明细)</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- OCR 识别中提示 -->
|
<!-- OCR 识别中提示 -->
|
||||||
@@ -67,9 +68,9 @@
|
|||||||
<!-- 发票明细条目表 -->
|
<!-- 发票明细条目表 -->
|
||||||
<div class="block-title">
|
<div class="block-title">
|
||||||
拨款明细
|
拨款明细
|
||||||
<span class="block-title-hint">(上传发票后自动填充,也可手动添加)</span>
|
<span class="block-title-hint">(上传发票后自动识别,删除条目将同步移除对应文件)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-table">
|
<div class="invoice-table" v-if="invoiceItems.length">
|
||||||
<div class="invoice-table-header">
|
<div class="invoice-table-header">
|
||||||
<span class="col-reason">事由说明</span>
|
<span class="col-reason">事由说明</span>
|
||||||
<span class="col-amount">金额(元)</span>
|
<span class="col-amount">金额(元)</span>
|
||||||
@@ -95,8 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-table-footer">
|
<div class="invoice-table-footer">
|
||||||
<el-button size="mini" type="primary" plain icon="el-icon-plus" @click="addInvoiceItem">添加条目</el-button>
|
<span class="total-hint">
|
||||||
<span class="total-hint" v-if="invoiceItems.length">
|
|
||||||
合计:<b>¥{{ invoiceTotalFormatted }}</b>(已自动更新拨款总金额)
|
合计:<b>¥{{ invoiceTotalFormatted }}</b>(已自动更新拨款总金额)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -350,23 +350,27 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const res = await ocrAppropriationInvoice(ossId)
|
const res = await ocrAppropriationInvoice(ossId)
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
const { items, totalAmount } = res.data
|
const { items, totalAmount, sellerName, invoiceDate, invoiceType } = res.data
|
||||||
|
// 拼接发票头部信息作为事由前缀:发票类型 · 销售方 · 开票日期
|
||||||
|
const prefix = [invoiceType, sellerName, invoiceDate].filter(Boolean).join(' · ')
|
||||||
if (items && items.length) {
|
if (items && items.length) {
|
||||||
const startIdx = this.invoiceItems.length
|
const startIdx = this.invoiceItems.length
|
||||||
items.forEach((item, i) => {
|
items.forEach((item, i) => {
|
||||||
|
const reason = [prefix, item.itemName].filter(Boolean).join(' / ')
|
||||||
this.invoiceItems.push({
|
this.invoiceItems.push({
|
||||||
ossId: Number(ossId),
|
ossId: Number(ossId),
|
||||||
itemName: item.itemName || '',
|
itemName: item.itemName || '',
|
||||||
reason: item.itemName || '',
|
reason,
|
||||||
amount: item.amount || 0,
|
amount: item.amount || 0,
|
||||||
sortNo: startIdx + i
|
sortNo: startIdx + i
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else if (totalAmount) {
|
} else if (totalAmount) {
|
||||||
|
// 没有明细时用总金额创建一条,事由取发票头部信息
|
||||||
this.invoiceItems.push({
|
this.invoiceItems.push({
|
||||||
ossId: Number(ossId),
|
ossId: Number(ossId),
|
||||||
itemName: '',
|
itemName: sellerName || '',
|
||||||
reason: '',
|
reason: prefix || '',
|
||||||
amount: totalAmount,
|
amount: totalAmount,
|
||||||
sortNo: this.invoiceItems.length
|
sortNo: this.invoiceItems.length
|
||||||
})
|
})
|
||||||
@@ -382,12 +386,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addInvoiceItem () {
|
removeInvoiceItem (idx) {
|
||||||
this.invoiceItems.push({ ossId: null, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length })
|
const item = this.invoiceItems[idx]
|
||||||
|
if (item.ossId) {
|
||||||
|
// 删除该文件下所有条目
|
||||||
|
const ossId = String(item.ossId)
|
||||||
|
this.invoiceItems = this.invoiceItems.filter(it => String(it.ossId) !== ossId)
|
||||||
|
// 从附件 CSV 中移除该文件
|
||||||
|
const ids = (this.form.accessoryApplyIds || '').split(',').filter(id => id && id !== ossId)
|
||||||
|
this.form.accessoryApplyIds = ids.join(',')
|
||||||
|
} else {
|
||||||
|
this.invoiceItems.splice(idx, 1)
|
||||||
|
}
|
||||||
|
this.recalcTotal()
|
||||||
},
|
},
|
||||||
|
|
||||||
removeInvoiceItem (idx) {
|
onFileDelete (deletedFile) {
|
||||||
this.invoiceItems.splice(idx, 1)
|
const ossId = String(deletedFile.ossId)
|
||||||
|
this.invoiceItems = this.invoiceItems.filter(item => String(item.ossId) !== ossId)
|
||||||
this.recalcTotal()
|
this.recalcTotal()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,9 @@
|
|||||||
<!-- 报销单据附件(OCR触发区) -->
|
<!-- 报销单据附件(OCR触发区) -->
|
||||||
<el-form-item label="报销单据附件" prop="accessoryApplyIds">
|
<el-form-item label="报销单据附件" prop="accessoryApplyIds">
|
||||||
<file-upload v-model="form.accessoryApplyIds" :limit="200" :file-size="50"
|
<file-upload v-model="form.accessoryApplyIds" :limit="200" :file-size="50"
|
||||||
:file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']" multiple />
|
:file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']" multiple
|
||||||
<div class="hint-text">上传发票、收据、付款截图等(支持 PDF/图片,上传后自动识别)</div>
|
@delete="onFileDelete" />
|
||||||
|
<div class="hint-text">上传发票、收据、付款截图等(支持 PDF/图片,上传后自动识别明细)</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- OCR 识别中提示 -->
|
<!-- OCR 识别中提示 -->
|
||||||
@@ -67,9 +68,9 @@
|
|||||||
<!-- 发票明细条目表 -->
|
<!-- 发票明细条目表 -->
|
||||||
<div class="block-title">
|
<div class="block-title">
|
||||||
发票明细
|
发票明细
|
||||||
<span class="block-title-hint">(上传发票后自动填充,也可手动添加)</span>
|
<span class="block-title-hint">(上传发票后自动识别,删除条目将同步移除对应文件)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-table">
|
<div class="invoice-table" v-if="invoiceItems.length">
|
||||||
<div class="invoice-table-header">
|
<div class="invoice-table-header">
|
||||||
<span class="col-reason">事由说明</span>
|
<span class="col-reason">事由说明</span>
|
||||||
<span class="col-amount">金额(元)</span>
|
<span class="col-amount">金额(元)</span>
|
||||||
@@ -95,8 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="invoice-table-footer">
|
<div class="invoice-table-footer">
|
||||||
<el-button size="mini" type="primary" plain icon="el-icon-plus" @click="addInvoiceItem">添加条目</el-button>
|
<span class="total-hint">
|
||||||
<span class="total-hint" v-if="invoiceItems.length">
|
|
||||||
合计:<b>¥{{ invoiceTotalFormatted }}</b>(已自动更新报销总金额)
|
合计:<b>¥{{ invoiceTotalFormatted }}</b>(已自动更新报销总金额)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -325,24 +325,27 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const res = await ocrReimburseInvoice(ossId)
|
const res = await ocrReimburseInvoice(ossId)
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
const { items, totalAmount } = res.data
|
const { items, totalAmount, sellerName, invoiceDate, invoiceType } = res.data
|
||||||
|
// 拼接发票头部信息作为事由前缀:发票类型 · 销售方 · 开票日期
|
||||||
|
const prefix = [invoiceType, sellerName, invoiceDate].filter(Boolean).join(' · ')
|
||||||
if (items && items.length) {
|
if (items && items.length) {
|
||||||
const startIdx = this.invoiceItems.length
|
const startIdx = this.invoiceItems.length
|
||||||
items.forEach((item, i) => {
|
items.forEach((item, i) => {
|
||||||
|
const reason = [prefix, item.itemName].filter(Boolean).join(' / ')
|
||||||
this.invoiceItems.push({
|
this.invoiceItems.push({
|
||||||
ossId: Number(ossId),
|
ossId: Number(ossId),
|
||||||
itemName: item.itemName || '',
|
itemName: item.itemName || '',
|
||||||
reason: item.itemName || '',
|
reason,
|
||||||
amount: item.amount || 0,
|
amount: item.amount || 0,
|
||||||
sortNo: startIdx + i
|
sortNo: startIdx + i
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else if (totalAmount) {
|
} else if (totalAmount) {
|
||||||
// 没有明细时用总金额创建一条
|
// 没有明细时用总金额创建一条,事由取发票头部信息
|
||||||
this.invoiceItems.push({
|
this.invoiceItems.push({
|
||||||
ossId: Number(ossId),
|
ossId: Number(ossId),
|
||||||
itemName: '',
|
itemName: sellerName || '',
|
||||||
reason: '',
|
reason: prefix || '',
|
||||||
amount: totalAmount,
|
amount: totalAmount,
|
||||||
sortNo: this.invoiceItems.length
|
sortNo: this.invoiceItems.length
|
||||||
})
|
})
|
||||||
@@ -358,12 +361,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addInvoiceItem () {
|
removeInvoiceItem (idx) {
|
||||||
this.invoiceItems.push({ ossId: null, itemName: '', reason: '', amount: 0, sortNo: this.invoiceItems.length })
|
const item = this.invoiceItems[idx]
|
||||||
|
if (item.ossId) {
|
||||||
|
// 删除该文件下所有条目
|
||||||
|
const ossId = String(item.ossId)
|
||||||
|
this.invoiceItems = this.invoiceItems.filter(it => String(it.ossId) !== ossId)
|
||||||
|
// 从附件 CSV 中移除该文件
|
||||||
|
const ids = (this.form.accessoryApplyIds || '').split(',').filter(id => id && id !== ossId)
|
||||||
|
this.form.accessoryApplyIds = ids.join(',')
|
||||||
|
} else {
|
||||||
|
this.invoiceItems.splice(idx, 1)
|
||||||
|
}
|
||||||
|
this.recalcTotal()
|
||||||
},
|
},
|
||||||
|
|
||||||
removeInvoiceItem (idx) {
|
onFileDelete (deletedFile) {
|
||||||
this.invoiceItems.splice(idx, 1)
|
const ossId = String(deletedFile.ossId)
|
||||||
|
this.invoiceItems = this.invoiceItems.filter(item => String(item.ossId) !== ossId)
|
||||||
this.recalcTotal()
|
this.recalcTotal()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user