JSAAS's Activiti countersign development extension processing

1. What is a countersignature?

 

In process business management, tasks are usually handled by one person, and multiple people handle a task at the same time. We call this task a countersignature task. This kind of business requirement is very common. For example, in the approval process of an invoice, the leaders of multiple departments need to sign. In the process business, we can define the link of each leader's signature as a task, and the person who countersigns is not fixed. If it is fixed, we can handle it through Activiti's parallel tasks or serial tasks. The introduction of countersignature is nothing more than to transfer the process to a certain link point, and the approval personnel are dynamic, and different flow of the process needs to be realized according to the results of the countersignature approval.

2. What is the demand for countersignature with Chinese characteristics?

 

      The requirements for countersignature mainly include the following two aspects:

  1. countersigned participants
  2. The order of countersign approval
  3. The result of the countersign approval
  4. Dynamic signature

The following is what we expand and implement around the above requirements

3. Activiti's implementation of countersignature

 

The BPMN2 standard does not provide complete support for the above scenario, so to achieve countersign approval in Activiti, we need to combine the multi-instance feature of process tasks provided by Activiti to make some necessary extensions to support our Chinese characteristics countersignment requirements.
The countersignature task is also a kind of manual task, which is also defined by UserTask in the definition of activiti, but we need to make special configuration for the type of this definition in terms of attributes, that is, any multitask instance type (parallel or serial) A sort of. In addition, it is necessary to define the participants of the countersignature, and then define the completion conditions of the countersignature (if not defined, it means that the process will jump down after all participants have completed it).

3.1. Multi-instance human task configuration

 

By configuring on the properties of the UserTask node as follows:

1.png

The generated BPMN configuration file is as follows:

  <userTask id= "sid-78A17A9B-1185-48AA-A1CA-611421251D52" name= "经理会签" >
      <multiInstanceLoopCharacteristics isSequential= "false" activiti:collection= "${counterSignService.getUsers(execution)}" >
        <completionCondition> ${counterSignService.isComplete(execution)} </completionCondition>
      </multiInstanceLoopCharacteristics>
    </userTask>

【illustrate】:

  1. isSequential="false" means that this is a non-serial countersign, that is, a parallel countersign. If three people participate in the countersign, three people receive the to-do at the same time, and the task instance is generated at the same time.
  2. activiti:collection represents a collection of countersigned participants, which users can obtain by defining their own service classes
  3. completionCondition indicates that it is the completion condition for the task to jump down. If true is returned, it means that the condition is satisfied, and the process will jump to the next approval link.

We revolve around these points to realize the Chinese-style process of countersigning

3.2 Personnel set calculation processing for countersigning tasks

 

我们在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)){//Wait for all processing
   execution.setVariable("voteResult_"+nodeId, result);
   //Delete the countersigned data of this node
   for(BpmSignData data:bpmSignDatas){
    bpmSignDataManager.deleteObject(data);
   }
   return true;
  }
  
  return false;
  
}
}
 

[Explanation]
There are several sources for the personnel calculation of the above code:

  1. Get the people who originally participated when the process is rolled back
  2. Get it directly from the process variable
  3. Get the user from the submit variable in the interface
  4. Read the node staffing configuration from the database to get the list of participants

The voting processing result of the countersignature is put into the process variable for processing by the subsequent branch conditions. We manage the configuration settings of the countersignature as follows:

task-config.png

We provide voting processing by number of votes and percentages, while allowing voting rules for privileged users to support flexible countersignature calculation.

After the above configuration is implemented, the countersigning of the process is relatively simple. The specific effect is shown in the following video:

 

http://www.redxun.cn/vedio/taskSign.htm

Demonstration effects such as:

http://www.redxun.cn:8020/saweb/login.jsp 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326845546&siteId=291194637