package com.sinata.appointment.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.sinata.appointment.constants.ApprovalNodeCode; import com.sinata.appointment.constants.ApprovalTaskStatus; import com.sinata.appointment.dto.ApprovalFlowGroupVo; import com.sinata.appointment.dto.ApprovalFlowNodeVo; import com.sinata.appointment.dto.ApprovalFlowTaskUserVo; import com.sinata.appointment.dto.MiniVisitedUserVo; import com.sinata.appointment.entity.AppointmentOrder; import com.sinata.appointment.entity.ApprovalInstance; import com.sinata.appointment.entity.ApprovalTask; import com.sinata.appointment.entity.BizApprovalNodeConfig; import com.sinata.appointment.entity.MpStaffGroup; import com.sinata.appointment.entity.MpUser; import com.sinata.appointment.entity.SysDept; import com.sinata.appointment.mapper.AppointmentOrderMapper; import com.sinata.appointment.mapper.ApprovalInstanceMapper; import com.sinata.appointment.mapper.ApprovalTaskMapper; import com.sinata.appointment.mapper.BizApprovalNodeConfigMapper; import com.sinata.appointment.mapper.MpStaffGroupMapper; import com.sinata.appointment.mapper.MpUserMapper; import com.sinata.appointment.mapper.SysDeptMapper; import com.sinata.appointment.util.UuidAssert; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor public class ApprovalFlowViewApplicationService { private static final TypeReference> STRING_LIST_TYPE = new TypeReference<>() {}; private final ApprovalInstanceMapper approvalInstanceMapper; private final ApprovalTaskMapper approvalTaskMapper; private final BizApprovalNodeConfigMapper bizApprovalNodeConfigMapper; private final AppointmentOrderMapper appointmentOrderMapper; private final MpUserMapper mpUserMapper; private final SysDeptMapper sysDeptMapper; private final MpStaffGroupMapper mpStaffGroupMapper; private final ObjectMapper objectMapper; public List treeByOrderId(Long orderId) { return treeByOrderId(orderId, null); } public List treeByOrderId(Long orderId, String orderUuid) { if (orderId == null || orderId <= 0) { return List.of(); } ApprovalInstance inst = loadLatestInstance(orderId); if (inst == null) { return List.of(); } AppointmentOrder order = appointmentOrderMapper.selectById(orderId); if (order != null && StringUtils.hasText(orderUuid)) { UuidAssert.match(order, orderUuid); } List tasks = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getDelFlag, 0) .eq(ApprovalTask::getInstanceId, inst.getId()) .orderByAsc(ApprovalTask::getCreateTime) .orderByAsc(ApprovalTask::getId)); if (tasks.isEmpty()) { return List.of(); } Map nodeNameMap = loadNodeNameMap(); Map userMap = loadUserMap(tasks); Map deptNameMap = loadDeptNameMap(tasks); Map staffGroupNameMap = loadStaffGroupNameMap(tasks); List localUnitNames = parseStringList(order == null ? null : order.getPassLocalUnitNames()); LinkedHashMap>> byNode = new LinkedHashMap<>(); for (ApprovalTask t : tasks) { byNode.computeIfAbsent(t.getNodeCode(), k -> new LinkedHashMap<>()) .computeIfAbsent(t.getGroupKey(), k -> new ArrayList<>()) .add(t); } List out = new ArrayList<>(); int tmpIdx = 1; for (Map.Entry>> nodeEntry : byNode.entrySet()) { String nodeCode = nodeEntry.getKey(); ApprovalFlowNodeVo nodeVo = new ApprovalFlowNodeVo(); nodeVo.setNodeCode(nodeCode); nodeVo.setNodeName(nodeNameMap.getOrDefault(nodeCode, nodeCode)); for (Map.Entry> groupEntry : nodeEntry.getValue().entrySet()) { String groupKey = groupEntry.getKey(); List groupTasks = groupEntry.getValue(); ApprovalFlowGroupVo g = new ApprovalFlowGroupVo(); g.setGroupKey(groupKey); GroupMeta meta = resolveGroupMeta(nodeCode, groupKey, deptNameMap, staffGroupNameMap, localUnitNames, tmpIdx); if ("TEMP_GROUP".equals(meta.groupType())) { tmpIdx++; } g.setGroupType(meta.groupType()); g.setGroupName(meta.groupName()); g.setGroupStatus(calcStatus(groupTasks)); for (ApprovalTask t : groupTasks) { ApprovalFlowTaskUserVo u = new ApprovalFlowTaskUserVo(); u.setTaskId(t.getId()); u.setUserId(t.getAssigneeUserId()); MpUser mp = userMap.get(t.getAssigneeUserId()); u.setUserName(resolveUserName(mp)); u.setAvatarUrl(mp == null ? null : mp.getAvatarUrl()); u.setTaskStatus(t.getTaskStatus()); u.setComment(t.getComment()); u.setActionTime(t.getUpdateTime()); u.setNoticeUsers(loadNoticeUsers(t.getNoticeIds(), userMap, deptNameMap)); g.getUsers().add(u); } nodeVo.getGroups().add(g); } nodeVo.setNodeStatus(calcNodeStatus(nodeVo.getGroups())); out.add(nodeVo); } return out; } private ApprovalInstance loadLatestInstance(Long orderId) { List list = approvalInstanceMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalInstance::getDelFlag, 0) .eq(ApprovalInstance::getOrderId, orderId) .orderByDesc(ApprovalInstance::getId)); return list.isEmpty() ? null : list.get(0); } private Map loadNodeNameMap() { List rows = bizApprovalNodeConfigMapper.selectList( new LambdaQueryWrapper() .eq(BizApprovalNodeConfig::getDelFlag, 0) .orderByAsc(BizApprovalNodeConfig::getSortOrder)); Map map = new LinkedHashMap<>(); for (BizApprovalNodeConfig row : rows) { map.put(row.getNodeCode(), row.getNodeName()); } return map; } private Map loadUserMap(List tasks) { Set ids = new LinkedHashSet<>(); for (ApprovalTask t : tasks) { if (t.getAssigneeUserId() != null && t.getAssigneeUserId() > 0) { ids.add(t.getAssigneeUserId()); } } if (ids.isEmpty()) { return Map.of(); } List rows = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .in(MpUser::getId, ids)); Map out = new LinkedHashMap<>(); for (MpUser row : rows) { out.put(row.getId(), row); } return out; } private Map loadDeptNameMap(List tasks) { Set deptIds = new LinkedHashSet<>(); for (ApprovalTask t : tasks) { Long deptId = parseTailId(t.getGroupKey(), ":DEPT:"); if (deptId != null && deptId > 0) { deptIds.add(deptId); } } if (deptIds.isEmpty()) { return Map.of(); } List rows = sysDeptMapper.selectList( new LambdaQueryWrapper() .eq(SysDept::getDelFlag, 0) .in(SysDept::getId, deptIds)); Map out = new LinkedHashMap<>(); for (SysDept row : rows) { out.put(row.getId(), row.getDeptName()); } return out; } private Map loadStaffGroupNameMap(List tasks) { Set ids = new LinkedHashSet<>(); for (ApprovalTask t : tasks) { Long gid = parseTailId(t.getGroupKey(), ":GROUP:"); if (gid != null && gid > 0) { ids.add(gid); } } if (ids.isEmpty()) { return Map.of(); } List rows = mpStaffGroupMapper.selectList( new LambdaQueryWrapper() .eq(MpStaffGroup::getDelFlag, 0) .in(MpStaffGroup::getId, ids)); Map out = new LinkedHashMap<>(); for (MpStaffGroup row : rows) { out.put(row.getId(), row.getGroupName()); } return out; } private GroupMeta resolveGroupMeta( String nodeCode, String groupKey, Map deptNameMap, Map staffGroupNameMap, List localUnitNames, int tmpIdx) { if (!ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER.equals(nodeCode)) { return new GroupMeta("DEFAULT", "审批人"); } Long deptId = parseTailId(groupKey, ":DEPT:"); if (deptId != null) { String name = deptNameMap.getOrDefault(deptId, "部门#" + deptId); return new GroupMeta("DEPT", name + "审批组"); } Long gid = parseTailId(groupKey, ":GROUP:"); if (gid != null) { String name = staffGroupNameMap.getOrDefault(gid, "分组#" + gid); return new GroupMeta("STAFF_GROUP", name + "审批组"); } if (groupKey != null && groupKey.contains(":TMP:")) { String unitName = localUnitNames.isEmpty() ? null : (localUnitNames.size() == 1 ? localUnitNames.get(0) : localUnitNames.get(Math.min(tmpIdx - 1, localUnitNames.size() - 1))); String label = StringUtils.hasText(unitName) ? unitName + "临时负责组" : "临时负责组"; return new GroupMeta("TEMP_GROUP", label); } return new GroupMeta("DEFAULT", "审批组"); } private static Long parseTailId(String groupKey, String token) { if (!StringUtils.hasText(groupKey) || !groupKey.contains(token)) { return null; } int idx = groupKey.lastIndexOf(token); if (idx < 0) { return null; } String raw = groupKey.substring(idx + token.length()); if (!StringUtils.hasText(raw)) { return null; } try { return Long.parseLong(raw.trim()); } catch (Exception ex) { return null; } } private List parseStringList(String json) { if (!StringUtils.hasText(json)) { return List.of(); } try { List rows = objectMapper.readValue(json, STRING_LIST_TYPE); if (rows == null) { return List.of(); } List out = new ArrayList<>(); for (String row : rows) { if (StringUtils.hasText(row)) { out.add(row.trim()); } } return out; } catch (Exception ex) { return List.of(); } } private List loadNoticeUsers( String noticeIds, Map existingUserMap, Map existingDeptNameMap) { List ids = parseCommaSeparatedIds(noticeIds); if (ids.isEmpty()) { return List.of(); } Map userMap = existingUserMap; LinkedHashSet missingIds = ids.stream() .filter(id -> !existingUserMap.containsKey(id)) .collect(Collectors.toCollection(LinkedHashSet::new)); if (!missingIds.isEmpty()) { List extraUsers = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .in(MpUser::getId, missingIds)); userMap = new LinkedHashMap<>(existingUserMap); for (MpUser extraUser : extraUsers) { userMap.put(extraUser.getId(), extraUser); } } Map deptNameMap = existingDeptNameMap; LinkedHashSet missingDeptIds = ids.stream() .map(userMap::get) .filter(Objects::nonNull) .map(MpUser::getDeptId) .filter(Objects::nonNull) .filter(id -> id > 0 && !existingDeptNameMap.containsKey(id)) .collect(Collectors.toCollection(LinkedHashSet::new)); if (!missingDeptIds.isEmpty()) { deptNameMap = new LinkedHashMap<>(existingDeptNameMap); List depts = sysDeptMapper.selectList( new LambdaQueryWrapper() .eq(SysDept::getDelFlag, 0) .in(SysDept::getId, missingDeptIds)); for (SysDept dept : depts) { deptNameMap.put(dept.getId(), dept.getDeptName()); } } List result = new ArrayList<>(); for (Long id : ids) { MpUser user = userMap.get(id); if (user == null) { continue; } MiniVisitedUserVo vo = new MiniVisitedUserVo(); vo.setId(user.getId()); vo.setRealName(resolveUserName(user)); vo.setDeptName(user.getDeptId() == null ? null : deptNameMap.get(user.getDeptId())); result.add(vo); } return result; } private List parseCommaSeparatedIds(String raw) { if (!StringUtils.hasText(raw)) { return List.of(); } LinkedHashSet out = new LinkedHashSet<>(); for (String part : raw.split(",")) { if (!StringUtils.hasText(part)) { continue; } try { long id = Long.parseLong(part.trim()); if (id > 0) { out.add(id); } } catch (NumberFormatException ignored) { } } return new ArrayList<>(out); } private String calcNodeStatus(List groups) { if (groups.isEmpty()) { return ApprovalTaskStatus.WAITING; } boolean hasRejected = false; boolean hasCancelled = false; boolean hasPending = false; boolean hasWaiting = false; for (ApprovalFlowGroupVo g : groups) { String s = g.getGroupStatus(); if (ApprovalTaskStatus.REJECTED.equals(s)) { hasRejected = true; } else if (ApprovalTaskStatus.CANCELLED.equals(s)) { hasCancelled = true; } else if (ApprovalTaskStatus.PENDING.equals(s)) { hasPending = true; } else if (ApprovalTaskStatus.WAITING.equals(s)) { hasWaiting = true; } } if (hasRejected) { return ApprovalTaskStatus.REJECTED; } if (hasCancelled) { return ApprovalTaskStatus.CANCELLED; } if (hasPending) { return ApprovalTaskStatus.PENDING; } if (hasWaiting) { return ApprovalTaskStatus.WAITING; } return ApprovalTaskStatus.APPROVED; } private String calcStatus(List tasks) { if (tasks == null || tasks.isEmpty()) { return ApprovalTaskStatus.WAITING; } boolean hasRejected = false; boolean hasCancelled = false; boolean hasPending = false; boolean hasWaiting = false; for (ApprovalTask t : tasks) { if (ApprovalTaskStatus.REJECTED.equals(t.getTaskStatus())) { hasRejected = true; } else if (ApprovalTaskStatus.CANCELLED.equals(t.getTaskStatus())) { hasCancelled = true; } else if (ApprovalTaskStatus.PENDING.equals(t.getTaskStatus())) { hasPending = true; } else if (ApprovalTaskStatus.WAITING.equals(t.getTaskStatus())) { hasWaiting = true; } } if (hasRejected) { return ApprovalTaskStatus.REJECTED; } if (hasCancelled) { return ApprovalTaskStatus.CANCELLED; } if (hasPending) { return ApprovalTaskStatus.PENDING; } if (hasWaiting) { return ApprovalTaskStatus.WAITING; } return ApprovalTaskStatus.APPROVED; } private static String resolveUserName(MpUser user) { if (user == null) { return ""; } if (StringUtils.hasText(user.getNickName())) { return user.getNickName(); } if (StringUtils.hasText(user.getStaffNo())) { return user.getStaffNo(); } return String.valueOf(Objects.requireNonNullElse(user.getId(), 0L)); } private record GroupMeta(String groupType, String groupName) {} }