From b8a0affd03b5fa9fa33cd6f870e90394c2df86c7 Mon Sep 17 00:00:00 2001
From: 潘志宝 <979469083@qq.com>
Date: 星期一, 06 一月 2025 13:31:07 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java                                          |    2 
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java                   |    2 
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java                                          |   12 ++
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java                              |    9 +
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java                                       |   16 +-
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java                                              |   60 +++++++--
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java                                      |    5 
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java                                                         |   14 +
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java                                      |    8 
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java                                 |   11 +
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java                                                  |    4 
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java                                         |   21 +-
 iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/pojo/CommonResult.java                                                                      |   22 ++-
 iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java                                                   |    1 
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java                 |    6 
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/tenant/TenantPackageMapper.java                                       |    5 
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java |   17 ++
 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java                                                     |   89 +++++++++++---
 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantPackageServiceImpl.java                                    |   22 +++
 19 files changed, 242 insertions(+), 84 deletions(-)

diff --git a/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/pojo/CommonResult.java b/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/pojo/CommonResult.java
index 2f39029..063d0a6 100644
--- a/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/pojo/CommonResult.java
+++ b/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/pojo/CommonResult.java
@@ -1,11 +1,12 @@
 package com.iailab.framework.common.pojo;
 
+import cn.hutool.core.lang.Assert;
 import com.iailab.framework.common.exception.ErrorCode;
 import com.iailab.framework.common.exception.ServiceException;
 import com.iailab.framework.common.exception.enums.GlobalErrorCodeConstants;
 import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.iailab.framework.common.exception.util.ServiceExceptionUtil;
 import lombok.Data;
-import org.springframework.util.Assert;
 
 import java.io.Serializable;
 import java.util.Objects;
@@ -41,7 +42,7 @@
      * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
      *
      * @param result 传入的 result 对象
-     * @param <T>    返回的泛型
+     * @param <T> 返回的泛型
      * @return 新的 CommonResult 对象
      */
     public static <T> CommonResult<T> error(CommonResult<?> result) {
@@ -49,10 +50,18 @@
     }
 
     public static <T> CommonResult<T> error(Integer code, String message) {
-        Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!");
+        cn.hutool.core.lang.Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
         CommonResult<T> result = new CommonResult<>();
         result.code = code;
         result.msg = message;
+        return result;
+    }
+
+    public static <T> CommonResult<T> error(ErrorCode errorCode, Object... params) {
+        Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), errorCode.getCode(), "code 必须是错误的!");
+        CommonResult<T> result = new CommonResult<>();
+        result.code = errorCode.getCode();
+        result.msg = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), params);
         return result;
     }
 
@@ -65,13 +74,6 @@
         result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
         result.data = data;
         result.msg = "";
-        return result;
-    }
-
-    public static CommonResult<String> success() {
-        CommonResult<String> result = new CommonResult<>();
-        result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
-        result.msg = "success";
         return result;
     }
 
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java
index 43d6863..1486eb5 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java
@@ -97,7 +97,8 @@
             return null;
         }
         byte[] bpmnBytes = modelService.getModelBpmnXML(id);
-        return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes));
+        BpmSimpleModelNodeVO simpleModel = modelService.getSimpleModel(id);
+        return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes, simpleModel));
     }
 
     @PostMapping("/create")
@@ -106,7 +107,6 @@
     public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) {
         return success(modelService.createModel(createRetVO));
     }
-
 
     @PutMapping("/update")
     @Operation(summary = "修改模型")
@@ -141,6 +141,7 @@
         return success(true);
     }
 
+    @Deprecated
     @PutMapping("/update-bpmn")
     @Operation(summary = "修改模型的 BPMN")
     @PreAuthorize("@ss.hasPermission('bpm:model:update')")
@@ -167,6 +168,7 @@
         return success(modelService.getSimpleModel(modelId));
     }
 
+    @Deprecated
     @PostMapping("/simple/update")
     @Operation(summary = "保存仿钉钉流程设计模型")
     @PreAuthorize("@ss.hasPermission('bpm:model:update')")
@@ -174,6 +176,4 @@
         modelService.updateSimpleModel(getLoginUserId(), reqVO);
         return success(Boolean.TRUE);
     }
