仿钉钉流程设计- 获取审批记录第二版

This commit is contained in:
jason 2024-09-15 19:40:29 +08:00
parent b28d917d56
commit da398dfefc
10 changed files with 89 additions and 439 deletions

View File

@ -1,68 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程节点进度的枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmProcessNodeProgressEnum {
// 0 未开始
NOT_START(0,"未开始"),
// 1 ~ 20 进行中
RUNNING(1, "进行中"), // 节点的进行
// 特殊的进行中状态
USER_TASK_DELEGATE(10, "委派中"), // 审批节点
USER_TASK_APPROVING(11, "向后加签审批通过中"), //向后加签 审批通过中.
USER_TASK_WAIT(12, "待审批"), // 一般用于先前加签
// 30 ~ 50 已经结束
// 30 ~ 40 审批节点的结束状态
USER_TASK_APPROVE(30, "审批通过"), // 审批节点
USER_TASK_REJECT(31, "审批不通过"), // 审批节点
USER_TASK_RETURN(32, "已退回"), // 审批节点
USER_TASK_CANCEL(34, "已取消"), // 审批节点
// 40 ~ 50 节点的通用结束状态
FINISHED(41, "已结束"), // 一般节点的节点的结束状态
SKIP(42, "跳过"); // 未执行跳过的节点
private final Integer status;
private final String name;
public static Integer convertBpmnTaskStatus(Integer taskStatus) {
Integer convertStatus = null;
if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)) {
convertStatus = RUNNING.getStatus();
} else if (BpmTaskStatusEnum.REJECT.getStatus().equals(taskStatus)) {
convertStatus = USER_TASK_REJECT.getStatus();
} else if( BpmTaskStatusEnum.APPROVE.getStatus().equals(taskStatus) ) {
convertStatus = USER_TASK_APPROVE.getStatus();
} else if (BpmTaskStatusEnum.DELEGATE.getStatus().equals(taskStatus)) {
convertStatus = USER_TASK_DELEGATE.getStatus();
} else if (BpmTaskStatusEnum.APPROVING.getStatus().equals(taskStatus)) {
convertStatus = USER_TASK_APPROVE.getStatus();
} else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(taskStatus)) {
convertStatus = USER_TASK_CANCEL.getStatus();
} else if (BpmTaskStatusEnum.WAIT.getStatus().equals(taskStatus)) {
convertStatus = USER_TASK_WAIT.getStatus();
}
return convertStatus;
}
/**
* 判断用户节点是不是未通过
*
* @param status 状态
*/
public static boolean isUserTaskNotApproved(Integer status) {
return ObjectUtils.equalsAny(status,
USER_TASK_REJECT.getStatus(), USER_TASK_RETURN.getStatus(), USER_TASK_CANCEL.getStatus());
}
}

View File

