package com.sinata.appointment.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.sinata.appointment.constants.BizErrorCode; import com.sinata.appointment.constants.MiniUserType; import com.sinata.appointment.dto.DeptCreateRequest; import com.sinata.appointment.dto.DeptTreeNodeVo; import com.sinata.appointment.dto.DeptUpdateRequest; import com.sinata.appointment.entity.MpUser; import com.sinata.appointment.entity.SysDept; import com.sinata.appointment.exception.BizException; 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.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor public class DeptManageApplicationService { private static final int MAX_LEVEL = 5; private final SysDeptMapper sysDeptMapper; private final MpUserMapper mpUserMapper; public List tree() { List all = sysDeptMapper.selectList( new LambdaQueryWrapper() .eq(SysDept::getDelFlag, 0) .orderByAsc(SysDept::getSortOrder) .orderByAsc(SysDept::getId)); Map vos = new HashMap<>(); for (SysDept d : all) { DeptTreeNodeVo v = toVo(d); v.setChildren(new ArrayList<>()); vos.put(d.getId(), v); } List roots = new ArrayList<>(); for (SysDept d : all) { DeptTreeNodeVo v = vos.get(d.getId()); long pid = d.getParentId() == null ? 0L : d.getParentId(); if (pid == 0L) { roots.add(v); } else { DeptTreeNodeVo p = vos.get(pid); if (p != null) { p.getChildren().add(v); } else { roots.add(v); } } } sortChildrenRecursive(roots); for (DeptTreeNodeVo r : roots) { assignLevel(r, 1); } return roots; } @Transactional(rollbackFor = Exception.class) public Map create(DeptCreateRequest req) { long pid = req.getParentId() == null ? 0L : req.getParentId(); if (pid != 0L) { SysDept parent = loadActive(pid); int parentDepth = depthOf(parent.getId()); if (parentDepth >= MAX_LEVEL) { throw new BizException(BizErrorCode.DEPT_MAX_DEPTH, "无法在第五级下再添加下级部门"); } if (parentDepth + 1 > MAX_LEVEL) { throw new BizException(BizErrorCode.DEPT_MAX_DEPTH, "部门层级不能超过 " + MAX_LEVEL + " 级"); } } SysDept d = new SysDept(); d.setParentId(pid); d.setDeptName(trimName(req.getDeptName())); d.setStatus(normalizeStatus(req.getStatus())); d.setSortOrder(0); applyDeptLeaders(d, req.getLeaderIds()); sysDeptMapper.insert(d); SysDept fresh = sysDeptMapper.selectById(d.getId()); if (fresh == null || !StringUtils.hasText(fresh.getUuid())) { throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "创建后读取部门失败"); } Map m = new HashMap<>(); m.put("id", fresh.getId()); m.put("uuid", fresh.getUuid()); return m; } @Transactional(rollbackFor = Exception.class) public void update(Long id, DeptUpdateRequest req) { SysDept d = loadActiveWithUuid(id, req.getUuid()); d.setDeptName(trimName(req.getDeptName())); d.setStatus(normalizeStatus(req.getStatus())); applyDeptLeaders(d, req.getLeaderIds()); Long oldParent = d.getParentId() == null ? 0L : d.getParentId(); if (oldParent == 0L) { sysDeptMapper.updateById(d); return; } long newPid = req.getParentId() != null ? req.getParentId() : oldParent; if (newPid == 0L) { throw new BizException(BizErrorCode.DEPT_PARENT_INVALID, "非一级部门不能改为无上级"); } if (newPid != oldParent) { validateReparent(id, newPid); d.setParentId(newPid); } sysDeptMapper.updateById(d); } @Transactional(rollbackFor = Exception.class) public void delete(Long id, String uuid) { SysDept d = loadActiveWithUuid(id, uuid); Set deptScope = collectSubtreeDeptIds(id); long staffCnt = mpUserMapper.selectCount( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .eq(MpUser::getUserType, "EMPLOYEE") .in(MpUser::getDeptId, deptScope)); if (staffCnt > 0) { throw new BizException(BizErrorCode.DEPT_HAS_BOUND_STAFF, "该部门下有关联员工,无法删除"); } long childCnt = sysDeptMapper.selectCount( new LambdaQueryWrapper() .eq(SysDept::getParentId, id) .eq(SysDept::getDelFlag, 0)); if (childCnt > 0) { throw new BizException(BizErrorCode.DEPT_HAS_CHILDREN, "存在下级部门,无法删除"); } int affected = sysDeptMapper.deleteById(d.getId()); if (affected <= 0) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "删除失败,请刷新后重试"); } } private void validateReparent(long deptId, long newParentId) { if (newParentId == deptId) { throw new BizException(BizErrorCode.DEPT_REORDER_CYCLE, "上级部门不能为自身"); } loadActive(newParentId); if (isUnderAncestor(newParentId, deptId)) { throw new BizException(BizErrorCode.DEPT_REORDER_CYCLE, "上级部门不能为当前部门的下级"); } Map> byParent = buildChildrenMap(); int subtreeH = subtreeHeight(deptId, byParent); int newParentDepth = depthOf(newParentId); if (newParentDepth + subtreeH > MAX_LEVEL) { throw new BizException(BizErrorCode.DEPT_MAX_DEPTH, "调整上级后将超过 " + MAX_LEVEL + " 级限制"); } } private boolean isUnderAncestor(long nodeId, long ancestorId) { Long cur = nodeId; Set guard = new HashSet<>(); while (cur != null && cur != 0L) { if (Objects.equals(cur, ancestorId)) { return true; } if (!guard.add(cur)) { break; } SysDept n = sysDeptMapper.selectById(cur); if (n == null || n.getDelFlag() != null && n.getDelFlag() == 1) { break; } Long p = n.getParentId(); if (p == null || p == 0L) { break; } cur = p; } return false; } private void applyDeptLeaders(SysDept dept, List leaderIds) { List normalized = normalizeLeaderIds(leaderIds); validateLeaders(normalized); dept.setLeaderIds(joinIds(normalized)); } private List normalizeLeaderIds(List leaderIds) { List raw = leaderIds == null ? new ArrayList<>() : new ArrayList<>(leaderIds); List out = new ArrayList<>(); Set seen = new HashSet<>(); for (Long id : raw) { if (id == null || id <= 0 || !seen.add(id)) { continue; } out.add(id); } return out; } private void validateLeaders(List leaderIds) { if (leaderIds == null || leaderIds.isEmpty()) { return; } List users = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .eq(MpUser::getStatus, 1) .eq(MpUser::getUserType, MiniUserType.EMPLOYEE) .in(MpUser::getId, leaderIds)); Set validIds = new HashSet<>(); for (MpUser user : users) { validIds.add(user.getId()); } for (Long id : leaderIds) { if (!validIds.contains(id)) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "部门负责人不存在或已禁用: " + id); } } } private int subtreeHeight(long id, Map> byParent) { List ch = byParent.getOrDefault(id, List.of()); int max = 1; for (SysDept c : ch) { if (c.getDelFlag() != null && c.getDelFlag() == 1) { continue; } max = Math.max(max, 1 + subtreeHeight(c.getId(), byParent)); } return max; } private Map> buildChildrenMap() { List all = sysDeptMapper.selectList( new LambdaQueryWrapper().eq(SysDept::getDelFlag, 0)); Map> m = new HashMap<>(); for (SysDept d : all) { m.computeIfAbsent(d.getParentId() == null ? 0L : d.getParentId(), k -> new ArrayList<>()) .add(d); } return m; } private Set collectSubtreeDeptIds(Long rootDeptId) { Set deptIds = new HashSet<>(); if (rootDeptId == null || rootDeptId <= 0) { return deptIds; } List all = sysDeptMapper.selectList( new LambdaQueryWrapper() .eq(SysDept::getDelFlag, 0) .select(SysDept::getId, SysDept::getParentId)); Map> children = new HashMap<>(); for (SysDept dept : all) { Long parentId = dept.getParentId() == null ? 0L : dept.getParentId(); children.computeIfAbsent(parentId, k -> new ArrayList<>()).add(dept.getId()); } List stack = new ArrayList<>(); stack.add(rootDeptId); while (!stack.isEmpty()) { Long current = stack.remove(stack.size() - 1); if (!deptIds.add(current)) { continue; } List next = children.getOrDefault(current, List.of()); stack.addAll(next); } return deptIds; } /** 节点到根的距离:根为 1 */ private int depthOf(long id) { int d = 1; Long cur = id; Set guard = new HashSet<>(); while (cur != null && cur != 0L) { SysDept n = sysDeptMapper.selectById(cur); if (n == null || n.getDelFlag() != null && n.getDelFlag() == 1) { break; } Long p = n.getParentId(); if (p == null || p == 0L) { break; } d++; if (!guard.add(cur)) { break; } cur = p; } return d; } private void sortChildrenRecursive(List nodes) { nodes.sort( Comparator.comparing(DeptTreeNodeVo::getSortOrder, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(DeptTreeNodeVo::getId)); for (DeptTreeNodeVo n : nodes) { sortChildrenRecursive(n.getChildren()); } } private void assignLevel(DeptTreeNodeVo n, int level) { n.setLevel(level); for (DeptTreeNodeVo c : n.getChildren()) { assignLevel(c, level + 1); } } private DeptTreeNodeVo toVo(SysDept d) { DeptTreeNodeVo v = new DeptTreeNodeVo(); v.setId(d.getId()); v.setUuid(d.getUuid()); v.setParentId(d.getParentId()); v.setDeptName(d.getDeptName()); v.setSortOrder(d.getSortOrder()); v.setStatus(d.getStatus() == null ? 1 : d.getStatus()); List leaderIds = parseLeaderIds(d); v.setLeaderIds(leaderIds); v.setLeaderNames(resolveLeaderNames(leaderIds)); return v; } private List parseLeaderIds(SysDept dept) { return parseIds(dept.getLeaderIds()); } private List parseIds(String raw) { if (!StringUtils.hasText(raw)) { return List.of(); } List ids = new ArrayList<>(); Set seen = new HashSet<>(); for (String part : raw.split(",")) { if (!StringUtils.hasText(part)) { continue; } try { Long id = Long.parseLong(part.trim()); if (id > 0 && seen.add(id)) { ids.add(id); } } catch (NumberFormatException ignored) { } } return ids; } private String joinIds(List ids) { if (ids == null || ids.isEmpty()) { return null; } return ids.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(",")); } private String resolveLeaderNames(List leaderIds) { if (leaderIds == null || leaderIds.isEmpty()) { return null; } List users = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .in(MpUser::getId, leaderIds)); Map names = new HashMap<>(); for (MpUser user : users) { names.put(user.getId(), user.getNickName()); } List ordered = new ArrayList<>(); for (Long id : leaderIds) { String name = names.get(id); if (StringUtils.hasText(name)) { ordered.add(name.trim()); } } return ordered.isEmpty() ? null : String.join("、", ordered); } private SysDept loadActive(long id) { SysDept d = sysDeptMapper.selectById(id); if (d == null || d.getDelFlag() != null && d.getDelFlag() == 1) { throw new BizException(BizErrorCode.DEPT_PARENT_INVALID, "上级部门不存在"); } return d; } private SysDept loadActiveWithUuid(long id, String uuid) { SysDept d = sysDeptMapper.selectById(id); if (d == null || d.getDelFlag() != null && d.getDelFlag() == 1) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "资源不存在或无权访问"); } UuidAssert.match(d, uuid); return d; } private static int normalizeStatus(Integer s) { if (s == null || (s != 0 && s != 1)) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "部门状态无效"); } return s; } private static String trimName(String name) { if (!StringUtils.hasText(name)) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "部门名称不能为空"); } return name.trim(); } }