-
-
 }
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
index 4ea235d..84e7ad0 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
@@ -1,6 +1,7 @@
 package com.iailab.module.bpm.controller.admin.definition.vo.model;
 
 import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
 import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -35,15 +36,19 @@
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
-    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
-    private String bpmnXml;
-
     @Schema(description = "可发起的用户数组")
     private List<UserSimpleBaseVO> startUsers;
+
+    @Schema(description = "BPMN XML")
+    private String bpmnXml;
+
+    @Schema(description = "仿钉钉流程设计模型对象")
+    private BpmSimpleModelNodeVO simpleModel;
 
     /**
      * 最新部署的流程定义
      */
     private BpmProcessDefinitionRespVO processDefinition;
 
+
 }
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java
index 3e8fb9f..fa629e8 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java
@@ -1,8 +1,10 @@
 package com.iailab.module.bpm.controller.admin.definition.vo.model;
 
+import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import javax.validation.Valid;
 import javax.validation.constraints.NotEmpty;
 
 @Schema(description = "管理后台 - 流程模型的保存 Request VO")
@@ -23,4 +25,11 @@
     @Schema(description = "流程分类", example = "1")
     private String category;
 
+    @Schema(description = "BPMN XML")
+    private String bpmnXml;
+
+    @Schema(description = "仿钉钉流程设计模型对象")
+    @Valid
+    private BpmSimpleModelNodeVO simpleModel;
+
 }
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
index eae4182..2765a17 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
@@ -12,9 +12,12 @@
 @Data
 public class BpmTaskPageReqVO extends PageParam {
 
-    @Schema(description = "流程任务名", example = "平台")
+    @Schema(description = "流程任务名", example = "芋道")
     private String name;
 
+    @Schema(description = "流程分类", example = "1")
+    private String category;
+
     @Schema(description = "创建时间")
     @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java
index 1f9e2ea..8ba4a80 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java
@@ -8,6 +8,7 @@
 import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
 import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO;
 import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
+import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
 import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
 import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO;
 import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
@@ -58,12 +59,13 @@
         return result;
     }
 
-    default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) {
+    default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes, BpmSimpleModelNodeVO simpleModel) {
         BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
         BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null);
         if (ArrayUtil.isNotEmpty(bpmnBytes)) {
             modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes));
         }
+        modelVO.setSimpleModel(simpleModel);
         return modelVO;
     }
 
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java
index 5b1e179..efa01fc 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java
@@ -58,7 +58,7 @@
                                                     Map<String, HistoricProcessInstance> processInstanceMap,
                                                     Map<Long, AdminUserRespDTO> userMap,
                                                     Map<Long, DeptRespDTO> deptMap) {
-        List<BpmTaskRespVO> taskVOList = convertList(pageResult.getList(), task -> {
+        List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(pageResult.getList(), task -> {
             BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
             taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
             // 用户信息
@@ -83,7 +83,7 @@
                                                                  Map<Long, BpmFormDO> formMap,
                                                                  Map<Long, AdminUserRespDTO> userMap,
                                                                  Map<Long, DeptRespDTO> deptMap) {
-        return convertList(taskList, task -> {
+        return CollectionUtils.convertList(taskList, task -> {
             // 特殊:已取消的任务,不返回
             BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
             Integer taskStatus = FlowableUtils.getTaskStatus(task);
@@ -125,12 +125,18 @@
     }
 
     default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks,
-                                              Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) {
-        return BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
+                                        Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting,
+                                        BpmFormDO form) {
+        BpmTaskRespVO bpmTaskRespVO = BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
                 .setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask))
                 .setButtonsSetting(buttonsSetting)
                 .setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class)
                         .setStatus(FlowableUtils.getTaskStatus(childTask))));
+        if (form != null) {
+            bpmTaskRespVO.setFormId(form.getId()).setFormName(form.getName())
+                    .setFormConf(form.getConf()).setFormFields(form.getFields());
+        }
+        return bpmTaskRespVO;
     }
 
     default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
index 3b36c24..eb5dcde 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
@@ -54,13 +54,13 @@
         Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
         if (assigneeUserIds == null) {
             assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
-            execution.setVariable(super.collectionVariable, assigneeUserIds);
             if (CollUtil.isEmpty(assigneeUserIds)) {
                 // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
                 // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
                 // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
                 assigneeUserIds = SetUtils.asSet((Long) null);
             }
+            execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
         }
         return assigneeUserIds.size();
     }
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
index e7ecc99..2c897f2 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java
@@ -10,7 +10,6 @@
 import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
 import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
 
-import java.util.LinkedHashSet;
 import java.util.Set;
 
 /**
@@ -44,17 +43,18 @@
         super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
 
         // 第二步,获取任务的所有处理人
+        // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
         @SuppressWarnings("unchecked")
-        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
+        Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
         if (assigneeUserIds == null) {
             assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
-            execution.setVariable(super.collectionVariable, assigneeUserIds);
             if (CollUtil.isEmpty(assigneeUserIds)) {
                 // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
                 // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
                 // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时
                 assigneeUserIds = SetUtils.asSet((Long) null);
             }
+            execution.setVariableLocal(super.collectionVariable, assigneeUserIds);
         }
         return assigneeUserIds.size();
     }
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
index 50d10e3..11ff269 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
@@ -1,13 +1,17 @@
 package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.other;
 
 import cn.hutool.core.convert.Convert;
+import com.google.common.collect.Sets;
 import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
 import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
 import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.common.engine.api.FlowableException;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.springframework.stereotype.Component;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -17,6 +21,7 @@
  * @author iailab
  */
 @Component
+@Slf4j
 public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy {
 
     @Override
@@ -38,8 +43,16 @@
     @Override
     public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
                                               Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
-        Object result = FlowableUtils.getExpressionValue(processVariables, param);
-        return Convert.toSet(Long.class, result);
+        Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables;
+        try {
+            Object result = FlowableUtils.getExpressionValue(variables, param);
+            return Convert.toSet(Long.class, result);
+        } catch (FlowableException ex) {
+            // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,
+            log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
+            // 不能预测候选人,返回空列表, 避免流程无法进行
+            return Sets.newHashSet();
+        }
     }
 
 }