@ -18,26 +18,27 @@ import java.util.Objects;
public enum BpmSimpleModelNodeType implements IntArrayValuable { public enum BpmSimpleModelNodeType implements IntArrayValuable {
// 0 ~ 1 开始和结束 // 0 ~ 1 开始和结束
START_NODE(0, "开始节点"), START_NODE(0, "startEvent", "开始节点"),
END_NODE(1, "结束节点"), END_NODE(1, "endEvent", "结束节点"),
// 10 ~ 49 各种节点 // 10 ~ 49 各种节点
START_USER_NODE(10, "发起人节点"), // 发起人节点前端的开始节点Id 固定 START_USER_NODE(10, "userTask", "发起人节点"), // 发起人节点前端的开始节点Id 固定
APPROVE_NODE(11, "审批人节点"), APPROVE_NODE(11, "userTask", "审批人节点"),
COPY_NODE(12, "抄送人节点"), COPY_NODE(12, "serviceTask", "抄送人节点"),
// 50 ~ 条件分支 // 50 ~ 条件分支
CONDITION_NODE(50, "条件节点"), // 用于构建流转条件的表达式 CONDITION_NODE(50, "sequenceFlow", "条件节点"), // 用于构建流转条件的表达式
CONDITION_BRANCH_NODE(51, "条件分支节点"), // TODO @jason是不是改成叫 条件分支 CONDITION_BRANCH_NODE(51, " “parallelGateway”", "条件分支节点"), // TODO @jason是不是改成叫 条件分支
PARALLEL_BRANCH_NODE(52, "并行分支节点"), // TODO @jason是不是一个 并行分支 就可以啦 后面是否去掉并行网关只用包容网关 PARALLEL_BRANCH_NODE(52, "exclusiveGateway", "并行分支节点"), // TODO @jason是不是一个 并行分支 就可以啦 后面是否去掉并行网关只用包容网关
INCLUSIVE_BRANCH_NODE(53, "包容分支节点"), INCLUSIVE_BRANCH_NODE(53, "inclusiveGateway", "包容分支节点"),
// TODO @jason建议整合 join最终只有 条件分支并行分支包容分支三种~ // TODO @jason建议整合 join最终只有 条件分支并行分支包容分支三种~
// TODO @芋艿 感觉还是分开好理解一点,也好处理一点前端结构中把聚合节点显示并传过来 // TODO @芋艿 感觉还是分开好理解一点,也好处理一点前端结构中把聚合节点显示并传过来
; ;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
public static final String BPMN_USER_TASK_TYPE ="userTask";
private final Integer type; private final Integer type;
private final String bpmnType;
private final String name; private final String name;
/** /**
@ -48,7 +49,17 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
public static boolean isBranchNode(Integer type) { public static boolean isBranchNode(Integer type) {
return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) return Objects.equals(CONDITION_BRANCH_NODE.getType(), type)
|| Objects.equals(PARALLEL_BRANCH_NODE.getType(), type) || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type)
|| Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type) ; || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type);
}
/**
* 判断是否需要记录的节点
*
* @param bpmnType bpmn节点类型
*/
public static boolean isRecordNode(String bpmnType) {
return Objects.equals(APPROVE_NODE.getBpmnType(), bpmnType)
|| Objects.equals(END_NODE.getBpmnType(), bpmnType);
} }
public static BpmSimpleModelNodeType valueOf(Integer type) { public static BpmSimpleModelNodeType valueOf(Integer type) {

View File

@ -12,7 +12,7 @@ import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum BpmTaskStatusEnum { public enum BpmTaskStatusEnum {
NOT_START(-1, "未开始"),
RUNNING(1, "审批中"), RUNNING(1, "审批中"),
APPROVE(2, "审批通过"), APPROVE(2, "审批通过"),
REJECT(3, "审批不通过"), REJECT(3, "审批不通过"),
@ -57,5 +57,9 @@ public enum BpmTaskStatusEnum {
APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus(), APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus(),
RETURN.getStatus(), APPROVING.getStatus()); RETURN.getStatus(), APPROVING.getStatus());
} }
public static boolean isEndStatusButNotApproved(Integer status) {
return ObjectUtils.equalsAny(status,
REJECT.getStatus(), CANCEL.getStatus(), RETURN.getStatus());
}
} }

View File

