1.什么是會簽?
在流程業務管理中,任務是通常都是由一個人去處理的,而多個人同時處理一個任務,這種任務我們稱之為會簽任務。這種業務需求很常見,如一個請款單,領導審批環節中,就需要多個部門領導簽字。在流程業務中,我們可以把每個領導簽字的環節都定義為任務,並且這個會簽的人員是不固定的,若固定的我們可以通過Activiti的並行任務或串行任務來處理。會簽的引入說明,無非就是為了流程流轉至某一環節點,其審批的人員是動態的,並且需要根據會簽審批的結果實現流程的不同流轉。
2.中國特色的會簽需求是什么?
會簽需求主要有以下兩方面:
- 會簽的參與人員
- 會簽審批的順序
- 會簽審批的結果
- 動態加簽
以下我們就是圍繞以上的需求進行擴展實現的
3.Activiti對於會簽的實現
BPMN2的標准中並沒有對以上這種情景提供完善的支持,因此要在Activiti中實現會簽審批,我們需要結合Activiti提供的流程任務的多實例特性,進行一些必要的擴展,以支持我們的中國特色的會簽需求。
會簽任務也是一種人工任務,其在activiti的定義中,也是使用UserTask來定義,但在屬性上我們需要對這個定義的類型進行特殊的配置,即為多任務實例類型(並行或串行)任何一種。另外需要定義會簽的參與人員,再定義會簽的完成條件(若不定義,表示其是所有參與人均完成后,流程才往下跳轉)。
3.1.多實例的人工任務配置
通過在UserTask節點的屬性上配置,如下所示:
其生成的BPMN的配置文件如下所示:
<userTask id=”sid-78A17A9B-1185-48AA-A1CA-611421251D52″ name=”經理會簽”>
<multiInstanceLoopCharacteristics isSequential=”false” activiti:collection=”${counterSignService.getUsers(execution)}”>
<completionCondition>${counterSignService.isComplete(execution)}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<multiInstanceLoopCharacteristics isSequential=”false” activiti:collection=”${counterSignService.getUsers(execution)}”>
<completionCondition>${counterSignService.isComplete(execution)}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
【說明】:
- isSequential=”false” 表示這是非串行會簽,即為並行會簽,如三個人參與會簽,是三個人同時收到待辦,任務實例是同時產生的。
- activiti:collection 表示是會簽的參與人員集合,用戶可以通過定義自身的服務類來獲取
- completionCondition 表示是任務往下跳轉的完成條件,返回true是,表示條件成立,流程會跳至下一審批環節。
我們就是圍繞着這幾點來實現中國式的流程會簽的
3.2 會簽任務的人員集合計算處理
我們在Spring容器中定義一個會簽服務類(counterSignService)里面提供兩個api接口,一個是獲得任務的人員集合,另一個是判斷當前任務是否已經完成了會簽的計算,其參考代碼如下所示:
package com.redxun.bpm.core.service.sign; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.annotation.Resource; import org.activiti.engine.impl.pvm.delegate.ActivityExecution; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.redxun.bpm.activiti.util.ProcessHandleHelper; import com.redxun.bpm.core.entity.BpmDestNode; import com.redxun.bpm.core.entity.BpmRuPath; import com.redxun.bpm.core.entity.BpmSignData; import com.redxun.bpm.core.entity.IExecutionCmd; import com.redxun.bpm.core.entity.ProcessMessage; import com.redxun.bpm.core.entity.config.MultiTaskConfig; import com.redxun.bpm.core.entity.config.TaskVotePrivConfig; import com.redxun.bpm.core.entity.config.UserTaskConfig; import com.redxun.bpm.core.identity.service.BpmIdentityCalService; import com.redxun.bpm.core.manager.BpmNodeSetManager; import com.redxun.bpm.core.manager.BpmSignDataManager; import com.redxun.bpm.enums.TaskOptionType; import com.redxun.org.api.model.IdentityInfo; import com.redxun.sys.org.entity.OsGroup; import com.redxun.sys.org.entity.OsRelType; import com.redxun.sys.org.entity.OsUser; import com.redxun.sys.org.manager.OsGroupManager; import com.redxun.sys.org.manager.OsUserManager; /** * 會簽配置服務類 * @author csx * */ public class CounterSignService { @Resource private BpmSignDataManager bpmSignDataManager; @Resource private BpmNodeSetManager bpmNodeSetManager; @Resource BpmIdentityCalService bpmIdentityCalService; @Resource private OsGroupManager osGroupManager; @Resource private OsUserManager osUserManager; private Log logger=LogFactory.getLog(CounterSignService.class); /** * 獲得會簽任務中的人員計算集合 * @param execution * @return */ public Set<String> getUsers(ActivityExecution execution){ logger.debug("enter the CounterSignService "); Set<String> userIds=new LinkedHashSet<String>(); String nodeId=execution.getActivity().getId(); //1.回退處理通過 BpmRuPath backRuPath=ProcessHandleHelper.getBackPath(); if(backRuPath!=null && "YES".equals(backRuPath.getIsMultiple())){ String uIds=backRuPath.getUserIds(); userIds.addAll(Arrays.asList(uIds.split("[,]"))); execution.setVariable("signUserIds_"+nodeId,uIds); return userIds; } //2.通過變量來判斷是否第一次進入該方法 String signUserIds=(String)execution.getVariable("signUserIds_"+nodeId); if(StringUtils.isNotEmpty(signUserIds)){ String[]uIds=signUserIds.split("[,]"); userIds.addAll(Arrays.asList(uIds)); return userIds; } //3.從界面中的提交變量取用戶 IExecutionCmd nextCmd=ProcessHandleHelper.getProcessCmd(); BpmDestNode bpmDestNode=nextCmd.getNodeUserMap().get(nodeId); if(bpmDestNode!=null && StringUtils.isNotEmpty(bpmDestNode.getUserIds())){ //加至流程變量中,以使后續繼續不需要從線程及數據庫中獲取 execution.setVariable("signUserIds_"+nodeId,bpmDestNode.getUserIds()); execution.setVariable("priority_"+nodeId,bpmDestNode.getPriority()); execution.setVariable("expiretime_"+nodeId, bpmDestNode.getExpireTime()); String[]uIds=bpmDestNode.getUserIds().split("[,]"); userIds.addAll(Arrays.asList(uIds)); return userIds; } //4.從數據庫中讀取節點人員配置獲得參與人員列表 Collection<IdentityInfo> idInfoList=bpmIdentityCalService.calNodeUsersOrGroups(execution.getProcessDefinitionId(), execution.getCurrentActivityId(),execution.getVariables()); for(IdentityInfo identityInfo:idInfoList){ if(IdentityInfo.IDENTIFY_TYPE_USER.equals(identityInfo.getIdentityType())){ userIds.add(identityInfo.getIdentityInfoId()); }else{ List<OsUser> users= osUserManager.getByGroupIdRelTypeId(identityInfo.getIdentityInfoId(), OsRelType.REL_CAT_GROUP_USER_BELONG_ID); for(OsUser u:users){ userIds.add(u.getUserId()); } } } if(userIds.size()>0){ StringBuffer sb=new StringBuffer(); for(String uId:userIds){ sb.append(uId).append(","); } if(sb.length()>0){ sb.deleteCharAt(sb.length()-1); } execution.setVariable("signUserIds_"+nodeId,sb.toString()); }else{ String name=(String)execution.getActivity().getProperty("name"); ProcessMessage msg=ProcessHandleHelper.getProcessMessage(); msg.getErrorMsges().add("會簽節點["+name+"]沒有設置執行人員,請聯系管理員!"); } return userIds; } /** * 會簽是否計算完成 * @param execution * @return */ public boolean isComplete(ActivityExecution execution){ //完成會簽的次數 Integer completeCounter=(Integer)execution.getVariable("nrOfCompletedInstances"); //總循環次數 Integer instanceOfNumbers=(Integer)execution.getVariable("nrOfInstances"); String solId=(String)execution.getVariable("solId"); String nodeId=execution.getActivity().getId(); UserTaskConfig taskConfig=bpmNodeSetManager.getTaskConfig(solId, nodeId); //獲得任務及其多實例的配置,則任務不進行任何投票的設置及處理,即需要所有投票完成后來才跳至下一步。 if(taskConfig.getMultiTaskConfig()==null){ return completeCounter==instanceOfNumbers; } //獲得會簽的數據 List<BpmSignData> bpmSignDatas=bpmSignDataManager.getByInstIdNodeId(execution.getProcessInstanceId(), nodeId); MultiTaskConfig multiTask=taskConfig.getMultiTaskConfig(); //通過票數 int passCount=0; //反對票數 int refuseCount=0; //棄權票數 int abstainCount=0; for(BpmSignData data:bpmSignDatas){ int calCount=1; //棄權不作票數統計 if(TaskOptionType.ABSTAIN.name().equals(data.getVoteStatus())){ abstainCount++; continue; } String userId=data.getUserId(); //檢查是否有特權的處理 if(multiTask.getVotePrivConfigs().size()>0){ //計算用戶的用戶組 List<OsGroup> osGroups=osGroupManager.getBelongGroups(userId); for(TaskVotePrivConfig voteConfig:multiTask.getVotePrivConfigs()){ //是否在特權里 boolean isInPriv=false; //為用戶類型 if(TaskVotePrivConfig.USER.equals(voteConfig.getIdentityType()) && voteConfig.getIdentityIds().contains(userId)){ isInPriv=true; }else{//為用戶組類型 for(OsGroup osGroup:osGroups){ if(voteConfig.getIdentityIds().contains(osGroup.getGroupId())){ isInPriv=true; break; } } } //若找到特權,則計算其值 if(isInPriv){ calCount=voteConfig.getVoteNums(); break; } } } //統計同意票數 if(TaskOptionType.AGREE.name().equals(data.getVoteStatus())){ passCount+=calCount; }else{//統計反對票數 refuseCount+=calCount; } } logger.debug("==============================passCount:"+passCount +" refuseCount:" + refuseCount +" abstainCount:"+abstainCount); //是否可以跳出會簽 boolean isNext=false; String result=null; //按投票通過數進行計算 if(MultiTaskConfig.VOTE_TYPE_PASS.equals(multiTask.getVoteResultType())){ //計算是否通過 //按投票數進行統計 if(MultiTaskConfig.CAL_TYPE_NUMS.equals(multiTask.getCalType())){ //代表通過 if(passCount>=multiTask.getVoteValue()){ isNext=true; result="PASS"; } }else{//按百分比進行計算 int resultPercent=new Double(passCount*100/(passCount+refuseCount+abstainCount)).intValue(); //代表通過 if(resultPercent>=multiTask.getVoteValue()){ isNext=true; result="PASS"; } } }else{//按投票反對數進行計算 //計算是否通過 //按投票數進行統計 if(MultiTaskConfig.CAL_TYPE_NUMS.equals(multiTask.getCalType())){ //代表通過 if(refuseCount>=multiTask.getVoteValue()){ isNext=true; result="REFUSE"; } }else{//按百分比進行計算 int resultPercent=new Double(refuseCount*100/(passCount+refuseCount+abstainCount)).intValue(); //代表通過 if(resultPercent>=multiTask.getVoteValue()){ isNext=true; result="REFUSE"; } } } if((MultiTaskConfig.HANDLE_TYPE_DIRECT.equals(multiTask.getHandleType())&& isNext)//直接處理 || (MultiTaskConfig.HANDLE_TYPE_WAIT_TO.equals(multiTask.getHandleType()) && completeCounter==instanceOfNumbers)){//等待所有的處理完 execution.setVariable("voteResult_"+nodeId, result); //刪除該節點的會簽數據 for(BpmSignData data:bpmSignDatas){ bpmSignDataManager.deleteObject(data); } return true; }