Commit 5d5f0257f902dca2a920e87b9204b3ff4d5dd554
Merge remote-tracking branch 'origin/feature/bpm' into feature/bpm
# Conflicts: # yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
Showing
21 changed files
with
197 additions
and
122 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
| @@ -23,7 +23,7 @@ public interface ErrorCodeConstants { | @@ -23,7 +23,7 @@ public interface ErrorCodeConstants { | ||
| 23 | "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置"); | 23 | "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置"); |
| 24 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); | 24 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); |
| 25 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); | 25 | ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); |
| 26 | - ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程的管理员"); | 26 | + ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); |
| 27 | 27 | ||
| 28 | // ========== 流程定义 1-009-003-000 ========== | 28 | // ========== 流程定义 1-009-003-000 ========== |
| 29 | ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); | 29 | ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmCategoryController.java
| @@ -48,6 +48,15 @@ public class BpmCategoryController { | @@ -48,6 +48,15 @@ public class BpmCategoryController { | ||
| 48 | return success(true); | 48 | return success(true); |
| 49 | } | 49 | } |
| 50 | 50 | ||
| 51 | + @PutMapping("/update-sort-batch") | ||
| 52 | + @Operation(summary = "批量更新流程分类的排序") | ||
| 53 | + @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3") | ||
| 54 | + @PreAuthorize("@ss.hasPermission('bpm:category:update')") | ||
| 55 | + public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) { | ||
| 56 | + categoryService.updateCategorySortBatch(ids); | ||
| 57 | + return success(true); | ||
| 58 | + } | ||
| 59 | + | ||
| 51 | @DeleteMapping("/delete") | 60 | @DeleteMapping("/delete") |
| 52 | @Operation(summary = "删除流程分类") | 61 | @Operation(summary = "删除流程分类") |
| 53 | @Parameter(name = "id", description = "编号", required = true) | 62 | @Parameter(name = "id", description = "编号", required = true) |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
| @@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition; | @@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 6 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
| 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*; |
| 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | 6 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; | 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| @@ -28,7 +26,7 @@ import org.springframework.security.access.prepost.PreAuthorize; | @@ -28,7 +26,7 @@ import org.springframework.security.access.prepost.PreAuthorize; | ||
| 28 | import org.springframework.validation.annotation.Validated; | 26 | import org.springframework.validation.annotation.Validated; |
| 29 | import org.springframework.web.bind.annotation.*; | 27 | import org.springframework.web.bind.annotation.*; |
| 30 | 28 | ||
| 31 | -import java.util.HashSet; | 29 | +import java.util.Collections; |
| 32 | import java.util.List; | 30 | import java.util.List; |
| 33 | import java.util.Map; | 31 | import java.util.Map; |
| 34 | import java.util.Set; | 32 | import java.util.Set; |
| @@ -56,38 +54,38 @@ public class BpmModelController { | @@ -56,38 +54,38 @@ public class BpmModelController { | ||
| 56 | @Resource | 54 | @Resource |
| 57 | private AdminUserApi adminUserApi; | 55 | private AdminUserApi adminUserApi; |
| 58 | 56 | ||
| 59 | - @GetMapping("/page") | 57 | + @GetMapping("/list") |
| 60 | @Operation(summary = "获得模型分页") | 58 | @Operation(summary = "获得模型分页") |
| 61 | - public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) { | ||
| 62 | - PageResult<Model> pageResult = modelService.getModelPage(pageVO); | ||
| 63 | - if (CollUtil.isEmpty(pageResult.getList())) { | ||
| 64 | - return success(PageResult.empty(pageResult.getTotal())); | 59 | + @Parameter(name = "name", description = "模型名称", example = "芋艿") |
| 60 | + public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) { | ||
| 61 | + List<Model> list = modelService.getModelList(name); | ||
| 62 | + if (CollUtil.isEmpty(list)) { | ||
| 63 | + return success(Collections.emptyList()); | ||
| 65 | } | 64 | } |
| 66 | 65 | ||
| 67 | - // 拼接数据 | ||
| 68 | // 获得 Form 表单 | 66 | // 获得 Form 表单 |
| 69 | - Set<Long> formIds = convertSet(pageResult.getList(), model -> { | 67 | + Set<Long> formIds = convertSet(list, model -> { |
| 70 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); | 68 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| 71 | return metaInfo != null ? metaInfo.getFormId() : null; | 69 | return metaInfo != null ? metaInfo.getFormId() : null; |
| 72 | }); | 70 | }); |
| 73 | Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds); | 71 | Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds); |
| 74 | // 获得 Category Map | 72 | // 获得 Category Map |
| 75 | Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap( | 73 | Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap( |
| 76 | - convertSet(pageResult.getList(), Model::getCategory)); | 74 | + convertSet(list, Model::getCategory)); |
| 77 | // 获得 Deployment Map | 75 | // 获得 Deployment Map |
| 78 | - Set<String> deploymentIds = new HashSet<>(); | ||
| 79 | - pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId())); | ||
| 80 | - Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds); | 76 | + Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap( |
| 77 | + convertSet(list, Model::getDeploymentId)); | ||
| 81 | // 获得 ProcessDefinition Map | 78 | // 获得 ProcessDefinition Map |
| 82 | - List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds); | 79 | + List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds( |
| 80 | + deploymentMap.keySet()); | ||
| 83 | Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); | 81 | Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); |
| 84 | // 获得 User Map | 82 | // 获得 User Map |
| 85 | - Set<Long> userIds = convertSetByFlatMap(pageResult.getList(), model -> { | 83 | + Set<Long> userIds = convertSetByFlatMap(list, model -> { |
| 86 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); | 84 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| 87 | return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); | 85 | return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); |
| 88 | }); | 86 | }); |
| 89 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | 87 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| 90 | - return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, | 88 | + return success(BpmModelConvert.INSTANCE.buildModelList(list, |
| 91 | formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); | 89 | formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); |
| 92 | } | 90 | } |
| 93 | 91 | ||
| @@ -111,6 +109,7 @@ public class BpmModelController { | @@ -111,6 +109,7 @@ public class BpmModelController { | ||
| 111 | return success(modelService.createModel(createRetVO)); | 109 | return success(modelService.createModel(createRetVO)); |
| 112 | } | 110 | } |
| 113 | 111 | ||
| 112 | + | ||
| 114 | @PutMapping("/update") | 113 | @PutMapping("/update") |
| 115 | @Operation(summary = "修改模型") | 114 | @Operation(summary = "修改模型") |
| 116 | @PreAuthorize("@ss.hasPermission('bpm:model:update')") | 115 | @PreAuthorize("@ss.hasPermission('bpm:model:update')") |
| @@ -119,6 +118,14 @@ public class BpmModelController { | @@ -119,6 +118,14 @@ public class BpmModelController { | ||
| 119 | return success(true); | 118 | return success(true); |
| 120 | } | 119 | } |
| 121 | 120 | ||
| 121 | + @PutMapping("/update-sort-batch") | ||
| 122 | + @Operation(summary = "批量修改模型排序") | ||
| 123 | + @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3") | ||
| 124 | + public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) { | ||
| 125 | + modelService.updateModelSortBatch(getLoginUserId(), ids); | ||
| 126 | + return success(true); | ||
| 127 | + } | ||
| 128 | + | ||
| 122 | @PostMapping("/deploy") | 129 | @PostMapping("/deploy") |
| 123 | @Operation(summary = "部署模型") | 130 | @Operation(summary = "部署模型") |
| 124 | @Parameter(name = "id", description = "编号", required = true, example = "1024") | 131 | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
| @@ -15,7 +15,9 @@ import java.util.List; | @@ -15,7 +15,9 @@ import java.util.List; | ||
| 15 | * BPM 流程 MetaInfo Response DTO | 15 | * BPM 流程 MetaInfo Response DTO |
| 16 | * 主要用于 { Model#setMetaInfo(String)} 的存储 | 16 | * 主要用于 { Model#setMetaInfo(String)} 的存储 |
| 17 | * | 17 | * |
| 18 | - * 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的 | 18 | + * 最终,它的字段和 |
| 19 | + * {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} | ||
| 20 | + * 是一致的 | ||
| 19 | * | 21 | * |
| 20 | * @author 芋道源码 | 22 | * @author 芋道源码 |
| 21 | */ | 23 | */ |
| @@ -40,13 +42,11 @@ public class BpmModelMetaInfoVO { | @@ -40,13 +42,11 @@ public class BpmModelMetaInfoVO { | ||
| 40 | @NotNull(message = "表单类型不能为空") | 42 | @NotNull(message = "表单类型不能为空") |
| 41 | private Integer formType; | 43 | private Integer formType; |
| 42 | @Schema(description = "表单编号", example = "1024") | 44 | @Schema(description = "表单编号", example = "1024") |
| 43 | - private Long formId; // formType 为 NORMAL 使用,必须非空 | ||
| 44 | - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", | ||
| 45 | - example = "/bpm/oa/leave/create") | ||
| 46 | - private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空 | ||
| 47 | - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", | ||
| 48 | - example = "/bpm/oa/leave/view") | ||
| 49 | - private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空 | 45 | + private Long formId; // formType 为 NORMAL 使用,必须非空 |
| 46 | + @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create") | ||
| 47 | + private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空 | ||
| 48 | + @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view") | ||
| 49 | + private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空 | ||
| 50 | 50 | ||
| 51 | @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | 51 | @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") |
| 52 | @NotNull(message = "是否可见不能为空") | 52 | @NotNull(message = "是否可见不能为空") |
| @@ -59,4 +59,7 @@ public class BpmModelMetaInfoVO { | @@ -59,4 +59,7 @@ public class BpmModelMetaInfoVO { | ||
| 59 | @NotEmpty(message = "可管理用户编号数组不能为空") | 59 | @NotEmpty(message = "可管理用户编号数组不能为空") |
| 60 | private List<Long> managerUserIds; | 60 | private List<Long> managerUserIds; |
| 61 | 61 | ||
| 62 | + @Schema(description = "排序", example = "1") | ||
| 63 | + private Long sort; // 创建时,后端自动生成 | ||
| 64 | + | ||
| 62 | } | 65 | } |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 4 | -import io.swagger.v3.oas.annotations.media.Schema; | ||
| 5 | -import lombok.Data; | ||
| 6 | -import lombok.EqualsAndHashCode; | ||
| 7 | -import lombok.ToString; | ||
| 8 | - | ||
| 9 | - | ||
| 10 | -@Schema(description = "管理后台 - 流程模型分页 Request VO") | ||
| 11 | -@Data | ||
| 12 | -public class BpmModelPageReqVO extends PageParam { | ||
| 13 | - | ||
| 14 | - @Schema(description = "标识,精准匹配", example = "process1641042089407") | ||
| 15 | - private String key; | ||
| 16 | - | ||
| 17 | - @Schema(description = "名字,模糊匹配", example = "芋道") | ||
| 18 | - private String name; | ||
| 19 | - | ||
| 20 | - @Schema(description = "流程分类", example = "1") | ||
| 21 | - private String category; | ||
| 22 | - | ||
| 23 | -} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
| @@ -62,6 +62,9 @@ public class BpmProcessDefinitionRespVO { | @@ -62,6 +62,9 @@ public class BpmProcessDefinitionRespVO { | ||
| 62 | @Schema(description = "BPMN XML") | 62 | @Schema(description = "BPMN XML") |
| 63 | private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回 | 63 | private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回 |
| 64 | 64 | ||
| 65 | + @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | ||
| 66 | + private Long sort; | ||
| 67 | + | ||
| 65 | @Schema(description = "发起用户需要选择审批人的任务数组") | 68 | @Schema(description = "发起用户需要选择审批人的任务数组") |
| 66 | private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回 | 69 | private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回 |
| 67 | 70 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
| @@ -123,18 +123,15 @@ public class BpmTaskController { | @@ -123,18 +123,15 @@ public class BpmTaskController { | ||
| 123 | } | 123 | } |
| 124 | 124 | ||
| 125 | // 拼接数据 | 125 | // 拼接数据 |
| 126 | - HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId); | ||
| 127 | - // 获得 User 和 Dept Map | ||
| 128 | Set<Long> userIds = convertSetByFlatMap(taskList, task -> | 126 | Set<Long> userIds = convertSetByFlatMap(taskList, task -> |
| 129 | Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); | 127 | Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); |
| 130 | - userIds.add(NumberUtils.parseLong(processInstance.getStartUserId())); | ||
| 131 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | 128 | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| 132 | Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( | 129 | Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( |
| 133 | convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); | 130 | convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); |
| 134 | // 获得 Form Map | 131 | // 获得 Form Map |
| 135 | Map<Long, BpmFormDO> formMap = formService.getFormMap( | 132 | Map<Long, BpmFormDO> formMap = formService.getFormMap( |
| 136 | convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey()))); | 133 | convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey()))); |
| 137 | - return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance, | 134 | + return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, |
| 138 | formMap, userMap, deptMap)); | 135 | formMap, userMap, deptMap)); |
| 139 | } | 136 | } |
| 140 | 137 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
| 1 | package cn.iocoder.yudao.module.bpm.convert.definition; | 1 | package cn.iocoder.yudao.module.bpm.convert.definition; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.util.ArrayUtil; | 3 | import cn.hutool.core.util.ArrayUtil; |
| 4 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 5 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; | 4 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 6 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| @@ -22,6 +21,7 @@ import org.mapstruct.Mapper; | @@ -22,6 +21,7 @@ import org.mapstruct.Mapper; | ||
| 22 | import org.mapstruct.factory.Mappers; | 21 | import org.mapstruct.factory.Mappers; |
| 23 | 22 | ||
| 24 | import java.util.Collections; | 23 | import java.util.Collections; |
| 24 | +import java.util.Comparator; | ||
| 25 | import java.util.List; | 25 | import java.util.List; |
| 26 | import java.util.Map; | 26 | import java.util.Map; |
| 27 | 27 | ||
| @@ -37,25 +37,31 @@ public interface BpmModelConvert { | @@ -37,25 +37,31 @@ public interface BpmModelConvert { | ||
| 37 | 37 | ||
| 38 | BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class); | 38 | BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class); |
| 39 | 39 | ||
| 40 | - default PageResult<BpmModelRespVO> buildModelPage(PageResult<Model> pageResult, | ||
| 41 | - Map<Long, BpmFormDO> formMap, | ||
| 42 | - Map<String, BpmCategoryDO> categoryMap, Map<String, Deployment> deploymentMap, | ||
| 43 | - Map<String, ProcessDefinition> processDefinitionMap, | ||
| 44 | - Map<Long, AdminUserRespDTO> userMap) { | ||
| 45 | - List<BpmModelRespVO> list = convertList(pageResult.getList(), model -> { | 40 | + default List<BpmModelRespVO> buildModelList(List<Model> list, |
| 41 | + Map<Long, BpmFormDO> formMap, | ||
| 42 | + Map<String, BpmCategoryDO> categoryMap, | ||
| 43 | + Map<String, Deployment> deploymentMap, | ||
| 44 | + Map<String, ProcessDefinition> processDefinitionMap, | ||
| 45 | + Map<Long, AdminUserRespDTO> userMap) { | ||
| 46 | + List<BpmModelRespVO> result = convertList(list, model -> { | ||
| 46 | BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); | 47 | BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); |
| 47 | BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; | 48 | BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; |
| 48 | BpmCategoryDO category = categoryMap.get(model.getCategory()); | 49 | BpmCategoryDO category = categoryMap.get(model.getCategory()); |
| 49 | Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; | 50 | Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; |
| 50 | - ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null; | ||
| 51 | - List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; | 51 | + ProcessDefinition processDefinition = model.getDeploymentId() != null |
| 52 | + ? processDefinitionMap.get(model.getDeploymentId()) | ||
| 53 | + : null; | ||
| 54 | + List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) | ||
| 55 | + : null; | ||
| 52 | return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); | 56 | return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); |
| 53 | }); | 57 | }); |
| 54 | - return new PageResult<>(list, pageResult.getTotal()); | 58 | + // 排序 |
| 59 | + result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort)); | ||
| 60 | + return result; | ||
| 55 | } | 61 | } |
| 56 | 62 | ||
| 57 | default BpmModelRespVO buildModel(Model model, | 63 | default BpmModelRespVO buildModel(Model model, |
| 58 | - byte[] bpmnBytes) { | 64 | + byte[] bpmnBytes) { |
| 59 | BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); | 65 | BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); |
| 60 | BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); | 66 | BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); |
| 61 | if (ArrayUtil.isNotEmpty(bpmnBytes)) { | 67 | if (ArrayUtil.isNotEmpty(bpmnBytes)) { |
| @@ -65,9 +71,9 @@ public interface BpmModelConvert { | @@ -65,9 +71,9 @@ public interface BpmModelConvert { | ||
| 65 | } | 71 | } |
| 66 | 72 | ||
| 67 | default BpmModelRespVO buildModel0(Model model, | 73 | default BpmModelRespVO buildModel0(Model model, |
| 68 | - BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, | ||
| 69 | - Deployment deployment, ProcessDefinition processDefinition, | ||
| 70 | - List<AdminUserRespDTO> startUsers) { | 74 | + BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, |
| 75 | + Deployment deployment, ProcessDefinition processDefinition, | ||
| 76 | + List<AdminUserRespDTO> startUsers) { | ||
| 71 | BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) | 77 | BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) |
| 72 | .setKey(model.getKey()).setCategory(model.getCategory()) | 78 | .setKey(model.getKey()).setCategory(model.getCategory()) |
| 73 | .setCreateTime(DateUtils.of(model.getCreateTime())); | 79 | .setCreateTime(DateUtils.of(model.getCreateTime())); |
| @@ -83,8 +89,9 @@ public interface BpmModelConvert { | @@ -83,8 +89,9 @@ public interface BpmModelConvert { | ||
| 83 | // ProcessDefinition | 89 | // ProcessDefinition |
| 84 | if (processDefinition != null) { | 90 | if (processDefinition != null) { |
| 85 | modelRespVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); | 91 | modelRespVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); |
| 86 | - modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ? | ||
| 87 | - SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode()); | 92 | + modelRespVO.getProcessDefinition() |
| 93 | + .setSuspensionState(processDefinition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() | ||
| 94 | + : SuspensionState.ACTIVE.getStateCode()); | ||
| 88 | if (deployment != null) { | 95 | if (deployment != null) { |
| 89 | modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime())); | 96 | modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime())); |
| 90 | } | 97 | } |
| @@ -112,6 +119,10 @@ public interface BpmModelConvert { | @@ -112,6 +119,10 @@ public interface BpmModelConvert { | ||
| 112 | if (vo.getStartUserIds() == null) { | 119 | if (vo.getStartUserIds() == null) { |
| 113 | vo.setStartUserIds(Collections.emptyList()); | 120 | vo.setStartUserIds(Collections.emptyList()); |
| 114 | } | 121 | } |
| 122 | + // 如果为空,兜底处理,使用 createTime 创建时间 | ||
| 123 | + if (vo.getSort() == null) { | ||
| 124 | + vo.setSort(model.getCreateTime().getTime()); | ||
| 125 | + } | ||
| 115 | return vo; | 126 | return vo; |
| 116 | } | 127 | } |
| 117 | 128 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
| @@ -20,6 +20,7 @@ import org.mapstruct.Mapping; | @@ -20,6 +20,7 @@ import org.mapstruct.Mapping; | ||
| 20 | import org.mapstruct.MappingTarget; | 20 | import org.mapstruct.MappingTarget; |
| 21 | import org.mapstruct.factory.Mappers; | 21 | import org.mapstruct.factory.Mappers; |
| 22 | 22 | ||
| 23 | +import java.util.Comparator; | ||
| 23 | import java.util.List; | 24 | import java.util.List; |
| 24 | import java.util.Map; | 25 | import java.util.Map; |
| 25 | 26 | ||
| @@ -47,7 +48,7 @@ public interface BpmProcessDefinitionConvert { | @@ -47,7 +48,7 @@ public interface BpmProcessDefinitionConvert { | ||
| 47 | Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap, | 48 | Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap, |
| 48 | Map<Long, BpmFormDO> formMap, | 49 | Map<Long, BpmFormDO> formMap, |
| 49 | Map<String, BpmCategoryDO> categoryMap) { | 50 | Map<String, BpmCategoryDO> categoryMap) { |
| 50 | - return CollectionUtils.convertList(list, definition -> { | 51 | + List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> { |
| 51 | Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class); | 52 | Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class); |
| 52 | BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class); | 53 | BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class); |
| 53 | BpmFormDO form = null; | 54 | BpmFormDO form = null; |
| @@ -57,6 +58,9 @@ public interface BpmProcessDefinitionConvert { | @@ -57,6 +58,9 @@ public interface BpmProcessDefinitionConvert { | ||
| 57 | BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class); | 58 | BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class); |
| 58 | return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null); | 59 | return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null); |
| 59 | }); | 60 | }); |
| 61 | + // 排序 | ||
| 62 | + result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort)); | ||
| 63 | + return result; | ||
| 60 | } | 64 | } |
| 61 | 65 | ||
| 62 | default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition, | 66 | default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition, |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
| @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||
| 9 | import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | 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.enums.task.BpmTaskStatusEnum; | ||
| 12 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; | 13 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; |
| 13 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; | 14 | import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; |
| 14 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | 15 | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| @@ -78,18 +79,17 @@ public interface BpmTaskConvert { | @@ -78,18 +79,17 @@ public interface BpmTaskConvert { | ||
| 78 | } | 79 | } |
| 79 | 80 | ||
| 80 | default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList, | 81 | default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList, |
| 81 | - HistoricProcessInstance processInstance, | ||
| 82 | Map<Long, BpmFormDO> formMap, | 82 | Map<Long, BpmFormDO> formMap, |
| 83 | Map<Long, AdminUserRespDTO> userMap, | 83 | Map<Long, AdminUserRespDTO> userMap, |
| 84 | Map<Long, DeptRespDTO> deptMap) { | 84 | Map<Long, DeptRespDTO> deptMap) { |
| 85 | return CollectionUtils.convertList(taskList, task -> { | 85 | return CollectionUtils.convertList(taskList, task -> { |
| 86 | + // 特殊:已取消的任务,不返回 | ||
| 86 | BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); | 87 | BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); |
| 87 | Integer taskStatus = FlowableUtils.getTaskStatus(task); | 88 | Integer taskStatus = FlowableUtils.getTaskStatus(task); |
| 89 | + if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) { | ||
| 90 | + return null; | ||
| 91 | + } | ||
| 88 | taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); | 92 | taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); |
| 89 | - // 流程实例 | ||
| 90 | - AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); | ||
| 91 | - taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); | ||
| 92 | - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); | ||
| 93 | // 表单信息 | 93 | // 表单信息 |
| 94 | BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); | 94 | BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); |
| 95 | if (form != null) { | 95 | if (form != null) { |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
| @@ -121,6 +121,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { | @@ -121,6 +121,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { | ||
| 121 | * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 | 121 | * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 |
| 122 | */ | 122 | */ |
| 123 | private Boolean visible; | 123 | private Boolean visible; |
| 124 | + /** | ||
| 125 | + * 排序值 | ||
| 126 | + */ | ||
| 127 | + private Long sort; | ||
| 124 | 128 | ||
| 125 | /** | 129 | /** |
| 126 | * 可发起用户编号数组 | 130 | * 可发起用户编号数组 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
| 1 | package cn.iocoder.yudao.module.bpm.dal.mysql.definition; | 1 | package cn.iocoder.yudao.module.bpm.dal.mysql.definition; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 3 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; | 4 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | import org.apache.ibatis.annotations.Mapper; | 6 | import org.apache.ibatis.annotations.Mapper; |
| @@ -18,4 +19,9 @@ public interface BpmProcessDefinitionInfoMapper extends BaseMapperX<BpmProcessDe | @@ -18,4 +19,9 @@ public interface BpmProcessDefinitionInfoMapper extends BaseMapperX<BpmProcessDe | ||
| 18 | return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId); | 19 | return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId); |
| 19 | } | 20 | } |
| 20 | 21 | ||
| 22 | + default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) { | ||
| 23 | + update(updateObj, | ||
| 24 | + new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId)); | ||
| 25 | + } | ||
| 26 | + | ||
| 21 | } | 27 | } |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmCategoryService.java
| @@ -82,4 +82,11 @@ public interface BpmCategoryService { | @@ -82,4 +82,11 @@ public interface BpmCategoryService { | ||
| 82 | */ | 82 | */ |
| 83 | List<BpmCategoryDO> getCategoryListByStatus(Integer status); | 83 | List<BpmCategoryDO> getCategoryListByStatus(Integer status); |
| 84 | 84 | ||
| 85 | + /** | ||
| 86 | + * 批量更新流程分类的排序:每个分类的 sort 值,从 0 开始递增 | ||
| 87 | + * | ||
| 88 | + * @param ids 分类编号列表 | ||
| 89 | + */ | ||
| 90 | + void updateCategorySortBatch(List<Long> ids); | ||
| 91 | + | ||
| 85 | } | 92 | } |
| 86 | \ No newline at end of file | 93 | \ No newline at end of file |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmCategoryServiceImpl.java
| @@ -2,21 +2,22 @@ package cn.iocoder.yudao.module.bpm.service.definition; | @@ -2,21 +2,22 @@ package cn.iocoder.yudao.module.bpm.service.definition; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.util.ObjUtil; | 4 | import cn.hutool.core.util.ObjUtil; |
| 5 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 6 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||
| 5 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; | 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO; |
| 6 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO; |
| 7 | -import org.springframework.stereotype.Service; | ||
| 8 | -import jakarta.annotation.Resource; | ||
| 9 | -import org.springframework.validation.annotation.Validated; | ||
| 10 | - | ||
| 11 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; | 9 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 12 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 13 | -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||
| 14 | - | ||
| 15 | import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper; | 10 | import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper; |
| 11 | +import jakarta.annotation.Resource; | ||
| 12 | +import org.springframework.stereotype.Service; | ||
| 13 | +import org.springframework.transaction.annotation.Transactional; | ||
| 14 | +import org.springframework.validation.annotation.Validated; | ||
| 16 | 15 | ||
| 17 | import java.util.Collection; | 16 | import java.util.Collection; |
| 18 | import java.util.Collections; | 17 | import java.util.Collections; |
| 19 | import java.util.List; | 18 | import java.util.List; |
| 19 | +import java.util.stream.Collectors; | ||
| 20 | +import java.util.stream.IntStream; | ||
| 20 | 21 | ||
| 21 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 22 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; | 23 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; |
| @@ -58,7 +59,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { | @@ -58,7 +59,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { | ||
| 58 | private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) { | 59 | private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) { |
| 59 | BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName()); | 60 | BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName()); |
| 60 | if (category == null | 61 | if (category == null |
| 61 | - || ObjUtil.equal(category.getId(), updateReqVO.getId())) { | 62 | + || ObjUtil.equal(category.getId(), updateReqVO.getId())) { |
| 62 | return; | 63 | return; |
| 63 | } | 64 | } |
| 64 | throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName()); | 65 | throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName()); |
| @@ -67,7 +68,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { | @@ -67,7 +68,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { | ||
| 67 | private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) { | 68 | private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) { |
| 68 | BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode()); | 69 | BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode()); |
| 69 | if (category == null | 70 | if (category == null |
| 70 | - || ObjUtil.equal(category.getId(), updateReqVO.getId())) { | 71 | + || ObjUtil.equal(category.getId(), updateReqVO.getId())) { |
| 71 | return; | 72 | return; |
| 72 | } | 73 | } |
| 73 | throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode()); | 74 | throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode()); |
| @@ -110,4 +111,20 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { | @@ -110,4 +111,20 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { | ||
| 110 | return bpmCategoryMapper.selectListByStatus(status); | 111 | return bpmCategoryMapper.selectListByStatus(status); |
| 111 | } | 112 | } |
| 112 | 113 | ||
| 114 | + @Override | ||
| 115 | + @Transactional(rollbackFor = Exception.class) | ||
| 116 | + public void updateCategorySortBatch(List<Long> ids) { | ||
| 117 | + // 校验分类都存在 | ||
| 118 | + List<BpmCategoryDO> categories = bpmCategoryMapper.selectByIds(ids); | ||
| 119 | + if (categories.size() != ids.size()) { | ||
| 120 | + throw exception(CATEGORY_NOT_EXISTS); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + // 批量更新排序 | ||
| 124 | + List<BpmCategoryDO> updateList = IntStream.range(0, ids.size()) | ||
| 125 | + .mapToObj(index -> new BpmCategoryDO().setId(ids.get(index)).setSort(index)) | ||
| 126 | + .collect(Collectors.toList()); | ||
| 127 | + bpmCategoryMapper.updateBatch(updateList); | ||
| 128 | + } | ||
| 129 | + | ||
| 113 | } | 130 | } |
| 114 | \ No newline at end of file | 131 | \ No newline at end of file |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
| 1 | package cn.iocoder.yudao.module.bpm.service.definition; | 1 | package cn.iocoder.yudao.module.bpm.service.definition; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 4 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; | ||
| 5 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; | 3 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; |
| 6 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | 4 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 7 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| @@ -9,20 +7,22 @@ import jakarta.validation.Valid; | @@ -9,20 +7,22 @@ import jakarta.validation.Valid; | ||
| 9 | import org.flowable.bpmn.model.BpmnModel; | 7 | import org.flowable.bpmn.model.BpmnModel; |
| 10 | import org.flowable.engine.repository.Model; | 8 | import org.flowable.engine.repository.Model; |
| 11 | 9 | ||
| 10 | +import java.util.List; | ||
| 11 | + | ||
| 12 | /** | 12 | /** |
| 13 | - * Flowable流程模型接口 | 13 | + * 流程模型接口 |
| 14 | * | 14 | * |
| 15 | * @author yunlongn | 15 | * @author yunlongn |
| 16 | */ | 16 | */ |
| 17 | public interface BpmModelService { | 17 | public interface BpmModelService { |
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | - * 获得流程模型分页 | 20 | + * 获得流程模型列表 |
| 21 | * | 21 | * |
| 22 | - * @param pageVO 分页查询 | ||
| 23 | - * @return 流程模型分页 | 22 | + * @param name 模型名称 |
| 23 | + * @return 流程模型列表 | ||
| 24 | */ | 24 | */ |
| 25 | - PageResult<Model> getModelPage(BpmModelPageReqVO pageVO); | 25 | + List<Model> getModelList(String name); |
| 26 | 26 | ||
| 27 | /** | 27 | /** |
| 28 | * 创建流程模型 | 28 | * 创建流程模型 |
| @@ -65,6 +65,14 @@ public interface BpmModelService { | @@ -65,6 +65,14 @@ public interface BpmModelService { | ||
| 65 | void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); | 65 | void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); |
| 66 | 66 | ||
| 67 | /** | 67 | /** |
| 68 | + * 批量更新模型排序 | ||
| 69 | + * | ||
| 70 | + * @param userId 用户编号 | ||
| 71 | + * @param ids 编号列表 | ||
| 72 | + */ | ||
| 73 | + void updateModelSortBatch(Long userId, List<String> ids); | ||
| 74 | + | ||
| 75 | + /** | ||
| 68 | * 将流程模型,部署成一个流程定义 | 76 | * 将流程模型,部署成一个流程定义 |
| 69 | * | 77 | * |
| 70 | * @param userId 用户编号 | 78 | * @param userId 用户编号 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
| @@ -3,12 +3,9 @@ package cn.iocoder.yudao.module.bpm.service.definition; | @@ -3,12 +3,9 @@ package cn.iocoder.yudao.module.bpm.service.definition; | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.util.ArrayUtil; | 4 | import cn.hutool.core.util.ArrayUtil; |
| 5 | import cn.hutool.core.util.StrUtil; | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 7 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 6 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 8 | -import cn.iocoder.yudao.framework.common.util.object.PageUtils; | ||
| 9 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | 7 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; | 8 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; |
| 11 | -import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; | ||
| 12 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; |
| 13 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| 14 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; | 11 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; |
| @@ -35,14 +32,15 @@ import org.springframework.transaction.annotation.Transactional; | @@ -35,14 +32,15 @@ import org.springframework.transaction.annotation.Transactional; | ||
| 35 | import org.springframework.validation.annotation.Validated; | 32 | import org.springframework.validation.annotation.Validated; |
| 36 | 33 | ||
| 37 | import java.util.List; | 34 | import java.util.List; |
| 35 | +import java.util.Map; | ||
| 38 | import java.util.Objects; | 36 | import java.util.Objects; |
| 39 | 37 | ||
| 40 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 38 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 39 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||
| 41 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; | 40 | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; |
| 42 | 41 | ||
| 43 | /** | 42 | /** |
| 44 | - * Flowable流程模型实现 | ||
| 45 | - * 主要进行 Flowable {@link Model} 的维护 | 43 | + * 流程模型实现:主要进行 Flowable {@link Model} 的维护 |
| 46 | * | 44 | * |
| 47 | * @author yunlongn | 45 | * @author yunlongn |
| 48 | * @author 芋道源码 | 46 | * @author 芋道源码 |
| @@ -64,27 +62,12 @@ public class BpmModelServiceImpl implements BpmModelService { | @@ -64,27 +62,12 @@ public class BpmModelServiceImpl implements BpmModelService { | ||
| 64 | private BpmTaskCandidateInvoker taskCandidateInvoker; | 62 | private BpmTaskCandidateInvoker taskCandidateInvoker; |
| 65 | 63 | ||
| 66 | @Override | 64 | @Override |
| 67 | - public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) { | 65 | + public List<Model> getModelList(String name) { |
| 68 | ModelQuery modelQuery = repositoryService.createModelQuery(); | 66 | ModelQuery modelQuery = repositoryService.createModelQuery(); |
| 69 | - modelQuery.modelTenantId(FlowableUtils.getTenantId()); | ||
| 70 | - if (StrUtil.isNotBlank(pageVO.getKey())) { | ||
| 71 | - modelQuery.modelKey(pageVO.getKey()); | 67 | + if (StrUtil.isNotEmpty(name)) { |
| 68 | + modelQuery.modelNameLike(name); | ||
| 72 | } | 69 | } |
| 73 | - if (StrUtil.isNotBlank(pageVO.getName())) { | ||
| 74 | - modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配 | ||
| 75 | - } | ||
| 76 | - if (StrUtil.isNotBlank(pageVO.getCategory())) { | ||
| 77 | - modelQuery.modelCategory(pageVO.getCategory()); | ||
| 78 | - } | ||
| 79 | - // 执行查询 | ||
| 80 | - long count = modelQuery.count(); | ||
| 81 | - if (count == 0) { | ||
| 82 | - return PageResult.empty(count); | ||
| 83 | - } | ||
| 84 | - List<Model> models = modelQuery | ||
| 85 | - .orderByCreateTime().desc() | ||
| 86 | - .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); | ||
| 87 | - return new PageResult<>(models, count); | 70 | + return modelQuery.list(); |
| 88 | } | 71 | } |
| 89 | 72 | ||
| 90 | @Override | 73 | @Override |
| @@ -100,6 +83,7 @@ public class BpmModelServiceImpl implements BpmModelService { | @@ -100,6 +83,7 @@ public class BpmModelServiceImpl implements BpmModelService { | ||
| 100 | } | 83 | } |
| 101 | 84 | ||
| 102 | // 2.1 创建流程定义 | 85 | // 2.1 创建流程定义 |
| 86 | + createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序 | ||
| 103 | Model model = repositoryService.newModel(); | 87 | Model model = repositoryService.newModel(); |
| 104 | BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); | 88 | BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); |
| 105 | model.setTenantId(FlowableUtils.getTenantId()); | 89 | model.setTenantId(FlowableUtils.getTenantId()); |
| @@ -120,6 +104,34 @@ public class BpmModelServiceImpl implements BpmModelService { | @@ -120,6 +104,34 @@ public class BpmModelServiceImpl implements BpmModelService { | ||
| 120 | repositoryService.saveModel(model); | 104 | repositoryService.saveModel(model); |
| 121 | } | 105 | } |
| 122 | 106 | ||
| 107 | + @Override | ||
| 108 | + @Transactional(rollbackFor = Exception.class) | ||
| 109 | + public void updateModelSortBatch(Long userId, List<String> ids) { | ||
| 110 | + // 1.1 校验流程模型存在 | ||
| 111 | + List<Model> models = repositoryService.createModelQuery() | ||
| 112 | + .modelTenantId(FlowableUtils.getTenantId()).list(); | ||
| 113 | + models.removeIf(model ->!ids.contains(model.getId())); | ||
| 114 | + if (ids.size() != models.size()) { | ||
| 115 | + throw exception(MODEL_NOT_EXISTS); | ||
| 116 | + } | ||
| 117 | + Map<String, Model> modelMap = convertMap(models, Model::getId); | ||
| 118 | + // 1.2 校验是否为管理员 | ||
| 119 | + ids.forEach(id -> validateModelManager(id, userId)); | ||
| 120 | + | ||
| 121 | + // 保存排序 | ||
| 122 | + long sort = System.currentTimeMillis(); // 使用时间戳 - i 作为排序 | ||
| 123 | + for (int i = ids.size() - 1; i > 0; i--) { | ||
| 124 | + Model model = modelMap.get(ids.get(i)); | ||
| 125 | + // 更新模型 | ||
| 126 | + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model).setSort(sort); | ||
| 127 | + model.setMetaInfo(JsonUtils.toJsonString(metaInfo)); | ||
| 128 | + repositoryService.saveModel(model); | ||
| 129 | + // 更新排序 | ||
| 130 | + processDefinitionService.updateProcessDefinitionSortByModelId(model.getId(), sort); | ||
| 131 | + sort--; | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 123 | private Model validateModelExists(String id) { | 135 | private Model validateModelExists(String id) { |
| 124 | Model model = repositoryService.getModel(id); | 136 | Model model = repositoryService.getModel(id); |
| 125 | if (model == null) { | 137 | if (model == null) { |
| @@ -139,7 +151,7 @@ public class BpmModelServiceImpl implements BpmModelService { | @@ -139,7 +151,7 @@ public class BpmModelServiceImpl implements BpmModelService { | ||
| 139 | Model model = validateModelExists(id); | 151 | Model model = validateModelExists(id); |
| 140 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); | 152 | BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); |
| 141 | if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { | 153 | if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { |
| 142 | - throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER); | 154 | + throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER, model.getName()); |
| 143 | } | 155 | } |
| 144 | return model; | 156 | return model; |
| 145 | } | 157 | } |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
| @@ -18,7 +18,7 @@ import java.util.Set; | @@ -18,7 +18,7 @@ import java.util.Set; | ||
| 18 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | 18 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 19 | 19 | ||
| 20 | /** | 20 | /** |
| 21 | - * Flowable流程定义接口 | 21 | + * 流程定义接口 |
| 22 | * | 22 | * |
| 23 | * @author yunlong.li | 23 | * @author yunlong.li |
| 24 | * @author ZJQ | 24 | * @author ZJQ |
| @@ -64,6 +64,14 @@ public interface BpmProcessDefinitionService { | @@ -64,6 +64,14 @@ public interface BpmProcessDefinitionService { | ||
| 64 | void updateProcessDefinitionState(String id, Integer state); | 64 | void updateProcessDefinitionState(String id, Integer state); |
| 65 | 65 | ||
| 66 | /** | 66 | /** |
| 67 | + * 更新模型编号 | ||
| 68 | + * | ||
| 69 | + * @param modelId 流程定义编号 | ||
| 70 | + * @param sort 排序 | ||
| 71 | + */ | ||
| 72 | + void updateProcessDefinitionSortByModelId(String modelId, Long sort); | ||
| 73 | + | ||
| 74 | + /** | ||
| 67 | * 获得流程定义对应的 BPMN | 75 | * 获得流程定义对应的 BPMN |
| 68 | * | 76 | * |
| 69 | * @param id 流程定义编号 | 77 | * @param id 流程定义编号 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
| @@ -173,6 +173,11 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ | @@ -173,6 +173,11 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ | ||
| 173 | } | 173 | } |
| 174 | 174 | ||
| 175 | @Override | 175 | @Override |
| 176 | + public void updateProcessDefinitionSortByModelId(String modelId, Long sort) { | ||
| 177 | + processDefinitionMapper.updateByModelId(modelId, new BpmProcessDefinitionInfoDO().setSort(sort)); | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + @Override | ||
| 176 | public BpmnModel getProcessDefinitionBpmnModel(String id) { | 181 | public BpmnModel getProcessDefinitionBpmnModel(String id) { |
| 177 | return repositoryService.getBpmnModel(id); | 182 | return repositoryService.getBpmnModel(id); |
| 178 | } | 183 | } |
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.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 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.strategy.dept.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.*; 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.framework.common.util.collection.CollectionUtils.filterList; 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.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 AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; // ========== 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; // 流程实例 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); } 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, processDefinition, processVariables, 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, processVariables, activities); // 4. 拼接最终数据 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask); } /** * 拼接审批详情的最终数据 * <p> * 主要是,拼接审批人的用户信息、部门信息 */ 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, todoTask); 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(BpmSimpleModelNodeType.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, ProcessDefinition processDefinition, Map<String, Object> processVariables, 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 = filterList( taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), childTask -> childTask.getEndTime() == null ); 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(bpmnModel, flowNode.getId(), startUserId, processDefinition.getId(), processVariables); // 截取当前审批人位置后面的候选人,不包含当前审批人 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks()); Assert.notNull(approvalTaskInfo, "任务不能为空"); int index = CollUtil.indexOf(candidateUserIds, userId -> userId.equals(approvalTaskInfo.getOwner()) || userId.equals(approvalTaskInfo.getAssignee())); // 委派或者向前加签情况需要先比较 owner activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); } return activityNode; }); } /** * 获得【预测(未来)】的活动节点们 */ private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, 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, processVariables); return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel, processDefinitionInfo, processVariables, 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, processVariables); return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel, processDefinitionInfo, processVariables, simpleNode, runActivityIds)); } throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); } private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, 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())) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); activityNode.setCandidateUserIds(candidateUserIds); return activityNode; } // 2. 结束节点 if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) { return activityNode; } // 3. 抄送节点 TODO @jason:抄送节点 return null; } private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, 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(BpmSimpleModelNodeType.START_USER_NODE.getType()); } // 2. 审批节点 if (node instanceof UserTask) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setCandidateUserIds(candidateUserIds); } // 3. 结束节点 if (node instanceof EndEvent) { return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()); } return null; } private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId, Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, startUserId, processDefinitionId, processVariables); return new ArrayList<>(userIds); } @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; } BpmSimpleModelNodeVO simpleModel = null; BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); if (processDefinitionInfo != null) { simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); } // 1.3 获得流程实例对应的活动实例列表 + 任务列表 List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true); // 2.1 拼接进度信息 Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() == null); Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); Set<String> finishedSequenceFlowActivityIds = convertSet(activities, 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))) { tasks.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 = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel, 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)); }); } } | ||
| 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.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 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.strategy.dept.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.*; 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.framework.common.util.collection.CollectionUtils.filterList; 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.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 AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; // ========== 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; // 流程实例 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); } 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, processDefinition, processVariables, 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, processVariables, activities); // 4. 拼接最终数据 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask); } /** * 拼接审批详情的最终数据 * <p> * 主要是,拼接审批人的用户信息、部门信息 */ 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, todoTask); 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(BpmSimpleModelNodeType.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, ProcessDefinition processDefinition, Map<String, Object> processVariables, 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()) .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime())) .setTasks(new ArrayList<>()); // 处理每个任务的 tasks 属性 for (HistoricActivityInstance activity : taskActivities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); // 加签子任务,需要过滤掉已经完成的加签子任务 List<HistoricTaskInstance> childrenTasks = filterList( taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), childTask -> childTask.getEndTime() == null ); 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(bpmnModel, flowNode.getId(), startUserId, processDefinition.getId(), processVariables); // 截取当前审批人位置后面的候选人,不包含当前审批人 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks()); Assert.notNull(approvalTaskInfo, "任务不能为空"); int index = CollUtil.indexOf(candidateUserIds, userId -> userId.equals(approvalTaskInfo.getOwner()) || userId.equals(approvalTaskInfo.getAssignee())); // 委派或者向前加签情况需要先比较 owner activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); } return activityNode; }); } /** * 获得【预测(未来)】的活动节点们 */ private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, 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, processVariables); return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel, processDefinitionInfo, processVariables, 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, processVariables); return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel, processDefinitionInfo, processVariables, simpleNode, runActivityIds)); } throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); } private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, 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())) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); activityNode.setCandidateUserIds(candidateUserIds); return activityNode; } // 2. 结束节点 if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) { return activityNode; } // 3. 抄送节点 TODO @jason:抄送节点 return null; } private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, 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(BpmSimpleModelNodeType.START_USER_NODE.getType()); } // 2. 审批节点 if (node instanceof UserTask) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setCandidateUserIds(candidateUserIds); } // 3. 结束节点 if (node instanceof EndEvent) { return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()); } return null; } private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId, Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, startUserId, processDefinitionId, processVariables); return new ArrayList<>(userIds); } @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; } BpmSimpleModelNodeVO simpleModel = null; BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); if (processDefinitionInfo != null && BpmModelTypeEnum.SIMPLE.getType().equals(processDefinitionInfo.getModelType())) { simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); } // 1.3 获得流程实例对应的活动实例列表 + 任务列表 List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true); // 2.1 拼接进度信息 Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() == null); Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); Set<String> finishedSequenceFlowActivityIds = convertSet(activities, 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))) { tasks.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 = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel, 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 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| @@ -764,9 +764,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -764,9 +764,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 764 | taskService.setOwner(taskId, task.getAssignee()); | 764 | taskService.setOwner(taskId, task.getAssignee()); |
| 765 | // 3.2 执行委派,将任务委派给 delegateUser | 765 | // 3.2 执行委派,将任务委派给 delegateUser |
| 766 | taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); | 766 | taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); |
| 767 | - // 3.3 更新 task 状态。 | ||
| 768 | - // 为什么不更新原因?因为原因目前主要给审批通过、不通过时使用 | ||
| 769 | - updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus()); | 767 | + // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中 |
| 770 | } | 768 | } |
| 771 | 769 | ||
| 772 | @Override | 770 | @Override |