\ No newline at end of file
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
index 2a79423..799ada4 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -73,7 +73,6 @@
         extensionElement.setName(name);
         attributes.forEach((key, value) -> {
             ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
-            extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
             extensionElement.addAttribute(extensionAttribute);
         });
         element.addExtensionElement(extensionElement);
@@ -278,8 +277,8 @@
         }
         Map<String, String> fieldsPermission = MapUtil.newHashMap();
         extensionElements.forEach(element -> {
-            String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
-            String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
+            String field = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE);
+            String permission = element.getAttributeValue(null, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE);
             if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) {
                 fieldsPermission.put(field, permission);
             }
@@ -321,9 +320,9 @@
         }
         Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size());
         extensionElements.forEach(element -> {
-            String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
-            String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
-            String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
+            String id = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
+            String displayName = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
+            String enable = element.getAttributeValue(null, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE);
             if (StrUtil.isNotEmpty(id)) {
                 BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting();
                 buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable)));
@@ -699,9 +698,9 @@
 
         // 情况:StartEvent/EndEvent/UserTask/ServiceTask
         if (currentElement instanceof StartEvent
-            || currentElement instanceof EndEvent
-            || currentElement instanceof UserTask
-            || currentElement instanceof ServiceTask) {
+                || currentElement instanceof EndEvent
+                || currentElement instanceof UserTask
+                || currentElement instanceof ServiceTask) {
             // 添加元素
             FlowNode flowNode = (FlowNode) currentElement;
             resultElements.add(flowNode);
@@ -720,7 +719,7 @@
                             && evalConditionExpress(variables, flow.getConditionExpression()));
             if (matchSequenceFlow == null) {
                 matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
-                        flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
+                        flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
                 // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
                 if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
                     matchSequenceFlow = gateway.getOutgoingFlows().get(0);
@@ -742,7 +741,7 @@
                             && evalConditionExpress(variables, flow.getConditionExpression()));
             if (CollUtil.isEmpty(matchSequenceFlows)) {
                 matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
-                        flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
+                        flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
                 // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
                 if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
                     matchSequenceFlows = gateway.getOutgoingFlows();
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java
index 3a73405..65b1eec 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java
@@ -22,6 +22,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.Callable;
 
 /**
  * Flowable 相关的工具方法
@@ -40,6 +41,17 @@
         Authentication.setAuthenticatedUserId(null);
     }
 
+    public static <V> V executeAuthenticatedUserId(Long userId, Callable<V> callable) {
+        setAuthenticatedUserId(userId);
+        try {
+            return callable.call();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            clearAuthenticatedUserId();
+        }
+    }
+
     public static String getTenantId() {
         Long tenantId = TenantContextHolder.getTenantId();
         return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID;
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
index 8dfac59..1024310 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -82,7 +82,7 @@
 
     private static BpmSimpleModelNodeVO buildStartNode() {
         return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID)
-                .setName(BpmSimpleModelNodeType.START_USER_NODE.getName())
+                .setName(BpmSimpleModelNodeType.START_NODE.getName())
                 .setType(BpmSimpleModelNodeType.START_NODE.getType());
     }
 
@@ -316,7 +316,7 @@
             userTask.setName(node.getName());
 
             // 人工审批
-            addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
+            addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType());
             // 候选人策略为发起人自己
             addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask);
             // 添加表单字段权限属性元素
@@ -619,7 +619,7 @@
     }
 
     private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables,
-                                  List<BpmSimpleModelNodeVO> resultNodes) {
+                                         List<BpmSimpleModelNodeVO> resultNodes) {
         // 如果不合法(包括为空),则直接结束
         if (!isValidNode(currentNode)) {
             return;
@@ -629,10 +629,10 @@
 
         // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE
         if (nodeType == BpmSimpleModelNodeType.START_NODE
-            || nodeType == BpmSimpleModelNodeType.START_USER_NODE
-            || nodeType == BpmSimpleModelNodeType.APPROVE_NODE
-            || nodeType == BpmSimpleModelNodeType.COPY_NODE
-            || nodeType == BpmSimpleModelNodeType.END_NODE) {
+                || nodeType == BpmSimpleModelNodeType.START_USER_NODE
+                || nodeType == BpmSimpleModelNodeType.APPROVE_NODE
+                || nodeType == BpmSimpleModelNodeType.COPY_NODE
+                || nodeType == BpmSimpleModelNodeType.END_NODE) {
             // 添加元素
             resultNodes.add(currentNode);
         }
@@ -642,7 +642,7 @@
             // 查找满足条件的 BpmSimpleModelNodeVO 节点
             BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
                     conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow())
-                        && evalConditionExpress(variables, conditionNode));
+                            && evalConditionExpress(variables, conditionNode));
             if (matchConditionNode == null) {
                 matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
                         conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow()));
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java
index 49bd31f..e8d4e2a 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java
@@ -2,6 +2,7 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
 import com.iailab.framework.common.util.json.JsonUtils;
 import com.iailab.framework.common.util.validation.ValidationUtils;
@@ -12,11 +13,11 @@
 import com.iailab.module.bpm.convert.definition.BpmModelConvert;
 import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
 import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum;
 import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
 import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
 import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils;
-import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
 import lombok.extern.slf4j.Slf4j;
 import org.flowable.bpmn.model.BpmnModel;
 import org.flowable.bpmn.model.StartEvent;
@@ -28,7 +29,6 @@
 import org.flowable.engine.repository.ProcessDefinition;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.util.ObjectUtils;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
@@ -66,7 +66,7 @@
     public List<Model> getModelList(String name) {
         ModelQuery modelQuery = repositoryService.createModelQuery();
         if (StrUtil.isNotEmpty(name)) {
-            modelQuery.modelNameLike(name);
+            modelQuery.modelNameLike("%" + name + "%");
         }
         return modelQuery.list();
     }
@@ -83,26 +83,54 @@
             throw exception(MODEL_KEY_EXISTS, createReqVO.getKey());
         }
 
-        // 2.1 创建流程定义
+        // 2. 创建 Model 对象
         createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序
         Model model = repositoryService.newModel();
         BpmModelConvert.INSTANCE.copyToModel(model, createReqVO);
         model.setTenantId(FlowableUtils.getTenantId());
-        // 2.2 保存流程定义
-        repositoryService.saveModel(model);
+
+        // 3. 保存模型
+        saveModel(model, createReqVO);
         return model.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
-    public void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO) {
+    public void updateModel(Long userId, BpmModelSaveReqVO updateReqVO) {
         // 1. 校验流程模型存在
         Model model = validateModelManager(updateReqVO.getId(), userId);
 
-        // 修改流程定义
+        // 2. 填充 Model 信息
         BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO);
-        // 更新模型
+
+        // 3. 保存模型
+        saveModel(model, updateReqVO);
+    }
+
+    /**
+     * 保存模型的基本信息、流程图
+     *
+     * @param model 模型
+     * @param saveReqVO 保存信息
+     */
+    private void saveModel(Model model, BpmModelSaveReqVO saveReqVO) {
+        // 1. 保存模型的基础信息
         repositoryService.saveModel(model);
+
+        // 2. 保存流程图
+        if (ObjUtil.equals(BpmModelTypeEnum.BPMN.getType(), saveReqVO.getType())
+                && StrUtil.isNotEmpty(saveReqVO.getBpmnXml())) {
+            updateModelBpmnXml(model.getId(), saveReqVO.getBpmnXml());
+        } else if (ObjUtil.equals(BpmModelTypeEnum.SIMPLE.getType(), saveReqVO.getType())
+                && saveReqVO.getSimpleModel() != null) {
+            // JSON 转换成 bpmnModel
+            BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(),
+                    saveReqVO.getSimpleModel());
+            // 保存 Bpmn XML
+            updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
+            // 保存 JSON 数据
+            updateModelSimpleJson(model.getId(), saveReqVO.getSimpleModel());
+        }
     }
 
     @Override
