Commit ff70aaa6a9dea2ede66eaa8b57d1a343045af2e6

Authored by YunaiV
2 parents 8ffe9272 176e8714

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

sql/tools/convertor.py
@@ -17,6 +17,7 @@ uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql @@ -17,6 +17,7 @@ uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql
17 import argparse 17 import argparse
18 import pathlib 18 import pathlib
19 import re 19 import re
  20 +import sys
20 import time 21 import time
21 from abc import ABC, abstractmethod 22 from abc import ABC, abstractmethod
22 from typing import Dict, Generator, Optional, Tuple, Union 23 from typing import Dict, Generator, Optional, Tuple, Union
@@ -293,8 +294,10 @@ class Convertor(ABC): @@ -293,8 +294,10 @@ class Convertor(ABC):
293 294
294 # 将parse失败的脚本打印出来 295 # 将parse失败的脚本打印出来
295 if error_scripts: 296 if error_scripts:
  297 + print("!!! 以下内容无法正常解析", file=sys.stderr)
296 for script in error_scripts: 298 for script in error_scripts:
297 - print(script) 299 + # print to stderr
  300 + print(script, file=sys.stderr)
298 301
299 302
300 class PostgreSQLConvertor(Convertor): 303 class PostgreSQLConvertor(Convertor):
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -6,6 +6,8 @@ import cn.hutool.core.map.MapUtil; @@ -6,6 +6,8 @@ import cn.hutool.core.map.MapUtil;
6 import cn.hutool.core.util.ObjUtil; 6 import cn.hutool.core.util.ObjUtil;
7 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
8 import cn.hutool.extra.servlet.JakartaServletUtil; 8 import cn.hutool.extra.servlet.JakartaServletUtil;
  9 +import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
  10 +import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
9 import cn.iocoder.yudao.framework.common.exception.ServiceException; 11 import cn.iocoder.yudao.framework.common.exception.ServiceException;
10 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; 12 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
11 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 13 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@@ -14,8 +16,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -14,8 +16,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
14 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 16 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
15 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 17 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
16 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; 18 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
17 -import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;  
18 -import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;  
19 import com.fasterxml.jackson.databind.exc.InvalidFormatException; 19 import com.fasterxml.jackson.databind.exc.InvalidFormatException;
20 import jakarta.servlet.http.HttpServletRequest; 20 import jakarta.servlet.http.HttpServletRequest;
21 import jakarta.validation.ConstraintViolation; 21 import jakarta.validation.ConstraintViolation;
@@ -29,6 +29,7 @@ import org.springframework.util.Assert; @@ -29,6 +29,7 @@ import org.springframework.util.Assert;
29 import org.springframework.validation.BindException; 29 import org.springframework.validation.BindException;
30 import org.springframework.validation.FieldError; 30 import org.springframework.validation.FieldError;
31 import org.springframework.validation.ObjectError; 31 import org.springframework.validation.ObjectError;
  32 +import org.springframework.web.HttpMediaTypeNotSupportedException;
