基于activiti框架的工作流提交、审批以及撤销
背景:在一个企业中,大部分的事情都不不是一个人做决定的,往往需要经过几个层级的审批。这样就有一个流程,工作流就是方便这一个方面的需求。
1.认识工作流常用表
- 历史节点表( act_hi_actinst )
-
简要描述
历史活动信息,这里记录流程流转过的所有节点。act_hi_taskinst表中只记录了历史节点表中节点类型为usertask的信息。
-
表结构说明
-
- 运行时流程执行实例表( act_ru_execution )
-
简要描述
流程执行记录表。
-
表结构说明
-
- 运行时任务节点表( act_ru_task )
-
简要描述
运行时任务数据表。
-
表结构说明
-
2.工作流的提交(以HGXP项目中的供应商提交为例)
-
前台请求
$.ajax({ type: 'GET', url: "${base.contextPath}/cux/gxp/vendor/basic/pubapprove",//提交审批先走各自的入口,方便进行状态等的校验。然后再调用通入审批。 success: function (json) { kendo.ui.showInfoDialog({ message: json.message }).done(function () { if (json.success) { window.location.reload();//如果提交成功,刷新状态 } }); }, async: false, dataType: "json", data: {"docId": headerId, "approveCode": "GXP_VENDOR"} //提交需要审批的供应商头ID,以及在通用审批设置平台中维护的CODE(对应应该走哪条工作流) });
如下图,HGXP中就是在这个块码中维护
approveCode
:
并在通用审批设置平台中维护:
可以看出,在这个通用审批设置平台中,我们维护好了各工作流对应的头表、主键以及记录审批状态的字段status_Code
-
后台走通用审批处理service (
PubApproveServiceImpl.java
)public ResponseData pubApprove(IRequest request, String approveCode, Long docId) { PubApproveSetup pubApproveSetup = new PubApproveSetup(); ResponseData responseData = null; String errMsg = ""; int result = 0; //根据docId查找对应资质头信息并校验是否可以提交审批 if("GXP_VENDOR".equals(approveCode)){ GxpVendorBasic gxpVendorBasic =new GxpVendorBasic(); gxpVendorBasic.setBasicId(docId); List
gxpVendorBasicList=gxpVendorBasicMapper.select(gxpVendorBasic); if(gxpVendorBasicList.size()==1){ String status=gxpVendorBasicList.get(0).getStatusCode(); responseData =returnResponseData(status); //这一步就是校验该资质头是否可以提交(不为已提交和已审批状态) if(responseData !=null){ return responseData; } } } /************************************************************************* * 1.0 根据approveCode获取审批配置平台配置属性 ************************************************************************ */ pubApproveSetup.setApproveCode(approveCode); pubApproveSetup = pubApproveSetupMapper.selectOne(pubApproveSetup); if (pubApproveSetup == null) { errMsg += "审批代码" + approveCode + "未在通用审批平台定义!"; responseData = new ResponseData(false); responseData.setMessage(errMsg); return responseData; } /************************************************************************* * 1.0 根据approveCode获取审批配置平台配置属性 ************************************************************************ */ if ("Y".equals(pubApproveSetup.getApproveByself()) && "Y".equals(pubApproveSetup.getApproveAlterFlag())) { //如果为自审批并且审批直接修改状态,则直接修改状态 try { result = updateDocStatus(pubApproveSetup.getTableName(), pubApproveSetup.getTableKey(), pubApproveSetup.getStatusField(), pubApproveSetup.getApproveStatus(), docId); } catch (Exception e) { e.printStackTrace(); errMsg += "自审批失败:" + ExceptionUtils.getRootCauseMessage(e); } } else if ("N".equals(pubApproveSetup.getApproveByself())) { //提交审批工作流进行审批 //组合参数 List list=new ArrayList<>(); RestVariable var1=new RestVariable(); var1.setName("approveCode"); var1.setValue(approveCode); RestVariable var2=new RestVariable(); var2.setName("processDefinitionKey"); var2.setValue(pubApproveSetup.getActivitiKey()); RestVariable var3=new RestVariable(); var3.setName("docId"); var3.setValue(docId); RestVariable var4=new RestVariable(); var4.setName("title"); var4.setValue(pubApproveSetup.getFormTitle()); RestVariable var5=new RestVariable(); var5.setName("url"); var5.setValue(pubApproveSetup.getFormUrl()); list.add(var1); list.add(var2); list.add(var3); list.add(var4); list.add(var5); ProcessInstanceResponse response=startActivitiService.startActiviti(request,list); System.out.println(response==null); if(response!=null) { if ("Y".equals(pubApproveSetup.getSubmitAlterFlag())) { //如果不为自审批,并且提交时自动修改状态,则修改为提交动作的状态 errMsg+=processStatus(request,approveCode,docId,"SUBMIT"); } //工作流提交成功 向通用审批事务表表插入数据 PubApproveTransaction pubApproveTransaction=new PubApproveTransaction(); pubApproveTransaction.setActivitiKey(pubApproveSetup.getActivitiKey()); pubApproveTransaction.setActivitiId(Long.parseLong(response.getId())); pubApproveTransaction.setApproveCode(approveCode); pubApproveTransaction.setDocId(docId); pubApproveTransaction.setObjectVersionNumber(1L); pubApproveTransactionService.insertSelective(request,pubApproveTransaction); } else { errMsg+="提交工作流出错!"; } } if (errMsg==null||"".equals(errMsg)) { responseData = new ResponseData(true); responseData.setMessage("提交成功!"); } else { responseData = new ResponseData(false); responseData.setMessage(errMsg); } return responseData; } 上面的代码可以分割为:
- 排除为已提交和已审批的状态(若为这两个状态就提示不能提交)
- 获取通用审批配置平台上维护的数据
- 判断是否为自审批
- 若为自审批则直接修改资质头的状态不发起工作流
- 若不为自审批状态,则发起工作流,
List
中我们就将通用审批配置平台中的值全部带在工作流中
其中
startActivitiService.startActiviti(request,list)
这个方法是发起工作流的方法
实现类实现红色框中的接口,重写其中的
startActiviti
方法:@Autowired private IActivitiService activitiService; @Override public ProcessInstanceResponse startActiviti(IRequest irequest, List
list) { ProcessInstanceCreateRequest request=new ProcessInstanceCreateRequest(); request.setBusinessKey("104"); // request.setProcessDefinitionId(null); String key=""; for (int i=0;i 上面的代码就可以理解为:
- 模拟一个request请求
- 其中
processDefinitionKey
就是通用配置平台中维护的工作流程字段,这个字段的作用就决定着我们是走的那一条工作流,这个字段就是与我们流程设计中的流程编码对应
-
variables
就是我们流程中的变量,这其中就是上一步骤中获取的通用审批配置平台中数据,存在 运行时流程变量数据表(act_ru_variable
)中
- 其中
至此工作流的发起工作就完了,工作流程发起了之后我们剩下需要做的工作就是改变我们对应资质头的状态,并记录工作流程到 通用审批事务表(
cux_pub_approve_transaction
)中if ("Y".equals(pubApproveSetup.getSubmitAlterFlag())) { //如果不为自审批,并且提交时自动修改状态,则修改为提交动作的状态 errMsg+=processStatus(request,approveCode,docId,"SUBMIT"); } //工作流提交成功 向通用审批事务表表插入数据 PubApproveTransaction pubApproveTransaction=new PubApproveTransaction(); pubApproveTransaction.setActivitiKey(pubApproveSetup.getActivitiKey()); pubApproveTransaction.setActivitiId(Long.parseLong(response.getId())); pubApproveTransaction.setApproveCode(approveCode); pubApproveTransaction.setDocId(docId); pubApproveTransaction.setObjectVersionNumber(1L); pubApproveTransactionService.insertSelective(request,pubApproveTransaction);
第一步,就是改变资质头的状态为
SUBMITTED
(已提交)-
第二部,就是将工作流程记录到通用审批事务表中
-
activitiKey
工作流程的标识即:流程设计页面中的流程编码 -
activitiId
发起工作流后的,产生的流程Id -
approveCode
审批代码(块码中维护的值) -
docId
资质头Id -
objectversionnumber
版本号
-
3.工作流的审批(以HGXP项目中供应商资质审批为例)
-
前台请求
var currentTaskId = '${RequestParameters.taskId!taskId}'; //获取任务ID $('#btn-approved').click(function () { kendo.ui.showConfirmDialog({ title: $l('hap.confirm'), message: $l('hap.confirm') + '' + $('#text-approved').text() + '?' }).done(function (e) { if (e.button == 'OK') { taskAction({approveResult: 'APPROVED'}); //传的参数为:`approveResult`属性值为`APPROVED`的对象 } }) }); function taskAction(p) { p = p || {}; //传进的对象(会带有`approveResult`属性) p.action = p.action || 'complete' //设置 `action` 属性为 `complete` (完成任务) if('pending' == currentTaskInfo.delegationState && 'delegate'!=p.action){ //如果有转交任务的操作 p.action = 'resolve' //设置 `action` 属性为 `resolve`(转交操作) } var variables = []; if (p.action != 'delegate') { var formVars = {}; formVars.approveResult = p.approveResult; formVars.comment = $("#ta-comment").val(); $.each(formVars, function (k, v) { variables.push({name: k, value: v}); //设置 `variables` 的 `approveResult` 和 `comment` 属性的值 }) } var param = { assignee: p.targetUser || null, action: p.action, //任务的动作`complete` comment: $("#ta-comment").val(), // 审批意见 variables: variables, jumpTarget: p.jumpTarget || null, carbonCopyUsers :null, }; /** * p{approveResult: "APPROVED", action: "complete"} * variables = [{name: "approveResult", value: "APPROVED"}, {name: "comment", value: ""}] * param = {assignee: null, action: "complete", comment: "", variables: Array(2), jumpTarget: null, carbonCopyUsers:""} */ $.ajax({ url: contextPath_ + '/wfl/runtime/<#if isAdmin!false>admin/#if>tasks/' + currentTaskId, type: 'POST', contentType: 'application/json', data: kendo.stringify(param), success: function (args) { if (args.success === false) { kendo.ui.showErrorDialog({ title: $l('hap.error'), message: args.message }); } else { if (p.callback) { p.callback(args); } else { kendo.ui.showInfoDialog({ title: $l('hap.tip.info'), message: '操作完成!' }).done(function () { closeCurrentWin() }); } } }, error: function (args) { kendo.ui.showInfoDialog({ title: $l('hap.error'), message: kendo.stringify(args) }); } }) }
-
后台走框架封装好的
ActivitiController.class
@RequestMapping( value = {"/runtime/tasks/{taskId}"}, method = {RequestMethod.POST} ) @ResponseStatus(HttpStatus.OK) public void executeTaskAction(@PathVariable String taskId, @RequestBody TaskActionRequestExt actionRequest, HttpServletRequest request, HttpServletResponse response) throws TaskActionException { IRequest iRequest = this.createRequestContext(request); this.activitiService.executeTaskAction(iRequest, taskId, actionRequest, false); }
- 从目前来应用方面来讲,主要就是在前台设置好完成当前任务的几个参数,主要的参数
action:"complete"
-
variables
中的approveResult
- 从目前来应用方面来讲,主要就是在前台设置好完成当前任务的几个参数,主要的参数
4.工作流的撤销
- 前台请求
工作流的撤销,前台请求和提交工作流类似,就是从前台传一个function cancelApproveData() { $.ajax({ url:"${base.contextPath}/hmdmwfl/canclewf?id="+headerId+"&approveCode="+"GXP_VENDOR", dataType:"json", contentType:"application/json", type:"POST", success:function (data) { if(data.success==true){ Hap.submitForm({ url: '${base.contextPath}/cux/gxp/vendor/basic/ignore/submit', formModel: viewModel.model, success: function (json){ if(json.success){ kendo.ui.showInfoDialog({ message: "成功" }).done(function () { window.location.reload(); }); }else{ kendo.ui.showErrorDialog({ message: "失败" }).done(function () { window.location.reload(); }); } } }); }else { kendo.ui.showErrorDialog({ message: data.message }); } } }); }
basicId
和approveCode
到后台 - 后台走
HmdmActivitiController.java
@RequestMapping(value="/canclewf") public ResponseData cancle(HttpServletRequest request, @RequestParam Long id,@RequestParam String approveCode){ IRequest irequest = createRequestContext(request); ResponseData rDate = new ResponseData(); List
transactionList=pubApproveTransactionService.selectAllByHeaderId(id,approveCode); if(transactionList.size()==0){ rDate.setSuccess(false); rDate.setMessage("此流程为批量分配数据审批流程,正在审批中,不能撤销!"); return rDate; }else { PubApproveTransaction approveTransaction=transactionList.get(transactionList.size()-1); String procId=approveTransaction.getActivitiId().toString(); // 当前用户的ID Long userId = irequest.getUserId(); User currentUser = userMapper.selectByPrimaryKey(userId); String currentUserName = currentUser.getUserName(); String employeeCode = irequest.getEmployeeCode(); // 根据流程Id,查找流程实例 HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery(); historicProcessInstanceQuery.processInstanceId(procId); // 流程实例 HistoricProcessInstance historicProcessInstance = historicProcessInstanceQuery.singleResult(); // 流程的启动人 String startUser = historicProcessInstance.getStartUserId(); String processInstanceId = historicProcessInstance.getId(); String priorActType = pubApproveTransactionService.selectPriorNodeActType(processInstanceId); // 当前用户和流程启动是同一人 if (employeeCode.equals(startUser)) { if(priorActType.equals("startEvent")){ // 根据流程实例,查找盖流程实例的所有任务 HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery(); historicTaskInstanceQuery.processInstanceId(processInstanceId); historicTaskInstanceQuery.orderByTaskCreateTime().asc(); List historicTaskInstanceList = historicTaskInstanceQuery.list(); // 排除只有一个节点的流程,而且提交人就是审批人的情况 TaskQuery taskQuery = taskService.createTaskQuery(); List taskList = taskQuery.processInstanceId(processInstanceId).list(); HistoricTaskInstance instance = historicTaskInstanceList.get(0); String taskId = instance.getId(); //增加撤销节点,并办理 Map variables = new HashMap (); variables.put("approveResult", "RETRACT");//REJECTED taskService.addComment(taskId, null, "action", "RETRACT"); taskService.addComment(taskId, processInstanceId, "comment", ""); //添加评论 taskService.claim(taskId, currentUserName); taskService.complete(taskId, variables); //办理任务,相当于将任务办理掉以此来达到撤销工作流的效果 rDate.setSuccess(true); rDate.setMessage(id.toString()); return rDate; }else { rDate.setSuccess(false); rDate.setMessage("流程已有审批,不能撤回!"); return rDate; } } } rDate.setSuccess(false); rDate.setMessage("非发起人,不能撤回!"); return rDate; }