feat(approve): 新增财务付款审批功能

- 添加付款审批列表页面,支持搜索和分页
- 实现付款单详情查看功能
- 集成审批流程操作,包括同意和驳回
- 添加审批意见输入和验证逻辑
- 展示付款单流转历史记录
- 创建付款详情组件,显示付款单和应付单信息
- 添加跳转至审批历史页面的功能按钮
dev_1.0.0
chenhao 2025-12-16 09:52:53 +08:00
parent 2cf2fdff08
commit 995f7f8b03
3 changed files with 450 additions and 0 deletions

View File

@ -0,0 +1,150 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="付款编号" prop="paymentNo">
<el-input v-model="queryParams.paymentNo" placeholder="请输入付款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="制造商" prop="manufacturer">
<el-input v-model="queryParams.manufacturer" placeholder="请输入制造商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="paymentList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="付款编号" align="center" prop="paymentBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" />
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax" />
<el-table-column label="汇智负责人" align="center" prop="hzUserName" />
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 详情对话框 -->
<el-dialog title="付款单详情" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<ApproveLayout title="付款单详情">
<payment-detail :data="form"></payment-detail>
<template #footer>
<span>付款编号: {{ form.paymentBillCode }}</span>
</template>
</ApproveLayout>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag size="small">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listPaymentApproved, getPayment } from "@/api/finance/payment";
import { listCompletedFlows } from "@/api/flow";
import PaymentDetail from "../components/PaymentDetail"; // Relative path adjustment
import ApproveLayout from "@/views/approve/ApproveLayout";
export default {
name: "PaymentApproved",
components: { PaymentDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
paymentList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
paymentNo: null,
manufacturer: null,
projectName: null
},
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
currentPaymentId: null
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listPaymentApproved(this.queryParams).then(response => {
this.paymentList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
handleView(row) {
this.form = {};
this.approveLogs = [];
this.currentPaymentId = row.id;
this.detailLoading = true;
this.detailDialogVisible = true;
getPayment(this.currentPaymentId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.paymentBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
loadApproveHistory(businessKey) {
if (businessKey) {
listCompletedFlows({ businessKey: businessKey }).then(response => {
this.approveLogs = response.data;
});
}
},
getStatusText(status) {
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<div class="payment-detail">
<el-descriptions title="付款单信息" :column="3" border>
<el-descriptions-item label="采购付款单编号">{{ data.paymentBillCode }}</el-descriptions-item>
<el-descriptions-item label="制造商名称">{{ data.vendorName }}</el-descriptions-item>
<el-descriptions-item label="付款单类型">
<dict-tag :options="dict.type.payment_bill_type" :value="data.paymentBillType"/>
</el-descriptions-item>
<el-descriptions-item label="含税总价">{{ data.totalPriceWithTax }}</el-descriptions-item>
<el-descriptions-item label="未税总价">{{ data.totalPriceWithoutTax }}</el-descriptions-item>
<el-descriptions-item label="税额">{{ data.taxAmount }}</el-descriptions-item>
<el-descriptions-item label="付款方式">
<dict-tag :options="dict.type.payment_method" :value="data.paymentMethod"/>
</el-descriptions-item>
<el-descriptions-item label="银行账号">{{ data.payBankNumber }}</el-descriptions-item>
<el-descriptions-item label="账户名称">{{ data.payName }}</el-descriptions-item>
<el-descriptions-item label="银行开户行">{{ data.payBankOpenAddress }}</el-descriptions-item>
<el-descriptions-item label="银行行号">{{ data.bankNumber }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="3">{{ data.remark }}</el-descriptions-item>
</el-descriptions>
<div class="section" style="margin-top: 20px;" v-show="data.payableDetails && data.payableDetails.length>0">
<div class="el-descriptions__title">应付单列表</div>
<el-table :data="data.payableDetails" border style="width: 100%; margin-top: 10px;">
<el-table-column type="index" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="payableBillCode" label="采购应付单编号" align="center"></el-table-column>
<el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
<!-- Note: Product Type is requested but not present in reference DetailDrawer.vue. Omitting for safety or need to ask. -->
<el-table-column prop="totalPriceWithTax" label="含税总价" align="center"></el-table-column>
<el-table-column prop="paymentAmount" label="本次付款金额" align="center"></el-table-column>
</el-table>
</div>
</div>
</template>
<script>
export default {
name: "PaymentDetail",
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['payment_bill_type', 'payment_method']
};
</script>
<style scoped>
.payment-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,246 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="付款编号" prop="paymentNo">
<el-input v-model="queryParams.paymentNo" placeholder="请输入付款编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="制造商" prop="manufacturer">
<el-input v-model="queryParams.manufacturer" placeholder="请输入制造商" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="mini"
@click="toApproved()"
>审批历史</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="paymentList">
<el-table-column label="序号" type="index" width="50" align="center" />
<el-table-column label="付款编号" align="center" prop="paymentBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" />
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="金额" align="center" prop="totalPriceWithTax" />
<el-table-column label="汇智负责人" align="center" prop="hzUserName" />
<el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleApprove(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 审批详情主对话框 -->
<el-dialog title="付款单审批" :visible.sync="detailDialogVisible" width="80%" append-to-body>
<div v-loading="detailLoading" style="max-height: 70vh; overflow-y: auto; padding: 20px;">
<ApproveLayout title="付款单详情">
<payment-detail :data="form"></payment-detail>
<template #footer>
<span>付款编号: {{ form.paymentBillCode }}</span>
</template>
</ApproveLayout>
<el-divider content-position="left">流转意见</el-divider>
<div class="process-container">
<el-timeline>
<el-timeline-item v-for="(log, index) in approveLogs" :key="index" :timestamp="log.approveTime" placement="top">
<el-card>
<h4>{{ log.approveOpinion }}</h4>
<p><b>操作人:</b> {{ log.approveUserName }} </p>
<p><b>审批状态:</b> <el-tag size="small">{{ getStatusText(log.approveStatus) }}</el-tag></p>
</el-card>
</el-timeline-item>
</el-timeline>
<div v-if="!approveLogs || approveLogs.length === 0"></div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="openOpinionDialog('approve')"></el-button>
<el-button type="danger" @click="openOpinionDialog('reject')"></el-button>
<el-button @click="detailDialogVisible = false"> </el-button>
</span>
</el-dialog>
<!-- 审批意见对话框 -->
<el-dialog :title="confirmDialogTitle" :visible.sync="opinionDialogVisible" width="30%" append-to-body>
<el-form ref="opinionForm" :model="opinionForm" :rules="opinionRules" label-width="100px">
<el-form-item label="审批意见" prop="approveOpinion">
<el-input v-model="opinionForm.approveOpinion" type="textarea" :rows="4" placeholder="请输入审批意见"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="opinionDialogVisible = false"> </el-button>
<el-button type="primary" @click="showConfirmDialog()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listPaymentApprove, getPayment } from "@/api/finance/payment";
import { approveTask, listCompletedFlows } from "@/api/flow";
import PaymentDetail from "./components/PaymentDetail";
import ApproveLayout from "@/views/approve/ApproveLayout";
export default {
name: "PaymentApprove",
components: { PaymentDetail, ApproveLayout },
data() {
return {
loading: true,
showSearch: true,
total: 0,
paymentList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
paymentNo: null,
manufacturer: null,
projectName: null
},
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_payment',
taskId: null,
currentPaymentId: null
};
},
created() {
this.getList();
},
methods: {
getList() {
this.loading = true;
listPaymentApprove(this.queryParams).then(response => {
this.paymentList = response.rows;
this.total = response.total;
this.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
toApproved() {
this.$router.push( '/approve/paymentLog' )
},
handleApprove(row) {
this.resetDetailForm();
this.currentPaymentId = row.id;
this.taskId = row.taskId;
this.detailLoading = true;
this.detailDialogVisible = true;
getPayment(this.currentPaymentId).then(response => {
this.form = response.data;
this.loadApproveHistory(this.form.paymentBillCode);
this.detailLoading = false;
}).catch(() => {
this.detailLoading = false;
});
},
resetDetailForm() {
this.form = {};
this.approveLogs = [];
this.opinionForm.approveOpinion = '';
},
loadApproveHistory(businessKey) {
if (businessKey) {
// Assuming processKeyList might be generic or specific, using generic fetch for now
// Usually need to know the specific process key. For payment, it might be 'payment_approval' etc.
// However, listCompletedFlows logic in reference used specific keys.
// If I don't know the key, maybe I can omit it or guess.
// The reference used `processKeyList: ['purchase_order_online']`.
// I will use a generic one or try to find it.
// If row.processKey is available I can use that.
let keys = [];
console.log(this.processKey)
if(this.processKey) keys.push(this.processKey);
listCompletedFlows({ businessKey: businessKey, processKeyList: keys }).then(response => {
this.approveLogs = response.data;
});
}
},
openOpinionDialog(type) {
this.currentApproveType = type;
this.confirmDialogTitle = type === 'approve' ? '同意审批' : '驳回审批';
this.opinionDialogVisible = true;
this.opinionForm.approveOpinion = '';
this.$nextTick(() => {
if(this.$refs.opinionForm) this.$refs.opinionForm.clearValidate();
});
},
showConfirmDialog() {
this.$refs.opinionForm.validate(valid => {
if (valid) {
this.opinionDialogVisible = false;
this.submitApproval();
}
});
},
submitApproval() {
const approveBtn = this.currentApproveType === 'approve' ? 1 : 0;
const params = {
businessKey: this.form.paymentBillCode,
processKey: this.processKey,
taskId: this.taskId,
variables: {
comment: this.opinionForm.approveOpinion,
approveBtn: approveBtn
}
};
approveTask(params).then(() => {
this.$modal.msgSuccess(this.confirmDialogTitle + "成功");
this.detailDialogVisible = false;
this.getList();
});
},
getStatusText(status) {
if (!status) {
return '提交审批'
}
// Map status codes to text
const map = { '1': '提交审批', '2': '驳回', '3': '批准' };
return map[status] || '提交审批';
}
}
};
</script>
<style scoped>
.process-container {
padding: 10px;
}
</style>