瀏覽代碼

仿钉钉流程设计- 简化审批拒绝流程, code review 修改

jason 1 年之前
父節點
當前提交
633a7c50ae
共有 18 個文件被更改,包括 60 次插入258 次删除
  1. 0 3
      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/definition/BpmFieldPermissionEnum.java
  3. 1 5
      yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java
  4. 1 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
  5. 1 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
  6. 1 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java
  7. 3 4
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java
  8. 0 40
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java
  9. 0 74
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java
  10. 0 10
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
  11. 0 44
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
  12. 13 5
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
  13. 9 29
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
  14. 1 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
  15. 7 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java
  16. 14 8
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java
  17. 0 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
  18. 8 28
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

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

@@ -51,9 +51,6 @@ public interface ErrorCodeConstants {
     ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
     ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
     ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
-    ErrorCode TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID = new ErrorCode(1_009_005_015, "回退任务未指定目标任务编号");
-    ErrorCode TASK_REJECT_HANDLER_TYPE_BY_REJECT_RATIO_ERROR = new ErrorCode(1_009_005_016, "按拒绝人数比例终止流程只能用于会签任务");
-
     ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
 
     // ========== 动态表单模块 1-009-010-000 ==========

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

@@ -14,6 +14,7 @@ import lombok.Getter;
 public enum BpmFieldPermissionEnum {
 
     // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3)
+    // @芋艿 我看钉钉页面的顺序 是 可编辑 只读 隐藏
     WRITE(1, "可编辑"),
     READ(2, "只读"),
     NONE(3, "隐藏");

+ 1 - 5
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java

@@ -13,12 +13,8 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum BpmUserTaskRejectHandlerType {
 
-    // TODO @jason:是不是收敛成 2 个:FINISH_PROCESS => 1. 直接结束流程;RETURN_PRE_USER_TASK => 2. 驳回到指定节点(RETURN_USER_TASK【去掉 PRE】)
     FINISH_PROCESS(1, "终止流程"),
-    RETURN_PRE_USER_TASK(2, "驳回到指定任务节点"),
-
-    FINISH_PROCESS_BY_REJECT_NUMBER(3, "按拒绝人数终止流程"), // 用于会签
-    FINISH_TASK(4, "结束任务"); // 待实现,可能会用于意见分支
+    RETURN_USER_TASK(2, "驳回到指定任务节点");
 
     private final Integer type;
     private final String name;

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

@@ -149,11 +149,10 @@ public class BpmModelController {
 
     // ========== 仿钉钉/飞书的精简模型 =========
 
-    // TODO @jason:modelId => id 哈。一般属于自己的模块,可以简化命名。
     @GetMapping("/simple/get")
     @Operation(summary = "获得仿钉钉流程设计模型")
     @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a")
-    public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("modelId") String modelId){
+    public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){
         return success(modelService.getSimpleModel(modelId));
     }
 

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

@@ -43,10 +43,9 @@ public class BpmSimpleModelNodeVO {
     @Schema(description = "节点的属性")
     private Map<String, Object> attributes; // TODO @jason:建议是字段分拆下;类似说:
 
-    // TODO @jason:看看是不是可以简化;
+    // TODO @jason:看看是不是可以简化;@芋艿: 暂时先放着。不知道后面是否会用到
     /**
      * 附加节点 Id, 该节点不从前端传入。 由程序生成. 由于当个节点无法完成功能。 需要附加节点来完成。
-     * 例如: 会签时需要按拒绝人数来终止流程。 需要 userTask + ServiceTask 两个节点配合完成。 serviceTask 由后端生成。
      */
     @JsonIgnore
     private String attachNodeId;

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

@@ -11,10 +11,9 @@ import lombok.Data;
 @Data
 public class BpmSimpleModelUpdateReqVO {
 
-    // TODO @jason:=> id
     @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotEmpty(message = "流程模型编号不能为空")
-    private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段
+    private String id; // 对应 Flowable act_re_model 表 ID_ 字段
 
     @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotNull(message = "仿钉钉流程设计模型对象不能为空")

+ 3 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/CopyUserDelegate.java → yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/BpmCopyTaskDelegate.java

@@ -7,18 +7,17 @@ import jakarta.annotation.Resource;
 import org.flowable.bpmn.model.FlowElement;
 import org.flowable.engine.delegate.DelegateExecution;
 import org.flowable.engine.delegate.JavaDelegate;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
 
 import java.util.Set;
 
-// TODO @jason:类名可以改成 BpmCopyTaskDelegate
 /**
  * 处理抄送用户的 {@link JavaDelegate} 的实现类
  *
  * @author jason
  */
-@Service // TODO @jason:这种注解,建议用 @Component
-public class CopyUserDelegate implements JavaDelegate  {
+@Component
+public class BpmCopyTaskDelegate implements JavaDelegate  {
 
     @Resource
     private BpmTaskCandidateInvoker taskCandidateInvoker;

+ 0 - 40
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/delegate/MultiInstanceServiceTaskDelegate.java

@@ -1,40 +0,0 @@
-package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.delegate;
-
-import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.BooleanUtil;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
-import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
-import jakarta.annotation.Resource;
-import org.flowable.engine.delegate.DelegateExecution;
-import org.flowable.engine.delegate.JavaDelegate;
-import org.springframework.stereotype.Component;
-
-// TODO @jason:微信已经讨论,简化哈
-/**
- * 处理会签 Service Task 代理
- *
- * @author jason
- */
-@Component
-public class MultiInstanceServiceTaskDelegate implements JavaDelegate {
-
-    @Resource
-    private BpmProcessInstanceService processInstanceService;
-
-    @Override
-    public void execute(DelegateExecution execution) {
-
-        String attachUserTaskId = BpmnModelUtils.parseExtensionElement(execution.getCurrentFlowElement(),
-                BpmnModelConstants.SERVICE_TASK_ATTACH_USER_TASK_ID); // TODO @jason:上面不需要加空行哈;
-        Assert.notNull(attachUserTaskId, "附属的用户任务 Id 不能为空");
-        // 获取会签任务是否被拒绝
-        Boolean userTaskRejected = execution.getVariable(String.format("%s_reject", attachUserTaskId), Boolean.class);
-        // 如果会签任务被拒绝, 终止流程, 跳转到 EndEvent 节点
-        if (BooleanUtil.isTrue(userTaskRejected)) {
-            processInstanceService.updateProcessInstanceReject(execution.getProcessInstanceId(),
-                   execution.getCurrentActivityId(), "会签任务未达到通过比例" );
-        }
-    }
-
-}

+ 0 - 74
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/custom/expression/CompleteByRejectCountExpression.java

@@ -1,74 +0,0 @@
-package cn.iocoder.yudao.module.bpm.framework.flowable.core.custom.expression;
-
-import cn.hutool.core.lang.Assert;
-import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
-import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
-import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
-import lombok.extern.slf4j.Slf4j;
-import org.flowable.bpmn.model.FlowElement;
-import org.flowable.engine.delegate.DelegateExecution;
-import org.springframework.stereotype.Component;
-
-import java.util.Objects;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum.APPROVE_BY_RATIO;
-import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_METHOD;
-import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_APPROVE_RATIO;
-
-// TODO @jason:微信已经讨论,简化哈
-/**
- * 按拒绝人数计算会签的完成条件的流程表达式实现
- *
- * @author jason
- */
-@Component
-@Slf4j
-public class CompleteByRejectCountExpression {
-
-    /**
-     * 会签的完成条件
-     */
-    public boolean completionCondition(DelegateExecution execution) {
-        FlowElement flowElement = execution.getCurrentFlowElement();
-        // 实例总数
-        Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");
-        // 完成的实例数
-        Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances");
-        // 审批方式
-        Integer approveMethod = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_METHOD));
-        Assert.notNull(approveMethod, "审批方式不能空");
-        if (!Objects.equals(APPROVE_BY_RATIO.getMethod(), approveMethod)) {
-            log.error("[completionCondition] the execution is [{}] 审批方式[{}] 不匹配", execution, approveMethod);
-            throw exception(GlobalErrorCodeConstants.ERROR_CONFIGURATION);
-        }
-        // 获取拒绝人数
-        // TODO @jason:CollUtil.filter().size();貌似可以更简洁 @芋艿 CollUtil.filter().size() 使用这个会报错,好坑了.
-        Integer rejectCount = CollectionUtils.getSumValue(execution.getExecutions(),
-                item -> Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), item.getVariableLocal(BpmConstants.TASK_VARIABLE_STATUS, Integer.class)) ? 1 : 0,
-                Integer::sum, 0);
-        // 同意人数: 完成人数 - 拒绝人数
-        int agreeCount = nrOfCompletedInstances - rejectCount;
-        // 多人会签(按通过比例)
-        Integer approveRatio = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_APPROVE_RATIO));
-        Assert.notNull(approveRatio, "通过比例不能空");
-        // 判断通过比例
-        double approvePct = approveRatio / (double) 100;
-        double realApprovePct = (double) agreeCount / nrOfInstances;
-        if (realApprovePct >= approvePct) {
-            return true;
-        }
-        double rejectPct = (100 - approveRatio) / (double) 100;
-        double realRejectPct = (double) rejectCount / nrOfInstances;
-        // 判断拒绝比例
-        if (realRejectPct > rejectPct) {
-            execution.setVariable(String.format("%s_reject", flowElement.getId()), Boolean.TRUE);
-            return true;
-        }
-        return false;
-    }
-
-}

+ 0 - 10
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java

@@ -56,16 +56,6 @@ public interface BpmnModelConstants {
      */
     String USER_TASK_APPROVE_METHOD = "approveMethod";
 
-    /**
-     * BPMN UserTask 的扩展属性,当审批方式为按通过比例时, 标记会签通过比例
-     */
-    String USER_TASK_APPROVE_RATIO = "approveRatio";
-
-    /**
-     * BPMN ExtensionElement 的扩展属性,用于标记 服务任务附属的用户任务 Id
-     */
-    String SERVICE_TASK_ATTACH_USER_TASK_ID = "attachUserTaskId";
-
     /**
      * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
      */

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

@@ -73,48 +73,4 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
         });
     }
 
