仿钉钉流程设计- 审批节点添加拒绝处理方式

This commit is contained in:
jason 2024-05-26 10:57:23 +08:00
parent d34fef67da
commit d2750f08ce
10 changed files with 254 additions and 48 deletions

View File

@ -5,20 +5,21 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
/** /**
* 定时器边界事件类型枚举 * BPM 边界事件 (boundary event) 自定义类型枚举
* *
* @author jason * @author jason
*/ */
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum BpmTimerBoundaryEventType { public enum BpmBoundaryEventType {
USER_TASK_TIMEOUT(1,"用户任务超时"); USER_TASK_TIMEOUT(1,"用户任务超时"),
USER_TASK_REJECT_POST_PROCESS(2, "用户任务拒绝后处理");
private final Integer type; private final Integer type;
private final String name; private final String name;
public static BpmTimerBoundaryEventType typeOf(Integer type) { public static BpmBoundaryEventType typeOf(Integer type) {
return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
} }
} }

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* BPM 用户任务拒绝处理类型枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskRejectHandlerType {
TERMINATION(1, "终止流程"),
RETURN_PRE_USER_TASK(2, "驳回到用户任务");
private final Integer type;
private final String name;
public static BpmUserTaskRejectHandlerType typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
}

View File

@ -30,15 +30,25 @@ public interface BpmnModelConstants {
*/ */
String USER_TASK_CANDIDATE_PARAM = "candidateParam"; String USER_TASK_CANDIDATE_PARAM = "candidateParam";
/**
* BPMN ExtensionElement 的扩展属性用于标记边界事件类型
*/
String BOUNDARY_EVENT_TYPE = "boundaryEventType";
/** /**
* BPMN ExtensionElement 的扩展属性用于标记用户任务超时执行动作 * BPMN ExtensionElement 的扩展属性用于标记用户任务超时执行动作
*/ */
String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction";
/** /**
* BPMN ExtensionElement 的扩展属性用于标记定时边界事件类型 * BPMN ExtensionElement 的扩展属性用于标记用户任务拒绝处理类型
*/ */
String TIMER_BOUNDARY_EVENT_TYPE = "timerBoundaryEventType"; String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType";
/**
* BPMN ExtensionElement 的扩展属性用于标记用户任务拒绝后的回退的任务 Id
*/
String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId";
/** /**
* BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限
@ -66,4 +76,5 @@ public interface BpmnModelConstants {
*/ */
Set<Class<? extends FlowNode>> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); Set<Class<? extends FlowNode>> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class);
String REJECT_POST_PROCESS_MESSAGE_NAME = "message_reject_post_process";
} }

View File