32 import org.springframework.web.HttpRequestMethodNotSupportedException; 33 import org.springframework.web.HttpRequestMethodNotSupportedException;
33 import org.springframework.web.bind.MethodArgumentNotValidException; 34 import org.springframework.web.bind.MethodArgumentNotValidException;
34 import org.springframework.web.bind.MissingServletRequestParameterException; 35 import org.springframework.web.bind.MissingServletRequestParameterException;
@@ -101,6 +102,9 @@ public class GlobalExceptionHandler { @@ -101,6 +102,9 @@ public class GlobalExceptionHandler {
101 if (ex instanceof HttpRequestMethodNotSupportedException) { 102 if (ex instanceof HttpRequestMethodNotSupportedException) {
102 return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex); 103 return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
103 } 104 }
  105 + if (ex instanceof HttpMediaTypeNotSupportedException) {
  106 + return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
  107 + }
104 if (ex instanceof ServiceException) { 108 if (ex instanceof ServiceException) {
105 return serviceExceptionHandler((ServiceException) ex); 109 return serviceExceptionHandler((ServiceException) ex);
106 } 110 }
@@ -171,17 +175,19 @@ public class GlobalExceptionHandler { @@ -171,17 +175,19 @@ public class GlobalExceptionHandler {
171 /** 175 /**
172 * 处理 SpringMVC 请求参数类型错误 176 * 处理 SpringMVC 请求参数类型错误
173 * 177 *
174 - * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String 178 + * 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
175 */ 179 */
176 @ExceptionHandler(HttpMessageNotReadableException.class) 180 @ExceptionHandler(HttpMessageNotReadableException.class)
177 public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) { 181 public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
178 log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex); 182 log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
179 - if(ex.getCause() instanceof InvalidFormatException) { 183 + if (ex.getCause() instanceof InvalidFormatException) {
180 InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause(); 184 InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
181 return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue())); 185 return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
182 - }else {  
183 - return defaultExceptionHandler(ServletUtils.getRequest(), ex);  
184 } 186 }
  187 + if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) {
  188 + return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失");
  189 + }
  190 + return defaultExceptionHandler(ServletUtils.getRequest(), ex);
