Commit d1fead11daccc23551750ae8aa2e42425b945984

Authored by jason
2 parents ffa7c246 d8a7d668

Merge remote-tracking branch 'origin/feature/bpm' into feature/bpm

Showing 23 changed files with 288 additions and 83 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmChildProcessMultiInstanceSourceTypeEnum.java
@@ -17,8 +17,8 @@ import java.util.Arrays; @@ -17,8 +17,8 @@ import java.util.Arrays;
17 public enum BpmChildProcessMultiInstanceSourceTypeEnum implements ArrayValuable<Integer> { 17 public enum BpmChildProcessMultiInstanceSourceTypeEnum implements ArrayValuable<Integer> {
18 18
19 FIXED_QUANTITY(1, "固定数量"), 19 FIXED_QUANTITY(1, "固定数量"),
20 - DIGITAL_FORM(2, "数字表单"),  
21 - MULTI_FORM(3, "多项表单"); 20 + NUMBER_FORM(2, "数字表单"),
  21 + MULTIPLE_FORM(3, "多选表单");
22 22
23 private final Integer type; 23 private final Integer type;
24 private final String name; 24 private final String name;
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTriggerTypeEnum.java
@@ -16,8 +16,9 @@ import java.util.Arrays; @@ -16,8 +16,9 @@ import java.util.Arrays;
16 @AllArgsConstructor 16 @AllArgsConstructor
17 public enum BpmTriggerTypeEnum implements ArrayValuable<Integer> { 17 public enum BpmTriggerTypeEnum implements ArrayValuable<Integer> {
18 18
19 - HTTP_REQUEST(1, "发起 HTTP 请求"),  
20 - HTTP_CALLBACK(2, "发起 HTTP 回调"), 19 + HTTP_REQUEST(1, "发起 HTTP 请求"), // BPM => 业务,流程继续执行,无需等待业务
  20 + HTTP_CALLBACK(2, "接收 HTTP 回调"), // BPM => 业务 => BPM,流程卡主,等待业务回调
  21 +
21 FORM_UPDATE(10, "更新流程表单数据"), 22 FORM_UPDATE(10, "更新流程表单数据"),
22 FORM_DELETE(11, "删除流程表单数据"), 23 FORM_DELETE(11, "删除流程表单数据"),
23 ; 24 ;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
@@ -57,7 +57,7 @@ public class BpmModelController { @@ -57,7 +57,7 @@ public class BpmModelController {
57 @GetMapping("/list") 57 @GetMapping("/list")
58 @Operation(summary = "获得模型分页") 58 @Operation(summary = "获得模型分页")
59 @Parameter(name = "name", description = "模型名称", example = "芋艿") 59 @Parameter(name = "name", description = "模型名称", example = "芋艿")
60 - public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) { 60 + public CommonResult<List<BpmModelRespVO>> getModelList(@RequestParam(value = "name", required = false) String name) {
61 List<Model> list = modelService.getModelList(name); 61 List<Model> list = modelService.getModelList(name);
62 if (CollUtil.isEmpty(list)) { 62 if (CollUtil.isEmpty(list)) {
63 return success(Collections.emptyList()); 63 return success(Collections.emptyList());
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
@@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.Parameter; @@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.Parameter;
17 import io.swagger.v3.oas.annotations.tags.Tag; 17 import io.swagger.v3.oas.annotations.tags.Tag;
18 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
19 import org.flowable.bpmn.model.BpmnModel; 19 import org.flowable.bpmn.model.BpmnModel;
  20 +import org.flowable.common.engine.impl.db.SuspensionState;
20 import org.flowable.engine.repository.Deployment; 21 import org.flowable.engine.repository.Deployment;
21 import org.flowable.engine.repository.ProcessDefinition; 22 import org.flowable.engine.repository.ProcessDefinition;
22 import org.springframework.security.access.prepost.PreAuthorize; 23 import org.springframework.security.access.prepost.PreAuthorize;
@@ -31,6 +32,7 @@ import java.util.List; @@ -31,6 +32,7 @@ import java.util.List;
31 import java.util.Map; 32 import java.util.Map;
32 33
33 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 34 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 36 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
35 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 37 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
36 38
@@ -99,6 +101,17 @@ public class BpmProcessDefinitionController { @@ -99,6 +101,17 @@ public class BpmProcessDefinitionController {
99 list, null, processDefinitionMap, null, null)); 101 list, null, processDefinitionMap, null, null));
100 } 102 }
101 103
  104 + @GetMapping("/simple-list")
  105 + @Operation(summary = "获得流程定义精简列表", description = "只包含未挂起的流程,主要用于前端的下拉选项")
  106 + public CommonResult<List<BpmProcessDefinitionRespVO>> getSimpleProcessDefinitionList() {
  107 + // 只查询未挂起的流程
  108 + List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(
  109 + SuspensionState.ACTIVE.getStateCode());
  110 + // 拼接 VO 返回,只返回 id、name、key
  111 + return success(convertList(list, definition -> new BpmProcessDefinitionRespVO()
  112 + .setId(definition.getId()).setName(definition.getName()).setKey(definition.getKey())));
  113 + }
  114 +
102 @GetMapping ("/get") 115 @GetMapping ("/get")
103 @Operation(summary = "获得流程定义") 116 @Operation(summary = "获得流程定义")
104 @Parameter(name = "id", description = "流程编号", required = true, example = "1024") 117 @Parameter(name = "id", description = "流程编号", required = true, example = "1024")
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 { @@ -25,7 +25,7 @@ public class BpmModelRespVO extends BpmModelMetaInfoVO {
25 @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") 25 @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
26 private String icon; 26 private String icon;
27 27
28 - @Schema(description = "流程分类编码", example = "1") 28 + @Schema(description = "流程分类编号", example = "1")
29 private String category; 29 private String category;
30 @Schema(description = "流程分类名字", example = "请假") 30 @Schema(description = "流程分类名字", example = "请假")
31 private String categoryName; 31 private String categoryName;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
@@ -509,7 +509,7 @@ public class BpmSimpleModelNodeVO { @@ -509,7 +509,7 @@ public class BpmSimpleModelNodeVO {
509 509
510 @Schema(description = "完成比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") 510 @Schema(description = "完成比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
511 @NotNull(message = "完成比例不能为空") 511 @NotNull(message = "完成比例不能为空")
512 - private Integer completeRatio; // TODO @lesan:approveRatio 要不这个,和上面保持一致? 512 + private Integer approveRatio;
513 513
514 @Schema(description = "多实例来源类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 514 @Schema(description = "多实例来源类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
515 @NotNull(message = "多实例来源类型不能为空") 515 @NotNull(message = "多实例来源类型不能为空")
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process; 1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process;
2 2
  3 +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
3 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 5 import lombok.Data;
5 6
6 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
7 -import java.util.List;  
8 8
9 @Schema(description = "管理后台 - 流程定义 Response VO") 9 @Schema(description = "管理后台 - 流程定义 Response VO")
10 @Data 10 @Data
11 -public class BpmProcessDefinitionRespVO { 11 +public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
12 12
13 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 13 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
14 private String id; 14 private String id;
@@ -22,36 +22,19 @@ public class BpmProcessDefinitionRespVO { @@ -22,36 +22,19 @@ public class BpmProcessDefinitionRespVO {
22 @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") 22 @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
23 private String key; 23 private String key;
24 24
25 - @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")  
26 - private String icon;  
27 -  
28 - @Schema(description = "流程描述", example = "我是描述")  
29 - private String description;  
30 -  
31 @Schema(description = "流程分类", example = "1") 25 @Schema(description = "流程分类", example = "1")
32 private String category; 26 private String category;
33 @Schema(description = "流程分类名字", example = "请假") 27 @Schema(description = "流程分类名字", example = "请假")
34 private String categoryName; 28 private String categoryName;
35 29
  30 + @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
  31 + private String modelId;
  32 +
36 @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") 33 @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
37 private Integer modelType; // 参见 BpmModelTypeEnum 枚举类 34 private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
38 35
39 - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")  
40 - private Integer formType;  
41 - @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024")  
42 - private Long formId;  
43 @Schema(description = "表单名字", example = "请假表单") 36 @Schema(description = "表单名字", example = "请假表单")
44 private String formName; 37 private String formName;
45 - @Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)  
46 - private String formConf;  
47 - @Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)  
48 - private List<String> formFields;  
49 - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空",  
50 - example = "/bpm/oa/leave/create")  
51 - private String formCustomCreatePath;  
52 - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空",  
53 - example = "/bpm/oa/leave/view")  
54 - private String formCustomViewPath;  
55 38
56 @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 39 @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
57 private Integer suspensionState; // 参见 SuspensionState 枚举 40 private Integer suspensionState; // 参见 SuspensionState 枚举
@@ -67,7 +50,7 @@ public class BpmProcessDefinitionRespVO { @@ -67,7 +50,7 @@ public class BpmProcessDefinitionRespVO {
67 50
68 @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 51 @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
69 private Long sort; 52 private Long sort;
70 - 53 +
71 @Schema(description = "BPMN UserTask 用户任务") 54 @Schema(description = "BPMN UserTask 用户任务")
72 @Data 55 @Data
73 public static class UserTask { 56 public static class UserTask {
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
@@ -32,10 +32,10 @@ import org.springframework.web.bind.annotation.*; @@ -32,10 +32,10 @@ import org.springframework.web.bind.annotation.*;
32 32
33 import java.util.List; 33 import java.util.List;
34 import java.util.Map; 34 import java.util.Map;
  35 +import java.util.Set;
35 36
36 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
37 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;  
38 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 38 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
39 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 39 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
40 40
41 @Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请” 41 @Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请”
@@ -78,8 +78,14 @@ public class BpmProcessInstanceController { @@ -78,8 +78,14 @@ public class BpmProcessInstanceController {
78 convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory)); 78 convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory));
79 Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap( 79 Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
80 convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId)); 80 convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId));
  81 + Set<Long> userIds = convertSet(pageResult.getList(), processInstance -> NumberUtils.parseLong(processInstance.getStartUserId()));
  82 + userIds.addAll(convertSetByFlatMap(taskMap.values(),
  83 + tasks -> tasks.stream().map(Task::getAssignee).filter(StrUtil::isNotBlank).map(Long::parseLong)));
  84 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  85 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
  86 + convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
81 return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult, 87 return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult,
82 - processDefinitionMap, categoryMap, taskMap, null, null, processDefinitionInfoMap)); 88 + processDefinitionMap, categoryMap, taskMap, userMap, deptMap, processDefinitionInfoMap));
83 } 89 }
84 90
85 @GetMapping("/manager-page") 91 @GetMapping("/manager-page")
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; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
3 import cn.iocoder.yudao.framework.common.core.KeyValue; 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; 4 import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
  6 +import com.fasterxml.jackson.annotation.JsonIgnore;
6 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 8 import lombok.Data;
8 9
@@ -73,6 +74,13 @@ public class BpmProcessInstanceRespVO { @@ -73,6 +74,13 @@ public class BpmProcessInstanceRespVO {
73 @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") 74 @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
74 private String name; 75 private String name;
75 76
  77 + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
  78 + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
  79 + private Long assignee;
  80 +
  81 + @Schema(description = "任务分配人", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
  82 + private UserSimpleBaseVO assigneeUser;
  83 +
76 } 84 }
77 85
78 } 86 }
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; @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
5 import lombok.Data; 5 import lombok.Data;
6 6
  7 +import java.util.List;
7 import java.util.Map; 8 import java.util.Map;
8 9
9 @Schema(description = "管理后台 - 通过流程任务的 Request VO") 10 @Schema(description = "管理后台 - 通过流程任务的 Request VO")
@@ -23,4 +24,7 @@ public class BpmTaskApproveReqVO { @@ -23,4 +24,7 @@ public class BpmTaskApproveReqVO {
23 @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) 24 @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
24 private Map<String, Object> variables; 25 private Map<String, Object> variables;
25 26
  27 + @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}")
  28 + private Map<String, List<Long>> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况
  29 +
26 } 30 }
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 { @@ -18,6 +18,9 @@ public class BpmTaskPageReqVO extends PageParam {
18 @Schema(description = "流程分类", example = "1") 18 @Schema(description = "流程分类", example = "1")
19 private String category; 19 private String category;
20 20
  21 + @Schema(description = "流程定义的标识", example = "2048")
  22 + private String processDefinitionKey; // 精准匹配
  23 +
21 @Schema(description = "创建时间") 24 @Schema(description = "创建时间")
22 @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 25 @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
23 private LocalDateTime[] createTime; 26 private LocalDateTime[] createTime;
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 { @@ -76,6 +76,15 @@ public interface BpmProcessInstanceConvert {
76 respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); 76 respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
77 MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName())); 77 MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName()));
78 } 78 }
  79 + if (CollUtil.isNotEmpty(respVO.getTasks())) {
  80 + respVO.getTasks().forEach(task -> {
  81 + AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee());
  82 + if (assigneeUser!= null) {
  83 + task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class));
  84 + MapUtils.findAndThen(deptMap, assigneeUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName()));
  85 + }
  86 + });
  87 + }
