Ver código fonte

Merge branch 'feature/bpm' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17

YunaiV 4 meses atrás
pai
commit
a53c7dafd3
20 arquivos alterados com 238 adições e 89 exclusões
  1. 1 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
  2. 1 0
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java
  3. 15 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java
  4. 11 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
  5. 9 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
  6. 4 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
  7. 2 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
  8. 0 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
  9. 3 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
  10. 11 6
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
  11. 20 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
  12. 6 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
  13. 9 6
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
  14. 20 5
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
  15. 53 37
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
  16. 7 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
  17. 63 17
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
  18. 1 4
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java
  19. 1 4
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java
  20. 1 4
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java

+ 1 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java

@@ -42,6 +42,7 @@ public interface ErrorCodeConstants {
     ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
     ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败");
     ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
+    ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消");
 
     // ========== 流程任务 1-009-005-000 ==========
     ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");

+ 1 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java

@@ -18,6 +18,7 @@ public enum BpmReasonEnum {
     REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法
     CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程
     CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程
+    CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS("子流程自动取消,原因:主流程已取消"),
 
     // ========== 流程任务的独有原因 ==========
 

+ 15 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.base.dept;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "部门精简信息 VO")
+@Data
+public class DeptSimpleBaseVO {
+
+    @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+    @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术部")
+    private String name;
+
+}

+ 11 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java

@@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
@@ -53,6 +55,8 @@ public class BpmModelController {
 
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
 
     @GetMapping("/list")
     @Operation(summary = "获得模型分页")
@@ -79,14 +83,19 @@ public class BpmModelController {
         List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
                 deploymentMap.keySet());
         Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
-        // 获得 User Map
+        // 获得 User Map、Dept Map
         Set<Long> userIds = convertSetByFlatMap(list, model -> {
             BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
             return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
         });
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+        Set<Long> deptIds = convertSetByFlatMap(list, model -> {
+            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
+            return metaInfo != null && metaInfo.getStartDeptIds() != null ? metaInfo.getStartDeptIds().stream() : Stream.empty();
+        });
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
         return success(BpmModelConvert.INSTANCE.buildModelList(list,
-                formMap, categoryMap, deploymentMap, processDefinitionMap, userMap));
+                formMap, categoryMap, deploymentMap, processDefinitionMap, userMap, deptMap));
     }
 
     @GetMapping("/get")

+ 9 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java

@@ -59,6 +59,9 @@ public class BpmModelMetaInfoVO {
     @Schema(description = "可发起用户编号数组", example = "[1,2,3]")
     private List<Long> startUserIds;
 
+    @Schema(description = "可发起部门编号数组", example = "[2,4,6]")
+    private List<Long> startDeptIds;
+
     @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]")
     @NotEmpty(message = "可管理用户编号数组不能为空")
     private List<Long> managerUserIds;
