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

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

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

咨询了解

QQ: 1950148199

手机:18620763495

电话:020-29026351

你明白什么是会签?工作流+会签应用相关推荐

  1. Activiti工作流会签与获取下一节点任务信息

    2018-03-29 问题描述:会签节点选择2个审核人只能看到一条代办任务. 解决办法: Sequential设置为false即可.(true 串行   false  并行) activiti 工作流 ...

  2. Activiti 工作流会签开发设计思路

    在流程业务管理中,任务是通常都是由一个人去处理的,而多个人同时处理一个任 务,这种任务我们称之为会签任务.这种业务需求也很常见,如一个请款单,领导审批环节中,就需要多个部门领导签字.在流程业务中,我们 ...

  3. activiti 工作流会签 / 多人审批时若一人通过即可

    equenceFlow  流程定义文件leave-formkey.bpmn20.xml: [html]  <?xml version="1.0" encoding=" ...

  4. Camunda 工作流并行子流程、工作流会签、或签、加签、比例签、跳转节点

    如下图为一个流程图,其需求过程如下: 1.某业务员发起一个登记单 2.领导审批 3.领导选择多个部门进行阅办,每个部门并行进行 4.部门内有两个审批环节(环节一:部门经理或经理助理或签,环节二:部门内 ...

  5. 26 工作流会签开发一票通过,一票否决,多数同意通过

    ​ 案例说明,本案例描述的场景是多人会签,包括一票通过.一票否决和多人同意通过.我们接下来将整个案例在一个实例中做展示和描述,绘制的流程图如下,此用例有四个人工节点,除第一个发起申请是单人任务,其他都 ...

  6. activiti工作流会签功能的实现

    需求:统计会签部门的审核情况然后决定下一步流程的走向: 逻辑实现: 1.设置一个变量signCount 如果同意,signCount+1 // 如果是会签流程List< Task > ta ...

  7. 基于camunda开源流程引擎如何实现会签及会签原理解析

    一.背景 市场上比较有名的开源流程引擎有osworkflow.jbpm.activiti.flowable.camunda.由于jbpm.activiti.flowable这几个流程引擎出现的比较早, ...

  8. Activiti多人会签的实现 Activiti

    https://www.zybuluo.com/ruoli/note/479483 https://www.iteye.com/blog/huan1993-2249764 https://cloud. ...

  9. 会签是什么,何时使用会签,如何设计使用会签

    一.会签 1.1会签定义 会签用于与本次审核内容相关的各有关部门进行协商并核签,简单地讲就是多方共同签署,对签字内容进行确认并签字,签字就意味着要负责.要为确认内容承担责任. 1.2会签与加签 会签是 ...

最新文章

  1. 《松本行弘的程序世界》中文版原作者序
  2. iOSunicode转中文
  3. ACL 2019 | 基于知识增强的语言表示模型,多项NLP任务表现超越BERT
  4. 做移动应用使用地图API时需要注意的问题
  5. .NET 指南:实现 Equals 方法
  6. Git 查看并修改 name 和 email
  7. Java中各种引用(Reference)解析
  8. 50个超实用的生活小点子
  9. python工资一般多少西安-干货|python人工智能工程师工资多少钱
  10. 《MYSQL必知必会》—3~9.使用MySQL、检索数据列、排序检索数据列、过滤数据(WHERE子句、组合WHERE子句、通配符、正则表达式)
  11. 华为手机楷体字体下载_正楷字体下载正楷字体官方下载[字体下载]-华军软件园...
  12. C语言 通讯录项目完整代码
  13. 电脑软件:推荐一款磁盘空间分析工具——WizTree
  14. 手把手教你使用R语言绘制交互效应的森林图
  15. 转载]施一公:如何提高英文的科研写作能力
  16. html判断eq相反,HTML中Smarty中的if语句条件修饰词eq相等ne、neq不相等,gt大于
  17. 录音转文字的app哪个好用?来试试这几个宝藏软件
  18. 【应用安全】什么是身份和访问管理 (IAM)?
  19. 大学计算机实验报告虚拟机,安装虚拟机的实验报告(共10篇).docx
  20. Windows10+deepin双系统安装(选用意义,安装教程)

热门文章

  1. 19.0~19.11 Dates, Calendars, and Events 日历事件的处理
  2. pythonocc 如何把TopoDS_Edge转换成Geom_Curve
  3. 翻转课堂融入计算机课,“翻转课堂”教学模式在职业院校计算机课程中的应用...
  4. CyclicBarrier让多线程齐步走
  5. 解决win10下localhost打不开的问题
  6. 计算机视觉室内定位的原理,基于计算机视觉的室内定位与导航系统
  7. EFM32芯片jlink无法连接,无法调试,解锁流程
  8. 电商erp是什么软件
  9. android is not translated in 报错解决方案
  10. 更换头像功能(前端)