1.什么是会签?

在流程业务管理中,任务是通常都是由一个人去处理的,而多个人同时处理一个任务,这种任务我们称之为会签任务。这种业务需求很常见,如一个请款单,领导审批环节中,就需要多个部门领导签字。在流程业务中,我们可以把每个领导签字的环节都定义为任务,并且这个会签的人员是不固定的,若固定的我们可以通过Activiti的并行任务或串行任务来处理。会签的引入说明,无非就是为了流程流转至某一环节点,其审批的人员是动态的,并且需要根据会签审批的结果实现流程的不同流转。

2.中国特色的会签需求是什么?

会签需求主要有以下两方面:

  1. 会签的参与人员
  2. 会签审批的顺序
  3. 会签审批的结果
  4. 动态加签

以下我们就是围绕以上的需求进行扩展实现的

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>

【说明】:

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

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

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

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

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

演示效果如:

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

JSAAS的Activiti会签开发扩展处理相关推荐

  1. Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台项目

    项目介绍 Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台 基于 Layui 的后台管理系统模板,扩展 Layui 原生 U ...

  2. Spring Boot + Security + Thymeleaf + Activiti 快速开发平台项目

    点击关注公众号,实用技术文章及时了解 项目介绍 Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台 基于 Layui 的后台管 ...

  3. Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台

    前言 项目介绍 Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台 基于Layui的后台管理系统模板,扩展Layui原生UI样 ...

  4. share extension 不显示_这几项超好用的云开发扩展能力,别说你还不知道!

    云开发CloudBase是云开发团队为开发者提供的一站式云服务,旨在降低开发者使用云服务的门槛,助力开发者快速开发应用.在具体的开发过程中,云开发提供了许多实用的扩展能力,包含图像标签.图像安全审核. ...

  5. base64 不一致_这几项超好用的云开发扩展能力,别说你还不知道!

    云开发CloudBase是云开发团队为开发者提供的一站式云服务,旨在降低开发者使用云服务的门槛,助力开发者快速开发应用.在具体的开发过程中,云开发提供了许多实用的扩展能力,包含图像标签.图像安全审核. ...

  6. 如何为SAP WebIDE开发扩展(Extension),并部署到SAP云平台上

    本文通过一个最简单的例子,介绍如何给SAP WebIDE开发扩展(WebIDE Extension) 新建一个SAP WebIDE扩展,基于的模板如下,这个项目也是一个MTA项目: WebIDE ex ...

  7. activiti idea 请假流程_IDEA创建Activiti工作流开发

    IDEA创建Activiti工作流开发 一.安装Activiti插件 1.首先打开FIle的setting功能,搜索Plugins: 2.输入actiBPM,然后点击搜索: 3.点击安装.应用: 安装 ...

  8. 详述Visual Studio 代码远程开发扩展中的远程命令执行漏洞

     聚焦源代码安全,网罗国内外最新资讯! 编译:代码卫士 Visual Studio 代码远程开发扩展(Code Remote Development Extension) 1.50 未能在将其用作 s ...

  9. activiti 会签流程图画法

    activiti 会签流程图画法 会签是指:一个流程节点存在两个或两个以上人员共同审核,并行和串行审核

  10. Revit二次开发——扩展存储

    Revit二次开发--扩展存储 ​  在revitAPI中,提供了Extensible Storage framework,可以使开发者将需要存储的数据存到Revit的rvt文件中, 扩展的数据始终跟 ...

最新文章

  1. bootstrap 垂直居中 布局_给你一份详细的CSS布局指南,请查收
  2. 《编译原理》课程标准
  3. 程序员编程艺术:第二章、字符串是否包含问题
  4. 图像锐化处理算法matlab,图像锐化matlab算法
  5. leetcode343. 整数拆分(动态规划)
  6. vscode 中 markdown 插件和使用
  7. 贾跃亭成了,FF 91预量产车下线完成
  8. sqlite3 cmd命令输出乱码
  9. java框架是什么_Spring 是什么框架?
  10. java财务对账系统设计_对账系统设计
  11. 多读少写的场景 如何提高性能
  12. c语言输出字母空心菱形,C语言实现打印菱形和空心菱形
  13. 拼写检查工具是android,Android基础知识之拼写检查框架
  14. java 健身会所_基于jsp的健身俱乐部会员-JavaEE实现健身俱乐部会员 - java项目源码...
  15. 我是如何获取到全校同学的证件照?
  16. vscode 设置setting文件
  17. 【跟彤砸学编程】—— 第一课
  18. DeepMind新AI可生成逼真视频
  19. STL模型转点云数据
  20. 构造方法、String类、集合

热门文章

  1. input maxlength 属性不起作用
  2. 解决fullpage滑动,控制台的报错提示
  3. 一文搞懂MySQL索引(清晰明了)
  4. unity中计算三角形的外接圆
  5. jetbrain试用
  6. 防干扰继电器控制电路
  7. Git使用教程之初级入门命令行(二)
  8. matlab 传函将s换为jw,2010MATLAB及控制系统仿真_总复习.ppt
  9. ubuntu安装anaconda3+cuda11.2+cuDNN+pytorch1.7
  10. 阿里云 Linux云服务器登陆方式(Windows远程登录工具XShell,基于ssh建立会话)