From 91035e9f2c3fd747d8ca99976e8b9cc4ce2389d6 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 9 Jul 2026 17:12:23 +0800 Subject: [PATCH] feat: add batch-import students to class endpoint --- apps/server/src/classes/classes.controller.ts | 11 ++++ apps/server/src/classes/classes.module.ts | 4 +- apps/server/src/classes/classes.service.ts | 57 ++++++++++++++++++- apps/server/src/classes/dto/class.dto.ts | 14 ++++- 4 files changed, 80 insertions(+), 6 deletions(-) diff --git a/apps/server/src/classes/classes.controller.ts b/apps/server/src/classes/classes.controller.ts index 40927cc..3fcd8bf 100644 --- a/apps/server/src/classes/classes.controller.ts +++ b/apps/server/src/classes/classes.controller.ts @@ -21,6 +21,7 @@ import { AddTeacherDto, QueryClassScheduleDto, QueryClassAttendanceSummaryDto, + BatchImportStudentsDto, } from './dto/class.dto'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { OperationLogsService } from '../operation-logs/operation-logs.service'; @@ -107,6 +108,16 @@ export class ClassesController { return result; } + /** 批量导入学生到班级(通过钉钉用户ID) */ + @Post(':id/students/import') + @RequirePermission('class:edit') + async batchImportStudents( + @Param('id') id: string, + @Body() dto: BatchImportStudentsDto, + ) { + return this.service.batchImportStudents(+id, dto.dingUserIds); + } + /** 归档班级 */ @Put(':id/archive') @RequirePermission('class:edit') diff --git a/apps/server/src/classes/classes.module.ts b/apps/server/src/classes/classes.module.ts index 614ebb0..37e9b00 100644 --- a/apps/server/src/classes/classes.module.ts +++ b/apps/server/src/classes/classes.module.ts @@ -1,14 +1,14 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CommonModule } from '../common/common.module'; -import { Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Department } from '../entities'; +import { Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Department, Student, StudentDingMapping } from '../entities'; import { ClassesService } from './classes.service'; import { ClassesController } from './classes.controller'; import { OperationLogsModule } from '../operation-logs/operation-logs.module'; import { NotificationsModule } from '../notifications/notifications.module'; @Module({ - imports: [TypeOrmModule.forFeature([Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Department]), OperationLogsModule, NotificationsModule, CommonModule], + imports: [TypeOrmModule.forFeature([Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Department, Student, StudentDingMapping]), OperationLogsModule, NotificationsModule, CommonModule], controllers: [ClassesController], providers: [ClassesService], exports: [ClassesService], diff --git a/apps/server/src/classes/classes.service.ts b/apps/server/src/classes/classes.service.ts index ded89d7..e1b3336 100644 --- a/apps/server/src/classes/classes.service.ts +++ b/apps/server/src/classes/classes.service.ts @@ -1,8 +1,8 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, In, Like } from 'typeorm'; -import { Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Department, Classroom } from '../entities'; -import { CreateClassDto, UpdateClassDto, QueryClassDto, AddTeacherDto, QueryClassScheduleDto, QueryClassAttendanceSummaryDto } from './dto/class.dto'; +import { Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Department, Classroom, Student, StudentDingMapping } from '../entities'; +import { CreateClassDto, UpdateClassDto, QueryClassDto, AddTeacherDto, QueryClassScheduleDto, QueryClassAttendanceSummaryDto, BatchImportStudentsDto } from './dto/class.dto'; interface RawStudentCount { classId: string; @@ -24,6 +24,10 @@ export class ClassesService { private attendanceRepo: Repository, @InjectRepository(Department) private deptRepo: Repository, + @InjectRepository(Student) + private studentRepo: Repository, + @InjectRepository(StudentDingMapping) + private studentDingMappingRepo: Repository, ) {} async findAll(query: QueryClassDto) { @@ -93,7 +97,7 @@ export class ClassesService { } async create(dto: CreateClassDto) { - const { studentIds, teachers, ...classData } = dto; + const { studentIds, teachers, dingUserIds, ...classData } = dto; const cls = this.classRepo.create(classData); const saved = await this.classRepo.save(cls); @@ -117,9 +121,56 @@ export class ClassesService { await this.syncClassTeacherIds(saved.id); } + // batch import students by dingUserIds + if (dingUserIds?.length) { + await this.batchImportStudents(saved.id, dingUserIds); + } + return this.findOne(saved.id); } + + async batchImportStudents(classId: number, dingUserIds: string[]): Promise<{ imported: number; skipped: number }> { + const classEntity = await this.classRepo.findOne({ where: { id: classId } }); + if (!classEntity) throw new NotFoundException('班级不存在'); + + let imported = 0; + let skipped = 0; + + for (const dingUserId of dingUserIds) { + let mapping = await this.studentDingMappingRepo.findOne({ where: { dingUserId } }); + let studentId: number; + + if (mapping) { + studentId = mapping.studentId; + } else { + const student = this.studentRepo.create({ + name: `dd_${dingUserId}`, + status: 'active', + departmentId: classEntity.departmentId ?? undefined, + }); + const saved = await this.studentRepo.save(student); + studentId = saved.id; + mapping = this.studentDingMappingRepo.create({ dingUserId, studentId }); + await this.studentDingMappingRepo.save(mapping); + } + + const existing = await this.classStudentRepo.findOne({ + where: { classId, studentId }, + }); + if (existing) { skipped++; continue; } + + await this.classStudentRepo.save( + this.classStudentRepo.create({ + classId, studentId, status: 'active', + joinDate: new Date().toISOString().slice(0, 10), + }), + ); + imported++; + } + + return { imported, skipped }; + } async update(id: number, dto: UpdateClassDto) { const cls = await this.classRepo.findOne({ where: { id } }); if (!cls) throw new NotFoundException('班级不存在'); diff --git a/apps/server/src/classes/dto/class.dto.ts b/apps/server/src/classes/dto/class.dto.ts index 9a123ae..a5c68ca 100644 --- a/apps/server/src/classes/dto/class.dto.ts +++ b/apps/server/src/classes/dto/class.dto.ts @@ -1,4 +1,4 @@ -import { IsOptional, IsString, IsNotEmpty, IsInt, IsArray, IsDateString, IsEnum } from 'class-validator'; +import { IsOptional, IsString, IsNotEmpty, IsInt, IsArray, IsDateString, IsEnum, ArrayNotEmpty } from 'class-validator'; import { Type } from 'class-transformer'; import { ClassType, ClassStatus, TeacherRoleType } from '../../entities'; @@ -43,6 +43,11 @@ export class CreateClassDto { studentIds?: number[]; @IsOptional() @IsArray() + + @IsArray() + @IsString({ each: true }) + @IsOptional() + dingUserIds?: string[]; teachers?: Array<{ userId: number; roleType: string; subject?: string }>; } @@ -135,3 +140,10 @@ export class QueryClassAttendanceSummaryDto { @IsOptional() @IsDateString() endDate?: string; } + +export class BatchImportStudentsDto { + @IsArray() + @IsString({ each: true }) + @ArrayNotEmpty() + dingUserIds: string[]; +}