mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-11-02 13:31:49 +08:00
fix 修复 待审批的节点无法展示审批人信息问题
fix 修复 流程发布接口未适配事件节点 发布提示没有审批人问题
This commit is contained in:
parent
6daa3162eb
commit
1f618c6909
@ -20,10 +20,15 @@ import org.dromara.warm.flow.core.dto.PromptContent;
|
||||
import org.dromara.warm.flow.core.enums.NodeType;
|
||||
import org.dromara.warm.flow.core.utils.MapUtil;
|
||||
import org.dromara.warm.flow.orm.entity.FlowHisTask;
|
||||
import org.dromara.warm.flow.orm.entity.FlowTask;
|
||||
import org.dromara.warm.flow.orm.entity.FlowUser;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowUserMapper;
|
||||
import org.dromara.warm.flow.ui.service.ChartExtService;
|
||||
import org.dromara.workflow.common.ConditionalOnEnable;
|
||||
import org.dromara.workflow.common.constant.FlowConstant;
|
||||
import org.dromara.workflow.common.enums.TaskAssigneeType;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -45,6 +50,8 @@ public class FlwChartExtServiceImpl implements ChartExtService {
|
||||
private final UserService userService;
|
||||
private final DeptService deptService;
|
||||
private final FlowHisTaskMapper flowHisTaskMapper;
|
||||
private final FlowTaskMapper flowTaskMapper;
|
||||
private final FlowUserMapper flowUserMapper;
|
||||
private final DictService dictService;
|
||||
@Value("${warm-flow.node-tooltip:true}")
|
||||
private boolean nodeTooltip;
|
||||
@ -61,45 +68,80 @@ public class FlwChartExtServiceImpl implements ChartExtService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据流程实例ID查询所有相关的历史任务列表
|
||||
List<FlowHisTask> flowHisTasks = this.getHisTaskGroupedByNode(defJson.getInstance().getId());
|
||||
if (CollUtil.isEmpty(flowHisTasks)) {
|
||||
Long instanceId = defJson.getInstance().getId();
|
||||
List<FlowHisTask> flowHisTasks = this.getHisTaskGroupedByNode(instanceId);
|
||||
List<FlowTask> activeTasks = flowTaskMapper.selectList(Wrappers.<FlowTask>lambdaQuery()
|
||||
.eq(FlowTask::getInstanceId, instanceId)
|
||||
.eq(FlowTask::getNodeType, NodeType.BETWEEN.getKey()));
|
||||
if (CollUtil.isEmpty(flowHisTasks) && CollUtil.isEmpty(activeTasks)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 按节点编号(nodeCode)对历史任务进行分组
|
||||
Map<String, List<FlowHisTask>> groupedByNode = StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode);
|
||||
Map<String, List<FlowHisTask>> groupedByNode = CollUtil.isEmpty(flowHisTasks)
|
||||
? Collections.emptyMap() : StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode);
|
||||
Map<Long, String> taskNodeCodes = activeTasks.stream()
|
||||
.collect(Collectors.toMap(FlowTask::getId, FlowTask::getNodeCode));
|
||||
List<FlowUser> activeUsers = taskNodeCodes.isEmpty() ? Collections.emptyList()
|
||||
: flowUserMapper.selectList(Wrappers.<FlowUser>lambdaQuery()
|
||||
.in(FlowUser::getAssociated, taskNodeCodes.keySet())
|
||||
.in(FlowUser::getType, List.of(TaskAssigneeType.APPROVER.getCode(),
|
||||
TaskAssigneeType.TRANSFER.getCode(), TaskAssigneeType.DELEGATE.getCode())));
|
||||
Map<String, Set<Long>> pendingUsersByNode = new HashMap<>();
|
||||
for (FlowUser activeUser : activeUsers) {
|
||||
Long userId = Convert.toLong(activeUser.getProcessedBy(), null);
|
||||
String nodeCode = taskNodeCodes.get(activeUser.getAssociated());
|
||||
if (userId != null && nodeCode != null) {
|
||||
pendingUsersByNode.computeIfAbsent(nodeCode, key -> new LinkedHashSet<>()).add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询所有审批人的用户信息
|
||||
List<UserDTO> userDTOList = userService.selectListByIds(StreamUtils.toSet(flowHisTasks, e -> Convert.toLong(e.getApprover())));
|
||||
Set<Long> userIds = new HashSet<>();
|
||||
flowHisTasks.forEach(task -> {
|
||||
Long approverId = Convert.toLong(task.getApprover(), null);
|
||||
if (approverId != null) {
|
||||
userIds.add(approverId);
|
||||
}
|
||||
});
|
||||
pendingUsersByNode.values().forEach(userIds::addAll);
|
||||
if (userIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<UserDTO> userDTOList = userService.selectListByIds(userIds);
|
||||
|
||||
// 将查询到的用户列表转换为以用户ID为key的映射
|
||||
Map<Long, UserDTO> userMap = StreamUtils.toIdentityMap(userDTOList, UserDTO::getUserId);
|
||||
Set<Long> deptIds = StreamUtils.toSet(userDTOList, UserDTO::getDeptId);
|
||||
Map<Long, String> deptNameMap = deptService.selectDeptNamesByIds(deptIds);
|
||||
deptIds.remove(null);
|
||||
Map<Long, String> deptNameMap = deptIds.isEmpty() ? Collections.emptyMap()
|
||||
: deptService.selectDeptNamesByIds(deptIds);
|
||||
|
||||
Map<String, String> dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS);
|
||||
Map<String, String> dictType = flowHisTasks.isEmpty() ? Collections.emptyMap()
|
||||
: dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS);
|
||||
|
||||
for (NodeJson nodeJson : defJson.getNodeList()) {
|
||||
List<FlowHisTask> taskList = groupedByNode.get(nodeJson.getNodeCode());
|
||||
if (CollUtil.isEmpty(taskList)) {
|
||||
continue;
|
||||
if (CollUtil.isNotEmpty(taskList)) {
|
||||
List<FlowHisTask> latestPerApprover = taskList.stream()
|
||||
.filter(task -> StringUtils.isNotBlank(task.getApprover()))
|
||||
.collect(Collectors.collectingAndThen(
|
||||
Collectors.toMap(
|
||||
FlowHisTask::getApprover,
|
||||
Function.identity(),
|
||||
(oldTask, newTask) -> newTask.getUpdateTime().after(oldTask.getUpdateTime()) ? newTask : oldTask,
|
||||
LinkedHashMap::new
|
||||
),
|
||||
map -> new ArrayList<>(map.values())
|
||||
));
|
||||
this.processNodeExtInfo(nodeJson, latestPerApprover, userMap, deptNameMap, dictType);
|
||||
}
|
||||
for (Long userId : pendingUsersByNode.getOrDefault(nodeJson.getNodeCode(), Collections.emptySet())) {
|
||||
UserDTO user = userMap.get(userId);
|
||||
if (user != null) {
|
||||
addUserInfo(nodeJson.getPromptContent().getInfo(), user,
|
||||
deptNameMap.getOrDefault(user.getDeptId(), StringUtils.EMPTY));
|
||||
nodeJson.getPromptContent().getInfo().add(buildInfoItem("审批状态", "待审批"));
|
||||
}
|
||||
}
|
||||
|
||||
// 按审批人分组去重,保留最新处理记录,最终转换成 List
|
||||
List<FlowHisTask> latestPerApprover = taskList.stream()
|
||||
.collect(Collectors.collectingAndThen(
|
||||
Collectors.toMap(
|
||||
FlowHisTask::getApprover,
|
||||
Function.identity(),
|
||||
(oldTask, newTask) -> newTask.getUpdateTime().after(oldTask.getUpdateTime()) ? newTask : oldTask,
|
||||
LinkedHashMap::new
|
||||
),
|
||||
map -> new ArrayList<>(map.values())
|
||||
));
|
||||
|
||||
// 处理当前节点的扩展信息
|
||||
this.processNodeExtInfo(nodeJson, latestPerApprover, userMap, deptNameMap, dictType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,10 +225,16 @@ public class FlwChartExtServiceImpl implements ChartExtService {
|
||||
continue;
|
||||
}
|
||||
|
||||
String deptName = deptNameMap.getOrDefault(userDTO.getDeptId(), StringUtils.EMPTY);
|
||||
addUserInfo(info, userDTO, deptNameMap.getOrDefault(userDTO.getDeptId(), StringUtils.EMPTY));
|
||||
|
||||
// 添加标题项,如:👤 张三(市场部)
|
||||
info.add(new PromptContent.InfoItem()
|
||||
info.add(buildInfoItem("审批状态", dictType.get(task.getFlowStatus())));
|
||||
info.add(buildInfoItem("审批耗时", DateUtils.formatBetweenBySecond(task.getUpdateTime(), task.getCreateTime())));
|
||||
info.add(buildInfoItem("办理时间", DateUtils.formatDateTime(task.getUpdateTime())));
|
||||
}
|
||||
}
|
||||
|
||||
private void addUserInfo(List<PromptContent.InfoItem> info, UserDTO userDTO, String deptName) {
|
||||
info.add(new PromptContent.InfoItem()
|
||||
.setPrefix(StringUtils.format("👥 {}({})", userDTO.getNickName(), deptName))
|
||||
.setPrefixStyle(Map.of(
|
||||
"fontWeight", "bold",
|
||||
@ -199,12 +247,7 @@ public class FlwChartExtServiceImpl implements ChartExtService {
|
||||
))
|
||||
);
|
||||
|
||||
// 添加具体信息项:账号、耗时、时间
|
||||
info.add(buildInfoItem("用户账号", userDTO.getUserName()));
|
||||
info.add(buildInfoItem("审批状态", dictType.get(task.getFlowStatus())));
|
||||
info.add(buildInfoItem("审批耗时", DateUtils.formatBetweenBySecond(task.getUpdateTime(), task.getCreateTime())));
|
||||
info.add(buildInfoItem("办理时间", DateUtils.formatDateTime(task.getUpdateTime())));
|
||||
}
|
||||
info.add(buildInfoItem("用户账号", userDTO.getUserName()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -126,7 +126,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
|
||||
if (CollUtil.isNotEmpty(flowNodes)) {
|
||||
String applyNodeCode = flwCommonService.applyNodeCode(id);
|
||||
for (FlowNode flowNode : flowNodes) {
|
||||
if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode()) && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) {
|
||||
if (requiresAssignee(flowNode, applyNodeCode) && StringUtils.isBlank(flowNode.getPermissionFlag())) {
|
||||
errorMsg.add(flowNode.getNodeName());
|
||||
}
|
||||
}
|
||||
@ -137,6 +137,12 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
|
||||
return defService.publish(id);
|
||||
}
|
||||
|
||||
static boolean requiresAssignee(FlowNode node, String applyNodeCode) {
|
||||
return NodeType.BETWEEN.getKey().equals(node.getNodeType())
|
||||
&& !applyNodeCode.equals(node.getNodeCode())
|
||||
&& StringUtils.isBlank(node.getEventType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入流程定义
|
||||
*
|
||||
|
||||
@ -0,0 +1,81 @@
|
||||
package org.dromara.workflow.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.dromara.common.core.service.DictService;
|
||||
import org.dromara.system.api.DeptService;
|
||||
import org.dromara.system.api.UserService;
|
||||
import org.dromara.system.api.domain.UserDTO;
|
||||
import org.dromara.warm.flow.core.dto.DefJson;
|
||||
import org.dromara.warm.flow.core.dto.NodeJson;
|
||||
import org.dromara.warm.flow.orm.entity.FlowInstance;
|
||||
import org.dromara.warm.flow.orm.entity.FlowTask;
|
||||
import org.dromara.warm.flow.orm.entity.FlowUser;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowUserMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class FlwChartExtServiceImplTest {
|
||||
|
||||
@Test
|
||||
void showsCurrentAssigneeWithoutApprovalHistory() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"), FlowUser.class);
|
||||
UserService userService = mock(UserService.class);
|
||||
DeptService deptService = mock(DeptService.class);
|
||||
FlowHisTaskMapper historyMapper = mock(FlowHisTaskMapper.class);
|
||||
FlowTaskMapper taskMapper = mock(FlowTaskMapper.class);
|
||||
FlowUserMapper flowUserMapper = mock(FlowUserMapper.class);
|
||||
DictService dictService = mock(DictService.class);
|
||||
FlwChartExtServiceImpl service = new FlwChartExtServiceImpl(
|
||||
userService, deptService, historyMapper, taskMapper, flowUserMapper, dictService);
|
||||
ReflectionTestUtils.setField(service, "nodeTooltip", true);
|
||||
|
||||
NodeJson node = new NodeJson().setNodeCode("approve").setNodeName("部门审批");
|
||||
DefJson definition = new DefJson()
|
||||
.setFlowName("请假申请")
|
||||
.setInstance(new FlowInstance().setId(1L))
|
||||
.setNodeList(List.of(node));
|
||||
service.initPromptContent(definition);
|
||||
|
||||
when(historyMapper.selectList(any())).thenReturn(List.of());
|
||||
when(taskMapper.selectList(any())).thenReturn(List.of(
|
||||
new FlowTask().setId(10L).setNodeCode("approve")));
|
||||
when(flowUserMapper.selectList(any())).thenReturn(List.of(
|
||||
new FlowUser().setAssociated(10L).setType("1").setProcessedBy("7")));
|
||||
UserDTO user = new UserDTO();
|
||||
user.setUserId(7L);
|
||||
user.setDeptId(3L);
|
||||
user.setNickName("张三");
|
||||
user.setUserName("zhangsan");
|
||||
when(userService.selectListByIds(any())).thenReturn(List.of(user));
|
||||
when(deptService.selectDeptNamesByIds(any())).thenReturn(Map.of(3L, "研发部"));
|
||||
when(dictService.getAllDictByDictType(any())).thenReturn(Map.of());
|
||||
|
||||
service.execute(definition);
|
||||
|
||||
var info = node.getPromptContent().getInfo();
|
||||
assertTrue(info.stream().anyMatch(item -> item.getPrefix().contains("张三")
|
||||
&& item.getPrefix().contains("研发部")));
|
||||
assertTrue(info.stream().anyMatch(item -> "用户账号: ".equals(item.getPrefix())
|
||||
&& "zhangsan".equals(item.getContent())));
|
||||
assertTrue(info.stream().anyMatch(item -> "审批状态: ".equals(item.getPrefix())
|
||||
&& "待审批".equals(item.getContent())));
|
||||
verify(userService).selectListByIds(argThat(ids -> ids.size() == 1 && ids.contains(7L)));
|
||||
verify(deptService).selectDeptNamesByIds(argThat(ids -> ids.size() == 1 && ids.contains(3L)));
|
||||
assertEquals(4, info.size());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package org.dromara.workflow.service.impl;
|
||||
|
||||
import org.dromara.warm.flow.core.enums.NodeType;
|
||||
import org.dromara.warm.flow.orm.entity.FlowNode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class FlwDefinitionServiceImplTest {
|
||||
|
||||
@Test
|
||||
void eventNodesDoNotRequireAssignee() {
|
||||
for (String eventType : new String[]{"MESSAGE_START", "TIMER_CATCH", "ERROR_BOUNDARY", "TERMINATE_END"}) {
|
||||
FlowNode node = new FlowNode()
|
||||
.setNodeType(NodeType.BETWEEN.getKey())
|
||||
.setNodeCode("event")
|
||||
.setEventType(eventType);
|
||||
assertFalse(FlwDefinitionServiceImpl.requiresAssignee(node, "apply"), eventType);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualApprovalStillRequiresAssignee() {
|
||||
FlowNode approval = new FlowNode()
|
||||
.setNodeType(NodeType.BETWEEN.getKey())
|
||||
.setNodeCode("approve");
|
||||
assertTrue(FlwDefinitionServiceImpl.requiresAssignee(approval, "apply"));
|
||||
assertFalse(FlwDefinitionServiceImpl.requiresAssignee(approval, "approve"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serviceTaskDoesNotRequireAssignee() {
|
||||
FlowNode service = new FlowNode()
|
||||
.setNodeType(NodeType.SERVICE.getKey())
|
||||
.setNodeCode("service");
|
||||
assertFalse(FlwDefinitionServiceImpl.requiresAssignee(service, "apply"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user