import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Department } from '../entities/department.entity'; import { DepartmentType } from '../entities/department.entity'; import { UserDepartment } from '../entities/user-department.entity'; import { CreateDepartmentDto, UpdateDepartmentDto, AssignUserDto } from './dto/department.dto'; @Injectable() export class DepartmentsService { constructor( @InjectRepository(Department) private deptRepo: Repository, @InjectRepository(UserDepartment) private userDeptRepo: Repository, ) {} async findAll(): Promise { return this.deptRepo.find({ where: { status: 'active' }, order: { sortOrder: 'ASC', name: 'ASC' }, }); } async findTree(): Promise { // Load ALL departments flat (no relations — avoids N+1 and only loads 1 level), // then build the full tree in memory. const all = await this.deptRepo.find({ where: { status: 'active' }, order: { sortOrder: 'ASC', name: 'ASC' }, }); const byParent = new Map(); for (const dept of all) { const key = dept.parentId ?? null; const list = byParent.get(key); if (list) { list.push(dept); } else { byParent.set(key, [dept]); } } const attachChildren = (dept: Department): void => { const children = byParent.get(dept.id) ?? []; dept.children = children; for (const child of children) attachChildren(child); }; const roots = byParent.get(null) ?? []; for (const root of roots) attachChildren(root); return roots; } async findOne(id: number): Promise { const dept = await this.deptRepo.findOne({ where: { id } }); if (!dept) throw new NotFoundException('部门不存在'); return dept; } async create(dto: CreateDepartmentDto): Promise { if (dto.type === DepartmentType.CAMPUS && dto.parentId) { throw new ConflictException('校区类型的部门必须是根部门,不能设置上级'); } const dept = this.deptRepo.create(dto); return this.deptRepo.save(dept); } async update(id: number, dto: UpdateDepartmentDto): Promise { const dept = await this.findOne(id); const effectiveType = dto.type ?? dept.type; const effectiveParentId = dto.parentId !== undefined ? dto.parentId : dept.parentId; if (effectiveType === DepartmentType.CAMPUS && effectiveParentId) { throw new ConflictException('校区类型的部门必须是根部门,不能设置上级'); } // Prevent parent cycles: parentId must not be the dept itself or one of its descendants if (dto.parentId !== undefined && dto.parentId !== null) { const newParentId = dto.parentId; if (newParentId === id) { throw new ConflictException('不能将部门的上级设为自身'); } const descendantIds = await this.getDescendantIds(id); if (descendantIds.includes(newParentId)) { throw new ConflictException('不能将部门的上级设为其子部门,会形成循环'); } } Object.assign(dept, dto); return this.deptRepo.save(dept); } async remove(id: number): Promise { const children = await this.deptRepo.count({ where: { parentId: id } }); if (children > 0) throw new ConflictException('该部门下存在子部门,无法删除'); const users = await this.userDeptRepo.count({ where: { departmentId: id } }); if (users > 0) throw new ConflictException('该部门下有用户关联,无法删除'); await this.deptRepo.update(id, { status: 'archived' }); } /** 获取部门的所有子部门 ID(递归,含自身) */ async getDescendantIds(departmentId: number): Promise { const all = await this.deptRepo.find({ where: { status: 'active' }, select: ['id', 'parentId'] }); const byParent = new Map(); for (const d of all) { const key = d.parentId ?? null; byParent.set(key, [...(byParent.get(key) ?? []), d.id]); } const ids: number[] = [departmentId]; const collect = (parentId: number) => { const children = byParent.get(parentId) ?? []; for (const childId of children) { ids.push(childId); collect(childId); } }; collect(departmentId); return ids; } /** 获取用户可访问的部门 ID 列表 */ async getUserDepartments(userId: number): Promise { const records = await this.userDeptRepo.find({ where: { userId }, }); return records.map((r) => r.departmentId); } /** 获取用户默认校区 ID */ async getUserDefaultDepartmentId(userId: number): Promise { const record = await this.userDeptRepo.findOne({ where: { userId, isDefault: true }, }); return record?.departmentId ?? null; } /** 获取部门下的用户 */ async getUsers(departmentId: number): Promise { return this.userDeptRepo.find({ where: { departmentId }, relations: ['user'], }); } /** 为用户分配部门 */ async assignUser(departmentId: number, dto: AssignUserDto): Promise { const record = this.userDeptRepo.create({ userId: dto.userId, departmentId, isDefault: dto.isDefault ?? false, }); return this.userDeptRepo.save(record); } /** 移除用户-部门关联 */ async removeUser(departmentId: number, userId: number): Promise { await this.userDeptRepo.delete({ departmentId, userId }); } }