feat(finance): 新增财务票据审批功能

- 在配置文件中增加财务票据相关流程定义
- 实现票据退款审批流程的开启与处理逻辑
- 新增付款退款审批页面及详情组件
- 提供付款退款审批接口及业务方法
- 支持在审批流中动态设置流程变量并启动指定流程
- 优化审批节点信息填充方法以支持多流程场景
- 修复部分SQL查询条件限制提高灵活性
- 更新前端页面去除冗余字段展示提升用户体验
dev_1.0.0
chenhao 2025-12-16 14:47:15 +08:00
parent 995f7f8b03
commit 56d79e96b6
17 changed files with 701 additions and 17 deletions

View File

@ -0,0 +1,34 @@
import request from '@/utils/request'
import {tansParams} from "@/utils/ruoyi";
// 查询付款退款待审批列表
export function listPaymentRefundApprove(query) {
return request({
url: '/finance/payment/approve/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: query
})
}
// 查询付款退款已审批列表
export function listPaymentRefundApproved(query) {
return request({
url: '/finance/payment/approved/list',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
params: query
})
}
// 查询付款退款详情
export function getPaymentRefund(id) {
return request({
url: '/finance/paymentRefund/' + id,
method: 'get'
})
}

View File

@ -20,9 +20,9 @@
<el-table-column label="序号" type="index" width="50" align="center" /> <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="paymentBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" /> <el-table-column label="制造商" align="center" prop="vendorName" />
<el-table-column label="项目名称" align="center" prop="projectName" /> <!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" /> <el-table-column label="金额" align="center" prop="totalPriceWithTax" />
<el-table-column label="汇智负责人" align="center" prop="hzUserName" /> <!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="审批节点" align="center" prop="approveNode" /> <el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope"> <template slot-scope="scope">
@ -84,7 +84,8 @@ export default {
pageSize: 10, pageSize: 10,
paymentNo: null, paymentNo: null,
manufacturer: null, manufacturer: null,
projectName: null projectName: null,
processKey: 'finance_payment',
}, },
detailDialogVisible: false, detailDialogVisible: false,
detailLoading: false, detailLoading: false,

View File

@ -32,9 +32,9 @@
<el-table-column label="序号" type="index" width="50" align="center" /> <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="paymentBillCode" />
<el-table-column label="制造商" align="center" prop="vendorName" /> <el-table-column label="制造商" align="center" prop="vendorName" />
<el-table-column label="项目名称" align="center" prop="projectName" /> <!-- <el-table-column label="项目名称" align="center" prop="projectName" />-->
<el-table-column label="金额" align="center" prop="totalPriceWithTax" /> <el-table-column label="金额" align="center" prop="totalPriceWithTax" />
<el-table-column label="汇智负责人" align="center" prop="hzUserName" /> <!-- <el-table-column label="汇智负责人" align="center" prop="hzUserName" />-->
<el-table-column label="审批节点" align="center" prop="approveNode" /> <el-table-column label="审批节点" align="center" prop="approveNode" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope"> <template slot-scope="scope">
@ -111,7 +111,8 @@ export default {
pageSize: 10, pageSize: 10,
paymentNo: null, paymentNo: null,
manufacturer: null, manufacturer: null,
projectName: null projectName: null,
processKey: 'finance_payment',
}, },
detailDialogVisible: false, detailDialogVisible: false,
detailLoading: false, detailLoading: false,

View File

@ -0,0 +1,151 @@
<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/PaymentRefundDetail.vue"; // 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,
processKey: 'finance_refund',
},
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-refund-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: "PaymentRefundDetail",
props: {
data: {
type: Object,
required: true,
default: () => ({})
}
},
dicts: ['payment_bill_type', 'payment_method']
};
</script>
<style scoped>
.payment-refund-detail {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,247 @@
<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/PaymentRefundDetail.vue";
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,
processKey: 'finance_refund',
},
detailDialogVisible: false,
detailLoading: false,
form: {},
approveLogs: [],
opinionDialogVisible: false,
confirmDialogTitle: '',
currentApproveType: '',
opinionForm: {
approveOpinion: ''
},
opinionRules: {
approveOpinion: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
},
processKey: 'finance_refund',
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>

