ERP系统中流程审批是必需的模块,流程审批需要工作流的支持,工作流实现企业业务流程重组(BPR)与系统解耦合,工作流产品有很多,国内的工作流产品比较适合国内项目的业务,国外开源的工作流更接近于标准WfMC,对国内复杂的业务支持不够灵活。通常情况软件公司都会开放适合自己的工作流模块,以适应项目开发。我们开发流程审批也是使用自己开发的流程引擎,流程的定义工具现在没有提供,需要手工配置XML流程模块。
工作流引擎关键接口
启动流程
startWorkflow(processId,startUserId,nextUser.getId(),instanceid,type,
title,nexttask,url,hmap,flag,testMode);
提交流程
wapi.submitWorkflow(instprocessid,instactivityid,instworkitemid,userIdLst,instanceid,
type,title,nexttask,url,hmap,flag,testMode);
获取代办列表
getWorkitemList(user.getId(), testMode);
工作流模板
<?xml version="1.0" encoding="gb2312"?>
<!--
注意:
流程触发外部接口,其类加载器必须位于工作流引擎类加载器平级或者更高一级
-->
<process id="testwf" name="请款流程" description="">
<status id="Start" name="开始" type="synchronized" isstart="true">
<tostatuses>
<tostatus batchid="tomanager" id="account" name="财务审批" condition="false" invoke="com.gf.statusflow.TestInvokeApp" invokecommand="sendmail"/>
</tostatuses>
<actors>
</actors>
</status>
<status id="draft" name="修改" type="synchronized" isstart="false">
<tostatuses>
<tostatus batchid="tomanager" id="account" name="财务审批" condition="false" invoke="" invokecommand=""/>
<tostatus batchid="destory" id="destory" name="作废" condition="false" invoke="com.gf.statusflow.TestInvokeApp" invokecommand="orderdestory"/>
</tostatuses>
<actors>
<actor type="variable" id="starter"/>
</actors>
</status>
<status id="account" name="财务审批" type="asynchronized" isstart="false">
<tostatuses>
<tostatus batchid="vp" id="vp" name="副总审批" condition="false" invoke="com.gf.statusflow.TestInvokeApp" invokecommand="orderdestory"/>
<tostatus batchid="destory" id="destory" name="作废" condition="false" invoke="com.gf.statusflow.TestInvokeApp" invokecommand="orderdestory"/>
<tostatus batchid="tostarter" id="draft" name="返回修改" condition="false" invoke="" invokecommand=""/>
</tostatuses>
<actors>
<actor type="user" id="fiadmin"/>
</actors>
</status>
<status id="vp" name="副总审批" type="asynchronized" isstart="false">
<tostatuses>
<tostatus batchid="tonotice" id="notice" name="通知申请人" condition="false" invoke="" invokecommand=""/>
<tostatus batchid="tostarter" id="draft" name="返回修改" condition="false" invoke="" invokecommand=""/>
<tostatus batchid="destory" id="destory" name="作废" condition="false" invoke="com.gf.statusflow.TestInvokeApp" invokecommand="orderdestory"/>
</tostatuses>
<actors>
<actor type="user" id="sysadmin"/>
</actors>
</status>
<status id="notice" name="通知申请人" type="asynchronized" isstart="false" workflow="outstore" condition="storeName EQ '发货库A(运达)'">
<tostatuses>
<tostatus batchid="end" id="end" name="完成" condition="false" invoke="com.gf.statusflow.TestInvokeApp" invokecommand="outstore"/>
<tostatus batchid="destory" id="destory" name="作废" condition="false" invoke="com.gf.statusflow.TestInvokeApp" invokecommand="outstoredestory"/>
</tostatuses>
<actors>
<actor type="variable" id="starter"/>
</actors>
</status>
<status id="destory" name="作废" type="synchronized" isstart="false">
<tostatuses>
</tostatuses>
<actors>
</actors>
</status>
<status id="end" name="结束" type="synchronized" isstart="false">
<tostatuses>
</tostatuses>
<actors>
</actors>
</status>
</process>
模块实体类
package com.gf.model;
import java.io.Serializable;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
@TableName("gf_expense")
public class ExpenseInfo implements Serializable{
@TableId
private String id = null;
private String name = null;
private String company = null;
private String bank = null;
private String bankAccount = null;
private BigDecimal amount = null;
private String userId = null;
@TableField(exist=false)
private String userName = null;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getBank() {
return bank;
}
public void setBank(String bank) {
this.bank = bank;
}
public String getBankAccount() {
return bankAccount;
}
public void setBankAccount(String bankAccount) {
this.bankAccount = bankAccount;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
SQL
create table gf_expense(id varchar(32),
name varchar(100),
company varchar(100),
bank varchar(100),
bankaccount varchar(100),
amount decimal(10,2),
userId varchar(100)
);
使用Mybatis-Plus操作数据
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gf.mapper.ExpenseMapper">
</mapper>
package com.gf.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.gf.model.ExpenseInfo;
@Mapper
public interface ExpenseMapper extends BaseMapper<ExpenseInfo>{
}
package com.gf.service;
import com.baomidou.mybatisplus.service.IService;
import com.gf.model.ExpenseInfo;
public interface ExpenseService extends IService<ExpenseInfo>{
}
package com.gf.service.impl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.gf.mapper.ExpenseMapper;
import com.gf.model.ExpenseInfo;
import com.gf.service.ExpenseService;
@Service
public class ExpenseServiceImpl extends ServiceImpl<ExpenseMapper,ExpenseInfo>
implements ExpenseService
{
}
模块Controller集成工作流引擎
package com.gf.ctrl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.gf.model.ExpenseInfo;
import com.gf.service.ExpenseService;
import com.gf.statusflow.IOrgModel;
import com.gf.statusflow.IUser;
import com.gf.statusflow.StatusFlowData;
import com.gf.statusflow.StatusFlowMng;
import com.gf.statusflow.StatusFlowWAPI;
import com.gf.statusflow.StatusMsg;
import com.gf.statusflow.UUID;
import com.gf.statusflow.Util;
@Controller
public class ExpenseCtrl {
@Autowired
private StatusFlowMng sfmng;
@Autowired
private StatusFlowWAPI wapi;
@Autowired
private IOrgModel orgmodel;
@Autowired
private ExpenseService expserv;
@RequestMapping("/expense.action")
public String expense(HttpServletRequest req)
{
String instanceid = req.getParameter("instanceid");
String instprocessid = req.getParameter("instprocessid");
String instactivityid = req.getParameter("instactivityid");
String instworkitemid = req.getParameter("instworkitemid");
StatusMsg wfMsg = null;
String nexttask = null;
List<Properties> nextBtnList = null;
List<Properties> nextActList = null;
IUser loginUser = Util.getLoginUser();
String userId = loginUser.getId();
String processId = "testwf";
HashMap hmap = null;
StatusFlowData wfdata = sfmng.getWorkflowData(userId, processId, nexttask, instworkitemid,hmap);
nextBtnList = wfdata.getNextBtnList();
nextActList = wfdata.getNextActList();
req.setAttribute("button", nextBtnList);
if(instanceid != null)
{
ExpenseInfo eps = expserv.selectById(instanceid);
req.setAttribute("eps", eps);
req.setAttribute("instprocessid", instprocessid);
req.setAttribute("instactivityid", instactivityid);
req.setAttribute("instworkitemid", instworkitemid);
}
System.out.println("nextActList==="+nextActList);
return "/fi/expense";
}
@RequestMapping("/expsubmit.action")
@ResponseBody
public StatusMsg expsave(HttpServletRequest req,ExpenseInfo exps)
{
try
{
String instanceid = exps.getId();
if("".equals(Util.fmtStr(instanceid)))
instanceid = UUID.create("expense");
String instprocessid = req.getParameter("instprocessid");
String instactivityid = req.getParameter("instactivityid");
String instworkitemid = req.getParameter("instworkitemid");
String nexttask = req.getParameter("nexttask");
String nextUserId = req.getParameter("nextUserId");
IUser loginUser = Util.getLoginUser();
String startUserId = loginUser.getId();
String processId = "testwf";
String type = "expense";
IUser nextUser = orgmodel.getUserByLoginId(nextUserId);
String title = exps.getName()+":"+exps.getAmount().toString();
String url = "/expense.action";
HashMap hmap = new HashMap();
String flag = "gf";
String testMode = "no";
exps.setId(instanceid);
StatusMsg wfMsg = null;
if("".equals(Util.fmtStr(instworkitemid)))
{
expserv.insert(exps);
System.out.println("nexttask==="+nexttask);
wfMsg = wapi.startWorkflow(processId,startUserId,nextUser.getId(),instanceid,type,
title,nexttask,url,hmap,flag,testMode);
}
else
{
List<String> userIdLst = new ArrayList<String>();
if(nextUser != null)
userIdLst.add(nextUser.getId());
expserv.updateById(exps);
wfMsg = wapi.submitWorkflow(instprocessid,instactivityid,instworkitemid,userIdLst,instanceid,
type,title,nexttask,url,hmap,flag,testMode);
}
return wfMsg;
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
}
JSP页面工作流集成
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<script type="text/javascript" src="/static/easyui/jquery.min.js"></script>
<script type="text/javascript" src="/static/easyui/jquery.easyui.min.js"></script>
<script type="text/javascript" src="/static/easyui/easyui-lang-zh_CN.js"></script>
<link rel="stylesheet" href="/static/easyui/themes/default/easyui.css"/>
<link rel="stylesheet" href="/static/easyui/themes/icon.css"/>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>财务报销系统</title>
<script>
function wfsubmit(nexttask,nextuserid)
{
var rtn = $('#frm').form('validate');
if(!rtn)
return;
var nextUserId = '';
if('end' != nexttask && 'endall' != nexttask && 'destory' != nexttask)
{
if(nextuserid == null || nextuserid == '')
{
$.messager.alert('错误提示','下一办理人为空!','error');
return;
}
var objdim = nextuserid.split(";");
for(var k=0;k<objdim.length;k++)
{
var objdim2 = objdim[k].split(",");
if(objdim2[0] == null || objdim2[0] == '')
{
$.messager.alert('错误提示','下一办理人为空!','error');
return;
}
nextUserId = objdim2[0];
}
}
var rtn = $('#frm').form('validate');
if(rtn)
{
$('#nexttask').val(nexttask);
$('#nextUserId').val(nextUserId);
$('#frm').form('submit', {
url:'/expsubmit.action',
onSubmit: function(){
},
success:function(data){
//alert(data);
$('#buttondiv').empty();
}
});
}
}
</script>
</head>
<body class="easyui-layout">
<div data-options="region:'center',title:'部门维护'" style="padding:5px;">
<div>
<img height="100" width="800" src="/downimg.jsp?isorderwf=true&width=600&height=100&processid=testwf&instanceid=${eps.id}&t=<%=new Date()%>"/>
</div>
<form id="frm" method="post">
<input type="hidden" id="instprocessid" name="instprocessid" value="${instprocessid}"/>
<input type="hidden" id="instactivityid" name="instactivityid" value="${instactivityid}"/>
<input type="hidden" id="instworkitemid" name="instworkitemid" value="${instworkitemid}"/>
<input type="hidden" id="id" name="id" value="${eps.id}"/>
<input type="hidden" id="nexttask" name="nexttask"/>
<input type="hidden" id="nextUserId" name="nextUserId"/>
<input type="hidden" id="userId" name="userId" value="${eps.userId}"/>
<div style="margin-left:50px;margin-top:30px">
<input class="easyui-textbox" type="text" id="name" name="name" value="${eps.name}" data-options="width:300,label:'请款名称:'" />
</div>
<div style="margin-left:50px;margin-top:30px">
<input class="easyui-textbox" type="text" id="company" name="company" value="${eps.company}" data-options="width:300,label:'公司名称:'" />
</div>
<div style="margin-left:50px;margin-top:30px">
<input class="easyui-textbox" type="text" id="bank" name="bank" value="${eps.bank}" data-options="width:300,label:'银行名称:'" />
</div>
<div style="margin-left:50px;margin-top:30px">
<input class="easyui-textbox" type="text" id="bankAccount" name="bankAccount" value="${eps.bankAccount}" data-options="width:300,label:'银行账号:'" />
</div>
<div style="margin-left:50px;margin-top:30px">
<input class="easyui-textbox" type="text" id="amount" name="amount" value="${eps.amount}" data-options="width:300,label:'金额(单位:分):'" />
</div>
<div id="buttondiv" style="margin-left:50px;margin-top:30px">
<%
List<Properties> button = (List<Properties>)request.getAttribute("button");
for(Properties prop:button)
{
String name = prop.getProperty("name");
String js = prop.getProperty("js");
%>
<a id="btn" onclick="<%=js %>" class="easyui-linkbutton"><%=name %></a>
<%
}
%>
</div>
</form>
</div>
</body>
</html>