-    // TODO @jason:这块如果不需要,可以删除掉~~~
-//    @Override
-//    protected void activityMessageReceived(FlowableMessageEvent event) {
-//        BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId());
-//        FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, event.getActivityId());
-//        if (element instanceof BoundaryEvent) {
-//            BoundaryEvent boundaryEvent = (BoundaryEvent) element;
-//            String boundaryEventType = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
-//            // 如果自定义类型为拒绝后处理,进行拒绝处理
-//            if (Objects.equals(USER_TASK_REJECT_POST_PROCESS.getType(), NumberUtils.parseInt(boundaryEventType))) {
-//                String rejectHandlerType = parseBoundaryEventExtensionElement((BoundaryEvent) element, BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE);
-//                rejectHandler(boundaryEvent, event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(rejectHandlerType));
-//            }
-//        }
-//    }
-//
-//    private void rejectHandler(BoundaryEvent boundaryEvent, String processInstanceId, String taskDefineKey, Integer rejectHandlerType) {
-//        BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
-//        if (userTaskRejectHandlerType != null) {
-//            List<Task> taskList = taskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefineKey);
-//            taskList.forEach(task -> {
-//                Integer taskStatus = FlowableUtils.getTaskStatus(task);
-//                // 只有处于拒绝状态下才处理
-//                if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), taskStatus)) {
-//                    // 终止流程
-//                    if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.TERMINATION) {
-//                        processInstanceService.updateProcessInstanceReject(task.getProcessInstanceId(), FlowableUtils.getTaskReason(task));
-//                        return;
-//                    }
-//                    // 驳回
-//                    if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) {
-//                        String returnTaskId = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID);
-//                        if (returnTaskId != null) {
-//                            BpmTaskReturnReqVO reqVO = new BpmTaskReturnReqVO().setId(task.getId())
-//                                    .setTargetTaskDefinitionKey(returnTaskId)
-//                                    .setReason("任务拒绝回退");
-//                            taskService.returnTask(getLoginUserId(), reqVO);
-//                        }
-//                    }
-//                }
-//            });
-//        }
-//    }
-
 }

