客户需求
1.公文办理要求同时送给主办部门,会办部门,厅领导。
2.会办部门支持多个部门并行办理。
3.主办部门支持多个部门并行办理,并且主办部门人员可以增加会办部门。
4.厅领导支持多个领导审批。
流程图
收文流程
主办部门子流程
会办部门子流程
流程难点
1.拟办人要求与部门关联
一般流程中可以把办理任务设置为一个固定角色,然后这个角色关联多个用户来实现或签。但这里需求要在角色上加一个维度-拟办人角色与部门相关,即每个部门都可以有一个拟办人,拟办人通过后,会流转到本部门的领导那里审批。所以送上xml配置:
<bpmn2:userTask xmlns:flowable="http://flowable.org/bpmn" id="Activity_0el2prr" name="办公室拟办" flowable:dataType="ROLES" flowable:assignee="${assignee}" flowable:candidateGroups="ROLE131-${bgsId}" flowable:text="拟办人">
<bpmn2:extensionElements>
<flowable:properties>
<flowable:property name="type" value="bgs" />
</flowable:properties>
</bpmn2:extensionElements>
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="${multiInstanceHandler.getUserIds(execution)}" flowable:elementVariable="assignee">
<bpmn2:completionCondition>${nrOfCompletedInstances > 0}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
如果流程引擎不能配置出xml,建议直接复制以上xml到流程图中,然后手动调整代码。
2.秘书组送多个主办/会办部门
如果主办部门只有一个,可以把主办子流程节点全部画出来,但现实需求是主办部门不固定,要求支持N个,那就没办法全部画出来了。会办部门也是要求支持多个,更复杂的是主办部门还要能增加会办部门。所以解决思路只能朝着怎么能在主流程中调用子流程的方向思考了。
找找资料,最后发现flowable流程引擎提供了callActivity[调用活动]-允许主流程自动执行子流程,所以问题就简单了。把主办部门流程、会办部门流程单独画出来,主流程中用callActivity来代替子流程。
调用活动+子流程选择后,任务节点边框加粗且有[+]
<bpmn2:callActivity id="callReceiveDocHb" name="会办子流程" calledElement="receive_doc_hb" flowable:calledElementType="key" flowable:inheritBusinessKey="true" flowable:inheritVariables="true" flowable:sameDeployment="true">
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="deptIdList" flowable:elementVariable="deptId">
<bpmn2:completionCondition>${nrOfCompletedInstances == nrOfInstances}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:callActivity>
<bpmn2:sequenceFlow id="Flow_0spx6dr" name="送会办" sourceRef="Gateway_0pn01ra" targetRef="callReceiveDocHb">
<bpmn2:extensionElements>
<flowable:properties>
<flowable:property name="deptIdList" value="" />
</flowable:properties>
</bpmn2:extensionElements>
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${execution.getVariable('deptIdList').size() > 0}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:callActivity id="callReceiveDocHb" name="会办子流程" calledElement="receive_doc_hb" flowable:calledElementType="key" flowable:inheritBusinessKey="true" flowable:inheritVariables="true" flowable:sameDeployment="true">
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="deptIdList" flowable:elementVariable="deptId">
<bpmn2:completionCondition>${nrOfCompletedInstances == nrOfInstances}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:callActivity>
<bpmn2:sequenceFlow id="Flow_0spx6dr" name="送会办" sourceRef="Gateway_0pn01ra" targetRef="callReceiveDocHb">
<bpmn2:extensionElements>
<flowable:properties>
<flowable:property name="deptIdList" value="" />
</flowable:properties>
</bpmn2:extensionElements>
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${execution.getVariable('deptIdList').size() > 0}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
3.厅领导可以有多个人员
这里可以在同意接口 /complete 中传入 assigneeList (勾选多个userId)
<bpmn2:userTask xmlns:flowable="http://flowable.org/bpmn" id="Activity_0fgj1zi" name="厅领导批示" flowable:dataType="ROLES" flowable:assignee="${assignee}">
<bpmn2:extensionElements>
<flowable:properties>
<flowable:property name="type" value="leader" />
</flowable:properties>
</bpmn2:extensionElements>
<bpmn2:multiInstanceLoopCharacteristics flowable:collection="assigneeList" flowable:elementVariable="assignee">
<bpmn2:completionCondition>${nrOfCompletedInstances > 0}</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:userTask>
核心代码
MultiInstanceHandler.java
@Component("multiInstanceHandler")
@Slf4j
public class MultiInstanceHandler {
public HashSet<String> getUserIds(DelegateExecution execution) {
HashSet<String> candidateUserIds = new LinkedHashSet<>();
FlowElement flowElement = execution.getCurrentFlowElement();
Map<String, Object> variables = execution.getVariables();
Objects.requireNonNull(variables, "can not find variables !");
if (ObjectUtil.isNotEmpty(flowElement) && flowElement instanceof UserTask) {
UserTask userTask = (UserTask) flowElement;
log.debug("userTask = {}", userTask.getName());
String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE);
if ("USERS".equals(dataType) && CollUtil.isNotEmpty(userTask.getCandidateUsers())) {
candidateUserIds.addAll(userTask.getCandidateUsers());
} else if (CollUtil.isNotEmpty(userTask.getCandidateGroups())) {
List<String> groups = userTask.getCandidateGroups()
.stream().map(item -> item.substring(4)).collect(Collectors.toList());
if ("ROLES".equals(dataType)) {
log.debug("candidateGroups = {}", userTask.getCandidateGroups());
SysUserRoleMapper userRoleMapper = SpringUtil.getBean(SysUserRoleMapper.class);
groups.forEach(item -> {
// ROLE120-${deptId}
if(item.indexOf("-") > 0){
for(String key : variables.keySet()){
if(variables.get(key)!=null){
item = item.replace("${"+key+"}", String.valueOf(variables.get(key)));
}
}
String roleId = item.substring(0, item.indexOf("-"));
//支持传入多个部门(要求多个部门id使用,分隔)
String deptIds = item.substring(item.indexOf("-") + 1);
for(String deptId : deptIds.split(",")){
if(StrUtil.isNotBlank(deptId)){
List<String> userIds = userRoleMapper.selectUserIds(roleId, deptId).stream().map(String::valueOf).collect(Collectors.toList());
if(CollUtil.isEmpty(userIds)){
SysRoleMapper roleMapper = SpringUtil.getBean(SysRoleMapper.class);
SysRole sysRole = roleMapper.selectById(roleId);
SysDeptMapper deptMapper = SpringUtil.getBean(SysDeptMapper.class);
SysDept sysDept = deptMapper.selectById(deptId);
throw new ServiceException(String.format("[%s]部门下的[%s]角色未分配用户!", sysDept.getDeptName(), sysRole.getRoleName()));
}
log.info("selectUserIds userIds = {} by roleId={}, deptId={}", userIds, roleId, deptId);
candidateUserIds.addAll(userIds);
}
}
}else {
List<String> userIds = userRoleMapper.selectUserIdsByRoleId(item).stream().map(String::valueOf).collect(Collectors.toList());
if(CollUtil.isEmpty(userIds)){
SysRoleMapper roleMapper = SpringUtil.getBean(SysRoleMapper.class);
SysRole sysRole = roleMapper.selectById(item);
throw new ServiceException(String.format("[%s]角色未分配用户!", sysRole.getRoleName()));
}
log.info("selectUserIdsByRoleId userIds = {} by roleId={}", userIds, item);
candidateUserIds.addAll(userIds);
}
});
} else if ("DEPTS".equals(dataType)) {
User2deptMapper user2deptMapper = SpringUtil.getBean(User2deptMapper.class);
groups.forEach(item -> {
List<String> userIds = user2deptMapper.selectUserIdList(item)
.stream().map(String::valueOf).collect(Collectors.toList());
if(CollUtil.isEmpty(userIds)){
SysDeptMapper deptMapper = SpringUtil.getBean(SysDeptMapper.class);
SysDept sysDept = deptMapper.selectById(item);
throw new ServiceException(String.format("[%s]部门未分配用户!", sysDept.getDeptName()));
}
log.info("selectUserIdsByDeptId userIds = {} by deptId={}", userIds, item);
candidateUserIds.addAll(userIds);
});
}
}
}
return candidateUserIds;
}
}
ProcessConstants.java
/**
* 流程常量信息
*/
public class ProcessConstants {
public static final String SUFFIX = ".bpmn";
/**
* 动态数据
*/
public static final String DATA_TYPE = "dynamic";
/**
* 单个审批人
*/
public static final String USER_TYPE_ASSIGNEE = "assignee";
/**
* 候选人
*/
public static final String USER_TYPE_USERS = "candidateUsers";
/**
* 审批组
*/
public static final String USER_TYPE_ROUPS = "candidateGroups";
/**
* 单个审批人
*/
public static final String PROCESS_APPROVAL = "approval";
/**
* 会签人员
*/
public static final String PROCESS_MULTI_INSTANCE_USER = "userList";
/**
* nameapace
*/
public static final String NAMASPASE = "http://flowable.org/bpmn";
/**
* 会签节点
*/
public static final String PROCESS_MULTI_INSTANCE = "multiInstance";
/**
* 自定义属性 dataType
*/
public static final String PROCESS_CUSTOM_DATA_TYPE = "dataType";
/**
* 自定义属性 userType
*/
public static final String PROCESS_CUSTOM_USER_TYPE = "userType";
/**
* 流程跳过
*/
public static final String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
}
@ApiOperation(value = "同意任务")
@PostMapping(value = "/complete")
public Result complete(@Valid @RequestBody WfTaskBo bo) {
if(StrUtil.isBlank(bo.getComment())){
bo.setComment("通过");
}
Task task = flowTaskService.getTask(bo.getTaskId());
if (Objects.isNull(task)) {
throw new ServiceException("任务不存在");
}
//完成任务
flowTaskService.complete(bo, task);
//更新公文当前所在节点
String title = flowTaskService.updateNode(bo, task.getProcessInstanceId());
//给相关用户发送消息通知
SysMsgType msgType = "leader".equals(bo.getType()) ? SysMsgType.ps : SysMsgType.gw;
messageHandler.doHandle(msgType.name(), title, bo.getTaskId());
return Result.ok();
}
/**
* 完成任务
*
* @param taskBo 请求实体参数
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void complete(WfTaskBo taskBo, Task task) {
String instanceId = task.getProcessInstanceId();
if (DelegationState.PENDING.equals(task.getDelegationState())) {
if (StrUtil.isNotEmpty(taskBo.getComment())) {
taskService.addComment(taskBo.getTaskId(), instanceId, FlowComment.DELEGATE.getType(), taskBo.getComment());
}
taskService.resolveTask(taskBo.getTaskId());
} else {
if (StrUtil.isNotEmpty(taskBo.getComment())) {
//taskService.addComment(taskBo.getTaskId(), instanceId, FlowComment.NORMAL.getType(), taskBo.getComment());
taskService.addComment(taskBo.getTaskId(), instanceId, taskBo.getType(), taskBo.getComment());
}
if(StrUtil.isNotBlank(taskBo.getOwner())){//更新代理人
taskService.setOwner(taskBo.getTaskId(), taskBo.getOwner());
}
/*if(StrUtil.isNotBlank(taskBo.getAssignee())){//更新办理人
taskService.setAssignee(taskBo.getTaskId(), taskBo.getAssignee());
}*/
Map<String, Object> variables = new HashMap<>();
if(taskBo.getFlag()!=null){
variables.put("flag", Integer.parseInt(taskBo.getFlag()));
}
if(taskBo.getGateFlag()!=null){
variables.put("gateFlag", Integer.parseInt(taskBo.getGateFlag()));
}
if(StrUtil.isNotBlank(taskBo.getAssignee())){
variables.put("assignee", taskBo.getAssignee());
}
if(taskBo.getAssigneeList()!=null && taskBo.getAssigneeList().size()>0){
variables.put("assigneeList", taskBo.getAssigneeList());
}
if(taskBo.getHandleDeptIdList()!=null && taskBo.getHandleDeptIdList().size()>0){
variables.put("handleDeptIdList", taskBo.getHandleDeptIdList());
}
if(taskBo.getDeptIdList()!=null && taskBo.getDeptIdList().size()>0){
variables.put("deptIdList", taskBo.getDeptIdList());
}
if (CollUtil.isNotEmpty(taskBo.getMap())) {
variables.putAll(taskBo.getMap());
}
log.info("variables = {}", variables);
/*if ("1".equals(gw.getIsTransfer())) {
log.info(">>>> getIsTransfer = ", gw.getIsTransfer());
boolean localScope = false;
taskService.complete(taskBo.getTaskId(), variables, localScope);
} else {
}*/
taskService.complete(taskBo.getTaskId(), variables);
//更新待办任务的parentTaskId
HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().processInstanceId(taskBo.getProcInstId()).unfinished();
List<HistoricTaskInstance> list = query.list();
if(list!=null && list.size()>0){
for (HistoricTaskInstance instance : list) {
HistoricTaskInstanceEntity entity = (HistoricTaskInstanceEntity)instance;
entity.setParentTaskId(taskBo.getTaskId());
}
}
}
}
WfTaskBo.java
@Getter
@Setter
public class WfTaskBo {
@ApiModelProperty("任务Id")
private String taskId;
@ApiModelProperty("任务名称")
private String taskName;
@ApiModelProperty("用户Id")
private String userId;
@ApiModelProperty("任务意见")
private String comment;
@ApiModelProperty("流程实例Id")
private String procInstId;
@ApiModelProperty("节点")
private String targetKey;
@ApiModelProperty("参数map:{'handleDeptId': 128, 'deptId': '125'}")
private Map<String, Object> map;
@ApiModelProperty("办理人(接收人)")
private String assignee;
@ApiModelProperty(value = "实际办理人(用于代理人)")
private String owner;
@ApiModelProperty("多个审批人")
private List<String> assigneeList;
@ApiModelProperty("网关标识")
private String gateFlag;
@NotBlank(message = "flag can not be blank !")
@ApiModelProperty("同意标识:1同意,2驳回,0结束")
private String flag;
@ApiModelProperty("业务id")
@NotBlank(message = "dataId can not be blank !")
private String dataId;
private List<String> taskIds;
@ApiModelProperty("是否为转办理(0办理 1转办理)")
private String isTransfer;
@ApiModelProperty("办理类型(zb主办,hb会办,bg办公室审,leader领导审)")
private String type;
@ApiModelProperty("主办部门ids")
private List<String> handleDeptIdList;
@ApiModelProperty("会办部门ids")
private List<String> deptIdList;
}