@@ -88,6 +91,12 @@ public class BpmModelMetaInfoVO {
     @Schema(description = "流程后置通知设置", example = "{}")
     private HttpRequestSetting processAfterTriggerSetting;
 
+    @Schema(description = "任务前置通知设置", example = "{}")
+    private HttpRequestSetting taskBeforeTriggerSetting;
+
+    @Schema(description = "任务后置通知设置", example = "{}")
+    private HttpRequestSetting taskAfterTriggerSetting;
+
     @Schema(description = "流程 ID 规则")
     @Data
     @Valid

+ 4 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
 
+import cn.iocoder.yudao.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
@@ -39,6 +40,9 @@ public class BpmModelRespVO extends BpmModelMetaInfoVO {
     @Schema(description = "可发起的用户数组")
     private List<UserSimpleBaseVO> startUsers;
 
+    @Schema(description = "可发起的部门数组")
+    private List<DeptSimpleBaseVO> startDepts;
+
     @Schema(description = "BPMN XML")
     private String bpmnXml;
 

+ 2 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java

@@ -112,7 +112,7 @@ public class BpmSimpleModelNodeVO {
     /**
      * 条件节点设置
      */
-    private ConditionSetting conditionSetting; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
+    private ConditionSetting conditionSetting; // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE
 
     @Schema(description = "路由分支组", example = "[]")
     private List<RouterSetting> routerGroups;
@@ -241,7 +241,7 @@ public class BpmSimpleModelNodeVO {
     @Schema(description = "条件设置")
     @Data
     @Valid
-    // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE
+    // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE
     public static class ConditionSetting {
 
         @Schema(description = "条件类型", example = "1")

+ 0 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java

@@ -148,7 +148,6 @@ public class BpmProcessInstanceController {
                 processDefinition, processDefinitionInfo, startUser, dept));
     }
 
-    // TODO @lesan:【子流程】子流程如果取消,主流程应该是通过、还是不通过哈?还是禁用掉子流程的取消?
     @DeleteMapping("/cancel-by-start-user")
     @Operation(summary = "用户取消流程实例", description = "取消发起的流程")
     @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")

+ 3 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java

@@ -72,6 +72,9 @@ public class BpmApprovalDetailRespVO {
         @Schema(description = "候选人用户列表")
         private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
 
+        @Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf")
+        private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 processInstanceId 字段)
+
     }
 
     @Schema(description = "活动节点的任务信息")

+ 11 - 6
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java

@@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO;
@@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmPro
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import org.flowable.common.engine.impl.db.SuspensionState;
 import org.flowable.engine.repository.Deployment;
@@ -43,7 +45,8 @@ public interface BpmModelConvert {
                                                 Map<String, BpmCategoryDO> categoryMap,
                                                 Map<String, Deployment> deploymentMap,
                                                 Map<String, ProcessDefinition> processDefinitionMap,
-                                                Map<Long, AdminUserRespDTO> userMap) {
+                                                Map<Long, AdminUserRespDTO> userMap,
+                                                Map<Long, DeptRespDTO> deptMap) {
         List<BpmModelRespVO> result = convertList(list, model -> {
             BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
             BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
@@ -52,7 +55,8 @@ public interface BpmModelConvert {
             ProcessDefinition processDefinition = model.getDeploymentId() != null ?
                     processDefinitionMap.get(model.getDeploymentId()) : null;
             List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null;
-            return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers);
+            List<DeptRespDTO> startDepts = metaInfo != null ? convertList(metaInfo.getStartDeptIds(), deptMap::get) : null;
+            return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers, startDepts);
         });
         // 排序
         result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort));
@@ -61,7 +65,7 @@ public interface BpmModelConvert {
 
     default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes, BpmSimpleModelNodeVO simpleModel) {
         BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
-        BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null);
+        BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null, null);
         if (ArrayUtil.isNotEmpty(bpmnBytes)) {
             modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes));
         }
@@ -72,7 +76,7 @@ public interface BpmModelConvert {
     default BpmModelRespVO buildModel0(Model model,
                                        BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category,
                                        Deployment deployment, ProcessDefinition processDefinition,
-                                       List<AdminUserRespDTO> startUsers) {
+                                       List<AdminUserRespDTO> startUsers, List<DeptRespDTO> startDepts) {
         BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName())
                 .setKey(model.getKey()).setCategory(model.getCategory())
                 .setCreateTime(DateUtils.of(model.getCreateTime()));
@@ -94,8 +98,9 @@ public interface BpmModelConvert {
                 modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime()));
             }
         }
-        // User
-        modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class));
+        // User、Dept
+        modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class))
+                .setStartDepts(BeanUtils.toBean(startDepts, DeptSimpleBaseVO.class));
         return modelRespVO;
     }
 

+ 20 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java

@@ -151,6 +151,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
     @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
     private List<Long> startUserIds;
 
