fix: F12 P2 follow-up — batch imports set departmentId, optimize getDescendantIds, enforce campus root invariant
This commit is contained in:
@@ -211,7 +211,8 @@ export class ClassroomsController {
|
|||||||
supervisor: String(row.getCell(7).value || '') || undefined,
|
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({
|
await this.logService.log({
|
||||||
userId: req.user?.id,
|
userId: req.user?.id,
|
||||||
username: req.user?.username,
|
username: req.user?.username,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export class ClassroomsService {
|
|||||||
roomType?: string;
|
roomType?: string;
|
||||||
courseType?: string;
|
courseType?: string;
|
||||||
}[],
|
}[],
|
||||||
|
departmentId?: number,
|
||||||
) {
|
) {
|
||||||
let imported = 0;
|
let imported = 0;
|
||||||
let skipped = 0;
|
let skipped = 0;
|
||||||
@@ -74,7 +75,7 @@ export class ClassroomsService {
|
|||||||
if (!row.name?.trim()) { skipped++; continue; }
|
if (!row.name?.trim()) { skipped++; continue; }
|
||||||
const exists = await this.repo.findOne({ where: { name: row.name.trim() } });
|
const exists = await this.repo.findOne({ where: { name: row.name.trim() } });
|
||||||
if (exists) { errors.push(`教室 ${row.name} 已存在`); skipped++; continue; }
|
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++;
|
imported++;
|
||||||
}
|
}
|
||||||
return { message: `成功导入 ${imported} 间教室,跳过 ${skipped} 间`, imported, skipped, errors: errors.length > 0 ? errors : undefined };
|
return { message: `成功导入 ${imported} 间教室,跳过 ${skipped} 间`, imported, skipped, errors: errors.length > 0 ? errors : undefined };
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Injectable, NotFoundException, ConflictException } from '@nestjs/common
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { Department } from '../entities/department.entity';
|
import { Department } from '../entities/department.entity';
|
||||||
|
import { DepartmentType } from '../entities/department.entity';
|
||||||
import { UserDepartment } from '../entities/user-department.entity';
|
import { UserDepartment } from '../entities/user-department.entity';
|
||||||
import { CreateDepartmentDto, UpdateDepartmentDto, AssignUserDto } from './dto/department.dto';
|
import { CreateDepartmentDto, UpdateDepartmentDto, AssignUserDto } from './dto/department.dto';
|
||||||
|
|
||||||
@@ -58,6 +59,9 @@ export class DepartmentsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async create(dto: CreateDepartmentDto): Promise<Department> {
|
async create(dto: CreateDepartmentDto): Promise<Department> {
|
||||||
|
if (dto.type === DepartmentType.CAMPUS && dto.parentId) {
|
||||||
|
throw new ConflictException('校区类型的部门必须是根部门,不能设置上级');
|
||||||
|
}
|
||||||
const dept = this.deptRepo.create(dto);
|
const dept = this.deptRepo.create(dto);
|
||||||
return this.deptRepo.save(dept);
|
return this.deptRepo.save(dept);
|
||||||
}
|
}
|
||||||
@@ -65,6 +69,12 @@ export class DepartmentsService {
|
|||||||
async update(id: number, dto: UpdateDepartmentDto): Promise<Department> {
|
async update(id: number, dto: UpdateDepartmentDto): Promise<Department> {
|
||||||
const dept = await this.findOne(id);
|
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
|
// Prevent parent cycles: parentId must not be the dept itself or one of its descendants
|
||||||
if (dto.parentId !== undefined && dto.parentId !== null) {
|
if (dto.parentId !== undefined && dto.parentId !== null) {
|
||||||
const newParentId = dto.parentId;
|
const newParentId = dto.parentId;
|
||||||
@@ -93,14 +103,21 @@ export class DepartmentsService {
|
|||||||
|
|
||||||
/** 获取部门的所有子部门 ID(递归,含自身) */
|
/** 获取部门的所有子部门 ID(递归,含自身) */
|
||||||
async getDescendantIds(departmentId: number): Promise<number[]> {
|
async getDescendantIds(departmentId: number): Promise<number[]> {
|
||||||
const ids = [departmentId];
|
const all = await this.deptRepo.find({ where: { status: 'active' }, select: ['id', 'parentId'] });
|
||||||
const children = await this.deptRepo.find({
|
const byParent = new Map<number | null, number[]>();
|
||||||
where: { parentId: departmentId, status: 'active' },
|
for (const d of all) {
|
||||||
});
|
const key = d.parentId ?? null;
|
||||||
for (const child of children) {
|
byParent.set(key, [...(byParent.get(key) ?? []), d.id]);
|
||||||
const childIds = await this.getDescendantIds(child.id);
|
|
||||||
ids.push(...childIds);
|
|
||||||
}
|
}
|
||||||
|
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;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,8 @@ export class RoomsController {
|
|||||||
monthlyRate,
|
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({
|
await this.logService.log({
|
||||||
userId: req.user?.id,
|
userId: req.user?.id,
|
||||||
username: req.user?.username,
|
username: req.user?.username,
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ export class RoomsService {
|
|||||||
rentalCategory?: string;
|
rentalCategory?: string;
|
||||||
monthlyRate?: number;
|
monthlyRate?: number;
|
||||||
}[],
|
}[],
|
||||||
|
departmentId?: number,
|
||||||
) {
|
) {
|
||||||
let imported = 0;
|
let imported = 0;
|
||||||
let skipped = 0;
|
let skipped = 0;
|
||||||
@@ -270,6 +271,7 @@ export class RoomsService {
|
|||||||
roomType: row.roomType || parsed.roomType || undefined,
|
roomType: row.roomType || parsed.roomType || undefined,
|
||||||
rentalCategory: row.rentalCategory || undefined,
|
rentalCategory: row.rentalCategory || undefined,
|
||||||
monthlyRate: row.monthlyRate ?? undefined,
|
monthlyRate: row.monthlyRate ?? undefined,
|
||||||
|
departmentId: departmentId ?? undefined,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
imported++;
|
imported++;
|
||||||
|
|||||||
@@ -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({
|
await this.logService.log({
|
||||||
userId: req.user?.id,
|
userId: req.user?.id,
|
||||||
username: req.user?.username,
|
username: req.user?.username,
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export class StudentsService {
|
|||||||
supervisor?: string;
|
supervisor?: string;
|
||||||
tenantId?: number;
|
tenantId?: number;
|
||||||
}[],
|
}[],
|
||||||
|
departmentId?: number,
|
||||||
) {
|
) {
|
||||||
let imported = 0;
|
let imported = 0;
|
||||||
let skipped = 0;
|
let skipped = 0;
|
||||||
@@ -140,6 +141,7 @@ export class StudentsService {
|
|||||||
organization: row.organization || undefined,
|
organization: row.organization || undefined,
|
||||||
supervisor: row.supervisor || undefined,
|
supervisor: row.supervisor || undefined,
|
||||||
tenantId: row.tenantId || undefined,
|
tenantId: row.tenantId || undefined,
|
||||||
|
departmentId: departmentId ?? undefined,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
imported++;
|
imported++;
|
||||||
|
|||||||
Reference in New Issue
Block a user