body;
+
+ /**
+ * 请求返回处理设置,用于修改流程表单值
+ *
+ * key:表示要修改的流程表单字段名(name)
+ * value:接口返回的字段名
+ */
+ @Schema(description = "请求返回处理设置", example = "[]")
+ private List> response;
+
+ }
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
index 278291483..275368c7c 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
@@ -25,7 +25,7 @@ public class BpmModelRespVO extends BpmModelMetaInfoVO {
@Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
private String icon;
- @Schema(description = "流程分类编码", example = "1")
+ @Schema(description = "流程分类编号", example = "1")
private String category;
@Schema(description = "流程分类名字", example = "请假")
private String categoryName;
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
index 291cf4d83..f002e6894 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
@@ -4,16 +4,19 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
+import org.flowable.bpmn.model.IOParameter;
import org.hibernate.validator.constraints.URL;
import java.util.List;
import java.util.Map;
+import java.util.Set;
@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
@Data
@@ -114,7 +117,8 @@ public class BpmSimpleModelNodeVO {
@Schema(description = "路由分支组", example = "[]")
private List routerGroups;
- @Schema(description = "路由分支默认分支 ID", example = "Flow_xxx", hidden = true) // 由后端生成,所以 hidden = true
+ @Schema(description = "路由分支默认分支 ID", example = "Flow_xxx", hidden = true) // 由后端生成(不从前端传递),所以 hidden = true
+ @JsonIgnore
private String routerDefaultFlowId; // 仅用于路由分支节点 BpmSimpleModelNodeType.ROUTER_BRANCH_NODE
/**
@@ -122,6 +126,15 @@ public class BpmSimpleModelNodeVO {
*/
private TriggerSetting triggerSetting;
+ @Schema(description = "附加节点 Id", example = "UserTask_xxx", hidden = true) // 由后端生成(不从前端传递),所以 hidden = true
+ @JsonIgnore
+ private String attachNodeId; // 目前用于触发器节点(HTTP 回调)。需要 UserTask 和 ReceiveTask(附加节点) 来完成
+
+ /**
+ * 子流程设置
+ */
+ private ChildProcessSetting childProcessSetting;
+
@Schema(description = "任务监听器")
@Valid
@Data
@@ -345,12 +358,10 @@ public class BpmSimpleModelNodeVO {
@Valid
private HttpRequestTriggerSetting httpRequestSetting;
- // TODO @jason:这个要不直接叫 formSetting,更好理解一点哈
- // TODO @jason:如果搞成 List,是不是可以做条件组了?微信讨论哈
/**
* 流程表单触发器设置
*/
- private NormalFormTriggerSetting normalFormSetting;
+ private List formSettings;
@Schema(description = "http 请求触发器设置", example = "{}")
@Data
@@ -369,7 +380,6 @@ public class BpmSimpleModelNodeVO {
@Valid
private List body;
- // TODO @json:可能未来看情况,搞个 HttpResponseParam;得看看有没别的业务需要,抽象统一
/**
* 请求返回处理设置,用于修改流程表单值
*
@@ -379,15 +389,137 @@ public class BpmSimpleModelNodeVO {
@Schema(description = "请求返回处理设置", example = "[]")
private List> response;
+ /**
+ * Http 回调请求,需要指定回调任务 Key,用于回调执行
+ */
+ @Schema(description = "回调任务 Key", example = "xxx", hidden = true)
+ private String callbackTaskDefineKey;
+
}
@Schema(description = "流程表单触发器设置", example = "{}")
@Data
- public static class NormalFormTriggerSetting {
+ public static class FormTriggerSetting {
- @Schema(description = "修改的表单字段", example = "userName")
+ @Schema(description = "条件类型", example = "1")
+ @InEnum(BpmSimpleModeConditionTypeEnum.class)
+ private Integer conditionType;
+
+ @Schema(description = "条件表达式", example = "${day>3}")
+ private String conditionExpression;
+
+ @Schema(description = "条件组", example = "{}")
+ private ConditionGroups conditionGroups;
+
+ @Schema(description = "修改的表单字段", example = "{}")
private Map updateFormFields;
+ @Schema(description = "删除表单字段", example = "[]")
+ private Set deleteFields;
+ }
+ }
+
+ @Schema(description = "子流程节点配置")
+ @Data
+ @Valid
+ public static class ChildProcessSetting {
+
+ @Schema(description = "被调用流程", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx")
+ @NotEmpty(message = "被调用流程不能为空")
+ private String calledProcessDefinitionKey;
+
+ @Schema(description = "被调用流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx")
+ @NotEmpty(message = "被调用流程名称不能为空")
+ private String calledProcessDefinitionName;
+
+ @Schema(description = "是否异步", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+ @NotNull(message = "是否异步不能为空")
+ private Boolean async;
+
+ @Schema(description = "输入参数(主->子)", example = "[]")
+ private List inVariables;
+
+ @Schema(description = "输出参数(子->主)", example = "[]")
+ private List outVariables;
+
+ @Schema(description = "是否自动跳过子流程发起节点", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+ @NotNull(message = "是否自动跳过子流程发起节点不能为空")
+ private Boolean skipStartUserNode;
+
+ @Schema(description = "子流程发起人配置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
+ @NotNull(message = "子流程发起人配置不能为空")
+ private StartUserSetting startUserSetting;
+
+ @Schema(description = "超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
+ private TimeoutSetting timeoutSetting;
+
+ @Schema(description = "多实例设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
+ private MultiInstanceSetting multiInstanceSetting;
+
+ @Schema(description = "子流程发起人配置")
+ @Data
+ @Valid
+ public static class StartUserSetting {
+
+ @Schema(description = "子流程发起人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "子流程发起人类型")
+ @InEnum(BpmChildProcessStartUserTypeEnum.class)
+ private Integer type;
+
+ @Schema(description = "表单", example = "xxx")
+ private String formField;
+
+ @Schema(description = "当子流程发起人为空时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "当子流程发起人为空时类型不能为空")
+ @InEnum(BpmChildProcessStartUserEmptyTypeEnum.class)
+ private Integer emptyType;
+
+ }
+
+ @Schema(description = "超时设置")
+ @Data
+ @Valid
+ public static class TimeoutSetting {
+
+ @Schema(description = "是否开启超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+ @NotNull(message = "是否开启超时设置不能为空")
+ private Boolean enable;
+
+ @Schema(description = "时间类型", example = "1")
+ @InEnum(BpmDelayTimerTypeEnum.class)
+ private Integer type;
+
+ @Schema(description = "时间表达式", example = "PT1H,2025-01-01T00:00:00")
+ private String timeExpression;
+
+ }
+
+ @Schema(description = "多实例设置")
+ @Data
+ @Valid
+ public static class MultiInstanceSetting {
+
+ @Schema(description = "是否开启多实例", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+ @NotNull(message = "是否开启多实例不能为空")
+ private Boolean enable;
+
+ @Schema(description = "是否串行", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+ @NotNull(message = "是否串行不能为空")
+ private Boolean sequential;
+
+ @Schema(description = "完成比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "完成比例不能为空")
+ private Integer approveRatio;
+
+ @Schema(description = "多实例来源类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "多实例来源类型不能为空")
+ @InEnum(BpmChildProcessMultiInstanceSourceTypeEnum.class)
+ private Integer sourceType;
+
+ @Schema(description = "多实例来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "多实例来源不能为空")
+ private String source;
+
}
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
index 1e9dfc820..0ec737174 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -8,7 +9,7 @@ import java.util.List;
@Schema(description = "管理后台 - 流程定义 Response VO")
@Data
-public class BpmProcessDefinitionRespVO {
+public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String id;
@@ -19,15 +20,9 @@ public class BpmProcessDefinitionRespVO {
@Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
private String name;
- @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
+ @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "youdao")
private String key;
- @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
- private String icon;
-
- @Schema(description = "流程描述", example = "我是描述")
- private String description;
-
@Schema(description = "流程分类", example = "1")
private String category;
@Schema(description = "流程分类名字", example = "请假")
@@ -36,22 +31,15 @@ public class BpmProcessDefinitionRespVO {
@Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
- @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
- private Integer formType;
- @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024")
- private Long formId;
- @Schema(description = "表单名字", example = "请假表单")
- private String formName;
+ @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
+ private String modelId;
+
@Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
private String formConf;
@Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
private List formFields;
- @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空",
- example = "/bpm/oa/leave/create")
- private String formCustomCreatePath;
- @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空",
- example = "/bpm/oa/leave/view")
- private String formCustomViewPath;
+ @Schema(description = "表单名字", example = "请假表单")
+ private String formName;
@Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer suspensionState; // 参见 SuspensionState 枚举
@@ -67,7 +55,7 @@ public class BpmProcessDefinitionRespVO {
@Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long sort;
-
+
@Schema(description = "BPMN UserTask 用户任务")
@Data
public static class UserTask {
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
index 3a847ce4e..931626167 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
@@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
@@ -30,10 +32,10 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请”
@@ -76,8 +78,14 @@ public class BpmProcessInstanceController {
convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory));
Map processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId));
+ Set userIds = convertSet(pageResult.getList(), processInstance -> NumberUtils.parseLong(processInstance.getStartUserId()));
+ userIds.addAll(convertSetByFlatMap(taskMap.values(),
+ tasks -> tasks.stream().map(Task::getAssignee).filter(StrUtil::isNotBlank).map(Long::parseLong)));
+ Map userMap = adminUserApi.getUserMap(userIds);
+ Map deptMap = deptApi.getDeptMap(
+ convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult,
- processDefinitionMap, categoryMap, taskMap, null, null, processDefinitionInfoMap));
+ processDefinitionMap, categoryMap, taskMap, userMap, deptMap, processDefinitionInfoMap));
}
@GetMapping("/manager-page")
@@ -140,6 +148,7 @@ public class BpmProcessInstanceController {
processDefinition, processDefinitionInfo, startUser, dept));
}
+ // TODO @lesan:【子流程】子流程如果取消,主流程应该是通过、还是不通过哈?还是禁用掉子流程的取消?
@DeleteMapping("/cancel-by-start-user")
@Operation(summary = "用户取消流程实例", description = "取消发起的流程")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")
@@ -162,10 +171,25 @@ public class BpmProcessInstanceController {
@Operation(summary = "获得审批详情")
@Parameter(name = "id", description = "流程实例的编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+ @SuppressWarnings("unchecked")
public CommonResult getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) {
+ if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
+ reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
+ }
return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
}
+ @GetMapping("/get-next-approval-nodes")
+ @Operation(summary = "获取下一个执行的流程节点")
+ @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+ @SuppressWarnings("unchecked")
+ public CommonResult> getNextApprovalNodes(@Valid BpmApprovalDetailReqVO reqVO) {
+ if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
+ reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
+ }
+ return success(processInstanceService.getNextApprovalNodes(getLoginUserId(), reqVO));
+ }
+
@GetMapping("/get-bpmn-model-view")
@Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
@Parameter(name = "id", description = "流程实例的编号", required = true)
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
index 9121f1036..069ec2000 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
@@ -18,6 +18,9 @@ public class BpmApprovalDetailReqVO {
@Schema(description = "流程变量")
private Map processVariables; // 使用场景:同 processDefinitionId,用于流程预测
+ @Schema(description = "流程变量")
+ private String processVariablesStr; // 解决 GET 无法传递对象的问题,最终转换成 processVariables 变量
+
@Schema(description = "流程实例的编号", example = "1024")
private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
index 76dca606a..f73c490a5 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -73,6 +74,13 @@ public class BpmProcessInstanceRespVO {
@Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
private String name;
+ @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
+ @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
+ private Long assignee;
+
+ @Schema(description = "任务分配人", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
+ private UserSimpleBaseVO assigneeUser;
+
}
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
index 40df86efc..0969fda13 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
+import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - 通过流程任务的 Request VO")
@@ -23,4 +24,7 @@ public class BpmTaskApproveReqVO {
@Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
private Map variables;
+ @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}")
+ private Map> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
index 11c59ce3e..f129e5a31 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
@@ -18,6 +18,9 @@ public class BpmTaskPageReqVO extends PageParam {
@Schema(description = "流程分类", example = "1")
private String category;
+ @Schema(description = "流程定义的标识", example = "2048")
+ private String processDefinitionKey; // 精准匹配
+
@Schema(description = "创建时间")
@DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
index 83812ee1a..fce72a490 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
@@ -85,6 +85,9 @@ public class BpmTaskRespVO {
@Schema(description = "是否填写审批意见", example = "false")
private Boolean reasonRequire;
+ @Schema(description = "节点类型", example = "10")
+ private Integer nodeType; // 参见 BpmSimpleModelNodeTypeEnum 枚举。
+
@Data
@Schema(description = "流程实例")
public static class ProcessInstance {
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
index 6e798e77f..ccd7f06e7 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
@@ -76,6 +76,15 @@ public interface BpmProcessInstanceConvert {
respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName()));
}
+ if (CollUtil.isNotEmpty(respVO.getTasks())) {
+ respVO.getTasks().forEach(task -> {
+ AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee());
+ if (assigneeUser!= null) {
+ task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class));
+ MapUtils.findAndThen(deptMap, assigneeUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName()));
+ }
+ });
+ }
}
// 摘要
respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()),
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
index ccab7b043..86c83ed61 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
-import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
@@ -60,6 +59,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
*/
private Integer modelType;
+ /**
+ * 流程分类的编码
+ *
+ * 关联 {@link BpmCategoryDO#getCode()}
+ *
+ * 为什么要存储?原因是,{@link ProcessDefinition#getCategory()} 无法设置
+ */
+ private String category;
/**
* 图标
*/
@@ -149,7 +156,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
*
* 关联 {@link AdminUserRespDTO#getId()} 字段的数组
*/
- @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
+ @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
private List managerUserIds;
/**
@@ -175,11 +182,21 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.TitleSetting titleSetting;
-
/**
* 摘要设置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.SummarySetting summarySetting;
+ /**
+ * 流程前置通知设置
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private BpmModelMetaInfoVO.HttpRequestSetting processBeforeTriggerSetting;
+ /**
+ * 流程后置通知设置
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private BpmModelMetaInfoVO.HttpRequestSetting processAfterTriggerSetting;
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
index d239dbe3f..57f4d393f 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
@@ -1,15 +1,21 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
+import org.flowable.bpmn.model.CallActivity;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import java.util.List;
import java.util.Set;
/**
@@ -42,27 +48,44 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
*/
@Override
protected int resolveNrOfInstances(DelegateExecution execution) {
- // 第一步,设置 collectionVariable 和 CollectionVariable
- // 从 execution.getVariable() 读取所有任务处理人的 key
- super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
- super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
- // 从 execution.getVariable() 读取当前所有任务处理的人的 key
- super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
+ // 情况一:UserTask 节点
+ if (execution.getCurrentFlowElement() instanceof UserTask) {
+ // 第一步,设置 collectionVariable 和 CollectionVariable
+ // 从 execution.getVariable() 读取所有任务处理人的 key
+ super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
+ super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
+ // 从 execution.getVariable() 读取当前所有任务处理的人的 key
+ super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
- // 第二步,获取任务的所有处理人
- @SuppressWarnings("unchecked")
- Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class);
- if (assigneeUserIds == null) {
- assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
- if (CollUtil.isEmpty(assigneeUserIds)) {
- // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
- // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
- // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
- assigneeUserIds = SetUtils.asSet((Long) null);
+ // 第二步,获取任务的所有处理人
+ @SuppressWarnings("unchecked")
+ Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class);
+ if (assigneeUserIds == null) {
+ assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
+ if (CollUtil.isEmpty(assigneeUserIds)) {
+ // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
+ // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
+ // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
+ assigneeUserIds = SetUtils.asSet((Long) null);
+ }
+ execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
}
- execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
+ return assigneeUserIds.size();
}
- return assigneeUserIds.size();
+
+ // 情况二:CallActivity 节点
+ if (execution.getCurrentFlowElement() instanceof CallActivity) {
+ FlowElement flowElement = execution.getCurrentFlowElement();
+ Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
+ if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) {
+ return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
+ }
+ if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
+ return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
+ }
+ }
+
+ return super.resolveNrOfInstances(execution);
}
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
index b3a3a24f8..cb748182e 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
@@ -2,14 +2,20 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessMultiInstanceSourceTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
+import org.flowable.bpmn.model.CallActivity;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
+import java.util.List;
import java.util.Set;
/**
@@ -35,28 +41,45 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
*/
@Override
protected int resolveNrOfInstances(DelegateExecution execution) {
- // 第一步,设置 collectionVariable 和 CollectionVariable
- // 从 execution.getVariable() 读取所有任务处理人的 key
- super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
- super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
- // 从 execution.getVariable() 读取当前所有任务处理的人的 key
- super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
+ // 情况一:UserTask 节点
+ if (execution.getCurrentFlowElement() instanceof UserTask) {
+ // 第一步,设置 collectionVariable 和 CollectionVariable
+ // 从 execution.getVariable() 读取所有任务处理人的 key
+ super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
+ super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
+ // 从 execution.getVariable() 读取当前所有任务处理的人的 key
+ super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
- // 第二步,获取任务的所有处理人
- // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
- @SuppressWarnings("unchecked")
- Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class);
- if (assigneeUserIds == null) {
- assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
- if (CollUtil.isEmpty(assigneeUserIds)) {
- // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
- // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
- // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
- assigneeUserIds = SetUtils.asSet((Long) null);
+ // 第二步,获取任务的所有处理人
+ // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
+ @SuppressWarnings("unchecked")
+ Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class);
+ if (assigneeUserIds == null) {
+ assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
+ if (CollUtil.isEmpty(assigneeUserIds)) {
+ // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
+ // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
+ // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
+ assigneeUserIds = SetUtils.asSet((Long) null);
+ }
+ execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
}
- execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
+ return assigneeUserIds.size();
}
- return assigneeUserIds.size();
+
+ // 情况二:CallActivity 节点
+ if (execution.getCurrentFlowElement() instanceof CallActivity) {
+ FlowElement flowElement = execution.getCurrentFlowElement();
+ Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
+ if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) {
+ return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
+ }
+ if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
+ return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
+ }
+ }
+
+ return super.resolveNrOfInstances(execution);
}
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
index cba5187b3..c4c8167c8 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java
@@ -10,7 +10,10 @@ import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
+import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
+import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.TaskHelper;
+import org.flowable.engine.interceptor.CreateUserTaskBeforeContext;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.transaction.annotation.Transactional;
@@ -69,4 +72,15 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
return CollUtil.get(candidateUserIds, index);
}
+ @Override
+ protected void handleCategory(CreateUserTaskBeforeContext beforeContext, ExpressionManager expressionManager,
+ TaskEntity task, DelegateExecution execution) {
+ ProcessDefinitionEntity processDefinitionEntity = CommandContextUtil.getProcessDefinitionEntityManager().findById(execution.getProcessDefinitionId());
+ if (processDefinitionEntity == null) {
+ log.warn("[handleCategory][任务编号({}) 找不到流程定义({})]", task.getId(), execution.getProcessDefinitionId());
+ return;
+ }
+ task.setCategory(processDefinitionEntity.getCategory());
+ }
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
index 952f0f1be..7f66b29d3 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
@@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.bpmn.model.CallActivity;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
@@ -129,8 +130,12 @@ public class BpmTaskCandidateInvoker {
public Set calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
Long startUserId, String processDefinitionId, Map processVariables) {
- // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
+ // 如果是 CallActivity 子流程,不进行计算候选人
FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
+ if (flowElement instanceof CallActivity) {
+ return new HashSet<>();
+ }
+ // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
if (ObjectUtils.equalsAny(approveType,
BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java
new file mode 100644
index 000000000..a31692565
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateApproveUserSelectStrategy.java
@@ -0,0 +1,78 @@
+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.strategy.user.BpmTaskCandidateUserStrategy;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+import com.google.common.collect.Sets;
+import jakarta.annotation.Resource;
+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 java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 审批人自选 {@link BpmTaskCandidateUserStrategy} 实现类
+ * 审批人在审批时选择下一个节点的审批人
+ *
+ * @author smallNorthLee
+ */
+@Component
+public class BpmTaskCandidateApproveUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
+
+ @Resource
+ @Lazy // 延迟加载,避免循环依赖
+ private BpmProcessInstanceService processInstanceService;
+
+ @Override
+ public BpmTaskCandidateStrategyEnum getStrategy() {
+ return BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT;
+ }
+
+ @Override
+ public void validateParam(String param) {}
+
+ @Override
+ public boolean isParamRequired() {
+ return false;
+ }
+
+ @Override
+ public LinkedHashSet calculateUsersByTask(DelegateExecution execution, String param) {
+ ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
+ Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
+ Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance);
+ Assert.notNull(approveUserSelectAssignees, "流程实例({}) 的下一个执行节点审批人不能为空",
+ execution.getProcessInstanceId());
+ if (approveUserSelectAssignees == null) {
+ return Sets.newLinkedHashSet();
+ }
+ // 获得审批人
+ List assignees = approveUserSelectAssignees.get(execution.getCurrentActivityId());
+ return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
+ }
+
+ @Override
+ public LinkedHashSet calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
+ Long startUserId, String processDefinitionId, Map processVariables) {
+ if (processVariables == null) {
+ return Sets.newLinkedHashSet();
+ }
+ // 流程预测时会使用,允许审批人为空,如果为空前端会弹出提示选择下一个节点审批人,避免流程无法进行,审批时会真正校验节点是否配置审批人
+ Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processVariables);
+ if (approveUserSelectAssignees == null) {
+ return Sets.newLinkedHashSet();
+ }
+ // 获得审批人
+ List assignees = approveUserSelectAssignees.get(activityId);
+ return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java
index 9fd14d6de..9304d288a 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java
@@ -2,24 +2,21 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.d
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 jakarta.annotation.Resource;
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 java.util.*;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
/**
* 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类
@@ -55,7 +52,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
execution.getProcessInstanceId());
// 获得审批人
List assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
- return new LinkedHashSet<>(assignees);
+ return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
}
@Override
@@ -70,28 +67,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
}
// 获得审批人
List assignees = startUserSelectAssignees.get(activityId);
- return new LinkedHashSet<>(assignees);
- }
-
- /**
- * 获得发起人自选审批人或抄送人的 Task 列表
- *
- * @param bpmnModel BPMN 模型
- * @return Task 列表
- */
- public static List getStartUserSelectTaskList(BpmnModel bpmnModel) {
- if (bpmnModel == null) {
- return Collections.emptyList();
- }
- List 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;
+ return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
}
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
index 990bc6303..2a45e3a10 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java
@@ -24,7 +24,8 @@ public enum BpmTaskCandidateStrategyEnum implements ArrayValuable {
MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"),
POST(22, "岗位"),
USER(30, "用户"),
- START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人
+ APPROVE_USER_SELECT(34, "审批人自身"), // 当前审批人,可在审批时,选择下一个节点的审批人
+ START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时,选择此节点的审批人
START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景
START_USER_DEPT_LEADER(37, "发起人部门负责人"),
START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"),
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
index 9dce8a2ea..e416428c4 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
+
/**
* BPMN XML 常量信息
*
@@ -66,6 +68,11 @@ public interface BpmnModelConstants {
*/
String USER_TASK_APPROVE_METHOD = "approveMethod";
+ /**
+ * BPMN Child Process 的扩展属性,用于标记多实例来源类型
+ */
+ String CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = "childProcessMultiInstanceSourceType";
+
/**
* BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
*/
@@ -129,4 +136,11 @@ public interface BpmnModelConstants {
*/
String REASON_REQUIRE = "reasonRequire";
+ /**
+ * 节点类型
+ *
+ * 目前只有 {@link BpmModelTypeEnum#SIMPLE} 的 UserTask 节点会设置该属性,用于区分是审批节点、还是办理节点
+ */
+ String NODE_TYPE = "nodeType";
+
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
index 8172cf59a..893c4d053 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
@@ -27,8 +27,16 @@ public class BpmnVariableConstants {
* 流程实例的变量 - 发起用户选择的审批人 Map
*
* @see ProcessInstance#getProcessVariables()
+ * @see BpmTaskCandidateStrategyEnum#START_USER_SELECT
*/
public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
+ /**
+ * 流程实例的变量 - 审批人选择的审批人 Map
+ *
+ * @see ProcessInstance#getProcessVariables()
+ * @see BpmTaskCandidateStrategyEnum#APPROVE_USER_SELECT
+ */
+ public static final String PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES = "PROCESS_APPROVE_USER_SELECT_ASSIGNEES";
/**
* 流程实例的变量 - 发起用户 ID
*
@@ -51,6 +59,13 @@ public class BpmnVariableConstants {
*/
public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
+ /**
+ * 流程实例的变量 - 用于判断流程是否需要跳过发起人节点
+ *
+ * @see ProcessInstance#getProcessVariables()
+ */
+ public static final String PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE = "PROCESS_SKIP_START_USER_NODE";
+
/**
* 流程实例的变量 - 流程开始时间
*
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
index 5251aea64..ba2aaa6bc 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
@@ -22,6 +22,7 @@ import java.util.Set;
public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
public static final Set PROCESS_INSTANCE_EVENTS = ImmutableSet.builder()
+ .add(FlowableEngineEventType.PROCESS_CREATED)
.add(FlowableEngineEventType.PROCESS_COMPLETED)
.add(FlowableEngineEventType.PROCESS_CANCELLED)
.build();
@@ -34,6 +35,11 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
super(PROCESS_INSTANCE_EVENTS);
}
+ @Override
+ protected void processCreated(FlowableEngineEntityEvent event) {
+ processInstanceService.processProcessInstanceCreated((ProcessInstance)event.getEntity());
+ }
+
@Override
protected void processCompleted(FlowableEngineEntityEvent event) {
processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity());
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
index ecef8fdb4..329241f79 100644
--- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
@@ -109,7 +109,11 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
// 2.2 延迟器超时处理
} else if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT)) {
String taskKey = boundaryEvent.getAttachedToRefId();
- taskService.processDelayTimerTimeout(event.getProcessInstanceId(), taskKey);
+ taskService.triggerTask(event.getProcessInstanceId(), taskKey);
+ // 2.3 子流程超时处理
+ } else if (ObjectUtil.equal(bpmTimerBoundaryEventType, BpmBoundaryEventTypeEnum.CHILD_PROCESS_TIMEOUT)) {
+ String taskKey = boundaryEvent.getAttachedToRefId();
+ taskService.processChildProcessTimeout(event.getProcessInstanceId(), taskKey);
}
}
diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java
new file mode 100644
index 000000000..2503c0fff
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java
@@ -0,0 +1,158 @@
+package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+import com.fasterxml.jackson.core.type.TypeReference;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR;
+
+/**
+ * 工作流发起 HTTP 请求工具类
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class BpmHttpRequestUtils {
+
+ public static void executeBpmHttpRequest(ProcessInstance processInstance,
+ String url,
+ List headerParams,
+ List bodyParams,
+ Boolean handleResponse,
+ List> response) {
+ RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
+ BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
+
+ // 1.1 设置请求头
+ MultiValueMap headers = buildHttpHeaders(processInstance, headerParams);
+ // 1.2 设置请求体
+ MultiValueMap body = buildHttpBody(processInstance, bodyParams);
+
+ // 2. 发起请求
+ ResponseEntity responseEntity = sendHttpRequest(url, headers, body, restTemplate);
+
+ // 3. 处理返回
+ if (Boolean.FALSE.equals(handleResponse)) {
+ return;
+ }
+ // 3.1 判断是否需要解析返回值
+ if (responseEntity == null
+ || StrUtil.isEmpty(responseEntity.getBody())
+ || !responseEntity.getStatusCode().is2xxSuccessful()
+ || CollUtil.isEmpty(response)) {
+ return;
+ }
+ // 3.2 解析返回值, 返回值必须符合 CommonResult 规范。
+ CommonResult