基于activiti框架的工作流提交朗恳、審批以及撤銷
背景:在一個企業(yè)中,大部分的事情都不不是一個人做決定的载绿,往往需要經(jīng)過幾個層級的審批粥诫。這樣就有一個流程,工作流就是方便這一個方面的需求崭庸。
1.認(rèn)識工作流常用表
- 歷史節(jié)點表( act_hi_actinst )
-
簡要描述
歷史活動信息怀浆,這里記錄流程流轉(zhuǎn)過的所有節(jié)點。act_hi_taskinst表中只記錄了歷史節(jié)點表中節(jié)點類型為usertask的信息怕享。
-
表結(jié)構(gòu)說明
歷史節(jié)點表
-
- 運行時流程執(zhí)行實例表( act_ru_execution )
-
簡要描述
流程執(zhí)行記錄表执赡。
-
表結(jié)構(gòu)說明
運行時流程執(zhí)行實例表
-
- 運行時任務(wù)節(jié)點表( act_ru_task )
-
簡要描述
運行時任務(wù)數(shù)據(jù)表。
-
表結(jié)構(gòu)說明
運行時任務(wù)節(jié)點表
-
2.工作流的提交(以HGXP項目中的供應(yīng)商提交為例)
-
前臺請求
$.ajax({ type: 'GET', url: "${base.contextPath}/cux/gxp/vendor/basic/pubapprove",//提交審批先走各自的入口函筋,方便進行狀態(tài)等的校驗沙合。然后再調(diào)用通入審批。 success: function (json) { kendo.ui.showInfoDialog({ message: json.message }).done(function () { if (json.success) { window.location.reload();//如果提交成功跌帐,刷新狀態(tài) } }); }, async: false, dataType: "json", data: {"docId": headerId, "approveCode": "GXP_VENDOR"} //提交需要審批的供應(yīng)商頭ID首懈,以及在通用審批設(shè)置平臺中維護的CODE(對應(yīng)應(yīng)該走哪條工作流) });
如下圖,HGXP中就是在這個塊碼中維護
approveCode
:
mark
并在通用審批設(shè)置平臺中維護:
mark
可以看出谨敛,在這個通用審批設(shè)置平臺中究履,我們維護好了各工作流對應(yīng)的頭表、主鍵以及記錄審批狀態(tài)的字段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; //根據(jù)docId查找對應(yīng)資質(zhì)頭信息并校驗是否可以提交審批 if("GXP_VENDOR".equals(approveCode)){ GxpVendorBasic gxpVendorBasic =new GxpVendorBasic(); gxpVendorBasic.setBasicId(docId); List<GxpVendorBasic> gxpVendorBasicList=gxpVendorBasicMapper.select(gxpVendorBasic); if(gxpVendorBasicList.size()==1){ String status=gxpVendorBasicList.get(0).getStatusCode(); responseData =returnResponseData(status); //這一步就是校驗該資質(zhì)頭是否可以提交(不為已提交和已審批狀態(tài)) if(responseData !=null){ return responseData; } } } /************************************************************************* * 1.0 根據(jù)approveCode獲取審批配置平臺配置屬性 ************************************************************************ */ pubApproveSetup.setApproveCode(approveCode); pubApproveSetup = pubApproveSetupMapper.selectOne(pubApproveSetup); if (pubApproveSetup == null) { errMsg += "審批代碼" + approveCode + "未在通用審批平臺定義!"; responseData = new ResponseData(false); responseData.setMessage(errMsg); return responseData; } /************************************************************************* * 1.0 根據(jù)approveCode獲取審批配置平臺配置屬性 ************************************************************************ */ if ("Y".equals(pubApproveSetup.getApproveByself()) && "Y".equals(pubApproveSetup.getApproveAlterFlag())) { //如果為自審批并且審批直接修改狀態(tài)佣盒,則直接修改狀態(tài) 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())) { //提交審批工作流進行審批 //組合參數(shù) List<RestVariable> 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())) { //如果不為自審批,并且提交時自動修改狀態(tài),則修改為提交動作的狀態(tài) errMsg+=processStatus(request,approveCode,docId,"SUBMIT"); } //工作流提交成功 向通用審批事務(wù)表表插入數(shù)據(jù) 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; }
上面的代碼可以分割為:
- 排除為已提交和已審批的狀態(tài)(若為這兩個狀態(tài)就提示不能提交)
- 獲取通用審批配置平臺上維護的數(shù)據(jù)
- 判斷是否為自審批
- 若為自審批則直接修改資質(zhì)頭的狀態(tài)不發(fā)起工作流
- 若不為自審批狀態(tài)盯仪,則發(fā)起工作流紊搪,
List<RestVariable>
中我們就將通用審批配置平臺中的值全部帶在工作流中
其中
startActivitiService.startActiviti(request,list)
這個方法是發(fā)起工作流的方法
mark實現(xiàn)類實現(xiàn)紅色框中的接口,重寫其中的
startActiviti
方法:@Autowired private IActivitiService activitiService; @Override public ProcessInstanceResponse startActiviti(IRequest irequest, List<RestVariable> list) { ProcessInstanceCreateRequest request=new ProcessInstanceCreateRequest(); request.setBusinessKey("104"); // request.setProcessDefinitionId(null); String key=""; for (int i=0;i<list.size();i++) { System.out.println(list.get(i).getName()); System.out.println(list.get(i).getName()=="processDefinitionKey"); System.out.println("processDefinitionKey".equals(list.get(i).getName())); if(list.get(i).getName()=="processDefinitionKey"||"processDefinitionKey".equals(list.get(i).getName())) { key=list.get(i).getValue().toString(); } } System.out.println(key); request.setProcessDefinitionKey(key); request.setReturnVariables(false); request.setTenantId(null); request.setVariables(list); request.setTransientVariables(null); return activitiService.startProcess(irequest, request); }
上面的代碼就可以理解為:
- 模擬一個request請求
- 其中
processDefinitionKey
就是通用配置平臺中維護的工作流程字段全景,這個字段的作用就決定著我們是走的那一條工作流耀石,這個字段就是與我們流程設(shè)計中的流程編碼對應(yīng)
mark -
variables
就是我們流程中的變量,這其中就是上一步驟中獲取的通用審批配置平臺中數(shù)據(jù)爸黄,存在 運行時流程變量數(shù)據(jù)表(act_ru_variable
)中
- 其中
至此工作流的發(fā)起工作就完了滞伟,工作流程發(fā)起了之后我們剩下需要做的工作就是改變我們對應(yīng)資質(zhì)頭的狀態(tài),并記錄工作流程到 通用審批事務(wù)表(
cux_pub_approve_transaction
)中if ("Y".equals(pubApproveSetup.getSubmitAlterFlag())) { //如果不為自審批炕贵,并且提交時自動修改狀態(tài)梆奈,則修改為提交動作的狀態(tài) errMsg+=processStatus(request,approveCode,docId,"SUBMIT"); } //工作流提交成功 向通用審批事務(wù)表表插入數(shù)據(jù) 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);
第一步,就是改變資質(zhì)頭的狀態(tài)為
SUBMITTED
(已提交)-
第二部称开,就是將工作流程記錄到通用審批事務(wù)表中
-
activitiKey
工作流程的標(biāo)識即:流程設(shè)計頁面中的流程編碼 -
activitiId
發(fā)起工作流后的亩钟,產(chǎn)生的流程Id -
approveCode
審批代碼(塊碼中維護的值) -
docId
資質(zhì)頭Id -
objectversionnumber
版本號
-
3.工作流的審批(以HGXP項目中供應(yīng)商資質(zhì)審批為例)
-
前臺請求
var currentTaskId = '${RequestParameters.taskId!taskId}'; //獲取任務(wù)ID $('#btn-approved').click(function () { kendo.ui.showConfirmDialog({ title: $l('hap.confirm'), message: $l('hap.confirm') + '<span class="action_ok">' + $('#text-approved').text() + '</span>?' }).done(function (e) { if (e.button == 'OK') { taskAction({approveResult: 'APPROVED'}); //傳的參數(shù)為:`approveResult`屬性值為`APPROVED`的對象 } }) }); function taskAction(p) { p = p || {}; //傳進的對象(會帶有`approveResult`屬性) p.action = p.action || 'complete' //設(shè)置 `action` 屬性為 `complete` (完成任務(wù)) if('pending' == currentTaskInfo.delegationState && 'delegate'!=p.action){ //如果有轉(zhuǎn)交任務(wù)的操作 p.action = 'resolve' //設(shè)置 `action` 屬性為 `resolve`(轉(zhuǎn)交操作) } 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}); //設(shè)置 `variables` 的 `approveResult` 和 `comment` 屬性的值 }) } var param = { assignee: p.targetUser || null, action: p.action, //任務(wù)的動作`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); }
- 從目前來應(yīng)用方面來講鳖轰,主要就是在前臺設(shè)置好完成當(dāng)前任務(wù)的幾個參數(shù)清酥,主要的參數(shù)
action:"complete"
-
variables
中的approveResult
- 從目前來應(yīng)用方面來講鳖轰,主要就是在前臺設(shè)置好完成當(dāng)前任務(wù)的幾個參數(shù)清酥,主要的參數(shù)
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<PubApproveTransaction> transactionList=pubApproveTransactionService.selectAllByHeaderId(id,approveCode); if(transactionList.size()==0){ rDate.setSuccess(false); rDate.setMessage("此流程為批量分配數(shù)據(jù)審批流程焰轻,正在審批中,不能撤銷昆雀!"); return rDate; }else { PubApproveTransaction approveTransaction=transactionList.get(transactionList.size()-1); String procId=approveTransaction.getActivitiId().toString(); // 當(dāng)前用戶的ID Long userId = irequest.getUserId(); User currentUser = userMapper.selectByPrimaryKey(userId); String currentUserName = currentUser.getUserName(); String employeeCode = irequest.getEmployeeCode(); // 根據(jù)流程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); // 當(dāng)前用戶和流程啟動是同一人 if (employeeCode.equals(startUser)) { if(priorActType.equals("startEvent")){ // 根據(jù)流程實例辱志,查找蓋流程實例的所有任務(wù) HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery(); historicTaskInstanceQuery.processInstanceId(processInstanceId); historicTaskInstanceQuery.orderByTaskCreateTime().asc(); List<HistoricTaskInstance> historicTaskInstanceList = historicTaskInstanceQuery.list(); // 排除只有一個節(jié)點的流程,而且提交人就是審批人的情況 TaskQuery taskQuery = taskService.createTaskQuery(); List<Task> taskList = taskQuery.processInstanceId(processInstanceId).list(); HistoricTaskInstance instance = historicTaskInstanceList.get(0); String taskId = instance.getId(); //增加撤銷節(jié)點忆肾,并辦理 Map<String, Object> variables = new HashMap<String, Object>(); variables.put("approveResult", "RETRACT");//REJECTED taskService.addComment(taskId, null, "action", "RETRACT"); taskService.addComment(taskId, processInstanceId, "comment", ""); //添加評論 taskService.claim(taskId, currentUserName); taskService.complete(taskId, variables); //辦理任務(wù)荸频,相當(dāng)于將任務(wù)辦理掉以此來達(dá)到撤銷工作流的效果 rDate.setSuccess(true); rDate.setMessage(id.toString()); return rDate; }else { rDate.setSuccess(false); rDate.setMessage("流程已有審批,不能撤回客冈!"); return rDate; } } } rDate.setSuccess(false); rDate.setMessage("非發(fā)起人,不能撤回稳强!"); return rDate; }