@ -14,41 +14,35 @@ public class BpmProcessInstanceProgressRespVO {
@Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
private List<ProcessNodeProgress> nodeProgressList; @Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ApproveNodeInfo> approveNodeList;
@Schema(description = "节点进度信息") @Schema(description = "审批节点信息")
@Data @Data
public static class ProcessNodeProgress { public static class ApproveNodeInfo {
@Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode")
private String id; // Bpmn XML 节点 Id private String id;
@Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人") @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人")
private String name; private String name;
@Schema(description = "节点展示内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "指定成员: 芋道源码")
private String displayText;
@Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举 private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举
// TODO @jason可以复用 BpmTaskStatusEnum 非必要不加太多状态枚举哈
@Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status; // 参见 BpmProcessNodeProgressEnum 枚举 private Integer status; // 参见 BpmTaskStatusEnum 枚举
@Schema(description = "节点的开始时间") @Schema(description = "节点的开始时间")
private LocalDateTime startTime; private LocalDateTime startTime;
@Schema(description = "节点的结束时间") @Schema(description = "节点的结束时间")
private LocalDateTime endTime; private LocalDateTime endTime;
@Schema(description = "用户列表") @Schema(description = "审批节点的任务信息")
private List<User> userList; private List<ApproveTaskInfo> tasks;
// TODO @jason如果条件信息怎么展示哈 @Schema(description = "候选人用户列表")
@Schema(description = "分支节点") private List<User> candidateUserList; // 用于未运行任务节点
private List<ProcessNodeProgress> branchNodes; // 有且仅有条件并行包容节点才会有分支节点
// TODO 用户意见评论
} }
@ -65,14 +59,25 @@ public class BpmProcessInstanceProgressRespVO {
@Schema(description = "用户头像", example = "芋艿") @Schema(description = "用户头像", example = "芋艿")
private String avatar; private String avatar;
// TODO @jason是不是把 processed userTaskStatus 合并 }
@Schema(description = "审批任务信息")
@Data
public static class ApproveTaskInfo {
@Schema(description = "是否已处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") @Schema(description = "任务编号", example = "1")
private Boolean processed; private String id;
@Schema(description = "用户任务的处理状态", example = "1") @Schema(description = "任务所属人")
private Integer userTaskStatus; private User ownerUser;
@Schema(description = "任务分配人")
private User assigneeUser;
@Schema(description = "任务状态", example = "1")
private Integer status; // 参见 BpmTaskStatusEnum 枚举
@Schema(description = "审批意见", example = "同意")
private String reason;
} }
} }

View File

@ -109,4 +109,9 @@ public interface BpmnModelConstants {
*/ */
String START_EVENT_NODE_NAME = "开始"; String START_EVENT_NODE_NAME = "开始";
/**
* 发起人节点 Id
*/
String START_USER_NODE_ID = "StartUserNode";
} }

View File

