在我们的工作流系统里已经有工作移交的功能,工作移交常用于用户离职、异动、岗位变化、长期不在岗位等情况,将已经分配给该用户的工作移交给其他用户。移交的本质是更改正在审批中的任务的审批人。而委托和转交功能中的转交功能与移交相似,移交功能由管理员发起,而转交则是由当前审批人主动发起的改变审批的行为。在日常运维中,经常出现类似这样的场景,某一工作被错误的移交到我手里,或者是我现在已经不办理类似流程了,我想主动的将该流程转给他人办理,这就是转交。另外,用户请假、外出、出差或针对某些低级别的流程(按照流程规定需要高层审批,比如总监级的请假单、加班单会由越级领导高级副总裁审批,而高级副对类似的单子不愿意去审批)希望委托他人审批,这即是一种事前的委托,而转交或转办也被称为审批中的委托。
在中国特色的工作流系统中,委托与转交功这样较常见。以下是我对委托与转交功能的基本定义:
1.首先在t_bpm_process_task中需增加委托人和转交人,同理将审批人改变为被委托人或被转交人;
2.该功能由该任务的审批人主动发起;
3.委托人数据(被委托人、委托时间)来源暂定为请假表的代理人及请假的开始、结束时间;
4.所有单据均可委托;
5.转交是当前审批人将该任务的审批权移交他人;
6.被转交人和被委托人不允许再将任务转交或委托他人;
7.我的工作中新增两个标签,我转交和我委托,转交人或委托人可以通过这两个模块查看自己转交或委托的任务的办理情况
以上功能为必要功能;
另外,还可增加更丰富的委托设置数据,比如
1.被委托人可以设置自己不允许被委托;
2.委托任务可以设置为是否允许转委托;
3.允许被委托或被转交人在委托/转交人恢复工作后仍未办理结束的任务退回委托/转交人;
在实际编码的过程中,对一些未清晰的需求进一步明确:
1.关于抢办的转交功能,抢办点击转交相当于jbpm4中候选功能的pick,即将抢办的任务先只归到自己名下,然后再进行转交,所以转交后暂定为只有被转交人有审批权限;
2.非OA任务不准转交;
3.改变审批人、加签等情况不自动转给被委托人;
4.抢办的暂无查看委托任务办理详情;
5.菜单表中需增加我转交我委托的菜单
我们先看看实现转交和委托所需要的表结构,实际是任务表,增加了两个字段用于表示委托人和转交人,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<
resultMap
type
=
"com.fx.oa.module.bpm.workflow.api.shared.domain.ProcessTaskEntity"
id
=
"TaskResultMap"
>
<
id
property
=
"id"
column
=
"id"
/>
<
result
property
=
"processExecutionId"
column
=
"processExecutionId"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"processDefineId"
column
=
"processDefineId"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"processDefineName"
column
=
"processDefineName"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"activityName"
column
=
"activityName"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"finishTime"
column
=
"finishTime"
jdbcType
=
"TIMESTAMP"
/>
<
result
property
=
"timeConsuming"
column
=
"timeConsuming"
jdbcType
=
"BIGINT"
/>
<
result
property
=
"fromTaskId"
column
=
"fromTaskId"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"fromTaskName"
column
=
"fromTaskName"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"taskId"
column
=
"taskId"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"transTo"
column
=
"transTo"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"status"
column
=
"status"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"preProcessTaskId"
column
=
"preProcessTaskId"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"description"
column
=
"description"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"creatorName"
column
=
"creatorName"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"createTime"
column
=
"createTime"
jdbcType
=
"TIMESTAMP"
/>
<
result
property
=
"modifyTime"
column
=
"modifyTime"
jdbcType
=
"TIMESTAMP"
/>
<
result
property
=
"createUserCode"
column
=
"createUserCode"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"modifyUserCode"
column
=
"modifyUserCode"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"entrustedUserCode"
column
=
"entrustedUserCode"
jdbcType
=
"VARCHAR"
/>
<
result
property
=
"substituteUserCode"
column
=
"substiuteUserCode"
jdbcType
=
"VARCHAR"
/>
</
resultMap
>
|
我们先来看转交功能。对于前端页面我们直接使用单选的人员选择器,选中某用户后,点击确定后进入转交的处理,代码如下:
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
|
/**
* raid task to other people
* @author chao.gao
* @date 2015-3-11 上午11:39:04
* @see com.gaochao.oa.module.bpm.workflow.api.server.service.IProcessActivityService#raidTo(com.gaochao.oa.module.bpm.workflow.api.shared.dto.FlowRunTime, com.gaochao.oa.module.bpm.workflow.api.shared.vo.TaskVo)
* @param flowRunTime
* @param taskVo
*/
@Override
public
boolean
raidTo(FlowRunTime flowRunTime, TaskVo task) {
if
(StringUtils.isNotEmpty(flowRunTime.getCurrentUser())){
ProcessExecutionEntity processExecutionEntity = processExecutionService.queryProcessExecutionByPkValue(flowRunTime.getPkValue());
List<Task> list = jbpmOperatorService.getTasksByProcessInstanceId(processExecutionEntity.getProcessInstanceId());
processExecutionService.changePeople(flowRunTime);
MailSettingEntity mailSetting = mailSettingService.queryMailSetting();
Map<String, List<MailblackwhitelistEntity>> mailBlackWhiteListMap = mailblackwhitelistService.queryMailBlackWhiteList();
//设置流程处理ID
flowRunTime.setProcessExecutionId(processExecutionEntity.getId());
for
(
int
i=
0
;i<list.size();i++){
Task task1 = list.get(i);
if
(task1.getId().equals(task.getTaskId())){
task.setTaskId(task1.getId());
processTaskService.raidTo(task);
//设置任务Id
flowRunTime.setNextTaskId(task1.getId());
//经办人
final
String assign = flowRunTime.getCurrentUser();
jbpmOperatorService.assignTask(task1.getId(), assign);
//发送邮件
EmailUtil.sendMail2Proxy(assign, flowRunTime, mailSetting, mailBlackWhiteListMap);
}
}
}
else
{
throw
new
ProcessActivityException(
"没有选择人员"
);
}
return
true
;
}
|
在以上的代码中,共有三处引用,分别是jbpm分配任务、更新扩展任务列表和向转交的审批人发送邮件。我们着重看一下分配任务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* 分配任务
* @author chao.gao
* @date 2013-10-9 下午7:18:31
* @param taskId 任务Id
* @param userId 用户
*/
public
void
assignTask(String taskId,String userId){
UserEntity userEntity = userService.queryUserByUserCode(userId);
if
(userEntity ==
null
){
throw
new
ProcessActivityException(
"流程引擎运行错误:审批用户异常!"
);
}
processEngine.getTaskService().assignTask(taskId,userId);
}
|
在更新扩展任务列表中,我们会将选中的用户用作当前任务的审批人,同时将转交人赋值为当前用户,即
1
|
substituteUserCode=OAUserContext.getUserCode;
|
下面介绍一下委托,委托是一种事前的工作委托,即前一步用户在将工作下发时,即将任务下发给工作的代理人,这里需要配合授权模块。授权模块的核心字段是委托人工号、被委托人工号和委托单号。如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<
resultMap
type
=
"com.fx.oa.module.auth.authorize.api.shared.domain.AuthorizeEntity"
id
=
"authorizeObject"
>
<
id
property
=
"id"
column
=
"id"
/>
<
result
property
=
"staffId"
column
=
"staff_id"
/>
<
result
property
=
"staffName"
column
=
"staff_name"
/>
<
result
property
=
"authTime"
column
=
"auth_time"
/>
<
result
property
=
"pstaffId"
column
=
"p_staff_id"
/>
<
result
property
=
"pstaffName"
column
=
"p_staff_name"
/>
<
result
property
=
"authorizeStart"
column
=
"authorize_start"
/>
<
result
property
=
"authorizeEnd"
column
=
"authorize_end"
/>
<
result
property
=
"active"
column
=
"active"
/>
<
result
property
=
"failureTime"
column
=
"failure_time"
/>
<
result
property
=
"scopeKey"
column
=
"scope_key"
/>
<
result
property
=
"scopeName"
column
=
"scope_name"
/>
<
result
property
=
"remarks"
column
=
"remarks"
/>
<
result
property
=
"define1"
column
=
"define1"
/>
<
result
property
=
"define2"
column
=
"define2"
/>
<
result
property
=
"define3"
column
=
"define3"
/>
</
resultMap
>
|
这种事前的委托体现在根据审批人设置规则筛选出审批人后,再根据审批人列表及授权设置将审批人及代理人显示给用户。
后端的处理代码如下,这段代码主要实现这样一个功能,将审批人列表userList遍历看是否在授权表中存在活跃的授权,然后将被授权也就是被委托人拿出,组成一个新的list返回。
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
|
/**
* get the final assignee based authorization
* TODO(方法详细描述说明、方法参数的具体涵义)
* @author chao.gao
* @date 2015-3-11 下午4:10:15
* @param userList
* @return
*/
private
List<UserEntity> filterByAuth(List<UserEntity> userList, String processDefineId) {
List<UserEntity> userNewList =
new
ArrayList<UserEntity>();
for
(UserEntity user : userList){
AuthorizeEntity authEntity = authorizeService.authorize(user.getStaffId(), processDefineId);
if
(authEntity ==
null
)
userNewList.add(user);
else
{
UserEntity user2 = userService.queryUserByStaffId(authEntity.getPstaffId());
if
(user2 !=
null
){
UserEntity user3 =
new
UserEntity();
user3.setUserCode(user2.getUserCode());
user3.setUserName(user2.getUserName()+
"("
+ user.getUserName() +
"的工作代理人)"
);
user3.setDefine1(user.getUserCode());
userNewList.add(user3);
}
}
}
return
userNewList;
}
|
前端我是这样处理的:
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
|
if
(result.userList.length >0){
var
jbrArray = [];
var
hiddenWtr = [];
$.each(result.userList,
function
(i,r){
jbrArray +=
"<span class='input_checked'><input type='checkbox' class='check_box' name='jbr' checked='checked' value='"
+ r.userCode+
"'/></span>"
+r.userName;
if
(r.define1){
hiddenWtr +=
"<input name='wtr' value='"
+(r.userCode + ':
' + r.define1) + "'
type=
'hidden'
/>"
}
});
if
(jbrArray.length > 0){
$(
"#jbr"
).append(jbrArray);
$(
"#jbr"
).append(hiddenWtr);
if
($(
"#jbr input[type='checkbox']"
).length == 1){
$(
"#jbr input[type='checkbox']"
).first().attr(
"checked"
,
"checked"
)
}
}
}
...
var
wtrArr =
new
Array();
$(
"input[name='jbr']:checked"
).each(
function
(index, element){
var
userCode = $(element).val()
if
(userCode!=
null
&&userCode!=
""
){
jbrArr.push(userCode);
}
flag++;
});
$(
"input[name='wtr']"
).each(
function
(index, element){
var
userCode = $(element).val()
if
(userCode!=
null
&&userCode!=
""
){
wtrArr.push(userCode);
}
});
|
初看下来这样写其实是有问题的,其实没有问题。虽然审批人列表长度可能大于1,在这种情况下,如非会签,我们可以选择一到多个审批人,在审批人的数组里我们只push了选中的审批人,但在委托人的数组里却未对是否选中进行甄别。但这并不影响我们的正确使用,接下来看:
1
2
3
4
5
6
7
8
|
if
(StringUtils.isNotEmpty(task.getAssignee())){
taskForm.setCreateUserCode(task.getAssignee());
Map<String, String> wtrMap = queryWtr(startFlowRunTime);
if
(StringUtils.isNotEmpty(wtrMap.get(task.getAssignee()))){
taskForm.setEntrustedUserCode(wtrMap.get(task.getAssignee()));
}
this
.processTaskService.save(taskForm);
}
|
jbpm的分配任务跟普通任务是一样的,因为我们通过授权的筛选已经将审批人替换为被委托人,又通过上述代码委托人作为扩展任务的trustedUserCode作为主动委托人供委托模块查询。我们再看一下queryWtr这个方法是如何处理我们前端传入的“被委托人:委托人”格式的委托人列表的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
*
* get entrustedUserCode based wtrList whose style as a : b
* @author chao.gao
* @date 2015-3-13 上午8:56:15
* @param startFlowRunTime
* @return
*/
private
Map<String, String> queryWtr(FlowRunTime startFlowRunTime) {
Map<String, String> map =
new
HashMap<String, String>();
List<String> wtrList = startFlowRunTime.getWtrList();
if
(!CollectionUtils.isEmpty(wtrList)){
for
(String wtr : wtrList){
if
(wtr.contains(
":"
)){
String[] wtrs = wtr.split(
":"
);
if
(wtrs.length ==
2
){
map.put(wtrs[
0
], wtrs[
1
]);
}
}
}
}
return
map;
}
|
最后是查询模块,委托和转交的查询语句是相似的,我们拿委托模块举例如下:
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
<!-- 查询我委托工作信息集合 -->
<
select
id
=
"queryMyWtTask"
resultMap
=
"ExecutionResultMap"
parameterType
=
"com.fx.oa.module.bpm.workflow.api.shared.domain.ProcessExecutionEntity"
>
SELECT
DISTINCT(Execution.id),
Execution.code,
Execution.userId,
Execution.processDefineId,
Execution.processInstanceId,
Execution.status,
Execution.tableName,
Execution.pkValue,
Execution.formDefineId,
Execution.urgencyDegree,
ProcessDefine.description,
Execution.createTime,
Execution.modifyTime,
sysUser1.userName AS createUserCode,
sysUser2.userName AS modifyUserCode,
ProcessDefine.name,
Execution.active,
RejectWork.status as rejectStatus,
Task.NAME_ as activityName,
task3.activityName AS joinActivityName,
Task.ASSIGNEE_ as currentHandler
FROM T_BPM_PROCESS_DEFINE ProcessDefine,T_BPM_PROCESS_TASK TASK3,T_BPM_PROCESS_EXECUTION Execution
LEFT JOIN T_BPM_REJECTWORK RejectWork
ON Execution.pkValue = RejectWork.pkValue
LEFT JOIN jbpm4_task task
ON Execution.processInstanceId = task.EXECUTION_ID_
LEFT JOIN T_SYS_USER sysUser1
ON Execution.createUserCode = sysUser1.userCode
LEFT JOIN T_SYS_USER sysUser2
ON Execution.modifyUserCode = sysUser2.userCode
WHERE 1 = 1
AND Execution.ID = task3.processExecutionId
AND task3.entrustedUserCode = #{createUserCode}
AND Execution.processDefineId = ProcessDefine.ID
<
choose
>
<
when
test
=
"status == '11'"
>
<!-- AND rejectWork.status = '1' -->
AND Execution.status = '2' AND Execution.active != 'Y'
</
when
>
<
when
test
=
"status == '2'"
>
<!-- AND rejectWork.status != '1' -->
AND Execution.active != 'N'
</
when
>
<
otherwise
>
AND Execution.active != 'N'
</
otherwise
>
</
choose
>
AND Execution.createUserCode != #{createUserCode}
<
if
test
=
"createUserName != null and createUserName !=''"
>
AND Execution.createUserCode = (SELECT userCode
FROM T_SYS_USER sysUser
WHERE sysUser.userName= #{createUserName} LIMIT 0,1)
</
if
>
<
if
test
=
"processDefineId != null and processDefineId !=''"
>
AND Execution.processDefineId = #{processDefineId}
</
if
>
<
if
test
=
"status != null and status !='' and status !='11'"
>
AND Execution.status = #{status}
</
if
>
<
if
test
=
"name != null and name !=''"
>
AND ProcessDefine.name LIKE '%' #{name} '%'
</
if
>
<
if
test
=
"startCreateTime != null and startCreateTime !=''"
>
AND Execution.createTime
<![CDATA[>= #{startCreateTime} ]]>
</
if
>
<
if
test
=
"endCreateTime != null and endCreateTime !=''"
>
AND Execution.createTime
<![CDATA[<= #{endCreateTime}]]>
</
if
>
GROUP BY Execution.id
ORDER BY Execution.createTime DESC
</
select
>
|