Commit 1dc5fd2c24499cb123b8cad26fb5804ee42df0f0

Authored by YunaiV
2 parents a4e23082 c2f9bad3

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

# Conflicts:
#	yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java
... ... @@ -43,14 +43,23 @@ public class BpmnVariableConstants {
43 43 * @see ProcessInstance#getProcessVariables()
44 44 */
45 45 public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID";
  46 +
46 47 /**
47 48 * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id}
48 49 *
49   - * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过
  50 + * 目的是:退回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过
50 51 *
51 52 * @see ProcessInstance#getProcessVariables()
52 53 */
53 54 public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s";
  55 +
  56 + /**
  57 + * 流程实例的变量前缀 - 用于退回操作。记录需要预测的节点. 格式 NEED_SIMULATE_TASK_{节点定义 id}
  58 + *
  59 + * 目的是:退回操作,预测节点会不准,在流程变量中记录需要预测的节点,来辅助预测
  60 + */
  61 + public static final String PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX = "NEED_SIMULATE_TASK_";
  62 +
54 63 /**
55 64 * 流程实例的变量 - 是否跳过表达式
56 65 *
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -658,10 +658,11 @@ public class BpmnModelUtils {
658 658  
659 659 // 根据类型,获取入口连线
660 660 List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
  661 + // 1.没有入口连线,则返回 false
661 662 if (CollUtil.isEmpty(sequenceFlows)) {
662   - return true;
  663 + return false;
663 664 }
664   - // 循环找到目标元素
  665 + // 2.循环找目标元素, 找到目标节点
665 666 for (SequenceFlow sequenceFlow : sequenceFlows) {
666 667 // 如果发现连线重复,说明循环了,跳过这个循环
667 668 if (visitedElements.contains(sequenceFlow.getId())) {
... ... @@ -669,21 +670,22 @@ public class BpmnModelUtils {
669 670 }
670 671 // 添加已经走过的连线
671 672 visitedElements.add(sequenceFlow.getId());
672   - // 这条线路存在目标节点,这条线路完成,进入下个线路
  673 + // 这条线路存在目标节点,直接返回 true
673 674 FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
674 675 if (target.getId().equals(sourceFlowElement.getId())) {
675   - continue;
  676 + return true;
676 677 }
677   - // 如果目标节点为并行网关,则不继续
  678 + // 如果目标节点为并行网关,跳过这个循环 (TODO 疑问:这个判断作用是防止回退到并行网关分支上的节点吗?)
678 679 if (sourceFlowElement instanceof ParallelGateway) {
679   - return false;
  680 + continue;
680 681 }
681   - // 否则就继续迭代
682   - if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
683   - return false;
  682 + // 继续迭代, 如果找到目标节点直接返回 true
  683 + if (isSequentialReachable(sourceFlowElement, target, visitedElements)) {
  684 + return true;
684 685 }
685 686 }
686   - return true;
  687 + // 未找到返回 false
  688 + return false;
687 689 }
688 690  
689 691 /**
... ... @@ -783,7 +785,6 @@ public class BpmnModelUtils {
783 785 return resultElements;
784 786 }
785 787  
786   - @SuppressWarnings("PatternVariableCanBeUsed")
787 788 private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables,
788 789 List<FlowElement> resultElements, Set<FlowElement> visitElements) {
789 790 // 如果为空,或者已经遍历过,则直接结束
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
... ... @@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil;
5 5 import cn.hutool.core.date.DatePattern;
6 6 import cn.hutool.core.date.DateUtil;
7 7 import cn.hutool.core.lang.Assert;
  8 +import cn.hutool.core.map.MapUtil;
8 9 import cn.hutool.core.util.*;
9 10 import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 11 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
... ... @@ -74,6 +75,7 @@ import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmA
74 75 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
75 76 import static cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum.REJECT_CHILD_PROCESS;
76 77 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
  78 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX;
77 79 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseNodeType;
78 80 import static java.util.Arrays.asList;
79 81 import static java.util.Collections.singletonList;
... ... @@ -220,10 +222,24 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
220 222  
221 223 // 3.1 计算当前登录用户的待办任务
222 224 BpmTaskRespVO todoTask = taskService.getTodoTask(loginUserId, reqVO.getTaskId(), reqVO.getProcessInstanceId());
223   - // 3.2 预测未运行节点的审批信息
  225 +
  226 + // 3.2 获取由于退回操作,需要预测的节点。 从流程变量中获取。 回退操作会设置这些变量
  227 + Set<String> needSimulateTaskDefKeysByReturn = new HashSet<>();
  228 + if (StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) {
  229 + Map<String, Object> variables = runtimeService.getVariables(reqVO.getProcessInstanceId());
  230 + Map<String, Object> simulateTaskVariables = MapUtil.filter(variables,
  231 + item -> item.getKey().startsWith(PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX));
  232 + simulateTaskVariables.forEach(
  233 + (key, value) -> needSimulateTaskDefKeysByReturn.add(StrUtil.removePrefix(key, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX)));
  234 + }
  235 + // 移除运行中的节点,运行中的节点无需预测
  236 + CollectionUtils.convertList(runActivityNodes, ActivityNode::getId).forEach(needSimulateTaskDefKeysByReturn::remove);
  237 +
  238 +
  239 + // 3.3 预测未运行节点的审批信息
224 240 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
225 241 processDefinitionInfo,
226   - processVariables, activities);
  242 + processVariables, activities, needSimulateTaskDefKeysByReturn);
227 243  
228 244 // 4. 拼接最终数据
229 245 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
... ... @@ -463,7 +479,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
463 479 }
464 480  
465 481 /**
466   - * 获取结束节点的状态
  482 + * 获取结束节点的状态
467 483 */
468 484 private Integer getEndActivityNodeStatus(HistoricTaskInstance task) {
469 485 Integer status = FlowableUtils.getTaskStatus(task);
... ... @@ -548,7 +564,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
548 564 private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel,
549 565 BpmProcessDefinitionInfoDO processDefinitionInfo,
550 566 Map<String, Object> processVariables,
551   - List<HistoricActivityInstance> activities) {
  567 + List<HistoricActivityInstance> activities,
  568 + Set<String> needSimulateTaskDefKeysByReturn) {
552 569 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance
553 570 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录
554 571 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);
... ... @@ -557,7 +574,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
557 574 List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
558 575 return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(
559 576 startUserId, bpmnModel, flowElements,
560   - processDefinitionInfo, processVariables, flowElement, runActivityIds));
  577 + processDefinitionInfo, processVariables, flowElement, runActivityIds, needSimulateTaskDefKeysByReturn));
