一、会签的概念
1.1什么是会签
即共同协商或审批。会签,又称会审,也就是流程中某个业务需要经过多人表决,并且根据表决意见的汇总结果,匹配设定的规则,决定流程的走向
1.2会签有多种类型
1.多人只发表意见,并决策;2.部分人决策、部分只发现意见;3.如有决策,决策规则有以下几类:i:一票通过;ii:一票否决;iii:计同意票数;iv:计同意票数占比等。
二、典型会签实现逻辑
2.1参与会签人均需发表意见,全部审批后进入下一节点;
2.2参与会签人可以进行同意不同意的决策 ,全部进行决策后进入下一节点由下一节点审批人人工统计票数决定会签是否通过
三、会签实现方案
jbpm并未直接提供会签机制,通过查阅资料以及前期对子任务及决策节点的理解,我们提出了一种基于子任务和决策的会签实现方案。
会签设置表结构如下:
流程定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<
process
xmlns
=
'http://jbpm.org/4.4/jpdl'
>
<
start
name
=
'start1'
g
=
'195,109,48,48'
>
<
transition
name
=
'进入会签'
g
=
'-25,-15'
to
=
'会签节点'
/>
</
start
>
<
task
g
=
'351.0909118652344,106.30681991577148,90,50'
name
=
'会签节点'
>
<
transition
name
=
'进入决策'
g
=
'-27,-11'
to
=
'decision 1'
/>
</
task
>
<
decision
g
=
'536.0909118652344,106.30681991577148,48,48'
name
=
'decision 1'
>
<
transition
name
=
'决策路径1'
g
=
'-61,-19'
to
=
'end 1'
/>
<
transition
name
=
'决策路径2'
g
=
'-47,45'
to
=
'其他节点'
/>
</
decision
>
<
end
g
=
'687.0909118652344,44.306819915771484,48,48'
name
=
'end 1'
/>
<
task
g
=
'677.0909118652344,163.30681991577148,90,50'
name
=
'其他节点'
>
<
transition
name
=
'正常结束'
g
=
'-26,-21'
to
=
'end 2'
/>
</
task
>
<
end
g
=
'840.0909118652344,163.30681991577148,48,48'
name
=
'end 2'
/>
</
process
>
|
流程图如下:
如果工作流不是每一步关联不同表单,而采用同一表单根据步骤名称确定权限的话,需要特别注意子任务的任务名生成方法。子任务名而不是activityName将用作权限的确定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
if
(!CollectionUtils.isEmpty(userIds) && !userIds.contains(
null
)) {
if
(userIds.size() >
1
){
String deploymentId = processDefineService.queryById(startFlowRunTime.getProcessDefineId()).getDeploymentId();
if
(StringUtils.isNotEmpty(task.getId())){
deploymentId = jbpmOperatorService.getProcessDefinitionByTaskId(task.getId()).getDeploymentId();
}
UserAssignEntity userAssign = userAssignService.queryUserAssignById(deploymentId, task.getActivityName(), UserAssignConstants.USERASSIGN_FLAG_ASSIGNEE,
""
);
AssignsettingEntity assignsetting =
new
AssignsettingEntity();
assignsetting.setDeployId(deploymentId);
assignsetting.setActivityName(task.getActivityName());
List<AssignsettingEntity> assignsettingList = assignsettingService.queryAssignsettingList(assignsetting);
//处理会签
if
(userAssign !=
null
&&
"1"
.equals(userAssign.getIsSigned()) ){
if
(CollectionUtils.isEmpty(assignsettingList)){
jbpmOperatorService.createSubTask(task.getId(), userIds.toArray(
new
String[userIds.size()]));
}
else
{
//如需要通过子任务名确定表单权限
try
{
jbpmOperatorService.createAssignSubTask(task.getId(), assignsettingList);
}
catch
(Exception e) {
throw
new
ProcessActivityException(e.getMessage());
}
}
}
else
{
//处理抢办
for
(String userId : userIds){
jbpmOperatorService.addTaskParticipatingUser(task.getId(),userId);
}
}
}
|
上面提到如需要通过会签子任务名确定表单权限,需特别注意createAssignSubTask方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/**
*
* create sub task as assign task
* @author chao.gao
* @date 2015-2-10 上午10:09:58
* @see com.gaochao.oa.module.bpm.workflow.api.server.service.IJbpmOperatorService#createAssignSubTask(java.lang.String, java.util.List)
* @param id
* @param assignsettingList
* @throws Exception
*/
@Override
public
void
createAssignSubTask(String parentTaskId, List<AssignsettingEntity> assignsettingList)
throws
Exception{
TaskServiceImpl taskServiceImpl = (TaskServiceImpl)processEngine.getTaskService();
Task parentTask = taskServiceImpl.getTask(parentTaskId);
Map<String,Object> vars =
new
HashMap<String,Object>();
taskServiceImpl.setVariables(parentTaskId, vars);
for
(AssignsettingEntity assignsetting : assignsettingList){
TaskImpl task = (TaskImpl)taskServiceImpl.newTask(parentTaskId);
task.setAssignee(assignsetting.getUserId());
task.setName(parentTask.getName() +
"-"
+ assignsetting.getTaskName());
task.setActivityName(parentTask.getName());
task.setProcessInstance(getTaskById(parentTaskId).getExecution());
task.setDescription(parentTask.getDescription());
taskServiceImpl.saveTask(task);
}
}
|
在jbpm4_task表中生成的子任务列表如下,其中子任务通过SUPERTASK_字段与父任务关联起来:
子任务的办理:在办理子任务时首先获得其父任务的子任务列表,判断列表长度,如长度大于1,则只需要关闭本子任务;如子任务列表长度=1,说明目前仅有本子任务未办理,则将本任务及父任务同时关闭。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* 处理父子任务,会签任务,完成任务
* @author chao.gao
* @date 2014-4-3 下午2:55:57
* @param parentTask
* @param subTask
* @param signalName
* @param variables
* @param opinion
*/
private
void
acch(TaskImpl parentTask, TaskImpl subTask, String signalName, Map<String,Object> variables, String opinion) {
int
subTasksSize = parentTask.getSubTasks().size();
jbpmOperatorService.evict(subTask);
jbpmOperatorService.evict(parentTask);
if
(subTasksSize >
1
){
//如当前父任务的子任务列表大于1,直接完成任务审批
jbpmOperatorService.completeTask(subTask.getId());
}
else
{
jbpmOperatorService.completeTask(subTask.getId());
//先完成子任务
jbpmOperatorService.completeTask(parentTask.getId());
//关闭父任务
//更新父任务状态
ProcessTaskEntity pT = processTaskService.queryByTaskId(parentTask.getId());
//更新父任务状态
if
(pT !=
null
){
pT.setStatus(
"1"
);
}
processTaskService.update(pT);
}
}
|
会签是否通过的决策:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
/**
* 流程决策
* @author chao.gao
* @date 2014-2-13 上午10:01:36
* @see org.jbpm.api.jpdl.DecisionHandler#decide(org.jbpm.api.model.OpenExecution)
* @param arg0
* @return
*/
@Override
public
String decide(OpenExecution openExecution) {
ProcessEngine processEngine = (ProcessEngine) SpringContextUtil.getApplicationContext().getBean(
"processEngine"
);
IDecisionRuleService decisionRuleService= (IDecisionRuleService)SpringContextUtil.getApplicationContext().getBean(
"decisionRuleService"
);
String processDefinitionId = openExecution.getProcessDefinitionId();
ProcessDefinition processDefinition = processEngine.getRepositoryService()
.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.uniqueResult();
String deploymentId = processDefinition.getDeploymentId();
Activity curActivity = openExecution.getActivity();
DecisionRuleEntity decisionRule = decisionRuleService.queryByDeployIdAndActivityName(deploymentId, curActivity.getName());
if
(decisionRule!=
null
){
Interpreter it =
new
Interpreter();
try
{
Map<String, ?> vars = openExecution.getVariables();
Iterator<?> iterator = vars.entrySet().iterator();
while
(iterator.hasNext()) {
Map.Entry entry = (Entry) iterator.next();
String key = (String)entry.getKey();
Object val = entry.getValue();
it.set(key.replace(
"."
,
"_"
), val);
}
it.set(
"execution"
, openExecution);
it.eval(decisionRule.getRuleExpression().replace(
"'"
,
""
));
String tran = (String)it.get(
"tranTo"
);
return
tran;
}
catch
(Exception e) {
//e.printStackTrace();
throw
new
DecisionRuleException(
"条件判断表达式错误"
);
}
}
String defaultTran =
""
;
List outs = curActivity.getOutgoingTransitions();
if
(outs.size() >
0
) {
defaultTran = ((Transition)outs.get(
0
)).getName();
}
return
defaultTran;
}
|
通过以上逻辑,我们可以得到类似会签的效果,另外通过分配子任务方法我们也可以方便的进行加签(生成子任务)、减签(销毁子任务)。
四、其他会签方案
4.1 fork-join
流程图如下:
流程定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<
process
xmlns
=
'http://jbpm.org/4.4/jpdl'
>
<
start
name
=
'start1'
g
=
'250,100,48,48'
>
<
transition
name
=
'进行会签'
g
=
'-28,-8'
to
=
'fork 1'
/>
</
start
>
<
fork
g
=
'384.0909118652344,103.30681991577148,48,48'
name
=
'fork 1'
>
<
transition
name
=
'会签分支1'
g
=
'-68,-14'
to
=
'task 1'
/>
<
transition
name
=
'会签分支2'
g
=
'-67,31'
to
=
'task 2'
/>
</
fork
>
<
task
g
=
'486.0909118652344,35.306819915771484,90,50'
name
=
'task 1'
>
<
transition
name
=
'会签汇聚1'
to
=
'join 1'
/>
</
task
>
<
task
g
=
'494.0909118652344,169.30681991577148,90,50'
name
=
'task 2'
>
<
transition
name
=
'会签汇聚1'
g
=
'5,34'
to
=
'join 1'
/>
</
task
>
<
join
g
=
'635.0909118652344,96.30681991577148,48,48'
name
=
'join 1'
>
<
transition
name
=
'会签结束'
g
=
'-27,-16'
to
=
'end 1'
/>
</
join
>
<
end
g
=
'771.0909118652344,96.30681991577148,48,48'
name
=
'end 1'
/>
</
process
>
|
fork-join的join节点具有multiplicity属性,通过该属性标记是全部task完成\仅有一个task完成进入join,或者是几个task完成才进入join,这也是fork-join可以用来实现会签的原因。
4.2 for-each(动态分支)
流程图:
流程定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<
process
xmlns
=
'http://jbpm.org/4.4/jpdl'
>
<
start
name
=
'start1'
g
=
'250,100,48,48'
>
<
transition
name
=
'进入会签'
g
=
'-26,-18'
to
=
'foreach 1'
/>
</
start
>
<
foreach
g
=
'357.0909118652344,99.30681991577148,48,48'
name
=
'foreach 1'
>
<
transition
name
=
'进入会签任务'
g
=
'-39,-17'
to
=
'会签任务'
/>
</
foreach
>
<
task
g
=
'512.0909118652344,98.30681991577148,90,50'
name
=
'会签任务'
>
<
transition
name
=
'会签决策'
g
=
'-23,-13'
to
=
'join 1'
/>
</
task
>
<
join
g
=
'680.0909118652344,97.30681991577148,48,48'
name
=
'join 1'
>
<
transition
name
=
'结束'
g
=
'-18,-17'
to
=
'end 1'
/>
</
join
>
<
end
g
=
'798.0909118652344,98.30681991577148,48,48'
name
=
'end 1'
/>
</
process
>
|
foreach(动态分支)属于jbpm的高级应用,其能够实现会签的原理与fork-join相近,fork-join是fork之后生成多个分支,不同分支各是不同节点,最多分支量是提前确定的;而foreach则是通过设置其in\var等属性动态生成分支,其最多分支量可以进行设置。分支生成以后的会签工作与fork-join类似。
4.3 custom节点
custom也是jbpm的高级应用,这里有一个详细的例子介绍通过custom实现会签。通过custom即定制化,我们可以任意定制custom的属性、逻辑、规则,所以通过custom是较自由、但也较复杂的会签实现方案。通过会签的custom实现,我们其实也可以发现,custom就是jbpm提供给我们的扩展接口,不仅会签,其他的工作流动作也可以通过定制实现。
我们看一下其定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<
custom
class
=
"sofocus.bpm.countersign.CountersignActivity"
g
=
"259,218,92,52"
name
=
"领导会签"
>
<
property
name
=
"description"
>
<
string
value
=
"#{equipment}设备订购会签"
/>
</
property
>
<
property
name
=
"form"
>
<
string
value
=
"/buyEquipment.do?action=countersign&taskId=:{TASK_ID}"
/>
</
property
>
<
property
name
=
"strategy"
>
<
object
class
=
"sofocus.bpm.countersign.AllAgreeStrategy"
/>
</
property
>
<
property
name
=
"passTransiton"
>
<
string
value
=
""
/>
</
property
>
<
property
name
=
"nopassTransiton"
>
<
string
value
=
""
/>
</
property
>
<
on
event
=
"start"
>
<
event-listener
class
=
"test.com.yy.ah.SetBuyEquipmetnCounterSignUsers"
/>
</
on
>
<
transition
g
=
"-41,-8"
name
=
"to 会签结果判断"
to
=
"会签结果判断"
/>
</
custom
>
|
在这个定义中主要有两个类,一个是CountersignActivity,一个是AllAgreeStragy,另外还有一个分配子任务的类。其实现本身不复杂,也是利用子任务,但却实现了最大的发挥自由度。感兴趣的同学可以通过链接进一步学习其思路和实现方案。
4.4 assignHandler
通过assigHandler实现会签的思路也与我们的思路相似,即子任务,也不做赘述。定义如下:
1
2
3
4
5
|
<
task
g
=
"182,379,92,52"
name
=
"审批"
>
<
assignment-handler
class
=
"org.jbpm.examples.attendance.commercialtrip.CommercialTripAssignment"
></
assignment-handler
>
<
transition
g
=
"147,329:-47,-17"
name
=
"审批不通过"
to
=
"申请"
/>
<
transition
name
=
"审批通过"
to
=
"end1"
g
=
"-47,-17"
/>
</
task
>
|
本文转自 gaochaojs 51CTO博客,原文链接:http://blog.51cto.com/jncumter/1613419,如需转载请自行联系原作者