1. 程式人生 > >你明白什麼是會籤?工作流+會籤應用

你明白什麼是會籤?工作流+會籤應用

1.什麼是會籤?

在流程業務管理中,任務是通常都是由一個人去處理的,而多個人同時處理一個任務,這種任務我們稱之為會籤任務。這種業務需求很常見,如一個請款單,領導審批環節中,就需要多個部門領導簽字。在流程業務中,我們可以把每個領導簽字的環節都定義為任務,並且這個會籤的人員是不固定的,若固定的我們可以通過Activiti的並行任務或序列任務來處理。會籤的引入說明,無非就是為了流程流轉至某一環節點,其審批的人員是動態的,並且需要根據會籤審批的結果實現流程的不同流轉。

2.中國特色的會籤需求是什麼?

會籤需求主要有以下兩方面:

  1. 會籤的參與人員
  2. 會籤審批的順序
  3. 會籤審批的結果
  4. 動態加簽

以下我們就是圍繞以上的需求進行擴充套件實現的

3.Activiti對於會籤的實現

BPMN2的標準中並沒有對以上這種情景提供完善的支援,因此要在Activiti中實現會籤審批,我們需要結合Activiti提供的流程任務的多例項特性,進行一些必要的擴充套件,以支援我們的中國特色的會籤需求。
會籤任務也是一種人工任務,其在activiti的定義中,也是使用UserTask來定義,但在屬性上我們需要對這個定義的型別進行特殊的配置,即為多工例項型別(並行或序列)任何一種。另外需要定義會籤的參與人員,再定義會籤的完成條件(若不定義,表示其是所有參與人均完成後,流程才往下跳轉)。

3.1.多例項的人工任務配置

通過在UserTask節點的屬性上配置,如下所示:

1.png

其生成的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>

【說明】:

  1. isSequential=”false” 表示這是非序列會籤,即為並行會籤,如三個人蔘與會籤,是三個人同時收到待辦,任務例項是同時產生的。
  2. activiti:collection 表示是會籤的參與人員集合,使用者可以通過定義自身的服務類來獲取
  3. 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;
  
}
}

【說明】
以上的程式碼的人員計算有幾個來源:

  1. 流程回退時獲得原來參與的人員
  2. 直接從流程變數中獲取
  3. 從介面中的提交變數取使用者
  4. 從資料庫中讀取節點人員配置獲得參與人員列表

會籤的投票處理結果放至流程變數中去,供後續的分支條件來處理,我們把會籤的配置設定管理如下:

我們提供了按票數、百分比的投票處理,同時允許有特權的使用者的投票規則以支援靈活的會簽結果運算。

諮詢瞭解

QQ: 1950148199

手機:18620763495

電話:020-29026351