JSAAS的Activiti会签开发扩展处理
1.什么是会签?
在流程业务管理中,任务是通常都是由一个人去处理的,而多个人同时处理一个任务,这种任务我们称之为会签任务。这种业务需求很常见,如一个请款单,领导审批环节中,就需要多个部门领导签字。在流程业务中,我们可以把每个领导签字的环节都定义为任务,并且这个会签的人员是不固定的,若固定的我们可以通过Activiti的并行任务或串行任务来处理。会签的引入说明,无非就是为了流程流转至某一环节点,其审批的人员是动态的,并且需要根据会签审批的结果实现流程的不同流转。
2.中国特色的会签需求是什么?
会签需求主要有以下两方面:
- 会签的参与人员
- 会签审批的顺序
- 会签审批的结果
- 动态加签
以下我们就是围绕以上的需求进行扩展实现的
3.Activiti对于会签的实现
BPMN2的标准中并没有对以上这种情景提供完善的支持,因此要在Activiti中实现会签审批,我们需要结合Activiti提供的流程任务的多实例特性,进行一些必要的扩展,以支持我们的中国特色的会签需求。
会签任务也是一种人工任务,其在activiti的定义中,也是使用UserTask来定义,但在属性上我们需要对这个定义的类型进行特殊的配置,即为多任务实例类型(并行或串行)任何一种。另外需要定义会签的参与人员,再定义会签的完成条件(若不定义,表示其是所有参与人均完成后,流程才往下跳转)。
3.1.多实例的人工任务配置
通过在UserTask节点的属性上配置,如下所示:
其生成的BPMN的配置文件如下所示:
<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;
@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;}
}
【说明】
以上的代码的人员计算有几个来源:
- 流程回退时获得原来参与的人员
- 直接从流程变量中获取
- 从界面中的提交变量取用户
- 从数据库中读取节点人员配置获得参与人员列表
会签的投票处理结果放至流程变量中去,供后续的分支条件来处理,我们把会签的配置设置管理如下:
我们提供了按票数、百分比的投票处理,同时允许有特权的用户的投票规则以支持灵活的会签结果运算。
实现了以上配置后,流程的会签就比较简单,具体的效果如下视频演示:
http://www.redxun.cn/vedio/taskSign.htm
演示效果如:
http://www.redxun.cn:8020/saweb/login.jsp
JSAAS的Activiti会签开发扩展处理相关推荐
- Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台项目
项目介绍 Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台 基于 Layui 的后台管理系统模板,扩展 Layui 原生 U ...
- Spring Boot + Security + Thymeleaf + Activiti 快速开发平台项目
点击关注公众号,实用技术文章及时了解 项目介绍 Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台 基于 Layui 的后台管 ...
- Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台
前言 项目介绍 Spring Boot + Security + MyBatis + Thymeleaf + Activiti 快速开发平台 基于Layui的后台管理系统模板,扩展Layui原生UI样 ...
- share extension 不显示_这几项超好用的云开发扩展能力,别说你还不知道!
云开发CloudBase是云开发团队为开发者提供的一站式云服务,旨在降低开发者使用云服务的门槛,助力开发者快速开发应用.在具体的开发过程中,云开发提供了许多实用的扩展能力,包含图像标签.图像安全审核. ...
- base64 不一致_这几项超好用的云开发扩展能力,别说你还不知道!
云开发CloudBase是云开发团队为开发者提供的一站式云服务,旨在降低开发者使用云服务的门槛,助力开发者快速开发应用.在具体的开发过程中,云开发提供了许多实用的扩展能力,包含图像标签.图像安全审核. ...
- 如何为SAP WebIDE开发扩展(Extension),并部署到SAP云平台上
本文通过一个最简单的例子,介绍如何给SAP WebIDE开发扩展(WebIDE Extension) 新建一个SAP WebIDE扩展,基于的模板如下,这个项目也是一个MTA项目: WebIDE ex ...
- activiti idea 请假流程_IDEA创建Activiti工作流开发
IDEA创建Activiti工作流开发 一.安装Activiti插件 1.首先打开FIle的setting功能,搜索Plugins: 2.输入actiBPM,然后点击搜索: 3.点击安装.应用: 安装 ...
- 详述Visual Studio 代码远程开发扩展中的远程命令执行漏洞
聚焦源代码安全,网罗国内外最新资讯! 编译:代码卫士 Visual Studio 代码远程开发扩展(Code Remote Development Extension) 1.50 未能在将其用作 s ...
- activiti 会签流程图画法
activiti 会签流程图画法 会签是指:一个流程节点存在两个或两个以上人员共同审核,并行和串行审核
- Revit二次开发——扩展存储
Revit二次开发--扩展存储 在revitAPI中,提供了Extensible Storage framework,可以使开发者将需要存储的数据存到Revit的rvt文件中, 扩展的数据始终跟 ...
最新文章
- bootstrap 垂直居中 布局_给你一份详细的CSS布局指南,请查收
- 《编译原理》课程标准
- 程序员编程艺术:第二章、字符串是否包含问题
- 图像锐化处理算法matlab,图像锐化matlab算法
- leetcode343. 整数拆分(动态规划)
- vscode 中 markdown 插件和使用
- 贾跃亭成了,FF 91预量产车下线完成
- sqlite3 cmd命令输出乱码
- java框架是什么_Spring 是什么框架?
- java财务对账系统设计_对账系统设计
- 多读少写的场景 如何提高性能
- c语言输出字母空心菱形,C语言实现打印菱形和空心菱形
- 拼写检查工具是android,Android基础知识之拼写检查框架
- java 健身会所_基于jsp的健身俱乐部会员-JavaEE实现健身俱乐部会员 - java项目源码...
- 我是如何获取到全校同学的证件照?
- vscode 设置setting文件
- 【跟彤砸学编程】—— 第一课
- DeepMind新AI可生成逼真视频
- STL模型转点云数据
- 构造方法、String类、集合