+    /**
+     * 可发起部门编号数组
+     *
+     * 关联 {@link AdminUserRespDTO#getDeptId()} 字段的数组
+     */
+    @TableField(typeHandler = LongListTypeHandler.class)
+    private List<Long> startDeptIds;
+
     /**
      * 可管理用户编号数组
      *
@@ -199,4 +207,16 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
     @TableField(typeHandler = JacksonTypeHandler.class)
     private BpmModelMetaInfoVO.HttpRequestSetting processAfterTriggerSetting;
 
+    /**
+     * 任务前置通知设置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.HttpRequestSetting taskBeforeTriggerSetting;
+
+    /**
+     * 任务后置通知设置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting;
+
 }

+ 6 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java

@@ -47,7 +47,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
     public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
             .add(FlowableEngineEventType.TASK_CREATED)
             .add(FlowableEngineEventType.TASK_ASSIGNED)
-//            .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了
+            .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,这里仅处理任务后置通知
             .add(FlowableEngineEventType.ACTIVITY_CANCELLED)
             .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时
             .build();
@@ -66,6 +66,11 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
         taskService.processTaskAssigned((Task) event.getEntity());
     }
 
+    @Override
+    protected void taskCompleted(FlowableEngineEntityEvent event) {
+        taskService.processTaskCompleted((Task) event.getEntity());
+    }
+
     @Override
     protected void activityCancelled(FlowableActivityCancelledEvent event) {
         List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId());

+ 9 - 6
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java

@@ -187,7 +187,7 @@ public class SimpleModelUtils {
     /**
      * 构建有附加节点的连线
      *
-     * @param nodeId 当前节点 ID
+     * @param nodeId       当前节点 ID
      * @param attachNodeId 附属节点 ID
      * @param targetNodeId 目标节点 ID
      */
@@ -662,6 +662,10 @@ public class SimpleModelUtils {
      * 构造条件表达式
      */
     public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
+        // 并行网关不需要设置条件
+        if (conditionSetting == null) {
+            return null;
+        }
         return buildConditionExpression(conditionSetting.getConditionType(), conditionSetting.getConditionExpression(),
                 conditionSetting.getConditionGroups());
     }
@@ -813,7 +817,6 @@ public class SimpleModelUtils {
             callActivity.setCalledElementType("key");
             // 1. 是否异步
             if (node.getChildProcessSetting().getAsync()) {
-                // TODO @lesan: 这里目前测试没有跳过执行call activity 后面的节点
                 callActivity.setAsynchronous(true);
             }
 
@@ -959,8 +962,8 @@ public class SimpleModelUtils {
         if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) {
             // 查找满足条件的 BpmSimpleModelNodeVO 节点
             BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
-                        conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
-                                && evalConditionExpress(variables, conditionNode.getConditionSetting()));
+                    conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
+                            && evalConditionExpress(variables, conditionNode.getConditionSetting()));
             if (matchConditionNode == null) {
                 matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(),
                         conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));
@@ -974,8 +977,8 @@ public class SimpleModelUtils {
         if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) {
             // 查找满足条件的 BpmSimpleModelNodeVO 节点
             Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
-                        conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
-                                && evalConditionExpress(variables, conditionNode.getConditionSetting()));
+                    conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())
+                            && evalConditionExpress(variables, conditionNode.getConditionSetting()));
             if (CollUtil.isEmpty(matchConditionNodes)) {
                 matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(),
                         conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));

+ 20 - 5
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java

@@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitio
 import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.flowable.bpmn.model.BpmnModel;
@@ -50,6 +52,9 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
     @Resource
     private BpmProcessDefinitionInfoMapper processDefinitionMapper;
 
+    @Resource
+    private AdminUserApi adminUserApi;
+
     @Override
     public ProcessDefinition getProcessDefinition(String id) {
         return repositoryService.getProcessDefinition(id);
@@ -88,12 +93,22 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
         if (processDefinition == null) {
             return false;
         }
-        // 为空,则所有人都可以发起
-        if (CollUtil.isEmpty(processDefinition.getStartUserIds())) {
-            return true;
+
+        // 校验用户是否在允许发起的用户列表中
+        if (CollUtil.isNotEmpty(processDefinition.getStartUserIds())) {
+            return processDefinition.getStartUserIds().contains(userId);
+        }
+
+        // 校验用户是否在允许发起的部门列表中
+        if (CollUtil.isNotEmpty(processDefinition.getStartDeptIds())) {
+            AdminUserRespDTO user = adminUserApi.getUser(userId);
+            return user != null
+                    && user.getDeptId() != null
+                    && processDefinition.getStartDeptIds().contains(user.getDeptId());
         }
-        // 不为空,则需要存在里面
-        return processDefinition.getStartUserIds().contains(userId);
+
+        // 都为空,则所有人都可以发起
+        return true;
     }
 
     @Override

+ 53 - 37
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java

@@ -14,7 +14,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
-import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
@@ -63,7 +62,6 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.client.RestTemplate;
 
 import java.util.*;
 
@@ -263,30 +261,31 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
             processVariables.putAll(reqVO.getProcessVariables());
         }
 