@ -2,23 +2,41 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskReturnReqVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BoundaryEvent;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent;
import org.flowable.engine.delegate.event.FlowableMessageEvent;
import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.task.api.Task; import org.flowable.task.api.Task;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseBoundaryEventExtensionElement;
/** /**
* 监听 {@link Task} 的开始与完成 * 监听 {@link Task} 的开始与完成
* *
@ -34,15 +52,22 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
@Resource @Resource
@Lazy // 解决循环依赖 @Lazy // 解决循环依赖
private BpmActivityService activityService; private BpmActivityService activityService;
@Resource
@Lazy // 解决循环依赖
private BpmProcessInstanceService processInstanceService;
@Resource
@Lazy // 延迟加载避免循环依赖
private BpmModelService bpmModelService;
public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
.add(FlowableEngineEventType.TASK_CREATED) .add(FlowableEngineEventType.TASK_CREATED)
.add(FlowableEngineEventType.TASK_ASSIGNED) .add(FlowableEngineEventType.TASK_ASSIGNED)
// .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时已经记录了 task status 为通过所以不需要监听了 //.add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时已经记录了 task status 为通过所以不需要监听了
.add(FlowableEngineEventType.ACTIVITY_MESSAGE_RECEIVED)
.add(FlowableEngineEventType.ACTIVITY_CANCELLED) .add(FlowableEngineEventType.ACTIVITY_CANCELLED)
.build(); .build();
public BpmTaskEventListener(){ public BpmTaskEventListener() {
super(TASK_EVENTS); super(TASK_EVENTS);
} }
@ -53,7 +78,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
@Override @Override
protected void taskAssigned(FlowableEngineEntityEvent event) { protected void taskAssigned(FlowableEngineEntityEvent event) {
taskService.updateTaskExtAssign((Task)event.getEntity()); taskService.updateTaskExtAssign((Task) event.getEntity());
} }
@Override @Override
@ -72,4 +97,47 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
}); });
} }
@Override
protected void activityMessageReceived(FlowableMessageEvent event) {
BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(event.getProcessDefinitionId());
FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, event.getActivityId());
if (element instanceof BoundaryEvent) {
BoundaryEvent boundaryEvent = (BoundaryEvent) element;
String boundaryEventType = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
// 如果自定义类型为拒绝后处理进行拒绝处理
if (Objects.equals(USER_TASK_REJECT_POST_PROCESS.getType(), NumberUtils.parseInt(boundaryEventType))) {
String rejectHandlerType = parseBoundaryEventExtensionElement((BoundaryEvent) element, BpmnModelConstants.USER_TASK_REJECT_HANDLER_TYPE);
rejectHandler(boundaryEvent, event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(rejectHandlerType));
}
}
}
private void rejectHandler(BoundaryEvent boundaryEvent, String processInstanceId, String taskDefineKey, Integer rejectHandlerType) {
BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
if (userTaskRejectHandlerType != null) {
List<Task> taskList = taskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefineKey);
taskList.forEach(task -> {
Integer taskStatus = FlowableUtils.getTaskStatus(task);
// 只有处于拒绝状态下才处理
if (Objects.equals(BpmTaskStatusEnum.REJECT.getStatus(), taskStatus)) {
// 终止流程
if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.TERMINATION) {
processInstanceService.updateProcessInstanceReject(task.getProcessInstanceId(), FlowableUtils.getTaskReason(task));
return;
}
// 驳回
if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK) {
String returnTaskId = parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_REJECT_RETURN_TASK_ID);
if (returnTaskId != null) {
BpmTaskReturnReqVO reqVO = new BpmTaskReturnReqVO().setId(task.getId())
.setTargetTaskDefinitionKey(returnTaskId)
.setReason("任务拒绝回退");
taskService.returnTask(getLoginUserId(), reqVO);
}
}
}
});
}
}
} }

View File

@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType; import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum;
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.mq.message.task.TodoTaskReminderMessage; import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage;
@ -18,7 +17,6 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BoundaryEvent; import org.flowable.bpmn.model.BoundaryEvent;
import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
@ -29,7 +27,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
/** /**
@ -69,23 +66,21 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe
// 如果是定时器边界事件 // 如果是定时器边界事件
if (element instanceof BoundaryEvent) { if (element instanceof BoundaryEvent) {
BoundaryEvent boundaryEvent = (BoundaryEvent) element; BoundaryEvent boundaryEvent = (BoundaryEvent) element;
ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.TIMER_BOUNDARY_EVENT_TYPE)); String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
Integer timerBoundaryEventType = NumberUtils.parseInt(Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null)); BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType));
BpmTimerBoundaryEventType bpmTimerBoundaryEventType = BpmTimerBoundaryEventType.typeOf(timerBoundaryEventType);
// 类型为用户任务超时未处理的情况 // 类型为用户任务超时未处理的情况
if (bpmTimerBoundaryEventType == BpmTimerBoundaryEventType.USER_TASK_TIMEOUT) { if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) {
ExtensionElement timeoutActionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION)); String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION);
Integer timeoutAction = NumberUtils.parseInt(Optional.ofNullable(timeoutActionElement).map(ExtensionElement::getElementText).orElse(null)); userTaskTimeoutHandler(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(timeoutAction));
processUserTaskTimeout(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), timeoutAction);
} }
} }
} }
private void processUserTaskTimeout(String processInstanceId, String taskDefKey, Integer timeoutAction) { private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) {
BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction);
if (userTaskTimeoutAction != null) { if (userTaskTimeoutAction != null) {
// 查询超时未处理的任务 // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ???
List<Task> taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, taskDefKey); List<Task> taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefKey);
taskList.forEach(task -> { taskList.forEach(task -> {
// 自动提醒 // 自动提醒
if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) {

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
@ -33,12 +34,15 @@ public class SimpleModelUserTaskConfig {
*/ */
private Integer approveMethod; private Integer approveMethod;
/** /**
* 超时处理 * 超时处理
*/ */
private TimeoutHandler timeoutHandler; private TimeoutHandler timeoutHandler;
/**
* 用户任务拒绝处理
*/
private RejectHandler rejectHandler;
@Data @Data
public static class TimeoutHandler { public static class TimeoutHandler {
@ -62,7 +66,20 @@ public class SimpleModelUserTaskConfig {
* 如果执行动作是自动提醒, 最大提醒次数 * 如果执行动作是自动提醒, 最大提醒次数
*/ */
private Integer maxRemindCount; private Integer maxRemindCount;
}
@Data
public static class RejectHandler {
/**
* 用户任务拒绝处理类型 {@link BpmUserTaskRejectHandlerType}
*/
private Integer type;
/**
* 用户任务拒绝后驳回的节点 Id
*/
private String returnNodeId;
} }
} }