View File

@ -147,6 +147,7 @@ process:
purchaseOrderApprove: purchase_order_online purchaseOrderApprove: purchase_order_online
financePayment: finance_payment financePayment: finance_payment
financeRefund: finance_refund financeRefund: finance_refund
fiananceTicket: fianance_ticket
#业务执行实例bean name ,可以按审批节点配置 业务审批回调方法处理业务逻辑. key 为流程节点主键ID value 要执行的业务方法名称,不配置则默认调用TodoCommonTemplate.todoApproveCallback #业务执行实例bean name ,可以按审批节点配置 业务审批回调方法处理业务逻辑. key 为流程节点主键ID value 要执行的业务方法名称,不配置则默认调用TodoCommonTemplate.todoApproveCallback
instance: instance:
@ -160,6 +161,8 @@ process:
beanName: omsPaymentBillServiceImpl beanName: omsPaymentBillServiceImpl
financeRefund: financeRefund:
beanName: omsPaymentBillServiceImpl beanName: omsPaymentBillServiceImpl
fiananceTicket:
beanName: omsTicketBillServiceImpl
unis: unis:
inventory: inventory:
allAuthRole: 103,101 allAuthRole: 103,101

View File

@ -15,5 +15,7 @@ public class Definition {
private String purchaseOrderApprove; private String purchaseOrderApprove;
private String financePayment; private String financePayment;
private String financeRefund; private String financeRefund;
private String fiananceTicket;
private String financeTicketRefound;
} }

View File

@ -17,6 +17,8 @@ public class Instance {
private Map<String, Object> purchase_order_online; private Map<String, Object> purchase_order_online;
private Map<String, Object> finance_payment; private Map<String, Object> finance_payment;
private Map<String, Object> finance_refund; private Map<String, Object> finance_refund;
private Map<String, Object> fianance_ticket;
private Map<String, Object> finance_ticket_refound;
} }

View File

@ -1,9 +1,13 @@
package com.ruoyi.sip.controller; package com.ruoyi.sip.controller;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.ruoyi.common.config.flow.ProcessConfig;
import com.ruoyi.sip.domain.OmsFinAttachment; import com.ruoyi.sip.domain.OmsFinAttachment;
import com.ruoyi.sip.flowable.service.TodoService;
import com.ruoyi.sip.service.IOmsFinAttachmentService; import com.ruoyi.sip.service.IOmsFinAttachmentService;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -36,6 +40,10 @@ public class OmsPaymentBillController extends BaseController
private IOmsPaymentBillService omsPaymentBillService; private IOmsPaymentBillService omsPaymentBillService;
@Autowired @Autowired
private IOmsFinAttachmentService omsFinAttachmentService; private IOmsFinAttachmentService omsFinAttachmentService;
@Autowired
private TodoService todoService;
@Autowired
private ProcessConfig processConfig;
@RequiresPermissions("finance:payment:view") @RequiresPermissions("finance:payment:view")
@GetMapping() @GetMapping()
@ -54,6 +62,10 @@ public class OmsPaymentBillController extends BaseController
{ {
startPage(); startPage();
List<OmsPaymentBill> list = omsPaymentBillService.selectOmsPaymentBillList(omsPaymentBill); List<OmsPaymentBill> list = omsPaymentBillService.selectOmsPaymentBillList(omsPaymentBill);
clearPage();
todoService.fillApproveNode(list,
Arrays.asList(processConfig.getDefinition().getFinancePayment(), processConfig.getDefinition().getFinanceRefund())
, OmsPaymentBill::getPaymentBillCode, (a, b) -> a.setApproveNode(b.get(a.getPaymentBillCode())));
return getDataTable(list); return getDataTable(list);
} }
@ -64,6 +76,10 @@ public class OmsPaymentBillController extends BaseController
public TableDataInfo listApprove(OmsPaymentBill omsPaymentBill) { public TableDataInfo listApprove(OmsPaymentBill omsPaymentBill) {
startPage(); startPage();
List<OmsPaymentBill> list = omsPaymentBillService.listApprove(omsPaymentBill); List<OmsPaymentBill> list = omsPaymentBillService.listApprove(omsPaymentBill);
clearPage();
todoService.fillApproveNode(list,
Arrays.asList(processConfig.getDefinition().getFinancePayment(), processConfig.getDefinition().getFinanceRefund())
, OmsPaymentBill::getPaymentBillCode, (a, b) -> a.setApproveNode(b.get(a.getPaymentBillCode())));
return getDataTable(list); return getDataTable(list);
} }
@ -73,6 +89,10 @@ public class OmsPaymentBillController extends BaseController
public TableDataInfo listApproved(OmsPaymentBill omsPaymentBill) { public TableDataInfo listApproved(OmsPaymentBill omsPaymentBill) {
startPage(); startPage();
List<OmsPaymentBill> list = omsPaymentBillService.listApproved(omsPaymentBill); List<OmsPaymentBill> list = omsPaymentBillService.listApproved(omsPaymentBill);
clearPage();
todoService.fillApproveNode(list,
Arrays.asList(processConfig.getDefinition().getFinancePayment(), processConfig.getDefinition().getFinanceRefund())
, OmsPaymentBill::getPaymentBillCode, (a, b) -> a.setApproveNode(b.get(a.getPaymentBillCode())));
return getDataTable(list); return getDataTable(list);
} }

