登记收款选择账单直接核销,附件上传支持拖动上传

This commit is contained in:
wuweihua
2026-10-09 10:09:50 +08:00
parent c95a9861cc
commit 95df65b4f6
14 changed files with 800 additions and 20 deletions
@@ -531,7 +531,12 @@
</el-form-item>
</el-form>
<el-table :data="writeoffList" border size="small" v-loading="writeoffLoading">
<el-table-column label="核销单号" align="center" prop="writeoffNo" width="180" />
<el-table-column label="核销单号" align="center" width="190">
<template #default="scope">
<div>{{ scope.row.writeoffNo }}</div>
<el-tag v-if="scope.row.receiptId" type="primary" size="small" effect="plain" style="margin-top: 2px;">收款联动</el-tag>
</template>
</el-table-column>
<el-table-column label="账单号" align="center" prop="billNo" width="170" show-overflow-tooltip />
<el-table-column label="账单名称" align="center" prop="billName" min-width="140" show-overflow-tooltip />
<el-table-column label="核销金额" align="center" width="120">
@@ -574,7 +579,7 @@
</el-card>
<!-- ====== 登记收款弹窗 ====== -->
<el-dialog title="登记收款" v-model="receiptDialog.visible" width="640px" append-to-body @closed="onReceiptDialogClosed">
<el-dialog title="登记收款" v-model="receiptDialog.visible" width="780px" append-to-body @closed="onReceiptDialogClosed">
<el-form ref="receiptFormRef" :model="receiptDialog.form" :rules="receiptRules" label-width="110px">
<el-row>
<el-col :span="12">
@@ -665,6 +670,54 @@
<el-input v-model="receiptDialog.form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="核销账单">
<div class="receipt-wo-toolbar">
<el-button type="primary" plain icon="Plus" @click="openBillPicker">选择账单核销</el-button>
<span class="receipt-wo-summary">
本次核销合计:<b class="money-out">{{ formatMoney(receiptWriteoffTotal) }} CNY</b>
,收款折算本位币:<b>{{ formatMoney(receiptBaseCny) }} CNY</b>
<!-- ,核销后剩余入账:<b :class="receiptWriteoffRemain < 0 ? 'money-out' : 'money-in'">-->
<!-- {{ formatMoney(receiptWriteoffRemain) }} CNY-->
<!-- </b>-->
</span>
</div>
<el-table :data="receiptDialog.form.writeoffPlans" border size="small" max-height="220">
<el-table-column label="账单号" prop="billNo" align="center" width="170" show-overflow-tooltip />
<el-table-column label="账单名称" prop="billName" align="center" min-width="150" show-overflow-tooltip />
<el-table-column label="未核销金额" align="center" width="120">
<template #default="scope">{{ formatMoney(scope.row.unwrittenAmount) }} {{ scope.row.currency }}</template>
</el-table-column>
<el-table-column label="本次核销金额(CNY)" align="center" width="160">
<template #default="scope">
<el-input-number
v-model="scope.row.writeoffAmount"
:min="0"
:max="Number(scope.row.unwrittenAmount)"
:precision="2"
:step="1"
:controls="false"
size="small"
style="width: 130px;"
/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="70">
<template #default="scope">
<el-button link type="danger" icon="Delete" @click="removeReceiptBill(scope.$index)" />
</template>
</el-table-column>
<template #empty>
<span style="color: var(--el-text-color-secondary);">不选账单则整笔计入客户余额,可在“账单核销”页手工核销</span>
</template>
</el-table>
<div class="upload-tip">
当前仅支持<strong>人民币(CNY)账单</strong>核销,账单行金额均按 CNY 填写;
所选账单在收款单<strong>审核通过</strong>后一并核销,核销合计不能超过收款金额按汇率折算的本位币金额
({{ formatMoney(receiptBaseCny) }} CNY);<strong>核销不扣减客户余额</strong>,正常计入客户余额。
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="收款凭证">
<el-upload
@@ -747,6 +800,27 @@
<el-descriptions-item label="备注" :span="2">{{ viewDialog.data.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<template v-if="viewDialog.data && viewDialog.data.writeoffPlans && viewDialog.data.writeoffPlans.length">
<div class="attach-title">关联核销账单({{ viewDialog.data.writeoffPlans.length }})</div>
<el-table :data="viewDialog.data.writeoffPlans" border size="small" max-height="220" style="margin-bottom: 12px;">
<el-table-column label="账单号" prop="billNo" align="center" width="170" show-overflow-tooltip />
<el-table-column label="账单名称" prop="billName" align="center" min-width="150" show-overflow-tooltip />
<el-table-column label="本次核销金额" align="center" width="140">
<template #default="scope">{{ formatMoney(scope.row.writeoffAmount) }} {{ scope.row.currency }}</template>
</el-table-column>
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-tag :type="receiptPlanStatusTag(scope.row.status)" size="small">
{{ receiptPlanStatusLabel(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="核销日期" align="center" width="110">
<template #default="scope">{{ scope.row.writeoffDate ? formatDate(scope.row.writeoffDate) : '-' }}</template>
</el-table-column>
</el-table>
</template>
<div class="attach-title">收款凭证附件</div>
<el-table :data="viewDialog.attachments" border size="small" max-height="240" v-loading="viewDialog.loading">
<el-table-column label="序号" type="index" width="55" align="center" />
@@ -913,6 +987,49 @@
</template>
</el-dialog>
<!-- ====== 登记收款-选择核销账单弹窗 ====== -->
<el-dialog title="选择待核销账单" v-model="billPicker.visible" width="860px" append-to-body>
<el-alert type="info" :closable="false" class="writeoff-summary">
当前仅支持人民币(CNY)账单核销,以下为状态已确认/部分核销的 CNY 账单;
已选 {{ billPicker.selected.length }} 张,合计未核销
<b class="money-out">{{ formatMoney(billPickerSelectedTotal) }} CNY</b>,
本收款单折算可核销上限 <b>{{ formatMoney(receiptBaseCny) }} CNY</b>
</el-alert>
<el-table
ref="billPickerTableRef"
:data="billPicker.rows"
border
size="small"
max-height="380"
row-key="arBillId"
v-loading="billPicker.loading"
@selection-change="onPickerSelectionChange"
>
<el-table-column type="selection" width="42" align="center" reserve-selection />
<el-table-column label="账单号" prop="billNo" align="center" width="160" show-overflow-tooltip />
<el-table-column label="账单名称" prop="billName" align="center" min-width="150" show-overflow-tooltip />
<el-table-column label="账单日期" prop="billDate" align="center" width="105">
<template #default="scope">{{ formatDate(scope.row.billDate) }}</template>
</el-table-column>
<el-table-column label="应收金额" align="center" width="110">
<template #default="scope">{{ formatMoney(scope.row.totalAmount) }} {{ scope.row.currency }}</template>
</el-table-column>
<el-table-column label="已核销" align="center" width="100">
<template #default="scope"><span class="money-in">{{ formatMoney(scope.row.writtenOffAmount) }}</span></template>
</el-table-column>
<el-table-column label="未核销" align="center" width="100">
<template #default="scope"><span class="money-out">{{ formatMoney(scope.row.unwrittenAmount) }}</span></template>
</el-table-column>
<template #empty>
<el-empty description="该币种暂无可核销账单" :image-size="70" />
</template>
</el-table>
<template #footer>
<el-button type="primary" @click="confirmBillPicker">确 定({{ billPicker.selected.length }})</el-button>
<el-button @click="billPicker.visible = false">取 消</el-button>
</template>
</el-dialog>
<!-- ====== 余额核销账单弹窗 ====== -->
<el-dialog title="余额核销账单" v-model="woDialog.visible" width="560px" append-to-body v-loading="woDialog.accountLoading">
<el-descriptions :column="1" border size="small" v-if="woDialog.bill">
@@ -966,7 +1083,10 @@
<el-alert type="warning" :closable="false" class="credit-tip">
<span v-if="cancelWoDialog.row">
反核销后,账单「{{ cancelWoDialog.row.billNo }}」恢复为未核销状态(核销金额 CNY {{ formatMoney(cancelWoDialog.row.writeoffAmount) }});
口径变更后不再回补账户余额、不再变动已用额度。
不回补账户余额、不变动已用额度。
<template v-if="cancelWoDialog.row.receiptId">
<br />该核销由收款单审核<strong>联动产生</strong>,反核销后收款单中本笔将回到“未分配”金额,可重新核销其他账单,已入账余额不受影响。
</template>
</span>
</el-alert>
<el-form label-width="100px" class="wo-form">
@@ -1386,12 +1506,13 @@ function handleExportCreditFlow() {
/** 审核通过 */
function handleConfirmReceipt(row) {
proxy.$modal.confirm(`是否确认审核通过收款单「${row.receiptNo}」?审核通过后金额将计入客户余额。`).then(() => {
proxy.$modal.confirm(`是否确认审核通过收款单「${row.receiptNo}」?审核通过后金额计入客户余额,登记时选择的账单将一并核销。`).then(() => {
return confirmReceipt(row.id)
}).then(() => {
proxy.$modal.msgSuccess("审核通过,余额已更新")
proxy.$modal.msgSuccess("审核通过,余额与账单核销已更新")
getReceiptList()
loadAccounts()
loadBillWriteoffData()
if (activeTab.value === 'flow') getFlowList()
}).catch(() => {})
}
@@ -1434,7 +1555,8 @@ function resetReceiptForm() {
bankAccountNo: '',
bankTransactionNo: '',
remark: '',
attachmentList: []
attachmentList: [],
writeoffPlans: []
}
proxy.resetForm('receiptFormRef')
}
@@ -1476,7 +1598,7 @@ function fetchDefaultRate() {
})
}
/** 切换币种:重置手工标记并重新取默认汇率 */
/** 切换币种:重置手工标记并重新取默认汇率;核销账单固定为CNY,只需按新汇率重算可核销上限,不清空已选 */
function handleCurrencyChange() {
rateEdited.value = false
rateSourceInfo.value = { source: '', ratePath: '' }
@@ -1502,6 +1624,130 @@ watch(() => receiptDialog.form.receivedAt, () => {
}
})
/* ============ 登记收款-选择核销账单 ============ */
const billPickerTableRef = ref(null)
const billPicker = reactive({
visible: false,
loading: false,
rows: [],
selected: []
})
/** 收款金额按汇率折算的本位币金额(CNY),即核销合计上限 */
const receiptBaseCny = computed(() => {
const amt = Number(receiptDialog.form.amount) || 0
const rate = Number(receiptDialog.form.exchangeRate) || 0
return Math.round(amt * rate * 100) / 100
})
/** 已分配合计(账单均为CNY,此值即 CNY) */
const receiptWriteoffTotal = computed(() => {
const list = receiptDialog.form.writeoffPlans || []
return list.reduce((s, r) => s + (Number(r.writeoffAmount) || 0), 0)
})
/** 核销后剩余入账金额(折算CNY - 已分配CNY) */
const receiptWriteoffRemain = computed(() => {
return Math.round((receiptBaseCny.value - receiptWriteoffTotal.value) * 100) / 100
})
/** 选择器中勾选账单的未核销额合计(仅提示) */
const billPickerSelectedTotal = computed(() => {
return billPicker.selected.reduce((s, r) => s + (Number(r.unwrittenAmount) || 0), 0)
})
const RECEIPT_PLAN_STATUS_MAP = {
PENDING: { label: '待核销', type: 'warning' },
EFFECTIVE: { label: '已核销', type: 'success' },
CANCELLED: { label: '已作废', type: 'info' },
REVERSED: { label: '已反核销', type: 'danger' }
}
function receiptPlanStatusLabel(s) {
return RECEIPT_PLAN_STATUS_MAP[s]?.label || s || '-'
}
function receiptPlanStatusTag(s) {
return RECEIPT_PLAN_STATUS_MAP[s]?.type || 'info'
}
/** 打开账单选择器:只列与收款同币种的未核销账单,并回显已选 */
function openBillPicker() {
const customerId = route.query.customerId
if (!customerId) {
proxy.$modal.msgError('缺少客户信息,请重新进入页面')
return
}
billPicker.visible = true
billPicker.loading = true
billPicker.rows = []
billPicker.selected = []
listUnsettledBill(customerId).then(res => {
// 现阶段只核销人民币账单,与收款币种无关;核销上限为收款折算 CNY
billPicker.rows = (res.data || []).filter(r => (r.currency || 'CNY') === 'CNY')
nextTick(() => {
billPickerTableRef.value?.clearSelection()
const chosen = receiptDialog.form.writeoffPlans || []
billPicker.rows.forEach(r => {
if (chosen.some(p => p.arBillId === r.arBillId)) {
billPickerTableRef.value?.toggleRowSelection(r, true)
}
})
})
}).finally(() => {
billPicker.loading = false
})
}
function onPickerSelectionChange(rows) {
billPicker.selected = rows || []
}
/** 确认选择:新增行默认核销额=min(账单未核销额CNY, 收款折算CNY剩余可用),保留已填金额 */
function confirmBillPicker() {
const oldMap = new Map((receiptDialog.form.writeoffPlans || []).map(p => [p.arBillId, p]))
receiptDialog.form.writeoffPlans = billPicker.selected.map(r => {
const existed = oldMap.get(r.arBillId)
const unwritten = Number(r.unwrittenAmount) || 0
const usedByOthers = receiptDialog.form.writeoffPlans
.filter(p => p.arBillId !== r.arBillId)
.reduce((s, p) => s + (Number(p.writeoffAmount) || 0), 0)
const remainCny = Math.max(receiptBaseCny.value - usedByOthers, 0)
return {
arBillId: r.arBillId,
billNo: r.billNo,
billName: r.billName,
currency: 'CNY',
unwrittenAmount: unwritten,
writeoffAmount: existed ? Number(existed.writeoffAmount) : Math.round(Math.min(unwritten, remainCny) * 100) / 100
}
})
billPicker.visible = false
}
function removeReceiptBill(index) {
receiptDialog.form.writeoffPlans.splice(index, 1)
}
/** 提交前校验核销账单(金额口径均为 CNY) */
function validateReceiptWriteoffPlans() {
const cap = receiptBaseCny.value
const list = receiptDialog.form.writeoffPlans || []
let total = 0
for (const p of list) {
const amt = Number(p.writeoffAmount)
if (!amt || amt <= 0) {
proxy.$modal.msgError(`账单【${p.billNo}】本次核销金额必须大于0`)
return false
}
if (amt > Number(p.unwrittenAmount)) {
proxy.$modal.msgError(`账单【${p.billNo}】核销金额超过其未核销金额`)
return false
}
total += amt
}
if (Math.round(total * 100) > Math.round(cap * 100)) {
proxy.$modal.msgError(`核销金额合计 ${total.toFixed(2)} CNY 超过收款折算本位币金额 ${cap.toFixed(2)} CNY`)
return false
}
return true
}
function submitReceipt() {
receiptFormRef.value.validate(valid => {
if (!valid) return
@@ -1511,6 +1757,7 @@ function submitReceipt() {
proxy.$modal.msgError(`请录入 ${cur} 对 CNY 的汇率(汇率表未配置时可手工填写)`)
return
}
if (!validateReceiptWriteoffPlans()) return
submitLoading.value = true
addReceipt(receiptDialog.form).then(() => {
proxy.$modal.msgSuccess("登记成功,等待审核")
@@ -2222,6 +2469,17 @@ watch(() => route.query.customerId, (newId, oldId) => {
.writeoff-summary {
margin-bottom: 12px;
}
.receipt-wo-toolbar {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
margin-bottom: 8px;
}
.receipt-wo-summary {
font-size: 13px;
color: #606266;
}
.wo-summary {
display: flex;
align-items: center;