+ 13 - 5
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java

@@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
 import org.flowable.bpmn.converter.BpmnXMLConverter;
 import org.flowable.bpmn.model.Process;
@@ -27,8 +28,7 @@ public class BpmnModelUtils {
         // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限
         if (candidateStrategy == null) {
             ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
-            // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? NumberUtils.parseInt(element.getElementText()) : null;
-            candidateStrategy = NumberUtils.parseInt(Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null));
+            candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null;
         }
         return candidateStrategy;
     }
@@ -38,18 +38,26 @@ public class BpmnModelUtils {
                 BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
         if (candidateParam == null) {
             ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM));
-            // TODO @jason:这里可以改成 element != null 看着会简单点 element != null ? element.getElementText() : null;
-            candidateParam = Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null);
+            candidateParam = element != null ? element.getElementText() : null;
         }
         return candidateParam;
     }
 
+    public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
+        Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
+        return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
+    }
+
+    public static String parseReturnTaskId(FlowElement flowElement) {
+        return BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
+    }
+
     public static String parseExtensionElement(FlowElement flowElement, String elementName) {
         if (flowElement == null) {
             return null;
         }
         ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
-        return Optional.ofNullable(element).map(ExtensionElement::getElementText).orElse(null);
+        return element != null ? element.getElementText() : null;
     }
 
     // TODO @jason:貌似这个没地方调用???  @芋艿 在 BpmTaskConvert里面。暂时注释掉了。

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