View File

@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.Process;
@ -360,4 +361,32 @@ public class BpmnModelUtils {
return userTaskList; return userTaskList;
} }
/**
* 在用户任务中查找自定义的边界事件
*
* @param userTask 用户任务
* @param bpmBoundaryEventType 自定义的边界事件类型
*/
public static BoundaryEvent findCustomBoundaryEventOfUserTask(UserTask userTask, BpmBoundaryEventType bpmBoundaryEventType) {
if (userTask == null) {
return null;
}
BoundaryEvent result = null;
for (BoundaryEvent item : userTask.getBoundaryEvents()) {
String boundaryEventType = parseBoundaryEventExtensionElement(item, BpmnModelConstants.BOUNDARY_EVENT_TYPE);
if (Objects.equals(bpmBoundaryEventType.getType(), NumberUtils.parseInt(boundaryEventType))) {
result = item;
break;
}
}
return result;
}
public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
if (boundaryEvent == null) {
return null;
}
ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
}
} }

View File

@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
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.simple.SimpleModelConditionGroups; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig.RejectHandler;
import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
@ -22,13 +23,14 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType.USER_TASK_TIMEOUT;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType.USER_TASK_TIMEOUT; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType.RETURN_PRE_USER_TASK;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER;
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.enums.SimpleModelConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; import static org.flowable.bpmn.constants.BpmnXMLConstants.*;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
/** /**
* 仿钉钉快搭模型相关的工具方法 * 仿钉钉快搭模型相关的工具方法
@ -42,12 +44,12 @@ public class SimpleModelUtils {
/** /**
* 所有审批人同意的表达式 * 所有审批人同意的表达式
*/ */
public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= 0 }"; public static final String ALL_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }";
/** /**
* 任一一名审批人同意的表达式 * 任一一名审批人同意的表达式
*/ */
public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances >= nrOfInstances }"; public static final String ANY_OF_APPROVE_COMPLETE_EXPRESSION = "${ nrOfCompletedInstances > 0 }";
/** /**
* 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善 * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善
@ -59,6 +61,12 @@ public class SimpleModelUtils {
*/ */
public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) {
BpmnModel bpmnModel = new BpmnModel(); BpmnModel bpmnModel = new BpmnModel();
// 不加这个 解析 Message 会报 NPE 异常
bpmnModel.setTargetNamespace(BPMN2_NAMESPACE);
Message rejectPostProcessMsg = new Message();
rejectPostProcessMsg.setName(REJECT_POST_PROCESS_MESSAGE_NAME);
bpmnModel.addMessage(rejectPostProcessMsg);
Process mainProcess = new Process(); Process mainProcess = new Process();
mainProcess.setId(processId); mainProcess.setId(processId);
mainProcess.setName(processName); mainProcess.setName(processName);
@ -214,6 +222,12 @@ public class SimpleModelUtils {
BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler());
mainProcess.addFlowElement(boundaryEvent); mainProcess.addFlowElement(boundaryEvent);
} }
if (userTaskConfig.getRejectHandler() != null) {
// 添加用户任务拒绝 Message Boundary Event, 用于任务的拒绝处理
BoundaryEvent boundaryEvent = buildUserTaskRejectBoundaryEvent(userTask, userTaskConfig.getRejectHandler());
mainProcess.addFlowElement(boundaryEvent);
}
break; break;
} }
case COPY_TASK: { case COPY_TASK: {
@ -270,10 +284,27 @@ public class SimpleModelUtils {
} }
} }
private static BoundaryEvent buildUserTaskRejectBoundaryEvent(UserTask userTask, RejectHandler rejectHandler) {
BoundaryEvent messageBoundaryEvent = new BoundaryEvent();
messageBoundaryEvent.setId("Event-" + IdUtil.fastUUID());
// 设置关联的任务为不会被中断
messageBoundaryEvent.setCancelActivity(false);
messageBoundaryEvent.setAttachedToRef(userTask);
MessageEventDefinition messageEventDefinition = new MessageEventDefinition();
messageEventDefinition.setMessageRef(REJECT_POST_PROCESS_MESSAGE_NAME);
messageBoundaryEvent.addEventDefinition(messageEventDefinition);
addExtensionElement(messageBoundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_REJECT_POST_PROCESS.getType().toString());
addExtensionElement(messageBoundaryEvent, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
if (Objects.equals(rejectHandler.getType(), RETURN_PRE_USER_TASK.getType())) {
addExtensionElement(messageBoundaryEvent, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
}
return messageBoundaryEvent;
}
private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) {
// 定时器边界事件 // 定时器边界事件
BoundaryEvent boundaryEvent = new BoundaryEvent(); BoundaryEvent boundaryEvent = new BoundaryEvent();
boundaryEvent.setId(IdUtil.fastUUID()); boundaryEvent.setId("Event-" + IdUtil.fastUUID());
// 设置关联的任务为不会被中断 // 设置关联的任务为不会被中断
boundaryEvent.setCancelActivity(false); boundaryEvent.setCancelActivity(false);
boundaryEvent.setAttachedToRef(userTask); boundaryEvent.setAttachedToRef(userTask);
@ -286,7 +317,7 @@ public class SimpleModelUtils {
} }
boundaryEvent.addEventDefinition(eventDefinition); boundaryEvent.addEventDefinition(eventDefinition);
// 添加定时器边界事件类型 // 添加定时器边界事件类型
addExtensionElement(boundaryEvent, TIMER_BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString());
// 添加超时执行动作元素 // 添加超时执行动作元素
addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction()));
return boundaryEvent; return boundaryEvent;