@ -7,23 +7,23 @@ import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*; import cn.hutool.core.util.*;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.RejectHandler;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ProcessNodeProgress; import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
import cn.iocoder.yudao.module.bpm.enums.definition.*; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModeConditionType;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveMethodEnum;
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.listener.BpmCopyTaskDelegate; import cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups; import cn.iocoder.yudao.module.bpm.framework.flowable.core.simplemodel.SimpleModelConditionGroups;
import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
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.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import java.util.*; import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.OperationButtonSetting;
import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler; import static cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TimeoutHandler;
@ -218,7 +218,7 @@ public class SimpleModelUtils {
* *
* @param conditionNode 条件节点 * @param conditionNode 条件节点
*/ */
private static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) { public static String buildConditionExpression(BpmSimpleModelNodeVO conditionNode) {
Integer conditionType = MapUtil.getInt(conditionNode.getAttributes(), CONDITION_TYPE_ATTRIBUTE); Integer conditionType = MapUtil.getInt(conditionNode.getAttributes(), CONDITION_TYPE_ATTRIBUTE);
BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionType); BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(conditionType);
String conditionExpression = null; String conditionExpression = null;
@ -293,7 +293,7 @@ public class SimpleModelUtils {
traverseNodeToBuildFlowNode(node.getChildNode(), process); traverseNodeToBuildFlowNode(node.getChildNode(), process);
} }
private static boolean isValidNode(BpmSimpleModelNodeVO node) { public static boolean isValidNode(BpmSimpleModelNodeVO node) {
return node != null && node.getId() != null; return node != null && node.getId() != null;
} }
@ -635,117 +635,4 @@ public class SimpleModelUtils {
return endEvent; return endEvent;
} }
/**
* 遍历简单模型, 构建节点的进度 TODO 回退节点暂未处理
*
* @param processInstance 流程实例
* @param simpleModel 简单模型
* @param historicActivityList 流程实例的活力列表
* @param activityInstanceMap 流程实例的活力 Map key: activityId
* @param nodeProgresses 节点的进度列表
* @param returnNodePosition 回退节点的位置 TODO 处理回退节点还未处理还没想好
*/
public static void traverseNodeToBuildNodeProgress(HistoricProcessInstance processInstance, BpmSimpleModelNodeVO simpleModel
, List<HistoricActivityInstance> historicActivityList, Map<String, HistoricActivityInstance> activityInstanceMap
, List<ProcessNodeProgress> nodeProgresses, List<Integer> returnNodePosition) {
// 判断是否有效节点
if (!isValidNode(simpleModel)) {
return;
}
buildNodeProgress(processInstance, simpleModel, nodeProgresses, historicActivityList, activityInstanceMap, returnNodePosition);
// 如果有节点则递归处理子节点
// TODO @jason需要根据条件是否继续递归例如说一共有 3 node 2 node 审批不通过了那么第 3 node 就不用了微信讨论下
traverseNodeToBuildNodeProgress(processInstance, simpleModel.getChildNode(), historicActivityList, activityInstanceMap, nodeProgresses, returnNodePosition);
}
// TODO @芋艿@jason可重构优化的点SimpleModelUtils 负责提供一个遍历的方法有个 Function 进行每个节点的处理目的是把逻辑拿回到 Service 里面或者说减少 Utils 去调用 Service
private static void buildNodeProgress(HistoricProcessInstance processInstance, BpmSimpleModelNodeVO node, List<ProcessNodeProgress> nodeProgresses,
List<HistoricActivityInstance> historicActivityList, Map<String, HistoricActivityInstance> activityInstanceMap, List<Integer> returnNodePosition) {
BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType());
Assert.notNull(nodeType, "模型节点类型不支持");
ProcessNodeProgress nodeProgress = new ProcessNodeProgress();
nodeProgress.setNodeType(nodeType.getType());
nodeProgress.setName(node.getName());
nodeProgress.setDisplayText(node.getShowText());
BpmActivityService activityService = SpringUtils.getBean(BpmActivityService.class);
if (!activityInstanceMap.containsKey(node.getId())) { // 说明这些节点没有执行过
// 1. 得到流程状态
Integer processInstanceStatus = FlowableUtils.getProcessInstanceStatus(processInstance);
// 2. 设置节点状态
nodeProgress.setStatus(activityService.getNotRunActivityProgressStatus(processInstanceStatus));
// 3. 抄送节点, 审批节点设置用户列表
// TODO @芋艿抄送节点要不要跳过不展示
if (COPY_NODE.getType().equals(node.getType()) ||
(APPROVE_NODE.getType().equals(node.getType()) && USER.getType().equals(node.getApproveType()))) {
nodeProgress.setUserList(activityService.getNotRunActivityUserList(processInstance.getId()
, processInstanceStatus, node.getCandidateStrategy(), node.getCandidateParam()));
}
} else {
nodeProgress.setStatus(BpmProcessNodeProgressEnum.FINISHED.getStatus()); // 默认设置成结束状态
HistoricActivityInstance historicActivity = activityInstanceMap.get(node.getId());
nodeProgress.setStartTime(DateUtils.of(historicActivity.getStartTime()));
nodeProgress.setEndTime(DateUtils.of(historicActivity.getEndTime()));
nodeProgress.setId(historicActivity.getId());
switch (nodeType) {
case START_USER_NODE: { // 发起人节点
nodeProgress.setDisplayText(""); // 发起人节点不需要显示 displayText
// 1. 设置节点的状态
nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList));
// 2. 设置用户信息
nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, false, historicActivityList));
break;
}
case APPROVE_NODE: { // 审批节点
if (USER.getType().equals(node.getApproveType())) { // 人工审批
// 1. 判断是否多人审批
boolean isMultiInstance = !RANDOM.getMethod().equals(node.getApproveMethod());
// 2. 设置节点的状态
nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, isMultiInstance, historicActivityList));
// 3. 设置用户信息
nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, isMultiInstance, historicActivityList));
} else {
nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList));
}
break;
}
// TODO @芋艿抄送节点要不要跳过不展示
case COPY_NODE: { // 抄送节点
// 1. 设置节点的状态
nodeProgress.setStatus(activityService.getHistoricActivityProgressStatus(historicActivity, false, historicActivityList));
// 2. 设置用户信息
nodeProgress.setUserList(activityService.getHistoricActivityUserList(historicActivity, false, historicActivityList));
break;
}
default: {
// TODO 其它节点类型的实现
}
}
}
// 如果是分支节点
if (BpmSimpleModelNodeType.isBranchNode(node.getType())
&& ArrayUtil.isNotEmpty(node.getConditionNodes())) {
// 网关是否执行了 执行了只包含运行的分支 未执行包含所有的分支
final boolean executed = activityInstanceMap.containsKey(node.getId());
LinkedList<ProcessNodeProgress> branchNodeList = new LinkedList<>();
node.getConditionNodes().forEach(item -> {
// 如果条件节点执行了 ACT_HI_ACTINST 表会记录
if (executed) {
if (activityInstanceMap.containsKey(item.getId())) {
List<Integer> branchReturnNodePosition = new ArrayList<>();
traverseNodeToBuildNodeProgress(processInstance, item, historicActivityList, activityInstanceMap, branchNodeList, branchReturnNodePosition);
// TODO 处理回退节点
}
} else {
List<Integer> branchReturnNodePosition = new ArrayList<>();
traverseNodeToBuildNodeProgress(processInstance, item, historicActivityList, activityInstanceMap, branchNodeList, branchReturnNodePosition);
// TODO 处理回退节点
}
});
nodeProgress.setBranchNodes(branchNodeList);
}
nodeProgresses.add(nodeProgress);
}
} }