79 } 88 }
80 // 摘要 89 // 摘要
81 respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()), 90 respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()),
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; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; 4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
5 -import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;  
6 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; 5 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum; 6 import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
8 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; 7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
@@ -61,6 +60,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @@ -61,6 +60,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
61 private Integer modelType; 60 private Integer modelType;
62 61
63 /** 62 /**
  63 + * 流程分类的编码
  64 + *
  65 + * 关联 {@link BpmCategoryDO#getCode()}
  66 + *
  67 + * 为什么要存储?原因是,{@link ProcessDefinition#getCategory()} 无法设置
  68 + */
  69 + private String category;
  70 + /**
64 * 图标 71 * 图标
65 */ 72 */
66 private String icon; 73 private String icon;
@@ -149,7 +156,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @@ -149,7 +156,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
149 * 156 *
150 * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 157 * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
151 */ 158 */
152 - @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 159 + @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
153 private List<Long> managerUserIds; 160 private List<Long> managerUserIds;
154 161
155 /** 162 /**
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
@@ -77,10 +77,10 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav @@ -77,10 +77,10 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
77 if (execution.getCurrentFlowElement() instanceof CallActivity) { 77 if (execution.getCurrentFlowElement() instanceof CallActivity) {
78 FlowElement flowElement = execution.getCurrentFlowElement(); 78 FlowElement flowElement = execution.getCurrentFlowElement();
79 Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement); 79 Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
80 - if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.DIGITAL_FORM.getType())) { 80 + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) {
81 return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class); 81 return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
82 } 82 }
83 - if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTI_FORM.getType())) { 83 + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
84 return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size(); 84 return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
85 } 85 }
86 } 86 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
@@ -71,10 +71,10 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB @@ -71,10 +71,10 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
71 if (execution.getCurrentFlowElement() instanceof CallActivity) { 71 if (execution.getCurrentFlowElement() instanceof CallActivity) {
72 FlowElement flowElement = execution.getCurrentFlowElement(); 72 FlowElement flowElement = execution.getCurrentFlowElement();
73 Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement); 73 Integer sourceType = BpmnModelUtils.parseMultiInstanceSourceType(flowElement);
74 - if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.DIGITAL_FORM.getType())) { 74 + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType())) {
75 return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class); 75 return execution.getVariable(super.collectionExpression.getExpressionText(), Integer.class);
76 } 76 }
77 - if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTI_FORM.getType())) { 77 + if (sourceType.equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
78 return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size(); 78 return execution.getVariable(super.collectionExpression.getExpressionText(), List.class).size();
79 } 79 }
80 } 80 }
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 @@ -2,24 +2,21 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.d
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 -import cn.hutool.core.util.ObjectUtil;  
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
8 -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;  
9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
10 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 8 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
11 import com.google.common.collect.Sets; 9 import com.google.common.collect.Sets;
12 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
13 import org.flowable.bpmn.model.BpmnModel; 11 import org.flowable.bpmn.model.BpmnModel;
14 -import org.flowable.bpmn.model.ServiceTask;  
15 -import org.flowable.bpmn.model.Task;  
16 -import org.flowable.bpmn.model.UserTask;  
17 import org.flowable.engine.delegate.DelegateExecution; 12 import org.flowable.engine.delegate.DelegateExecution;
18 import org.flowable.engine.runtime.ProcessInstance; 13 import org.flowable.engine.runtime.ProcessInstance;
19 import org.springframework.context.annotation.Lazy; 14 import org.springframework.context.annotation.Lazy;
20 import org.springframework.stereotype.Component; 15 import org.springframework.stereotype.Component;
21 16
22 -import java.util.*; 17 +import java.util.LinkedHashSet;
  18 +import java.util.List;
  19 +import java.util.Map;
23 20
24 /** 21 /**
25 * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类 22 * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类
@@ -55,7 +52,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand @@ -55,7 +52,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
55 execution.getProcessInstanceId()); 52 execution.getProcessInstanceId());
56 // 获得审批人 53 // 获得审批人
57 List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); 54 List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
58 - return new LinkedHashSet<>(assignees); 55 + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
59 } 56 }
60 57
61 @Override 58 @Override
@@ -70,28 +67,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand @@ -70,28 +67,7 @@ public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCand
70 } 67 }
71 // 获得审批人 68 // 获得审批人
72 List<Long> assignees = startUserSelectAssignees.get(activityId); 69 List<Long> assignees = startUserSelectAssignees.get(activityId);
73 - return new LinkedHashSet<>(assignees);  
74 - }  
75 -  
76 - /**  
77 - * 获得发起人自选审批人或抄送人的 Task 列表  
78 - *  
79 - * @param bpmnModel BPMN 模型  
80 - * @return Task 列表  
81 - */  
82 - public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) {  
83 - if (bpmnModel == null) {  
84 - return Collections.emptyList();  
85 - }  
86 - List<Task> tasks = new ArrayList<>();  
87 - tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class));  
88 - tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class));  
89 - if (CollUtil.isEmpty(tasks)) {  
90 - return Collections.emptyList();  
91 - }  
92 - tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task),  
93 - BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));  
94 - return tasks; 70 + return CollUtil.isNotEmpty(assignees) ? new LinkedHashSet<>(assignees) : Sets.newLinkedHashSet();
95 } 71 }
96 72
97 } 73 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -809,6 +809,7 @@ public class BpmnModelUtils { @@ -809,6 +809,7 @@ public class BpmnModelUtils {
809 if (currentElement instanceof ExclusiveGateway) { 809 if (currentElement instanceof ExclusiveGateway) {
810 // 查找满足条件的 SequenceFlow 路径 810 // 查找满足条件的 SequenceFlow 路径
811 Gateway gateway = (Gateway) currentElement; 811 Gateway gateway = (Gateway) currentElement;
  812 + // TODO @小北:当一个网关节点下存在多个满足的并行节点时,只查询一个节点流程流转会存在问题。需要优化,具体见issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/761
812 SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), 813 SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
813 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) 814 flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
814 && (evalConditionExpress(variables, flow.getConditionExpression()))); 815 && (evalConditionExpress(variables, flow.getConditionExpression())));
@@ -858,6 +859,130 @@ public class BpmnModelUtils { @@ -858,6 +859,130 @@ public class BpmnModelUtils {
858 } 859 }
859 860
860 /** 861 /**
  862 + * 根据当前节点,获取下一个节点
  863 + *
  864 + * @param currentElement 当前节点
  865 + * @param bpmnModel BPMN模型
  866 + * @param variables 流程变量
  867 + */
  868 + @SuppressWarnings("PatternVariableCanBeUsed")
  869 + public static List<FlowNode> getNextFlowNodes(FlowElement currentElement, BpmnModel bpmnModel,
  870 + Map<String, Object> variables){
  871 + List<FlowNode> nextFlowNodes = new ArrayList<>(); // 下一个执行的流程节点集合
  872 + FlowNode currentNode = (FlowNode) currentElement; // 当前执行节点的基本属性
  873 + List<SequenceFlow> outgoingFlows = currentNode.getOutgoingFlows(); // 当前节点的关联节点
  874 + if (CollUtil.isEmpty(outgoingFlows)) {
  875 + log.warn("[getNextFlowNodes][当前节点({}) 的 outgoingFlows 为空]", currentNode.getId());
  876 + return nextFlowNodes;
  877 + }
  878 +
  879 + // 遍历每个出口流
  880 + for (SequenceFlow outgoingFlow : outgoingFlows) {
  881 + // 获取目标节点的基本属性
  882 + FlowElement targetElement = bpmnModel.getFlowElement(outgoingFlow.getTargetRef());
  883 + if (targetElement == null) {
  884 + continue;
  885 + }
  886 + // 情况一:处理不同类型的网关
  887 + if (targetElement instanceof Gateway) {
  888 + Gateway gateway = (Gateway) targetElement;
  889 + if (gateway instanceof ExclusiveGateway) {
  890 + handleExclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
  891 + } else if (gateway instanceof InclusiveGateway) {
  892 + handleInclusiveGateway(gateway, bpmnModel, variables, nextFlowNodes);
  893 + } else if (gateway instanceof ParallelGateway) {
  894 + handleParallelGateway(gateway, bpmnModel, variables, nextFlowNodes);
  895 + }
  896 + } else {
  897 + // 情况二:如果不是网关,直接添加到下一个节点列表
  898 + nextFlowNodes.add((FlowNode) targetElement);
  899 + }
  900 + }
  901 + return nextFlowNodes;
  902 + }
  903 +
  904 + /**
  905 + * 处理排它网关
  906 + *
  907 + * @param gateway 排他网关
  908 + * @param bpmnModel BPMN模型
  909 + * @param variables 流程变量
  910 + * @param nextFlowNodes 下一个执行的流程节点集合
  911 + */
  912 + private static void handleExclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
  913 + Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  914 + // TODO @小北: 这里和 simulateNextFlowElements 中有重复代码,是否重构??每个网关节点拆分出方法应该比较合理化,@芋艿
  915 + // TODO @小北:ok,把 simulateNextFlowElements 里面处理网关的,复用这个方法,可以么?
  916 + SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  917 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  918 + && (evalConditionExpress(variables, flow.getConditionExpression())));
  919 + if (matchSequenceFlow == null) {
  920 + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
  921 + flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
  922 + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
  923 + if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
  924 + matchSequenceFlow = gateway.getOutgoingFlows().get(0);
  925 + }
  926 + }
  927 + // 遍历满足条件的 SequenceFlow 路径
  928 + if (matchSequenceFlow != null) {
  929 + FlowElement targetElement = bpmnModel.getFlowElement(matchSequenceFlow.getTargetRef());
  930 + if (targetElement instanceof FlowNode) {
  931 + nextFlowNodes.add((FlowNode) targetElement);
  932 + }
  933 + }
  934 + }
  935 +
  936 + /**
  937 + * 处理包容网关
  938 + *
  939 + * @param gateway 排他网关
  940 + * @param bpmnModel BPMN模型
  941 + * @param variables 流程变量
  942 + * @param nextFlowNodes 下一个执行的流程节点集合
  943 + */
  944 + private static void handleInclusiveGateway(Gateway gateway, BpmnModel bpmnModel,
  945 + Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  946 + Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
  947 + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
  948 + && evalConditionExpress(variables, flow.getConditionExpression()));
  949 + if (CollUtil.isEmpty(matchSequenceFlows)) {
  950 + matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
  951 + flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
  952 + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
  953 + if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
  954 + matchSequenceFlows = gateway.getOutgoingFlows();
  955 + }
  956 + }
  957 + // 遍历满足条件的 SequenceFlow 路径,获取目标节点
  958 + matchSequenceFlows.forEach(flow -> {
  959 + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
  960 + if (targetElement instanceof FlowNode) {
  961 + nextFlowNodes.add((FlowNode) targetElement);
  962 + }
  963 + });
  964 + }
  965 +
  966 + /**
  967 + * 处理并行网关
  968 + *
  969 + * @param gateway 排他网关
  970 + * @param bpmnModel BPMN模型
  971 + * @param variables 流程变量
  972 + * @param nextFlowNodes 下一个执行的流程节点集合
  973 + */
  974 + private static void handleParallelGateway(Gateway gateway, BpmnModel bpmnModel,
  975 + Map<String, Object> variables, List<FlowNode> nextFlowNodes) {
  976 + // 并行网关,遍历所有出口路径,获取目标节点
  977 + gateway.getOutgoingFlows().forEach(flow -> {
  978 + FlowElement targetElement = bpmnModel.getFlowElement(flow.getTargetRef());
  979 + if (targetElement instanceof FlowNode) {
  980 + nextFlowNodes.add((FlowNode) targetElement);
  981 + }
  982 + });
  983 + }
  984 +
  985 + /**
861 * 计算条件表达式是否为 true 满足条件 986 * 计算条件表达式是否为 true 满足条件
862 * 987 *
863 * @param variables 流程实例 988 * @param variables 流程实例
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -25,7 +25,6 @@ import org.springframework.util.MultiValueMap; @@ -25,7 +25,6 @@ import org.springframework.util.MultiValueMap;
25 25
26 import java.util.*; 26 import java.util.*;
27 27
28 -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum.HTTP_CALLBACK;  
29 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 28 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
30 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 29 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
31 import static java.util.Arrays.asList; 30 import static java.util.Arrays.asList;
@@ -737,13 +736,12 @@ public class SimpleModelUtils { @@ -737,13 +736,12 @@ public class SimpleModelUtils {
737 736
738 public static class TriggerNodeConvert implements NodeConvert { 737 public static class TriggerNodeConvert implements NodeConvert {
739 738
740 - // TODO @芋艿:【回调】在看看  
741 @Override 739 @Override
742 public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) { 740 public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) {
743 Assert.notNull(node.getTriggerSetting(), "触发器节点设置不能为空"); 741 Assert.notNull(node.getTriggerSetting(), "触发器节点设置不能为空");
744 List<FlowElement> flowElements = new ArrayList<>(2); 742 List<FlowElement> flowElements = new ArrayList<>(2);
745 // HTTP 回调请求。需要附加一个 ReceiveTask、发起请求后、等待回调执行 743 // HTTP 回调请求。需要附加一个 ReceiveTask、发起请求后、等待回调执行
746 - if (HTTP_CALLBACK.getType().equals(node.getTriggerSetting().getType())) { 744 + if (BpmTriggerTypeEnum.HTTP_CALLBACK.getType().equals(node.getTriggerSetting().getType())) {
747 Assert.notNull(node.getTriggerSetting().getHttpRequestSetting(), "触发器 HTTP 回调请求设置不能为空"); 745 Assert.notNull(node.getTriggerSetting().getHttpRequestSetting(), "触发器 HTTP 回调请求设置不能为空");
748 ReceiveTask receiveTask = new ReceiveTask(); 746 ReceiveTask receiveTask = new ReceiveTask();
749 receiveTask.setId("Activity_" + IdUtil.fastUUID()); 747 receiveTask.setId("Activity_" + IdUtil.fastUUID());
@@ -875,13 +873,12 @@ public class SimpleModelUtils { @@ -875,13 +873,12 @@ public class SimpleModelUtils {
875 if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY.getType())) { 873 if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY.getType())) {
876 multiInstanceCharacteristics.setLoopCardinality(childProcessSetting.getMultiInstanceSetting().getSource()); 874 multiInstanceCharacteristics.setLoopCardinality(childProcessSetting.getMultiInstanceSetting().getSource());
877 } 875 }
878 - if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.DIGITAL_FORM.getType()) ||  
879 - childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTI_FORM.getType())) { 876 + if (childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM.getType()) ||
  877 + childProcessSetting.getMultiInstanceSetting().getSourceType().equals(BpmChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM.getType())) {
880 multiInstanceCharacteristics.setInputDataItem(childProcessSetting.getMultiInstanceSetting().getSource()); 878 multiInstanceCharacteristics.setInputDataItem(childProcessSetting.getMultiInstanceSetting().getSource());
881 } 879 }
882 -// TODO @lesan:String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D)));  
883 - multiInstanceCharacteristics.setCompletionCondition(String.format("${ nrOfCompletedInstances/nrOfInstances >= %s}",  
884 - String.format("%.2f", childProcessSetting.getMultiInstanceSetting().getCompleteRatio() / 100D))); 880 + multiInstanceCharacteristics.setCompletionCondition(String.format(BpmUserTaskApproveMethodEnum.RATIO.getCompletionCondition(),
  881 + String.format("%.2f", childProcessSetting.getMultiInstanceSetting().getApproveRatio() / 100D)));
885 callActivity.setLoopCharacteristics(multiInstanceCharacteristics); 882 callActivity.setLoopCharacteristics(multiInstanceCharacteristics);
886 addExtensionElement(callActivity, CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE, childProcessSetting.getMultiInstanceSetting().getSourceType()); 883 addExtensionElement(callActivity, CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE, childProcessSetting.getMultiInstanceSetting().getSourceType());
887 } 884 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
@@ -143,9 +143,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @@ -143,9 +143,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
143 143
144 // 插入拓展表 144 // 插入拓展表
145 BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) 145 BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class)
146 - .setModelId(model.getId()).setProcessDefinitionId(definition.getId()) 146 + .setModelId(model.getId()).setCategory(model.getCategory()).setProcessDefinitionId(definition.getId())
147 .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson); 147 .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson);
148 -  
149 if (form != null) { 148 if (form != null) {
150 definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); 149 definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf());
151 } 150 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
@@ -85,7 +85,6 @@ public interface BpmProcessInstanceService { @@ -85,7 +85,6 @@ public interface BpmProcessInstanceService {
85 PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, 85 PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
86 @Valid BpmProcessInstancePageReqVO pageReqVO); 86 @Valid BpmProcessInstancePageReqVO pageReqVO);
87 87
88 - // TODO @芋艿:重点在 review 下  
89 /** 88 /**
90 * 获取审批详情。 89 * 获取审批详情。
91 * <p> 90 * <p>
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -175,7 +175,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -175,7 +175,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
175 } 175 }
176 startUserId = Long.valueOf(historicProcessInstance.getStartUserId()); 176 startUserId = Long.valueOf(historicProcessInstance.getStartUserId());
177 processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance); 177 processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance);
178 - processVariables = historicProcessInstance.getProcessVariables(); 178 + // 合并 DB 和前端传递的流量变量,以前端的为主
  179 + Map<String, Object> historicVariables = historicProcessInstance.getProcessVariables();
  180 + if (CollUtil.isNotEmpty(processVariables)) {
  181 + historicVariables.putAll(processVariables);
  182 + }
  183 + processVariables = historicVariables;
179 } 184 }
180 // 1.3 读取其它相关数据 185 // 1.3 读取其它相关数据
181 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( 186 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; @@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
20 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; 20 import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
21 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; 21 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
22 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; 22 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
  23 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
  24 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
23 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; 25 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
24 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 26 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
25 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 27 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@@ -120,6 +122,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -120,6 +122,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
120 if (StrUtil.isNotEmpty(pageVO.getCategory())) { 122 if (StrUtil.isNotEmpty(pageVO.getCategory())) {
121 taskQuery.taskCategory(pageVO.getCategory()); 123 taskQuery.taskCategory(pageVO.getCategory());
122 } 124 }
  125 + if (StrUtil.isNotEmpty(pageVO.getProcessDefinitionKey())) {
  126 + taskQuery.processDefinitionKey(pageVO.getProcessDefinitionKey());
  127 + }
123 if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { 128 if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
124 taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); 129 taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
125 taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); 130 taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
@@ -548,7 +553,21 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -548,7 +553,21 @@ public class BpmTaskServiceImpl implements BpmTaskService {
548 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 553 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
549 if (CollUtil.isNotEmpty(reqVO.getVariables())) { 554 if (CollUtil.isNotEmpty(reqVO.getVariables())) {
550 Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); 555 Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
551 - // 修改表单的值需要存储到 ProcessInstance 变量 556 + // 校验传递的参数中是否为下一个将要执行的任务节点
  557 + validateNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(), bpmnModel, reqVO.getNextAssignees(), instance);
  558 + // 如果有下一个审批人,则设置到流程变量中
  559 + // TODO @小北:validateNextAssignees 升级成 validateAndSetNextAssignees,然后里面吧下面这一小段逻辑,抽进去如何?
  560 + if (CollUtil.isNotEmpty(reqVO.getNextAssignees())) {
  561 + // 获取实例中的全部节点数据,避免后续节点的审批人被覆盖
  562 + // TODO @小北:这里有个需要讨论的点,微信哈;
  563 + // TODO 因为 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES 定位是发起人,那么审批人选择的,放在 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES。目前想到两个方案:
  564 + // TODO 方案一:增加一个 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,然后设置到这里面。然后,BpmTaskCandidateStartUserSelectStrategy 也从这里读
  565 + // TODO 方案二:也是增加一个 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES,根据节点审批人类型,放到 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES、PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES
  566 + // TODO 方案三:融合成 PROCESS_INSTANCE_VARIABLE_USER_SELECT_ASSIGNEES,不再区分是发起人选择、还是审批人选择。
  567 + Map<String, List<Long>> hisProcessVariables = FlowableUtils.getStartUserSelectAssignees(instance.getProcessVariables());
  568 + hisProcessVariables.putAll(reqVO.getNextAssignees());
  569 + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, hisProcessVariables);
  570 + }
552 runtimeService.setVariables(task.getProcessInstanceId(), variables); 571 runtimeService.setVariables(task.getProcessInstanceId(), variables);
553 taskService.complete(task.getId(), variables, true); 572 taskService.complete(task.getId(), variables, true);
554 } else { 573 } else {
@@ -559,6 +578,57 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -559,6 +578,57 @@ public class BpmTaskServiceImpl implements BpmTaskService {
559 handleParentTaskIfSign(task.getParentTaskId()); 578 handleParentTaskIfSign(task.getParentTaskId());
560 } 579 }
561 580
  581 +
  582 + /**
  583 + * 校验选择的下一个节点的审批人,是否合法
  584 + *
  585 + * 1. 是否有漏选:没有选择审批人
  586 + * 2. 是否有多选:非下一个节点
  587 + *
  588 + * @param taskDefinitionKey 当前任务节点标识
  589 + * @param variables 流程变量
  590 + * @param bpmnModel 流程模型
  591 + * @param nextAssignees 下一个节点审批人集合(参数)
  592 + * @param processInstance 流程实例
  593 + */
  594 + private void validateNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
  595 + Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
  596 + // 1. 获取当前任务节点的信息
  597 + FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
  598 + // 2. 获取下一个将要执行的节点集合
  599 + List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
  600 +
  601 + // 3. 循环下一个将要执行的节点集合
  602 + for (FlowNode nextFlowNode : nextFlowNodes) {
  603 + // 3.1 获取下一个将要执行节点的属性(是否为自选审批人等)
  604 + // TODO @小北:public static Integer parseCandidateStrategy(FlowElement userTask) 使用这个工具方法哈。
  605 + Map<String, List<ExtensionElement>> extensionElements = nextFlowNode.getExtensionElements();
  606 + List<ExtensionElement> elements = extensionElements.get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY);
  607 + if (CollUtil.isEmpty(elements)) {
  608 + continue;
  609 + }
  610 + // 3.2 获取节点中的审批人策略
  611 + Integer candidateStrategy = Integer.valueOf(elements.get(0).getElementText());
  612 + // 3.3 获取流程实例中的发起人自选审批人
  613 + Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
  614 + List<Long> startUserSelectAssignee = startUserSelectAssignees.get(nextFlowNode.getId());
  615 + // 3.4 如果节点中的审批人策略为 发起人自选,并且该节点的审批人为空
  616 + if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()) && CollUtil.isEmpty(startUserSelectAssignee)) {
  617 + // 先判断前端传递的参数节点节点是否为将要执行的节点
  618 + // TODO @小北:!nextAssignees.containsKey(nextFlowNode.getId())、和 CollUtil.isEmpty(nextAssignees.get(nextFlowNode.getId()))) 是不是等价的?
  619 + if (!nextAssignees.containsKey(nextFlowNode.getId())) {
  620 + throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
  621 + }
  622 + // 如果前端传递的节点为空,则抛出异常
  623 + // TODO @小北:换一个错误码哈。
  624 + if (CollUtil.isEmpty(nextAssignees.get(nextFlowNode.getId()))) {
  625 + throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
  626 + }
  627 + }
  628 + // TODO @小北:加一个“审批人选择”的校验;
  629 + }
  630 + }
  631 +
