package com.sinata.appointment.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.sinata.appointment.constants.BizErrorCode;
|
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<DeptTreeNodeVo> tree() {
|
List<SysDept> all =
|
sysDeptMapper.selectList(
|
new LambdaQueryWrapper<SysDept>()
|
.eq(SysDept::getDelFlag, 0)
|
.orderByAsc(SysDept::getSortOrder)
|
.orderByAsc(SysDept::getId));
|
Map<Long, DeptTreeNodeVo> vos = new HashMap<>();
|
for (SysDept d : all) {
|
DeptTreeNodeVo v = toVo(d);
|
v.setChildren(new ArrayList<>());
|
vos.put(d.getId(), v);
|
}
|
List<DeptTreeNodeVo> 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<String, Object> 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);
|
sysDeptMapper.insert(d);
|
SysDept fresh = sysDeptMapper.selectById(d.getId());
|
if (fresh == null || !StringUtils.hasText(fresh.getUuid())) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "创建后读取部门失败");
|
}
|
Map<String, Object> 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()));
|
|
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<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>()
|
.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<Long, List<SysDept>> 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<Long> 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 int subtreeHeight(long id, Map<Long, List<SysDept>> byParent) {
|
List<SysDept> 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<Long, List<SysDept>> buildChildrenMap() {
|
List<SysDept> all =
|
sysDeptMapper.selectList(
|
new LambdaQueryWrapper<SysDept>().eq(SysDept::getDelFlag, 0));
|
Map<Long, List<SysDept>> m = new HashMap<>();
|
for (SysDept d : all) {
|
m.computeIfAbsent(d.getParentId() == null ? 0L : d.getParentId(), k -> new ArrayList<>())
|
.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 */
|
private int depthOf(long id) {
|
int d = 1;
|
Long cur = id;
|
Set<Long> 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<DeptTreeNodeVo> 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());
|
return v;
|
}
|
|
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();
|
}
|
}
|