-        // 3 获取当前任务节点的信息
-        // 3.1 获取下一个将要执行的节点集合
+        // 3. 获取下一个将要执行的节点集合
         FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
         List<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables);
-        return convertList(nextFlowNodes, node -> {
-            List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
-                    loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables);
-            // 3.2 获取节点的审批人信息
-            Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(candidateUserIds);
-            // 3.3 获取节点的审批人部门信息
-            Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
-            // 3.4 存在一个节点多人审批的情况,组装审批人信息
-            List<UserSimpleBaseVO> candidateUsers = new ArrayList<>();
-            userMap.forEach((key, value) -> candidateUsers.add(BpmProcessInstanceConvert.INSTANCE.buildUser(key, userMap, deptMap)));
-            return new ActivityNode().setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
-                    .setId(node.getId())
-                    .setName(node.getName())
-                    .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
-                    .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
-                    // TODO @小北:先把 candidateUserIds 设置完,然后最后拼接 candidateUsers 信息。这样,如果有多个节点,就不用重复查询啦;类似 buildApprovalDetail 思路;
-                    // TODO 先拼接处 List ActivityNode
-                    // TODO 接着,再起一段,处理 adminUserApi.getUserMap(candidateUserIds)、deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId))
-                    .setCandidateUsers(candidateUsers);
-        });
+        List<ActivityNode> nextActivityNodes = convertList(nextFlowNodes, node -> new ActivityNode().setId(node.getId())
+                .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
+                .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
+                .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
+                .setCandidateUserIds(getTaskCandidateUserList(bpmnModel, node.getId(),
+                        loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables)));
+        if (CollUtil.isNotEmpty(nextActivityNodes)) {
+            return nextActivityNodes;
+        }
+
+        // 4. 拼接基础信息
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSetByFlatMap(nextActivityNodes, ActivityNode::getCandidateUserIds, Collection::stream));
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+        nextActivityNodes.forEach(node -> node.setCandidateUsers(convertList(node.getCandidateUserIds(), userId -> {
+            AdminUserRespDTO user = userMap.get(userId);
+            if (user != null) {
+                return BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap);
+            }
+            return null;
+        })));
+        return nextActivityNodes;
     }
 
     @Override
@@ -388,8 +387,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
                                                       List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {
         // 遍历 tasks 列表,只处理已结束的 UserTask
         // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点
-        // TODO @芋艿:子流程只有activity,这里获取不到已结束的子流程;
-        // TODO @lesan:【子流程】基于 activities 查询出 usertask、callactivity,然后拼接?如果是子流程,就是可以点击过去?
         List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);
         List<ActivityNode> approvalNodes = convertList(endTasks, task -> {
             FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
@@ -411,7 +408,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
 
         // 遍历 activities,只处理已结束的 StartEvent、EndEvent
         List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null
-                && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END)));
+                && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_CALL_ACTIVITY, ELEMENT_EVENT_END)));
         endActivities.forEach(activity -> {
             // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点
             if (ELEMENT_EVENT_START.equals(activity.getActivityType())
@@ -445,7 +442,20 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
                 }
                 approvalNodes.add(endNode);
             }
+            // CallActivity
+            if (ELEMENT_CALL_ACTIVITY.equals(activity.getActivityType())) {
+                ActivityNode callActivity = new ActivityNode().setId(activity.getId())
+                        .setName(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getName())
+                        .setNodeType(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType()).setStatus(processInstanceStatus)
+                        .setStartTime(DateUtils.of(activity.getStartTime()))
+                        .setEndTime(DateUtils.of(activity.getEndTime()))
+                        .setProcessInstanceId(activity.getProcessInstanceId());
+                approvalNodes.add(callActivity);
+            }
         });