561 578 }
562 579 // 情况二:SIMPLE 设计器
563 580 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) {
... ... @@ -566,17 +583,19 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
566 583 List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
567 584 return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(
568 585 startUserId, bpmnModel,
569   - processDefinitionInfo, processVariables, simpleNode, runActivityIds));
  586 + processDefinitionInfo, processVariables, simpleNode, runActivityIds, needSimulateTaskDefKeysByReturn));
570 587 }
571 588 throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
572 589 }
573 590  
574 591 private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,
575 592 BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
576   - BpmSimpleModelNodeVO node, Set<String> runActivityIds) {
  593 + BpmSimpleModelNodeVO node, Set<String> runActivityIds,
  594 + Set<String> needSimulateTaskDefKeysByReturn) {
577 595 // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance
578 596 // 包括了历史的操作,不是只有 startEvent 到当前节点的记录
579   - if (runActivityIds.contains(node.getId())) {
  597 + // 回退操作时候,会记录需要预测的节点到流程变量中。即使在历史操作中,也需要预测。
  598 + if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) {
580 599 return null;
581 600 }
582 601 Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
... ... @@ -623,13 +642,17 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
623 642 private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List<FlowElement> flowElements,
624 643 BpmProcessDefinitionInfoDO processDefinitionInfo,
625 644 Map<String, Object> processVariables,
626   - FlowElement node, Set<String> runActivityIds) {
627   - if (runActivityIds.contains(node.getId())) {
  645 + FlowElement node, Set<String> runActivityIds,
  646 + Set<String> needSimulateTaskDefKeysByReturn) {
  647 +
  648 + // 回退操作时候,会记录需要预测的节点到流程变量中。即使节点在历史操作中,也需要预测。
  649 + if (!needSimulateTaskDefKeysByReturn.contains(node.getId()) && runActivityIds.contains(node.getId())) {
628 650 return null;
629 651 }
  652 +
630 653 Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
631 654 // 如果节点被跳过,状态设置为跳过
632   - if(BpmnModelUtils.isSkipNode(node, processVariables)){
  655 + if (BpmnModelUtils.isSkipNode(node, processVariables)) {
633 656 status = BpmTaskStatusEnum.SKIP.getStatus();
634 657 }
635 658 ActivityNode activityNode = new ActivityNode().setId(node.getId())
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -2,10 +2,12 @@ package cn.iocoder.yudao.module.bpm.service.task;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.convert.Convert;
  5 +import cn.hutool.core.date.DateUtil;
5 6 import cn.hutool.core.lang.Assert;
6 7 import cn.hutool.core.util.*;
7 8 import cn.hutool.extra.spring.SpringUtil;
8 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  10 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
9 11 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
10 12 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
11 13 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
... ... @@ -68,8 +70,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
68 70 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
69 71 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
70 72 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
71   -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;
72   -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
  73 +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.*;
73 74 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
74 75  
75 76 /**
... ... @@ -601,7 +602,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
601 602 bpmnModel, reqVO.getNextAssignees(), instance);
602 603 runtimeService.setVariables(task.getProcessInstanceId(), variables);
603 604  
604   - // 5. 调用 BPM complete 去完成任务
  605 + // 5. 移除辅助预测的流程变量,这些变量在回退操作中设置
  606 + String simulateVariableName = StrUtil.concat(false, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, task.getTaskDefinitionKey());
  607 + runtimeService.removeVariable(task.getProcessInstanceId(), simulateVariableName);
  608 +
  609 + // 6. 调用 BPM complete 去完成任务
605 610 taskService.complete(task.getId(), variables, true);
606 611  
607 612 // 【加签专属】处理加签任务
... ... @@ -851,25 +856,26 @@ public class BpmTaskServiceImpl implements BpmTaskService {
851 856 if (task.isSuspended()) {
852 857 throw exception(TASK_IS_PENDING);
853 858 }
854   - // 1.2 校验源头和目标节点的关系,并返回目标元素
855   - FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(),
856   - reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
  859 + // 1.2 获取流程模型信息
  860 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
  861 + // 1.3 校验源头和目标节点的关系,并返回目标元素
  862 + FlowElement targetElement = validateTargetTaskCanReturn(bpmnModel, task.getTaskDefinitionKey(),
  863 + reqVO.getTargetTaskDefinitionKey());
857 864  
858 865 // 2. 调用 Flowable 框架的退回逻辑
859   - returnTask(userId, task, targetElement, reqVO);
  866 + returnTask(userId, bpmnModel, task, targetElement, reqVO);
860 867 }
861 868  
862 869 /**
863 870 * 退回流程节点时,校验目标任务节点是否可退回
864 871 *
865   - * @param sourceKey 当前任务节点 Key
866   - * @param targetKey 目标任务节点 key
867   - * @param processDefinitionId 当前流程定义 ID
  872 + * @param bpmnModel 流程模型
  873 + * @param sourceKey 当前任务节点 Key
  874 + * @param targetKey 目标任务节点 key
868 875 * @return 目标任务节点元素
869 876 */
870   - private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) {
871   - // 1.1 获取流程模型信息
872   - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
  877 + private FlowElement validateTargetTaskCanReturn(BpmnModel bpmnModel, String sourceKey, String targetKey) {
  878 +
873 879 // 1.3 获取当前任务节点元素
874 880 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey);
875 881 // 1.3 获取跳转的节点元素
... ... @@ -889,11 +895,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
889 895 * 执行退回逻辑
890 896 *
891 897 * @param userId 用户编号
  898 + * @param bpmnModel 流程模型
892 899 * @param currentTask 当前退回的任务
893 900 * @param targetElement 需要退回到的目标任务
894 901 * @param reqVO 前端参数封装
895 902 */
896   - public void returnTask(Long userId, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
  903 + public void returnTask(Long userId, BpmnModel bpmnModel, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
897 904 // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤
898 905 // 1.1 获取所有正常进行的任务节点 Key
899 906 List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list();
... ... @@ -926,18 +933,45 @@ public class BpmTaskServiceImpl implements BpmTaskService {
926 933 }
927 934 });
928 935  
929   - // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过
930   - runtimeService.setVariable(currentTask.getProcessInstanceId(),
931   - String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE);
  936 + // 3. 构建需要预测的任务流程变量
  937 + Set<String> taskDefinitionKeyList = needSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement);
  938 + Map<String, Object> needSimulateVariables = convertMap(taskDefinitionKeyList,
  939 + taskId -> StrUtil.concat(false, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, taskId), item -> Boolean.TRUE);
  940 +
932 941 // 4. 执行驳回
933 942 // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因:
934 943 // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944
935 944 runtimeService.createChangeActivityStateBuilder()
936 945 .processInstanceId(currentTask.getProcessInstanceId())
937 946 .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey())
  947 + // 设置需要预测的任务流程变量。用于辅助预测
  948 + .processVariables(needSimulateVariables)
  949 + // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过
  950 + .localVariable(reqVO.getTargetTaskDefinitionKey(),
  951 + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE)
938 952 .changeState();
939 953 }
940 954  
  955 + private Set<String> needSimulateTaskDefinitionKeys(BpmnModel bpmnModel, Task currentTask, FlowElement targetElement) {
  956 + // 获取需要预测的任务的 definition key。 当前任务还没完成。也需要预测。
  957 + Set<String> taskDefinitionKeys = CollUtil.newHashSet(currentTask.getTaskDefinitionKey());
  958 + // 从已结束任务中找到要回退的目标任务。按时间倒序最近的一个目标任务
  959 + List<HistoricTaskInstance> endTaskList = CollectionUtils.filterList(
  960 + getTaskListByProcessInstanceId(currentTask.getProcessInstanceId(), Boolean.FALSE), item -> item.getEndTime() != null);
  961 + HistoricTaskInstance targetTask = findFirst(endTaskList,
  962 + item -> item.getTaskDefinitionKey().equals(targetElement.getId()));
  963 + endTaskList.forEach(item -> {
  964 + FlowElement element = getFlowElementById(bpmnModel, item.getTaskDefinitionKey());
  965 + // 如果已结束的任务在回退目标节点之后生成,且串行可达,则标记为需要预算节点。
  966 + // TODO 串行可达的方法需要和判断可回退节点 validateTargetTaskCanReturn 分开吗? 并行网关可能会有问题。
  967 + if (targetTask != null && DateUtil.compare(item.getCreateTime(), targetTask.getCreateTime()) > 0
  968 + && BpmnModelUtils.isSequentialReachable(element, targetElement, null)) {
  969 + taskDefinitionKeys.add(item.getTaskDefinitionKey());
  970 + }
  971 + });
  972 + return taskDefinitionKeys;
  973 + }
  974 +
941 975 @Override
942 976 @Transactional(rollbackFor = Exception.class)
943 977 public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) {
... ... @@ -1449,9 +1483,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1449 1483 return;
1450 1484 }
1451 1485 FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
1452   - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略
1453   - // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
1454   - Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
  1486 + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略, 使用 local variable
  1487 + Boolean returnTaskFlag = runtimeService.getVariableLocal(task.getExecutionId(),
1455 1488 String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
1456 1489 Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
1457 1490 PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
... ...