forked from wangziqi/gongxue-base
feat: add batch-import students to class endpoint
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
|||||||
AddTeacherDto,
|
AddTeacherDto,
|
||||||
QueryClassScheduleDto,
|
QueryClassScheduleDto,
|
||||||
QueryClassAttendanceSummaryDto,
|
QueryClassAttendanceSummaryDto,
|
||||||
|
BatchImportStudentsDto,
|
||||||
} from './dto/class.dto';
|
} from './dto/class.dto';
|
||||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||||
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
||||||
@@ -107,6 +108,16 @@ export class ClassesController {
|
|||||||
return result;
|
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')
|
@Put(':id/archive')
|
||||||
@RequirePermission('class:edit')
|
@RequirePermission('class:edit')
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { CommonModule } from '../common/common.module';
|
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 { ClassesService } from './classes.service';
|
||||||
import { ClassesController } from './classes.controller';
|
import { ClassesController } from './classes.controller';
|
||||||
import { OperationLogsModule } from '../operation-logs/operation-logs.module';
|
import { OperationLogsModule } from '../operation-logs/operation-logs.module';
|
||||||
import { NotificationsModule } from '../notifications/notifications.module';
|
import { NotificationsModule } from '../notifications/notifications.module';
|
||||||
|
|
||||||
@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],
|
controllers: [ClassesController],
|
||||||
providers: [ClassesService],
|
providers: [ClassesService],
|
||||||
exports: [ClassesService],
|
exports: [ClassesService],
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository, In, Like } from 'typeorm';
|
import { Repository, In, Like } from 'typeorm';
|
||||||
import { Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Department, Classroom } from '../entities';
|
import { Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Department, Classroom, Student, StudentDingMapping } from '../entities';
|
||||||
import { CreateClassDto, UpdateClassDto, QueryClassDto, AddTeacherDto, QueryClassScheduleDto, QueryClassAttendanceSummaryDto } from './dto/class.dto';
|
import { CreateClassDto, UpdateClassDto, QueryClassDto, AddTeacherDto, QueryClassScheduleDto, QueryClassAttendanceSummaryDto, BatchImportStudentsDto } from './dto/class.dto';
|
||||||
|
|
||||||
interface RawStudentCount {
|
interface RawStudentCount {
|
||||||
classId: string;
|
classId: string;
|
||||||
@@ -24,6 +24,10 @@ export class ClassesService {
|
|||||||
private attendanceRepo: Repository<AttendanceRecord>,
|
private attendanceRepo: Repository<AttendanceRecord>,
|
||||||
@InjectRepository(Department)
|
@InjectRepository(Department)
|
||||||
private deptRepo: Repository<Department>,
|
private deptRepo: Repository<Department>,
|
||||||
|
@InjectRepository(Student)
|
||||||
|
private studentRepo: Repository<Student>,
|
||||||
|
@InjectRepository(StudentDingMapping)
|
||||||
|
private studentDingMappingRepo: Repository<StudentDingMapping>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findAll(query: QueryClassDto) {
|
async findAll(query: QueryClassDto) {
|
||||||
@@ -93,7 +97,7 @@ export class ClassesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async create(dto: CreateClassDto) {
|
async create(dto: CreateClassDto) {
|
||||||
const { studentIds, teachers, ...classData } = dto;
|
const { studentIds, teachers, dingUserIds, ...classData } = dto;
|
||||||
|
|
||||||
const cls = this.classRepo.create(classData);
|
const cls = this.classRepo.create(classData);
|
||||||
const saved = await this.classRepo.save(cls);
|
const saved = await this.classRepo.save(cls);
|
||||||
@@ -117,9 +121,56 @@ export class ClassesService {
|
|||||||
await this.syncClassTeacherIds(saved.id);
|
await this.syncClassTeacherIds(saved.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// batch import students by dingUserIds
|
||||||
|
if (dingUserIds?.length) {
|
||||||
|
await this.batchImportStudents(saved.id, dingUserIds);
|
||||||
|
}
|
||||||
|
|
||||||
return this.findOne(saved.id);
|
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) {
|
async update(id: number, dto: UpdateClassDto) {
|
||||||
const cls = await this.classRepo.findOne({ where: { id } });
|
const cls = await this.classRepo.findOne({ where: { id } });
|
||||||
if (!cls) throw new NotFoundException('班级不存在');
|
if (!cls) throw new NotFoundException('班级不存在');
|
||||||
|
|||||||
@@ -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 { Type } from 'class-transformer';
|
||||||
import { ClassType, ClassStatus, TeacherRoleType } from '../../entities';
|
import { ClassType, ClassStatus, TeacherRoleType } from '../../entities';
|
||||||
|
|
||||||
@@ -43,6 +43,11 @@ export class CreateClassDto {
|
|||||||
studentIds?: number[];
|
studentIds?: number[];
|
||||||
|
|
||||||
@IsOptional() @IsArray()
|
@IsOptional() @IsArray()
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
dingUserIds?: string[];
|
||||||
teachers?: Array<{ userId: number; roleType: string; subject?: string }>;
|
teachers?: Array<{ userId: number; roleType: string; subject?: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,3 +140,10 @@ export class QueryClassAttendanceSummaryDto {
|
|||||||
@IsOptional() @IsDateString()
|
@IsOptional() @IsDateString()
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class BatchImportStudentsDto {
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@ArrayNotEmpty()
|
||||||
|
dingUserIds: string[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user