+
+        // 按照时间排序
+        approvalNodes.sort(Comparator.comparing(ActivityNode::getStartTime));
         return approvalNodes;
     }
 
@@ -465,7 +475,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
                 HistoricActivityInstance::getActivityId);
 
         // 按照 activityId 分组,构建 ApprovalNodeInfo 节点
-        // TODO @lesan:【子流程】在子流程进行审批的时候,HistoricActivityInstance 里面可以拿到 runActivities.get(0).getCalledProcessInstanceId()。要不要支持跳转???
         Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId);
         return convertList(runningTaskMap.entrySet(), entry -> {
             String activityId = entry.getKey();
@@ -511,6 +520,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
                                 approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner
                 activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size()));
             }
+            if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(activityNode.getNodeType())) {
+                activityNode.setProcessInstanceId(firstActivity.getProcessInstanceId());
+            }
             return activityNode;
         });
     }
@@ -824,6 +836,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
                 && Boolean.FALSE.equals(processDefinitionInfo.getAllowCancelRunningProcess())) {
             throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW);
         }
+        // 1.4 子流程不允许取消
+        if (StrUtil.isNotBlank(instance.getSuperExecutionId())) {
+            throw exception(PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW);
+        }
 
         // 2. 取消流程
         updateProcessInstanceCancel(cancelReqVO.getId(),
@@ -850,7 +866,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
                 BpmProcessInstanceStatusEnum.CANCEL.getStatus());
         runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason);
 
-        // 2. 结束流程
+        // 2. 取消所有子流程
+        List<ProcessInstance> childProcessInstances = runtimeService.createProcessInstanceQuery()
+                .superProcessInstanceId(id).list();
+        childProcessInstances.forEach(processInstance -> updateProcessInstanceCancel(
+                processInstance.getProcessInstanceId(), BpmReasonEnum.CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS.getReason()));
+
+        // 3. 结束流程
         taskService.moveTaskToEnd(id, reason);
     }
 
@@ -915,10 +937,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
                     BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessAfterTriggerSetting();
 
                     BpmHttpRequestUtils.executeBpmHttpRequest(instance,
-                            setting.getUrl(),
-                            setting.getHeader(),
-                            setting.getBody(),
-                            true, setting.getResponse());
+                            setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
                 }
             }
         });
@@ -937,10 +956,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
             }
             BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessBeforeTriggerSetting();
             BpmHttpRequestUtils.executeBpmHttpRequest(instance,
-                    setting.getUrl(),
-                    setting.getHeader(),
-                    setting.getBody(),
-                    true, setting.getResponse());
+                    setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
         });
     }
 

+ 7 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java

@@ -278,6 +278,13 @@ public interface BpmTaskService {
      */
     void processTaskAssigned(Task task);
 
+    /**
+     * 处理 Task 完成事件,目前是发送任务后置通知
+     *
+     * @param task 任务实体
+     */
+    void processTaskCompleted(Task task);
+
     /**
      * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务
      *

+ 63 - 17
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -6,12 +6,14 @@ import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.*;
 import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.framework.common.util.object.PageUtils;
 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
@@ -23,6 +25,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
 import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
@@ -597,47 +600,54 @@ public class BpmTaskServiceImpl implements BpmTaskService {
      */
     private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
                                                             Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
+        // simple 设计器第一个节点默认为发起人节点,不校验是否存在审批人
+        if (Objects.equals(taskDefinitionKey, START_USER_NODE_ID)) {
+            return variables;
+        }
         // 1. 获取下一个将要执行的节点集合
         FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
         List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
 
         // 2. 校验选择的下一个节点的审批人,是否合法
-        Map<String, List<Long>> processVariables;
         for (FlowNode nextFlowNode : nextFlowNodes) {
             Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
             // 2.1 情况一:如果节点中的审批人策略为 发起人自选
             if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
+                // 特殊:如果当前节点已经存在审批人,则不允许覆盖
+                Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
+                if (startUserSelectAssignees != null && CollUtil.isNotEmpty(startUserSelectAssignees.get(nextFlowNode.getId()))) {
+                    continue;
+                }
                 // 如果节点存在,但未配置审批人
                 List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
                 if (CollUtil.isEmpty(assignees)) {
                     throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
                 }
-                processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
-                // 特殊:如果当前节点已经存在审批人,则不允许覆盖
-                if (processVariables != null && CollUtil.isNotEmpty(processVariables.get(nextFlowNode.getId()))) {
-                    continue;
-                }
+
                 // 设置 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES
-                if (processVariables == null) {
-                    processVariables = new HashMap<>();
+                if (startUserSelectAssignees == null) {
+                    startUserSelectAssignees = new HashMap<>();
                 }
-                processVariables.put(nextFlowNode.getId(), assignees);
-                variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables);
+                startUserSelectAssignees.put(nextFlowNode.getId(), assignees);
+                variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);
+                continue;
             }