@@ -111,7 +139,7 @@
         // 1.1 校验流程模型存在
         List<Model> models = repositoryService.createModelQuery()
                 .modelTenantId(FlowableUtils.getTenantId()).list();
-        models.removeIf(model ->!ids.contains(model.getId()));
+        models.removeIf(model -> !ids.contains(model.getId()));
         if (ids.size() != models.size()) {
             throw exception(MODEL_NOT_EXISTS);
         }
@@ -174,7 +202,8 @@
         String simpleJson = getModelSimpleJson(model.getId());
 
         // 2.1 创建流程定义
-        String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form);
+        String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson,
+                form);
 
         // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
         updateProcessDefinitionSuspended(model.getDeploymentId());
@@ -221,7 +250,8 @@
         // 1.1 校验流程模型存在
         Model model = validateModelManager(id, userId);
         // 1.2 校验流程定义存在
-        ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
+        ProcessDefinition definition = processDefinitionService
+                .getProcessDefinitionByDeploymentId(model.getDeploymentId());
         if (definition == null) {
             throw exception(PROCESS_DEFINITION_NOT_EXISTS);
         }
@@ -277,7 +307,8 @@
             }
             return form;
         } else {
-            if (StrUtil.isEmpty(metaInfo.getFormCustomCreatePath()) || StrUtil.isEmpty(metaInfo.getFormCustomViewPath())) {
+            if (StrUtil.isEmpty(metaInfo.getFormCustomCreatePath())
+                    || StrUtil.isEmpty(metaInfo.getFormCustomViewPath())) {
                 throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
             }
             return null;
@@ -324,7 +355,8 @@
         if (oldDefinition == null) {
             return;
         }
-        processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
+        processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(),
+                SuspensionState.SUSPENDED.getStateCode());
     }
 
     private Model getModelByKey(String key) {
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
index cbe75f9..f5cb5bf 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -1 +1 @@
-package com.iailab.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 com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.date.DateUtils;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.framework.common.util.object.ObjectUtils;
import com.iailab.framework.common.util.object.PageUtils;
import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.*;
import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.iailab.module.bpm.enums.ErrorCodeConstants;
import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmSimpleModelNodeType;
import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import com.iailab.module.bpm.enums.task.BpmReasonEnum;
import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import com.iailab.module.bpm.service.message.BpmMessageService;
import com.iailab.module.system.api.dept.DeptApi;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
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 javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;

import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.framework.common.util.collection.CollectionUtils.*;
import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask;
import static com.iailab.module.bpm.enums.ErrorCodeConstants.*;
import static com.iailab.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, bpmnModel, 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, BpmnModel bpmnModel,
                                                      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 -> {
            FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
            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))
                    .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
                    .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();
            // 构建活动节点
            FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
            HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同
            ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName())
                    .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
                    .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
                    .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
            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 -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(),
                        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()).setCandidateStrategy(node.getCandidateStrategy())
                .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. 抄送节点
        if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人
                BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) {
            return activityNode;
        }
        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())
                    .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
                    .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_START_USER_ID, userId); // 设置流程变量,发起人 ID
        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/ServiceTask 列表
        BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());
        List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel);
        if (CollUtil.isEmpty(tasks)) {
            return;
        }

        // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了
        tasks.forEach(task -> {
            List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;
            if (CollUtil.isEmpty(assignees)) {
                throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.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, task.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).getCheckedData();
        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));
        });
    }

}
\ No newline at end of file
+package com.iailab.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 com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.date.DateUtils;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.framework.common.util.object.ObjectUtils;
import com.iailab.framework.common.util.object.PageUtils;
import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.iailab.module.bpm.controller.admin.task.vo.instance.*;
import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert;
import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.iailab.module.bpm.enums.ErrorCodeConstants;
import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum;
import com.iailab.module.bpm.enums.definition.BpmSimpleModelNodeType;
import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import com.iailab.module.bpm.enums.task.BpmReasonEnum;
import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum;
import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
import com.iailab.module.bpm.service.message.BpmMessageService;
import com.iailab.module.system.api.dept.DeptApi;
import com.iailab.module.system.api.dept.dto.DeptRespDTO;
import com.iailab.module.system.api.user.AdminUserApi;
import com.iailab.module.system.api.user.dto.AdminUserRespDTO;
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 javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;