185 } 191 }
186 192
187 /** 193 /**
@@ -238,6 +244,17 @@ public class GlobalExceptionHandler { @@ -238,6 +244,17 @@ public class GlobalExceptionHandler {
238 } 244 }
239 245
240 /** 246 /**
  247 + * 处理 SpringMVC 请求的 Content-Type 不正确
  248 + *
  249 + * 例如说,A 接口的 Content-Type 为 application/json,结果请求的 Content-Type 为 application/octet-stream,导致不匹配
  250 + */
  251 + @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  252 + public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) {
  253 + log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex);
  254 + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage()));
  255 + }
  256 +
  257 + /**
241 * 处理 Spring Security 权限不足的异常 258 * 处理 Spring Security 权限不足的异常
242 * 259 *
243 * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截 260 * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
@@ -71,6 +71,9 @@ public class BpmSimpleModelNodeVO { @@ -71,6 +71,9 @@ public class BpmSimpleModelNodeVO {
71 @Schema(description = "是否填写审批意见", example = "false") 71 @Schema(description = "是否填写审批意见", example = "false")
72 private Boolean reasonRequire; 72 private Boolean reasonRequire;
73 73
  74 + @Schema(description = "跳过表达式", example = "{true}")
  75 + private String skipExpression; // 用于审批节点
  76 +
74 /** 77 /**
75 * 审批节点拒绝处理 78 * 审批节点拒绝处理
76 */ 79 */
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
@@ -14,6 +14,7 @@ import lombok.Getter; @@ -14,6 +14,7 @@ import lombok.Getter;
14 @AllArgsConstructor 14 @AllArgsConstructor
15 public enum BpmTaskStatusEnum { 15 public enum BpmTaskStatusEnum {
16 16
  17 + SKIP(-2, "跳过"),
17 NOT_START(-1, "未开始"), 18 NOT_START(-1, "未开始"),
18 RUNNING(1, "审批中"), 19 RUNNING(1, "审批中"),
19 APPROVE(2, "审批通过"), 20 APPROVE(2, "审批通过"),
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -800,9 +800,10 @@ public class BpmnModelUtils { @@ -800,9 +800,10 @@ public class BpmnModelUtils {
800 || currentElement instanceof EndEvent 800 || currentElement instanceof EndEvent
801 || currentElement instanceof UserTask 801 || currentElement instanceof UserTask
802 || currentElement instanceof ServiceTask) { 802 || currentElement instanceof ServiceTask) {
803 - // 添加元素 803 + // 添加节点
804 FlowNode flowNode = (FlowNode) currentElement; 804 FlowNode flowNode = (FlowNode) currentElement;
805 resultElements.add(flowNode); 805 resultElements.add(flowNode);
  806 +
806 // 遍历子节点 807 // 遍历子节点
807 flowNode.getOutgoingFlows().forEach( 808 flowNode.getOutgoingFlows().forEach(
808 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); 809 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
@@ -836,6 +837,31 @@ public class BpmnModelUtils { @@ -836,6 +837,31 @@ public class BpmnModelUtils {
836 } 837 }
837 838
838 /** 839 /**
  840 + * 判断是否跳过此节点
  841 + *
  842 + * @param flowNode 节点
  843 + * @param variables 流程变量
  844 + */
  845 + public static boolean isSkipNode(FlowElement flowNode, Map<String, Object> variables) {
  846 + // 1. 检查节点是否有跳过表达式(支持多种任务节点类型)
  847 + String skipExpression = null;
  848 + if (flowNode instanceof UserTask) {
  849 + skipExpression = ((UserTask) flowNode).getSkipExpression();
  850 + } else if (flowNode instanceof ServiceTask) {
  851 + skipExpression = ((ServiceTask) flowNode).getSkipExpression();
  852 + } else if (flowNode instanceof ScriptTask) {
  853 + skipExpression = ((ScriptTask) flowNode).getSkipExpression();
  854 + }
  855 +
  856 + if (StrUtil.isEmpty(skipExpression)) {
  857 + return false;
  858 + }
  859 +
  860 + // 2. 计算跳过表达式的值
  861 + return evalConditionExpress(variables, skipExpression);
  862 + }
  863 +
  864 + /**
839 * 根据当前节点,获取下一个节点 865 * 根据当前节点,获取下一个节点
840 * 866 *
841 * @param currentElement 当前节点 867 * @param currentElement 当前节点
@@ -997,7 +1023,7 @@ public class BpmnModelUtils { @@ -997,7 +1023,7 @@ public class BpmnModelUtils {
997 * @return 是否满足条件 1023 * @return 是否满足条件
998 */ 1024 */
999 public static boolean evalConditionExpress(Map<String, Object> variables, String expression) { 1025 public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
1000 - if (expression == null) { 1026 + if (StrUtil.isEmpty(expression)) {
1001 return Boolean.FALSE; 1027 return Boolean.FALSE;
1002 } 1028 }
1003 // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables 1029 // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -464,6 +464,10 @@ public class SimpleModelUtils { @@ -464,6 +464,10 @@ public class SimpleModelUtils {
464 addReasonRequire(node.getReasonRequire(), userTask); 464 addReasonRequire(node.getReasonRequire(), userTask);
465 // 节点类型 465 // 节点类型
466 addNodeType(node.getType(), userTask); 466 addNodeType(node.getType(), userTask);
  467 + // 添加跳过表达式
  468 + if (StrUtil.isNotEmpty(node.getSkipExpression())) {
  469 + userTask.setSkipExpression(node.getSkipExpression());
  470 + }
467 return userTask; 471 return userTask;
468 } 472 }
469 473
@@ -968,7 +972,7 @@ public class SimpleModelUtils { @@ -968,7 +972,7 @@ public class SimpleModelUtils {
968 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE 972 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
969 || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS 973 || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
970 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { 974 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
971 - // 添加元素 975 + // 添加此节点
972 resultNodes.add(currentNode); 976 resultNodes.add(currentNode);
973 } 977 }
974 978
@@ -1014,6 +1018,16 @@ public class SimpleModelUtils { @@ -1014,6 +1018,16 @@ public class SimpleModelUtils {
1014 simulateNextNode(currentNode.getChildNode(), variables, resultNodes); 1018 simulateNextNode(currentNode.getChildNode(), variables, resultNodes);
1015 } 1019 }
1016 1020
  1021 + /**
  1022 + * 根据跳过表达式,判断是否跳过此节点
  1023 + */
  1024 + public static boolean isSkipNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables) {
  1025 + if (StrUtil.isEmpty(currentNode.getSkipExpression())) {
  1026 + return false;
  1027 + }
  1028 + return BpmnModelUtils.evalConditionExpress(variables, currentNode.getSkipExpression());
  1029 + }
  1030 +
1017 public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { 1031 public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
1018 return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting)); 1032 return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting));
1019 } 1033 }
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -398,7 +398,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -398,7 +398,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
398 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() 398 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
399 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别 399 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
400 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())) 400 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
401 - .setStatus(FlowableUtils.getTaskStatus(task)) 401 + .setStatus(getEndActivityNodeStatus(task))
402 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) 402 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
403 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) 403 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
404 .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); 404 .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));
@@ -463,6 +463,18 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -463,6 +463,18 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
463 } 463 }
464 464
465 /** 465 /**
  466 + * 获取结束节点的状态
  467 + */
  468 + private Integer getEndActivityNodeStatus(HistoricTaskInstance task) {
  469 + Integer status = FlowableUtils.getTaskStatus(task);
  470 + if (status != null) {
  471 + return status;
  472 + }
  473 + // 结束节点未获取到状态,为跳过状态。可见 bpmn 或者 simple 的 skipExpression
  474 + return BpmTaskStatusEnum.SKIP.getStatus();
  475 + }
  476 +
  477 + /**
466 * 获得【进行中】的活动节点们 478 * 获得【进行中】的活动节点们
467 */ 479 */
468 private List<ActivityNode> getRunApproveNodeList(Long startUserId, 480 private List<ActivityNode> getRunApproveNodeList(Long startUserId,
@@ -565,10 +577,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -565,10 +577,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
565 if (runActivityIds.contains(node.getId())) { 577 if (runActivityIds.contains(node.getId())) {
566 return null; 578 return null;
567 } 579 }
568 - 580 + Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
  581 + // 如果节点被跳过。设置状态为跳过
  582 + if (SimpleModelUtils.isSkipNode(node, processVariables)) {
  583 + status = BpmTaskStatusEnum.SKIP.getStatus();
  584 + }
569 ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName()) 585 ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())
570 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy()) 586 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())
571 - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); 587 + .setStatus(status);
572 588
573 // 1. 开始节点/审批节点 589 // 1. 开始节点/审批节点
574 if (ObjectUtils.equalsAny(node.getType(), 590 if (ObjectUtils.equalsAny(node.getType(),
@@ -608,8 +624,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -608,8 +624,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
608 if (runActivityIds.contains(node.getId())) { 624 if (runActivityIds.contains(node.getId())) {
609 return null; 625 return null;
610 } 626 }
  627 + Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
  628 + // 如果节点被跳过,状态设置为跳过
  629 + if(BpmnModelUtils.isSkipNode(node, processVariables)){
  630 + status = BpmTaskStatusEnum.SKIP.getStatus();
  631 + }
611 ActivityNode activityNode = new ActivityNode().setId(node.getId()) 632 ActivityNode activityNode = new ActivityNode().setId(node.getId())
612 - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); 633 + .setStatus(status);
613 634
614 // 1. 开始节点 635 // 1. 开始节点
615 if (node instanceof StartEvent) { 636 if (node instanceof StartEvent) {
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -947,7 +947,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -947,7 +947,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
947 BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason())); 947 BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason()));
948 948
949 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) 949 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
950 - taskService.setOwner(taskId, task.getAssignee()); 950 + // 特殊:如果已经被委派(owner 非空),则不需要更新 owner:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153
  951 + if (StrUtil.isEmpty(task.getOwner())) {
  952 + taskService.setOwner(taskId, task.getAssignee());
  953 + }
951 // 3.2 执行委派,将任务委派给 delegateUser 954 // 3.2 执行委派,将任务委派给 delegateUser
952 taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); 955 taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
953 // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中 956 // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中
@@ -973,7 +976,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -973,7 +976,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
973 BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason())); 976 BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason()));
974 977
975 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) 978 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
976 - taskService.setOwner(taskId, task.getAssignee()); 979 + // 特殊:如果已经被转派(owner 非空),则不需要更新 owner:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153
  980 + if (StrUtil.isEmpty(task.getOwner())) {
  981 + taskService.setOwner(taskId, task.getAssignee());
  982 + }
977 // 3.2 执行转派(审批人),将任务转派给 assigneeUser 983 // 3.2 执行转派(审批人),将任务转派给 assigneeUser
978 // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!! 984 // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!!
979 taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString()); 985 taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString());