Commit b88c09f48d42d8c4d7cd4d081d7ad43d17f8da11
Merge remote-tracking branch 'origin/feature/bpm' into feature/bpm
Showing
5 changed files
with
47 additions
and
23 deletions
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
| 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task; | 1 | package cn.iocoder.yudao.module.bpm.controller.admin.task; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 6 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | 8 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 7 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; | 9 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; |
| 8 | import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; | 10 | import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; |
| @@ -162,7 +164,11 @@ public class BpmProcessInstanceController { | @@ -162,7 +164,11 @@ public class BpmProcessInstanceController { | ||
| 162 | @Operation(summary = "获得审批详情") | 164 | @Operation(summary = "获得审批详情") |
| 163 | @Parameter(name = "id", description = "流程实例的编号", required = true) | 165 | @Parameter(name = "id", description = "流程实例的编号", required = true) |
| 164 | @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") | 166 | @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") |
| 167 | + @SuppressWarnings("unchecked") | ||
| 165 | public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { | 168 | public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { |
| 169 | + if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) { | ||
| 170 | + reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class)); | ||
| 171 | + } | ||
| 166 | return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); | 172 | return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); |
| 167 | } | 173 | } |
| 168 | 174 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
| @@ -18,6 +18,9 @@ public class BpmApprovalDetailReqVO { | @@ -18,6 +18,9 @@ public class BpmApprovalDetailReqVO { | ||
| 18 | @Schema(description = "流程变量") | 18 | @Schema(description = "流程变量") |
| 19 | private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测 | 19 | private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测 |
| 20 | 20 | ||
| 21 | + @Schema(description = "流程变量") | ||
| 22 | + private String processVariablesStr; // 解决 GET 无法传递对象的问题,最终转换成 processVariables 变量 | ||
| 23 | + | ||
| 21 | @Schema(description = "流程实例的编号", example = "1024") | 24 | @Schema(description = "流程实例的编号", example = "1024") |
| 22 | private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID | 25 | private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID |
| 23 | 26 |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
| @@ -800,7 +800,7 @@ public class BpmnModelUtils { | @@ -800,7 +800,7 @@ public class BpmnModelUtils { | ||
| 800 | Gateway gateway = (Gateway) currentElement; | 800 | Gateway gateway = (Gateway) currentElement; |
| 801 | SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), | 801 | SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), |
| 802 | flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) | 802 | flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) |
| 803 | - && evalConditionExpress(variables, flow.getConditionExpression())); | 803 | + && (evalConditionExpress(variables, flow.getConditionExpression()))); |
| 804 | if (matchSequenceFlow == null) { | 804 | if (matchSequenceFlow == null) { |
| 805 | matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), | 805 | matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), |
| 806 | flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())); | 806 | flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())); |
| @@ -821,8 +821,8 @@ public class BpmnModelUtils { | @@ -821,8 +821,8 @@ public class BpmnModelUtils { | ||
| 821 | // 查找满足条件的 SequenceFlow 路径 | 821 | // 查找满足条件的 SequenceFlow 路径 |
| 822 | Gateway gateway = (Gateway) currentElement; | 822 | Gateway gateway = (Gateway) currentElement; |
| 823 | Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), | 823 | Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), |
| 824 | - flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) | ||
| 825 | - && evalConditionExpress(variables, flow.getConditionExpression())); | 824 | + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) |
| 825 | + && evalConditionExpress(variables, flow.getConditionExpression())); | ||
| 826 | if (CollUtil.isEmpty(matchSequenceFlows)) { | 826 | if (CollUtil.isEmpty(matchSequenceFlows)) { |
| 827 | matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), | 827 | matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), |
| 828 | flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())); | 828 | flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())); |
| @@ -850,18 +850,25 @@ public class BpmnModelUtils { | @@ -850,18 +850,25 @@ public class BpmnModelUtils { | ||
| 850 | * 计算条件表达式是否为 true 满足条件 | 850 | * 计算条件表达式是否为 true 满足条件 |
| 851 | * | 851 | * |
| 852 | * @param variables 流程实例 | 852 | * @param variables 流程实例 |
| 853 | - * @param express 条件表达式 | 853 | + * @param expression 条件表达式 |
| 854 | * @return 是否满足条件 | 854 | * @return 是否满足条件 |
| 855 | */ | 855 | */ |
| 856 | - public static boolean evalConditionExpress(Map<String, Object> variables, String express) { | ||
| 857 | - if (express == null) { | 856 | + public static boolean evalConditionExpress(Map<String, Object> variables, String expression) { |
| 857 | + if (expression == null) { | ||
| 858 | return Boolean.FALSE; | 858 | return Boolean.FALSE; |
| 859 | } | 859 | } |
| 860 | + // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables | ||
| 861 | + if (variables == null) { | ||
| 862 | + variables = new HashMap<>(); | ||
| 863 | + } | ||
| 864 | + | ||
| 865 | + // 执行计算 | ||
| 860 | try { | 866 | try { |
| 861 | - Object result = FlowableUtils.getExpressionValue(variables, express); | 867 | + Object result = FlowableUtils.getExpressionValue(variables, expression); |
| 862 | return Boolean.TRUE.equals(result); | 868 | return Boolean.TRUE.equals(result); |
| 863 | } catch (FlowableException ex) { | 869 | } catch (FlowableException ex) { |
| 864 | - log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", express, variables, ex); | 870 | + // 为什么使用 info 日志?原因是,expression 如果从 variables 取不到值,会报错。实际这种情况下,可以忽略 |
| 871 | + log.info("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", expression, variables, ex); | ||
| 865 | return Boolean.FALSE; | 872 | return Boolean.FALSE; |
| 866 | } | 873 | } |
| 867 | } | 874 | } |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
| @@ -811,8 +811,8 @@ public class SimpleModelUtils { | @@ -811,8 +811,8 @@ public class SimpleModelUtils { | ||
| 811 | if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) { | 811 | if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) { |
| 812 | // 查找满足条件的 BpmSimpleModelNodeVO 节点 | 812 | // 查找满足条件的 BpmSimpleModelNodeVO 节点 |
| 813 | BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), | 813 | BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), |
| 814 | - conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) | ||
| 815 | - && evalConditionExpress(variables, conditionNode.getConditionSetting())); | 814 | + conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) |
| 815 | + && evalConditionExpress(variables, conditionNode.getConditionSetting())); | ||
| 816 | if (matchConditionNode == null) { | 816 | if (matchConditionNode == null) { |
| 817 | matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), | 817 | matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), |
| 818 | conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); | 818 | conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); |
| @@ -826,8 +826,8 @@ public class SimpleModelUtils { | @@ -826,8 +826,8 @@ public class SimpleModelUtils { | ||
| 826 | if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { | 826 | if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { |
| 827 | // 查找满足条件的 BpmSimpleModelNodeVO 节点 | 827 | // 查找满足条件的 BpmSimpleModelNodeVO 节点 |
| 828 | Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), | 828 | Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), |
| 829 | - conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) | ||
| 830 | - && evalConditionExpress(variables, conditionNode.getConditionSetting())); | 829 | + conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) |
| 830 | + && evalConditionExpress(variables, conditionNode.getConditionSetting())); | ||
| 831 | if (CollUtil.isEmpty(matchConditionNodes)) { | 831 | if (CollUtil.isEmpty(matchConditionNodes)) { |
| 832 | matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), | 832 | matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), |
| 833 | conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); | 833 | conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
| @@ -29,7 +29,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; | @@ -29,7 +29,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; | ||
| 29 | import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; | 29 | import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; |
| 30 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; | 30 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| 31 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; | 31 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| 32 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; | 32 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 33 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | 33 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| 34 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; | 34 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| 35 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; | 35 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; |
| @@ -210,7 +210,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -210,7 +210,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 210 | // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 | 210 | // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 |
| 211 | // B,会不会表单权限不一致哈。 | 211 | // B,会不会表单权限不一致哈。 |
| 212 | BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId()); | 212 | BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId()); |
| 213 | - | ||
| 214 | // 3.2 预测未运行节点的审批信息 | 213 | // 3.2 预测未运行节点的审批信息 |
| 215 | List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, | 214 | List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, |
| 216 | processDefinitionInfo, | 215 | processDefinitionInfo, |
| @@ -499,6 +498,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -499,6 +498,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 499 | // 3. 抄送节点 | 498 | // 3. 抄送节点 |
| 500 | if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人 | 499 | if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人 |
| 501 | BpmSimpleModelNodeTypeEnum.COPY_NODE.getType().equals(node.getType())) { | 500 | BpmSimpleModelNodeTypeEnum.COPY_NODE.getType().equals(node.getType())) { |
| 501 | + List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), | ||
| 502 | + startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); | ||
| 503 | + activityNode.setCandidateUserIds(candidateUserIds); | ||
| 502 | return activityNode; | 504 | return activityNode; |
| 503 | } | 505 | } |
| 504 | return null; | 506 | return null; |
| @@ -648,7 +650,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -648,7 +650,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 648 | throw exception(PROCESS_INSTANCE_START_USER_CAN_START); | 650 | throw exception(PROCESS_INSTANCE_START_USER_CAN_START); |
| 649 | } | 651 | } |
| 650 | // 1.3 校验发起人自选审批人 | 652 | // 1.3 校验发起人自选审批人 |
| 651 | - validateStartUserSelectAssignees(definition, startUserSelectAssignees); | 653 | + validateStartUserSelectAssignees(userId, definition, startUserSelectAssignees, variables); |
| 652 | 654 | ||
| 653 | // 2. 创建流程实例 | 655 | // 2. 创建流程实例 |
| 654 | if (variables == null) { | 656 | if (variables == null) { |
| @@ -693,17 +695,23 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -693,17 +695,23 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 693 | return instance.getId(); | 695 | return instance.getId(); |
| 694 | } | 696 | } |
| 695 | 697 | ||
| 696 | - private void validateStartUserSelectAssignees(ProcessDefinition definition, | ||
| 697 | - Map<String, List<Long>> startUserSelectAssignees) { | ||
| 698 | - // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表 | ||
| 699 | - BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); | ||
| 700 | - List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel); | ||
| 701 | - if (CollUtil.isEmpty(tasks)) { | 698 | + private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition, |
| 699 | + Map<String, List<Long>> startUserSelectAssignees, | ||
| 700 | + Map<String,Object> variables) { | ||
| 701 | + // 1. 获取预测的节点信息 | ||
| 702 | + BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO() | ||
| 703 | + .setProcessDefinitionId(definition.getId()) | ||
| 704 | + .setProcessVariables(variables)); | ||
| 705 | + List<ActivityNode> activityNodes = detailRespVO.getActivityNodes(); | ||
| 706 | + if (CollUtil.isEmpty(activityNodes)){ | ||
| 702 | return; | 707 | return; |
| 703 | } | 708 | } |
| 704 | 709 | ||
| 705 | - // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了 | ||
| 706 | - tasks.forEach(task -> { | 710 | + // 2.1 移除掉不是发起人自选审批人节点 |
| 711 | + activityNodes.removeIf(task -> | ||
| 712 | + ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy())); | ||
| 713 | + // 2.2 流程发起时要先获取当前流程的预测走向节点,发起时只校验预测的节点发起人自选审批人的审批人和抄送人是否都配置了 | ||
| 714 | + activityNodes.forEach(task -> { | ||
| 707 | List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null; | 715 | List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null; |
| 708 | if (CollUtil.isEmpty(assignees)) { | 716 | if (CollUtil.isEmpty(assignees)) { |
| 709 | throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName()); | 717 | throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName()); |