import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.iailab.framework.common.util.collection.CollectionUtils.*;
import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask;
import static com.iailab.module.bpm.enums.ErrorCodeConstants.*;
import static com.iailab.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, bpmnModel, 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. 表单权限
        String taskId = reqVO.getTaskId() == null && todoTask != null ? todoTask.getId() : reqVO.getTaskId();
        Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), taskId);

        // 3. 拼接数据
        return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance,
                processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap);
    }

    /**
     * 获得【已结束】的活动节点们
     */
    private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,
                                                      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 -> {
            FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
            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))
                    .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
                    .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();
            // 构建活动节点
            FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
            HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同
            ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName())
                    .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
                    .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
                    .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
            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 -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(),
                        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()).setCandidateStrategy(node.getCandidateStrategy())
                .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. 抄送节点
        if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人
                BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) {
            return activityNode;
        }
        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())
                    .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
                    .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 移除掉
        finishedTaskActivityIds.removeAll(unfinishedTaskActivityIds);
        // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。
        // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点
        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) {
        return FlowableUtils.executeAuthenticatedUserId(userId, () -> {
            // 获得流程定义
            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_START_USER_ID, userId); // 设置流程变量,发起人 ID
        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/ServiceTask 列表
        BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());
        List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel);
        if (CollUtil.isEmpty(tasks)) {
            return;
        }

        // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了
        tasks.forEach(task -> {
            List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;
            if (CollUtil.isEmpty(assignees)) {
                throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.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, task.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).getCheckedData();
        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));
        });
    }

}
\ No newline at end of file
diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java
index b63071e..d491b59 100644
--- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java
+++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -13,6 +13,7 @@
 import com.iailab.framework.web.core.util.WebFrameworkUtils;
 import com.iailab.module.bpm.controller.admin.task.vo.task.*;
 import com.iailab.module.bpm.convert.task.BpmTaskConvert;
