会签任务是工作流里一个比较觉见的任务方式,意思是一个处理任务需要多个人同时处理,然后汇总多个人的意见,决定流程下一步该如何执行。在JBPM4里,并没有会签这个概念,其只有并行任务的处理方式,如下所示:
这种方式会签的任务数量是固定的,并且任务名称也不一样,不能由用户来动态决定有多少人参与会签的任务。当然我们可以让中间那部分只有一个节点,然后根据用户在后台设置了多少人参与或在流程运行过程中,动态知道有多少人参与这个任务,动态创建并发的任务(不过这样实现上有一点困难,需要扩展jbpm的api相对多一些)
而另一种相对比较完善的解决方案是可以用一个任务来代表会签任务,而在流程运行至该任务节点的时候,根据当前有多少人参与这流程,动态生成该任务下的会签子任务,这样可以使得每个会签的参与人都可以在待办事项中看到该任务。这种办法实现上相对容易得多,因为jbpm4本身对子任务的动态创建支持得比较好。
joffice2就是基于这种方式来解决会签的配置及处理。那么,如何汇总会签任务的投票意见呢?我们知道,日常的会签常见的方式有:一票否决,一票通过,全票通过,半数通过等。其实现无非是分为两种,按投票数的绝对票数,另一种是按投票占的百份比数,投票决策又分为两种:通过或拒绝(反对),投票过程中会有通过,反对或弃权的动作。因此,在后台上我们设置某个节点若为会签时,其会签的投票决策方式信息包括如下:
并且我们希望在执行会签任务的时候,会签的参与人员均可以填写意见及选择投票的类型(如同意,反对,弃权),如:
那么投票的情况如何处理,以下为投票的决策处理方式:
为什么要在后面加上流程变量decisionType呢,这里就是为了下一任务可以根据上一任务的会签结果来决定如何往下执行的,我们可以在后续的任务或条件结点中,通过这个变量的值,来决定其如何往下执行。若该变量的值为“pass"表示上一步会签的结果是同意,否则为反对。
如我们有一流程:
那么在分支节点上,我们插入我们需要流程执行的分支决定代码,并且根据这个分支决定代码决定往下一步时执行的路径。于是我们在分支上加上干预的代码如下所示:
根据上面的逻辑判断,我们把完成会签任务的代码展示部分如下:
/** * 完成会签子任务 * @param parentTask 父任务 * @param subTask 子任务 * @param variables 任务中的流程变量 */ private void completeSignSubTask(TaskImpl parentTask,TaskImpl subTask,String signalName,Map variables){ //看目前还有多少子任务 int subTasksSize=parentTask.getSubTasks().size(); //检查会签的配置情况 ProcessInstance pi=((TaskImpl)parentTask).getProcessInstance(); ProcessDefinition pd=repositoryService.createProcessDefinitionQuery().processDefinitionId(pi.getProcessDefinitionId()).uniqueResult(); //取得该任务的后台人员配置 ProUserAssign assignSetting=proUserAssignService.getByDeployIdActivityName(pd.getDeploymentId(), parentTask.getActivityName()); evict(subTask); evict(parentTask); if(assignSetting!=null){ //取得会签配置 TaskSign taskSign=taskSignService.findByAssignId(assignSetting.getAssignId()); if(taskSign!=null){//按照配置执行任务跳转 //是否完成父任务 boolean isFinishSupTask=false; //查看用户投的是哪一种票(同意还是不同意还是弃权) Short isAgree=(Short)variables.get(FlowConstants.SIGN_VOTE_TYPE); if(isAgree==null){//若为空,则认为是投通过票 isAgree=TaskSign.DECIDE_TYPE_PASS; } //1.保存投票信息 taskSignDataService.addVote(parentTask.getId(),isAgree); //加上子任务的流程变量 taskService.setVariables(subTask.getId(),variables); //2.完成子任务 taskService.completeTask(subTask.getId()); //3.检查其投票数是否已满足后台会签配置条件 //3.1根据后台配置的投票类型,取得投票的总数 Long voteCounts=taskSignDataService.getVoteCounts(parentTask.getId(),taskSign.getDecideType()); if(taskSign.getVoteCounts()!=null){//按绝对投票数来进行 if(voteCounts>=taskSign.getVoteCounts()){ isFinishSupTask=true; } }else if(taskSign.getVotePercents()!=null){//按投票百分比来进行 //取到动态有多少子任务 Integer taskSignCounts=(Integer)taskService.getVariable(parentTask.getId(), "taskSignCounts"); if(taskSignCounts==null || taskSignCounts==0){ taskSignCounts=1; } BigDecimal totalSubTasks=new BigDecimal(taskSignCounts); //当前投票后占的百分比 BigDecimal tempPercent=new BigDecimal(voteCounts).divide(totalSubTasks); Integer curPercent=new Integer(tempPercent.multiply(new BigDecimal(100)).intValue()); if(curPercent>=taskSign.getVotePercents()){ isFinishSupTask=true; } } //若投票完成后,把投票结果存在decisionType变量里,方便在后台通过脚本根据投票的结果进行跳转 Map varsMap=new HashMap(); //当前会签子任务完成后,若投票的情况已经满足后台的会签设置条件 //或没有满足会签设置的情况,并且会签所有子任务均已经完成 if(isFinishSupTask || (!isFinishSupTask && subTasksSize==1) ){ String passRefuse=null; if(isFinishSupTask){//所有子任务完成,满足会签条件设置 passRefuse=TaskSign.DECIDE_TYPE_PASS==taskSign.getDecideType()?"pass":"refuse"; }else{//所有子任务完成,不满足会签条件设置 passRefuse=taskSign.getDecideType()==TaskSign.DECIDE_TYPE_PASS?"refuse":"pass"; } logger.debug("会签投票结果:"+ passRefuse); varsMap.put("decisionType",passRefuse); taskService.setVariables(parentTask.getId(),varsMap); //完成父任务 taskService.completeTask(parentTask.getId(),signalName); } }else{//没有设置对应的会签配置,则认为会签是全部完成后才能往下执行 logger.error("Task "+parentTask.getActivityName()+" is not config right sign config in process admin console."); if(((TaskImpl)parentTask).getSubTasks().size()==1){//若只有当前子任务,则表示可以结束目前这个任务 taskService.setVariables(subTask.getId(),variables); //完成子任务 taskService.completeTask(subTask.getId()); //完成父任务 taskService.completeTask(parentTask.getId(),signalName); }else{ taskService.setVariables(subTask.getId(), variables); //完成子任务后,直接返回则可 taskService.completeTask(subTask.getId()); return ; } } }else{ //TODO logger.error("Task "+parentTask.getActivityName()+"is not config the setting in process admin console."); } }
产生会签的任务代码如下:
/** * 创建新的任务 * @param parentTaskId 父任务 ID * @param assignIds 任务执行人IDs */ public void newSubTask(String parentTaskId,Long[]userIds){ TaskServiceImpl taskServiceImpl=(TaskServiceImpl) taskService; Task parentTask=taskServiceImpl.getTask(parentTaskId); //为该父任务加上会签的人员数,方便后面对会签的投票进行统计 Map vars=new HashMap(); vars.put("taskSignCounts", new Integer(userIds.length)); taskServiceImpl.setVariables(parentTaskId, vars); for(int i=0;i<userIds.length;i++){ String userId=userIds[i].toString(); TaskImpl task=(TaskImpl)taskServiceImpl.newTask(parentTaskId); task.setAssignee(userId); task.setName(parentTask.getName() + "-" + (i+1)); task.setActivityName(parentTask.getName() ); task.setDescription(parentTask.getDescription()); //保存 taskServiceImpl.saveTask(task); } }
其最终的实现效果如下flash演示。
http://bbs.jee-soft.cn/swf/signTask.html
或