562 /** 632 /**
563 * 审批通过存在“后加签”的任务。 633 * 审批通过存在“后加签”的任务。
564 * <p> 634 * <p>
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java
@@ -42,8 +42,8 @@ public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger { @@ -42,8 +42,8 @@ public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger {
42 MultiValueMap<String, String> headers = buildHttpHeaders(processInstance, setting.getHeader()); 42 MultiValueMap<String, String> headers = buildHttpHeaders(processInstance, setting.getHeader());
43 // 2.2 设置请求体 43 // 2.2 设置请求体
44 MultiValueMap<String, String> body = buildHttpBody(processInstance, setting.getBody()); 44 MultiValueMap<String, String> body = buildHttpBody(processInstance, setting.getBody());
45 - // TODO @芋艿:【回调】在看看  
46 - body.add("callbackId", setting.getCallbackTaskDefineKey()); // 回调请求 callbackId 需要传给被调用方,用于回调执行 45 + // 重要:回调请求 taskDefineKey 需要传给被调用方,用于回调执行
  46 + body.add("taskDefineKey", setting.getCallbackTaskDefineKey());
47 47
48 // 3. 发起请求 48 // 3. 发起请求
49 sendHttpRequest(setting.getUrl(), headers, body); 49 sendHttpRequest(setting.getUrl(), headers, body);