View File

@ -90,6 +90,8 @@ public interface BpmTaskService {
*/ */
void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO); void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO);
/** /**
* 将流程任务分配给指定用户 * 将流程任务分配给指定用户
* *
@ -129,10 +131,11 @@ public interface BpmTaskService {
/** /**
* 根据条件查询已经分配的用户任务列表 * 根据条件查询已经分配的用户任务列表
* @param processInstanceId 流程实例编号 * @param processInstanceId 流程实例编号不允许为空
* @param executionId execution Id
* @param taskDefineKey 任务定义 Key * @param taskDefineKey 任务定义 Key
*/ */
List<Task> getAssignedTaskListByConditions(String processInstanceId, String taskDefineKey); List<Task> getAssignedTaskListByConditions(String processInstanceId, String executionId, String taskDefineKey);
/** /**
* 获取当前任务的可回退的 UserTask 集合 * 获取当前任务的可回退的 UserTask 集合

View File

@ -9,16 +9,18 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmDeleteReasonEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum;
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.enums.BpmConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@ -26,6 +28,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BoundaryEvent;
import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask; import org.flowable.bpmn.model.UserTask;
@ -33,6 +36,7 @@ import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService; import org.flowable.engine.ManagementService;
import org.flowable.engine.RuntimeService; import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService; import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.DelegationState; import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task; import org.flowable.task.api.Task;
@ -245,7 +249,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/** /**
* 如果父任务是有前后加签的任务如果它加签出来的子任务都被处理需要处理父任务 * 如果父任务是有前后加签的任务如果它加签出来的子任务都被处理需要处理父任务
* * <p>
* 1. 如果是向前加签则需要重新激活父任务让它可以被审批 * 1. 如果是向前加签则需要重新激活父任务让它可以被审批
* 2. 如果是向后加签则需要完成父任务让它完成审批 * 2. 如果是向后加签则需要完成父任务让它完成审批
* *
@ -278,7 +282,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
taskService.resolveTask(parentTaskId); taskService.resolveTask(parentTaskId);
// 3.1.2 更新流程任务 status // 3.1.2 更新流程任务 status
updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus()); updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus());
// 3.2 情况二处理向向后加签 // 3.2 情况二处理向向后加签
} else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) { } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) {
// 只有 parentTask 处于 APPROVING 的情况下才可以继续 complete 完成 // 只有 parentTask 处于 APPROVING 的情况下才可以继续 complete 完成
// 否则一个未审批的 parentTask 任务在加签出来的任务都被减签的情况下就直接完成审批这样会存在问题 // 否则一个未审批的 parentTask 任务在加签出来的任务都被减签的情况下就直接完成审批这样会存在问题
@ -333,14 +337,29 @@ public class BpmTaskServiceImpl implements BpmTaskService {
taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(),
BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
// 3. 更新流程实例审批不通过 BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
// 寻找用户任务的自定义拒绝后处理边界事件
BoundaryEvent rejectBoundaryEvent = BpmnModelUtils.findCustomBoundaryEventOfUserTask((UserTask) flowElement,
BpmBoundaryEventType.USER_TASK_REJECT_POST_PROCESS);
if (rejectBoundaryEvent != null) {
Execution execution = runtimeService.createExecutionQuery().processInstanceId(task.getProcessInstanceId())
.activityId(rejectBoundaryEvent.getId()).singleResult();
if (execution != null) {
// 3.1 触发消息边界事件. 进一步的处理交给 BpmTaskEventListener
runtimeService.messageEventReceived(BpmnModelConstants.REJECT_POST_PROCESS_MESSAGE_NAME, execution.getId());
return;
}
}
// 3.2 没有找到拒绝后处理边界事件, 更新流程实例审批不通过
processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason());
} }
/** /**
* 更新流程任务的 status 状态 * 更新流程任务的 status 状态
* *
* @param id 任务编号 * @param id 任务编号
* @param status 状态 * @param status 状态
*/ */
private void updateTaskStatus(String id, Integer status) { private void updateTaskStatus(String id, Integer status) {
@ -350,7 +369,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/** /**
* 更新流程任务的 status 状态reason 理由 * 更新流程任务的 status 状态reason 理由
* *
* @param id 任务编号 * @param id 任务编号
* @param status 状态 * @param status 状态
* @param reason 理由审批通过审批不通过的理由 * @param reason 理由审批通过审批不通过的理由
*/ */
@ -434,9 +453,16 @@ public class BpmTaskServiceImpl implements BpmTaskService {
} }
@Override @Override
public List<Task> getAssignedTaskListByConditions(String processInstanceId, String defineKey) { public List<Task> getAssignedTaskListByConditions(String processInstanceId, String executionId, String defineKey) {
TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId) Assert.notNull(processInstanceId, "processInstanceId 不能为空");
.taskDefinitionKey(defineKey).active().taskAssigned().includeTaskLocalVariables(); TaskQuery taskQuery = taskService.createTaskQuery().taskAssigned().processInstanceId(processInstanceId).active()
.includeTaskLocalVariables();
if (StrUtil.isNotEmpty(executionId)) {
taskQuery.executionId(executionId);
}
if (StrUtil.isNotEmpty(defineKey)) {
taskQuery.taskDefinitionKey(defineKey);
}
return taskQuery.list(); return taskQuery.list();
} }
@ -664,7 +690,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
List<Long> currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况因为向后加签时它暂时没 assignee 而是 owner List<Long> currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况因为向后加签时它暂时没 assignee 而是 owner
Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) { if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) {
List<AdminUserRespDTO> userList = adminUserApi.getUserList( CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())); List<AdminUserRespDTO> userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds()));
throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname))); throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname)));
} }
return taskEntity; return taskEntity;
@ -673,8 +699,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
/** /**
* 创建加签子任务 * 创建加签子任务
* *
* @param userIds 被加签的用户 ID * @param userIds 被加签的用户 ID
* @param taskEntity 被加签的任务 * @param taskEntity 被加签的任务
*/ */
private void createSignTaskList(List<String> userIds, TaskEntityImpl taskEntity) { private void createSignTaskList(List<String> userIds, TaskEntityImpl taskEntity) {
if (CollUtil.isEmpty(userIds)) { if (CollUtil.isEmpty(userIds)) {
@ -703,7 +729,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
// 2.1 向前加签设置审批人 // 2.1 向前加签设置审批人
if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) { if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) {
task.setAssignee(assignee); task.setAssignee(assignee);
// 2.2 向后加签设置 owner 不设置 assignee 是因为不能同时审批需要等父任务完成 // 2.2 向后加签设置 owner 不设置 assignee 是因为不能同时审批需要等父任务完成
} else { } else {
task.setOwner(assignee); task.setOwner(assignee);
} }