Commit d34fef67dae7259e7a05c5f19778d097467ec0e4
1 parent
afad8ac6
仿钉钉流程设计- 审批节点超时处理
Showing
11 changed files
with
417 additions
and
24 deletions
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTimerBoundaryEventType.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.ArrayUtil; | ||
| 4 | +import lombok.AllArgsConstructor; | ||
| 5 | +import lombok.Getter; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 定时器边界事件类型枚举 | ||
| 9 | + * | ||
| 10 | + * @author jason | ||
| 11 | + */ | ||
| 12 | +@Getter | ||
| 13 | +@AllArgsConstructor | ||
| 14 | +public enum BpmTimerBoundaryEventType { | ||
| 15 | + | ||
| 16 | + USER_TASK_TIMEOUT(1,"用户任务超时"); | ||
| 17 | + | ||
| 18 | + private final Integer type; | ||
| 19 | + private final String name; | ||
| 20 | + | ||
| 21 | + public static BpmTimerBoundaryEventType typeOf(Integer type) { | ||
| 22 | + return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); | ||
| 23 | + } | ||
| 24 | +} |
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmUserTaskTimeoutActionEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.definition; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.ArrayUtil; | ||
| 4 | +import lombok.AllArgsConstructor; | ||
| 5 | +import lombok.Getter; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 用户任务超时处理执行动作枚举 | ||
| 9 | + * | ||
| 10 | + * @author jason | ||
| 11 | + */ | ||
| 12 | +@Getter | ||
| 13 | +@AllArgsConstructor | ||
| 14 | +public enum BpmUserTaskTimeoutActionEnum { | ||
| 15 | + | ||
| 16 | + AUTO_REMINDER(1,"自动提醒"), | ||
| 17 | + AUTO_APPROVE(2, "自动同意"), | ||
| 18 | + AUTO_REJECT(3, "自动拒绝"); | ||
| 19 | + | ||
| 20 | + private final Integer action; | ||
| 21 | + private final String name; | ||
| 22 | + | ||
| 23 | + public static BpmUserTaskTimeoutActionEnum actionOf(Integer action) { | ||
| 24 | + return ArrayUtil.firstMatch(item -> item.getAction().equals(action), values()); | ||
| 25 | + } | ||
| 26 | +} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java
| @@ -31,6 +31,16 @@ public interface BpmnModelConstants { | @@ -31,6 +31,16 @@ public interface BpmnModelConstants { | ||
| 31 | String USER_TASK_CANDIDATE_PARAM = "candidateParam"; | 31 | String USER_TASK_CANDIDATE_PARAM = "candidateParam"; |
| 32 | 32 | ||
| 33 | /** | 33 | /** |
| 34 | + * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 | ||
| 35 | + */ | ||
| 36 | + String USER_TASK_TIMEOUT_HANDLER_ACTION = "timeoutAction"; | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * BPMN ExtensionElement 的扩展属性,用于标记定时边界事件类型 | ||
| 40 | + */ | ||
| 41 | + String TIMER_BOUNDARY_EVENT_TYPE = "timerBoundaryEventType"; | ||
| 42 | + | ||
| 43 | + /** | ||
| 34 | * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 | 44 | * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 |
| 35 | */ | 45 | */ |
| 36 | String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; | 46 | String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTimerFiredEventListener.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||
| 5 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||
| 6 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; | ||
| 7 | +import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; | ||
| 8 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType; | ||
| 9 | +import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum; | ||
| 10 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | ||
| 11 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; | ||
| 12 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task.TodoTaskReminderProducer; | ||
| 13 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | ||
| 14 | +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; | ||
| 15 | +import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; | ||
| 16 | +import com.google.common.collect.ImmutableSet; | ||
| 17 | +import jakarta.annotation.Resource; | ||
| 18 | +import lombok.extern.slf4j.Slf4j; | ||
| 19 | +import org.flowable.bpmn.model.BoundaryEvent; | ||
| 20 | +import org.flowable.bpmn.model.BpmnModel; | ||
| 21 | +import org.flowable.bpmn.model.ExtensionElement; | ||
| 22 | +import org.flowable.bpmn.model.FlowElement; | ||
| 23 | +import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; | ||
| 24 | +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; | ||
| 25 | +import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; | ||
| 26 | +import org.flowable.job.api.Job; | ||
| 27 | +import org.flowable.task.api.Task; | ||
| 28 | +import org.springframework.context.annotation.Lazy; | ||
| 29 | +import org.springframework.stereotype.Component; | ||
| 30 | + | ||
| 31 | +import java.util.List; | ||
| 32 | +import java.util.Optional; | ||
| 33 | +import java.util.Set; | ||
| 34 | + | ||
| 35 | +/** | ||
| 36 | + * 监听定时器触发事件 | ||
| 37 | + * | ||
| 38 | + * @author jason | ||
| 39 | + */ | ||
| 40 | +@Component | ||
| 41 | +@Slf4j | ||
| 42 | +public class BpmTimerFiredEventListener extends AbstractFlowableEngineEventListener { | ||
| 43 | + | ||
| 44 | + @Resource | ||
| 45 | + @Lazy // 延迟加载,避免循环依赖 | ||
| 46 | + private BpmModelService bpmModelService; | ||
| 47 | + | ||
| 48 | + @Resource | ||
| 49 | + @Lazy // 延迟加载,避免循环依赖 | ||
| 50 | + private BpmTaskService bpmTaskService; | ||
| 51 | + | ||
| 52 | + @Resource | ||
| 53 | + private TodoTaskReminderProducer todoTaskReminderProducer; | ||
| 54 | + | ||
| 55 | + public static final Set<FlowableEngineEventType> TIME_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() | ||
| 56 | + .add(FlowableEngineEventType.TIMER_FIRED) | ||
| 57 | + .build(); | ||
| 58 | + | ||
| 59 | + public BpmTimerFiredEventListener() { | ||
| 60 | + super(TIME_EVENTS); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Override | ||
| 64 | + protected void timerFired(FlowableEngineEntityEvent event) { | ||
| 65 | + String processDefinitionId = event.getProcessDefinitionId(); | ||
| 66 | + BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); | ||
| 67 | + Job entity = (Job) event.getEntity(); | ||
| 68 | + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); | ||
| 69 | + // 如果是定时器边界事件 | ||
| 70 | + if (element instanceof BoundaryEvent) { | ||
| 71 | + BoundaryEvent boundaryEvent = (BoundaryEvent) element; | ||
| 72 | + ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.TIMER_BOUNDARY_EVENT_TYPE)); | ||
| 73 | + Integer timerBoundaryEventType = NumberUtils.parseInt(Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null)); | ||
| 74 | + BpmTimerBoundaryEventType bpmTimerBoundaryEventType = BpmTimerBoundaryEventType.typeOf(timerBoundaryEventType); | ||
| 75 | + // 类型为用户任务超时未处理的情况 | ||
| 76 | + if (bpmTimerBoundaryEventType == BpmTimerBoundaryEventType.USER_TASK_TIMEOUT) { | ||
| 77 | + ExtensionElement timeoutActionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_ACTION)); | ||
| 78 | + Integer timeoutAction = NumberUtils.parseInt(Optional.ofNullable(timeoutActionElement).map(ExtensionElement::getElementText).orElse(null)); | ||
| 79 | + processUserTaskTimeout(event.getProcessInstanceId(), boundaryEvent.getAttachedToRefId(), timeoutAction); | ||
| 80 | + } | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + private void processUserTaskTimeout(String processInstanceId, String taskDefKey, Integer timeoutAction) { | ||
| 85 | + BpmUserTaskTimeoutActionEnum userTaskTimeoutAction = BpmUserTaskTimeoutActionEnum.actionOf(timeoutAction); | ||
| 86 | + if (userTaskTimeoutAction != null) { | ||
| 87 | + // 查询超时未处理的任务 | ||
| 88 | + List<Task> taskList = bpmTaskService.getAssignedTaskListByConditions(processInstanceId, taskDefKey); | ||
| 89 | + taskList.forEach(task -> { | ||
| 90 | + // 自动提醒 | ||
| 91 | + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REMINDER) { | ||
| 92 | + TodoTaskReminderMessage message = new TodoTaskReminderMessage().setTenantId(Long.parseLong(task.getTenantId())) | ||
| 93 | + .setUserId(Long.parseLong(task.getAssignee())).setTaskName(task.getName()); | ||
| 94 | + todoTaskReminderProducer.sendReminderMessage(message); | ||
| 95 | + } | ||
| 96 | + // 自动同意 | ||
| 97 | + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_APPROVE) { | ||
| 98 | + // TODO @芋艿 这个上下文如何清除呢? 任务通过后, BpmProcessInstanceEventListener 会有回调 | ||
| 99 | + TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); | ||
| 100 | + TenantContextHolder.setIgnore(false); | ||
| 101 | + BpmTaskApproveReqVO req = new BpmTaskApproveReqVO().setId(task.getId()) | ||
| 102 | + .setReason("超时系统自动同意"); | ||
| 103 | + bpmTaskService.approveTask(Long.parseLong(task.getAssignee()), req); | ||
| 104 | + | ||
| 105 | + } | ||
| 106 | + // 自动拒绝 | ||
| 107 | + if (userTaskTimeoutAction == BpmUserTaskTimeoutActionEnum.AUTO_REJECT) { | ||
| 108 | + // TODO @芋艿 这个上下文如何清除呢? 任务拒绝后, BpmProcessInstanceEventListener 会有回调 | ||
| 109 | + TenantContextHolder.setTenantId(Long.parseLong(task.getTenantId())); | ||
| 110 | + TenantContextHolder.setIgnore(false); | ||
| 111 | + BpmTaskRejectReqVO req = new BpmTaskRejectReqVO().setId(task.getId()).setReason("超时系统自动拒绝"); | ||
| 112 | + bpmTaskService.rejectTask(Long.parseLong(task.getAssignee()), req); | ||
| 113 | + } | ||
| 114 | + }); | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | +} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/consumer/task/SysNotifyTodoTaskReminderConsumer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.consumer.task; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.map.MapUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | ||
| 5 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; | ||
| 6 | +import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; | ||
| 7 | +import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; | ||
| 8 | +import jakarta.annotation.Resource; | ||
| 9 | +import lombok.extern.slf4j.Slf4j; | ||
| 10 | +import org.springframework.context.event.EventListener; | ||
| 11 | +import org.springframework.scheduling.annotation.Async; | ||
| 12 | +import org.springframework.stereotype.Component; | ||
| 13 | + | ||
| 14 | +import java.util.Map; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * 待办任务提醒 - 站内信的消费者 | ||
| 18 | + * | ||
| 19 | + * @author jason | ||
| 20 | + */ | ||
| 21 | +@Component | ||
| 22 | +@Slf4j | ||
| 23 | +public class SysNotifyTodoTaskReminderConsumer { | ||
| 24 | + | ||
| 25 | + private static final String TASK_REMIND_TEMPLATE_CODE = "user_task_remind"; | ||
| 26 | + | ||
| 27 | + @Resource | ||
| 28 | + private NotifyMessageSendApi notifyMessageSendApi; | ||
| 29 | + | ||
| 30 | + @EventListener | ||
| 31 | + @Async | ||
| 32 | + public void onMessage(TodoTaskReminderMessage message) { | ||
| 33 | + log.info("站内信消费者接收到消息 [消息内容({})] ", message); | ||
| 34 | + TenantUtils.execute(message.getTenantId(), ()-> { | ||
| 35 | + Map<String,Object> templateParams = MapUtil.newHashMap(); | ||
| 36 | + templateParams.put("name", message.getTaskName()); | ||
| 37 | + NotifySendSingleToUserReqDTO req = new NotifySendSingleToUserReqDTO().setUserId(message.getUserId()) | ||
| 38 | + .setTemplateCode(TASK_REMIND_TEMPLATE_CODE).setTemplateParams(templateParams); | ||
| 39 | + notifyMessageSendApi.sendSingleMessageToAdmin(req); | ||
| 40 | + }); | ||
| 41 | + } | ||
| 42 | +} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/message/task/TodoTaskReminderMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task; | ||
| 2 | + | ||
| 3 | +import jakarta.validation.constraints.NotEmpty; | ||
| 4 | +import jakarta.validation.constraints.NotNull; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 待办任务提醒消息 | ||
| 9 | + * | ||
| 10 | + * @author jason | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +public class TodoTaskReminderMessage { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 租户 Id | ||
| 17 | + */ | ||
| 18 | + @NotNull(message = "租户 Id 不能未空") | ||
| 19 | + private Long tenantId; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 用户Id | ||
| 23 | + */ | ||
| 24 | + @NotNull(message = "用户 Id 不能未空") | ||
| 25 | + private Long userId; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 任务名称 | ||
| 29 | + */ | ||
| 30 | + @NotEmpty(message = "任务名称不能未空") | ||
| 31 | + private String taskName; | ||
| 32 | + | ||
| 33 | + // TODO 暂时只有站内信通知. 后面可以增加 | ||
| 34 | +} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/mq/producer/task/TodoTaskReminderProducer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.producer.task; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.mq.message.task.TodoTaskReminderMessage; | ||
| 4 | +import jakarta.annotation.Resource; | ||
| 5 | +import jakarta.validation.Valid; | ||
| 6 | +import org.springframework.context.ApplicationContext; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | +import org.springframework.validation.annotation.Validated; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 待办任务提醒 Producer | ||
| 12 | + * | ||
| 13 | + * @author jason | ||
| 14 | + */ | ||
| 15 | +@Component | ||
| 16 | +@Validated | ||
| 17 | +public class TodoTaskReminderProducer { | ||
| 18 | + | ||
| 19 | + @Resource | ||
| 20 | + private ApplicationContext applicationContext; | ||
| 21 | + | ||
| 22 | + public void sendReminderMessage(@Valid TodoTaskReminderMessage message) { | ||
| 23 | + applicationContext.publishEvent(message); | ||
| 24 | + } | ||
| 25 | +} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/simple/SimpleModelUserTaskConfig.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.core.simple; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +import java.util.List; | ||
| 6 | +import java.util.Map; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 仿钉钉流程设计器审批节点配置 Model | ||
| 10 | + * | ||
| 11 | + * @author jason | ||
| 12 | + */ | ||
| 13 | +@Data | ||
| 14 | +public class SimpleModelUserTaskConfig { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 候选人策略 | ||
| 18 | + */ | ||
| 19 | + private Integer candidateStrategy; | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 候选人参数 | ||
| 23 | + */ | ||
| 24 | + private String candidateParam; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 字段权限 | ||
| 28 | + */ | ||
| 29 | + private List<Map<String,String>> fieldsPermission; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 审批方式 | ||
| 33 | + */ | ||
| 34 | + private Integer approveMethod; | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * 超时处理 | ||
| 39 | + */ | ||
| 40 | + private TimeoutHandler timeoutHandler; | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + @Data | ||
| 44 | + public static class TimeoutHandler { | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 是否开启超时处理 | ||
| 48 | + */ | ||
| 49 | + private Boolean enable; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 超时执行的动作 | ||
| 53 | + */ | ||
| 54 | + private Integer action; | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 超时时间设置 | ||
| 58 | + */ | ||
| 59 | + private String timeDuration; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * 如果执行动作是自动提醒, 最大提醒次数 | ||
| 63 | + */ | ||
| 64 | + private Integer maxRemindCount; | ||
| 65 | + | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | +} |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
| @@ -5,27 +5,27 @@ import cn.hutool.core.collection.CollUtil; | @@ -5,27 +5,27 @@ import cn.hutool.core.collection.CollUtil; | ||
| 5 | import cn.hutool.core.lang.Assert; | 5 | import cn.hutool.core.lang.Assert; |
| 6 | import cn.hutool.core.lang.TypeReference; | 6 | import cn.hutool.core.lang.TypeReference; |
| 7 | import cn.hutool.core.map.MapUtil; | 7 | import cn.hutool.core.map.MapUtil; |
| 8 | -import cn.hutool.core.util.ArrayUtil; | ||
| 9 | -import cn.hutool.core.util.BooleanUtil; | ||
| 10 | -import cn.hutool.core.util.NumberUtil; | ||
| 11 | -import cn.hutool.core.util.StrUtil; | 8 | +import cn.hutool.core.util.*; |
| 12 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | 9 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 13 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; | 10 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; |
| 14 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; | 11 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmApproveMethodEnum; |
| 15 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; | 12 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType; |
| 16 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; | 13 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; |
| 17 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; | 14 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; |
| 18 | -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants; | ||
| 19 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups; | 15 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelConditionGroups; |
| 16 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.simple.SimpleModelUserTaskConfig; | ||
| 20 | import org.flowable.bpmn.BpmnAutoLayout; | 17 | import org.flowable.bpmn.BpmnAutoLayout; |
| 21 | import org.flowable.bpmn.model.Process; | 18 | import org.flowable.bpmn.model.Process; |
| 22 | import org.flowable.bpmn.model.*; | 19 | import org.flowable.bpmn.model.*; |
| 23 | 20 | ||
| 24 | import java.util.List; | 21 | import java.util.List; |
| 25 | import java.util.Map; | 22 | import java.util.Map; |
| 23 | +import java.util.Objects; | ||
| 26 | 24 | ||
| 27 | import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; | 25 | import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.END_EVENT; |
| 28 | -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.FORM_FIELD_PERMISSION_ELEMENT; | 26 | +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmTimerBoundaryEventType.USER_TASK_TIMEOUT; |
| 27 | +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutActionEnum.AUTO_REMINDER; | ||
| 28 | +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; | ||
| 29 | import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; | 29 | import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.SimpleModelConstants.*; |
| 30 | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; | 30 | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; |
| 31 | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; | 31 | import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; |
| @@ -205,8 +205,15 @@ public class SimpleModelUtils { | @@ -205,8 +205,15 @@ public class SimpleModelUtils { | ||
| 205 | break; | 205 | break; |
| 206 | } | 206 | } |
| 207 | case USER_TASK: { | 207 | case USER_TASK: { |
| 208 | - UserTask userTask = buildBpmnUserTask(simpleModelNode); | 208 | + // 获取用户任务的配置 |
| 209 | + SimpleModelUserTaskConfig userTaskConfig = BeanUtil.toBean(simpleModelNode.getAttributes(), SimpleModelUserTaskConfig.class); | ||
| 210 | + UserTask userTask = buildBpmnUserTask(simpleModelNode, userTaskConfig); | ||
| 209 | mainProcess.addFlowElement(userTask); | 211 | mainProcess.addFlowElement(userTask); |
| 212 | + if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { | ||
| 213 | + // 添加用户任务的 Timer Boundary Event, 用于任务的超时处理 | ||
| 214 | + BoundaryEvent boundaryEvent = buildUserTaskTimerBoundaryEvent(userTask, userTaskConfig.getTimeoutHandler()); | ||
| 215 | + mainProcess.addFlowElement(boundaryEvent); | ||
| 216 | + } | ||
| 210 | break; | 217 | break; |
| 211 | } | 218 | } |
| 212 | case COPY_TASK: { | 219 | case COPY_TASK: { |
| @@ -263,6 +270,28 @@ public class SimpleModelUtils { | @@ -263,6 +270,28 @@ public class SimpleModelUtils { | ||
| 263 | } | 270 | } |
| 264 | } | 271 | } |
| 265 | 272 | ||
| 273 | + private static BoundaryEvent buildUserTaskTimerBoundaryEvent(UserTask userTask, SimpleModelUserTaskConfig.TimeoutHandler timeoutHandler) { | ||
| 274 | + // 定时器边界事件 | ||
| 275 | + BoundaryEvent boundaryEvent = new BoundaryEvent(); | ||
| 276 | + boundaryEvent.setId(IdUtil.fastUUID()); | ||
| 277 | + // 设置关联的任务为不会被中断 | ||
| 278 | + boundaryEvent.setCancelActivity(false); | ||
| 279 | + boundaryEvent.setAttachedToRef(userTask); | ||
| 280 | + TimerEventDefinition eventDefinition = new TimerEventDefinition(); | ||
| 281 | + eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); | ||
| 282 | + if (Objects.equals(AUTO_REMINDER.getAction(), timeoutHandler.getAction()) && | ||
| 283 | + timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { | ||
| 284 | + // 最大提醒次数 | ||
| 285 | + eventDefinition.setTimeCycle(String.format("R%d/%s", timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); | ||
| 286 | + } | ||
| 287 | + boundaryEvent.addEventDefinition(eventDefinition); | ||
| 288 | + // 添加定时器边界事件类型 | ||
| 289 | + addExtensionElement(boundaryEvent, TIMER_BOUNDARY_EVENT_TYPE, USER_TASK_TIMEOUT.getType().toString()); | ||
| 290 | + // 添加超时执行动作元素 | ||
| 291 | + addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_ACTION, StrUtil.toStringOrNull(timeoutHandler.getAction())); | ||
| 292 | + return boundaryEvent; | ||
| 293 | + } | ||
| 294 | + | ||
| 266 | private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) { | 295 | private static ParallelGateway buildBpmnParallelGateway(BpmSimpleModelNodeVO node) { |
| 267 | ParallelGateway parallelGateway = new ParallelGateway(); | 296 | ParallelGateway parallelGateway = new ParallelGateway(); |
| 268 | parallelGateway.setId(node.getId()); | 297 | parallelGateway.setId(node.getId()); |
| @@ -278,9 +307,14 @@ public class SimpleModelUtils { | @@ -278,9 +307,14 @@ public class SimpleModelUtils { | ||
| 278 | // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; | 307 | // TODO @jason:建议使用 ServiceTask,通过 executionListeners 实现; |
| 279 | // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners | 308 | // @芋艿 ServiceTask 就可以了吧。 不需要 executionListeners |
| 280 | // 添加抄送候选人元素 | 309 | // 添加抄送候选人元素 |
| 281 | - addCandidateElements(node, serviceTask); | 310 | + addCandidateElements(MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY), |
| 311 | + MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM), | ||
| 312 | + serviceTask); | ||
| 282 | // 添加表单字段权限属性元素 | 313 | // 添加表单字段权限属性元素 |
| 283 | - addFormFieldsPermission(node, serviceTask); | 314 | + List<Map<String, String>> fieldsPermissions = MapUtil.get(node.getAttributes(), |
| 315 | + FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() { | ||
| 316 | + }); | ||
| 317 | + addFormFieldsPermission(fieldsPermissions, serviceTask); | ||
| 284 | return serviceTask; | 318 | return serviceTask; |
| 285 | } | 319 | } |
| 286 | 320 | ||
| @@ -288,12 +322,10 @@ public class SimpleModelUtils { | @@ -288,12 +322,10 @@ public class SimpleModelUtils { | ||
| 288 | /** | 322 | /** |
| 289 | * 给节点添加候选人元素 | 323 | * 给节点添加候选人元素 |
| 290 | */ | 324 | */ |
| 291 | - private static void addCandidateElements(BpmSimpleModelNodeVO node, FlowElement flowElement) { | ||
| 292 | - Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); | 325 | + private static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) { |
| 293 | addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, | 326 | addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, |
| 294 | - candidateStrategy == null ? null : String.valueOf(candidateStrategy)); | ||
| 295 | - addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, | ||
| 296 | - MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); | 327 | + candidateStrategy == null ? null : candidateStrategy.toString()); |
| 328 | + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam); | ||
| 297 | } | 329 | } |
| 298 | 330 | ||
| 299 | private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) { | 331 | private static ExclusiveGateway buildBpmnExclusiveGateway(BpmSimpleModelNodeVO node) { |
| @@ -328,21 +360,25 @@ public class SimpleModelUtils { | @@ -328,21 +360,25 @@ public class SimpleModelUtils { | ||
| 328 | return endEvent; | 360 | return endEvent; |
| 329 | } | 361 | } |
| 330 | 362 | ||
| 331 | - private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { | 363 | + private static UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node, SimpleModelUserTaskConfig userTaskConfig) { |
| 332 | UserTask userTask = new UserTask(); | 364 | UserTask userTask = new UserTask(); |
| 333 | userTask.setId(node.getId()); | 365 | userTask.setId(node.getId()); |
| 334 | userTask.setName(node.getName()); | 366 | userTask.setName(node.getName()); |
| 367 | + // 设置审批任务的截止时间 | ||
| 368 | + if (userTaskConfig.getTimeoutHandler() != null && userTaskConfig.getTimeoutHandler().getEnable()) { | ||
| 369 | + userTask.setDueDate(userTaskConfig.getTimeoutHandler().getTimeDuration()); | ||
| 370 | + } | ||
| 371 | + | ||
| 335 | // 添加候选人元素 | 372 | // 添加候选人元素 |
| 336 | - addCandidateElements(node, userTask); | 373 | + addCandidateElements(userTaskConfig.getCandidateStrategy(), userTaskConfig.getCandidateParam(), userTask); |
| 337 | // 添加表单字段权限属性元素 | 374 | // 添加表单字段权限属性元素 |
| 338 | - addFormFieldsPermission(node, userTask); | 375 | + addFormFieldsPermission(userTaskConfig.getFieldsPermission(), userTask); |
| 339 | // 处理多实例 | 376 | // 处理多实例 |
| 340 | - processMultiInstanceLoopCharacteristics(node, userTask); | 377 | + processMultiInstanceLoopCharacteristics(userTaskConfig.getApproveMethod(), userTask); |
| 341 | return userTask; | 378 | return userTask; |
| 342 | } | 379 | } |
| 343 | 380 | ||
| 344 | - private static void processMultiInstanceLoopCharacteristics(BpmSimpleModelNodeVO node, UserTask userTask) { | ||
| 345 | - Integer approveMethod = MapUtil.getInt(node.getAttributes(), SimpleModelConstants.APPROVE_METHOD_ATTRIBUTE); | 381 | + private static void processMultiInstanceLoopCharacteristics(Integer approveMethod, UserTask userTask) { |
| 346 | BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); | 382 | BpmApproveMethodEnum bpmApproveMethodEnum = BpmApproveMethodEnum.valueOf(approveMethod); |
| 347 | if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { | 383 | if (bpmApproveMethodEnum == null || bpmApproveMethodEnum == BpmApproveMethodEnum.SINGLE_PERSON_APPROVE) { |
| 348 | return; | 384 | return; |
| @@ -369,10 +405,7 @@ public class SimpleModelUtils { | @@ -369,10 +405,7 @@ public class SimpleModelUtils { | ||
| 369 | /** | 405 | /** |
| 370 | * 给节点添加表单字段权限元素 | 406 | * 给节点添加表单字段权限元素 |
| 371 | */ | 407 | */ |
| 372 | - private static void addFormFieldsPermission(BpmSimpleModelNodeVO node, FlowElement flowElement) { | ||
| 373 | - List<Map<String, String>> fieldsPermissions = MapUtil.get(node.getAttributes(), | ||
| 374 | - FORM_FIELD_PERMISSION_ELEMENT, new TypeReference<>() { | ||
| 375 | - }); | 408 | + private static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) { |
| 376 | if (CollUtil.isNotEmpty(fieldsPermissions)) { | 409 | if (CollUtil.isNotEmpty(fieldsPermissions)) { |
| 377 | fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); | 410 | fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); |
| 378 | } | 411 | } |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
| @@ -128,6 +128,13 @@ public interface BpmTaskService { | @@ -128,6 +128,13 @@ public interface BpmTaskService { | ||
| 128 | Task getTask(String id); | 128 | Task getTask(String id); |
| 129 | 129 | ||
| 130 | /** | 130 | /** |
| 131 | + * 根据条件查询已经分配的用户任务列表 | ||
| 132 | + * @param processInstanceId 流程实例编号 | ||
| 133 | + * @param taskDefineKey 任务定义 Key | ||
| 134 | + */ | ||
| 135 | + List<Task> getAssignedTaskListByConditions(String processInstanceId, String taskDefineKey); | ||
| 136 | + | ||
| 137 | + /** | ||
| 131 | * 获取当前任务的可回退的 UserTask 集合 | 138 | * 获取当前任务的可回退的 UserTask 集合 |
| 132 | * | 139 | * |
| 133 | * @param id 当前的任务 ID | 140 | * @param id 当前的任务 ID |
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| @@ -433,6 +433,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -433,6 +433,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 433 | return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); | 433 | return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); |
| 434 | } | 434 | } |
| 435 | 435 | ||
| 436 | + @Override | ||
| 437 | + public List<Task> getAssignedTaskListByConditions(String processInstanceId, String defineKey) { | ||
| 438 | + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId) | ||
| 439 | + .taskDefinitionKey(defineKey).active().taskAssigned().includeTaskLocalVariables(); | ||
| 440 | + return taskQuery.list(); | ||
| 441 | + } | ||
| 442 | + | ||
| 436 | private HistoricTaskInstance getHistoricTask(String id) { | 443 | private HistoricTaskInstance getHistoricTask(String id) { |
| 437 | return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); | 444 | return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); |
| 438 | } | 445 | } |