@@ -26,7 +26,6 @@ import java.util.Objects;
 
 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT;
 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.*;
-import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER;
 import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*;
@@ -55,9 +54,9 @@ public class SimpleModelUtils {
     public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }";
 
     /**
-     * 按拒绝人数计算多实例完成条件的表达式
+     * 按通过比例完成表达式
      */
-    public static final String COMPLETE_BY_REJECT_COUNT_EXPRESSION = "${completeByRejectCountExpression.completionCondition(execution)}";
+    public static final String APPROVE_BY_RATIO_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances/nrOfInstances >= %s}";
 
     // TODO-DONE @jason:建议方法名,改成 buildBpmnModel
     // TODO @yunai:注释需要完善下;
@@ -185,8 +184,9 @@ public class SimpleModelUtils {
     }
 
     /**
-     *  构建有附加节点的连线
-     * @param nodeId 当前节点 Id
+     * 构建有附加节点的连线
+     *
+     * @param nodeId       当前节点 Id
      * @param attachNodeId 附属节点 Id
      * @param targetNodeId 目标节点 Id
      */
@@ -344,28 +344,9 @@ public class SimpleModelUtils {
             BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler());
             flowElements.add(boundaryEvent);
         }
-        // 如果按拒绝人数终止流程。需要添加附加的 ServiceTask 处理
-        if (userTaskConfig.getRejectHandler() != null &&
-                Objects.equals(FINISH_PROCESS_BY_REJECT_NUMBER.getType(), userTaskConfig.getRejectHandler().getType())) {
-            ServiceTask serviceTask = buildMultiInstanceServiceTask(node);
-            flowElements.add(serviceTask);
-        }
         return flowElements;
     }
 
