Commit b88c09f48d42d8c4d7cd4d081d7ad43d17f8da11

Authored by jason
2 parents cbdc081c ceba5b8c

Merge remote-tracking branch 'origin/feature/bpm' into feature/bpm

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());