View File

@ -1,7 +1,5 @@
package cn.iocoder.yudao.module.bpm.service.task; package cn.iocoder.yudao.module.bpm.service.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricActivityInstance;
import java.util.List; import java.util.List;
@ -29,52 +27,4 @@ public interface BpmActivityService {
*/ */
List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId); List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId);
/**
* 获取活动的用户列表
*
* 例如抄送人列表审批人列表
*
* @param historicActivity 活动
* @param isMultiInstance 是否多实例 (会签或签 )
* @param historicActivityList 某个流程实例的所有活动列表
* @return 用户列表
*/
List<BpmProcessInstanceProgressRespVO.User> getHistoricActivityUserList(HistoricActivityInstance historicActivity,
Boolean isMultiInstance,
List<HistoricActivityInstance> historicActivityList);
/**
* 获取活动的进度状态
*
* @param historicActivity 活动
* @param isMultiInstance 是否多实例 (会签或签 )
* @param historicActivityList 某个流程实例的所有活动列表
* @return 活动的进度状态
*/
Integer getHistoricActivityProgressStatus(HistoricActivityInstance historicActivity,
Boolean isMultiInstance,
List<HistoricActivityInstance> historicActivityList);
/**
* 获取未执行活动的进度状态
*
* @param processInstanceStatus 流程实例的状态 {@link BpmProcessInstanceStatusEnum}
* @return 活动的进度状态
*/
Integer getNotRunActivityProgressStatus(Integer processInstanceStatus);
/**
* 获取未执行活动的用户列表
*
* @param processInstanceId 流程实例的编号
* @param processInstanceStatus 流程实例的状态 {@link BpmProcessInstanceStatusEnum}
* @param candidateStrategy 活动的候选人策略
* @param candidateParam 活动的候选人参数
* @return 用户列表
*/
List<BpmProcessInstanceProgressRespVO.User> getNotRunActivityUserList(String processInstanceId,
Integer processInstanceStatus,
Integer candidateStrategy,
String candidateParam);
} }

