你明白什麼是會籤?工作流+會籤應用
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>
【說明】:
- 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;
}
return false;
}
}
【說明】
以上的程式碼的人員計算有幾個來源:
- 流程回退時獲得原來參與的人員
- 直接從流程變數中獲取
- 從介面中的提交變數取使用者
- 從資料庫中讀取節點人員配置獲得參與人員列表
會籤的投票處理結果放至流程變數中去,供後續的分支條件來處理,我們把會籤的配置設定管理如下:
我們提供了按票數、百分比的投票處理,同時允許有特權的使用者的投票規則以支援靈活的會簽結果運算。
諮詢瞭解
QQ: 1950148199
手機:18620763495
電話:020-29026351