78bb38e955
认领只登记本地,额度由本地累计认领控制,金蝶未匹配金额不再作为上限;批量认领校验复用剩余可认领金额口径。
462 lines
16 KiB
Vue
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>
|