longz
2026-07-08 700f9d0da314a4ea701fe9f68d305b67d72ec5fb
backend/src/main/java/com/sinata/appointment/service/DeptManageApplicationService.java
@@ -2,11 +2,14 @@
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;
@@ -29,6 +32,7 @@
    private static final int MAX_LEVEL = 5;
    private final SysDeptMapper sysDeptMapper;
    private final MpUserMapper mpUserMapper;
    public List<DeptTreeNodeVo> tree() {
        List<SysDept> all =
@@ -83,6 +87,7 @@
        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())) {
@@ -99,6 +104,7 @@
        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) {
@@ -119,6 +125,16 @@
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id, String uuid) {
        SysDept d = loadActiveWithUuid(id, uuid);
        Set<Long> deptScope = collectSubtreeDeptIds(id);
        long staffCnt =
                mpUserMapper.selectCount(
                        new LambdaQueryWrapper<MpUser>()
                                .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<SysDept>()
@@ -172,6 +188,47 @@
        return false;
    }
    private void applyDeptLeaders(SysDept dept, List<Long> leaderIds) {
        List<Long> normalized = normalizeLeaderIds(leaderIds);
        validateLeaders(normalized);
        dept.setLeaderIds(joinIds(normalized));
    }
    private List<Long> normalizeLeaderIds(List<Long> leaderIds) {
        List<Long> raw = leaderIds == null ? new ArrayList<>() : new ArrayList<>(leaderIds);
        List<Long> out = new ArrayList<>();
        Set<Long> 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<Long> leaderIds) {
        if (leaderIds == null || leaderIds.isEmpty()) {
            return;
        }
        List<MpUser> users =
                mpUserMapper.selectList(
                        new LambdaQueryWrapper<MpUser>()
                                .eq(MpUser::getDelFlag, 0)
                                .eq(MpUser::getStatus, 1)
                                .eq(MpUser::getUserType, MiniUserType.EMPLOYEE)
                                .in(MpUser::getId, leaderIds));
        Set<Long> 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<Long, List<SysDept>> byParent) {
        List<SysDept> ch = byParent.getOrDefault(id, List.of());
        int max = 1;
@@ -194,6 +251,34 @@
                    .add(d);
        }
        return m;
    }
    private Set<Long> collectSubtreeDeptIds(Long rootDeptId) {
        Set<Long> deptIds = new HashSet<>();
        if (rootDeptId == null || rootDeptId <= 0) {
            return deptIds;
        }
        List<SysDept> all =
                sysDeptMapper.selectList(
                        new LambdaQueryWrapper<SysDept>()
                                .eq(SysDept::getDelFlag, 0)
                                .select(SysDept::getId, SysDept::getParentId));
        Map<Long, List<Long>> 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<Long> stack = new ArrayList<>();
        stack.add(rootDeptId);
        while (!stack.isEmpty()) {
            Long current = stack.remove(stack.size() - 1);
            if (!deptIds.add(current)) {
                continue;
            }
            List<Long> next = children.getOrDefault(current, List.of());
            stack.addAll(next);
        }
        return deptIds;
    }
    /** 节点到根的距离:根为 1 */
@@ -243,9 +328,67 @@
        v.setDeptName(d.getDeptName());
        v.setSortOrder(d.getSortOrder());
        v.setStatus(d.getStatus() == null ? 1 : d.getStatus());
        List<Long> leaderIds = parseLeaderIds(d);
        v.setLeaderIds(leaderIds);
        v.setLeaderNames(resolveLeaderNames(leaderIds));
        return v;
    }
    private List<Long> parseLeaderIds(SysDept dept) {
        return parseIds(dept.getLeaderIds());
    }
    private List<Long> parseIds(String raw) {
        if (!StringUtils.hasText(raw)) {
            return List.of();
        }
        List<Long> ids = new ArrayList<>();
        Set<Long> 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<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return null;
        }
        return ids.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(","));
    }
    private String resolveLeaderNames(List<Long> leaderIds) {
        if (leaderIds == null || leaderIds.isEmpty()) {
            return null;
        }
        List<MpUser> users =
                mpUserMapper.selectList(
                        new LambdaQueryWrapper<MpUser>()
                                .eq(MpUser::getDelFlag, 0)
                                .in(MpUser::getId, leaderIds));
        Map<Long, String> names = new HashMap<>();
        for (MpUser user : users) {
            names.put(user.getId(), user.getNickName());
        }
        List<String> 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) {