热线电话

020-89773035

工作时间:

9:30-18:30

微信公众账号

JSAAS的Activiti会签开发扩展处理

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. 从数据库中读取节点人员配置获得参与人员列表

会签的投票处理结果放至流程变量中去,供后续的分支条件来处理,我们把会签的配置设置管理如下:

task-config.png

我们提供了按票数、百分比的投票处理,同时允许有特权的用户的投票规则以支持灵活的会签结果运算。

实现了以上配置后,流程的会签就比较简单,具体的效果如下视频演示:

http://www.redxun.cn/?p=456

 

 

redxun

此项有1个回复

  1. ergfirnolikz says:

    Attractive component to content. I just stumbled upon your website and in accession capital to claim that I acquire actually enjoyed account your weblog posts. Anyway I will be subscribing on your feeds or even I success you get entry to persistently fast.

回复

你也可以使用 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

2222222222222222222222222222