Files
tms-erp-web/src/views/payment/receipt-flow-batch-form.vue
T
刘泉佳 78bb38e955 🐛 收款流水认领额度改用收款金额并新增剩余可认领金额列
认领只登记本地,额度由本地累计认领控制,金蝶未匹配金额不再作为上限;批量认领校验复用剩余可认领金额口径。
2026-10-29 20:07:53 +08:00

462 lines
16 KiB
Vue

<template>
<basic-container class="receipt-claim-batch-form-page">
<div class="archive-page-form__title">批量认领收款流水</div>
<el-form ref="formRef" :model="form" class="receipt-claim-batch-form-page__form">
<section-card title="收款信息">
<el-table v-loading="loading" :data="flows" border>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column
prop="receiptNoticeNo"
label="收款单号"
min-width="160"
show-overflow-tooltip
/>
<el-table-column prop="payerName" label="付款人" min-width="150" show-overflow-tooltip />
<el-table-column label="收款金额" min-width="130" align="right">
<template #default="{ row }">{{ formatMoney(row.receiptAmount) }}</template>
</el-table-column>
<el-table-column prop="transactionTime" label="交易时间" min-width="170" />
<el-table-column
prop="counterpartyName"
label="对方户名"
min-width="150"
show-overflow-tooltip
/>
<el-table-column
prop="counterpartyBank"
label="对方开户行"
min-width="170"
show-overflow-tooltip
/>
<el-table-column
prop="counterpartyAccount"
label="对方账号"
min-width="180"
show-overflow-tooltip
/>
<el-table-column
prop="detailSerialNo"
label="明细流水号"
min-width="180"
show-overflow-tooltip
/>
<el-table-column label="已认领金额" min-width="130" align="right">
<template #default="{ row }">{{ formatMoney(row.claimedAmount) }}</template>
</el-table-column>
<el-table-column label="未匹配金额" min-width="140" align="right">
<template #default="{ row }">{{ formatMoney(row.unmatchAmountRec) }}</template>
</el-table-column>
<el-table-column label="剩余可认领金额" min-width="140" align="right">
<template #default="{ row }">{{ formatMoney(flowRemaining(row)) }}</template>
</el-table-column>
<el-table-column prop="summary" label="摘要" min-width="180" show-overflow-tooltip />
</el-table>
<el-row :gutter="32" class="receipt-claim-batch-form-page__claim-info">
<el-col :span="6">
<el-form-item label="认领人">
<el-input v-model="claimerName" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="认领日期">
<el-input v-model="claimDate" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="认领人部门">
<el-input v-model="claimerDeptName" disabled />
</el-form-item>
</el-col>
</el-row>
</section-card>
<section-card title="结算信息">
<div class="receipt-claim-batch-form-page__settlement-toolbar">
<el-button type="primary" @click="openSettlementDialog">选择结算单</el-button>
</div>
<el-table
:data="allocationRows"
border
show-summary
:summary-method="summaryMethod"
:span-method="settlementSpanMethod"
empty-text="请先选择应收正式结算单"
>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column label="结算单号" min-width="180">
<template #default="{ row }">{{ row.settlement.formalSettlementNo || '-' }}</template>
</el-table-column>
<el-table-column label="结算总金额" min-width="140" align="right">
<template #default="{ row }">{{
formatMoney(row.settlement.settlementAmount)
}}</template>
</el-table-column>
<el-table-column label="已认领收款金额" min-width="150" align="right">
<template #default="{ row }">{{
formatMoney(row.settlement.claimedReceiptAmount)
}}</template>
</el-table-column>
<el-table-column label="收款单号" min-width="180">
<template #default="{ row }">{{ row.flow.receiptNoticeNo || '-' }}</template>
</el-table-column>
<el-table-column label="收款金额" min-width="140" align="right">
<template #default="{ row }">{{ formatMoney(row.flow.receiptAmount) }}</template>
</el-table-column>
<el-table-column label="分摊收款金额" min-width="180" align="right">
<template #default="{ row }">
<el-input-number
v-model="row.settlement.allocations[row.flow.id]"
:min="0"
:max="maxAllocation(row)"
:precision="2"
:controls="false"
@change="handleAllocationChange(row.settlement)"
/>
</template>
</el-table-column>
</el-table>
</section-card>
<section-card title="附件信息">
<vehicle-attachment-table v-model="form.attachments" />
</section-card>
<section-card title="备注">
<el-form-item class="receipt-claim-batch-form-page__remark">
<el-input
v-model="form.remark"
type="textarea"
:rows="2"
maxlength="200"
show-word-limit
/>
</el-form-item>
</section-card>
<div class="receipt-claim-batch-form-page__actions">
<el-button :disabled="submitting" @click="goBack">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitClaim">确认</el-button>
</div>
</el-form>
<el-dialog
v-model="settlementDialog.visible"
title="选择应收正式结算单"
width="86%"
append-to-body
>
<div class="receipt-claim-batch-form-page__dialog-search">
<el-input
v-model="settlementDialog.keyword"
clearable
placeholder="结算单号、项目或合同"
@keyup.enter="loadSettlementCandidates"
/>
<el-button type="primary" @click="loadSettlementCandidates">查询</el-button>
</div>
<el-table v-loading="settlementDialog.loading" :data="settlementDialog.rows" border>
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="170" />
<el-table-column prop="projectName" label="所属项目" min-width="150" />
<el-table-column prop="deptName" label="所属组织" min-width="150" />
<el-table-column prop="payerName" label="付款方" min-width="150" />
<el-table-column prop="payeeName" label="收款方" min-width="150" />
<el-table-column label="结算总金额" min-width="140" align="right">
<template #default="{ row }">{{ formatMoney(row.settlementAmount) }}</template>
</el-table-column>
<el-table-column label="已认领收款金额" min-width="150" align="right">
<template #default="{ row }">{{ formatMoney(row.claimedReceiptAmount) }}</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right" align="center">
<template #default="{ row }">
<el-link type="primary" @click="selectSettlement(row)">选择</el-link>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="settlementDialog.visible = false">取消</el-button>
</template>
</el-dialog>
</basic-container>
</template>
<script>
import * as api from '@/api/payment/receiptFlow';
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
const emptyForm = () => ({ attachments: [], remark: '' });
export default {
name: 'ReceiptFlowBatchForm',
components: { VehicleAttachmentTable },
data() {
return {
form: emptyForm(),
flows: [],
settlements: [],
loading: false,
submitting: false,
claimerName: '',
claimerDeptName: '',
claimDate: '',
settlementDialog: { visible: false, loading: false, keyword: '', rows: [], selection: [] },
};
},
created() {
const userInfo = this.$store.getters.userInfo || {};
this.claimerName = userInfo.realName || userInfo.userName || '';
this.claimerDeptName = userInfo.deptName || userInfo.dept_name || '';
this.claimDate = this.$dayjs().format('YYYY-MM-DD');
this.initialize();
},
computed: {
allocationRows() {
return this.settlements.flatMap(settlement => this.flows.map(flow => ({ settlement, flow })));
},
},
methods: {
unwrapData(response) {
const body = response?.data || response || {};
return body?.data || body;
},
async initialize() {
const ids = [
...new Set(
String(this.$route.query.ids || '')
.split(',')
.filter(Boolean)
),
];
if (!ids.length) {
this.$message.error('缺少批量认领流水');
this.goBack();
return;
}
this.loading = true;
try {
const details = await Promise.all(ids.map(id => api.getDetail(id)));
this.flows = details.map(item => this.unwrapData(item)).filter(Boolean);
if (!this.flows.length) throw new Error('没有可认领的收款流水');
const first = this.flows[0];
const sameParty = this.flows.every(
row =>
String(row.payerName || '').trim() === String(first.payerName || '').trim() &&
String(row.counterpartyAccount || '').trim() ===
String(first.counterpartyAccount || '').trim()
);
if (!sameParty) throw new Error('批量认领所选流水的付款人、对方账号必须一致');
} catch (error) {
this.$message.error(error?.message || '批量认领流水加载失败');
this.goBack();
} finally {
this.loading = false;
}
},
async openSettlementDialog() {
this.settlementDialog.visible = true;
await this.loadSettlementCandidates();
},
async loadSettlementCandidates() {
if (!this.flows.length) return;
this.settlementDialog.loading = true;
try {
this.settlementDialog.rows =
this.unwrapData(
await api.getSettlementCandidates(this.settlementDialog.keyword, this.flows[0].id)
) || [];
} finally {
this.settlementDialog.loading = false;
}
},
selectSettlement(row) {
this.settlementDialog.selection = [row];
this.confirmSettlements();
},
confirmSettlements() {
const rows = this.settlementDialog.selection;
if (!rows.length) {
this.$message.warning('请至少选择一张应收正式结算单');
return;
}
const item = rows[0];
this.settlements = [
{
...item,
allocations: this.flows.reduce((result, flow) => {
result[flow.id] = 0;
return result;
}, {}),
},
];
this.settlementDialog.visible = false;
},
settlementRemaining(row) {
return Math.max(Number(row.settlementAmount || 0) - Number(row.claimedReceiptAmount || 0), 0);
},
settlementAllocatedTotal(row) {
return this.flows.reduce((total, flow) => total + Number(row.allocations?.[flow.id] || 0), 0);
},
flowAllocatedTotal(flowId) {
return this.settlements.reduce(
(total, row) => total + Number(row.allocations?.[flowId] || 0),
0
);
},
flowRemaining(flow) {
// 认领只登记本地:剩余可认领 = 收款金额 − 已认领金额
return Math.max(Number(flow.receiptAmount || 0) - Number(flow.claimedAmount || 0), 0);
},
maxAllocation(row) {
const current = Number(row.settlement.allocations?.[row.flow.id] || 0);
const otherTotal = this.settlementAllocatedTotal(row.settlement) - current;
// 流水维度:该流水其余分摊已占用部分同样挤占剩余可认领金额
const flowOtherTotal = this.flowAllocatedTotal(row.flow.id) - current;
return Math.max(
Math.min(
this.settlementRemaining(row.settlement) - otherTotal,
this.flowRemaining(row.flow) - flowOtherTotal
),
0
);
},
handleAllocationChange(settlement) {
const total = this.settlementAllocatedTotal(settlement);
if (total > this.settlementRemaining(settlement) + 0.001) {
this.$message.warning(`结算单${settlement.formalSettlementNo}的分摊金额不能超过可认领金额`);
}
},
summaryMethod({ columns }) {
return columns.map((column, index) => {
if (index === 0) return '合计';
if (index === 6) return this.formatMoney(this.allocatedTotal());
return '';
});
},
settlementSpanMethod({ rowIndex, columnIndex }) {
if (![1, 2, 3].includes(columnIndex)) return [1, 1];
if (!this.flows.length) return [1, 1];
if (rowIndex % this.flows.length === 0) return [this.flows.length, 1];
return [0, 0];
},
allocatedTotal() {
return this.settlements.reduce((total, row) => total + this.settlementAllocatedTotal(row), 0);
},
async submitClaim() {
if (!this.settlements.length) {
this.$message.warning('请至少选择一张应收正式结算单');
return;
}
for (const row of this.settlements) {
if (this.settlementAllocatedTotal(row) > this.settlementRemaining(row) + 0.001) {
this.$message.warning(`结算单${row.formalSettlementNo}的累计认领金额不能超过结算总金额`);
return;
}
}
for (const flow of this.flows) {
const total = this.flowAllocatedTotal(flow.id);
if (total > this.flowRemaining(flow) + 0.001) {
this.$message.warning(`流水${flow.receiptNoticeNo}的累计认领金额不能超过剩余可认领金额`);
return;
}
}
const requests = this.flows
.map(flow => ({
flow,
settlements: this.settlements
.map(row => ({
settlementId: row.id,
allocatedReceiptAmount: Number(row.allocations?.[flow.id] || 0),
}))
.filter(row => row.allocatedReceiptAmount > 0),
}))
.filter(item => item.settlements.length);
if (!requests.length) {
this.$message.warning('没有需要入库的分摊收款金额');
return;
}
this.submitting = true;
try {
for (const item of requests) {
await api.claim({
flowId: item.flow.id,
attachmentsJson: JSON.stringify(this.form.attachments || []),
remark: this.form.remark,
settlements: item.settlements,
});
}
this.$message.success(`批量认领成功,共${requests.length}条流水`);
this.goBack();
} finally {
this.submitting = false;
}
},
goBack() {
this.$router.push('/payment/receipt-flow');
},
formatMoney(value) {
return Number(value || 0).toFixed(2);
},
},
};
</script>
<style scoped lang="scss">
.receipt-claim-batch-form-page__form {
padding-bottom: 72px;
}
.receipt-claim-batch-form-page__claim-info {
margin-top: 16px;
}
.receipt-claim-batch-form-page__form :deep(.el-input-number) {
width: 100%;
}
.receipt-claim-batch-form-page__settlement-toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
color: #606266;
}
.receipt-claim-batch-form-page__notice-list {
display: flex;
flex-wrap: wrap;
gap: 4px 8px;
}
.receipt-claim-batch-form-page__actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 24px;
position: fixed;
right: 0;
left: 230px;
bottom: 0;
z-index: 10;
background: #fff;
border-top: 1px solid #eff1f7;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
}
.receipt-claim-batch-form-page__remark :deep(.el-form-item__content) {
margin-left: 0 !important;
}
.receipt-claim-batch-form-page__dialog-search {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-bottom: 12px;
}
.receipt-claim-batch-form-page__dialog-search .el-input {
width: 360px;
}
.receipt-claim-batch-form-page :deep(.el-table) {
--el-table-border-color: #eff1f7;
}
.receipt-claim-batch-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
background: #fafafa;
}
@media (max-width: 900px) {
.receipt-claim-batch-form-page__actions {
left: 0;
}
}
</style>