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

# Conflicts:
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
This commit is contained in:
jason 2025-02-22 22:53:49 +08:00
commit 8c681a77f5
14 changed files with 386 additions and 31 deletions

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 当子流程发起人为空时类型枚举
*
* @author Lesan
*/
@Getter
@AllArgsConstructor
public enum BpmChildProcessStartUserEmptyTypeEnum implements ArrayValuable<Integer> {
MAIN_PROCESS_START_USER(1, "同主流程发起人"),
CHILD_PROCESS_ADMIN(2, "子流程管理员"),
MAIN_PROCESS_ADMIN(3, "主流程管理员");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserEmptyTypeEnum::getType).toArray(Integer[]::new);
public static BpmChildProcessStartUserEmptyTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 子流程发起人类型枚举
*
* @author Lesan
*/
@Getter
@AllArgsConstructor
public enum BpmChildProcessStartUserTypeEnum implements ArrayValuable<Integer> {
MAIN_PROCESS_START_USER(1, "同主流程发起人"),
FROM_FORM(2, "表单");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserTypeEnum::getType).toArray(Integer[]::new);
public static BpmChildProcessStartUserTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -30,6 +30,9 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable<Integer> {
DELAY_TIMER_NODE(14, "延迟器", "receiveTask"), DELAY_TIMER_NODE(14, "延迟器", "receiveTask"),
TRIGGER_NODE(15, "触发器", "serviceTask"), TRIGGER_NODE(15, "触发器", "serviceTask"),
CHILD_PROCESS(20, "子流程", "callActivity"), // TODO @lesanCHILD_PROCESSASYNC_CHILD_PROCESS 可以合并为一个么
ASYNC_CHILD_PROCESS(21, "异步子流程", "callActivity"),
// 50 ~ 条件分支 // 50 ~ 条件分支
CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),

View File

@ -26,6 +26,7 @@ public enum BpmReasonEnum {
TIMEOUT_REJECT("审批超时,系统自动不通过"), TIMEOUT_REJECT("审批超时,系统自动不通过"),
ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE("发起人节点首次自动通过"), // 目前仅子流程使用
ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),

View File

