bpm大升级
This commit is contained in:
parent
7633239213
commit
fe72d796e2
@ -93,7 +93,6 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
|||||||
if (loginUser == null) {
|
if (loginUser == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
System.out.println("getExpression..............1,table:"+tableName);
|
|
||||||
|
|
||||||
// 只有管理员类型的用户,才进行数据权限的处理
|
// 只有管理员类型的用户,才进行数据权限的处理
|
||||||
if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
|
if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO")
|
||||||
|
@Data
|
||||||
|
public class BpmModeUpdateBpmnReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||||
|
@NotEmpty(message = "流程编号不能为空")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotEmpty(message = "BPMN XML 不能为空")
|
||||||
|
private String bpmnXml;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||||
|
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
|
||||||
|
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.URL;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BPM 流程 MetaInfo Response DTO
|
||||||
|
* 主要用于 { Model#setMetaInfo(String)} 的存储
|
||||||
|
*
|
||||||
|
* 最终,它的字段和
|
||||||
|
* {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO}
|
||||||
|
* 是一致的
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BpmModelMetaInfoVO {
|
||||||
|
|
||||||
|
@Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
|
||||||
|
@NotEmpty(message = "流程图标不能为空")
|
||||||
|
@URL(message = "流程图标格式不正确")
|
||||||
|
private String icon;
|
||||||
|
|
||||||
|
@Schema(description = "流程描述", example = "我是描述")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||||
|
@InEnum(BpmModelTypeEnum.class)
|
||||||
|
@NotNull(message = "流程类型不能为空")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||||
|
@InEnum(BpmModelFormTypeEnum.class)
|
||||||
|
@NotNull(message = "表单类型不能为空")
|
||||||
|
private Integer formType;
|
||||||
|
@Schema(description = "表单编号", example = "1024")
|
||||||
|
private Long formId; // formType 为 NORMAL 使用,必须非空
|
||||||
|
@Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
|
||||||
|
private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
|
||||||
|
@Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
|
||||||
|
private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空
|
||||||
|
|
||||||
|
@Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||||
|
@NotNull(message = "是否可见不能为空1")
|
||||||
|
private Boolean visible;
|
||||||
|
|
||||||
|
@Schema(description = "可发起用户编号数组", example = "[1,2,3]")
|
||||||
|
private List<Long> startUserIds;
|
||||||
|
|
||||||
|
@Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]")
|
||||||
|
@NotEmpty(message = "可管理用户编号数组不能为空")
|
||||||
|
private List<Long> managerUserIds;
|
||||||
|
|
||||||
|
@Schema(description = "排序", example = "1")
|
||||||
|
private Long sort; // 创建时,后端自动生成
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 流程模型的保存 Request VO")
|
||||||
|
@Data
|
||||||
|
public class BpmModelSaveReqVO extends BpmModelMetaInfoVO {
|
||||||
|
|
||||||
|
@Schema(description = "编号", example = "1024")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao")
|
||||||
|
@NotEmpty(message = "流程标识不能为空")
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
@Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
|
||||||
|
@NotEmpty(message = "流程名称不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "流程分类", example = "1")
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
@Schema(description = "BPMN XML")
|
||||||
|
private String bpmnXml;
|
||||||
|
|
||||||
|
@Schema(description = "仿钉钉流程设计模型对象")
|
||||||
|
@Valid
|
||||||
|
private BpmSimpleModelNodeVO simpleModel;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,212 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||||
|
import cn.iocoder.yudao.module.bpm.enums.definition.*;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class BpmSimpleModelNodeVO {
|
||||||
|
|
||||||
|
@Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1")
|
||||||
|
@NotEmpty(message = "模型节点编号不能为空")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@NotNull(message = "模型节点类型不能为空")
|
||||||
|
@InEnum(BpmSimpleModelNodeType.class)
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "模型节点名称", example = "领导审批")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Schema(description = "节点展示内容", example = "指定成员: 芋道源码")
|
||||||
|
private String showText;
|
||||||
|
|
||||||
|
@Schema(description = "子节点")
|
||||||
|
private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个
|
||||||
|
|
||||||
|
@Schema(description = "条件节点")
|
||||||
|
private List<BpmSimpleModelNodeVO> conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用
|
||||||
|
|
||||||
|
@Schema(description = "条件类型", example = "1")
|
||||||
|
@InEnum(BpmSimpleModeConditionType.class)
|
||||||
|
private Integer conditionType; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
|
||||||
|
|
||||||
|
@Schema(description = "条件表达式", example = "${day>3}")
|
||||||
|
private String conditionExpression; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
|
||||||
|
|
||||||
|
@Schema(description = "是否默认条件", example = "true")
|
||||||
|
private Boolean defaultFlow; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
|
||||||
|
/**
|
||||||
|
* 条件组
|
||||||
|
*/
|
||||||
|
private ConditionGroups conditionGroups; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
|
||||||
|
|
||||||
|
@Schema(description = "候选人策略", example = "30")
|
||||||
|
@InEnum(BpmTaskCandidateStrategyEnum.class)
|
||||||
|
private Integer candidateStrategy; // 用于审批,抄送节点
|
||||||
|
|
||||||
|
@Schema(description = "候选人参数")
|
||||||
|
private String candidateParam; // 用于审批,抄送节点
|
||||||
|
|
||||||
|
@Schema(description = "审批节点类型", example = "1")
|
||||||
|
@InEnum(BpmUserTaskApproveTypeEnum.class)
|
||||||
|
private Integer approveType; // 用于审批节点
|
||||||
|
|
||||||
|
@Schema(description = "多人审批方式", example = "1")
|
||||||
|
@InEnum(BpmUserTaskApproveMethodEnum.class)
|
||||||
|
private Integer approveMethod; // 用于审批节点
|
||||||
|
|
||||||
|
@Schema(description = "通过比例", example = "100")
|
||||||
|
private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置
|
||||||
|
|
||||||
|
@Schema(description = "表单权限", example = "[]")
|
||||||
|
private List<Map<String, String>> fieldsPermission;
|
||||||
|
|
||||||
|
@Schema(description = "操作按钮设置", example = "[]")
|
||||||
|
private List<OperationButtonSetting> buttonsSetting; // 用于审批节点
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批节点拒绝处理
|
||||||
|
*/
|
||||||
|
private RejectHandler rejectHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批节点超时处理
|
||||||
|
*/
|
||||||
|
private TimeoutHandler timeoutHandler;
|
||||||
|
|
||||||
|
@Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1")
|
||||||
|
@InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class)
|
||||||
|
private Integer assignStartUserHandlerType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空处理策略
|
||||||
|
*/
|
||||||
|
private AssignEmptyHandler assignEmptyHandler;
|
||||||
|
|
||||||
|
@Schema(description = "审批节点拒绝处理策略")
|
||||||
|
@Data
|
||||||
|
public static class RejectHandler {
|
||||||
|
|
||||||
|
@Schema(description = "拒绝处理类型", example = "1")
|
||||||
|
@InEnum(BpmUserTaskRejectHandlerType.class)
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1")
|
||||||
|
private String returnNodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "审批节点超时处理策略")
|
||||||
|
@Valid
|
||||||
|
@Data
|
||||||
|
public static class TimeoutHandler {
|
||||||
|
|
||||||
|
@Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
|
||||||
|
@NotNull(message = "是否开启超时处理不能为空")
|
||||||
|
private Boolean enable;
|
||||||
|
|
||||||
|
@Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@NotNull(message = "任务超时未处理的行为不能为空")
|
||||||
|
@InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class)
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H")
|
||||||
|
@NotEmpty(message = "超时时间不能为空")
|
||||||
|
private String timeDuration;
|
||||||
|
|
||||||
|
@Schema(description = "最大提醒次数", example = "1")
|
||||||
|
private Integer maxRemindCount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "空处理策略")
|
||||||
|
@Data
|
||||||
|
@Valid
|
||||||
|
public static class AssignEmptyHandler {
|
||||||
|
|
||||||
|
@Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@NotNull(message = "空处理类型不能为空")
|
||||||
|
@InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class)
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@Schema(description = "指定人员审批的用户编号数组", example = "1")
|
||||||
|
private List<Long> userIds;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "操作按钮设置")
|
||||||
|
@Data
|
||||||
|
@Valid
|
||||||
|
public static class OperationButtonSetting {
|
||||||
|
|
||||||
|
// TODO @jason:是不是按钮的标识?id 会和数据库的 id 自增有点模糊,key 标识会更合理一点点哈。
|
||||||
|
@Schema(description = "按钮 Id", example = "1")
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Schema(description = "显示名称", example = "审批")
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
|
@Schema(description = "是否启用", example = "true")
|
||||||
|
private Boolean enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "条件组")
|
||||||
|
@Data
|
||||||
|
@Valid
|
||||||
|
public static class ConditionGroups {
|
||||||
|
|
||||||
|
@Schema(description = "条件组下的条件关系是否为与关系", example = "true")
|
||||||
|
@NotNull(message = "条件关系不能为空")
|
||||||
|
private Boolean and;
|
||||||
|
|
||||||
|
@Schema(description = "条件组下的条件", example = "[]")
|
||||||
|
@NotEmpty(message = "条件不能为空")
|
||||||
|
private List<Condition> conditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "条件")
|
||||||
|
@Data
|
||||||
|
@Valid
|
||||||
|
public static class Condition {
|
||||||
|
|
||||||
|
@Schema(description = "条件下的规则关系是否为与关系", example = "true")
|
||||||
|
@NotNull(message = "规则关系不能为空")
|
||||||
|
private Boolean and;
|
||||||
|
|
||||||
|
@Schema(description = "条件下的规则", example = "[]")
|
||||||
|
@NotEmpty(message = "规则不能为空")
|
||||||
|
private List<ConditionRule> rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "条件规则")
|
||||||
|
@Data
|
||||||
|
@Valid
|
||||||
|
public static class ConditionRule {
|
||||||
|
|
||||||
|
@Schema(description = "运行符号", example = "==")
|
||||||
|
@NotEmpty(message = "运行符号不能为空")
|
||||||
|
private String opCode;
|
||||||
|
|
||||||
|
@Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId")
|
||||||
|
@NotEmpty(message = "运算符左边的值不能为空")
|
||||||
|
private String leftSide;
|
||||||
|
|
||||||
|
@Schema(description = "运算符右边的值", example = "1")
|
||||||
|
@NotEmpty(message = "运算符右边的值不能为空")
|
||||||
|
private String rightSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
// TODO @jason:需要考虑,如果某个节点的配置不正确,需要有提示;具体怎么实现,可以讨论下;
|
||||||
|
@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO")
|
||||||
|
@Data
|
||||||
|
public class BpmSimpleModelUpdateReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
@NotEmpty(message = "流程模型编号不能为空")
|
||||||
|
private String id; // 对应 Flowable act_re_model 表 ID_ 字段
|
||||||
|
|
||||||
|
@Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "仿钉钉流程设计模型对象不能为空")
|
||||||
|
@Valid
|
||||||
|
private BpmSimpleModelNodeVO simpleModel;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.AssertTrue;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 审批详情 Request VO")
|
||||||
|
@Data
|
||||||
|
public class BpmApprovalDetailReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "流程定义的编号", example = "1024")
|
||||||
|
private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID
|
||||||
|
|
||||||
|
@Schema(description = "流程变量")
|
||||||
|
private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测
|
||||||
|
|
||||||
|
@Schema(description = "流程实例的编号", example = "1024")
|
||||||
|
private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID
|
||||||
|
|
||||||
|
// TODO @芋艿:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。
|
||||||
|
@Schema(description = "流程活动编号", example = "StartUserNode")
|
||||||
|
private String activityId; // 用于获取表单权限。1)发起流程时,传“发起人节点” activityId 可获取发起人的表单权限;2)从抄送列表界面进来时,传抄送的 activityId 可获取抄送人的表单权限;
|
||||||
|
|
||||||
|
@Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
|
||||||
|
private String taskId; // 用于获取表单权限。1)从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限
|
||||||
|
|
||||||
|
@AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isValidProcessParam() {
|
||||||
|
return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
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 cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
protected DeptApi deptApi;
|
||||||
|
@Resource
|
||||||
|
protected AdminUserApi adminUserApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得指定层级的部门负责人,只有第 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<Long> getMultiLevelDeptLeaderIds(List<Long> deptIds, Integer level) {
|
||||||
|
Assert.isTrue(level > 0, "level 必须大于 0");
|
||||||
|
if (CollUtil.isEmpty(deptIds)) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
Set<Long> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取发起人的部门
|
||||||
|
*
|
||||||
|
* @param startUserId 发起人 Id
|
||||||
|
*/
|
||||||
|
protected DeptRespDTO getStartUserDept(Long startUserId) {
|
||||||
|
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
|
||||||
|
if (startUser.getDeptId() == null) { // 找不到部门
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return deptApi.getDept(startUser.getDeptId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
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 org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
|
||||||
|
|
||||||
|
@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, "参数格式不匹配");
|
||||||
|
List<Long> deptIds = StrUtils.splitToLong(params[0], ",");
|
||||||
|
int level = Integer.parseInt(params[1]);
|
||||||
|
// 校验部门存在
|
||||||
|
deptApi.validateDeptList(deptIds);
|
||||||
|
Assert.isTrue(level > 0, "部门层级必须大于 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers(String param) {
|
||||||
|
String[] params = param.split("\\|");
|
||||||
|
List<Long> deptIds = StrUtils.splitToLong(params[0], ",");
|
||||||
|
int level = Integer.parseInt(params[1]);
|
||||||
|
return super.getMultiLevelDeptLeaderIds(deptIds, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
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.candidate.expression.BpmTaskAssignLeaderExpression;
|
||||||
|
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 org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author kyle
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DeptApi deptApi;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy // 延迟加载,避免循环依赖
|
||||||
|
private BpmTaskAssignLeaderExpression TaskAssignLeader;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.DEPT_LEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
Set<Long> deptIds = StrUtils.splitToLongSet(param);
|
||||||
|
deptApi.validateDeptList(deptIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers(String param) {
|
||||||
|
Set<Long> deptIds = StrUtils.splitToLongSet(param);
|
||||||
|
List<DeptRespDTO> depts = deptApi.getDeptList(deptIds);
|
||||||
|
return convertSet(depts, DeptRespDTO::getLeaderUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers2(DelegateExecution execution, String param) {
|
||||||
|
Set<Long> userid = TaskAssignLeader.calculateUsers(execution, Integer.parseInt(param));
|
||||||
|
return userid;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersActivity(Long startUserId,String param) {
|
||||||
|
Set<Long> deptIds = StrUtils.splitToLongSet(param);
|
||||||
|
List<DeptRespDTO> depts = deptApi.getDeptList(deptIds);
|
||||||
|
return convertSet(depts, DeptRespDTO::getLeaderUserId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
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 cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门的成员 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author kyle
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DeptApi deptApi;
|
||||||
|
@Resource
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.DEPT_MEMBER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
Set<Long> deptIds = StrUtils.splitToLongSet(param);
|
||||||
|
deptApi.validateDeptList(deptIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers(String param) {
|
||||||
|
Set<Long> deptIds = StrUtils.splitToLongSet(param);
|
||||||
|
List<AdminUserRespDTO> users = adminUserApi.getUserListByDeptIds(deptIds);
|
||||||
|
return convertSet(users, AdminUserRespDTO::getId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
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.dto.DeptRespDTO;
|
||||||
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.hutool.core.collection.ListUtil.toList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy
|
||||||
|
private BpmProcessInstanceService processInstanceService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
int level = Integer.parseInt(param); // 参数是部门的层级
|
||||||
|
Assert.isTrue(level > 0, "部门的层级必须大于 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||||
|
int level = Integer.parseInt(param); // 参数是部门的层级
|
||||||
|
// 获得流程发起人
|
||||||
|
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
|
||||||
|
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
|
||||||
|
// 获取发起人的 multi 部门负责人
|
||||||
|
DeptRespDTO dept = super.getStartUserDept(startUserId);
|
||||||
|
if (dept == null) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
|
||||||
|
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
|
||||||
|
int level = Integer.parseInt(param); // 参数是部门的层级
|
||||||
|
DeptRespDTO dept = super.getStartUserDept(startUserId);
|
||||||
|
if (dept == null) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
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.dto.DeptRespDTO;
|
||||||
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy // 避免循环依赖
|
||||||
|
private BpmProcessInstanceService processInstanceService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
// 参数是部门的层级
|
||||||
|
Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||||
|
// 获得流程发起人
|
||||||
|
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
|
||||||
|
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
|
||||||
|
// 获取发起人的部门负责人
|
||||||
|
return getStartUserDeptLeader(startUserId, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
|
||||||
|
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
|
||||||
|
// 获取发起人的部门负责人
|
||||||
|
return getStartUserDeptLeader(startUserId, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Long> getStartUserDeptLeader(Long startUserId, String param) {
|
||||||
|
int level = Integer.parseInt(param); // 参数是部门的层级
|
||||||
|
DeptRespDTO dept = super.getStartUserDept(startUserId);
|
||||||
|
if (dept == null) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
Long deptLeaderId = super.getAssignLevelDeptLeaderId(dept, level);
|
||||||
|
return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
|
||||||
|
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 com.google.common.collect.Sets;
|
||||||
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.bpmn.model.ServiceTask;
|
||||||
|
import org.flowable.bpmn.model.Task;
|
||||||
|
import org.flowable.bpmn.model.UserTask;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy // 延迟加载,避免循环依赖
|
||||||
|
private BpmProcessInstanceService processInstanceService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isParamRequired() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||||
|
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
|
||||||
|
Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
|
||||||
|
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
|
||||||
|
Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空",
|
||||||
|
execution.getProcessInstanceId());
|
||||||
|
// 获得审批人
|
||||||
|
List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
|
||||||
|
return new LinkedHashSet<>(assignees);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
|
||||||
|
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
|
||||||
|
if (processVariables == null) {
|
||||||
|
return Sets.newLinkedHashSet();
|
||||||
|
}
|
||||||
|
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables);
|
||||||
|
if (startUserSelectAssignees == null) {
|
||||||
|
return Sets.newLinkedHashSet();
|
||||||
|
}
|
||||||
|
// 获得审批人
|
||||||
|
List<Long> assignees = startUserSelectAssignees.get(activityId);
|
||||||
|
return new LinkedHashSet<>(assignees);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得发起人自选审批人或抄送人的 Task 列表
|
||||||
|
*
|
||||||
|
* @param bpmnModel BPMN 模型
|
||||||
|
* @return Task 列表
|
||||||
|
*/
|
||||||
|
public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) {
|
||||||
|
if (bpmnModel == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<Task> tasks = new ArrayList<>();
|
||||||
|
tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class));
|
||||||
|
tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class));
|
||||||
|
if (CollUtil.isEmpty(tasks)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task),
|
||||||
|
BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
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.candidate.strategy.dept.AbstractBpmTaskCandidateDeptLeaderStrategy;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
|
||||||
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单内部门负责人 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateFormDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.FORM_DEPT_LEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
// 参数格式: | 分隔:1)左边为表单内部门字段。2)右边为部门层级
|
||||||
|
String[] params = param.split("\\|");
|
||||||
|
Assert.isTrue(params.length == 2, "参数格式不匹配");
|
||||||
|
Assert.notEmpty(param, "表单内部门字段不能为空");
|
||||||
|
int level = Integer.parseInt(params[1]);
|
||||||
|
Assert.isTrue(level > 0, "部门层级必须大于 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||||
|
String[] params = param.split("\\|");
|
||||||
|
Object result = execution.getVariable(params[0]);
|
||||||
|
int level = Integer.parseInt(params[1]);
|
||||||
|
return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
|
||||||
|
String param, Long startUserId, String processDefinitionId,
|
||||||
|
Map<String, Object> processVariables) {
|
||||||
|
String[] params = param.split("\\|");
|
||||||
|
Object result = processVariables == null ? null : processVariables.get(params[0]);
|
||||||
|
int level = Integer.parseInt(params[1]);
|
||||||
|
return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
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.candidate.strategy.user.BpmTaskCandidateUserStrategy;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
|
||||||
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单内用户字段 {@link BpmTaskCandidateUserStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.FORM_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
Assert.notEmpty(param, "表单内用户字段不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||||
|
Object result = execution.getVariable(param);
|
||||||
|
return Convert.toSet(Long.class, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
|
||||||
|
String param, Long startUserId, String processDefinitionId,
|
||||||
|
Map<String, Object> processVariables) {
|
||||||
|
Object result = processVariables == null ? null : processVariables.get(param);
|
||||||
|
return Convert.toSet(Long.class, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
|
||||||
|
|
||||||
|
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 org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.bpmn.model.FlowElement;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批人为空 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author kyle
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy // 延迟加载,避免循环依赖
|
||||||
|
private BpmProcessDefinitionService processDefinitionService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||||
|
return getCandidateUsers(execution.getProcessDefinitionId(), execution.getCurrentFlowElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
|
||||||
|
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
|
||||||
|
FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
|
||||||
|
return getCandidateUsers(processDefinitionId, flowElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Long> getCandidateUsers(String processDefinitionId, FlowElement flowElement) {
|
||||||
|
// 情况一:指定人员审批
|
||||||
|
Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(flowElement);
|
||||||
|
if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) {
|
||||||
|
return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(flowElement));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况二:流程管理员
|
||||||
|
if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) {
|
||||||
|
BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processDefinitionId);
|
||||||
|
Assert.notNull(processDefinition, "流程定义({})不存在", processDefinitionId);
|
||||||
|
return new HashSet<>(processDefinition.getManagerUserIds());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 都不满足,还是返回空
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
|
||||||
|
|
||||||
|
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 com.google.common.collect.Sets;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.common.engine.api.FlowableException;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程表达式 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.EXPRESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
// do nothing 因为它基本做不了校验
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||||
|
Object result = FlowableUtils.getExpressionValue(execution, param);
|
||||||
|
return Convert.toSet(Long.class, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
|
||||||
|
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
|
||||||
|
Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables;
|
||||||
|
try {
|
||||||
|
Object result = FlowableUtils.getExpressionValue(variables, param);
|
||||||
|
return Convert.toSet(Long.class, result);
|
||||||
|
} catch (FlowableException ex) {
|
||||||
|
// 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,
|
||||||
|
log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
|
||||||
|
// 不能预测候选人,返回空列表, 避免流程无法进行
|
||||||
|
return Sets.newHashSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||||
|
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 org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户组 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author kyle
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private BpmUserGroupService userGroupService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.USER_GROUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
Set<Long> groupIds = StrUtils.splitToLongSet(param);
|
||||||
|
userGroupService.validUserGroups(groupIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers(String param) {
|
||||||
|
Set<Long> groupIds = StrUtils.splitToLongSet(param);
|
||||||
|
List<BpmUserGroupDO> groups = userGroupService.getUserGroupList(groupIds);
|
||||||
|
return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
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.candidate.expression.BpmTaskAssignLeaderExpression;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
|
||||||
|
import cn.iocoder.yudao.module.system.api.dept.PostApi;
|
||||||
|
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||||
|
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 岗位 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author kyle
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PostApi postApi;
|
||||||
|
@Resource
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy // 延迟加载,避免循环依赖
|
||||||
|
private BpmTaskAssignLeaderExpression TaskAssignLeader;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.POST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
Set<Long> postIds = StrUtils.splitToLongSet(param);
|
||||||
|
postApi.validPostList(postIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers(String param) {
|
||||||
|
Set<Long> postIds = StrUtils.splitToLongSet(param);
|
||||||
|
List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(postIds);
|
||||||
|
return convertSet(users, AdminUserRespDTO::getId);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers2(DelegateExecution execution, String param) {
|
||||||
|
String[] params = param.split("#");
|
||||||
|
Set<Long> postIds = StrUtils.splitToLongSet(params[1]);
|
||||||
|
List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(postIds);
|
||||||
|
for (int i = 0; i < users.size(); i++) {
|
||||||
|
if (!TaskAssignLeader.sameDept(execution, Integer.parseInt(params[0]),users.get(i).getId())) {
|
||||||
|
users.remove(i);
|
||||||
|
i--; // 删除元素后需要调整索引
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return convertSet(users, AdminUserRespDTO::getId);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersActivity(Long startUserId,String param) {
|
||||||
|
String[] params = param.split("#");
|
||||||
|
Set<Long> postIds = StrUtils.splitToLongSet(params[1]);
|
||||||
|
List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(postIds);
|
||||||
|
for (int i = 0; i < users.size(); i++) {
|
||||||
|
if (!TaskAssignLeader.sameDeptActivity(startUserId,Integer.parseInt(params[0]),users.get(i).getId())) {
|
||||||
|
users.remove(i);
|
||||||
|
i--; // 删除元素后需要调整索引
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return convertSet(users, AdminUserRespDTO::getId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
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.candidate.expression.BpmTaskAssignLeaderExpression;
|
||||||
|
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 org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author kyle
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RoleApi roleApi;
|
||||||
|
@Resource
|
||||||
|
private PermissionApi permissionApi;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy // 延迟加载,避免循环依赖
|
||||||
|
private BpmTaskAssignLeaderExpression TaskAssignLeader;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.ROLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
Set<Long> roleIds = StrUtils.splitToLongSet(param);
|
||||||
|
roleApi.validRoleList(roleIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers(String param) {
|
||||||
|
Set<Long> roleIds = StrUtils.splitToLongSet(param);
|
||||||
|
return permissionApi.getUserRoleIdListByRoleIds(roleIds);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsers2(DelegateExecution execution, String param) {
|
||||||
|
String[] params = param.split("#");
|
||||||
|
Set<Long> roleIds = StrUtils.splitToLongSet(params[1]);
|
||||||
|
Set<Long> userids = permissionApi.getUserRoleIdListByRoleIds(roleIds);
|
||||||
|
Iterator<Long> iterator = userids.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Long userid = iterator.next();
|
||||||
|
|
||||||
|
if (!TaskAssignLeader.sameDept(execution, Integer.parseInt(params[0]),userid)) {
|
||||||
|
iterator.remove(); // 删除符合条件的元素
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return userids;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersActivity(Long startUserId,String param) {
|
||||||
|
String[] params = param.split("#");
|
||||||
|
Set<Long> roleIds = StrUtils.splitToLongSet(params[1]);
|
||||||
|
Set<Long> userids = permissionApi.getUserRoleIdListByRoleIds(roleIds);
|
||||||
|
Iterator<Long> iterator = userids.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Long userid = iterator.next();
|
||||||
|
|
||||||
|
if (!TaskAssignLeader.sameDeptActivity(startUserId, Integer.parseInt(params[0]),userid)) {
|
||||||
|
iterator.remove(); // 删除符合条件的元素
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return userids;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||||
|
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 org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类
|
||||||
|
* <p>
|
||||||
|
* 适合场景:用于需要发起人信息复核等场景
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Lazy // 延迟加载,避免循环依赖
|
||||||
|
private BpmProcessInstanceService processInstanceService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.START_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isParamRequired() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
|
||||||
|
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
|
||||||
|
return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
|
||||||
|
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
|
||||||
|
return SetUtils.asSet(startUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
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 org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户 {@link BpmTaskCandidateStrategy} 实现类
|
||||||
|
*
|
||||||
|
* @author kyle
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmTaskCandidateStrategyEnum getStrategy() {
|
||||||
|
return BpmTaskCandidateStrategyEnum.USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateParam(String param) {
|
||||||
|
adminUserApi.validateUserList(StrUtils.splitToLongSet(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedHashSet<Long> calculateUsers(String param) {
|
||||||
|
return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据流程变量 variable 的类型,转换参数的值
|
||||||
|
*
|
||||||
|
* 目前用于 ConditionNodeConvert 的 buildConditionExpression 方法中
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 org.flowable.bpmn.model.FlowElement;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.delegate.JavaDelegate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate.BEAN_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理抄送用户的 {@link JavaDelegate} 的实现类
|
||||||
|
* <p>
|
||||||
|
* 目前只有仿钉钉/飞书模式的【抄送节点】使用
|
||||||
|
*
|
||||||
|
* @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<Long> userIds = taskCandidateInvoker.calculateUsersByTask(execution);
|
||||||
|
if (CollUtil.isEmpty(userIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 2. 执行抄送
|
||||||
|
FlowElement currentFlowElement = execution.getCurrentFlowElement();
|
||||||
|
processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(),
|
||||||
|
currentFlowElement.getId(), currentFlowElement.getName(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,687 @@
|
|||||||
|
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.enums.definition.*;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
|
||||||
|
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.constants.BpmnXMLConstants;
|
||||||
|
import org.flowable.bpmn.model.Process;
|
||||||
|
import org.flowable.bpmn.model.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
|
||||||
|
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仿钉钉/飞书的模型相关的工具方法
|
||||||
|
* <p>
|
||||||
|
* 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法
|
||||||
|
* 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
public class SimpleModelUtils {
|
||||||
|
|
||||||
|
private static final Map<BpmSimpleModelNodeType, NodeConvert> NODE_CONVERTS = MapUtil.newHashMap();
|
||||||
|
|
||||||
|
static {
|
||||||
|
List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
|
||||||
|
new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(),
|
||||||
|
new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert());
|
||||||
|
converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model
|
||||||
|
* <p>
|
||||||
|
* 整体逻辑如下:
|
||||||
|
* 1. 创建:BpmnModel、Process 对象
|
||||||
|
* 2. 转换:将 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素
|
||||||
|
* 3. 连接:构建并添加节点之间的连线 Sequence Flow
|
||||||
|
*
|
||||||
|
* @param processId 流程标识
|
||||||
|
* @param processName 流程名称
|
||||||
|
* @param simpleModelNode 仿钉钉流程设计模型数据结构
|
||||||
|
* @return Bpmn Model
|
||||||
|
*/
|
||||||
|
public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
|
||||||
|
// 1. 创建 BpmnModel
|
||||||
|
BpmnModel bpmnModel = new BpmnModel();
|
||||||
|
bpmnModel.setTargetNamespace(BpmnXMLConstants.BPMN2_NAMESPACE); // 设置命名空间。不加这个,解析 Message 会报 NPE 异常
|
||||||
|
// 创建 Process 对象
|
||||||
|
Process process = new Process();
|
||||||
|
process.setId(processId);
|
||||||
|
process.setName(processName);
|
||||||
|
process.setExecutable(Boolean.TRUE);
|
||||||
|
bpmnModel.addProcess(process);
|
||||||
|
|
||||||
|
// 2.1 创建 StartNode 节点
|
||||||
|
// 原因是:目前前端的第一个节点是“发起人节点”,所以这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点
|
||||||
|
BpmSimpleModelNodeVO startNode = buildStartNode();
|
||||||
|
startNode.setChildNode(simpleModelNode);
|
||||||
|
// 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中
|
||||||
|
traverseNodeToBuildFlowNode(startNode, process);
|
||||||
|
|
||||||
|
// 3. 构建并添加节点之间的连线 Sequence Flow
|
||||||
|
EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel);
|
||||||
|
traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId());
|
||||||
|
|
||||||
|
// 4. 自动布局
|
||||||
|
new BpmnAutoLayout(bpmnModel).execute();
|
||||||
|
return bpmnModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BpmSimpleModelNodeVO buildStartNode() {
|
||||||
|
return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID)
|
||||||
|
.setName(BpmSimpleModelNodeType.START_NODE.getName())
|
||||||
|
.setType(BpmSimpleModelNodeType.START_NODE.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历节点,构建 FlowNode 元素
|
||||||
|
*
|
||||||
|
* @param node SIMPLE 节点
|
||||||
|
* @param process BPMN 流程
|
||||||
|
*/
|
||||||
|
private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) {
|
||||||
|
// 1. 判断是否有效节点
|
||||||
|
if (!isValidNode(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
|
||||||
|
Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType());
|
||||||
|
|
||||||
|
// 2. 处理当前节点
|
||||||
|
NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType);
|
||||||
|
Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType());
|
||||||
|
List<? extends FlowElement> flowElements = nodeConvert.convertList(node);
|
||||||
|
flowElements.forEach(process::addFlowElement);
|
||||||
|
|
||||||
|
// 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点
|
||||||
|
if (BpmSimpleModelNodeType.isBranchNode(node.getType())
|
||||||
|
&& CollUtil.isNotEmpty(node.getConditionNodes())) {
|
||||||
|
// 注意:这里的 item.getChildNode() 处理的是每个条件的子节点,不是处理条件
|
||||||
|
node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2 情况二:如果有“子”节点,则递归处理子节点
|
||||||
|
traverseNodeToBuildFlowNode(node.getChildNode(), process);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历节点,构建 SequenceFlow 元素
|
||||||
|
*
|
||||||
|
* @param process Bpmn 流程
|
||||||
|
* @param node 当前节点
|
||||||
|
* @param targetNodeId 目标节点 ID
|
||||||
|
*/
|
||||||
|
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 == BpmSimpleModelNodeType.END_NODE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.1 情况一:普通节点
|
||||||
|
if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) {
|
||||||
|
traverseNormalNodeToBuildSequenceFlow(process, node, targetNodeId);
|
||||||
|
} else {
|
||||||
|
// 2.2 情况二:分支节点
|
||||||
|
traverseBranchNodeToBuildSequenceFlow(process, node, targetNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历普通(非条件)节点,构建 SequenceFlow 元素
|
||||||
|
*
|
||||||
|
* @param process Bpmn 流程
|
||||||
|
* @param node 当前节点
|
||||||
|
* @param targetNodeId 目标节点 ID
|
||||||
|
*/
|
||||||
|
private static void traverseNormalNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
|
||||||
|
BpmSimpleModelNodeVO childNode = node.getChildNode();
|
||||||
|
boolean isChildNodeValid = isValidNode(childNode);
|
||||||
|
// 情况一:有“子”节点,则建立连线
|
||||||
|
// 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点
|
||||||
|
String finalTargetNodeId = isChildNodeValid? childNode.getId() : targetNodeId;
|
||||||
|
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId);
|
||||||
|
process.addFlowElement(sequenceFlow);
|
||||||
|
|
||||||
|
// 因为有子节点,递归调用后续子节点
|
||||||
|
if (isChildNodeValid) {
|
||||||
|
traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历条件节点,构建 SequenceFlow 元素
|
||||||
|
*
|
||||||
|
* @param process Bpmn 流程
|
||||||
|
* @param node 当前节点
|
||||||
|
* @param targetNodeId 目标节点 ID
|
||||||
|
*/
|
||||||
|
private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) {
|
||||||
|
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
|
||||||
|
BpmSimpleModelNodeVO childNode = node.getChildNode();
|
||||||
|
List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes();
|
||||||
|
Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空");
|
||||||
|
// 分支终点节点 ID
|
||||||
|
String branchEndNodeId = null;
|
||||||
|
if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { // 条件分支
|
||||||
|
// 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID
|
||||||
|
branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
|
||||||
|
} else if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE
|
||||||
|
|| nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) { // 并行分支或包容分支
|
||||||
|
// 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。
|
||||||
|
branchEndNodeId = buildGatewayJoinId(node.getId());
|
||||||
|
}
|
||||||
|
Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空");
|
||||||
|
|
||||||
|
// 3. 遍历分支节点
|
||||||
|
// 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点
|
||||||
|
for (BpmSimpleModelNodeVO item : conditionNodes) {
|
||||||
|
Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeType.CONDITION_NODE.getType()),
|
||||||
|
"条件节点类型({})不符合", item.getType());
|
||||||
|
BpmSimpleModelNodeVO conditionChildNode = item.getChildNode();
|
||||||
|
// 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况
|
||||||
|
if (isValidNode(conditionChildNode)) {
|
||||||
|
// 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线
|
||||||
|
SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item);
|
||||||
|
process.addFlowElement(sequenceFlow);
|
||||||
|
// 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线
|
||||||
|
traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId);
|
||||||
|
} else {
|
||||||
|
// 3.2 分支没有后续节点。例如说,建立 A->D 的连线
|
||||||
|
SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item);
|
||||||
|
process.addFlowElement(sequenceFlow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线
|
||||||
|
if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE
|
||||||
|
|| nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE ) {
|
||||||
|
String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId;
|
||||||
|
SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId);
|
||||||
|
process.addFlowElement(sequenceFlow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 递归调用后续节点 继续递归。例如说,建立 D->E 的连线
|
||||||
|
traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId) {
|
||||||
|
return buildBpmnSequenceFlow(sourceId, targetId, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId,
|
||||||
|
String sequenceFlowId, String sequenceFlowName,
|
||||||
|
String conditionExpression) {
|
||||||
|
Assert.notEmpty(sourceId, "sourceId 不能为空");
|
||||||
|
Assert.notEmpty(targetId, "targetId 不能为空");
|
||||||
|
// TODO @jason:如果 sequenceFlowId 不存在的时候,是不是要生成一个默认的 sequenceFlowId? @芋艿: 貌似不需要,Flowable 会默认生成;TODO @jason:建议还是搞一个,主要是后续好排查问题。
|
||||||
|
// TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧;TODO @jason:建议还是搞一个,主要是后续好排查问题。
|
||||||
|
SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId);
|
||||||
|
if (StrUtil.isNotEmpty(sequenceFlowId)) {
|
||||||
|
sequenceFlow.setId(sequenceFlowId);
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotEmpty(sequenceFlowName)) {
|
||||||
|
sequenceFlow.setName(sequenceFlowName);
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotEmpty(conditionExpression)) {
|
||||||
|
sequenceFlow.setConditionExpression(conditionExpression);
|
||||||
|
}
|
||||||
|
return sequenceFlow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidNode(BpmSimpleModelNodeVO node) {
|
||||||
|
return node != null && node.getId() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) {
|
||||||
|
return BpmSimpleModelNodeType.APPROVE_NODE.getType().equals(node.getType())
|
||||||
|
&& BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ==========
|
||||||
|
|
||||||
|
private interface NodeConvert {
|
||||||
|
|
||||||
|
default List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
|
||||||
|
return Collections.singletonList(convert(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
default FlowElement convert(BpmSimpleModelNodeVO node) {
|
||||||
|
throw new UnsupportedOperationException("请实现该方法");
|
||||||
|
}
|
||||||
|
|
||||||
|
BpmSimpleModelNodeType getType();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StartNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StartEvent convert(BpmSimpleModelNodeVO node) {
|
||||||
|
StartEvent startEvent = new StartEvent();
|
||||||
|
startEvent.setId(node.getId());
|
||||||
|
startEvent.setName(node.getName());
|
||||||
|
return startEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.START_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EndNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EndEvent convert(BpmSimpleModelNodeVO node) {
|
||||||
|
EndEvent endEvent = new EndEvent();
|
||||||
|
endEvent.setId(node.getId());
|
||||||
|
endEvent.setName(node.getName());
|
||||||
|
// TODO @芋艿 + jason:要不要加一个终止定义?
|
||||||
|
return endEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.END_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StartUserNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserTask convert(BpmSimpleModelNodeVO node) {
|
||||||
|
UserTask userTask = new UserTask();
|
||||||
|
userTask.setId(node.getId());
|
||||||
|
userTask.setName(node.getName());
|
||||||
|
|
||||||
|
// 人工审批
|
||||||
|
addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
|
||||||
|
// 候选人策略为发起人自己
|
||||||
|
addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
|
||||||
|
// 添加表单字段权限属性元素
|
||||||
|
addFormFieldsPermission(node.getFieldsPermission(), userTask);
|
||||||
|
// 添加操作按钮配置属性元素
|
||||||
|
addButtonsSetting(node.getButtonsSetting(), userTask);
|
||||||
|
// 使用自动通过策略
|
||||||
|
// TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过;
|
||||||
|
addAssignStartUserHandlerType(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType(), userTask);
|
||||||
|
return userTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.START_USER_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ApproveNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FlowElement> convertList(BpmSimpleModelNodeVO node) {
|
||||||
|
List<FlowElement> flowElements = new ArrayList<>(2);
|
||||||
|
// 1. 构建用户任务
|
||||||
|
UserTask userTask = buildBpmnUserTask(node);
|
||||||
|
flowElements.add(userTask);
|
||||||
|
|
||||||
|
// 2. 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理
|
||||||
|
if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) {
|
||||||
|
BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler());
|
||||||
|
flowElements.add(boundaryEvent);
|
||||||
|
}
|
||||||
|
return flowElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.APPROVE_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 UserTask 用户的审批超时 BoundaryEvent 事件
|
||||||
|
*
|
||||||
|
* @param userTask 审批任务
|
||||||
|
* @param timeoutHandler 超时处理器
|
||||||
|
* @return BoundaryEvent 超时事件
|
||||||
|
*/
|
||||||
|
private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask,
|
||||||
|
BpmSimpleModelNodeVO.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(BpmUserTaskTimeoutHandlerTypeEnum.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());
|
||||||
|
// 2.2 添加超时执行动作元素
|
||||||
|
addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType());
|
||||||
|
return boundaryEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) {
|
||||||
|
UserTask userTask = new UserTask();
|
||||||
|
userTask.setId(node.getId());
|
||||||
|
userTask.setName(node.getName());
|
||||||
|
|
||||||
|
// 如果不是审批人节点,则直接返回
|
||||||
|
addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, node.getApproveType());
|
||||||
|
if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.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 void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) {
|
||||||
|
BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod);
|
||||||
|
Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum);
|
||||||
|
// 添加审批方式的扩展属性
|
||||||
|
addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod);
|
||||||
|
if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) {
|
||||||
|
// 随机审批,不需要设置多实例属性
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理多实例审批方式
|
||||||
|
MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics();
|
||||||
|
// 设置 collectionVariable。本系统用不到,仅仅为了 Flowable 校验不报错
|
||||||
|
multiInstanceCharacteristics.setInputDataItem("${coll_userList}");
|
||||||
|
if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) {
|
||||||
|
multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition());
|
||||||
|
multiInstanceCharacteristics.setSequential(false);
|
||||||
|
} else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) {
|
||||||
|
multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition());
|
||||||
|
multiInstanceCharacteristics.setSequential(true);
|
||||||
|
multiInstanceCharacteristics.setLoopCardinality("1");
|
||||||
|
} else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) {
|
||||||
|
Assert.notNull(approveRatio, "通过比例不能为空");
|
||||||
|
multiInstanceCharacteristics.setCompletionCondition(
|
||||||
|
String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D)));
|
||||||
|
multiInstanceCharacteristics.setSequential(false);
|
||||||
|
}
|
||||||
|
userTask.setLoopCharacteristics(multiInstanceCharacteristics);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CopyNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServiceTask convert(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.COPY_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ConditionBranchNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExclusiveGateway convert(BpmSimpleModelNodeVO node) {
|
||||||
|
ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
|
||||||
|
exclusiveGateway.setId(node.getId());
|
||||||
|
// TODO @jason:setName
|
||||||
|
|
||||||
|
// 设置默认的序列流(条件)
|
||||||
|
BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
|
||||||
|
item -> BooleanUtil.isTrue(item.getDefaultFlow()));
|
||||||
|
Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId());
|
||||||
|
exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
|
||||||
|
return exclusiveGateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.CONDITION_BRANCH_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ParallelBranchNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) {
|
||||||
|
ParallelGateway parallelGateway = new ParallelGateway();
|
||||||
|
parallelGateway.setId(node.getId());
|
||||||
|
// TODO @jason:setName
|
||||||
|
|
||||||
|
// 并行聚合网关由程序创建,前端不需要传入
|
||||||
|
ParallelGateway joinParallelGateway = new ParallelGateway();
|
||||||
|
joinParallelGateway.setId(buildGatewayJoinId(node.getId()));
|
||||||
|
// TODO @jason:setName
|
||||||
|
return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InclusiveBranchNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) {
|
||||||
|
InclusiveGateway inclusiveGateway = new InclusiveGateway();
|
||||||
|
inclusiveGateway.setId(node.getId());
|
||||||
|
// 设置默认的序列流(条件)
|
||||||
|
BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(),
|
||||||
|
item -> BooleanUtil.isTrue(item.getDefaultFlow()));
|
||||||
|
Assert.notNull(defaultSeqFlow, "包容分支节点({})的默认序列流不能为空", node.getId());
|
||||||
|
inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId());
|
||||||
|
// TODO @jason:setName
|
||||||
|
|
||||||
|
// 并行聚合网关由程序创建,前端不需要传入
|
||||||
|
InclusiveGateway joinInclusiveGateway = new InclusiveGateway();
|
||||||
|
joinInclusiveGateway.setId(buildGatewayJoinId(node.getId()));
|
||||||
|
// TODO @jason:setName
|
||||||
|
return CollUtil.newArrayList(inclusiveGateway, joinInclusiveGateway);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ConditionNodeConvert implements NodeConvert {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
|
||||||
|
// 原因是:正常情况下,它不会被调用到
|
||||||
|
throw new UnsupportedOperationException("条件节点不支持转换");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BpmSimpleModelNodeType getType() {
|
||||||
|
return BpmSimpleModelNodeType.CONDITION_NODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SequenceFlow buildSequenceFlow(String sourceId, String targetId,
|
||||||
|
BpmSimpleModelNodeVO node) {
|
||||||
|
String conditionExpression = buildConditionExpression(node);
|
||||||
|
return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造条件表达式
|
||||||
|
*
|
||||||
|
* @param node 条件节点
|
||||||
|
*/
|
||||||
|
public static String buildConditionExpression(BpmSimpleModelNodeVO node) {
|
||||||
|
BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(node.getConditionType());
|
||||||
|
if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) {
|
||||||
|
return node.getConditionExpression();
|
||||||
|
}
|
||||||
|
if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) {
|
||||||
|
ConditionGroups conditionGroups = node.getConditionGroups();
|
||||||
|
if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> {
|
||||||
|
if (CollUtil.isEmpty(item.getRules())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 构造规则表达式
|
||||||
|
List<String> 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);
|
||||||
|
});
|
||||||
|
// 构造条件组的表达式
|
||||||
|
Boolean and = item.getAnd();
|
||||||
|
return "(" + CollUtil.join(list, and ? " && " : " || ") + ")";
|
||||||
|
});
|
||||||
|
return String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || "));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildGatewayJoinId(String id) {
|
||||||
|
return id + "_join";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== SIMPLE 流程预测相关的方法 ==========
|
||||||
|
|
||||||
|
public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) {
|
||||||
|
List<BpmSimpleModelNodeVO> resultNodes = new ArrayList<>();
|
||||||
|
|
||||||
|
// 从头开始遍历
|
||||||
|
simulateNextNode(rootNode, variables, resultNodes);
|
||||||
|
return resultNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables,
|
||||||
|
List<BpmSimpleModelNodeVO> resultNodes) {
|
||||||
|
// 如果不合法(包括为空),则直接结束
|
||||||
|
if (!isValidNode(currentNode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentNode.getType());
|
||||||
|
Assert.notNull(nodeType, "模型节点类型不支持");
|
||||||
|
|
||||||
|
// 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE
|
||||||
|
if (nodeType == BpmSimpleModelNodeType.START_NODE
|
||||||
|
|| nodeType == BpmSimpleModelNodeType.START_USER_NODE
|
||||||
|
|| nodeType == BpmSimpleModelNodeType.APPROVE_NODE
|
||||||
|
|| nodeType == BpmSimpleModelNodeType.COPY_NODE
|
||||||
|
|| nodeType == BpmSimpleModelNodeType.END_NODE) {
|
||||||
|
// 添加元素
|
||||||
|
resultNodes.add(currentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的
|
||||||
|
if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) {
|
||||||
|
// 查找满足条件的 BpmSimpleModelNodeVO 节点
|
||||||
|
BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
|
||||||
|
conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
|
||||||
|
&& evalConditionExpress(variables, conditionNode));
|
||||||
|
if (matchConditionNode == null) {
|
||||||
|
matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
|
||||||
|
conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
|
||||||
|
}
|
||||||
|
Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode);
|
||||||
|
// 遍历满足条件的 BpmSimpleModelNodeVO 节点
|
||||||
|
simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的
|
||||||
|
if (nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) {
|
||||||
|
// 查找满足条件的 BpmSimpleModelNodeVO 节点
|
||||||
|
Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
|
||||||
|
conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
|
||||||
|
&& evalConditionExpress(variables, conditionNode));
|
||||||
|
if (CollUtil.isEmpty(matchConditionNodes)) {
|
||||||
|
matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
|
||||||
|
conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
|
||||||
|
}
|
||||||
|
Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode);
|
||||||
|
// 遍历满足条件的 BpmSimpleModelNodeVO 节点
|
||||||
|
matchConditionNodes.forEach(matchConditionNode ->
|
||||||
|
simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走
|
||||||
|
if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE) {
|
||||||
|
// 遍历所有 BpmSimpleModelNodeVO 节点
|
||||||
|
currentNode.getConditionNodes().forEach(matchConditionNode ->
|
||||||
|
simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历子节点
|
||||||
|
simulateNextNode(currentNode.getChildNode(), variables, resultNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO conditionNode) {
|
||||||
|
return BpmnModelUtils.evalConditionExpress(variables, ConditionNodeConvert.buildConditionExpression(conditionNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||||
|
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||||
|
import org.assertj.core.util.Sets;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BpmTaskCandidateDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateDeptLeaderMultiStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private DeptApi deptApi;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsers() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "10,20|2";
|
||||||
|
// mock 方法
|
||||||
|
when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> {
|
||||||
|
Long deptId = invocationOnMock.getArgument(0);
|
||||||
|
return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsers(param);
|
||||||
|
// 断言结果
|
||||||
|
assertEquals(Sets.newLinkedHashSet(11L, 1001L, 21L, 2001L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||||
|
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||||
|
import org.assertj.core.util.Sets;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateDeptLeaderStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private DeptApi deptApi;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsers() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "10,20";
|
||||||
|
// mock 方法
|
||||||
|
when(deptApi.getDeptList(eq(SetUtils.asSet(10L, 20L)))).thenReturn(asList(
|
||||||
|
randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(10L).setLeaderUserId(11L)),
|
||||||
|
randomPojo(DeptRespDTO.class, o -> o.setId(20L).setParentId(20L).setLeaderUserId(21L))));
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsers(param);
|
||||||
|
// 断言结果
|
||||||
|
assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
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 org.assertj.core.util.Sets;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateDeptMemberStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private DeptApi deptApi;
|
||||||
|
@Mock
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsers() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "10,20";
|
||||||
|
// mock 方法
|
||||||
|
when(adminUserApi.getUserListByDeptIds(eq(SetUtils.asSet(10L, 20L)))).thenReturn(asList(
|
||||||
|
randomPojo(AdminUserRespDTO.class, o -> o.setId(11L)),
|
||||||
|
randomPojo(AdminUserRespDTO.class, o -> o.setId(21L))));
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsers(param);
|
||||||
|
// 断言结果
|
||||||
|
assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
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 org.assertj.core.util.Sets;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateStartUserDeptLeaderMultiStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BpmProcessInstanceService processInstanceService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
@Mock
|
||||||
|
private DeptApi deptApi;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByTask() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "2";
|
||||||
|
// mock 方法(获得流程发起人)
|
||||||
|
Long startUserId = 1L;
|
||||||
|
ProcessInstance processInstance = mock(ProcessInstance.class);
|
||||||
|
DelegateExecution execution = mock(DelegateExecution.class);
|
||||||
|
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
|
||||||
|
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
|
||||||
|
// mock 方法(获取发起人的 multi 部门负责人)
|
||||||
|
mockGetStartUserDept(startUserId);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByActivity() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "2";
|
||||||
|
// mock 方法
|
||||||
|
Long startUserId = 1L;
|
||||||
|
mockGetStartUserDept(startUserId);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
|
||||||
|
startUserId, null, null);
|
||||||
|
// 断言
|
||||||
|
assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockGetStartUserDept(Long startUserId) {
|
||||||
|
when(adminUserApi.getUser(eq(startUserId))).thenReturn(
|
||||||
|
randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L)));
|
||||||
|
when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> {
|
||||||
|
Long deptId = invocationOnMock.getArgument(0);
|
||||||
|
return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
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 org.assertj.core.util.Sets;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BpmTaskCandidateStartUserDeptLeaderStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateStartUserDeptLeaderStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BpmProcessInstanceService processInstanceService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
@Mock
|
||||||
|
private DeptApi deptApi;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByTask() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "2";
|
||||||
|
// mock 方法(获得流程发起人)
|
||||||
|
Long startUserId = 1L;
|
||||||
|
ProcessInstance processInstance = mock(ProcessInstance.class);
|
||||||
|
DelegateExecution execution = mock(DelegateExecution.class);
|
||||||
|
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
|
||||||
|
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
|
||||||
|
// mock 方法(获取发起人的部门负责人)
|
||||||
|
mockGetStartUserDeptLeader(startUserId);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(Sets.newLinkedHashSet(1001L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetStartUserDeptLeader() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "2";
|
||||||
|
// mock 方法
|
||||||
|
Long startUserId = 1L;
|
||||||
|
mockGetStartUserDeptLeader(startUserId);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
|
||||||
|
startUserId, null, null);
|
||||||
|
// 断言
|
||||||
|
assertEquals(Sets.newLinkedHashSet(1001L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockGetStartUserDeptLeader(Long startUserId) {
|
||||||
|
when(adminUserApi.getUser(eq(startUserId))).thenReturn(
|
||||||
|
randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L)));
|
||||||
|
when(deptApi.getDept(any())).thenAnswer((Answer<DeptRespDTO>) invocationOnMock -> {
|
||||||
|
Long deptId = invocationOnMock.getArgument(0);
|
||||||
|
return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
|
||||||
|
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
||||||
|
import org.assertj.core.util.Sets;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BpmTaskCandidateStartUserSelectStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateStartUserSelectStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BpmProcessInstanceService processInstanceService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByTask() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "2";
|
||||||
|
// mock 方法(获得流程发起人)
|
||||||
|
ProcessInstance processInstance = mock(ProcessInstance.class);
|
||||||
|
DelegateExecution execution = mock(DelegateExecution.class);
|
||||||
|
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
|
||||||
|
when(execution.getCurrentActivityId()).thenReturn("activity_001");
|
||||||
|
// mock 方法(FlowableUtils)
|
||||||
|
Map<String, Object> processVariables = new HashMap<>();
|
||||||
|
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
|
||||||
|
MapUtil.of("activity_001", ListUtil.of(1L, 2L)));
|
||||||
|
when(processInstance.getProcessVariables()).thenReturn(processVariables);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByActivity() {
|
||||||
|
// 准备参数
|
||||||
|
String activityId = "activity_001";
|
||||||
|
Map<String, Object> processVariables = new HashMap<>();
|
||||||
|
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
|
||||||
|
MapUtil.of("activity_001", ListUtil.of(1L, 2L)));
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByActivity(null, activityId, null,
|
||||||
|
null, null, processVariables);
|
||||||
|
// 断言
|
||||||
|
assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
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.util.BpmnModelUtils;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
|
||||||
|
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
|
||||||
|
import org.flowable.bpmn.model.BpmnModel;
|
||||||
|
import org.flowable.bpmn.model.FlowElement;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
public class BpmTaskCandidateAssignEmptyStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateAssignEmptyStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BpmProcessDefinitionService processDefinitionService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByTask() {
|
||||||
|
try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class);
|
||||||
|
MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
|
||||||
|
// 准备参数
|
||||||
|
DelegateExecution execution = mock(DelegateExecution.class);
|
||||||
|
String param = randomString();
|
||||||
|
// mock 方法(execution)
|
||||||
|
String processDefinitionId = randomString();
|
||||||
|
when(execution.getProcessDefinitionId()).thenReturn(processDefinitionId);
|
||||||
|
FlowElement flowElement = mock(FlowElement.class);
|
||||||
|
when(execution.getCurrentFlowElement()).thenReturn(flowElement);
|
||||||
|
// mock 方法(parseAssignEmptyHandlerType)
|
||||||
|
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement)))
|
||||||
|
.thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType());
|
||||||
|
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerUserIds(same(flowElement)))
|
||||||
|
.thenReturn(ListUtil.of(1L, 2L));
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(SetUtils.asSet(1L, 2L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByActivity() {
|
||||||
|
try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
|
||||||
|
// 准备参数
|
||||||
|
String processDefinitionId = randomString();
|
||||||
|
String activityId = randomString();
|
||||||
|
String param = randomString();
|
||||||
|
// mock 方法(getFlowElementById)
|
||||||
|
FlowElement flowElement = mock(FlowElement.class);
|
||||||
|
BpmnModel bpmnModel = mock(BpmnModel.class);
|
||||||
|
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId)))
|
||||||
|
.thenReturn(flowElement);
|
||||||
|
// mock 方法(parseAssignEmptyHandlerType)
|
||||||
|
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement)))
|
||||||
|
.thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType());
|
||||||
|
// mock 方法(getProcessDefinitionInfo)
|
||||||
|
BpmProcessDefinitionInfoDO processDefinition = randomPojo(BpmProcessDefinitionInfoDO.class,
|
||||||
|
o -> o.setManagerUserIds(ListUtil.of(1L, 2L)));
|
||||||
|
when(processDefinitionService.getProcessDefinitionInfo(eq(processDefinitionId))).thenReturn(processDefinition);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByActivity(bpmnModel, activityId, param,
|
||||||
|
null, processDefinitionId, null);
|
||||||
|
// 断言
|
||||||
|
assertEquals(SetUtils.asSet(1L, 2L), userIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@Disabled // TODO 芋艿:临时注释
|
||||||
|
public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateExpressionStrategy strategy;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByTask() {
|
||||||
|
try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) {
|
||||||
|
// 准备参数
|
||||||
|
String param = "1,2";
|
||||||
|
DelegateExecution execution = mock(DelegateExecution.class);
|
||||||
|
// mock 方法
|
||||||
|
flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(execution), eq(param)))
|
||||||
|
.thenReturn(asSet(1L, 2L));
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> results = strategy.calculateUsersByTask(execution, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(asSet(1L, 2L), results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByActivity() {
|
||||||
|
try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) {
|
||||||
|
// 准备参数
|
||||||
|
String param = "1,2";
|
||||||
|
Map<String, Object> processVariables = new HashMap<>();
|
||||||
|
// mock 方法
|
||||||
|
flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(processVariables), eq(param)))
|
||||||
|
.thenReturn(asSet(1L, 2L));
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> results = strategy.calculateUsersByActivity(null, null, param,
|
||||||
|
null, null, processVariables);
|
||||||
|
// 断言
|
||||||
|
assertEquals(asSet(1L, 2L), results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
|
||||||
|
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||||
|
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@Disabled // TODO 芋艿:临时注释
|
||||||
|
public class BpmTaskCandidateGroupStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateGroupStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BpmUserGroupService userGroupService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsers() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "1,2";
|
||||||
|
// mock 方法
|
||||||
|
BpmUserGroupDO userGroup1 = randomPojo(BpmUserGroupDO.class, o -> o.setUserIds(asSet(11L, 12L)));
|
||||||
|
BpmUserGroupDO userGroup2 = randomPojo(BpmUserGroupDO.class, o -> o.setUserIds(asSet(21L, 22L)));
|
||||||
|
when(userGroupService.getUserGroupList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(userGroup1, userGroup2));
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(null, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(asSet(11L, 12L, 21L, 22L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
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 org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@Disabled // TODO 芋艿:临时注释
|
||||||
|
public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidatePostStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PostApi postApi;
|
||||||
|
@Mock
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsers() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "1,2";
|
||||||
|
// mock 方法
|
||||||
|
List<AdminUserRespDTO> users = convertList(asSet(11L, 22L),
|
||||||
|
id -> new AdminUserRespDTO().setId(id));
|
||||||
|
when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(users);
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(null, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(asSet(11L, 22L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||||
|
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@Disabled // TODO 芋艿:临时注释
|
||||||
|
public class BpmTaskCandidateRoleStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateRoleStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RoleApi roleApi;
|
||||||
|
@Mock
|
||||||
|
private PermissionApi permissionApi;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsers() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "1,2";
|
||||||
|
// mock 方法
|
||||||
|
when(permissionApi.getUserRoleIdListByRoleIds(eq(asSet(1L, 2L))))
|
||||||
|
.thenReturn(asSet(11L, 22L));
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(null, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(asSet(11L, 22L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
||||||
|
import org.assertj.core.util.Sets;
|
||||||
|
import org.flowable.engine.delegate.DelegateExecution;
|
||||||
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BpmTaskCandidateStartUserStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateStartUserStrategy strategy;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BpmProcessInstanceService processInstanceService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByTask() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "2";
|
||||||
|
// mock 方法(获得流程发起人)
|
||||||
|
Long startUserId = 1L;
|
||||||
|
ProcessInstance processInstance = mock(ProcessInstance.class);
|
||||||
|
DelegateExecution execution = mock(DelegateExecution.class);
|
||||||
|
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
|
||||||
|
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(Sets.newLinkedHashSet(startUserId), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculateUsersByActivity() {
|
||||||
|
// 准备参数
|
||||||
|
Long startUserId = 1L;
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, null,
|
||||||
|
startUserId, null, null);
|
||||||
|
// 断言
|
||||||
|
assertEquals(Sets.newLinkedHashSet(startUserId), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
@Disabled // TODO 芋艿:临时注释
|
||||||
|
public class BpmTaskCandidateUserStrategyTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private BpmTaskCandidateUserStrategy strategy;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
// 准备参数
|
||||||
|
String param = "1,2";
|
||||||
|
|
||||||
|
// 调用
|
||||||
|
Set<Long> userIds = strategy.calculateUsersByTask(null, param);
|
||||||
|
// 断言
|
||||||
|
assertEquals(asSet(1L, 2L), userIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user