mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 18:28:43 +08:00 
			
		
		
		
	仿钉钉流程设计- 简化审批拒绝流程, code review 修改
This commit is contained in:
		| @@ -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 ========== | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import lombok.Getter; | ||||
| public enum BpmFieldPermissionEnum { | ||||
|  | ||||
|     // TODO @jason:这个顺序要不要改下,和页面保持一致;只读(1)、编辑(2)、隐藏(3) | ||||
|     // @芋艿 我看钉钉页面的顺序 是 可编辑 只读 隐藏 | ||||
|     WRITE(1, "可编辑"), | ||||
|     READ(2, "只读"), | ||||
|     NONE(3, "隐藏"); | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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)); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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 = "仿钉钉流程设计模型对象不能为空") | ||||
|   | ||||
| @@ -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; | ||||
| @@ -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(), "会签任务未达到通过比例" ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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 流程表单字段权限元素, 用于标记字段权限 | ||||
|      */ | ||||
|   | ||||
| @@ -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); | ||||
| //                        } | ||||
| //                    } | ||||
| //                } | ||||
| //            }); | ||||
| //        } | ||||
| //    } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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里面。暂时注释掉了。 | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|     /** | ||||
|      * 流程实例的抄送 | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -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()); | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 jason
					jason