taskVOList = CollectionUtils.convertList(taskList, task -> {
BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
- taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
+ Integer taskStatus = FlowableUtils.getTaskStatus(task);
+ taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task));
// 流程实例
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
@@ -106,7 +111,15 @@ public interface BpmTaskConvert {
taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class));
findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
}
- return taskVO;
+ if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){
+ // 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限;回复:可能不需要,但是发起人,需要有个权限配置
+ // TODO @jason:貌似这么返回,主要解决当前审批 task 的表单权限,但是不同抄送人的表单权限,可能不太对。例如说,对 A 抄送人是隐藏某个字段。
+ // @芋艿 表单权限需要分离开。单独的接口来获取了 BpmProcessInstanceService.getProcessInstanceFormFieldsPermission
+ taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey()));
+ // 操作按钮设置
+ taskVO.setButtonsSetting(BpmnModelUtils.parseButtonsSetting(bpmnModel, task.getTaskDefinitionKey()));
+ }
+ return taskVO;
});
// 拼接父子关系
@@ -151,12 +164,12 @@ public interface BpmTaskConvert {
/**
* 将父任务的属性,拷贝到子任务(加签任务)
- *
+ *
* 为什么不使用 mapstruct 映射?因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的。
* 使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题。
*
* @param parentTask 父任务
- * @param childTask 加签任务
+ * @param childTask 加签任务
*/
default void copyTo(TaskEntityImpl parentTask, TaskEntityImpl childTask) {
childTask.setName(parentTask.getName());
@@ -165,7 +178,6 @@ public interface BpmTaskConvert {
childTask.setParentTaskId(parentTask.getId());
childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId());
childTask.setProcessInstanceId(parentTask.getProcessInstanceId());
-// childTask.setExecutionId(parentTask.getExecutionId()); // TODO 芋艿:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错)
childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId());
childTask.setPriority(parentTask.getPriority());
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
index 9ac9252d5..d6a3093d8 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
@@ -1,12 +1,20 @@
package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
-import lombok.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.flowable.engine.repository.Model;
+import org.flowable.engine.repository.ProcessDefinition;
import java.util.List;
@@ -31,15 +39,21 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
/**
* 流程定义的编号
*
- * 关联 ProcessDefinition 的 id 属性
+ * 关联 {@link ProcessDefinition#getId()} 属性
*/
private String processDefinitionId;
/**
* 流程模型的编号
*
- * 关联 Model 的 id 属性
+ * 关联 {@link Model#getId()} 属性
*/
private String modelId;
+ /**
+ * 流程模型的类型
+ *
+ * 枚举 {@link BpmModelTypeEnum}
+ */
+ private Integer modelType;
/**
* 图标
@@ -53,11 +67,12 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
/**
* 表单类型
*
- * 关联 {@link BpmModelFormTypeEnum}
+ * 枚举 {@link BpmModelFormTypeEnum}
*/
private Integer formType;
/**
* 动态表单编号
+ *
* 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
*
* 关联 {@link BpmFormDO#getId()}
@@ -65,6 +80,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
private Long formId;
/**
* 表单的配置
+ *
* 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
*
* 冗余 {@link BpmFormDO#getConf()}
@@ -72,21 +88,59 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
private String formConf;
/**
* 表单项的数组
+ *
* 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
*
- * 冗余 {@link BpmFormDO#getFields()} ()}
+ * 冗余 {@link BpmFormDO#getFields()}
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List formFields;
/**
* 自定义表单的提交路径,使用 Vue 的路由地址
+ *
* 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
*/
private String formCustomCreatePath;
/**
* 自定义表单的查看路径,使用 Vue 的路由地址
+ *
* 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
*/
private String formCustomViewPath;
+ /**
+ * SIMPLE 设计器模型数据 json 格式
+ *
+ * 目的:当使用仿钉钉设计器时。流程模型发布的时候,需要保存流程模型设计器的快照数据。
+ */
+ private String simpleModel;
+ /**
+ * 是否可见
+ *
+ * 目的:如果 false 不可见,则不展示在“发起流程”的列表里
+ */
+ private Boolean visible;
+
+ /**
+ * 可发起用户编号数组
+ *
+ * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
+ *
+ * 如果为空,则表示“全部可以发起”!
+ *
+ * 它和 {@link #visible} 的区别在于:
+ * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起
+ * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的
+ */
+ @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
+ private List startUserIds;
+
+ /**
+ * 可管理用户编号数组
+ *
+ * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
+ */
+ @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
+ private List managerUserIds;
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java
index 57e729605..29da3fcfc 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java
@@ -48,10 +48,15 @@ public class BpmProcessInstanceCopyDO extends BaseDO {
* 冗余 ProcessInstance 的 category 字段
*/
private String category;
-
+ /**
+ * 流程活动编号
+ *
+ * 对应 BPMN XML 节点编号,用于查询抄送节点的表单字段权限
+ * 这里冗余的原因:如果是钉钉易搭的抄送节点 (ServiceTask),使用 taskId 可能查不到对应的 activityId
+ */
+ private String activityId;
/**
* 任务主键
- *
* 关联 Task 的 id 属性
*/
private String taskId;
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java
index c5ec50f65..8f23024e8 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
@Mapper
public interface BpmProcessInstanceCopyMapper extends BaseMapperX {
@@ -18,4 +20,9 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX selectListByProcessInstanceIdAndActivityId(String processInstanceId, String activityId) {
+ return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId,
+ BpmProcessInstanceCopyDO::getActivityId, activityId);
+ }
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java
index 8e69fdc75..e79437b43 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
@@ -56,12 +57,15 @@ public class BpmFlowableConfiguration {
@Bean
public EngineConfigurationConfigurer bpmProcessEngineConfigurationConfigurer(
ObjectProvider listeners,
+ ObjectProvider customFlowableFunctionDelegates,
BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
return configuration -> {
// 注册监听器,例如说 BpmActivityEventListener
configuration.setEventListeners(ListUtil.toList(listeners.iterator()));
// 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义
configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
+ // 设置自定义的函数
+ configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator()));
};
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
index ec392e496..d8cfd6d0b 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
@@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
+import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.engine.delegate.DelegateExecution;
@@ -48,12 +50,17 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
- // 由于每次审批(会签、或签等情况)后都会执行一次,所以 variable 已经有结果,不重复计算
@SuppressWarnings("unchecked")
Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class);
if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
+ if (CollUtil.isEmpty(assigneeUserIds)) {
+ // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
+ // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
+ // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
+ assigneeUserIds = SetUtils.asSet((Long) null);
+ }
}
return assigneeUserIds.size();
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
index 16a54481d..8e9acdd15 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
@@ -1,5 +1,7 @@
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.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter;
@@ -41,12 +43,17 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
- // 由于每次审批(会签、或签等情况)后都会执行一次,所以 variable 已经有结果,不重复计算
@SuppressWarnings("unchecked")
Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class);
if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
+ if (CollUtil.isEmpty(assigneeUserIds)) {
+ // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
+ // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
+ // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
+ assigneeUserIds = SetUtils.asSet((Long) null);
+ }
}
return assigneeUserIds.size();
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
index c49465273..592e02bfb 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
@@ -1,7 +1,6 @@
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.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import lombok.Setter;
@@ -14,6 +13,7 @@ import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.TaskHelper;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
+import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
@@ -36,14 +36,16 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
}
@Override
+ @Transactional(rollbackFor = Exception.class)
protected void handleAssignments(TaskService taskService, String assignee, String owner,
List candidateUsers, List candidateGroups, TaskEntity task, ExpressionManager expressionManager,
DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
// 第一步,获得任务的候选用户
Long assigneeUserId = calculateTaskCandidateUsers(execution);
- Assert.notNull(assigneeUserId, "任务处理人不能为空");
// 第二步,设置作为负责人
- TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
+ if (assigneeUserId != null) {
+ TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
+ }
}
private Long calculateTaskCandidateUsers(DelegateExecution execution) {
@@ -56,6 +58,9 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
// 情况二,如果非多实例的任务,则计算任务处理人
// 第一步,先计算可处理该任务的处理人们
Set candidateUserIds = taskCandidateInvoker.calculateUsers(execution);
+ if (CollUtil.isEmpty(candidateUserIds)) {
+ return null;
+ }
// 第二步,后随机选择一个任务的处理人
// 疑问:为什么一定要选择一个任务处理人?
// 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
index c0c7ca0d9..5ac0a00e6 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
@@ -2,11 +2,17 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.annotations.VisibleForTesting;
@@ -14,15 +20,12 @@ import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.runtime.ProcessInstance;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG;
-import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
/**
* {@link BpmTaskCandidateStrategy} 的调用者,用于调用对应的策略,实现任务的候选人的计算
@@ -57,7 +60,14 @@ public class BpmTaskCandidateInvoker {
List userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
// 遍历所有的 UserTask,校验审批人配置
userTaskList.forEach(userTask -> {
- // 1. 非空校验
+ // 1.1 非人工审批,无需校验审批人配置
+ Integer approveType = BpmnModelUtils.parseApproveType(userTask);
+ if (ObjectUtils.equalsAny(approveType,
+ BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
+ BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
+ return;
+ }
+ // 1.2 非空校验
Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask);
String param = BpmnModelUtils.parseCandidateParam(userTask);
if (strategy == null) {
@@ -80,19 +90,31 @@ public class BpmTaskCandidateInvoker {
*/
@DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人
public Set calculateUsers(DelegateExecution execution) {
+ // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
+ Integer approveType = BpmnModelUtils.parseApproveType(execution.getCurrentFlowElement());
+ if (ObjectUtils.equalsAny(approveType,
+ BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
+ BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
+ return new HashSet<>();
+ }
+
Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
// 1.1 计算任务的候选人
Set userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
+ removeDisableUsers(userIds);
// 1.2 移除被禁用的用户
removeDisableUsers(userIds);
- // 2. 校验是否有候选人
+ // 2. 候选人为空时,根据“审批人为空”的配置补充
if (CollUtil.isEmpty(userIds)) {
- log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(),
- execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param);
- throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
+ userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
+ .calculateUsers(execution, param);
+ // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
}
+
+ // 3. 移除发起人的用户
+ removeStartUserIfSkip(execution, userIds);
return userIds;
}
@@ -108,7 +130,30 @@ public class BpmTaskCandidateInvoker {
});
}
- private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
+ /**
+ * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人
+ *
+ * 注意:如果只有一个候选人,则不处理,避免无法审批
+ *
+ * @param execution 执行中的任务
+ * @param assigneeUserIds 当前分配的候选人
+ */
+ @VisibleForTesting
+ void removeStartUserIfSkip(DelegateExecution execution, Set assigneeUserIds) {
+ if (CollUtil.size(assigneeUserIds) <= 1) {
+ return;
+ }
+ Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement());
+ if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
+ return;
+ }
+ ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class)
+ .getProcessInstance(execution.getProcessInstanceId());
+ Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId());
+ assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId()));
+ }
+
+ public BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
BpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum);
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java
index 1534d39c2..9057a0ca1 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java
@@ -2,12 +2,14 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.runtime.ProcessInstance;
+import java.util.Collections;
import java.util.Set;
/**
* BPM 任务的候选人的策略接口
- *
+ *
* 例如说:分配审批人
*
* @author 芋道源码
@@ -28,14 +30,6 @@ public interface BpmTaskCandidateStrategy {
*/
void validateParam(String param);
- /**
- * 基于执行任务,获得任务的候选用户们
- *
- * @param execution 执行任务
- * @return 用户编号集合
- */
- Set calculateUsers(DelegateExecution execution, String param);
-
/**
* 是否一定要输入参数
*
@@ -45,4 +39,50 @@ public interface BpmTaskCandidateStrategy {
return true;
}
+ /**
+ * 基于候选人参数,获得任务的候选用户们
+ *
+ * @param param 执行任务
+ * @return 用户编号集合
+ */
+ default Set calculateUsers(String param) {
+ return Collections.emptySet();
+ }
+
+ /**
+ * 基于执行任务,获得任务的候选用户们
+ *
+ * @param execution 执行任务
+ * @return 用户编号集合
+ */
+ default Set calculateUsers(DelegateExecution execution, String param) {
+ Set users = calculateUsers(param);
+ removeDisableUsers(users);
+ return users;
+ }
+
+ /**
+ * 基于流程实例,获得任务的候选用户们
+ *
+ * 目的:用于获取未执行节点的候选用户们
+ *
+ * @param startUserId 流程发起人编号
+ * @param processInstance 流程实例编号
+ * @param activityId 活动 Id (对应 Bpmn XML id)
+ * @param param 节点的参数
+ * @return 用户编号集合
+ */
+ default Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
+ Set users = calculateUsers(param);
+ removeDisableUsers(users);
+ return users;
+ }
+
+ /**
+ * 移除被禁用的用户
+ *
+ * @param users 用户 Ids
+ */
+ void removeDisableUsers(Set users);
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java
new file mode 100644
index 000000000..548f448d5
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractDeptLeaderStrategy.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
+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 java.util.*;
+
+/**
+ * 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类
+ *
+ * @author jason
+ */
+public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy {
+
+ protected DeptApi deptApi;
+
+ public BpmTaskCandidateAbstractDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
+ super(adminUserApi);
+ this.deptApi = deptApi;
+ }
+
+ /**
+ * 获得指定层级的部门负责人,只有第 level 的负责人
+ *
+ * @param dept 指定部门
+ * @param level 第几级
+ * @return 部门负责人的编号
+ */
+ protected Long getAssignLevelDeptLeaderId(DeptRespDTO dept, Integer level) {
+ Assert.isTrue(level > 0, "level 必须大于 0");
+ if (dept == null) {
+ return null;
+ }
+ DeptRespDTO currentDept = dept;
+ for (int i = 1; i < level; i++) {
+ DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId());
+ if (parentDept == null) { // 找不到父级部门,到了最高级。返回最高级的部门负责人
+ break;
+ }
+ currentDept = parentDept;
+ }
+ return currentDept.getLeaderUserId();
+ }
+
+ /**
+ * 获得连续层级的部门负责人,包含 [1, level] 的负责人
+ *
+ * @param deptIds 指定部门编号数组
+ * @param level 最大层级
+ * @return 连续部门负责人 Id
+ */
+ protected Set getMultiLevelDeptLeaderIds(List deptIds, Integer level) {
+ Assert.isTrue(level > 0, "level 必须大于 0");
+ if (CollUtil.isEmpty(deptIds)) {
+ return new HashSet<>();
+ }
+ Set deptLeaderIds = new LinkedHashSet<>(); // 保证有序
+ for (Long deptId : deptIds) {
+ DeptRespDTO dept = deptApi.getDept(deptId);
+ for (int i = 0; i < level; i++) {
+ if (dept.getLeaderUserId() != null) {
+ deptLeaderIds.add(dept.getLeaderUserId());
+ }
+ DeptRespDTO parentDept = deptApi.getDept(dept.getParentId());
+ if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了
+ break;
+ }
+ dept = parentDept;
+ }
+ }
+ return deptLeaderIds;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java
new file mode 100644
index 000000000..8ff2bdaab
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAbstractStrategy.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * {@link BpmTaskCandidateStrategy} 抽象类
+ *
+ * @author jason
+ */
+public abstract class BpmTaskCandidateAbstractStrategy implements BpmTaskCandidateStrategy {
+
+ protected AdminUserApi adminUserApi;
+
+ public BpmTaskCandidateAbstractStrategy(AdminUserApi adminUserApi) {
+ this.adminUserApi = adminUserApi;
+ }
+
+ @Override
+ public void removeDisableUsers(Set users) {
+ if (CollUtil.isEmpty(users)) {
+ return;
+ }
+ Map userMap = adminUserApi.getUserMap(users);
+ users.removeIf(id -> {
+ AdminUserRespDTO user = userMap.get(id);
+ return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
+ });
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java
new file mode 100644
index 000000000..d6bd19caf
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateAssignEmptyStrategy.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import jakarta.annotation.Resource;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * 审批人为空 {@link BpmTaskCandidateStrategy} 实现类
+ *
+ * @author kyle
+ */
+@Component
+public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy {
+
+ @Resource
+ @Lazy // 延迟加载,避免循环依赖
+ private BpmProcessDefinitionService processDefinitionService;
+
+ public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) {
+ super(adminUserApi);
+ }
+
+ @Override
+ public BpmTaskCandidateStrategyEnum getStrategy() {
+ return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY;
+ }
+
+ @Override
+ public void validateParam(String param) {
+ }
+
+ @Override
+ public Set calculateUsers(DelegateExecution execution, String param) {
+ // 情况一:指定人员审批
+ Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement());
+ if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) {
+ Set users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement()));
+ removeDisableUsers(users);
+ return users;
+ }
+
+ // 情况二:流程管理员
+ if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) {
+ BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId());
+ Assert.notNull(processDefinition, "流程定义({})不存在", execution.getProcessDefinitionId());
+ return new HashSet<>(processDefinition.getManagerUserIds());
+ }
+
+ // 都不满足,还是返回空
+ return new HashSet<>();
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java
new file mode 100644
index 000000000..450dc33ba
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderMultiStrategy.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import org.springframework.stereotype.Component;
+
+import java.util.Set;
+
+/**
+ * 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
+ *
+ * @author jason
+ */
+@Component
+public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
+
+ public BpmTaskCandidateDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
+ super(adminUserApi, deptApi);
+ }
+
+ @Override
+ public BpmTaskCandidateStrategyEnum getStrategy() {
+ return BpmTaskCandidateStrategyEnum.MULTI_DEPT_LEADER_MULTI;
+ }
+
+ @Override
+ public void validateParam(String param) {
+ // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级
+ String[] params = param.split("\\|");
+ Assert.isTrue(params.length == 2, "参数格式不匹配");
+ deptApi.validateDeptList(StrUtils.splitToLong(params[0], ","));
+ Assert.isTrue(Integer.parseInt(params[1]) > 0, "部门层级必须大于 0");
+ }
+
+ @Override
+ public Set calculateUsers(String param) {
+ String[] params = param.split("\\|");
+ return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1]));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java
index 485552f91..bc049d01e 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java
@@ -5,8 +5,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
-import jakarta.annotation.Resource;
-import org.flowable.engine.delegate.DelegateExecution;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.springframework.stereotype.Component;
import java.util.List;
@@ -20,10 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
* @author kyle
*/
@Component
-public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
+public class BpmTaskCandidateDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy {
- @Resource
- private DeptApi deptApi;
+ private final DeptApi deptApi;
+
+ public BpmTaskCandidateDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
+ super(adminUserApi);
+ this.deptApi = deptApi;
+ }
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -37,7 +40,7 @@ public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrat
}
@Override
- public Set calculateUsers(DelegateExecution execution, String param) {
+ public Set calculateUsers(String param) {
Set deptIds = StrUtils.splitToLongSet(param);
List depts = deptApi.getDeptList(deptIds);
return convertSet(depts, DeptRespDTO::getLeaderUserId);
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java
index f60b1cc8b..d4a5c7e05 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java
@@ -6,8 +6,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
-import jakarta.annotation.Resource;
-import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.List;
@@ -21,12 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
* @author kyle
*/
@Component
-public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy {
+public class BpmTaskCandidateDeptMemberStrategy extends BpmTaskCandidateAbstractStrategy {
- @Resource
- private DeptApi deptApi;
- @Resource
- private AdminUserApi adminUserApi;
+ private final DeptApi deptApi;
+
+ public BpmTaskCandidateDeptMemberStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
+ super(adminUserApi);
+ this.deptApi = deptApi;
+ }
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -40,7 +40,7 @@ public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrat
}
@Override
- public Set calculateUsers(DelegateExecution execution, String param) {
+ public Set calculateUsers(String param) {
Set deptIds = StrUtils.splitToLongSet(param);
List users = adminUserApi.getUserListByDeptIds(deptIds);
return convertSet(users, AdminUserRespDTO::getId);
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java
index e0f9dabe5..1e48cdf94 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java
@@ -4,6 +4,7 @@ import cn.hutool.core.convert.Convert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
@@ -15,7 +16,11 @@ import java.util.Set;
* @author 芋道源码
*/
@Component
-public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy {
+public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstractStrategy {
+
+ public BpmTaskCandidateExpressionStrategy(AdminUserApi adminUserApi) {
+ super(adminUserApi);
+ }
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -30,7 +35,9 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
@Override
public Set calculateUsers(DelegateExecution execution, String param) {
Object result = FlowableUtils.getExpressionValue(execution, param);
- return Convert.toSet(Long.class, result);
+ Set users = Convert.toSet(Long.class, result);
+ removeDisableUsers(users);
+ return users;
}
}
\ No newline at end of file
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java
index bc161886b..9a239f7bb 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java
@@ -5,8 +5,7 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
-import jakarta.annotation.Resource;
-import org.flowable.engine.delegate.DelegateExecution;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.springframework.stereotype.Component;
import java.util.Collection;
@@ -21,10 +20,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
* @author kyle
*/
@Component
-public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy {
+public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrategy {
- @Resource
- private BpmUserGroupService userGroupService;
+ private final BpmUserGroupService userGroupService;
+
+ public BpmTaskCandidateGroupStrategy(AdminUserApi adminUserApi, BpmUserGroupService userGroupService) {
+ super(adminUserApi);
+ this.userGroupService = userGroupService;
+ }
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -38,7 +41,7 @@ public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy {
}
@Override
- public Set calculateUsers(DelegateExecution execution, String param) {
+ public Set calculateUsers(String param) {
Set groupIds = StrUtils.splitToLongSet(param);
List groups = userGroupService.getUserGroupList(groupIds);
return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream);
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java
index 3f2ae58f1..18dd4a493 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java
@@ -6,8 +6,6 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
-import jakarta.annotation.Resource;
-import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.List;
@@ -21,12 +19,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
* @author kyle
*/
@Component
-public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy {
+public class BpmTaskCandidatePostStrategy extends BpmTaskCandidateAbstractStrategy {
- @Resource
- private PostApi postApi;
- @Resource
- private AdminUserApi adminUserApi;
+ private final PostApi postApi;
+
+ public BpmTaskCandidatePostStrategy(AdminUserApi adminUserApi, PostApi postApi) {
+ super(adminUserApi);
+ this.postApi = postApi;
+ }
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -40,7 +40,7 @@ public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy {
}
@Override
- public Set calculateUsers(DelegateExecution execution, String param) {
+ public Set calculateUsers(String param) {
Set postIds = StrUtils.splitToLongSet(param);
List users = adminUserApi.getUserListByPostIds(postIds);
return convertSet(users, AdminUserRespDTO::getId);
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java
index 0dd178626..0693f036f 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java
@@ -5,8 +5,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
-import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Set;
@@ -17,13 +17,17 @@ import java.util.Set;
* @author kyle
*/
@Component
-public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy {
+public class BpmTaskCandidateRoleStrategy extends BpmTaskCandidateAbstractStrategy {
@Resource
private RoleApi roleApi;
@Resource
private PermissionApi permissionApi;
+ public BpmTaskCandidateRoleStrategy(AdminUserApi adminUserApi) {
+ super(adminUserApi);
+ }
+
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.ROLE;
@@ -36,7 +40,7 @@ public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy {
}
@Override
- public Set calculateUsers(DelegateExecution execution, String param) {
+ public Set calculateUsers(String param) {
Set roleIds = StrUtils.splitToLongSet(param);
return permissionApi.getUserRoleIdListByRoleIds(roleIds);
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java
new file mode 100644
index 000000000..a36db376a
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+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 jakarta.annotation.Resource;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static cn.hutool.core.collection.ListUtil.toList;
+
+/**
+ * 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
+ *
+ * @author jason
+ */
+@Component
+public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
+
+ @Resource
+ @Lazy
+ private BpmProcessInstanceService processInstanceService;
+
+ public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
+ super(adminUserApi, deptApi);
+ }
+
+ @Override
+ public BpmTaskCandidateStrategyEnum getStrategy() {
+ return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI;
+ }
+
+ @Override
+ public void validateParam(String param) {
+ // 参数是部门的层级
+ Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0");
+ }
+
+ @Override
+ public Set calculateUsers(DelegateExecution execution, String param) {
+ // 获得流程发起人
+ ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
+ Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
+ // 获取发起人的 multi 部门负责人
+ DeptRespDTO dept = getStartUserDept(startUserId);
+ if (dept == null) {
+ return new HashSet<>();
+ }
+ Set users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级
+ // TODO @jason:这里 removeDisableUsers 的原因是啥呀?
+ removeDisableUsers(users);
+ return users;
+ }
+
+ @Override
+ public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
+ DeptRespDTO dept = getStartUserDept(startUserId);
+ if (dept == null) {
+ return new HashSet<>();
+ }
+ Set users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级
+ removeDisableUsers(users);
+ return users;
+ }
+
+ /**
+ * 获取发起人的部门
+ *
+ * @param startUserId 发起人 Id
+ */
+ protected DeptRespDTO getStartUserDept(Long startUserId) {
+ AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
+ if (startUser.getDeptId() == null) { // 找不到部门
+ return null;
+ }
+ return deptApi.getDept(startUser.getDeptId());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java
new file mode 100644
index 000000000..38698c62c
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserDeptLeaderStrategy.java
@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+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 jakarta.annotation.Resource;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
+
+/**
+ * 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类
+ *
+ * @author jason
+ */
+@Component
+public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
+
+ @Resource
+ @Lazy // 避免循环依赖
+ private BpmProcessInstanceService processInstanceService;
+
+ @Override
+ public BpmTaskCandidateStrategyEnum getStrategy() {
+ return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER;
+ }
+
+ public BpmTaskCandidateStartUserDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
+ super(adminUserApi, deptApi);
+ }
+
+ @Override
+ public void validateParam(String param) {
+ // 参数是部门的层级
+ Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0");
+ }
+
+ @Override
+ public Set calculateUsers(DelegateExecution execution, String param) {
+ // 获得流程发起人
+ ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
+ Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
+ // 获取发起人的部门负责人
+ Set users = getStartUserDeptLeader(startUserId, param);
+ removeDisableUsers(users);
+ return users;
+ }
+
+ @Override
+ public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
+ // 获取发起人的部门负责人
+ Set users = getStartUserDeptLeader(startUserId, param);
+ removeDisableUsers(users);
+ return users;
+ }
+
+ private Set getStartUserDeptLeader(Long startUserId, String param) {
+ DeptRespDTO dept = getStartUserDept(startUserId);
+ if (dept == null) {
+ return new HashSet<>();
+ }
+ Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级
+ return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>();
+ }
+
+ /**
+ * 获取发起人的部门
+ *
+ * @param startUserId 发起人 Id
+ */
+ protected DeptRespDTO getStartUserDept(Long startUserId) {
+ AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
+ if (startUser.getDeptId() == null) { // 找不到部门
+ return null;
+ }
+ return deptApi.getDept(startUser.getDeptId());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java
index ef31d8885..af9438c56 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java
@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
@@ -23,12 +23,16 @@ import java.util.*;
* @author 芋道源码
*/
@Component
-public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidateStrategy {
+public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbstractStrategy {
@Resource
@Lazy // 延迟加载,避免循环依赖
private BpmProcessInstanceService processInstanceService;
+ public BpmTaskCandidateStartUserSelectStrategy(AdminUserApi adminUserApi) {
+ super(adminUserApi);
+ }
+
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
@@ -46,7 +50,23 @@ public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidate
execution.getProcessInstanceId());
// 获得审批人
List assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
- return new LinkedHashSet<>(assignees);
+ Set users = new LinkedHashSet<>(assignees);
+ removeDisableUsers(users);
+ return users;
+ }
+
+ @Override
+ public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
+ if (processInstance == null) {
+ return Collections.emptySet();
+ }
+ Map> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
+ Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", processInstance.getId());
+ // 获得审批人
+ List assignees = startUserSelectAssignees.get(activityId);
+ Set users = new LinkedHashSet<>(assignees);
+ removeDisableUsers(users);
+ return users;
}
@Override
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java
new file mode 100644
index 000000000..ddc990c61
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserStrategy.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+
+import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import jakarta.annotation.Resource;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.util.Set;
+
+/**
+ * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类
+ *
+ * 适合场景:用于需要发起人信息复核等场景
+ *
+ * @author jason
+ */
+@Component
+public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractStrategy {
+
+ @Resource
+ @Lazy // 延迟加载,避免循环依赖
+ private BpmProcessInstanceService processInstanceService;
+
+ public BpmTaskCandidateStartUserStrategy(AdminUserApi adminUserApi) {
+ super(adminUserApi);
+ }
+
+ @Override
+ public BpmTaskCandidateStrategyEnum getStrategy() {
+ return BpmTaskCandidateStrategyEnum.START_USER;
+ }
+
+ @Override
+ public void validateParam(String param) {
+ }
+
+ @Override
+ public boolean isParamRequired() {
+ return false;
+ }
+
+ @Override
+ public Set calculateUsers(DelegateExecution execution, String param) {
+ ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
+ Set users = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
+ removeDisableUsers(users);
+ return users;
+ }
+
+ @Override
+ public Set calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
+ Set users = SetUtils.asSet(startUserId);
+ removeDisableUsers(users);
+ return users;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java
index 390e4903a..9982f7eb2 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java
@@ -1,13 +1,13 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
+import cn.hutool.core.text.StrPool;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import jakarta.annotation.Resource;
-import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
+import java.util.LinkedHashSet;
import java.util.Set;
/**
@@ -16,10 +16,11 @@ import java.util.Set;
* @author kyle
*/
@Component
-public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy {
+public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrategy {
- @Resource
- private AdminUserApi adminUserApi;
+ public BpmTaskCandidateUserStrategy(AdminUserApi adminUserApi) {
+ super(adminUserApi);
+ }
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@@ -32,8 +33,8 @@ public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy {
}
@Override
- public Set calculateUsers(DelegateExecution execution, String param) {
- return StrUtils.splitToLongSet(param);
+ public Set calculateUsers(String param) {
+ return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA));
}
}
\ No newline at end of file
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java
new file mode 100644
index 000000000..e2a7252b7
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/el/VariableConvertByTypeExpressionFunction.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.el;
+
+import org.flowable.common.engine.api.variable.VariableContainer;
+import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction;
+import org.springframework.stereotype.Component;
+
+// TODO @jason:这个自定义转换的原因是啥呀?
+/**
+ * 根据流程变量 variable 的类型, 转换参数的值
+ *
+ * @author jason
+ */
+@Component
+public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVariableExpressionFunction {
+
+ public VariableConvertByTypeExpressionFunction() {
+ super("convertByType");
+ }
+
+ public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) {
+ Object variable = variableContainer.getVariable(variableName);
+ if (variable != null && parmaValue != null) {
+ // 如果值不是字符串类型, 流程变量的类型是字符串。 把值转成字符串
+ if (!(parmaValue instanceof String) && variable instanceof String ) {
+ return parmaValue.toString();
+ }
+ }
+ return parmaValue;
+ }
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
index a8b538501..240aa18dc 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
@@ -1,9 +1,12 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
+import java.util.Arrays;
+
/**
* BPM 任务的候选人策略枚举
*
@@ -13,18 +16,25 @@ import lombok.Getter;
*/
@Getter
@AllArgsConstructor
-public enum BpmTaskCandidateStrategyEnum {
+public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable {
ROLE(10, "角色"),
DEPT_MEMBER(20, "部门的成员"), // 包括负责人
DEPT_LEADER(21, "部门的负责人"),
+ MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
POST(22, "岗位"),
USER(30, "用户"),
START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人
+ START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景
+ START_USER_DEPT_LEADER(37, "发起人部门负责人"),
+ START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"),
USER_GROUP(40, "用户组"),
EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager
+ ASSIGN_EMPTY(1, "审批人为空"),
;
+ public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmTaskCandidateStrategyEnum::getStrategy).toArray();
+
/**
* 类型
*/
@@ -38,4 +48,9 @@ public enum BpmTaskCandidateStrategyEnum {
return ArrayUtil.firstMatch(o -> o.getStrategy().equals(strategy), values());
}
+ @Override
+ public int[] array() {
+ return ARRAYS;
+ }
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
index 3eb6981ef..25772d5f3 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
@@ -23,4 +23,95 @@ public interface BpmnModelConstants {
*/
String USER_TASK_CANDIDATE_PARAM = "candidateParam";
+ /**
+ * BPMN ExtensionElement 的扩展属性,用于标记边界事件类型
+ */
+ String BOUNDARY_EVENT_TYPE = "boundaryEventType";
+
+ /**
+ * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作
+ */
+ String USER_TASK_TIMEOUT_HANDLER_TYPE = "timeoutHandlerType";
+
+ /**
+ * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型
+ */
+ String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType";
+
+ /**
+ * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理类型
+ */
+ String USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE = "assignEmptyHandlerType";
+ /**
+ * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理的指定用户编号数组
+ */
+ String USER_TASK_ASSIGN_USER_IDS = "assignEmptyUserIds";
+
+ /**
+ * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型
+ */
+ String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType";
+ /**
+ * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的回退的任务 Id
+ */
+ String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId";
+
+ /**
+ * BPMN UserTask 的扩展属性,用于标记用户任务的审批类型
+ */
+ String USER_TASK_APPROVE_TYPE = "approveType";
+
+ /**
+ * BPMN UserTask 的扩展属性,用于标记用户任务的审批方式
+ */
+ String USER_TASK_APPROVE_METHOD = "approveMethod";
+
+ /**
+ * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
+ */
+ String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission";
+
+ /**
+ * BPMN ExtensionElement Attribute, 用于标记表单字段
+ */
+ String FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE = "field";
+ /**
+ * BPMN ExtensionElement Attribute, 用于标记表单权限
+ */
+ String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission";
+
+ /**
+ * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置
+ */
+ String BUTTON_SETTING_ELEMENT = "buttonsSetting";
+
+ /**
+ * BPMN ExtensionElement Attribute, 用于标记按钮编号
+ */
+ String BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE = "id";
+
+ /**
+ * BPMN ExtensionElement Attribute, 用于标记按钮显示名称
+ */
+ String BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE = "displayName";
+
+ /**
+ * BPMN ExtensionElement Attribute, 用于标记按钮是否启用
+ */
+ String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable";
+
+ /**
+ * BPMN Start Event Node Id
+ */
+ String START_EVENT_NODE_ID = "StartEvent";
+ /**
+ * BPMN Start Event Node Name
+ */
+ String START_EVENT_NODE_NAME = "开始";
+
+ /**
+ * 发起人节点 ID
+ */
+ String START_USER_NODE_ID = "StartUserNode";
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
similarity index 57%
rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java
rename to yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
index e965d2281..5ccaea306 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
import org.flowable.engine.runtime.ProcessInstance;
/**
- * BPM 通用常量
+ * BPM Variable 通用常量
*
* @author 芋道源码
*/
-public class BpmConstants {
+public class BpmnVariableConstants {
/**
* 流程实例的变量 - 状态
@@ -15,6 +15,14 @@ public class BpmConstants {
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS";
+ /**
+ * 流程实例的变量 - 理由
+ *
+ * 例如说:审批不通过的理由(目前审核通过暂时不会记录)
+ *
+ * @see ProcessInstance#getProcessVariables()
+ */
+ public static final String PROCESS_INSTANCE_VARIABLE_REASON = "PROCESS_REASON";
/**
* 流程实例的变量 - 发起用户选择的审批人 Map
*
@@ -22,6 +30,15 @@ public class BpmConstants {
*/
public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
+ /**
+ * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id}
+ *
+ * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过
+ *
+ * @see ProcessInstance#getProcessVariables()
+ */
+ public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s";
+
/**
* 任务的变量 - 状态
*
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java
new file mode 100644
index 000000000..2275f0c12
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/SimpleModelConstants.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
+
+// TODO @jason:要不合并到 BpmnModelConstants 那
+/**
+ * 仿钉钉快搭 JSON 常量信息
+ *
+ * @author jason
+ */
+public interface SimpleModelConstants {
+
+ // TODO @芋艿:条件表达式的字段名
+
+ /**
+ * 网关节点默认序列流属性
+ */
+ String DEFAULT_FLOW_ATTRIBUTE = "defaultFlow";
+
+ /**
+ * 条件节点的条件类型属性
+ */
+ String CONDITION_TYPE_ATTRIBUTE = "conditionType";
+
+ /**
+ * 条件节点条件表达式属性
+ */
+ String CONDITION_EXPRESSION_ATTRIBUTE = "conditionExpression";
+
+ /**
+ * 条件规则的条件组属性
+ */
+ String CONDITION_GROUPS_ATTRIBUTE = "conditionGroups";
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java
new file mode 100644
index 000000000..3b9b34e59
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceCopyService;
+import jakarta.annotation.Resource;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.flowable.engine.delegate.JavaDelegate;
+import org.springframework.stereotype.Component;
+
+import java.util.Set;
+
+import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate.BEAN_NAME;
+
+/**
+ * 处理抄送用户的 {@link JavaDelegate} 的实现类
+ *
+ * 目前只有快搭模式的【抄送节点】使用
+ *
+ * @author jason
+ */
+@Component(BEAN_NAME)
+public class BpmCopyTaskDelegate implements JavaDelegate {
+
+ public static final String BEAN_NAME = "bpmCopyTaskDelegate";
+
+ @Resource
+ private BpmTaskCandidateInvoker taskCandidateInvoker;
+
+ @Resource
+ private BpmProcessInstanceCopyService processInstanceCopyService;
+
+ @Override
+ public void execute(DelegateExecution execution) {
+ // 1. 获得抄送人
+ Set userIds = taskCandidateInvoker.calculateUsers(execution);
+ if (CollUtil.isEmpty(userIds)) {
+ return;
+ }
+ // 2. 执行抄送
+ FlowElement currentFlowElement = execution.getCurrentFlowElement();
+ processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(),
+ currentFlowElement.getId(), null, currentFlowElement.getName());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
index cf1506e8d..a0ec3e40a 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
@@ -6,7 +6,6 @@ import jakarta.annotation.Resource;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
-import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@@ -21,27 +20,21 @@ import java.util.Set;
@Component
public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
- @Resource
- @Lazy
- private BpmProcessInstanceService processInstanceService;
-
public static final Set PROCESS_INSTANCE_EVENTS = ImmutableSet.builder()
- .add(FlowableEngineEventType.PROCESS_CANCELLED)
- .add(FlowableEngineEventType.PROCESS_COMPLETED)
- .build();
+ .add(FlowableEngineEventType.PROCESS_COMPLETED)
+ .build();
+
+ @Resource
+ @Lazy // 延迟加载,避免循环依赖
+ private BpmProcessInstanceService processInstanceService;
public BpmProcessInstanceEventListener(){
super(PROCESS_INSTANCE_EVENTS);
}
- @Override
- protected void processCancelled(FlowableCancelledEvent event) {
- processInstanceService.updateProcessInstanceWhenCancel(event);
- }
-
@Override
protected void processCompleted(FlowableEngineEntityEvent event) {
- processInstanceService.updateProcessInstanceWhenApprove((ProcessInstance)event.getEntity());
+ processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity());
}
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
index 733cc2b41..bece6740e 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
@@ -1,21 +1,31 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import com.google.common.collect.ImmutableSet;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
+import org.flowable.bpmn.model.BoundaryEvent;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent;
import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.job.api.Job;
import org.flowable.task.api.Task;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
-import jakarta.annotation.Resource;
import java.util.List;
import java.util.Set;
@@ -28,6 +38,9 @@ import java.util.Set;
@Slf4j
public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
+ @Resource
+ @Lazy // 延迟加载,避免循环依赖
+ private BpmModelService modelService;
@Resource
@Lazy // 解决循环依赖
private BpmTaskService taskService;
@@ -40,20 +53,21 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
.add(FlowableEngineEventType.TASK_ASSIGNED)
// .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。
.add(FlowableEngineEventType.ACTIVITY_CANCELLED)
+ .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时
.build();
- public BpmTaskEventListener(){
+ public BpmTaskEventListener() {
super(TASK_EVENTS);
}
@Override
protected void taskCreated(FlowableEngineEntityEvent event) {
- taskService.updateTaskStatusWhenCreated((Task) event.getEntity());
+ taskService.processTaskCreated((Task) event.getEntity());
}
@Override
protected void taskAssigned(FlowableEngineEntityEvent event) {
- taskService.updateTaskExtAssign((Task)event.getEntity());
+ taskService.processTaskAssigned((Task) event.getEntity());
}
@Override
@@ -68,8 +82,34 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
if (StrUtil.isEmpty(activity.getTaskId())) {
return;
}
- taskService.updateTaskStatusWhenCanceled(activity.getTaskId());
+ taskService.processTaskCanceled(activity.getTaskId());
});
}
+ @Override
+ protected void timerFired(FlowableEngineEntityEvent event) {
+ // 1.1 只处理 BoundaryEvent 边界计时时间
+ String processDefinitionId = event.getProcessDefinitionId();
+ BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
+ Job entity = (Job) event.getEntity();
+ FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
+ if (!(element instanceof BoundaryEvent)) {
+ return;
+ }
+ // 1.2 判断是否为超时处理
+ BoundaryEvent boundaryEvent = (BoundaryEvent) element;
+ String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent,
+ BpmnModelConstants.BOUNDARY_EVENT_TYPE);
+ BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType));
+ if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) {
+ return;
+ }
+
+ // 2. 处理超时
+ String timeoutHandlerType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent,
+ BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_TYPE);
+ String taskKey = boundaryEvent.getAttachedToRefId();
+ taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutHandlerType));
+ }
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
index c046011b0..5ac1933aa 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -1,9 +1,13 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
@@ -12,19 +16,111 @@ import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import java.util.*;
+import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
+import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
+
/**
* 流程模型转操作工具类
*/
public class BpmnModelUtils {
public static Integer parseCandidateStrategy(FlowElement userTask) {
- return NumberUtils.parseInt(userTask.getAttributeValue(
+ Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
+ // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限
+ if (candidateStrategy == null) {
+ ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
+ candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null;
+ }
+ return candidateStrategy;
}
public static String parseCandidateParam(FlowElement userTask) {
- return userTask.getAttributeValue(
+ String candidateParam = userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
+ if (candidateParam == null) {
+ ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
+ candidateParam = element != null ? element.getElementText() : null;
+ }
+ return candidateParam;
+ }
+
+ public static Integer parseApproveType(FlowElement userTask) {
+ return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE));
+ }
+
+ public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
+ Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
+ return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
+ }
+
+ public static String parseReturnTaskId(FlowElement flowElement) {
+ return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
+ }
+
+ public static Integer parseAssignStartUserHandlerType(FlowElement userTask) {
+ return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE));
+ }
+
+ public static Integer parseAssignEmptyHandlerType(FlowElement userTask) {
+ return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE));
+ }
+
+ public static List parseAssignEmptyHandlerUserIds(FlowElement userTask) {
+ return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ",");
+ }
+
+ public static String parseExtensionElement(FlowElement flowElement, String elementName) {
+ if (flowElement == null) {
+ return null;
+ }
+ ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
+ return element != null ? element.getElementText() : null;
+ }
+
+ public static Map parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) {
+ if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) {
+ return null;
+ }
+ FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
+ if (flowElement == null) {
+ return null;
+ }
+ List extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT);
+ if (CollUtil.isEmpty(extensionElements)) {
+ return null;
+ }
+ Map fieldsPermission = MapUtil.newHashMap();
+ extensionElements.forEach(element -> {
+ String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
+ String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
+ if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) {
+ fieldsPermission.put(field, permission);
+ }
+ });
+ return fieldsPermission;
+ }
+
+ public static Map parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) {
+ FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
+ if (flowElement == null) {
+ return null;
+ }
+ List extensionElements = flowElement.getExtensionElements().get(BUTTON_SETTING_ELEMENT);
+ if (CollUtil.isEmpty(extensionElements)) {
+ return null;
+ }
+ Map buttonSettings = MapUtil.newHashMap(16);
+ extensionElements.forEach(element -> {
+ String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
+ String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
+ String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
+ if (StrUtil.isNotEmpty(id)) {
+ BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting();
+ buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable)));
+ }
+ });
+ return buttonSettings;
}
/**
@@ -95,6 +191,12 @@ public class BpmnModelUtils {
return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent);
}
+ public static EndEvent getEndEvent(BpmnModel model) {
+ Process process = model.getMainProcess();
+ // 从 flowElementList 找 endEvent. TODO 多个 EndEvent 会有问题
+ return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent);
+ }
+
public static BpmnModel getBpmnModel(byte[] bpmnBytes) {
if (ArrayUtil.isEmpty(bpmnBytes)) {
return null;
@@ -334,4 +436,11 @@ public class BpmnModelUtils {
return userTaskList;
}
+ public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
+ if (boundaryEvent == null) {
+ return null;
+ }
+ ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
+ return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
+ }
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
index a8ee4e7f9..6456c943a 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
@@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
+import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.ExpressionManager;
@@ -16,6 +18,7 @@ import org.flowable.task.api.TaskInfo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Flowable 相关的工具方法
@@ -39,6 +42,16 @@ public class FlowableUtils {
return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID;
}
+ public static void execute(String tenantIdStr, Runnable runnable) {
+ if (ObjectUtil.isEmpty(tenantIdStr)
+ || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) {
+ runnable.run();
+ } else {
+ Long tenantId = Long.valueOf(tenantIdStr);
+ TenantUtils.execute(tenantId, runnable);
+ }
+ }
+
// ========== Execution 相关的工具方法 ==========
/**
@@ -78,7 +91,7 @@ public class FlowableUtils {
* @return 状态
*/
private static Integer getProcessInstanceStatus(Map processVariables) {
- return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
+ return (Integer) processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
}
/**
@@ -102,7 +115,7 @@ public class FlowableUtils {
* @return 过滤后的表单
*/
public static Map filterProcessInstanceFormVariable(Map processVariables) {
- processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
+ processVariables.remove(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
return processVariables;
}
@@ -115,7 +128,7 @@ public class FlowableUtils {
@SuppressWarnings("unchecked")
public static Map> getStartUserSelectAssignees(ProcessInstance processInstance) {
return (Map>) processInstance.getProcessVariables().get(
- BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
+ BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
}
// ========== Task 相关的工具方法 ==========
@@ -127,7 +140,7 @@ public class FlowableUtils {
* @return 状态
*/
public static Integer getTaskStatus(TaskInfo task) {
- return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
+ return (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS);
}
/**
@@ -137,7 +150,7 @@ public class FlowableUtils {
* @return 审批原因
*/
public static String getTaskReason(TaskInfo task) {
- return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON);
+ return (String) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_REASON);
}
/**
@@ -161,8 +174,8 @@ public class FlowableUtils {
* @return 过滤后的表单
*/
public static Map filterTaskFormVariable(Map taskLocalVariables) {
- taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS);
- taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON);
+ taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_STATUS);
+ taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_REASON);
return taskLocalVariables;
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
new file mode 100644
index 000000000..f9e74e981
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -0,0 +1,631 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.*;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
+import org.flowable.bpmn.BpmnAutoLayout;
+import org.flowable.bpmn.model.Process;
+import org.flowable.bpmn.model.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting;
+import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum.*;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum.USER;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP;
+import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum.REMINDER;
+import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER;
+import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
+import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
+
+/**
+ * 仿钉钉快搭模型相关的工具方法
+ *
+ * @author jason
+ */
+public class SimpleModelUtils {
+
+ /**
+ * 聚合网关节点 Id 后缀
+ */
+ public static final String JOIN_GATE_WAY_NODE_ID_SUFFIX = "_join";
+
+ /**
+ * 所有审批人同意的表达式
+ */
+ public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }";
+
+ /**
+ * 任一一名审批人同意的表达式
+ */
+ public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }";
+
+ /**
+ * 按通过比例完成表达式
+ */
+ public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}";
+
+ // TODO @yunai:注释需要完善下;
+
+ /**
+ * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善)
+ *
+ * @param processId 流程标识
+ * @param processName 流程名称
+ * @param simpleModelNode 仿钉钉流程设计模型数据结构
+ * @return Bpmn Model
+ */
+ public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
+ BpmnModel bpmnModel = new BpmnModel();
+ // 不加这个 解析 Message 会报 NPE 异常 .
+ bpmnModel.setTargetNamespace(BPMN2_NAMESPACE); // TODO @jason:待定:是不是搞个自定义的 namespace;
+ // TODO 芋艿:后续在 review
+
+ Process process = new Process();
+ process.setId(processId);
+ process.setName(processName);
+ process.setExecutable(Boolean.TRUE); // TODO @jason:这个是必须设置的么?
+ bpmnModel.addProcess(process);
+
+ // TODO 芋艿:这里可能纠结下,到底前端传递,还是后端创建出来。
+ // 目前前端的第一个节点是“发起人节点”。这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点
+ BpmSimpleModelNodeVO startNode = buildStartSimpleModelNode();
+ startNode.setChildNode(simpleModelNode);
+ // 从 前端模型数据结构 SimpleModel 构建 FlowNode 并添加到 Main Process
+ traverseNodeToBuildFlowNode(startNode, process);
+ // 找到 end event
+ EndEvent endEvent = (EndEvent) CollUtil.findOne(process.getFlowElements(), item -> item instanceof EndEvent);
+
+ // 构建并添加节点之间的连线 Sequence Flow
+ traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
+ // 自动布局
+ new BpmnAutoLayout(bpmnModel).execute();
+ return bpmnModel;
+ }
+
+ private static BpmSimpleModelNodeVO buildStartSimpleModelNode() {
+ BpmSimpleModelNodeVO startNode = new BpmSimpleModelNodeVO();
+ startNode.setId(START_EVENT_NODE_ID);
+ startNode.setName(START_EVENT_NODE_NAME);
+ startNode.setType(START_NODE.getType());
+ return startNode;
+ }
+
+ // TODO @芋艿:在优化下这个注释
+ private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
+ // 1.1 无效节点返回
+ if (!isValidNode(node)) {
+ return;
+ }
+ // 1.2 END_NODE 直接返回
+ BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
+ Assert.notNull(nodeType, "模型节点类型不支持");
+ if (nodeType == END_NODE) {
+ return;
+ }
+ // 2.1 情况一:普通节点
+ BpmSimpleModelNodeVO childNode = node.getChildNode();
+ if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) {
+ if (!isValidNode(childNode)) {
+ // 2.1.1 普通节点且无孩子节点。分两种情况
+ // a.结束节点 b. 条件分支的最后一个节点.与分支节点的孩子节点或聚合节点建立连线。
+ if (StrUtil.isNotEmpty(node.getAttachNodeId())) {
+ // 2.1.1.1 如果有附加节点. 需要先建立和附加节点的连线。再建立附加节点和目标节点的连线
+ List sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), targetNodeId);
+ sequenceFlows.forEach(process::addFlowElement);
+ } else {
+ SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), targetNodeId, null, null, null);
+ process.addFlowElement(sequenceFlow);
+ }
+ } else {
+ // 2.1.2 普通节点且有孩子节点。建立连线
+ if (StrUtil.isNotEmpty(node.getAttachNodeId())) {
+ // 2.1.1.2 如果有附加节点. 需要先建立和附加节点的连线。再建立附加节点和目标节点的连线
+ List sequenceFlows = buildAttachNodeSequenceFlow(node.getId(), node.getAttachNodeId(), childNode.getId());
+ sequenceFlows.forEach(process::addFlowElement);
+ } else {
+ SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), childNode.getId(), null, null, null);
+ process.addFlowElement(sequenceFlow);
+ }
+ // 递归调用后续节点
+ traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
+ }
+ } else {
+ // 2.2 情况二:分支节点
+ List conditionNodes = node.getConditionNodes();
+ Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空");
+ // 分支终点节点 Id
+ String branchEndNodeId = null;
+ if (nodeType == CONDITION_BRANCH_NODE) { // 条件分支
+ // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点Id
+ branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
+ } else if (nodeType == PARALLEL_BRANCH_NODE) { // 并行分支
+ // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。
+ branchEndNodeId = node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX;
+ }
+ // TODO 包容网关待实现
+ Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空");
+ // 3.1 遍历分支节点. 如下情况:
+ // 分支1、A->B->C->D->E 和 分支2、A->D->E。 A为分支节点, D为A孩子节点
+ for (BpmSimpleModelNodeVO item : conditionNodes) {
+ // TODO @jason:条件分支的情况下,需要分 item 搞的条件,和 conditionNodes 搞的条件
+ // @芋艿 这个是啥意思。 这里的 item 的节点类型为 BpmSimpleModelNodeType.CONDITION_NODE 类型,没有对应的 bpmn 的节点。 仅仅用于构建条件表达式。
+ Assert.isTrue(Objects.equals(item.getType(), CONDITION_NODE.getType()), "条件节点类型不符合");
+ // 构建表达式,可以为空. 并行分支为空
+ String conditionExpression = buildConditionExpression(item);
+ BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode();
+ // 3.2 分支有后续节点, 分支1: A->B->C->D
+ if (isValidNode(nextNodeOnCondition)) {
+ // 3.2.1 建立 A->B
+ SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), nextNodeOnCondition.getId(),
+ item.getId(), item.getName(), conditionExpression);
+ process.addFlowElement(sequenceFlow);
+ // 3.2.2 递归调用后续节点连线。 建立 B->C->D 的连线
+ traverseNodeToBuildSequenceFlow(process, nextNodeOnCondition, branchEndNodeId);
+ } else {
+ // 3.3 分支无后续节点 建立 A->D
+ SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), branchEndNodeId,
+ item.getId(), item.getName(), conditionExpression);
+ process.addFlowElement(sequenceFlow);
+ }
+ }
+ // 如果是并行分支。由于是程序创建的聚合网关。需要手工创建聚合网关和下一个节点的连线
+ if (nodeType == PARALLEL_BRANCH_NODE) {
+ String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
+ SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId, null, null, null);
+ process.addFlowElement(sequenceFlow);
+ }
+ // 4.递归调用后续节点 继续递归建立 D->E 的连线
+ traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
+ }
+ }
+
+ /**
+ * 构建有附加节点的连线
+ *
+ * @param nodeId 当前节点 Id
+ * @param attachNodeId 附属节点 Id
+ * @param targetNodeId 目标节点 Id
+ */
+ private static List buildAttachNodeSequenceFlow(String nodeId, String attachNodeId, String targetNodeId) {
+ SequenceFlow sequenceFlow = buildBpmnSequenceFlow(nodeId, attachNodeId, null, null, null);
+ SequenceFlow attachSequenceFlow = buildBpmnSequenceFlow(attachNodeId, targetNodeId, null, null, null);
+ return CollUtil.newArrayList(sequenceFlow, attachSequenceFlow);
+ }
+
+ /**
+ * 构造条件表达式
+ *
+ * @param conditionNode 条件节点
+ */
+ public static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) {
+ BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionNode.getConditionType());
+ String conditionExpression = null;
+ if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) {
+ conditionExpression = conditionNode.getConditionExpression();
+ } else if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) {
+ ConditionGroups conditionGroups = conditionNode.getConditionGroups();
+ if (conditionGroups != null && CollUtil.isNotEmpty(conditionGroups.getConditions())) {
+ List strConditionGroups = conditionGroups.getConditions().stream().map(item -> {
+ if (CollUtil.isNotEmpty(item.getRules())) {
+ Boolean and = item.getAnd();
+ List list = CollectionUtils.convertList(item.getRules(), (rule) -> {
+ // 如果非数值类型加引号
+ String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() : "\"" + rule.getRightSide() + "\"";
+ return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide);
+ });
+ return "(" + CollUtil.join(list, and ? " && " : " || ") + ")";
+ } else {
+ return "";
+ }
+ }).toList();
+ conditionExpression = String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || "));
+ }
+ }
+ // TODO 待增加其它类型
+ return conditionExpression;
+ }
+
+ private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, String seqFlowId, String seqName, String conditionExpression) {
+ Assert.notEmpty(sourceId, "sourceId 不能为空");
+ Assert.notEmpty(targetId, "targetId 不能为空");
+ // TODO @jason:如果 seqFlowId 不存在的时候,是不是要生成一个默认的 seqFlowId? @芋艿: 貌似不需要,Flowable 会默认生成
+ // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧
+ SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
+ if (StrUtil.isNotEmpty(conditionExpression)) {
+ sequenceFlow.setConditionExpression(conditionExpression);
+ }
+ if (StrUtil.isNotEmpty(seqFlowId)) {
+ sequenceFlow.setId(seqFlowId);
+ }
+ if (StrUtil.isNotEmpty(seqName)) {
+ sequenceFlow.setName(seqName);
+ }
+ return sequenceFlow;
+ }
+
+ // TODO @芋艿 改成了 traverseNodeToBuildFlowNode, 连线的叫 traverseNodeToBuildSequenceFlow
+ private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) {
+ // 判断是否有效节点
+ if (!isValidNode(node)) {
+ return;
+ }
+ BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
+ Assert.notNull(nodeType, "模型节点类型不支持");
+
+ List flowElements = buildFlowNode(node, nodeType);
+ flowElements.forEach(process::addFlowElement);
+
+ // 如果不是网关类型的接口, 并且chileNode为空退出
+ // 如果是“分支”节点,则递归处理条件
+ if (BpmSimpleModelNodeType.isBranchNode(node.getType())
+ && ArrayUtil.isNotEmpty(node.getConditionNodes())) {
+ node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process));
+ }
+
+ // 如果有“子”节点,则递归处理子节点
+ traverseNodeToBuildFlowNode(node.getChildNode(), process);
+ }
+
+ public static boolean isValidNode(BpmSimpleModelNodeVO node) {
+ return node != null && node.getId() != null;
+ }
+
+ public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) {
+ return APPROVE_NODE.getType().equals(node.getType()) && SEQUENTIAL.getMethod().equals(node.getApproveMethod());
+ }
+
+ private static List buildFlowNode(BpmSimpleModelNodeVO node, BpmSimpleModelNodeType nodeType) {
+ List list = new ArrayList<>();
+ switch (nodeType) {
+ case START_NODE: { // 开始节点
+ StartEvent startEvent = convertStartNode(node);
+ list.add(startEvent);
+ break;
+ }
+ case END_NODE: { // 结束节点
+ EndEvent endEvent = convertEndNode(node);
+ list.add(endEvent);
+ break;
+ }
+ case START_USER_NODE: { // 发起人节点
+ UserTask userTask = convertStartUserNode(node);
+ list.add(userTask);
+ break;
+ }
+ case APPROVE_NODE: { // 审批节点
+ List flowElements = convertApproveNode(node);
+ list.addAll(flowElements);
+ break;
+ }
+ case COPY_NODE: { // 抄送节点
+ ServiceTask serviceTask = convertCopyNode(node);
+ list.add(serviceTask);
+ break;
+ }
+ case CONDITION_BRANCH_NODE: {
+ ExclusiveGateway exclusiveGateway = convertConditionBranchNode(node);
+ list.add(exclusiveGateway);
+ break;
+ }
+ case PARALLEL_BRANCH_NODE: {
+ List parallelGateways = convertParallelBranchNode(node);
+ list.addAll(parallelGateways);
+ break;
+ }
+
+ case INCLUSIVE_BRANCH_NODE: {
+ // TODO jason 待实现
+ break;
+ }
+ default: {
+ // TODO 其它节点类型的实现
+ }
+ }
+ return list;
+ }
+
+ private static UserTask convertStartUserNode(BpmSimpleModelNodeVO node) {
+ return buildBpmnStartUserTask(node);
+ }
+
+ private static List convertApproveNode(BpmSimpleModelNodeVO node) {
+ List flowElements = new ArrayList<>();
+ UserTask userTask = buildBpmnUserTask(node);
+ flowElements.add(userTask);
+
+ // 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
+ if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
+ BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler());
+ flowElements.add(boundaryEvent);
+ }
+ return flowElements;
+ }
+
+ /**
+ * 添加 UserTask 用户的审批超时 BoundaryEvent 事件
+ *
+ * @param userTask 审批任务
+ * @param timeoutHandler 超时处理器
+ * @return BoundaryEvent 超时事件
+ */
+ private static BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, TimeoutHandler timeoutHandler) {
+ // 1.1 定时器边界事件
+ BoundaryEvent boundaryEvent = new BoundaryEvent();
+ boundaryEvent.setId("Event-" + IdUtil.fastUUID());
+ boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断
+ boundaryEvent.setAttachedToRef(userTask);
+ // 1.2 定义超时时间、最大提醒次数
+ TimerEventDefinition eventDefinition = new TimerEventDefinition();
+ eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration());
+ if (Objects.equals(REMINDER.getType(), timeoutHandler.getType()) &&
+ timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) {
+ eventDefinition.setTimeCycle(String.format("R%d/%s",
+ timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration()));
+ }
+ boundaryEvent.addEventDefinition(eventDefinition);
+
+ // 2.1 添加定时器边界事件类型
+ addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType().toString());
+ // 2.2 添加超时执行动作元素
+ addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, StrUtil.toStringOrNull(timeoutHandler.getType()));
+ return boundaryEvent;
+ }
+
+ private static List convertParallelBranchNode(BpmSimpleModelNodeVO node) {
+ ParallelGateway parallelGateway = new ParallelGateway();
+ parallelGateway.setId(node.getId());
+ // TODO @jason:setName
+
+ // TODO @芋艿 + jason:合并网关;是不是要有条件啥的。微信讨论
+ // 并行聚合网关有程序创建。前端不需要传入
+ ParallelGateway joinParallelGateway = new ParallelGateway();
+ joinParallelGateway.setId(node.getId() + JOIN_GATE_WAY_NODE_ID_SUFFIX);
+ return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
+ }
+
+ private static ServiceTask convertCopyNode(BpmSimpleModelNodeVO node) {
+ ServiceTask serviceTask = new ServiceTask();
+ serviceTask.setId(node.getId());
+ serviceTask.setName(node.getName());
+ serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
+ serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}");
+
+ // 添加抄送候选人元素
+ addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask);
+ // 添加表单字段权限属性元素
+ addFormFieldsPermission(node.getFieldsPermission(), serviceTask);
+ return serviceTask;
+ }
+
+ /**
+ * 给节点添加候选人元素
+ */
+ private static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
+ addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
+ candidateStrategy == null ? null : candidateStrategy.toString());
+ addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
+ }
+
+ private static ExclusiveGateway convertConditionBranchNode(BpmSimpleModelNodeVO node) {
+ Assert.notEmpty(node.getConditionNodes(), "条件分支节点不能为空");
+ ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
+ exclusiveGateway.setId(node.getId());
+ // 寻找默认的序列流
+ BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
+ item -> BooleanUtil.isTrue(item.getDefaultFlow()));
+ if (defaultSeqFlow != null) {
+ exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
+ }
+ return exclusiveGateway;
+ }
+
+ private static InclusiveGateway convertInclusiveBranchNode(BpmSimpleModelNodeVO node, Boolean isFork) {
+ InclusiveGateway inclusiveGateway = new InclusiveGateway();
+ inclusiveGateway.setId(node.getId());
+ // TODO @jason:这里是不是 setName 哈;
+
+ // TODO @芋艿 + jason:是不是搞个合并网关;这里微信讨论下,有点奇怪;
+ // @芋艿 isFork 为 false 就是合并网关。由前端传入。这个前端暂时还未实现
+ if (isFork) {
+ Assert.notEmpty(node.getConditionNodes(), "条件节点不能为空");
+ // 寻找默认的序列流
+ BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(
+ node.getConditionNodes(), item -> BooleanUtil.isTrue(item.getDefaultFlow()));
+ if (defaultSeqFlow != null) {
+ inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
+ }
+ }
+ return inclusiveGateway;
+ }
+
+ private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
+ UserTask userTask = new UserTask();
+ userTask.setId(node.getId());
+ userTask.setName(node.getName());
+
+ // 如果不是审批人节点,则直接返回
+ addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, StrUtil.toStringOrNull(node.getApproveType()));
+ if (ObjectUtil.notEqual(node.getApproveType(), USER.getType())) {
+ return userTask;
+ }
+
+ // 添加候选人元素
+ addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask);
+ // 添加表单字段权限属性元素
+ addFormFieldsPermission(node.getFieldsPermission(), userTask);
+ // 添加操作按钮配置属性元素
+ addButtonsSetting(node.getButtonsSetting(), userTask);
+ // 处理多实例(审批方式)
+ processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask);
+ // 添加任务被拒绝的处理元素
+ addTaskRejectElements(node.getRejectHandler(), userTask);
+ // 添加用户任务的审批人与发起人相同时的处理元素
+ addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask);
+ // 添加用户任务的空处理元素
+ addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask);
+ // 设置审批任务的截止时间
+ if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
+ userTask.setDueDate(node.getTimeoutHandler().getTimeDuration());
+ }
+ return userTask;
+ }
+
+ private static void addTaskRejectElements(RejectHandler rejectHandler, UserTask userTask) {
+ if (rejectHandler == null) {
+ return;
+ }
+ addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
+ addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
+ }
+
+ private static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) {
+ if (assignStartUserHandlerType == null) {
+ return;
+ }
+ addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
+ }
+
+ private static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) {
+ if (emptyHandler == null) {
+ return;
+ }
+ addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType()));
+ addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds()));
+ }
+
+ private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
+ BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
+ // TODO @jason:这种枚举,最终不要去掉哈 BpmUserTaskApproveMethodEnum。因为容易不经意重叠
+ if (approveMethodEnum == null || approveMethodEnum == RANDOM) {
+ return;
+ }
+ // 添加审批方式的扩展属性
+ addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD,
+ approveMethod == null ? null : approveMethod.toString());
+ MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
+ // 设置 collectionVariable。本系统用不到。仅仅为了 Flowable 校验不报错。
+ multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
+ if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) {
+ multiInstanceCharacteristics.setCompletionCondition(ANY_OF_APPROVE_COMPLETE_EXPRESSION);
+ multiInstanceCharacteristics.setSequential(false);
+ userTask.setLoopCharacteristics(multiInstanceCharacteristics);
+ } else if (approveMethodEnum == SEQUENTIAL) {
+ multiInstanceCharacteristics.setCompletionCondition(ALL_APPROVE_COMPLETE_EXPRESSION);
+ multiInstanceCharacteristics.setSequential(true);
+ multiInstanceCharacteristics.setLoopCardinality("1");
+ userTask.setLoopCharacteristics(multiInstanceCharacteristics);
+ } else if (approveMethodEnum == RATIO) {
+ Assert.notNull(approveRatio, "通过比例不能为空");
+ multiInstanceCharacteristics.setCompletionCondition(
+ String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approveRatio / (double) 100)));
+ multiInstanceCharacteristics.setSequential(false);
+ }
+ userTask.setLoopCharacteristics(multiInstanceCharacteristics);
+ }
+
+ /**
+ * 给节点添加操作按钮设置元素
+ */
+ private static void addButtonsSetting(List buttonsSetting, UserTask userTask) {
+ if (CollUtil.isNotEmpty(buttonsSetting)) {
+ List