+import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO;
 import com.iailab.module.bpm.enums.definition.*;
 import com.iailab.module.bpm.enums.task.BpmCommentTypeEnum;
 import com.iailab.module.bpm.enums.task.BpmReasonEnum;
@@ -21,6 +22,7 @@
 import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
 import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils;
+import com.iailab.module.bpm.service.definition.BpmFormService;
 import com.iailab.module.bpm.service.definition.BpmModelService;
 import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService;
 import com.iailab.module.bpm.service.message.BpmMessageService;
@@ -92,6 +94,8 @@
     private BpmModelService modelService;
     @Resource
     private BpmMessageService messageService;
+    @Resource
+    private BpmFormService formService;
 
     @Resource
     private AdminUserApi adminUserApi;
@@ -109,6 +113,9 @@
                 .orderByTaskCreateTime().desc(); // 创建时间倒序
         if (StrUtil.isNotBlank(pageVO.getName())) {
             taskQuery.taskNameLike("%" + pageVO.getName() + "%");
+        }
+        if (StrUtil.isNotEmpty(pageVO.getCategory())) {
+            taskQuery.taskCategory(pageVO.getCategory());
         }
         if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
             taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
@@ -154,7 +161,13 @@
         BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId());
         Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting(
                 bpmnModel, todoTask.getTaskDefinitionKey());
