优化 BpmParallelMultiInstanceBehavior 逻辑,实现会签、或签的任务分配

This commit is contained in:
YunaiV 2022-05-28 17:52:24 +08:00
parent 39e89bd378
commit 692daf900b
26 changed files with 148 additions and 222 deletions

View File

@ -9,8 +9,15 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Flowable 相关的工具方法
*
* @author 芋道源码
*/
public class FlowableUtils {
// ========== User 相关的工具方法 ==========
public static void setAuthenticatedUserId(Long userId) {
Authentication.setAuthenticatedUserId(String.valueOf(userId));
}
@ -19,6 +26,8 @@ public class FlowableUtils {
Authentication.setAuthenticatedUserId(null);
}
// ========== BPMN 相关的工具方法 ==========
/**
* 获得 BPMN 流程中指定的元素们
*
@ -59,4 +68,15 @@ public class FlowableUtils {
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
// ========== Execution 相关的工具方法 ==========
public static String formatCollectionVariable(String activityId) {
return activityId + "_assignees";
}
public static String formatCollectionElementVariable(String activityId) {
return activityId + "_assignee";
}
}

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task;

View File

@ -3,12 +3,8 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Map;
@ApiModel("管理后台 - 通过流程任务的 Request VO")
@Data
@ -20,6 +16,6 @@ public class BpmTaskApproveReqVO {
@ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
@NotEmpty(message = "审批意见不能为空")
private String comment;
private String reason;
}

View File

@ -39,5 +39,5 @@ public class BpmTaskBackReqVO {
@ApiModelProperty(value = "审批结果", required = true, example = "任务驳回")
@NotNull(message = "审批结果")
private String comment;
private String reason;
}

View File

@ -22,6 +22,6 @@ public class BpmTaskDonePageItemRespVO extends BpmTaskTodoPageItemRespVO {
@ApiModelProperty(value = "任务结果", required = true, notes = "参见 bpm_process_instance_result", example = "2")
private Integer result;
@ApiModelProperty(value = "审批建议", required = true, example = "不请假了!")
private String comment;
private String reason;
}

View File

@ -16,6 +16,6 @@ public class BpmTaskRejectReqVO {
@ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
@NotEmpty(message = "审批意见不能为空")
private String comment;
private String reason;
}

View File

@ -18,12 +18,10 @@ import org.flowable.task.api.Task;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 流程实例 Convert
@ -105,11 +103,11 @@ public interface BpmProcessInstanceConvert {
.setProcessInstanceName(instance.getName());
}
default BpmMessageSendWhenProcessInstanceRejectReqDTO convert2RejectReq(ProcessInstance instance, String comment) {
default BpmMessageSendWhenProcessInstanceRejectReqDTO convert2RejectReq(ProcessInstance instance, String reason) {
return new BpmMessageSendWhenProcessInstanceRejectReqDTO()
.setProcessInstanceName(instance.getName())
.setProcessInstanceId(instance.getId())
.setComment(comment)
.setReason(reason)
.setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
}

View File

@ -65,7 +65,7 @@ public class BpmTaskExtDO extends BaseDO {
/**
* 审批建议
*/
private String comment;
private String reason;
/**
* 任务的结束时间
*

View File

@ -1,8 +1,7 @@
package cn.iocoder.yudao.module.bpm.dal.mysql.task;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -21,28 +20,8 @@ public interface BpmTaskExtMapper extends BaseMapperX<BpmTaskExtDO> {
return selectList(BpmTaskExtDO::getTaskId, taskIds);
}
/**
* 查询全部任务
*
* @return 返回任务
*/
@TenantIgnore
default List<BpmTaskExtDO> listAll() {
return selectList();
}
/**
* 查询任务
*
* @param procInstId 流程id
*
* @return 返回任务列表
*/
@TenantIgnore
List<BpmTaskExtDO> listByProcInstId(@Param("procInstId") String procInstId);
default List<BpmTaskExtDO> selectListByProcessInstanceId(String processInstanceId) {
return selectList("process_instance_id", processInstanceId);
return selectList(BpmTaskExtDO::getProcessInstanceId, processInstanceId);
}
/**
@ -56,11 +35,11 @@ public interface BpmTaskExtMapper extends BaseMapperX<BpmTaskExtDO> {
* 任务驳回
*
* @param taskId 任务列表
* @param comment 驳回理由
* @param reason 驳回理由
*
* @return 返回驳回结果是否成功
*/
Boolean backByTaskId(@Param("taskId") String taskId, @Param("comment") String comment);
Boolean backByTaskId(@Param("taskId") String taskId, @Param("reason") String reason);
/**
* 逻辑删除任务

View File

@ -1,8 +1,6 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@ -10,26 +8,20 @@ import org.flowable.bpmn.model.Activity;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.util.CommandContextUtil;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
/**
* 自定义的并行多个流程任务的 assignee 负责人的分配
* 第一步基于分配规则计算出分配任务的多个候选人们
* 第二步多个任务候选人们设置到 DelegateExecution collectionVariable 变量中以便 BpmUserTaskActivityBehavior 使用它
*
* @author kemengkai
* @create 2022-04-21 16:57
* @date 2022-04-21 16:57
*/
@Slf4j
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {
/**
* EL表达式集合模板
*/
private final static String EXPRESSION_TEXT_TEMPLATE = "${coll_userList}";
@Setter
private BpmTaskAssignRuleService bpmTaskRuleService;
@ -39,78 +31,28 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
}
/**
* 创建并行任务
* 重写该方法主要实现两个功能
* 1. 忽略原有的 collectionVariablecollectionElementVariable 表达式而是采用自己定义的
* 2. 获得任务的处理人并设置到 collectionVariable 用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
*
* @param multiInstanceRootExecution 并行任务入参
* 注意多个任务实例每个任务实例对应一个处理人所以返回的数量就是任务处理人的数量
*
* @return 返回结果
* @param execution 执行任务
* @return 数量
*/
@Override
protected int createInstances(DelegateExecution multiInstanceRootExecution) {
// 查找任务信息
// BpmTaskAssignRuleDO taskRule = getTaskRule(multiInstanceRootExecution);
BpmTaskAssignRuleDO taskRule = null;
// 获取任务用户
Set<Long> assigneeUserIds = calculateTaskCandidateUsers(multiInstanceRootExecution, taskRule);
// 设置任务集合变量
String expressionText = String.format("%s_userList", taskRule.getTaskDefinitionKey());
// 设置任务集合变量与任务关系
multiInstanceRootExecution.setVariable(expressionText, assigneeUserIds);
// 设置任务集合EL表达式
this.collectionExpression = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager()
.createExpression(String.format("${%s}", expressionText));
// 根据会签或签类型设置会签,或签条件
// if (BpmTaskAssignRuleTypeEnum.USER_SIGN.getType().equals(taskRule.getType())) {
// // 会签
// this.completionCondition = "${ nrOfInstances == nrOfCompletedInstances }";
// } else {
// // 或签
// this.completionCondition = "${ nrOfCompletedInstances == 1 }";
// }
// 设置取出集合变量
this.collectionElementVariable = "user";
return super.createInstances(multiInstanceRootExecution);
}
protected int resolveNrOfInstances(DelegateExecution execution) {
// 第一步设置 collectionVariable CollectionVariable
// execution.getVariable() 读取所有任务处理人的 key
super.collectionExpression = null; // collectionExpression collectionVariable 是互斥的
super.collectionVariable = FlowableUtils.formatCollectionVariable(execution.getCurrentActivityId());
// execution.getVariable() 读取当前所有任务处理的人的 key
super.collectionElementVariable = FlowableUtils.formatCollectionElementVariable(execution.getCurrentActivityId());
@Override
protected Object resolveCollection(DelegateExecution execution) {
Object collection = null;
if (EXPRESSION_TEXT_TEMPLATE.equals(this.collectionExpression.getExpressionText())) {
// 查找任务信息
// BpmTaskAssignRuleDO taskRule = getTaskRule(execution);
BpmTaskAssignRuleDO taskRule = null;
// 设置任务集合变量
String expressionText = String.format("%s_userList", execution.getCurrentActivityId());
// 获取任务用户
Set<Long> assigneeUserIds = calculateTaskCandidateUsers(execution, taskRule);
// 设置任务集合变量与任务关系
execution.setVariable(expressionText, assigneeUserIds);
// 设置任务集合EL表达式
this.collectionExpression = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager()
.createExpression(String.format("${%s}", expressionText));
}
if (this.collectionExpression != null) {
collection = this.collectionExpression.getValue(execution);
} else if (this.collectionVariable != null) {
collection = execution.getVariable(this.collectionVariable);
} else if (this.collectionString != null) {
collection = this.collectionString;
}
return collection;
}
Set<Long> calculateTaskCandidateUsers(DelegateExecution task, BpmTaskAssignRuleDO rule) {
Set<Long> assigneeUserIds = SetUtils.asSet(1L, 104L);
// 移除被禁用的用户
// 如果候选人为空抛出异常 TODO 芋艿没候选人的策略选择1 - 挂起2 - 直接结束3 - 强制一个兜底人
if (CollUtil.isEmpty(assigneeUserIds)) {
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", task.getId(),
task.getProcessDefinitionId(), task.getCurrentActivityId(), toJsonString(rule));
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
}
return assigneeUserIds;
// 第二步获取任务的所有处理人
Set<Long> assigneeUserIds = bpmTaskRuleService.calculateTaskCandidateUsers(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
return assigneeUserIds.size();
}
}

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@ -16,14 +16,12 @@ import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 自定义的流程任务的 assignee 负责人的分配
* 第一步获得对应的分配规则
* 第二步根据分配规则计算出分配任务的候选人如果找不到则直接报业务异常不继续执行后续的流程
* 第三步随机选择一个候选人则选择作为 assignee 负责人
* 自定义的单个流程任务的 assignee 负责人的分配
* 第一步基于分配规则计算出分配任务的单个候选人如果找不到则直接报业务异常不继续执行后续的流程
* 第二步随机选择一个候选人则选择作为 assignee 负责人
*
* @author 芋道源码
*/
@ -38,30 +36,28 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
}
@Override
@DataPermission(enable = false) // 不需要处理数据权限 不然会有问题查询不到数据
protected void handleAssignments(TaskService taskService, String assignee, String owner,
List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
// 第一步获得任务的候选用户们
Set<Long> candidateUserIds = bpmTaskRuleService.calculateTaskCandidateUsers(task);
// 第二步选择一个作为候选人
Long assigneeUserId = chooseTaskAssignee(execution, candidateUserIds);
// 第三步设置作为负责人
// 第一步获得任务的候选用户
Long assigneeUserId = calculateTaskCandidateUsers(execution);
Assert.notNull(assigneeUserId, "任务处理人不能为空");
// 第二步设置作为负责人
TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
}
private Long chooseTaskAssignee(DelegateExecution execution, Set<Long> candidateUserIds) {
// 获取任务变量
Map<String, Object> variables = execution.getVariables();
// 设置任务集合变量key
String expressionText = String.format("%s_userList", execution.getCurrentActivityId());
// 判断当前任务是否为并行任务, 是的话获取任务变量
if (variables.containsKey(expressionText)) {
String user = variables.get("user").toString();
return Long.valueOf(user);
private Long calculateTaskCandidateUsers(DelegateExecution execution) {
// 情况一如果是多实例的任务例如说会签或签等情况则从 Variable 中获取它的任务处理人在 BpmParallelMultiInstanceBehavior 中已经被分配了
if (super.multiInstanceActivityBehavior != null) {
return execution.getVariable(super.multiInstanceActivityBehavior.getCollectionElementVariable(), Long.class);
}
// TODO 芋艿未来可以优化下改成轮询的策略
// 情况二如果非多实例的任务则计算任务处理人
// 第一步先计算可处理该任务的处理人们
Set<Long> candidateUserIds = bpmTaskRuleService.calculateTaskCandidateUsers(execution);
// 第二步后随机选择一个任务的处理人
// 疑问为什么一定要选择一个任务处理人
// 解答项目对 bpm 的任务是责任到人所以每个任务有且仅有一个处理人如果希望一个任务可以同时被多个人处理可以考虑使用 BpmParallelMultiInstanceBehavior 实现的会签 or 或签
int index = RandomUtil.randomInt(candidateUserIds.size());
return CollUtil.get(candidateUserIds, index);
}

View File

@ -1,10 +1,11 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script;
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.engine.delegate.DelegateExecution;
import java.util.Set;
// TODO @芋艿迁移到 bpm core
/**
* Bpm 任务分配的自定义 Script 脚本
* 使用场景
@ -17,12 +18,12 @@ import java.util.Set;
public interface BpmTaskAssignScript {
/**
* 基于流程任务获得任务的候选用户们
* 基于执行任务获得任务的候选用户们
*
* @param task 任务
* @param execution 执行任务
* @return 候选人用户的编号数组
*/
Set<Long> calculateTaskCandidateUsers(TaskEntity task);
Set<Long> calculateTaskCandidateUsers(DelegateExecution execution);
/**
* 获得枚举值

View File

@ -7,9 +7,8 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.Assert;
@ -35,10 +34,10 @@ public abstract class BpmTaskAssignLeaderAbstractScript implements BpmTaskAssign
@Lazy // 解决循环依赖
private BpmProcessInstanceService bpmProcessInstanceService;
protected Set<Long> calculateTaskCandidateUsers(TaskEntity task, int level) {
protected Set<Long> calculateTaskCandidateUsers(DelegateExecution execution, int level) {
Assert.isTrue(level > 0, "level 必须大于 0");
// 获得发起人
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
// 获得对应 leve 的部门
DeptRespDTO dept = null;

View File

@ -1,8 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Set;
@ -16,9 +15,8 @@ import java.util.Set;
public class BpmTaskAssignLeaderX1Script extends BpmTaskAssignLeaderAbstractScript {
@Override
@DataPermission(enable = false) // 不需要处理数据权限 不然会有问题查询不到数据
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
return calculateTaskCandidateUsers(task, 1);
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
return calculateTaskCandidateUsers(execution, 1);
}
@Override

View File

@ -1,8 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Set;
@ -16,9 +15,8 @@ import java.util.Set;
public class BpmTaskAssignLeaderX2Script extends BpmTaskAssignLeaderAbstractScript {
@Override
@DataPermission(enable = false) // 不需要处理数据权限 不然会有问题查询不到数据
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
return calculateTaskCandidateUsers(task, 2);
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
return calculateTaskCandidateUsers(execution, 2);
}
@Override

View File

@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.domain.enums.definition.BpmTaskRuleScriptEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@ -27,8 +26,8 @@ public class BpmTaskAssignStartUserScript implements BpmTaskAssignScript {
private BpmProcessInstanceService bpmProcessInstanceService;
@Override
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
return SetUtils.asSet(startUserId);
}

View File

@ -7,6 +7,6 @@
* 1. Controller URL /bpm/ 开头避免和其它 Module 冲突
* 2. DataObject 表名 bpm_ 开头方便在数据库中区分
*
* 注意由于 Bpm 模块下容易和其它模块重名所以类名都加载 Pay 的前缀~
* 注意由于 Bpm 模块下容易和其它模块重名所以类名都加载 Bpm 的前缀~
*/
package cn.iocoder.yudao.module.bpm;

View File

@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAs
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.lang.Nullable;
import javax.validation.Valid;
@ -86,6 +86,12 @@ public interface BpmTaskAssignRuleService {
*/
void checkTaskAssignRuleAllConfig(String id);
Set<Long> calculateTaskCandidateUsers(TaskEntity task);
/**
* 计算当前执行任务的处理人
*
* @param execution 执行任务
* @return 处理人的编号数组
*/
Set<Long> calculateTaskCandidateUsers(DelegateExecution execution);
}

View File

@ -31,7 +31,7 @@ import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -40,6 +40,7 @@ import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
import static cn.hutool.core.text.CharSequenceUtil.format;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@ -226,95 +227,93 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
CollectionUtils.convertSet(options, String::valueOf));
} else {
throw new IllegalArgumentException(StrUtil.format("未知的规则类型({})", type));
throw new IllegalArgumentException(format("未知的规则类型({})", type));
}
}
@Override
@DataPermission(enable = false) // 忽略数据权限不然分配会存在问题
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
BpmTaskAssignRuleDO rule = getTaskRule(task);
return calculateTaskCandidateUsers(task, rule);
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
BpmTaskAssignRuleDO rule = getTaskRule(execution);
return calculateTaskCandidateUsers(execution, rule);
}
@VisibleForTesting
BpmTaskAssignRuleDO getTaskRule(TaskEntity task) {
BpmTaskAssignRuleDO getTaskRule(DelegateExecution execution) {
List<BpmTaskAssignRuleDO> taskRules = getTaskAssignRuleListByProcessDefinitionId(
task.getProcessDefinitionId(), task.getTaskDefinitionKey());
execution.getProcessDefinitionId(), execution.getCurrentActivityId());
if (CollUtil.isEmpty(taskRules)) {
throw new FlowableException(
StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则", task.getId(), task.getProcessDefinitionId(),
task.getTaskDefinitionKey()));
throw new FlowableException(format("流程任务({}/{}/{}) 找不到符合的任务规则",
execution.getId(), execution.getProcessDefinitionId(), execution.getCurrentActivityId()));
}
if (taskRules.size() > 1) {
throw new FlowableException(
StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})", task.getId(), task.getProcessDefinitionId(),
task.getTaskDefinitionKey(), taskRules.size()));
throw new FlowableException(format("流程任务({}/{}/{}) 找到过多任务规则({})",
execution.getId(), execution.getProcessDefinitionId(), execution.getCurrentActivityId()));
}
return taskRules.get(0);
}
@VisibleForTesting
Set<Long> calculateTaskCandidateUsers(TaskEntity task, BpmTaskAssignRuleDO rule) {
Set<Long> calculateTaskCandidateUsers(DelegateExecution execution, BpmTaskAssignRuleDO rule) {
Set<Long> assigneeUserIds = null;
if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByRole(task, rule);
assigneeUserIds = calculateTaskCandidateUsersByRole(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByDeptMember(task, rule);
assigneeUserIds = calculateTaskCandidateUsersByDeptMember(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(task, rule);
assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByPost(task, rule);
assigneeUserIds = calculateTaskCandidateUsersByPost(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
assigneeUserIds = calculateTaskCandidateUsersByUser(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
assigneeUserIds = calculateTaskCandidateUsersByScript(execution, rule);
}
// 移除被禁用的用户
removeDisableUsers(assigneeUserIds);
// 如果候选人为空抛出异常 TODO 芋艿没候选人的策略选择1 - 挂起2 - 直接结束3 - 强制一个兜底人
if (CollUtil.isEmpty(assigneeUserIds)) {
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", task.getId(),
task.getProcessDefinitionId(), task.getTaskDefinitionKey(), toJsonString(rule));
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", execution.getId(),
execution.getProcessDefinitionId(), execution.getCurrentActivityId(), toJsonString(rule));
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
}
return assigneeUserIds;
}
private Set<Long> calculateTaskCandidateUsersByRole(TaskEntity task, BpmTaskAssignRuleDO rule) {
private Set<Long> calculateTaskCandidateUsersByRole(BpmTaskAssignRuleDO rule) {
return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
}
private Set<Long> calculateTaskCandidateUsersByDeptMember(TaskEntity task, BpmTaskAssignRuleDO rule) {
private Set<Long> calculateTaskCandidateUsersByDeptMember(BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByDeptIds(rule.getOptions());
return convertSet(users, AdminUserRespDTO::getId);
}
private Set<Long> calculateTaskCandidateUsersByDeptLeader(TaskEntity task, BpmTaskAssignRuleDO rule) {
private Set<Long> calculateTaskCandidateUsersByDeptLeader(BpmTaskAssignRuleDO rule) {
List<DeptRespDTO> depts = deptApi.getDepts(rule.getOptions());
return convertSet(depts, DeptRespDTO::getLeaderUserId);
}
private Set<Long> calculateTaskCandidateUsersByPost(TaskEntity task, BpmTaskAssignRuleDO rule) {
private Set<Long> calculateTaskCandidateUsersByPost(BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
return convertSet(users, AdminUserRespDTO::getId);
}
private Set<Long> calculateTaskCandidateUsersByUser(TaskEntity task, BpmTaskAssignRuleDO rule) {
private Set<Long> calculateTaskCandidateUsersByUser(BpmTaskAssignRuleDO rule) {
return rule.getOptions();
}
private Set<Long> calculateTaskCandidateUsersByUserGroup(TaskEntity task, BpmTaskAssignRuleDO rule) {
private Set<Long> calculateTaskCandidateUsersByUserGroup(BpmTaskAssignRuleDO rule) {
List<BpmUserGroupDO> userGroups = userGroupService.getUserGroupList(rule.getOptions());
Set<Long> userIds = new HashSet<>();
userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds()));
return userIds;
}
private Set<Long> calculateTaskCandidateUsersByScript(TaskEntity task, BpmTaskAssignRuleDO rule) {
private Set<Long> calculateTaskCandidateUsersByScript(DelegateExecution execution, BpmTaskAssignRuleDO rule) {
// 获得对应的脚本
List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
rule.getOptions().forEach(id -> {
@ -326,7 +325,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
});
// 逐个计算任务
Set<Long> userIds = new HashSet<>();
scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(task)));
scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(execution)));
return userIds;
}

View File

@ -44,7 +44,7 @@ public class BpmMessageServiceImpl implements BpmMessageService {
public void sendMessageWhenProcessInstanceReject(BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO) {
Map<String, Object> templateParams = new HashMap<>();
templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
templateParams.put("comment", reqDTO.getComment());
templateParams.put("reason", reqDTO.getReason());
templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
BpmMessageEnum.PROCESS_INSTANCE_REJECT.getSmsTemplateCode(), templateParams));

View File

@ -28,6 +28,6 @@ public class BpmMessageSendWhenProcessInstanceRejectReqDTO {
* 不通过理由
*/
@NotEmpty(message = "不通过理由不能为空")
private String comment;
private String reason;
}

View File

@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
@ -141,9 +140,9 @@ public interface BpmProcessInstanceService {
* 更新 ProcessInstance 拓展记录为不通过
*
* @param id 流程编号
* @param comment 理由例如说审批不通过时需要传递该值
* @param reason 理由例如说审批不通过时需要传递该值
*/
void updateProcessInstanceExtReject(String id, String comment);
void updateProcessInstanceExtReject(String id, String reason);
}

View File

@ -251,11 +251,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
}
@Transactional(rollbackFor = Exception.class)
public void updateProcessInstanceExtReject(String id, String comment) {
public void updateProcessInstanceExtReject(String id, String reason) {
// 需要主动查询因为 instance 只有 id 属性
ProcessInstance processInstance = getProcessInstance(id);
// 删除流程实例以实现驳回任务时取消整个审批流程
deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(comment)));
deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason)));
// 更新 status + result
// 注意不能和上面的逻辑更换位置因为 deleteProcessInstance 会触发流程的取消进而调用 updateProcessInstanceExtCancel 方法
@ -266,7 +266,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程被不通过的消息
messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, comment));
messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason));
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(

View File

@ -186,7 +186,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));*/
// 拼接数据
List<BpmTaskExtDO> tmpBpmTaskExtDOList = taskExtMapper.listByProcInstId(processInstanceId);
// List<BpmTaskExtDO> tmpBpmTaskExtDOList = taskExtMapper.listByProcInstId(processInstanceId);
List<BpmTaskExtDO> tmpBpmTaskExtDOList = taskExtMapper.selectListByProcessInstanceId(processInstanceId);
tmpBpmTaskExtDOList.sort(Comparator.comparing(BpmTaskExtDO::getCreateTime));
return hiTaskInstService.taskGetComment(tmpBpmTaskExtDOList, "");
}
@ -207,13 +209,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 更新任务拓展表为通过
taskExtMapper.updateByTaskId(
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.APPROVE.getResult())
.setComment(reqVO.getComment()));
.setReason(reqVO.getReason()));
// 判断任务是否为或签或签时删除其余不用审批的任务
List<BpmTaskAssignRuleDO> bpmTaskAssignRuleList =
taskAssignRuleMapper.selectListByProcessDefinitionId(task.getProcessDefinitionId(),
task.getTaskDefinitionKey());
if (CollUtil.isNotEmpty(bpmTaskAssignRuleList) && bpmTaskAssignRuleList.size() > 0) {
// edit by 芋艿
// edit by 芋艿 TODO
// if (BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType().equals(bpmTaskAssignRuleList.get(0).getType())) {
// taskExtMapper.delTaskByProcInstIdAndTaskIdAndTaskDefKey(
// new BpmTaskExtDO().setTaskId(task.getId()).setTaskDefKey(task.getTaskDefinitionKey())
@ -233,12 +235,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
// 更新流程实例为不通过
processInstanceService.updateProcessInstanceExtReject(instance.getProcessInstanceId(), reqVO.getComment());
processInstanceService.updateProcessInstanceExtReject(instance.getProcessInstanceId(), reqVO.getReason());
// 更新任务拓展表为不通过
taskExtMapper.updateByTaskId(
new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.REJECT.getResult())
.setComment(reqVO.getComment()));
.setReason(reqVO.getReason()));
}
@Override
@ -269,7 +271,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 逻辑删除hiTaskInst表任务
Boolean delHiTaskInstResult = bpmActivityMapper.delHiTaskInstByTaskId(taskIdList);
// 更新任务拓展表
Boolean backResult = taskExtMapper.backByTaskId(reqVO.getTaskId(), reqVO.getComment());
Boolean backResult = taskExtMapper.backByTaskId(reqVO.getTaskId(), reqVO.getReason());
Boolean delTaskResult = taskExtMapper.delByTaskIds(taskIdList);
if (!delHiActInstResult && !delHiTaskInstResult && !backResult && !delTaskResult) {
throw new RuntimeException("任务驳回失败!!!");

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.business.hi.task.inst.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
@ -67,6 +68,7 @@ public class HiTaskInstService {
* @return 返回流程信息
*/
@TenantIgnore
@DataPermission(enable = false) // TODO 芋艿先临时去掉
public List<BpmTaskRespVO> taskGetComment(List<BpmTaskExtDO> taskList, Object approved) {
BpmTaskExtDO task = taskList.get(taskList.size() - 1);
Map<String, BpmTaskExtDO> bpmTaskMap =
@ -262,7 +264,7 @@ public class HiTaskInstService {
DeptDO deptDO = deptMap.get(adminUserDO.getDeptId());
bpmTaskRespVO.setAssigneeUser(setUser(adminUserDO));
bpmTaskRespVO.getAssigneeUser().setDeptName(deptDO.getName());
// edit by 芋艿
// edit by 芋艿 TODO
// if (!bpmTaskAssignRuleDO.getType().equals(BpmTaskAssignRuleTypeEnum.USER_OR_SIGN.getType())
// && !bpmTaskAssignRuleDO.getType().equals(BpmTaskAssignRuleTypeEnum.USER_SIGN.getType())) {
// break;

View File

@ -13,7 +13,7 @@
<update id="backByTaskId">
UPDATE bpm_task_ext
SET result=2,
`comment`=#{comment}
`reason`=#{reason}
WHERE task_id = #{taskId}
</update>
<update id="delByTaskIds">
@ -26,12 +26,5 @@
</foreach>
</update>
<select id="listByProcInstId" resultType="cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO">
SELECT *
FROM bpm_task_ext
WHERE `process_instance_id` = #{procInstId}
AND `deleted` = FALSE
ORDER BY create_time
LIMIT 500
</select>
</mapper>