-    private static ServiceTask buildMultiInstanceServiceTask(BpmSimpleModelNodeVO node) {
-        ServiceTask serviceTask = new ServiceTask();
-        String id = String.format("Activity-%s", IdUtil.fastSimpleUUID());
-        serviceTask.setId(id);
-        serviceTask.setName("会签服务任务");
-        serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
-        serviceTask.setImplementation("${multiInstanceServiceTaskDelegate}");
-        serviceTask.setAsynchronous(false);
-        addExtensionElement(serviceTask, SERVICE_TASK_ATTACH_USER_TASK_ID, node.getId());
-        node.setAttachNodeId(id);
-        return serviceTask;
-    }
-
     private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) {
         // 定时器边界事件
         BoundaryEvent boundaryEvent = new BoundaryEvent();
@@ -406,7 +387,7 @@ public class SimpleModelUtils {
         serviceTask.setId(node.getId());
         serviceTask.setName(node.getName());
         serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
-        serviceTask.setImplementation("${copyUserDelegate}");
+        serviceTask.setImplementation("${bpmCopyTaskDelegate}");
 
         // 添加抄送候选人元素
         addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY),
@@ -515,11 +496,10 @@ public class SimpleModelUtils {
             multiInstanceCharacteristics.setLoopCardinality("1");
             userTask.setLoopCharacteristics(multiInstanceCharacteristics);
         } else if (bpmApproveMethodEnum == BpmApproveMethodEnum.APPROVE_BY_RATIO) {
-            multiInstanceCharacteristics.setCompletionCondition(COMPLETE_BY_REJECT_COUNT_EXPRESSION);
-            multiInstanceCharacteristics.setSequential(false);
             Assert.notNull(approveRatio, "通过比例不能为空");
-            // 添加通过比例的扩展属性
-            addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_RATIO, approveRatio.toString());
+            double approvePct = approveRatio / (double) 100;
+            multiInstanceCharacteristics.setCompletionCondition(String.format(APPROVE_BY_RATIO_COMPLETE_EXPRESSION, String.format("%.2f", approvePct)));
+            multiInstanceCharacteristics.setSequential(false);
         }
         userTask.setLoopCharacteristics(multiInstanceCharacteristics);
     }

+ 1 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java