-        return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting);
+
+        // 4. 任务表单
+        BpmFormDO taskForm = null;
+        if (StrUtil.isNotBlank(todoTask.getFormKey())){
+            taskForm = formService.getForm(NumberUtils.parseLong(todoTask.getFormKey()));
+        }
+        return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting, taskForm);
     }
 
     @Override
@@ -188,6 +201,9 @@
                 .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
         if (StrUtil.isNotBlank(pageVO.getName())) {
             taskQuery.taskNameLike("%" + pageVO.getName() + "%");
+        }
+        if (StrUtil.isNotEmpty(pageVO.getCategory())) {
+            taskQuery.taskCategory(pageVO.getCategory());
         }
         if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
             taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
@@ -442,7 +458,7 @@
      * 判断指定用户,是否是当前任务的加签人
      *
      * @param userId 用户 Id
-     * @param task 任务
+     * @param task   任务
      * @return 是否
      */
     private boolean isAddSignUserTask(Long userId, Task task) {
@@ -670,7 +686,7 @@
                 reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
 
         // 2. 调用 Flowable 框架的退回逻辑
-        returnTask(task, targetElement, reqVO);
+        returnTask(userId, task, targetElement, reqVO);
     }
 
     /**
@@ -702,11 +718,12 @@
     /**
      * 执行退回逻辑
      *
+     * @param userId        用户编号
      * @param currentTask   当前退回的任务
      * @param targetElement 需要退回到的目标任务
      * @param reqVO         前端参数封装
      */
-    public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
+    public void returnTask(Long userId, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
         // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤
         // 1.1 获取所有正常进行的任务节点 Key
         List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
@@ -722,22 +739,29 @@
             if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
                 return;
             }
-            // 2.1 添加评论
-            taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), BpmCommentTypeEnum.RETURN.getType(),
-                    BpmCommentTypeEnum.RETURN.formatComment(reqVO.getReason()));
-            // 2.2 更新 task 状态 + 原因
-            updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
+
+            // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务
+            if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记
+                // 2.1.1 添加评论
+                taskService.addComment(task.getId(), currentTask.getProcessInstanceId(), BpmCommentTypeEnum.RETURN.getType(),
+                        BpmCommentTypeEnum.RETURN.formatComment(reqVO.getReason()));
+                // 2.1.2 更新 task 状态 + 原因
+                updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason());
+            } else { // 情况二:别人的任务,进行 CANCEL 标记
+                processTaskCanceled(task.getId());
+            }
         });
 
         // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过
         runtimeService.setVariable(currentTask.getProcessInstanceId(),
                 String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE);
-
         // 4. 执行驳回
+        // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因:
+        // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944
+        List<String> runExecutionIds = convertList(taskList, Task::getExecutionId);
         runtimeService.createChangeActivityStateBuilder()
                 .processInstanceId(currentTask.getProcessInstanceId())
-                .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多)
-                        reqVO.getTargetTaskDefinitionKey()) // targetKey 跳转到的节点(1)
+                .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey())
                 .changeState();
     }
 
@@ -1022,14 +1046,22 @@
         Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement);
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
 
+            /**
+             * 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以
+             * 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时
+             * 参见 <a href="https://gitee.com/zhijiantianya/yudao-cloud/issues/IB7V7Q">issue</a> 反馈
+             */
             @Override
             public void afterCompletion(int transactionStatus) {
-                // 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以
-                // 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时
-                if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) {
+                // 回滚情况,直接返回
+                if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) {
                     return;
                 }
-                // TODO 芋艿:可以后续优化成 getSelf();
+                // 特殊情况:第一个 task 【自动通过】时,第二个任务设置审批人时 transactionStatus 会为 STATUS_UNKNOWN,不知道啥原因
+                if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_UNKNOWN)
+                        && getTask(task.getId()) == null) {
+                    return;
+                }
                 // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝
                 if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
                     // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝
@@ -1037,19 +1069,19 @@
                         return;
                     }
                     if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
-                        SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
+                        getSelf().approveTask(null, new BpmTaskApproveReqVO()
                                 .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
                     } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) {
-                        SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
+                        getSelf().rejectTask(null, new BpmTaskRejectReqVO()
                                 .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason()));
                     }
                     // 特殊情况二:【自动审核】审批类型为自动通过、不通过
                 } else {
                     if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) {
-                        SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
+                        getSelf().approveTask(null, new BpmTaskApproveReqVO()
                                 .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason()));
                     } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