+
             // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空
             if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
                 // 如果节点存在,但未配置审批人
+                Map<String, List<Long>> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
                 List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
                 if (CollUtil.isEmpty(assignees)) {
                     throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
                 }
-                processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
-                if (processVariables == null) {
-                    processVariables = new HashMap<>();
-                }
+
                 // 设置 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES
-                processVariables.put(nextFlowNode.getId(), assignees);
-                variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables);
+                if (approveUserSelectAssignees == null) {
+                    approveUserSelectAssignees = new HashMap<>();
+                }
+                approveUserSelectAssignees.put(nextFlowNode.getId(), assignees);
+                variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, approveUserSelectAssignees);
             }
         }
         return variables;
@@ -1174,12 +1184,26 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         }
         updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus());
 
-        // 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过
         ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
         if (processInstance == null) {
             log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId());
             return;
         }
+        BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.
+                getProcessDefinitionInfo(processInstance.getProcessDefinitionId());
+        if (processDefinitionInfo == null) {
+            log.error("[processTaskCreated][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId());
+            return;
+        }
+
+        // 2. 任务前置通知
+        if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){
+            BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting();
+            BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
+                    setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
+        }
+
+        // 3. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过
         BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
         FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
         Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement);
@@ -1391,6 +1415,28 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         });
     }
 
+    @Override
+    public void processTaskCompleted(Task task) {
+        ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
+        if (processInstance == null) {
+            log.error("[processTaskCompleted][taskId({}) 没有找到流程实例]", task.getId());
+            return;
+        }
+        BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.
+                getProcessDefinitionInfo(processInstance.getProcessDefinitionId());
+        if (processDefinitionInfo == null) {
+            log.error("[processTaskCompleted][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId());
+            return;
+        }
+
+        // 任务后置通知
+        if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){
+            BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting();
+            BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
+                    setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType) {

+ 1 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java

@@ -53,10 +53,7 @@ public class BpmUserTaskListener implements TaskListener {
         listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskId")
                 .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getId()));
         BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
-                listenerHandler.getPath(),
-                listenerHandler.getHeader(),
-                listenerHandler.getBody(),
-                false, null);
+                listenerHandler.getPath(), listenerHandler.getHeader(), listenerHandler.getBody(), false, null);
 
         // 3. 是否需要后续操作?TODO 芋艿:待定!
     }

+ 1 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java

@@ -45,10 +45,7 @@ public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger {
                 .setKey("taskDefineKey") // 重要:回调请求 taskDefineKey 需要传给被调用方,用于回调执行
                 .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(setting.getCallbackTaskDefineKey()));
         BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
-                setting.getUrl(),
-                setting.getHeader(),
-                setting.getBody(),
-                false, null);
+                setting.getUrl(), setting.getHeader(), setting.getBody(), false, null);
     }
 
 }

+ 1 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java

@@ -40,10 +40,7 @@ public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger {
         // 2. 发起请求
         ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
         BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
-                setting.getUrl(),
-                setting.getHeader(),
-                setting.getBody(),
-                true, setting.getResponse());
+                setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
     }
 
 }