@@ -226,7 +226,7 @@ public class BpmModelServiceImpl implements BpmModelService {
     @Override
     public void updateSimpleModel(BpmSimpleModelUpdateReqVO reqVO) {
         // 1. 校验流程模型存在
-        Model model = getModel(reqVO.getModelId());
+        Model model = getModel(reqVO.getId());
         if (model == null) {
             throw exception(MODEL_NOT_EXISTS);
         }

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

@@ -13,7 +13,13 @@ import java.util.Collection;
  */
 public interface BpmProcessInstanceCopyService {
 
-    // TODO @jason:要不把 createProcessInstanceCopy 搞 2 个方法,一个方法参数是之前的 userIds、taskId;一个方法是现在 userIds、processInstanceId、taskId、taskName;
+    /**
+     * 流程实例的抄送
+     *
+     * @param userIds 抄送的用户编号
+     * @param taskId 流程任务编号
+     */
+    void createProcessInstanceCopy(Collection<Long> userIds, String taskId);
 
     /**
      * 流程实例的抄送

+ 14 - 8
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.bpm.service.task;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
@@ -10,6 +11,7 @@ import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.flowable.engine.repository.ProcessDefinition;
 import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.task.api.Task;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -44,21 +46,25 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
     @Lazy // 延迟加载,避免循环依赖
     private BpmProcessDefinitionService processDefinitionService;
 
+    @Override
+    public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) {
+        Task task = taskService.getTask(taskId);
+        if (ObjectUtil.isNull(task)) {
+            throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
+        }
+        String processInstanceId = task.getProcessInstanceId();
+        createProcessInstanceCopy(userIds, processInstanceId, task.getId(), task.getName());
+    }
+
     // TODO @芋艿:这里多加了一个 name;
     @Override
     public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String taskId, String taskName) {
-        // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(UserTask) TODO jason:抄送节点,会没有来源的 taskId 么? @芋艿 是否校验一下 传递进来的 id 不为空就行
-//        Task task = taskService.getTask(taskId);
-//        if (ObjectUtil.isNull(task)) {
-//            throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
-//        }
-        // 1.2 校验流程实例存在
-//      String processInstanceId = task.getProcessInstanceId();
+        // 1.1 校验流程实例存在
         ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
         if (processInstance == null) {
             throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS);
         }
-        // 1.3 校验流程定义存在
+        // 1.2 校验流程定义存在
         ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
                 processInstance.getProcessDefinitionId());
         if (processDefinition == null) {

文件差異過大導致無法顯示
+ 0 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java


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

@@ -54,8 +54,6 @@ import java.util.stream.Stream;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
-import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE;
-import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID;
 
 /**
  * 流程任务实例 Service 实现类
@@ -189,8 +187,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
 
         // 2. 抄送用户
         if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) {
-            processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), instance.getProcessInstanceId(),
-                    reqVO.getId(), task.getName());
+            processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId());
         }
 
         // 情况一:被委派的任务,不调用 complete 去完成任务
@@ -338,35 +335,18 @@ public class BpmTaskServiceImpl implements BpmTaskService {
 
         // 3.1 解析用户任务的拒绝处理类型
         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
-        // TODO @jason:342 到 344 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy
-        UserTask flowElement = (UserTask) BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
-        Integer rejectHandlerType = NumberUtils.parseInt(BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_HANDLER_TYPE));
-        BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
-        // 3.2 类型为驳回到指定的任务节点 TODO @jason:下面这种判断,最好是 JSON
-        if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) {
-            // TODO @jason:348 最好抽象一个方法出来哈。放在 BpmnModelUtils,参照类似 parseCandidateStrategy
-            String returnTaskId = BpmnModelUtils.parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
-            // TODO @jason:这里如果找不到,直接抛出系统异常;因为说白了,已经不是业务异常啦。
-            if (returnTaskId == null) {
-                throw exception(TASK_RETURN_NOT_ASSIGN_TARGET_TASK_ID);
-            }
+        FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
+        BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(flowElement);
+        // 3.2 类型为驳回到指定的任务节点
+        if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
+            String returnTaskId = BpmnModelUtils.parseReturnTaskId(flowElement);
+            Assert.notNull(returnTaskId, "回退的节点不能为空");
             BpmTaskReturnReqVO returnReq = new BpmTaskReturnReqVO().setId(task.getId()).setTargetTaskDefinitionKey(returnTaskId)
                     .setReason(reqVO.getReason());
             returnTask(userId, returnReq);
             return;
-        } else if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.FINISH_PROCESS_BY_REJECT_NUMBER) {
-            // TODO @jason:微信沟通,去掉类似的逻辑;
-            // 3.3 按拒绝人数终止流程
-            if (!flowElement.hasMultiInstanceLoopCharacteristics()) {
-                log.error("[rejectTask] 按拒绝人数终止流程类型,只能用于会签任务. 当前任务【{}】不是会签任务", task.getId());
-                throw new IllegalStateException("按拒绝人数终止流程类型,只能用于会签任务");
-            }
-            // 设置变量值为拒绝
-            runtimeService.setVariableLocal(task.getExecutionId(), BpmConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.REJECT.getStatus());
-            taskService.complete(task.getId());
-            return;
         }
-        // 3.4 其他情况 终止流程。
+        // 3.3 其他情况 终止流程。
         processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(),
                 task.getTaskDefinitionKey(), reqVO.getReason());
     }

部分文件因文件數量過多而無法顯示