-                        SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO()
+                        getSelf().rejectTask(null, new BpmTaskRejectReqVO()
                                 .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason()));
                     }
                 }
@@ -1088,8 +1120,22 @@
         // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
 
+            /**
+             * 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以
+             * 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时
+             * 参见 <a href="https://gitee.com/zhijiantianya/yudao-cloud/issues/IB7V7Q">issue</a> 反馈
+             */
             @Override
-            public void afterCommit() {
+            public void afterCompletion(int transactionStatus) {
+                // 回滚情况,直接返回
+                if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_ROLLED_BACK)) {
+                    return;
+                }
+                // 特殊情况:第一个 task 【自动通过】时,第二个任务设置审批人时 transactionStatus 会为 STATUS_UNKNOWN,不知道啥原因
+                if (ObjectUtil.equal(transactionStatus, TransactionSynchronization.STATUS_UNKNOWN)
+                        && getTask(task.getId()) == null) {
+                    return;
+                }
                 if (StrUtil.isEmpty(task.getAssignee())) {
                     log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId());
                     return;
@@ -1146,6 +1192,7 @@
                         }
                     }
                 }
+
                 AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData();
                 messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
             }
diff --git a/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java
index aa3103d..5a968ac 100644
--- a/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java
+++ b/iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java
@@ -110,6 +110,7 @@
     ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1_002_016_000, "租户套餐不存在");
     ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1_002_016_001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除");
     ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1_002_016_002, "名字为【{}】的租户套餐已被禁用");
+    ErrorCode TENANT_PACKAGE_NAME_DUPLICATE = new ErrorCode(1_002_016_003, "已经存在该名字的租户套餐");
 
     // ========== 社交用户 1-002-018-000 ==========
     ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1_002_018_000, "社交授权失败,原因是:{}");
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/tenant/TenantPackageMapper.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/tenant/TenantPackageMapper.java
index 031c4e3..84dd53b 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/tenant/TenantPackageMapper.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/dal/mysql/tenant/TenantPackageMapper.java
@@ -29,4 +29,9 @@
     default List<TenantPackageDO> selectListByStatus(Integer status) {
         return selectList(TenantPackageDO::getStatus, status);
     }
+
+
+    default TenantPackageDO selectByName(String name) {
+        return selectOne(TenantPackageDO::getName, name);
+    }
 }
diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantPackageServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantPackageServiceImpl.java
index 06371fa..e5898b6 100644
--- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantPackageServiceImpl.java
+++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/tenant/TenantPackageServiceImpl.java
@@ -1,6 +1,8 @@
 package com.iailab.module.system.service.tenant;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.google.common.annotations.VisibleForTesting;
 import com.iailab.framework.common.enums.CommonStatusEnum;
 import com.iailab.framework.common.pojo.PageResult;
 import com.iailab.framework.common.util.object.BeanUtils;
@@ -38,6 +40,8 @@
 
     @Override
     public Long createTenantPackage(TenantPackageSaveReqVO createReqVO) {
+        // 校验套餐名是否重复
+        validateTenantPackageNameUnique(null, createReqVO.getName());
         // 插入
         TenantPackageDO tenantPackage = BeanUtils.toBean(createReqVO, TenantPackageDO.class);
         tenantPackageMapper.insert(tenantPackage);
@@ -111,4 +115,22 @@
         return tenantPackageMapper.selectListByStatus(status);
     }
 
+    @VisibleForTesting
+    void validateTenantPackageNameUnique(Long id, String name) {
+        if (StrUtil.isBlank(name)) {
+            return;
+        }
+        TenantPackageDO tenantPackage = tenantPackageMapper.selectByName(name);
+        if (tenantPackage == null) {
+            return;
+        }
+        // 如果 id 为空,说明不用比较是否为相同 id 的用户
+        if (id == null) {
+            throw exception(TENANT_PACKAGE_NAME_DUPLICATE);
+        }
+        if (!tenantPackage.getId().equals(id)) {
+            throw exception(TENANT_PACKAGE_NAME_DUPLICATE);
+        }
+    }
+
 }

--
Gitblit v1.9.3