diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java similarity index 57% rename from yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java rename to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java index b4c63c25f..f824dfaac 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmBoundaryEventType.java @@ -5,20 +5,21 @@ import lombok.AllArgsConstructor; import lombok.Getter; /** - * 定时器边界事件类型枚举 + * BPM 边界事件 (boundary event) 自定义类型枚举 * * @author jason */ @Getter @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 String name; - public static BpmTimerBoundaryEventType typeOf(Integer type) { + public static BpmBoundaryEventType typeOf(Integer type) { return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java new file mode 100644 index 000000000..b1bb07f17 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -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()); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index ec99ff978..a3b0bc0c8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -30,15 +30,25 @@ public interface BpmnModelConstants { */ String USER_TASK_CANDIDATE_PARAM = "candidateParam"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记边界事件类型 + */ + String BOUNDARY_EVENT_TYPE = "boundaryEventType"; + /** * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 */ 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 流程表单字段权限元素, 用于标记字段权限 @@ -66,4 +76,5 @@ public interface BpmnModelConstants { */ Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); + String REJECT_POST_PROCESS_MESSAGE_NAME = "message_reject_post_process"; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index 733cc2b41..7f6d8c5c4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -2,23 +2,41 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; import cn.hutool.core.collection.CollUtil; 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.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; import com.google.common.collect.ImmutableSet; +import jakarta.annotation.Resource; 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.FlowableEngineEventType; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; +import org.flowable.engine.delegate.event.FlowableMessageEvent; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; import java.util.List; +import java.util.Objects; 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} 的开始与完成 * @@ -34,15 +52,22 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Resource @Lazy // 解决循环依赖 private BpmActivityService activityService; + @Resource + @Lazy // 解决循环依赖 + private BpmProcessInstanceService processInstanceService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmModelService bpmModelService; public static final Set TASK_EVENTS = ImmutableSet.builder() .add(FlowableEngineEventType.TASK_CREATED) .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) .build(); - public BpmTaskEventListener(){ + public BpmTaskEventListener() { super(TASK_EVENTS); } @@ -53,7 +78,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Override protected void taskAssigned(FlowableEngineEntityEvent event) { - taskService.updateTaskExtAssign((Task)event.getEntity()); + taskService.updateTaskExtAssign((Task) event.getEntity()); } @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 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); + } + } + } + }); + } + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java index 780b6b739..891d91409 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java @@ -1,11 +1,10 @@ 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.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.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.framework.flowable.core.enums.BpmnModelConstants; 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 org.flowable.bpmn.model.BoundaryEvent; import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.ExtensionElement; import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; 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 java.util.List; -import java.util.Optional; import java.util.Set; /** @@ -69,23 +66,21 @@ public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListe // 如果是定时器边界事件 if (element instanceof BoundaryEvent) { BoundaryEvent boundaryEvent = (BoundaryEvent) element; - ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.TIMER_BOUNDARY_EVENT_TYPE)); - Integer timerBoundaryEventType = NumberUtils.parseInt(Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null)); - BpmTimerBoundaryEventType bpmTimerBoundaryEventType = BpmTimerBoundaryEventType.typeOf(timerBoundaryEventType); + String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.BOUNDARY_EVENT_TYPE); + BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); // 类型为用户任务超时未处理的情况 - if (bpmTimerBoundaryEventType == BpmTimerBoundaryEventType.USER_TASK_TIMEOUT) { - ExtensionElement timeoutActionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION)); - Integer timeoutAction = NumberUtils.parseInt(Optional.ofNullable(timeoutActionElement).map(ExtensionElement::getElementText).orElse(null)); - processUserTaskTimeout(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), timeoutAction); + if (bpmTimerBoundaryEventType == BpmBoundaryEventType.USER_TASK_TIMEOUT) { + String timeoutAction = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION); + userTaskTimeoutHandler(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), NumberUtils.parseInt(timeoutAction)); } } } - private void processUserTaskTimeout(String processInstanceId, String taskDefKey, Integer timeoutAction) { + private void userTaskTimeoutHandler(String processInstanceId, String taskDefKey, Integer timeoutAction) { BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); if (userTaskTimeoutAction != null) { - // 查询超时未处理的任务 - List taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, taskDefKey); + // 查询超时未处理的任务 TODO 加签的情况会不会有问题 ??? + List taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, null, taskDefKey); taskList.forEach(task -> { // 自动提醒 if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java index 49fd21018..1ff3dd714 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import lombok.Data; import java.util.List; @@ -33,12 +34,15 @@ public class SimpleModelUserTaskConfig { */ private Integer approveMethod; - /** * 超时处理 */ private TimeoutHandler timeoutHandler; + /** + * 用户任务拒绝处理 + */ + private RejectHandler rejectHandler; @Data public static class TimeoutHandler { @@ -62,7 +66,20 @@ public class SimpleModelUserTaskConfig { * 如果执行动作是自动提醒, 最大提醒次数 */ private Integer maxRemindCount; + } + @Data + public static class RejectHandler { + + /** + * 用户任务拒绝处理类型 {@link BpmUserTaskRejectHandlerType} + */ + private Integer type; + + /** + * 用户任务拒绝后驳回的节点 Id + */ + private String returnNodeId; } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index e0b2d02b9..7a0b2b761 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; 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 org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; @@ -360,4 +361,32 @@ public class BpmnModelUtils { 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); + } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 72f6157a9..993baaa66 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -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.simple.SimpleModelConditionGroups; 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.model.Process; import org.flowable.bpmn.model.*; @@ -22,13 +23,14 @@ import java.util.List; import java.util.Map; 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.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.framework.flowable.core.enums.BpmnModelConstants.*; 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.FLOWABLE_EXTENSIONS_PREFIX; +import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /** * 仿钉钉快搭模型相关的工具方法 @@ -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 (待完善) @@ -59,6 +61,12 @@ public class SimpleModelUtils { */ public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { 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(); mainProcess.setId(processId); mainProcess.setName(processName); @@ -214,6 +222,12 @@ public class SimpleModelUtils { BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); mainProcess.addFlowElement(boundaryEvent); } + if (userTaskConfig.getRejectHandler() != null) { + // 添加用户任务拒绝 Message Boundary Event, 用于任务的拒绝处理 + BoundaryEvent boundaryEvent = buildUserTaskRejectBoundaryEvent(userTask, userTaskConfig.getRejectHandler()); + mainProcess.addFlowElement(boundaryEvent); + } + break; } 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) { // 定时器边界事件 BoundaryEvent boundaryEvent = new BoundaryEvent(); - boundaryEvent.setId(IdUtil.fastUUID()); + boundaryEvent.setId("Event-" + IdUtil.fastUUID()); // 设置关联的任务为不会被中断 boundaryEvent.setCancelActivity(false); boundaryEvent.setAttachedToRef(userTask); @@ -286,7 +317,7 @@ public class SimpleModelUtils { } 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())); return boundaryEvent; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index 0b989f44c..f65e3333b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -90,6 +90,8 @@ public interface BpmTaskService { */ 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 */ - List getAssignedTaskListByConditions(String processInstanceId, String taskDefineKey); + List getAssignedTaskListByConditions(String processInstanceId, String executionId, String taskDefineKey); /** * 获取当前任务的可回退的 UserTask 集合 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 4d6f9bae3..57ea05812 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -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.number.NumberUtils; 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.module.bpm.controller.admin.task.vo.task.*; 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.BpmDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; 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.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.message.BpmMessageService; 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.validation.Valid; 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.bpmn.model.UserTask; @@ -33,6 +36,7 @@ import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; +import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.DelegationState; import org.flowable.task.api.Task; @@ -245,7 +249,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务: - * + *

* 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批 * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批 * @@ -278,7 +282,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskService.resolveTask(parentTaskId); // 3.1.2 更新流程任务 status updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus()); - // 3.2 情况二:处理向【向后】加签 + // 3.2 情况二:处理向【向后】加签 } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) { // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成 // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题 @@ -333,14 +337,29 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), 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()); } /** * 更新流程任务的 status 状态 * - * @param id 任务编号 + * @param id 任务编号 * @param status 状态 */ private void updateTaskStatus(String id, Integer status) { @@ -350,7 +369,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 更新流程任务的 status 状态、reason 理由 * - * @param id 任务编号 + * @param id 任务编号 * @param status 状态 * @param reason 理由(审批通过、审批不通过的理由) */ @@ -434,9 +453,16 @@ public class BpmTaskServiceImpl implements BpmTaskService { } @Override - public List getAssignedTaskListByConditions(String processInstanceId, String defineKey) { - TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId) - .taskDefinitionKey(defineKey).active().taskAssigned().includeTaskLocalVariables(); + public List getAssignedTaskListByConditions(String processInstanceId, String executionId, String defineKey) { + Assert.notNull(processInstanceId, "processInstanceId 不能为空"); + 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(); } @@ -664,7 +690,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { List currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) { - List userList = adminUserApi.getUserList( CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())); + List userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())); throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname))); } return taskEntity; @@ -673,8 +699,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 创建加签子任务 * - * @param userIds 被加签的用户 ID - * @param taskEntity 被加签的任务 + * @param userIds 被加签的用户 ID + * @param taskEntity 被加签的任务 */ private void createSignTaskList(List userIds, TaskEntityImpl taskEntity) { if (CollUtil.isEmpty(userIds)) { @@ -703,7 +729,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2.1 向前加签,设置审批人 if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) { task.setAssignee(assignee); - // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 + // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 } else { task.setOwner(assignee); }