Commit 50418b25198f1a6271a5064b335e23f84e4df422
1 parent
ab7a3588
【功能完善】工作流:审批详情 approve-nodes => activity-nodes 字段
Showing
9 changed files
with
125 additions
and
53 deletions
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java
| @@ -9,11 +9,14 @@ public class UserSimpleBaseVO { | @@ -9,11 +9,14 @@ public class UserSimpleBaseVO { | ||
| 9 | 9 | ||
| 10 | @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | 10 | @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 11 | private Long id; | 11 | private Long id; |
| 12 | - | ||
| 13 | @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") | 12 | @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") |
| 14 | private String nickname; | 13 | private String nickname; |
| 15 | - | ||
| 16 | @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") | 14 | @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") |
| 17 | private String avatar; | 15 | private String avatar; |
| 18 | 16 | ||
| 17 | + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||
| 18 | + private Long deptId; | ||
| 19 | + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") | ||
| 20 | + private String deptName; | ||
| 21 | + | ||
| 19 | } | 22 | } |
| 20 | \ No newline at end of file | 23 | \ No newline at end of file |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http
| @@ -8,7 +8,9 @@ Authorization: Bearer {{token}} | @@ -8,7 +8,9 @@ Authorization: Bearer {{token}} | ||
| 8 | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3 | 8 | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3 |
| 9 | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911 | 9 | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911 |
| 10 | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4 | 10 | #GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4 |
| 11 | -GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932 | 11 | +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932 |
| 12 | +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b | ||
| 13 | +GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b | ||
| 12 | Content-Type: application/json | 14 | Content-Type: application/json |
| 13 | tenant-id: 1 | 15 | tenant-id: 1 |
| 14 | Authorization: Bearer {{token}} | 16 | Authorization: Bearer {{token}} |
| 15 | \ No newline at end of file | 17 | \ No newline at end of file |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
| @@ -19,8 +19,8 @@ public class BpmApprovalDetailRespVO { | @@ -19,8 +19,8 @@ public class BpmApprovalDetailRespVO { | ||
| 19 | @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | 19 | @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 20 | private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 | 20 | private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 |
| 21 | 21 | ||
| 22 | - @Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED) | ||
| 23 | - private List<NodeInfo> approveNodes; | 22 | + @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED) |
| 23 | + private List<ActivityNode> activityNodes; | ||
| 24 | 24 | ||
| 25 | @Schema(description = "表单字段权限") | 25 | @Schema(description = "表单字段权限") |
| 26 | private Map<String, String> formFieldsPermission; | 26 | private Map<String, String> formFieldsPermission; |
| @@ -38,9 +38,9 @@ public class BpmApprovalDetailRespVO { | @@ -38,9 +38,9 @@ public class BpmApprovalDetailRespVO { | ||
| 38 | */ | 38 | */ |
| 39 | private BpmProcessInstanceRespVO processInstance; | 39 | private BpmProcessInstanceRespVO processInstance; |
| 40 | 40 | ||
| 41 | - @Schema(description = "审批节点信息") | 41 | + @Schema(description = "活动节点信息") |
| 42 | @Data | 42 | @Data |
| 43 | - public static class NodeInfo { | 43 | + public static class ActivityNode { |
| 44 | 44 | ||
| 45 | @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") | 45 | @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") |
| 46 | private String id; | 46 | private String id; |
| @@ -60,7 +60,7 @@ public class BpmApprovalDetailRespVO { | @@ -60,7 +60,7 @@ public class BpmApprovalDetailRespVO { | ||
| 60 | private LocalDateTime endTime; | 60 | private LocalDateTime endTime; |
| 61 | 61 | ||
| 62 | @Schema(description = "审批节点的任务信息") | 62 | @Schema(description = "审批节点的任务信息") |
| 63 | - private List<ApprovalTaskInfo> tasks; | 63 | + private List<ActivityNodeTask> tasks; |
| 64 | 64 | ||
| 65 | @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") | 65 | @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") |
| 66 | @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers | 66 | @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers |
| @@ -71,9 +71,9 @@ public class BpmApprovalDetailRespVO { | @@ -71,9 +71,9 @@ public class BpmApprovalDetailRespVO { | ||
| 71 | 71 | ||
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | - @Schema(description = "审批任务信息") | 74 | + @Schema(description = "活动节点的任务信息") |
| 75 | @Data | 75 | @Data |
| 76 | - public static class ApprovalTaskInfo { | 76 | + public static class ActivityNodeTask { |
| 77 | 77 | ||
| 78 | @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | 78 | @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 79 | private String id; | 79 | private String id; |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
| 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | ||
| 3 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; | 4 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| 4 | import io.swagger.v3.oas.annotations.media.Schema; | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | import lombok.Data; | 6 | import lombok.Data; |
| @@ -44,7 +45,7 @@ public class BpmProcessInstanceRespVO { | @@ -44,7 +45,7 @@ public class BpmProcessInstanceRespVO { | ||
| 44 | /** | 45 | /** |
| 45 | * 发起流程的用户 | 46 | * 发起流程的用户 |
| 46 | */ | 47 | */ |
| 47 | - private User startUser; | 48 | + private UserSimpleBaseVO startUser; |
| 48 | 49 | ||
| 49 | @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | 50 | @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") |
| 50 | private String processDefinitionId; | 51 | private String processDefinitionId; |
| @@ -58,24 +59,6 @@ public class BpmProcessInstanceRespVO { | @@ -58,24 +59,6 @@ public class BpmProcessInstanceRespVO { | ||
| 58 | */ | 59 | */ |
| 59 | private List<Task> tasks; // 仅在流程实例分页才返回 | 60 | private List<Task> tasks; // 仅在流程实例分页才返回 |
| 60 | 61 | ||
| 61 | - @Schema(description = "用户信息") | ||
| 62 | - @Data | ||
| 63 | - public static class User { | ||
| 64 | - | ||
| 65 | - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||
| 66 | - private Long id; | ||
| 67 | - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") | ||
| 68 | - private String nickname; | ||
| 69 | - @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") | ||
| 70 | - private String avatar; | ||
| 71 | - | ||
| 72 | - @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | ||
| 73 | - private Long deptId; | ||
| 74 | - @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") | ||
| 75 | - private String deptName; | ||
| 76 | - | ||
| 77 | - } | ||
| 78 | - | ||
| 79 | @Schema(description = "流程任务") | 62 | @Schema(description = "流程任务") |
| 80 | @Data | 63 | @Data |
| 81 | public static class Task { | 64 | public static class Task { |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
| 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; | 3 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| 4 | import io.swagger.v3.oas.annotations.media.Schema; | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | import lombok.Data; | 5 | import lombok.Data; |
| 6 | 6 | ||
| @@ -36,11 +36,11 @@ public class BpmTaskRespVO { | @@ -36,11 +36,11 @@ public class BpmTaskRespVO { | ||
| 36 | /** | 36 | /** |
| 37 | * 负责人的用户信息 | 37 | * 负责人的用户信息 |
| 38 | */ | 38 | */ |
| 39 | - private BpmProcessInstanceRespVO.User ownerUser; | 39 | + private UserSimpleBaseVO ownerUser; |
| 40 | /** | 40 | /** |
| 41 | * 审核的用户信息 | 41 | * 审核的用户信息 |
| 42 | */ | 42 | */ |
| 43 | - private BpmProcessInstanceRespVO.User assigneeUser; | 43 | + private UserSimpleBaseVO assigneeUser; |
| 44 | 44 | ||
| 45 | @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") | 45 | @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") |
| 46 | private String taskDefinitionKey; | 46 | private String taskDefinitionKey; |
| @@ -89,7 +89,7 @@ public class BpmTaskRespVO { | @@ -89,7 +89,7 @@ public class BpmTaskRespVO { | ||
| 89 | /** | 89 | /** |
| 90 | * 发起人的用户信息 | 90 | * 发起人的用户信息 |
| 91 | */ | 91 | */ |
| 92 | - private BpmProcessInstanceRespVO.User startUser; | 92 | + private UserSimpleBaseVO startUser; |
| 93 | 93 | ||
| 94 | } | 94 | } |
| 95 | 95 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
| 1 | package cn.iocoder.yudao.module.bpm.convert.task; | 1 | package cn.iocoder.yudao.module.bpm.convert.task; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 3 | import cn.hutool.core.util.StrUtil; | 4 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | 6 | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| 6 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | 7 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | ||
| 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; |
| 9 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO; | 11 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO; |
| 10 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO; | 12 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO; |
| 11 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; | 13 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; |
| 12 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | 14 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| 15 | +import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert; | ||
| 13 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; | 16 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 14 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; | 17 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 15 | import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceStatusEvent; | 18 | import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceStatusEvent; |
| 19 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; | ||
| 16 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | 20 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| 17 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | 21 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 18 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; | 22 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; |
| @@ -20,6 +24,7 @@ import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcess | @@ -20,6 +24,7 @@ import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcess | ||
| 20 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | 24 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| 21 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | 25 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 22 | import org.flowable.bpmn.model.BpmnModel; | 26 | import org.flowable.bpmn.model.BpmnModel; |
| 27 | +import org.flowable.bpmn.model.UserTask; | ||
| 23 | import org.flowable.engine.history.HistoricProcessInstance; | 28 | import org.flowable.engine.history.HistoricProcessInstance; |
| 24 | import org.flowable.engine.repository.ProcessDefinition; | 29 | import org.flowable.engine.repository.ProcessDefinition; |
| 25 | import org.flowable.engine.runtime.ProcessInstance; | 30 | import org.flowable.engine.runtime.ProcessInstance; |
| @@ -30,11 +35,13 @@ import org.mapstruct.Mapping; | @@ -30,11 +35,13 @@ import org.mapstruct.Mapping; | ||
| 30 | import org.mapstruct.MappingTarget; | 35 | import org.mapstruct.MappingTarget; |
| 31 | import org.mapstruct.factory.Mappers; | 36 | import org.mapstruct.factory.Mappers; |
| 32 | 37 | ||
| 38 | +import java.util.HashSet; | ||
| 33 | import java.util.List; | 39 | import java.util.List; |
| 34 | import java.util.Map; | 40 | import java.util.Map; |
| 35 | import java.util.Set; | 41 | import java.util.Set; |
| 36 | 42 | ||
| 37 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 43 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 44 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | ||
| 38 | 45 | ||
| 39 | /** | 46 | /** |
| 40 | * 流程实例 Convert | 47 | * 流程实例 Convert |
| @@ -65,7 +72,7 @@ public interface BpmProcessInstanceConvert { | @@ -65,7 +72,7 @@ public interface BpmProcessInstanceConvert { | ||
| 65 | if (userMap != null) { | 72 | if (userMap != null) { |
| 66 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId())); | 73 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId())); |
| 67 | if (startUser != null) { | 74 | if (startUser != null) { |
| 68 | - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | 75 | + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| 69 | MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName())); | 76 | MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName())); |
| 70 | } | 77 | } |
| 71 | } | 78 | } |
| @@ -75,7 +82,7 @@ public interface BpmProcessInstanceConvert { | @@ -75,7 +82,7 @@ public interface BpmProcessInstanceConvert { | ||
| 75 | 82 | ||
| 76 | default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance, | 83 | default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance, |
| 77 | ProcessDefinition processDefinition, | 84 | ProcessDefinition processDefinition, |
| 78 | - BpmProcessDefinitionInfoDO processDefinitionExt, | 85 | + BpmProcessDefinitionInfoDO processDefinitionInfo, |
| 79 | AdminUserRespDTO startUser, | 86 | AdminUserRespDTO startUser, |
| 80 | DeptRespDTO dept) { | 87 | DeptRespDTO dept) { |
| 81 | BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class); | 88 | BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class); |
| @@ -83,10 +90,10 @@ public interface BpmProcessInstanceConvert { | @@ -83,10 +90,10 @@ public interface BpmProcessInstanceConvert { | ||
| 83 | respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); | 90 | respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); |
| 84 | // definition | 91 | // definition |
| 85 | respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); | 92 | respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); |
| 86 | - copyTo(processDefinitionExt, respVO.getProcessDefinition()); | 93 | + copyTo(processDefinitionInfo, respVO.getProcessDefinition()); |
| 87 | // user | 94 | // user |
| 88 | if (startUser != null) { | 95 | if (startUser != null) { |
| 89 | - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | 96 | + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| 90 | if (dept != null) { | 97 | if (dept != null) { |
| 91 | respVO.getStartUser().setDeptName(dept.getName()); | 98 | respVO.getStartUser().setDeptName(dept.getName()); |
| 92 | } | 99 | } |
| @@ -144,17 +151,27 @@ public interface BpmProcessInstanceConvert { | @@ -144,17 +151,27 @@ public interface BpmProcessInstanceConvert { | ||
| 144 | return respVO; | 151 | return respVO; |
| 145 | } | 152 | } |
| 146 | 153 | ||
| 147 | - default BpmProcessInstanceRespVO.User buildUser(String userId, | 154 | + default UserSimpleBaseVO buildUser(String userIdStr, |
| 155 | + Map<Long, AdminUserRespDTO> userMap, | ||
| 156 | + Map<Long, DeptRespDTO> deptMap) { | ||
| 157 | + if (StrUtil.isEmpty(userIdStr)) { | ||
| 158 | + return null; | ||
| 159 | + } | ||
| 160 | + Long userId = NumberUtils.parseLong(userIdStr); | ||
| 161 | + return buildUser(userId, userMap, deptMap); | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + default UserSimpleBaseVO buildUser(Long userId, | ||
| 148 | Map<Long, AdminUserRespDTO> userMap, | 165 | Map<Long, AdminUserRespDTO> userMap, |
| 149 | Map<Long, DeptRespDTO> deptMap) { | 166 | Map<Long, DeptRespDTO> deptMap) { |
| 150 | - if (StrUtil.isBlank(userId)) { | 167 | + if (userId == null) { |
| 151 | return null; | 168 | return null; |
| 152 | } | 169 | } |
| 153 | - AdminUserRespDTO user = userMap.get(NumberUtils.parseLong(userId)); | 170 | + AdminUserRespDTO user = userMap.get(userId); |
| 154 | if (user == null) { | 171 | if (user == null) { |
| 155 | return null; | 172 | return null; |
| 156 | } | 173 | } |
| 157 | - BpmProcessInstanceRespVO.User userVO = BeanUtils.toBean(user, BpmProcessInstanceRespVO.User.class); | 174 | + UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class); |
| 158 | DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null; | 175 | DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null; |
| 159 | if (dept != null) { | 176 | if (dept != null) { |
| 160 | userVO.setDeptName(dept.getName()); | 177 | userVO.setDeptName(dept.getName()); |
| @@ -162,12 +179,69 @@ public interface BpmProcessInstanceConvert { | @@ -162,12 +179,69 @@ public interface BpmProcessInstanceConvert { | ||
| 162 | return userVO; | 179 | return userVO; |
| 163 | } | 180 | } |
| 164 | 181 | ||
| 165 | - default BpmApprovalDetailRespVO.ApprovalTaskInfo buildApprovalTaskInfo(HistoricTaskInstance task) { | 182 | + default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) { |
| 166 | if (task == null) { | 183 | if (task == null) { |
| 167 | return null; | 184 | return null; |
| 168 | } | 185 | } |
| 169 | - return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ApprovalTaskInfo.class) | 186 | + return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class) |
| 170 | .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); | 187 | .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); |
| 171 | } | 188 | } |
| 172 | 189 | ||
| 190 | + default Set<Long> parseUserIds(HistoricProcessInstance processInstance, | ||
| 191 | + List<BpmApprovalDetailRespVO.ActivityNode> activityNodes) { | ||
| 192 | + Set<Long> userIds = new HashSet<>(); | ||
| 193 | + if (processInstance != null) { | ||
| 194 | + userIds.add(NumberUtils.parseLong(processInstance.getStartUserId())); | ||
| 195 | + } | ||
| 196 | + for (BpmApprovalDetailRespVO.ActivityNode activityNode : activityNodes) { | ||
| 197 | + CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getAssignee)); | ||
| 198 | + CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getOwner)); | ||
| 199 | + CollUtil.addAll(userIds, activityNode.getCandidateUserIds()); | ||
| 200 | + } | ||
| 201 | + return userIds; | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + default BpmApprovalDetailRespVO buildApprovalDetail(BpmnModel bpmnModel, | ||
| 205 | + ProcessDefinition processDefinition, | ||
| 206 | + BpmProcessDefinitionInfoDO processDefinitionInfo, | ||
| 207 | + HistoricProcessInstance processInstance, | ||
| 208 | + Integer processInstanceStatus, | ||
| 209 | + List<BpmApprovalDetailRespVO.ActivityNode> activityNodes, | ||
| 210 | + BpmTaskRespVO todoTask, | ||
| 211 | + Map<String, String> formFieldsPermission, | ||
| 212 | + Map<Long, AdminUserRespDTO> userMap, | ||
| 213 | + Map<Long, DeptRespDTO> deptMap) { | ||
| 214 | + // 1. 流程实例 | ||
| 215 | + BpmProcessInstanceRespVO processInstanceResp = null; | ||
| 216 | + if (processInstance != null) { | ||
| 217 | + AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); | ||
| 218 | + DeptRespDTO dept = startUser != null ? deptMap.get(startUser.getDeptId()) : null; | ||
| 219 | + processInstanceResp = buildProcessInstance(processInstance, null, null, startUser, dept); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + // 2. 流程定义 | ||
| 223 | + List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); | ||
| 224 | + BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( | ||
| 225 | + processDefinition, null, processDefinitionInfo, null, null, bpmnModel, userTaskList); | ||
| 226 | + | ||
| 227 | + // 3. 流程节点 | ||
| 228 | + activityNodes.forEach(approveNode -> { | ||
| 229 | + if (approveNode.getTasks() != null) { | ||
| 230 | + approveNode.getTasks().forEach(task -> { | ||
| 231 | + task.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap)); | ||
| 232 | + task.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap)); | ||
| 233 | + }); | ||
| 234 | + } | ||
| 235 | + approveNode.setCandidateUsers(convertList(approveNode.getCandidateUserIds(), userId -> buildUser(userId, userMap, deptMap))); | ||
| 236 | + }); | ||
| 237 | + | ||
| 238 | + // 4. 拼接起来 | ||
| 239 | + return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus) | ||
| 240 | + .setProcessDefinition(definitionResp) | ||
| 241 | + .setProcessInstance(processInstanceResp) | ||
| 242 | + .setFormFieldsPermission(formFieldsPermission) | ||
| 243 | + .setTodoTask(todoTask) | ||
| 244 | + .setActivityNodes(activityNodes); | ||
| 245 | + } | ||
| 246 | + | ||
| 173 | } | 247 | } |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
| @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 6 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | 6 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 7 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | 7 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 9 | -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; | 9 | +import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| 10 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; |
| 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; | 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 12 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | 12 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| @@ -51,7 +51,7 @@ public interface BpmTaskConvert { | @@ -51,7 +51,7 @@ public interface BpmTaskConvert { | ||
| 51 | } | 51 | } |
| 52 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); | 52 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| 53 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); | 53 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| 54 | - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | 54 | + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| 55 | }); | 55 | }); |
| 56 | } | 56 | } |
| 57 | 57 | ||
| @@ -65,7 +65,7 @@ public interface BpmTaskConvert { | @@ -65,7 +65,7 @@ public interface BpmTaskConvert { | ||
| 65 | // 用户信息 | 65 | // 用户信息 |
| 66 | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); | 66 | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); |
| 67 | if (assignUser != null) { | 67 | if (assignUser != null) { |
| 68 | - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); | 68 | + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); |
| 69 | findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); | 69 | findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); |
| 70 | } | 70 | } |
| 71 | // 流程实例 | 71 | // 流程实例 |
| @@ -73,7 +73,7 @@ public interface BpmTaskConvert { | @@ -73,7 +73,7 @@ public interface BpmTaskConvert { | ||
| 73 | if (processInstance != null) { | 73 | if (processInstance != null) { |
| 74 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); | 74 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| 75 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); | 75 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| 76 | - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | 76 | + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| 77 | } | 77 | } |
| 78 | return taskVO; | 78 | return taskVO; |
| 79 | }); | 79 | }); |
| @@ -92,7 +92,7 @@ public interface BpmTaskConvert { | @@ -92,7 +92,7 @@ public interface BpmTaskConvert { | ||
| 92 | // 流程实例 | 92 | // 流程实例 |
| 93 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); | 93 | AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); |
| 94 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); | 94 | taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); |
| 95 | - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); | 95 | + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); |
| 96 | // 表单信息 | 96 | // 表单信息 |
| 97 | BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); | 97 | BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); |
| 98 | if (form != null) { | 98 | if (form != null) { |
| @@ -112,7 +112,7 @@ public interface BpmTaskConvert { | @@ -112,7 +112,7 @@ public interface BpmTaskConvert { | ||
| 112 | return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> { | 112 | return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> { |
| 113 | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); | 113 | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); |
| 114 | if (assignUser != null) { | 114 | if (assignUser != null) { |
| 115 | - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); | 115 | + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); |
| 116 | DeptRespDTO dept = deptMap.get(assignUser.getDeptId()); | 116 | DeptRespDTO dept = deptMap.get(assignUser.getDeptId()); |
| 117 | if (dept != null) { | 117 | if (dept != null) { |
| 118 | taskVO.getAssigneeUser().setDeptName(dept.getName()); | 118 | taskVO.getAssigneeUser().setDeptName(dept.getName()); |
| @@ -120,7 +120,7 @@ public interface BpmTaskConvert { | @@ -120,7 +120,7 @@ public interface BpmTaskConvert { | ||
| 120 | } | 120 | } |
| 121 | AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); | 121 | AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); |
| 122 | if (ownerUser != null) { | 122 | if (ownerUser != null) { |
| 123 | - taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); | 123 | + taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); |
| 124 | findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); | 124 | findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); |
| 125 | } | 125 | } |
| 126 | })); | 126 | })); |
| @@ -166,7 +166,7 @@ public interface BpmTaskConvert { | @@ -166,7 +166,7 @@ public interface BpmTaskConvert { | ||
| 166 | Map<Long, DeptRespDTO> deptMap) { | 166 | Map<Long, DeptRespDTO> deptMap) { |
| 167 | AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner)); | 167 | AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner)); |
| 168 | if (ownerUser != null) { | 168 | if (ownerUser != null) { |
| 169 | - task.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); | 169 | + task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); |
| 170 | findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName())); | 170 | findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName())); |
| 171 | } | 171 | } |
| 172 | } | 172 | } |
| @@ -192,7 +192,7 @@ public interface BpmTaskConvert { | @@ -192,7 +192,7 @@ public interface BpmTaskConvert { | ||
| 192 | Map<Long, DeptRespDTO> deptMap) { | 192 | Map<Long, DeptRespDTO> deptMap) { |
| 193 | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee)); | 193 | AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee)); |
| 194 | if (assignUser != null) { | 194 | if (assignUser != null) { |
| 195 | - task.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); | 195 | + task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); |
| 196 | findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); | 196 | findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); |
| 197 | } | 197 | } |
| 198 | } | 198 | } |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
| @@ -99,6 +99,16 @@ public class FlowableUtils { | @@ -99,6 +99,16 @@ public class FlowableUtils { | ||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | /** | 101 | /** |
| 102 | + * 获得流程实例的审批原因 | ||
| 103 | + * | ||
| 104 | + * @param processInstance 流程实例 | ||
| 105 | + * @return 审批原因 | ||
| 106 | + */ | ||
| 107 | + public static String getProcessInstanceReason(HistoricProcessInstance processInstance) { | ||
| 108 | + return (String) processInstance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + /** | ||
| 102 | * 获得流程实例的表单 | 112 | * 获得流程实例的表单 |
| 103 | * | 113 | * |
| 104 | * @param processInstance 流程实例 | 114 | * @param processInstance 流程实例 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
| 1 | -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalTaskInfo; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; 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.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.NodeInfo; import static cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert.INSTANCE; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.NOT_START; import static cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum.RUNNING; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum.START_USER; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel, String activityId, String taskId) { // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) { activityId = Optional.ofNullable(taskService.getHistoricTask(taskId)) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1.1 从 reqVO 中,读取公共变量 Long startUserId = loginUserId; // 流程发起人 HistoricProcessInstance historicProcessInstance = null; // 流程实例 ProcessInstance processInstance = null; // 流程 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态 Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量 // 1.2 如果是流程已发起的场景,则使用流程实例的数据 if (reqVO.getProcessInstanceId() != null) { historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (historicProcessInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processInstance = getProcessInstance(historicProcessInstance.getId()); startUserId = Long.valueOf(historicProcessInstance.getStartUserId()); processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance); processVariables = historicProcessInstance.getProcessVariables(); } // 1.3 读取其它相关数据 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId()); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); // 2.1 已结束 + 进行中的流程节点 List<NodeInfo> endApprovalNodeInfos = null; // 已结束的审批信息 List<NodeInfo> runApprovalNodeInfos = null; // 进行中的审批信息 List<HistoricActivityInstance> activities = null; // 流程实例列表 if (reqVO.getProcessInstanceId() != null) { activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId()); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true); endApprovalNodeInfos = getEndApprovalNodeInfoList(startUserId, processDefinitionInfo, processInstanceStatus, activities, tasks); runApprovalNodeInfos = getRunApproveNodeList(startUserId, bpmnModel, processInstance, activities, tasks); } // 2.2 流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endApprovalNodeInfos, runApprovalNodeInfos, null, null); } // 3.1 计算当前登录用户的待办任务 // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。 // 目前的想法,是不是不依赖前端传递参数。而是后端推导,按照一个顺序。待审批 》 taskId/activityId BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId()); // 3.2 预测未运行节点的审批信息 List<NodeInfo> simulateApprovalNodeInfos = getSimulateApproveNodeList(processDefinitionInfo, processInstance, bpmnModel, startUserId, processVariables, activities); return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endApprovalNodeInfos, runApprovalNodeInfos, simulateApprovalNodeInfos, todoTask); } private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO, BpmnModel bpmnModel, ProcessDefinition processDefinition, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance processInstance, Integer processInstanceStatus, List<NodeInfo> endApprovalNodeInfos, List<NodeInfo> runningApprovalNodeInfos, List<NodeInfo> simulateApprovalNodeInfos, BpmTaskRespVO todoTask) { BpmProcessInstanceRespVO processInstanceResp = null; if (processInstance != null) { AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null && startUser.getDeptId() != null) { dept = deptApi.getDept(startUser.getDeptId()); } processInstanceResp = INSTANCE.buildProcessInstance(processInstance, null, null, startUser, dept); } List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( processDefinition, null, processDefinitionInfo, null, null, bpmnModel, userTaskList ); // 4. 表单权限 Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), reqVO.getTaskId()); return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus) .setProcessDefinition(definitionResp) .setProcessInstance(processInstanceResp) .setFormFieldsPermission(formFieldsPermission) .setTodoTask(todoTask) .setApproveNodes(CollectionUtils.newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos))); } /** * 获得【已结束】的流程节点们 */ private List<NodeInfo> getEndApprovalNodeInfoList(Long startUserId, BpmProcessDefinitionInfoDO processDefinitionInfo, Integer processInstanceStatus, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 遍历 tasks 列表,只处理已结束的 UserTask // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); List<NodeInfo> approvalNodes = convertList(endTasks, task -> { NodeInfo approvalNodeInfo = new NodeInfo().setId(task.getTaskDefinitionKey()).setName(task.getName()) .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) .setStatus(FlowableUtils.getTaskStatus(task)); // 如果是取消状态,则跳过 if (BpmTaskStatusEnum.isCancelStatus(approvalNodeInfo.getStatus())) { return null; } approvalNodeInfo.setNodeType(START_USER_NODE_ID.equals(approvalNodeInfo.getId()) ? BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType()); approvalNodeInfo.setTasks(singletonList(INSTANCE.buildApprovalTaskInfo(task))); return approvalNodeInfo; }); // 遍历 activities,只处理已结束的 StartEvent、EndEvent List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END))); endActivities.forEach(activity -> { // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点 if (ELEMENT_EVENT_START.equals(activity.getActivityType()) && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) { ApprovalTaskInfo startTask = new ApprovalTaskInfo().setId(BpmnModelConstants.START_USER_NODE_ID) .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus()); NodeInfo startNode = new NodeInfo().setId(startTask.getId()) .setName(BpmSimpleModelNodeType.START_USER_NODE.getName()).setNodeType(START_USER_NODE.getType()) .setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask)); approvalNodes.add(0, startNode); return; } // EndEvent if (ELEMENT_EVENT_END.equals(activity.getActivityType())) { NodeInfo approvalNodeInfo = new NodeInfo().setId(activity.getId()) .setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); approvalNodes.add(approvalNodeInfo); } }); return approvalNodes; } /** * 获得【进行中】的流程节点们 */ private List<NodeInfo> getRunApproveNodeList(Long startUserId, BpmnModel bpmnModel, ProcessInstance processInstance, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 构建运行中的任务,基于 activityId 分组 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER))); Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId); // 按照 activityId 分组,构建 ApprovalNodeInfo 节点 Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId); return convertList(runningTaskMap.entrySet(), entry -> { String activityId = entry.getKey(); List<HistoricActivityInstance> taskActivities = entry.getValue(); // 构建流程节点 HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同 NodeInfo approvalNodeInfo = new NodeInfo(); approvalNodeInfo.setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName()) .setNodeType(APPROVE_NODE.getType()).setStatus(RUNNING.getStatus()); // 处理每个任务 List<ApprovalTaskInfo> approveTasks = CollUtil.newArrayList(); for (HistoricActivityInstance activity : taskActivities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); ApprovalTaskInfo approveTask = BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task); approveTasks.add(approveTask); // 加签子任务 List<HistoricTaskInstance> childrenTasks = taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks); if (CollUtil.isNotEmpty(childrenTasks)) { approveTasks.addAll(convertList(childrenTasks, INSTANCE::buildApprovalTaskInfo)); } } approvalNodeInfo.setTasks(approveTasks); // 如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); if (BpmnModelUtils.isSequentialUserTask(flowNode)) { Set<Long> candidateUserIds = getTaskCandidateUserList(startUserId, processInstance, flowNode.getId(), BpmnModelUtils.parseCandidateStrategy(flowNode), BpmnModelUtils.parseCandidateParam(flowNode)); // 截取当前审批人位置后面的候选人,不包含当前审批人 ApprovalTaskInfo approvalTaskInfo = CollUtil.getFirst(approvalNodeInfo.getTasks()); Assert.notNull(approvalTaskInfo, "任务不能为空"); int index = CollUtil.indexOf(candidateUserIds, userId -> userId.equals(approvalTaskInfo.getAssignee())); approvalNodeInfo.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); } return approvalNodeInfo; }); } private List<NodeInfo> getSimulateApproveNodeList(BpmProcessDefinitionInfoDO processDefinitionInfo, ProcessInstance processInstance, BpmnModel bpmnModel, Long startUserId, Map<String, Object> processVariable, List<HistoricActivityInstance> historicActivityList) { Set<String> runNodeIds = convertSet(historicActivityList, HistoricActivityInstance::getActivityId); // 情况一:BPMN 设计器 if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariable); return convertList(flowElements, flowElement -> buildNotRunApproveNode(startUserId, processInstance, flowElement, runNodeIds)); // 情况二:SIMPLE 设计器 } if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariable); return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodes(startUserId, processInstance, simpleNode, runNodeIds)); } throw new IllegalArgumentException(""); // TODO 芋艿:优化下 } private NodeInfo buildNotRunApproveNodes(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set<String> runNodeIds) { if (runNodeIds.contains(node.getId())) { return null; } NodeInfo approvalNodeInfo = new NodeInfo().setNodeType(node.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); // 1. 对需要人工审批的审批节点,进行预测 if (APPROVE_NODE.getType().equals(node.getType()) || START_USER_NODE.getType().equals(node.getType())) { Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? START_USER.getStrategy() : node.getCandidateStrategy(); approvalNodeInfo.setCandidateUsers( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); // TODO 芋艿:需要测试非 USER 审批的情况; return approvalNodeInfo; // 2. 结束节点 } if (END_NODE.getType().equals(node.getType())) { return approvalNodeInfo; } // TODO 芋艿:抄送节点 return null; } private NodeInfo buildNotRunApproveNode(Long startUserId, ProcessInstance processInstance, FlowElement node, Set<String> runNodeIds) { if (runNodeIds.contains(node.getId())) { return null; } // 1. 对需要人工审批的审批节点,进行预测 if (node instanceof UserTask) { NodeInfo approvalNodeInfo = new NodeInfo().setNodeType(APPROVE_NODE.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); Integer candidateStrategy = BpmnModelUtils.parseCandidateStrategy(node); String candidateParam = BpmnModelUtils.parseCandidateParam(node); approvalNodeInfo.setCandidateUsers( getNotRunTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, candidateParam)); return approvalNodeInfo; // 2. 结束节点 } if (node instanceof EndEvent) { return new NodeInfo().setNodeType(END_NODE.getType()) .setName(node.getName()).setStatus(NOT_START.getStatus()); } return null; } private List<UserSimpleBaseVO> getNotRunTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); Set<Long> userIds = taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); Map<Long, AdminUserRespDTO> adminUserMap = adminUserApi.getUserMap(userIds); // 需要按照候选人的顺序返回。原因是,依次审批需要按顺序展示用户 return convertList(userIds, userId -> BeanUtils.toBean(adminUserMap.get(userId), UserSimpleBaseVO.class)); } private Set<Long> getTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); return taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam); } @Override public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) { // 1.1 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获得流程定义 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); if (bpmnModel == null) { return null; } // 1.3 获得流程实例对应的活动实例列表 + 任务列表 List<HistoricActivityInstance> activityInstances = taskService.getActivityListByProcessInstanceId(id); List<HistoricTaskInstance> taskInstances = taskService.getTaskListByProcessInstanceId(id, true); // 2.1 拼接进度信息 Set<String> unfinishedTaskActivityIds = convertSet(activityInstances, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() == null); Set<String> finishedTaskActivityIds = convertSet(activityInstances, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); Set<String> finishedSequenceFlowActivityIds = convertSet(activityInstances, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉 unfinishedTaskActivityIds.removeAll(finishedTaskActivityIds); // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。 // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点 Set<String> rejectTaskActivityIds = CollUtil.newHashSet(); if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) { taskInstances.stream() .filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task))) .max(Comparator.comparing(HistoricTaskInstance::getEndTime)) .ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey())); finishedTaskActivityIds.removeAll(rejectTaskActivityIds); } // 2.2 拼接基础信息 Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); taskInstances.forEach(task -> { CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee()))); CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner()))); }); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); return INSTANCE.buildProcessInstanceBpmnModelView(processInstance, taskInstances, bpmnModel, unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds, userMap, deptMap); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } | ||
| 2 | \ No newline at end of file | 1 | \ No newline at end of file |
| 2 | +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.SetUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; 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.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private BpmTaskCandidateInvoker bpmTaskCandidateInvoker; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel, String activityId, String taskId) { // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) { activityId = Optional.ofNullable(taskService.getHistoricTask(taskId)) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1.1 从 reqVO 中,读取公共变量 Long startUserId = loginUserId; // 流程发起人 HistoricProcessInstance historicProcessInstance = null; // 流程实例 ProcessInstance processInstance = null; // 流程 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态 Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量 // 1.2 如果是流程已发起的场景,则使用流程实例的数据 if (reqVO.getProcessInstanceId() != null) { historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (historicProcessInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } processInstance = getProcessInstance(historicProcessInstance.getId()); startUserId = Long.valueOf(historicProcessInstance.getStartUserId()); processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance); processVariables = historicProcessInstance.getProcessVariables(); } // 1.3 读取其它相关数据 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId()); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); // 2.1 已结束 + 进行中的活动节点 List<ActivityNode> endActivityNodes = null; // 已结束的审批信息 List<ActivityNode> runActivityNodes = null; // 进行中的审批信息 List<HistoricActivityInstance> activities = null; // 流程实例列表 if (reqVO.getProcessInstanceId() != null) { activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId()); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true); endActivityNodes = getEndActivityNodeList(startUserId, processDefinitionInfo, historicProcessInstance, processInstanceStatus, activities, tasks); runActivityNodes = getRunApproveNodeList(startUserId, bpmnModel, processInstance, activities, tasks); } // 2.2 流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, null, null); } // 3.1 计算当前登录用户的待办任务 // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。 BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId()); // 3.2 预测未运行节点的审批信息 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo, processInstance, processVariables, activities); // 4. 拼接最终数据 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask); } /** * 拼接审批详情的最终数据 * * 主要是,拼接审批人的用户信息、部门信息 */ private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO, BpmnModel bpmnModel, ProcessDefinition processDefinition, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance processInstance, Integer processInstanceStatus, List<ActivityNode> endApprovalNodeInfos, List<ActivityNode> runningApprovalNodeInfos, List<ActivityNode> simulateApprovalNodeInfos, BpmTaskRespVO todoTask) { // 1. 获取所有需要读取用户信息的 userIds List<ActivityNode> approveNodes = newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos)); Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); // 2. 表单权限 Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), reqVO.getTaskId()); // 3. 拼接数据 return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance, processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap); } /** * 获得【已结束】的活动节点们 */ private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 遍历 tasks 列表,只处理已结束的 UserTask // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); List<ActivityNode> approvalNodes = convertList(endTasks, task -> { ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ? BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setStatus(FlowableUtils.getTaskStatus(task)) .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); // 如果是取消状态,则跳过 if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) { return null; } return activityNode; }); // 遍历 activities,只处理已结束的 StartEvent、EndEvent List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END))); endActivities.forEach(activity -> { // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点 if (ELEMENT_EVENT_START.equals(activity.getActivityType()) && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) { ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID) .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus()); ActivityNode startNode = new ActivityNode().setId(startTask.getId()) .setName(BpmSimpleModelNodeType.START_USER_NODE.getName()).setNodeType(START_USER_NODE.getType()) .setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask)) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); approvalNodes.add(0, startNode); return; } // EndEvent if (ELEMENT_EVENT_END.equals(activity.getActivityType())) { if (BpmProcessInstanceStatusEnum.isRejectStatus(processInstanceStatus)) { // 拒绝情况下,不需要展示 EndEvent 结束节点。原因是:前端已经展示 x 效果,无需重复展示 return; } ActivityNode endNode = new ActivityNode().setId(activity.getId()) .setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); String reason = FlowableUtils.getProcessInstanceReason(historicProcessInstance); if (StrUtil.isNotEmpty(reason)) { endNode.setTasks(singletonList(new ActivityNodeTask().setId(endNode.getId()) .setStatus(endNode.getStatus()).setReason(reason))); } approvalNodes.add(endNode); } }); return approvalNodes; } /** * 获得【进行中】的活动节点们 */ private List<ActivityNode> getRunApproveNodeList(Long startUserId, BpmnModel bpmnModel, ProcessInstance processInstance, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 构建运行中的任务,基于 activityId 分组 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER))); Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId); // 按照 activityId 分组,构建 ApprovalNodeInfo 节点 Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId); return convertList(runningTaskMap.entrySet(), entry -> { String activityId = entry.getKey(); List<HistoricActivityInstance> taskActivities = entry.getValue(); // 构建活动节点 HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName()) .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) .setTasks(new ArrayList<>()); // 处理每个任务的 tasks 属性 for (HistoricActivityInstance activity : taskActivities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); // 加签子任务 List<HistoricTaskInstance> childrenTasks = taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks); if (CollUtil.isNotEmpty(childrenTasks)) { activityNode.getTasks().addAll(convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo)); } } // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); if (BpmnModelUtils.isSequentialUserTask(flowNode)) { List<Long> candidateUserIds = getTaskCandidateUserList(startUserId, processInstance, flowNode.getId(), BpmnModelUtils.parseCandidateStrategy(flowNode), BpmnModelUtils.parseCandidateParam(flowNode)); // 截取当前审批人位置后面的候选人,不包含当前审批人 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks()); Assert.notNull(approvalTaskInfo, "任务不能为空"); int index = CollUtil.indexOf(candidateUserIds, userId -> userId.equals(approvalTaskInfo.getAssignee())); activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); } return activityNode; }); } /** * 获得【预测(未来)】的活动节点们 */ private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, ProcessInstance processInstance, Map<String, Object> processVariable, List<HistoricActivityInstance> activities) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); // 情况一:BPMN 设计器 if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariable); return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, processInstance, flowElement, runActivityIds)); } // 情况二:SIMPLE 设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariable); return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, processInstance, simpleNode, runActivityIds)); } throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); } private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, ProcessInstance processInstance, BpmSimpleModelNodeVO node, Set<String> runActivityIds) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录 if (runActivityIds.contains(node.getId())) { return null; } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName()) .setNodeType(node.getType()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); // 1. 开始节点/审批节点 if (ObjectUtils.equalsAny(node.getType(), BpmSimpleModelNodeType.START_USER_NODE.getType(), BpmSimpleModelNodeType.APPROVE_NODE.getType())) { Integer candidateStrategy = START_USER_NODE.getType().equals(node.getType()) ? BpmTaskCandidateStrategyEnum.START_USER.getStrategy() : node.getCandidateStrategy(); if (candidateStrategy != null) { activityNode.setCandidateUserIds(getTaskCandidateUserList(startUserId, processInstance, node.getId(), candidateStrategy, node.getCandidateParam())); } return activityNode; } // 2. 结束节点 if (END_NODE.getType().equals(node.getType())) { return activityNode; } // 3. 抄送节点 TODO @jason:抄送节点 return null; } private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, ProcessInstance processInstance, FlowElement node, Set<String> runActivityIds) { if (runActivityIds.contains(node.getId())) { return null; } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); // 1. 开始节点 if (node instanceof StartEvent) { return activityNode.setName(BpmSimpleModelNodeType.START_USER_NODE.getName()).setNodeType(START_USER_NODE.getType()); } // 2. 审批节点 if (node instanceof UserTask) { activityNode.setName(node.getName()).setNodeType(APPROVE_NODE.getType()); activityNode.setCandidateUserIds(getTaskCandidateUserList(startUserId, processInstance, node.getId(), BpmnModelUtils.parseCandidateStrategy(node), BpmnModelUtils.parseCandidateParam(node))); return activityNode; } // 3. 结束节点 if (node instanceof EndEvent) { return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName()).setNodeType(END_NODE.getType()); } return null; } private List<Long> getTaskCandidateUserList(Long startUserId, ProcessInstance processInstance, String activityId, Integer candidateStrategy, String candidateParam) { BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy); return new ArrayList<>(taskCandidateStrategy.calculateUsers(startUserId, processInstance, activityId, candidateParam)); } @Override public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) { // 1.1 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获得流程定义 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); if (bpmnModel == null) { return null; } // 1.3 获得流程实例对应的活动实例列表 + 任务列表 List<HistoricActivityInstance> activityInstances = taskService.getActivityListByProcessInstanceId(id); List<HistoricTaskInstance> taskInstances = taskService.getTaskListByProcessInstanceId(id, true); // 2.1 拼接进度信息 Set<String> unfinishedTaskActivityIds = convertSet(activityInstances, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() == null); Set<String> finishedTaskActivityIds = convertSet(activityInstances, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); Set<String> finishedSequenceFlowActivityIds = convertSet(activityInstances, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉 unfinishedTaskActivityIds.removeAll(finishedTaskActivityIds); // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。 // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点 Set<String> rejectTaskActivityIds = CollUtil.newHashSet(); if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) { taskInstances.stream() .filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task))) .max(Comparator.comparing(HistoricTaskInstance::getEndTime)) .ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey())); finishedTaskActivityIds.removeAll(rejectTaskActivityIds); } // 2.2 拼接基础信息 Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); taskInstances.forEach(task -> { CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee()))); CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner()))); }); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, taskInstances, bpmnModel, unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds, userMap, deptMap); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } | ||
| 3 | \ No newline at end of file | 3 | \ No newline at end of file |