@ -11,6 +11,7 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import org.flowable.bpmn.model.IOParameter;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
import java.util.List; import java.util.List;
@ -131,6 +132,11 @@ public class BpmSimpleModelNodeVO {
private String attachNodeId; // 目前用于异步触发器节点需要 UserTask ReceiveTask附加节点) 来完成 private String attachNodeId; // 目前用于异步触发器节点需要 UserTask ReceiveTask附加节点) 来完成
/**
* 子流程设置
*/
private ChildProcessSetting childProcessSetting;
@Schema(description = "任务监听器") @Schema(description = "任务监听器")
@Valid @Valid
@Data @Data
@ -413,4 +419,61 @@ public class BpmSimpleModelNodeVO {
private Set<String> deleteFields; private Set<String> deleteFields;
} }
} }
@Schema(description = "子流程节点配置")
@Data
@Valid
public static class ChildProcessSetting {
// TODO @lesancalledElement => calledProcessDefinitionKey ? 这样更容易理解不过如果一个流程多次发起key 变了好像会有问题
@Schema(description = "被调用流程", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx")
@NotEmpty(message = "被调用流程不能为空")
private String calledElement;
@Schema(description = "被调用流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx")
@NotEmpty(message = "被调用流程名称不能为空")
private String calledElementName;
@Schema(description = "是否异步", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
@NotNull(message = "是否异步不能为空")
private Boolean async;
// TODO @lesaninVariables
@Schema(description = "输入参数(主->子)", requiredMode = Schema.RequiredMode.REQUIRED, example = "[]")
private List<IOParameter> inVariable;
// TODO @lesanoutVariables
@Schema(description = "输出参数(子->主)", example = "[]")
private List<IOParameter> outVariable;
@Schema(description = "是否自动跳过子流程发起节点", example = "false")
@NotNull(message = "是否自动跳过子流程发起节点不能为空")
private Boolean skipStartUserNode;
@Schema(description = "子流程发起人配置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
// TODO @lesan这个应该也必须填写
private StartUserSetting startUserSetting;
@Schema(description = "子流程发起人配置")
@Data
@Valid
public static class StartUserSetting {
@Schema(description = "子流程发起人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "子流程发起人类型")
@InEnum(BpmChildProcessStartUserTypeEnum.class)
private Integer type;
@Schema(description = "表单", example = "xxx")
private String formField;
// TODO @lesanemptyHandleType => emptyType type 对上
@Schema(description = "当子流程发起人为空时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "当子流程发起人为空时类型不能为空")
@InEnum(BpmChildProcessStartUserEmptyTypeEnum.class)
private Integer emptyHandleType;
}
}
} }

View File

@ -51,6 +51,13 @@ public class BpmnVariableConstants {
*/ */
public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; public static final String PROCESS_INSTANCE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
/**
* 流程实例的变量 - 用于判断流程是否需要跳过发起人节点
*
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE = "PROCESS_SKIP_START_USER_NODE";
/** /**
* 流程实例的变量 - 流程开始时间 * 流程实例的变量 - 流程开始时间
* *

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@ -24,7 +25,10 @@ import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskInfo;
import java.util.*; import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -240,7 +244,8 @@ public class FlowableUtils {
return formFieldsMap.entrySet().stream() return formFieldsMap.entrySet().stream()
.limit(3) .limit(3)
.map(entry -> new KeyValue<>(entry.getValue().getTitle(), .map(entry -> new KeyValue<>(entry.getValue().getTitle(),
processVariables.getOrDefault(entry.getValue().getField(), "").toString())) // TODO @lesan MapUtil.getStr 可以更简单
StrUtil.toStringOrEmpty(processVariables.getOrDefault(entry.getValue().getField(), ""))))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@ -5,17 +5,22 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*; import cn.hutool.core.util.*;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.enums.definition.*;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 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.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmTriggerTaskDelegate;
import cn.iocoder.yudao.module.bpm.service.task.listener.BpmCallActivityListener;
import cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener;
import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.engine.delegate.TaskListener; import org.flowable.engine.delegate.TaskListener;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@ -24,7 +29,6 @@ import java.util.*;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum.ASYNC_HTTP_REQUEST; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum.ASYNC_HTTP_REQUEST;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
import static cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener.DELEGATE_EXPRESSION;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
/** /**
@ -43,7 +47,8 @@ public class SimpleModelUtils {
List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(), List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(),
new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(), new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), new TransactorNodeConvert(),
new DelayTimerNodeConvert(), new TriggerNodeConvert(), new DelayTimerNodeConvert(), new TriggerNodeConvert(),
new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert()); new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert(), new RouteBranchNodeConvert(),
new ChildProcessConvert(), new AsyncChildProcessConvert());
converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert));
} }
@ -478,7 +483,7 @@ public class SimpleModelUtils {
FlowableListener flowableListener = new FlowableListener(); FlowableListener flowableListener = new FlowableListener();
flowableListener.setEvent(TaskListener.EVENTNAME_CREATE); flowableListener.setEvent(TaskListener.EVENTNAME_CREATE);
flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
flowableListener.setImplementation(DELEGATE_EXPRESSION); flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
addListenerConfig(flowableListener, node.getTaskCreateListener()); addListenerConfig(flowableListener, node.getTaskCreateListener());
flowableListeners.add(flowableListener); flowableListeners.add(flowableListener);
} }
@ -487,7 +492,7 @@ public class SimpleModelUtils {
FlowableListener flowableListener = new FlowableListener(); FlowableListener flowableListener = new FlowableListener();
flowableListener.setEvent(TaskListener.EVENTNAME_ASSIGNMENT); flowableListener.setEvent(TaskListener.EVENTNAME_ASSIGNMENT);
flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
flowableListener.setImplementation(DELEGATE_EXPRESSION); flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
addListenerConfig(flowableListener, node.getTaskAssignListener()); addListenerConfig(flowableListener, node.getTaskAssignListener());
flowableListeners.add(flowableListener); flowableListeners.add(flowableListener);
} }
@ -496,7 +501,7 @@ public class SimpleModelUtils {
FlowableListener flowableListener = new FlowableListener(); FlowableListener flowableListener = new FlowableListener();
flowableListener.setEvent(TaskListener.EVENTNAME_COMPLETE); flowableListener.setEvent(TaskListener.EVENTNAME_COMPLETE);
flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
flowableListener.setImplementation(DELEGATE_EXPRESSION); flowableListener.setImplementation(BpmUserTaskListener.DELEGATE_EXPRESSION);
addListenerConfig(flowableListener, node.getTaskCompleteListener()); addListenerConfig(flowableListener, node.getTaskCompleteListener());
flowableListeners.add(flowableListener); flowableListeners.add(flowableListener);
} }
@ -809,6 +814,88 @@ public class SimpleModelUtils {
} }
private static class ChildProcessConvert implements NodeConvert {
@Override
public CallActivity convert(BpmSimpleModelNodeVO node) {
BpmSimpleModelNodeVO.ChildProcessSetting childProcessSetting = node.getChildProcessSetting();
List<IOParameter> inVariable = childProcessSetting.getInVariable() == null ?
new ArrayList<>() : new ArrayList<>(childProcessSetting.getInVariable());
CallActivity callActivity = new CallActivity();
callActivity.setId(node.getId());
callActivity.setName(node.getName());
callActivity.setCalledElementType("key"); // TODO @lesan这里为啥是 key
// 1. 是否异步
callActivity.setAsynchronous(node.getChildProcessSetting().getAsync());
// 2. 调用的子流程
callActivity.setCalledElement(childProcessSetting.getCalledElement());
callActivity.setProcessInstanceName(childProcessSetting.getCalledElementName());
// 3. 是否自动跳过子流程发起节点
// TODO @lesan貌似只有 SourceExpression 的区别直接通过 valueOf childProcessSetting.getSkipStartUserNode()
if (Boolean.TRUE.equals(childProcessSetting.getSkipStartUserNode())) {
IOParameter ioParameter = new IOParameter();
ioParameter.setSourceExpression("true");
ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
inVariable.add(ioParameter);
} else {
IOParameter ioParameter = new IOParameter();
ioParameter.setSourceExpression("false");
ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE);
inVariable.add(ioParameter);
}
// 4. 子变量传递
// 4.1 默认需要传递的一些变量流程状态
// TODO @lesan4.1 这个要不单独一个序号类似 3. 这个然后下面就是把 子变量传递主变量传递这样逻辑连贯点哈
IOParameter ioParameter = new IOParameter();
ioParameter.setSource(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
ioParameter.setTarget(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
inVariable.add(ioParameter);
callActivity.setInParameters(inVariable);
// 5. 主变量传递
// TODO @lesan通过 isNotEmpty 这种哈
if (childProcessSetting.getOutVariable() != null && !childProcessSetting.getOutVariable().isEmpty()) {
callActivity.setOutParameters(childProcessSetting.getOutVariable());
}
// 6. 子流程发起人配置
List<FlowableListener> executionListeners = new ArrayList<>();
FlowableListener flowableListener = new FlowableListener();
flowableListener.setEvent(ExecutionListener.EVENTNAME_START);
flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
flowableListener.setImplementation(BpmCallActivityListener.DELEGATE_EXPRESSION);
FieldExtension fieldExtension = new FieldExtension();
fieldExtension.setFieldName("listenerConfig");
fieldExtension.setStringValue(JsonUtils.toJsonString(childProcessSetting.getStartUserSetting()));
flowableListener.getFieldExtensions().add(fieldExtension);
executionListeners.add(flowableListener);
callActivity.setExecutionListeners(executionListeners);
// 添加节点类型
addNodeType(node.getType(), callActivity);
return callActivity;
}
@Override
public BpmSimpleModelNodeTypeEnum getType() {
return BpmSimpleModelNodeTypeEnum.CHILD_PROCESS;
}
}
private static class AsyncChildProcessConvert extends ChildProcessConvert {
@Override
public BpmSimpleModelNodeTypeEnum getType() {
return BpmSimpleModelNodeTypeEnum.ASYNC_CHILD_PROCESS;
}
}
private static String buildGatewayJoinId(String id) { private static String buildGatewayJoinId(String id) {
return id + "_join"; return id + "_join";
} }
@ -838,6 +925,8 @@ public class SimpleModelUtils {
|| nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE || nodeType == BpmSimpleModelNodeTypeEnum.APPROVE_NODE
|| nodeType == BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE || nodeType == BpmSimpleModelNodeTypeEnum.TRANSACTOR_NODE
|| nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
|| nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
|| nodeType == BpmSimpleModelNodeTypeEnum.ASYNC_CHILD_PROCESS
|| nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
// 添加元素 // 添加元素
resultNodes.add(currentNode); resultNodes.add(currentNode);

View File

@ -160,10 +160,10 @@ public interface BpmProcessInstanceService {
/** /**
* 删除 ProcessInstance 的变量 * 删除 ProcessInstance 的变量
* *
* @param processInstanceId 流程编号 * @param id 流程编号
* @param variableNames 流程变量名 * @param variableNames 流程变量名
*/ */
void removeProcessInstanceVariables(String processInstanceId, Collection<String> variableNames); void removeProcessInstanceVariables(String id, Collection<String> variableNames);
// ========== Event 事件相关方法 ========== // ========== Event 事件相关方法 ==========

View File

@ -320,6 +320,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 遍历 tasks 列表只处理已结束的 UserTask // 遍历 tasks 列表只处理已结束的 UserTask
// 为什么不通过 activities 因为加签场景下它只存在于 tasks没有 activities导致如果遍历 activities // 为什么不通过 activities 因为加签场景下它只存在于 tasks没有 activities导致如果遍历 activities
// 的话它无法成为一个节点 // 的话它无法成为一个节点
// TODO @芋艿子流程只有activity这里获取不到已结束的子流程TODO @lesan这个会有啥影响微信聊
List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null);
List<ActivityNode> approvalNodes = convertList(endTasks, task -> { List<ActivityNode> approvalNodes = convertList(endTasks, task -> {
FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
@ -388,9 +389,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
Map<String, Object> processVariables, Map<String, Object> processVariables,
List<HistoricActivityInstance> activities, List<HistoricActivityInstance> activities,
List<HistoricTaskInstance> tasks) { List<HistoricTaskInstance> tasks) {
// 构建运行中的任务基于 activityId 分组 // 构建运行中的任务子流程基于 activityId 分组
List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null
&& (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER))); && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER, ELEMENT_CALL_ACTIVITY)));
Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities,
HistoricActivityInstance::getActivityId); HistoricActivityInstance::getActivityId);
@ -404,7 +405,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务会签/或签的任务开始时间相同 HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务会签/或签的任务开始时间相同
ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()) ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId())
.setName(firstActivity.getActivityName()) .setName(firstActivity.getActivityName())
.setNodeType(ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的解决办理节点的识别 .setNodeType(ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的解决办理节点"子流程"的识别
BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())) BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
.setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
.setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
@ -413,6 +414,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 处理每个任务的 tasks 属性 // 处理每个任务的 tasks 属性
for (HistoricActivityInstance activity : taskActivities) { for (HistoricActivityInstance activity : taskActivities) {
HistoricTaskInstance task = taskMap.get(activity.getTaskId()); HistoricTaskInstance task = taskMap.get(activity.getTaskId());
// TODO @lesan这里为啥 continue
if (task == null) {
continue;
}
activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task));
// 加签子任务需要过滤掉已经完成的加签子任务 // 加签子任务需要过滤掉已经完成的加签子任务
List<HistoricTaskInstance> childrenTasks = filterList( List<HistoricTaskInstance> childrenTasks = filterList(
@ -503,6 +508,12 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
activityNode.setCandidateUserIds(candidateUserIds); activityNode.setCandidateUserIds(candidateUserIds);
return activityNode; return activityNode;
} }
// 4. 子流程节点
if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(node.getType()) ||
BpmSimpleModelNodeTypeEnum.ASYNC_CHILD_PROCESS.getType().equals(node.getType())) {
return activityNode;
}
return null; return null;
} }
@ -790,8 +801,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
} }
@Override @Override
public void removeProcessInstanceVariables(String processInstanceId, Collection<String> variableNames) { public void removeProcessInstanceVariables(String id, Collection<String> variableNames) {
runtimeService.removeVariables(processInstanceId, variableNames); runtimeService.removeVariables(id, variableNames);
} }
// ========== Event 事件相关方法 ========== // ========== Event 事件相关方法 ==========

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.service.task; package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.*; import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
@ -63,6 +64,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
/** /**
@ -1212,19 +1214,31 @@ public class BpmTaskServiceImpl implements BpmTaskService {
} }
} }
// 审批人与提交人为同一人时根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 // 获取发起人节点
if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
// 判断是否为退回或者驳回如果是退回或者驳回不走这个策略
// TODO 芋艿优化未来有没更好的判断方式另外还要考虑清理机制就是说下次处理了之后就移除这个标识
Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId());
if (bpmnModel == null) { if (bpmnModel == null) {
log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId());
return; return;
} }
FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
// 判断是否为退回或者驳回如果是退回或者驳回不走这个策略
// TODO 芋艿优化未来有没更好的判断方式另外还要考虑清理机制就是说下次处理了之后就移除这个标识
Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);
Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
if (userTaskElement.getId().equals(START_USER_NODE_ID)
&& (skipStartUserNodeFlag == null // 目的一般是主流程发起人节点自动通过审核
|| Boolean.TRUE.equals(skipStartUserNodeFlag)) // 目的一般是子流程发起人节点按配置自动通过审核
&& !Boolean.TRUE.equals(returnTaskFlag)) { // TODO @lesanObjUtil.notEqual(returnTaskFlag, Boolean.TRUE) 改成这个有问题么尽量不用 ! 取反
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
return;
}
// 当不为发起人节点时审批人与提交人为同一人时根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
if (ObjectUtil.notEqual(userTaskElement.getId(), START_USER_NODE_ID)
&& StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement);
// 情况一自动跳过 // 情况一自动跳过

View File

@ -0,0 +1,91 @@
package cn.iocoder.yudao.module.bpm.service.task.listener;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserEmptyTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmChildProcessStartUserTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import jakarta.annotation.Resource;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.engine.impl.el.FixedValue;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* BPM 子流程监听器设置流程的发起人
*
* @author Lesan
*/
@Component
@Slf4j
public class BpmCallActivityListener implements ExecutionListener {
public static final String DELEGATE_EXPRESSION = "${bpmCallActivityListener}";
@Setter
private FixedValue listenerConfig;
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Override
public void notify(DelegateExecution execution) {
String expressionText = listenerConfig.getExpressionText();
Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText);
BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting startUserSetting = JsonUtils.parseObject(
expressionText, BpmSimpleModelNodeVO.ChildProcessSetting.StartUserSetting.class);
// 1. 当发起人来源为主流程发起人时并兜底 startUserSetting 为空时
if (startUserSetting == null
|| startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER.getType())) {
ExecutionEntity parent = (ExecutionEntity) execution.getParent();
FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
return;
}
// 2. 当发起人来源为表单时
if (startUserSetting.getType().equals(BpmChildProcessStartUserTypeEnum.FROM_FORM.getType())) {
ExecutionEntity parent = (ExecutionEntity) execution.getParent();
String formFieldValue = parent.getVariable(startUserSetting.getFormField(), String.class);
// 2.1 当表单值为空时
if (StrUtil.isEmpty(formFieldValue)) {
// 2.1.1 来自主流程发起人
if (startUserSetting.getEmptyHandleType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER.getType())) {
FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
return;
}
// 2.1.2 来自子流程管理员
if (startUserSetting.getEmptyHandleType().equals(BpmChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN.getType())) {
BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId());
List<Long> managerUserIds = processDefinition.getManagerUserIds();
FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0));
return;
}
// 2.1.3 来自主流程管理员
if (startUserSetting.getEmptyHandleType().equals(BpmChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN.getType())) {
BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(parent.getProcessDefinitionId());
List<Long> managerUserIds = processDefinition.getManagerUserIds();
FlowableUtils.setAuthenticatedUserId(managerUserIds.get(0));
return;
}
}
// 2.2 使用表单值并兜底字符串转 Long 失败时使用主流程发起人
try {
FlowableUtils.setAuthenticatedUserId(Long.parseLong(formFieldValue));
} catch (Exception e) {
// todo @lesan打个日志方便排查
FlowableUtils.setAuthenticatedUserId(Long.parseLong(parent.getStartUserId()));
}
}
}
}

View File

@ -46,7 +46,7 @@ public class BpmFormDeleteTrigger implements BpmTrigger {
// 2. 获取流程变量 // 2. 获取流程变量
Map<String, Object> processVariables = processInstanceService.getProcessInstance(processInstanceId).getProcessVariables(); Map<String, Object> processVariables = processInstanceService.getProcessInstance(processInstanceId).getProcessVariables();
// 3.获取需要删除的表单字段 // 3.1 获取需要删除的表单字段
Set<String> deleteFields = new HashSet<>(); Set<String> deleteFields = new HashSet<>();
settings.forEach(setting -> { settings.forEach(setting -> {
if (CollUtil.isEmpty(setting.getDeleteFields())) { if (CollUtil.isEmpty(setting.getDeleteFields())) {
@ -64,7 +64,7 @@ public class BpmFormDeleteTrigger implements BpmTrigger {
} }
}); });
// 4. 删除流程变量 // 3.2 删除流程变量
if (CollUtil.isNotEmpty(deleteFields)) { if (CollUtil.isNotEmpty(deleteFields)) {
processInstanceService.removeProcessInstanceVariables(processInstanceId, deleteFields); processInstanceService.removeProcessInstanceVariables(processInstanceId, deleteFields);
} }