View File

@ -8,6 +8,8 @@ import org.flowable.engine.runtime.ProcessInstance;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
/** /**
* Service * Service
@ -137,6 +139,7 @@ public interface TodoService
void fillOrderApproveNode(List<ProjectOrderInfo> list); void fillOrderApproveNode(List<ProjectOrderInfo> list);
void fillPurchaseOrderApproveNode(List<OmsPurchaseOrder> list); void fillPurchaseOrderApproveNode(List<OmsPurchaseOrder> list);
<T> void fillApproveNode(List<T> list, List<String> processKeyList, Function<T, String> function, BiConsumer<T, Map<String, String>> consumer);
AjaxResult statisticsTodo(); AjaxResult statisticsTodo();

View File

@ -36,6 +36,8 @@ import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -335,7 +337,7 @@ public class TodoServiceImpl implements TodoService {
// String orderFlowKey = ProjectOrderInfo.ProcessTypeEnum.ONLINE.getCode().equals(projectOrderInfo.getProcessType()) ? // String orderFlowKey = ProjectOrderInfo.ProcessTypeEnum.ONLINE.getCode().equals(projectOrderInfo.getProcessType()) ?
// orderOnlineFlowKey : orderOfflineFlowKey; // orderOnlineFlowKey : orderOfflineFlowKey;
//启动流程 //启动流程
ProcessInstance processInstance = startProcess(flowBusinessKey, variables, processConfig.getDefinition().getFinancePayment()); ProcessInstance processInstance = startProcess(flowBusinessKey, variables,processKey);
completed(null, null, flowBusinessKey); completed(null, null, flowBusinessKey);
return processInstance; return processInstance;
} }
@ -399,6 +401,23 @@ public class TodoServiceImpl implements TodoService {
} }
} }
@Override
public <T> void fillApproveNode(List<T> list, List<String> processKeyList, Function<T, String> function, BiConsumer<T, Map<String, String>> consumer) {
List<String> businessKeyList = list.stream().map(function).collect(Collectors.toList());
if (CollUtil.isEmpty(businessKeyList)) {
return;
}
List<Todo> todoList = todoMapper.listTodoByBusinessKeyAndProcessKey(businessKeyList, processKeyList);
Map<String, String> approveUserNameMap = todoList.stream()
.collect(Collectors.groupingBy(Todo::getBusinessKey,
Collectors.mapping(Todo::getApproveUserName,
Collectors.joining("、"))));
for (T orderInfo : list) {
consumer.accept(orderInfo, approveUserNameMap);
}
}
@Override @Override
public AjaxResult statisticsTodo() { public AjaxResult statisticsTodo() {
Long userId = ShiroUtils.getUserId(); Long userId = ShiroUtils.getUserId();

View File

@ -61,4 +61,6 @@ public interface OmsTicketBillMapper
public int selectMaxCodeByPrefix(String codePrefix); public int selectMaxCodeByPrefix(String codePrefix);
void updateOmsTicketBillByCode(OmsTicketBill omsTicketBill);
} }

View File

@ -279,7 +279,7 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoC
} else { } else {
if (approveBtn.equals(0)) { if (approveBtn.equals(0)) {
handleRejectOrder(businessKey); handleRejectOrder(businessKey);
}else{ }else if (taskName.startsWith("财务")){
handleCompanyLeaderApproval(businessKey); handleCompanyLeaderApproval(businessKey);
} }
} }

View File

@ -1,11 +1,13 @@
package com.ruoyi.sip.service.impl; package com.ruoyi.sip.service.impl;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.config.flow.ProcessConfig;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.enums.ApproveStatusEnum; import com.ruoyi.common.enums.ApproveStatusEnum;
@ -15,9 +17,13 @@ import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.sip.domain.*; import com.ruoyi.sip.domain.*;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO; import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
import com.ruoyi.sip.flowable.domain.Todo;
import com.ruoyi.sip.flowable.service.TodoCommonTemplate;
import com.ruoyi.sip.flowable.service.TodoService;
import com.ruoyi.sip.service.IOmsFinAttachmentService; import com.ruoyi.sip.service.IOmsFinAttachmentService;
import com.ruoyi.sip.service.IOmsPayableBillService; import com.ruoyi.sip.service.IOmsPayableBillService;
import com.ruoyi.sip.service.IOmsPayableTicketDetailService; import com.ruoyi.sip.service.IOmsPayableTicketDetailService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.sip.mapper.OmsTicketBillMapper; import com.ruoyi.sip.mapper.OmsTicketBillMapper;
@ -35,7 +41,7 @@ import org.springframework.web.multipart.MultipartFile;
*/ */
@Service @Service
@Transactional @Transactional
public class OmsTicketBillServiceImpl implements IOmsTicketBillService public class OmsTicketBillServiceImpl implements IOmsTicketBillService, TodoCommonTemplate
{ {
@Autowired @Autowired
private OmsTicketBillMapper omsTicketBillMapper; private OmsTicketBillMapper omsTicketBillMapper;
@ -45,7 +51,11 @@ public class OmsTicketBillServiceImpl implements IOmsTicketBillService
private IOmsFinAttachmentService omsFinAttachmentService; private IOmsFinAttachmentService omsFinAttachmentService;
@Autowired @Autowired
private IOmsPayableBillService payableBillService; private IOmsPayableBillService payableBillService;
@Autowired
private TodoService todoService;
@Autowired
private ProcessConfig processConfig;
/** /**
* *
* *
@ -182,7 +192,11 @@ public class OmsTicketBillServiceImpl implements IOmsTicketBillService
omsTicketBill.setTicketPriceWithoutTax(bill.getTicketPriceWithoutTax()); omsTicketBill.setTicketPriceWithoutTax(bill.getTicketPriceWithoutTax());
omsTicketBill.setTicketType(bill.getTicketType()); omsTicketBill.setTicketType(bill.getTicketType());
updateOmsTicketBill(omsTicketBill); updateOmsTicketBill(omsTicketBill);
//todo 开启审批流程 todoService.startProcessDeleteBefore(omsTicketBill.getTicketBillCode(), omsTicketBill.getTicketBillCode(),
new HashMap<String, Object>() {{
put("applyUserName", ShiroUtils.getSysUser().getUserName());
put("applyUser", ShiroUtils.getUserId());
}}, processConfig.getDefinition().getFiananceTicket());
return AjaxResult.success(attachment); return AjaxResult.success(attachment);
} }
@ -231,11 +245,12 @@ public class OmsTicketBillServiceImpl implements IOmsTicketBillService
updateOmsTicketBill(originalBill); updateOmsTicketBill(originalBill);
//4 创建付款明细 //4 创建付款明细
payableTicketDetailService.applyRefund(originalBill.getTicketBillCode(),refundBill.getTicketBillCode()); payableTicketDetailService.applyRefund(originalBill.getTicketBillCode(),refundBill.getTicketBillCode());
//5.todo 开始退款审批流程 //5. 开始退款审批流程
todoService.startProcessDeleteBefore(originalBill.getTicketBillCode(), originalBill.getTicketBillCode(),
new HashMap<String, Object>() {{
put("applyUserName", ShiroUtils.getSysUser().getUserName());
put("applyUser", ShiroUtils.getUserId());
}}, processConfig.getDefinition().getFinanceTicketRefound());
return AjaxResult.success("退款申请已提交,新的退款单号为:" + refundBill.getTicketBillCode()); return AjaxResult.success("退款申请已提交,新的退款单号为:" + refundBill.getTicketBillCode());
} }
@ -272,4 +287,61 @@ public class OmsTicketBillServiceImpl implements IOmsTicketBillService
throw new RuntimeException("退回付款单操作失败:" + e.getMessage(), e); throw new RuntimeException("退回付款单操作失败:" + e.getMessage(), e);
} }
} }
@Override
public Object todoDetail(String businessKey, String processKey, String todoId) {
return null;
}
@Override
public Object completedTodoDetail(String businessKey, String processKey, String todoId) {
return null;
}
@Override
public boolean todoApproveCallback(Todo todo) {
if (CollUtil.isEmpty(todo.getVariables())) {
return TodoCommonTemplate.super.todoApproveCallback(todo);
}
Integer approveBtn = (Integer) todo.getVariables().get("approveBtn");
if (approveBtn == null) {
return TodoCommonTemplate.super.todoApproveCallback(todo);
}
String taskName = todo.getTaskName();
String businessKey = todo.getBusinessKey();
if (approveBtn.equals(0)) {
handleRejectOrder(businessKey);
} else if (taskName.startsWith("财务")) {
handleCompanyLeaderApproval(businessKey);
}
return TodoCommonTemplate.super.todoApproveCallback(todo);
}
private void handleRejectOrder(String businessKey) {
OmsTicketBill omsTicketBill = new OmsTicketBill();
omsTicketBill.setTicketBillCode(businessKey);
omsTicketBill.setApproveStatus(ApproveStatusEnum.APPROVE_REJECT.getCode());
omsTicketBillMapper.updateOmsTicketBillByCode(omsTicketBill);
}
private void handleCompanyLeaderApproval(String businessKey){
OmsTicketBill omsTicketBill = new OmsTicketBill();
omsTicketBill.setTicketBillCode(businessKey);
omsTicketBill.setApproveStatus(ApproveStatusEnum.APPROVE_COMPLETE.getCode());
omsTicketBill.setApproveTime(DateUtils.getNowDate());
omsTicketBillMapper.updateOmsTicketBillByCode(omsTicketBill);
}
@Override
public boolean multiInstanceApproveCallback(String activityName, ProcessInstance processInstance) {
return TodoCommonTemplate.super.multiInstanceApproveCallback(activityName, processInstance);
}
@Override
public void fillBusinessInfo(List<Todo> todoCompletedList) {
}
} }

View File

@ -137,7 +137,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectOmsPaymentBillById" parameterType="Long" resultMap="OmsPaymentBillResult"> <select id="selectOmsPaymentBillById" parameterType="Long" resultMap="OmsPaymentBillResult">
<include refid="selectOmsPaymentBillVo"/> <include refid="selectOmsPaymentBillVo"/>
where pb.id = #{id} where pb.id = #{id}
group by pb.id
</select> </select>
<insert id="insertOmsPaymentBill" parameterType="OmsPaymentBill" useGeneratedKeys="true" keyProperty="id"> <insert id="insertOmsPaymentBill" parameterType="OmsPaymentBill" useGeneratedKeys="true" keyProperty="id">
@ -351,7 +351,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
left join oms_vendor_info t2 on pb.vendor_code = t2.vendor_code left join oms_vendor_info t2 on pb.vendor_code = t2.vendor_code
left join project_order_info t4 on pb.order_code = t4.order_code left join project_order_info t4 on pb.order_code = t4.order_code
left join sys_user t5 on t5.user_id = t4.duty left join sys_user t5 on t5.user_id = t4.duty
inner join ${tableName} t3 on (t3.process_key in ('finance_payment') and t3.approve_user=#{entity.approveUser} and t3.task_name!='商务' and t3.business_key=pb.payment_bill_code) inner join ${tableName} t3 on (t3.process_key in (#{entity.processKey}) and t3.approve_user=#{entity.approveUser} and t3.task_name!='商务' and t3.business_key=pb.payment_bill_code)
<where> <where>
<if test="entity.paymentBillCode != null and entity.paymentBillCode != ''"> and pb.payment_bill_code like concat('%', #{entity.paymentBillCode}, '%')</if> <if test="entity.paymentBillCode != null and entity.paymentBillCode != ''"> and pb.payment_bill_code like concat('%', #{entity.paymentBillCode}, '%')</if>
<if test="entity.paymentBillType != null and entity.paymentBillType != ''"> and pb.payment_bill_type = #{entity.paymentBillType}</if> <if test="entity.paymentBillType != null and entity.paymentBillType != ''"> and pb.payment_bill_type = #{entity.paymentBillType}</if>

View File

@ -315,6 +315,79 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</trim> </trim>
where id = #{id} where id = #{id}
</update> </update>
<update id="updateOmsTicketBillByCode">
update oms_ticket_bill
<trim prefix="SET" suffixOverrides=",">
<if test="ticketType != null and ticketType != ''">
ticket_type = #{ticketType},
</if>
<if test="ticketBillType != null and ticketBillType != ''">
ticket_bill_type = #{ticketBillType},
</if>
<if test="vendorTicketTime != null">
vendor_ticket_time = #{vendorTicketTime},
</if>
<if test="ticketTime != null">
ticket_time = #{ticketTime},
</if>
<if test="vendorCode != null and vendorCode != ''">
vendor_code = #{vendorCode},
</if>
<if test="totalPriceWithTax != null">
total_price_with_tax = #{totalPriceWithTax},
</if>
<if test="ticketPriceWithTax != null">
ticket_price_with_tax = #{ticketPriceWithTax},
</if>
<if test="totalPriceWithoutTax != null">
total_price_without_tax = #{totalPriceWithoutTax},
</if>
<if test="ticketPriceWithoutTax != null">
ticket_price_without_tax = #{ticketPriceWithoutTax},
</if>
<if test="taxRate != null">
tax_rate = #{taxRate},
</if>
<if test="createBy != null and createBy != ''">
create_by = #{createBy},
</if>
<if test="createTime != null">
create_time = #{createTime},
</if>
<if test="updateBy != null and updateBy != ''">
update_by = #{updateBy},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="remark != null and remark != ''">
remark = #{remark},
</if>
<if test="delFlag != null and delFlag != ''">
del_flag = #{delFlag},
</if>
<if test="actualTicketTime != null">
actual_ticket_time = #{actualTicketTime},
</if>
<if test="ticketStatus != null and ticketStatus != ''">
ticket_status = #{ticketStatus},
</if>
<if test="approveStatus != null and approveStatus != ''">
approve_status = #{approveStatus},
</if>
<if test="approveNode != null and approveNode != ''">
approve_node = #{approveNode},
</if>
<if test="approveTime != null">
approve_time = #{approveTime},
</if>
<if test="refundStatus != null and refundStatus != ''">
refund_status = #{refundStatus},
</if>
</trim>
where ticket_bill_code = #{ticketBillCode}
</update>
<delete id="deleteOmsTicketBillById" parameterType="Long"> <delete id="deleteOmsTicketBillById" parameterType="Long">
delete from oms_ticket_bill where id = #{id} delete from oms_ticket_bill where id = #{id}