View File

@ -1,33 +1,13 @@
package cn.iocoder.yudao.module.bpm.service.task; package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.User;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmProcessNodeProgressEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService; import org.flowable.engine.HistoryService;
import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.module.bpm.enums.definition.BpmProcessNodeProgressEnum.*;
/** /**
@ -40,26 +20,8 @@ import static cn.iocoder.yudao.module.bpm.enums.definition.BpmProcessNodeProgres
@Validated @Validated
public class BpmActivityServiceImpl implements BpmActivityService { public class BpmActivityServiceImpl implements BpmActivityService {
/**
* 抄送节点活动类型
*/
private static final String COPY_NODE_ACTIVITY_TYPE = "serviceTask";
/**
* 审批节点活动类型
*/
private static final String APPROVE_NODE_ACTIVITY_TYPE = "userTask";
@Resource @Resource
private HistoryService historyService; private HistoryService historyService;
@Resource
@Lazy
private BpmTaskService bpmTaskService;
@Resource
private BpmProcessInstanceCopyService bpmProcessInstanceCopyService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private BpmTaskCandidateInvoker bpmTaskCandidateInvoker;
@Override @Override
public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) { public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) {
@ -72,130 +34,4 @@ public class BpmActivityServiceImpl implements BpmActivityService {
return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list();
} }
// TODO @芋艿重点在 review ~
@Override
public List<User> getHistoricActivityUserList(HistoricActivityInstance historicActivity
, Boolean isMultiInstance, List<HistoricActivityInstance> historicActivityList) {
Assert.notNull(historicActivity, "historicActivity 不能为 null ");
List<User> returnUserList = Collections.emptyList();
if (COPY_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) {
Set<Long> copyUserIds = bpmProcessInstanceCopyService.getCopyUserIds(historicActivity.getProcessInstanceId(),
historicActivity.getActivityId());
List<AdminUserRespDTO> userList = adminUserApi.getUserList(copyUserIds);
returnUserList = CollectionUtils.convertList(userList, item -> {
User user = BeanUtils.toBean(item, User.class);
user.setProcessed(Boolean.TRUE);
return user;
});
} else if (APPROVE_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) {
if (isMultiInstance) { // 多人 (会签 或签 // TODO 依次审批可能要特殊处理一下
// 多个任务列表
List<HistoricActivityInstance> taskList = CollectionUtils.filterList(historicActivityList,
item -> historicActivity.getActivityId().equals(item.getActivityId()));
List<Long> userIds = CollectionUtils.convertList(taskList, item -> NumberUtil.parseLong(item.getAssignee(), null));
List<String> taskIds = CollectionUtils.convertList(taskList, HistoricActivityInstance::getTaskId);
Map<Long, AdminUserRespDTO> adminUserMap = CollectionUtils.convertMap(adminUserApi.getUserList(userIds), AdminUserRespDTO::getId);
Map<String, HistoricTaskInstance> historicTaskInstanceMap = CollectionUtils.convertMap(bpmTaskService.getHistoricTasks(taskIds), HistoricTaskInstance::getId);
returnUserList = CollectionUtils.convertList(taskList, item -> {
AdminUserRespDTO adminUser = adminUserMap.get(NumberUtil.parseLong(item.getAssignee(), null));
User user = BeanUtils.toBean(adminUser, User.class);
if (user != null) {
HistoricTaskInstance taskInstance = historicTaskInstanceMap.get(item.getTaskId());
if (taskInstance != null) {
user.setProcessed(taskInstance.getEndTime() != null);
user.setUserTaskStatus(FlowableUtils.getTaskStatus(taskInstance));
}
}
return user;
});
} else {
AdminUserRespDTO adminUserResp = adminUserApi.getUser(Long.valueOf(historicActivity.getAssignee()));
if (adminUserResp != null) {
User user = BeanUtils.toBean(adminUserResp, User.class);
// TODO 需要处理加签
// 查询任务状态
HistoricTaskInstance historicTask = bpmTaskService.getHistoricTask(historicActivity.getTaskId());
if (historicTask != null) {
Integer taskStatus = FlowableUtils.getTaskStatus(historicTask);
user.setProcessed(historicTask.getEndTime() != null);
user.setUserTaskStatus(taskStatus);
}
returnUserList = ListUtil.of(user);
}
}
}
return returnUserList;
}
@Override
public Integer getHistoricActivityProgressStatus(HistoricActivityInstance historicActivity
, Boolean isMultiInstance, List<HistoricActivityInstance> historicActivityList) {
Assert.notNull(historicActivity, "historicActivity 不能为 null ");
Integer progressStatus = null;
if (APPROVE_NODE_ACTIVITY_TYPE.equals(historicActivity.getActivityType())) {
if (isMultiInstance) { // 多人 (会签 或签
// 多个任务列表
List<HistoricActivityInstance> taskList = CollectionUtils.filterList(historicActivityList,
item -> historicActivity.getActivityId().equals(item.getActivityId()));
List<String> taskIds = CollectionUtils.convertList(taskList, HistoricActivityInstance::getTaskId);
Map<String, HistoricTaskInstance> historicTaskMap = CollectionUtils.convertMap(bpmTaskService.getHistoricTasks(taskIds), HistoricTaskInstance::getId);
for (HistoricActivityInstance activity : taskList) {
if (activity.getEndTime() == null) {
progressStatus = RUNNING.getStatus();
} else {
HistoricTaskInstance task = historicTaskMap.get(activity.getTaskId());
if (task != null) {
Integer taskStatus = FlowableUtils.getTaskStatus(task);
progressStatus = BpmProcessNodeProgressEnum.convertBpmnTaskStatus(taskStatus);
}
}
// 运行中或者未通过状态退出循环 (会签可能需要多人通过
if (RUNNING.getStatus().equals(progressStatus) || isUserTaskNotApproved(progressStatus)) {
break;
}
}
} else {
HistoricTaskInstance historicTask = bpmTaskService.getHistoricTask(historicActivity.getTaskId());
if (historicTask != null) {
Integer taskStatus = FlowableUtils.getTaskStatus(historicTask);
progressStatus = BpmProcessNodeProgressEnum.convertBpmnTaskStatus(taskStatus);
}
}
} else {
if (historicActivity.getEndTime() == null) {
progressStatus = RUNNING.getStatus();
}else {
progressStatus = BpmProcessNodeProgressEnum.FINISHED.getStatus();
}
}
return progressStatus;
}
@Override
public Integer getNotRunActivityProgressStatus(Integer processInstanceStatus) {
if(BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)){
return SKIP.getStatus();
}else {
return NOT_START.getStatus();
}
}
@Override
public List<User> getNotRunActivityUserList(String processInstanceId, Integer processInstanceStatus, Integer candidateStrategy, String candidateParam) {
if(BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)){
// 跳过节点返回空
return Collections.emptyList();
}else {
BpmTaskCandidateStrategy taskCandidateStrategy = bpmTaskCandidateInvoker.getCandidateStrategy(candidateStrategy);
Set<Long> userIds = taskCandidateStrategy.calculateUsers(processInstanceId, candidateParam);
List<AdminUserRespDTO> userList = adminUserApi.getUserList(userIds);
return CollectionUtils.convertList(userList, item -> {
User user = BeanUtils.toBean(item, User.class);
user.setProcessed(Boolean.FALSE);
return user;
});
}
}
} }

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.bpm.service.task.bo;
import lombok.Data;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceProgressRespVO.ApproveNodeInfo;
/**
* @author jason
*/
@Data
public class AlreadyRunApproveNodeRespBO {
private List<ApproveNodeInfo> approveNodeList;
private Set<String> runNodeIds;
}