Files
tms-erp-web/src/views/payment/receipt-flow-form.vue
T
2026-09-02 03:22:51 +08:00

440 lines
15 KiB
Vue

<template>
<receipt-flow-batch-form v-if="batchMode" />
<basic-container v-else class="receipt-claim-form-page">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="right"
label-width="auto"
class="receipt-claim-form-page__form"
>
<section-card title="收款信息">
<el-row :gutter="32">
<el-col :span="6"
><el-form-item label="认领通知单"
><el-input v-model="form.receiptNoticeNo" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="付款人"
><el-input v-model="form.payerName" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="收款金额"
><el-input :model-value="formatMoney(form.receiptAmount)" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="交易时间"
><el-input v-model="form.transactionTime" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="对方户名"
><el-input v-model="form.counterpartyName" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="对方开户行"
><el-input v-model="form.counterpartyBank" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="对方账号"
><el-input v-model="form.counterpartyAccount" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="明细流水号"
><el-input v-model="form.detailSerialNo" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="已认领金额"
><el-input :model-value="formatMoney(form.claimedAmount)" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="摘要"><el-input v-model="form.summary" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="关联结算单" prop="settlementIds"
><el-input
:model-value="settlementLabel"
readonly
placeholder="请选择已审批通过的应收正式结算单"
><template #append
><el-button
:icon="Search"
title="选择结算单"
@click="openSettlementDialog" /></template></el-input></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="认领人"
><el-input v-model="form.claimerName" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="认领日期"
><el-input v-model="form.claimDate" disabled /></el-form-item
></el-col>
<el-col :span="6"
><el-form-item label="认领人部门"
><el-input v-model="form.claimerDeptName" disabled /></el-form-item
></el-col>
</el-row>
</section-card>
<section-card title="结算信息">
<el-table :data="form.settlements" border>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column label="结算单号" min-width="180" align="center"
><template #default="{ row }"
><el-link type="primary" @click="openSettlementDetail(row)">{{
row.formalSettlementNo
}}</el-link></template
></el-table-column
>
<el-table-column label="结算总金额" min-width="150" align="right"
><template #default="{ row }">{{
formatMoney(row.settlementAmount)
}}</template></el-table-column
>
<el-table-column label="已认领收款金额" min-width="160" align="right"
><template #default="{ row }">{{
formatMoney(row.claimedReceiptAmount)
}}</template></el-table-column
>
<el-table-column label="分摊收款金额" min-width="180"
><template #default="{ row }"
><el-input-number
v-model="row.allocatedReceiptAmount"
:min="0"
:max="remainingAmount(row)"
:precision="2"
:controls="false" /></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-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-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-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
@selection-change="settlementDialog.selection = $event"
>
<el-table-column type="selection" width="52" align="center" />
<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.stop="selectSettlement(row)">选择</el-link>
</template>
</el-table-column>
</el-table>
<template #footer
><el-button @click="settlementDialog.visible = false">取消</el-button
><el-button type="primary" @click="confirmSettlements">确定</el-button></template
>
</el-dialog>
</basic-container>
</template>
<script>
import { Search } from '@element-plus/icons-vue';
import * as api from '@/api/payment/receiptFlow';
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
import ReceiptFlowBatchForm from './receipt-flow-batch-form.vue';
const emptyForm = () => ({
id: null,
receiptNoticeNo: '',
payerName: '',
receiptAmount: 0,
counterpartyName: '',
counterpartyAccount: '',
counterpartyBank: '',
summary: '',
transactionTime: '',
detailSerialNo: '',
claimedAmount: 0,
claimerName: '',
claimerDeptName: '',
claimDate: '',
settlementIds: [],
settlements: [],
attachments: [],
remark: '',
});
export default {
name: 'ReceiptFlowForm',
components: { VehicleAttachmentTable, ReceiptFlowBatchForm },
data() {
return {
Search,
form: emptyForm(),
submitting: false,
settlementDialog: { visible: false, loading: false, keyword: '', rows: [], selection: [] },
rules: {
settlementIds: [
{ required: true, message: '请选择应收正式结算单', trigger: 'change' },
{ validator: this.validateSettlements, trigger: 'change' },
],
},
};
},
computed: {
batchMode() {
return String(this.$route.query.batch || '') === '1';
},
flowId() {
return this.$route.query.id || '';
},
settlementLabel() {
return this.form.settlements
.map(item => item.formalSettlementNo)
.filter(Boolean)
.join(',');
},
allocatedTotal() {
return this.form.settlements.reduce(
(total, item) => total + Number(item.allocatedReceiptAmount || 0),
0
);
},
},
created() {
if (!this.batchMode) this.initialize();
},
methods: {
unwrapData(response) {
const body = response?.data || response || {};
return body?.data || body;
},
async initialize() {
if (!this.flowId) {
this.$message.error('缺少收款流水');
this.goBack();
return;
}
const data = this.unwrapData(await api.getDetail(this.flowId));
const userInfo = this.$store.getters.userInfo || {};
this.form = {
...emptyForm(),
...data,
claimerName: userInfo.realName || userInfo.userName || data.claimerName || '',
claimerDeptName: userInfo.deptName || userInfo.dept_name || data.claimerDeptName || '',
claimDate: this.$dayjs().format('YYYY-MM-DD'),
settlementIds: [],
settlements: [],
attachments: [],
};
},
validateSettlements(rule, value, callback) {
this.form.settlements.length ? callback() : callback(new Error('请选择应收正式结算单'));
},
async openSettlementDialog() {
this.settlementDialog.visible = true;
await this.loadSettlementCandidates();
},
async loadSettlementCandidates() {
this.settlementDialog.loading = true;
try {
this.settlementDialog.rows =
this.unwrapData(
await api.getSettlementCandidates(this.settlementDialog.keyword, this.flowId)
) || [];
} finally {
this.settlementDialog.loading = false;
}
},
confirmSettlements() {
const rows = this.settlementDialog.selection;
if (!rows.length) {
this.$message.warning('请至少选择一张应收正式结算单');
return;
}
const first = rows[0];
if (
rows.some(
item =>
String(item.projectId) !== String(first.projectId) ||
String(item.deptId) !== String(first.deptId) ||
item.payerName !== first.payerName ||
item.payeeName !== first.payeeName
)
) {
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
return;
}
this.form.settlements = rows.map(item => ({
...item,
formalSettlementId: item.id,
settlementId: item.id,
allocatedReceiptAmount: 0,
}));
this.form.settlementIds = this.form.settlements.map(item => item.formalSettlementId);
this.settlementDialog.visible = false;
this.$nextTick(() => this.$refs.formRef?.validateField('settlementIds'));
},
selectSettlement(row) {
this.settlementDialog.selection = [row];
this.confirmSettlements();
},
remainingAmount(row) {
return Math.max(Number(row.settlementAmount || 0) - Number(row.claimedReceiptAmount || 0), 0);
},
async submitClaim() {
await this.$refs.formRef.validate();
const amount = Number(this.form.receiptAmount || 0);
if (amount <= 0 || this.allocatedTotal <= 0) {
this.$message.warning('分摊收款金额必须大于0');
return;
}
if (this.allocatedTotal > amount - Number(this.form.claimedAmount || 0) + 0.001) {
this.$message.warning('本次分摊金额不能超过流水剩余可认领金额');
return;
}
for (const row of this.form.settlements)
if (Number(row.allocatedReceiptAmount || 0) > this.remainingAmount(row) + 0.001) {
this.$message.warning(`结算单${row.formalSettlementNo}的累计认领金额不能超过结算总金额`);
return;
}
this.submitting = true;
try {
await api.claim({
flowId: this.flowId,
attachmentsJson: JSON.stringify(this.form.attachments || []),
remark: this.form.remark,
settlements: this.form.settlements.map(item => ({
settlementId: item.formalSettlementId || item.settlementId,
allocatedReceiptAmount: item.allocatedReceiptAmount,
})),
});
this.$message.success('认领成功');
this.goBack();
} finally {
this.submitting = false;
}
},
openSettlementDetail(row) {
const id = row.formalSettlementId || row.settlementId || row.id;
if (id)
this.$router.push({
path: '/settlement/formal-settlement/form',
query: { mode: 'view', id, name: '查看正式结算' },
});
},
goBack() {
this.$router.push('/payment/receipt-flow');
},
formatMoney(value) {
return Number(value || 0).toFixed(2);
},
},
};
</script>
<style scoped lang="scss">
.receipt-claim-form-page__form {
padding-bottom: 72px;
}
.receipt-claim-form-page__form :deep(.el-select),
.receipt-claim-form-page__form :deep(.el-input-number) {
width: 100%;
}
.receipt-claim-form-page__links {
display: flex;
justify-content: center;
gap: 8px;
flex-wrap: wrap;
}
.receipt-claim-form-page__remark :deep(.el-form-item__content) {
margin-left: 0 !important;
}
.receipt-claim-form-page__uploader {
margin-top: 12px;
}
.receipt-claim-form-page__actions {
display: flex;
justify-content: flex-end;
padding: 12px 24px;
position: fixed;
right: 0;
left: 230px;
bottom: 0;
margin: 0;
z-index: 10;
background: #fff;
border-top: 1px solid #eff1f7;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
}
:global(.avue--collapse .receipt-claim-form-page__actions) {
left: 60px;
}
:global(.avue-layout--horizontal .receipt-claim-form-page__actions) {
left: 0;
}
.receipt-claim-form-page__dialog-search {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-bottom: 12px;
}
.receipt-claim-form-page__dialog-search .el-input {
width: 360px;
}
.receipt-claim-form-page :deep(.el-table) {
--el-table-border-color: #eff1f7;
}
.receipt-claim-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
background: #fafafa;
}
@media (max-width: 900px) {
.receipt-claim-form-page__form :deep(.el-col-6) {
max-width: 100%;
flex: 0 0 100%;
}
}
</style>