From 3bcb7d41c0a563d2cbfa627a95c41a2cea2f86d3 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Mon, 6 Jul 2026 01:06:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20F12=20P2=20follow-up=20=E2=80=94=20batch?= =?UTF-8?q?=20imports=20set=20departmentId,=20optimize=20getDescendantIds,?= =?UTF-8?q?=20enforce=20campus=20root=20invariant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/classrooms/classrooms.controller.ts | 3 +- .../src/classrooms/classrooms.service.ts | 3 +- .../src/departments/departments.service.ts | 31 ++++++++++++++----- apps/server/src/rooms/rooms.controller.ts | 3 +- apps/server/src/rooms/rooms.service.ts | 2 ++ .../src/students/students.controller.ts | 3 +- apps/server/src/students/students.service.ts | 2 ++ 7 files changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/server/src/classrooms/classrooms.controller.ts b/apps/server/src/classrooms/classrooms.controller.ts index da3aca4..94d622f 100644 --- a/apps/server/src/classrooms/classrooms.controller.ts +++ b/apps/server/src/classrooms/classrooms.controller.ts @@ -211,7 +211,8 @@ export class ClassroomsController { supervisor: String(row.getCell(7).value || '') || undefined, }); }); - const result = await this.service.batchImport(rows); + const departmentId = req.headers?.['x-campus-id'] ? parseInt(String(req.headers['x-campus-id']), 10) || undefined : undefined; + const result = await this.service.batchImport(rows, departmentId); await this.logService.log({ userId: req.user?.id, username: req.user?.username, diff --git a/apps/server/src/classrooms/classrooms.service.ts b/apps/server/src/classrooms/classrooms.service.ts index 4abef72..2789acb 100644 --- a/apps/server/src/classrooms/classrooms.service.ts +++ b/apps/server/src/classrooms/classrooms.service.ts @@ -66,6 +66,7 @@ export class ClassroomsService { roomType?: string; courseType?: string; }[], + departmentId?: number, ) { let imported = 0; let skipped = 0; @@ -74,7 +75,7 @@ export class ClassroomsService { if (!row.name?.trim()) { skipped++; continue; } const exists = await this.repo.findOne({ where: { name: row.name.trim() } }); if (exists) { errors.push(`教室 ${row.name} 已存在`); skipped++; continue; } - await this.repo.save(this.repo.create({ ...row, capacity: row.capacity || 30 })); + await this.repo.save(this.repo.create({ ...row, capacity: row.capacity || 30, departmentId: departmentId ?? undefined })); imported++; } return { message: `成功导入 ${imported} 间教室,跳过 ${skipped} 间`, imported, skipped, errors: errors.length > 0 ? errors : undefined }; diff --git a/apps/server/src/departments/departments.service.ts b/apps/server/src/departments/departments.service.ts index d653fed..a9d2126 100644 --- a/apps/server/src/departments/departments.service.ts +++ b/apps/server/src/departments/departments.service.ts @@ -2,6 +2,7 @@ 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'; @@ -58,6 +59,9 @@ export class DepartmentsService { } 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); } @@ -65,6 +69,12 @@ export class DepartmentsService { 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; @@ -93,14 +103,21 @@ export class DepartmentsService { /** 获取部门的所有子部门 ID(递归,含自身) */ async getDescendantIds(departmentId: number): Promise { - const ids = [departmentId]; - const children = await this.deptRepo.find({ - where: { parentId: departmentId, status: 'active' }, - }); - for (const child of children) { - const childIds = await this.getDescendantIds(child.id); - ids.push(...childIds); + 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; } diff --git a/apps/server/src/rooms/rooms.controller.ts b/apps/server/src/rooms/rooms.controller.ts index ecacbe6..f69ce00 100644 --- a/apps/server/src/rooms/rooms.controller.ts +++ b/apps/server/src/rooms/rooms.controller.ts @@ -275,7 +275,8 @@ export class RoomsController { monthlyRate, }); }); - const result = await this.service.batchImport(rows); + const departmentId = req.headers?.['x-campus-id'] ? parseInt(String(req.headers['x-campus-id']), 10) || undefined : undefined; + const result = await this.service.batchImport(rows, departmentId); await this.logService.log({ userId: req.user?.id, username: req.user?.username, diff --git a/apps/server/src/rooms/rooms.service.ts b/apps/server/src/rooms/rooms.service.ts index 8fb0ac8..f0489c2 100644 --- a/apps/server/src/rooms/rooms.service.ts +++ b/apps/server/src/rooms/rooms.service.ts @@ -246,6 +246,7 @@ export class RoomsService { rentalCategory?: string; monthlyRate?: number; }[], + departmentId?: number, ) { let imported = 0; let skipped = 0; @@ -270,6 +271,7 @@ export class RoomsService { roomType: row.roomType || parsed.roomType || undefined, rentalCategory: row.rentalCategory || undefined, monthlyRate: row.monthlyRate ?? undefined, + departmentId: departmentId ?? undefined, }), ); imported++; diff --git a/apps/server/src/students/students.controller.ts b/apps/server/src/students/students.controller.ts index e592ade..db52d78 100644 --- a/apps/server/src/students/students.controller.ts +++ b/apps/server/src/students/students.controller.ts @@ -284,7 +284,8 @@ export class StudentsController { } } } - const result = await this.service.batchImport(rows); + const departmentId = req.headers?.['x-campus-id'] ? parseInt(String(req.headers['x-campus-id']), 10) || undefined : undefined; + const result = await this.service.batchImport(rows, departmentId); await this.logService.log({ userId: req.user?.id, username: req.user?.username, diff --git a/apps/server/src/students/students.service.ts b/apps/server/src/students/students.service.ts index d87e4ef..82c5271 100644 --- a/apps/server/src/students/students.service.ts +++ b/apps/server/src/students/students.service.ts @@ -115,6 +115,7 @@ export class StudentsService { supervisor?: string; tenantId?: number; }[], + departmentId?: number, ) { let imported = 0; let skipped = 0; @@ -140,6 +141,7 @@ export class StudentsService { organization: row.organization || undefined, supervisor: row.supervisor || undefined, tenantId: row.tenantId || undefined, + departmentId: departmentId ?? undefined, }), ); imported++;