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

This commit is contained in:
jason 2025-02-21 22:42:10 +08:00
commit b88c09f48d
5 changed files with 47 additions and 23 deletions

View File

@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task; package cn.iocoder.yudao.module.bpm.controller.admin.task;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
@ -162,7 +164,11 @@ public class BpmProcessInstanceController {
@Operation(summary = "获得审批详情") @Operation(summary = "获得审批详情")
@Parameter(name = "id", description = "流程实例的编号", required = true) @Parameter(name = "id", description = "流程实例的编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
@SuppressWarnings("unchecked")
public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) {
if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
}
return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
} }

View File

@ -18,6 +18,9 @@ public class BpmApprovalDetailReqVO {
@Schema(description = "流程变量") @Schema(description = "流程变量")
private Map<String, Object> processVariables; // 使用场景 processDefinitionId用于流程预测 private Map<String, Object> processVariables; // 使用场景 processDefinitionId用于流程预测
@Schema(description = "流程变量")
private String processVariablesStr; // 解决 GET 无法传递对象的问题最终转换成 processVariables 变量
@Schema(description = "流程实例的编号", example = "1024") @Schema(description = "流程实例的编号", example = "1024")
private String processInstanceId; // 使用场景流程已发起时候传流程实例 ID private String processInstanceId; // 使用场景流程已发起时候传流程实例 ID

View File

@ -800,7 +800,7 @@ public class BpmnModelUtils {
Gateway gateway = (Gateway) currentElement; Gateway gateway = (Gateway) currentElement;
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& evalConditionExpress(variables, flow.getConditionExpression())); && (evalConditionExpress(variables, flow.getConditionExpression())));
if (matchSequenceFlow == null) { if (matchSequenceFlow == null) {
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())); flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
@ -850,18 +850,25 @@ public class BpmnModelUtils {
* 计算条件表达式是否为 true 满足条件 * 计算条件表达式是否为 true 满足条件
* *
* @param variables 流程实例 * @param variables 流程实例
* @param express 条件表达式 * @param expression 条件表达式
* @return 是否满足条件 * @return 是否满足条件
*/ */
public static boolean evalConditionExpress(Map<String, Object> variables, String express) { public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
if (express == null) { if (expression == null) {
return Boolean.FALSE; return Boolean.FALSE;
} }
// 如果 variables 为空则创建一个的原因可能 expression 的计算不依赖于 variables
if (variables == null) {
variables = new HashMap<>();
}
// 执行计算
try { try {
Object result = FlowableUtils.getExpressionValue(variables, express); Object result = FlowableUtils.getExpressionValue(variables, expression);
return Boolean.TRUE.equals(result); return Boolean.TRUE.equals(result);
} catch (FlowableException ex) { } catch (FlowableException ex) {
log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", express, variables, ex); // 为什么使用 info 日志原因是expression 如果从 variables 取不到值会报错实际这种情况下可以忽略
log.info("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错]", expression, variables, ex);
return Boolean.FALSE; return Boolean.FALSE;
} }
} }

View File

@ -29,7 +29,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
@ -210,7 +210,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// TODO @jason有一个极端情况如果一个用户有 2 task A BA 已经通过B 需要审核这个时通过 A 进来todo 拿到 // TODO @jason有一个极端情况如果一个用户有 2 task A BA 已经通过B 需要审核这个时通过 A 进来todo 拿到
// B会不会表单权限不一致哈 // B会不会表单权限不一致哈
BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId()); BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId());
// 3.2 预测未运行节点的审批信息 // 3.2 预测未运行节点的审批信息
List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
processDefinitionInfo, processDefinitionInfo,
@ -499,6 +498,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 3. 抄送节点 // 3. 抄送节点
if (CollUtil.isEmpty(runActivityIds) && // 流程发起时需要展示抄送节点用于选择抄送人 if (CollUtil.isEmpty(runActivityIds) && // 流程发起时需要展示抄送节点用于选择抄送人
BpmSimpleModelNodeTypeEnum.COPY_NODE.getType().equals(node.getType())) { BpmSimpleModelNodeTypeEnum.COPY_NODE.getType().equals(node.getType())) {
List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(),
startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables);
activityNode.setCandidateUserIds(candidateUserIds);
return activityNode; return activityNode;
} }
return null; return null;
@ -648,7 +650,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
throw exception(PROCESS_INSTANCE_START_USER_CAN_START); throw exception(PROCESS_INSTANCE_START_USER_CAN_START);
} }
// 1.3 校验发起人自选审批人 // 1.3 校验发起人自选审批人
validateStartUserSelectAssignees(definition, startUserSelectAssignees); validateStartUserSelectAssignees(userId, definition, startUserSelectAssignees, variables);
// 2. 创建流程实例 // 2. 创建流程实例
if (variables == null) { if (variables == null) {
@ -693,17 +695,23 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
return instance.getId(); return instance.getId();
} }
private void validateStartUserSelectAssignees(ProcessDefinition definition, private void validateStartUserSelectAssignees(Long userId, ProcessDefinition definition,
Map<String, List<Long>> startUserSelectAssignees) { Map<String, List<Long>> startUserSelectAssignees,
// 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表 Map<String,Object> variables) {
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); // 1. 获取预测的节点信息
List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel); BpmApprovalDetailRespVO detailRespVO = getApprovalDetail(userId, new BpmApprovalDetailReqVO()
if (CollUtil.isEmpty(tasks)) { .setProcessDefinitionId(definition.getId())
.setProcessVariables(variables));
List<ActivityNode> activityNodes = detailRespVO.getActivityNodes();
if (CollUtil.isEmpty(activityNodes)){
return; return;
} }
// 2. 校验发起人自选审批人的审批人和抄送人是否都配置了 // 2.1 移除掉不是发起人自选审批人节点
tasks.forEach(task -> { activityNodes.removeIf(task ->
ObjectUtil.notEqual(BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy(), task.getCandidateStrategy()));
// 2.2 流程发起时要先获取当前流程的预测走向节点发起时只校验预测的节点发起人自选审批人的审批人和抄送人是否都配置了
activityNodes.forEach(task -> {
List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null; List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null;
if (CollUtil.isEmpty(assignees)) { if (CollUtil.isEmpty(assignees)) {
throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName()); throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName());