Files
gongxue-base/backend/src/students/students.service.ts
陈浩 78676a124a feat: 恭学教育基地管理系统初始提交
- 后端: NestJS 11 + TypeORM + JWT认证 + SQLite/MySQL
- 前端: React 19 + Ant Design 6 + Vite 8 + ECharts
- 功能模块: 数据面板、学生管理、宿舍管理、入住管理、费用录入、账单管理、教室管理、押金管理、操作日志、账号管理
- 支持Docker一键部署
2026-06-06 17:26:10 +08:00

104 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like, Not, In } from 'typeorm';
import { Student } from '../entities/student.entity';
import { CreateStudentDto, UpdateStudentDto } from './dto/student.dto';
@Injectable()
export class StudentsService {
constructor(@InjectRepository(Student) private repo: Repository<Student>) {}
async findAll(query?: { name?: string; status?: string; includeArchived?: boolean }) {
const where: any = {};
if (query?.name) where.name = Like(`%${query.name}%`);
if (query?.status) {
where.status = query.status;
} else if (!query?.includeArchived) {
where.status = Not('archived');
}
return this.repo.find({ where, order: { createdAt: 'DESC' } });
}
async findOne(id: number) {
const student = await this.repo.findOne({ where: { id }, relations: ['occupancies', 'occupancies.room'] });
if (!student) throw new NotFoundException('学生不存在');
return student;
}
async create(dto: CreateStudentDto) {
return this.repo.save(this.repo.create(dto));
}
async update(id: number, dto: UpdateStudentDto) {
await this.findOne(id);
await this.repo.update(id, dto);
return this.repo.findOne({ where: { id } });
}
async remove(id: number) {
const student = await this.findOne(id);
if (student.status === 'archived') {
throw new BadRequestException('该学生已归档');
}
// 软删除:归档而非物理删除,保留历史数据
await this.repo.update(id, { status: 'archived' });
return { message: '已归档(数据已保留,可随时恢复)' };
}
async batchRemove(ids: number[]) {
if (!ids || ids.length === 0) throw new BadRequestException('请选择要归档的学生');
const students = await this.repo.find({ where: { id: In(ids) } });
const skipped: string[] = [];
const targetIds: number[] = [];
for (const s of students) {
if (s.status === 'archived') skipped.push(s.name);
else targetIds.push(s.id);
}
let affected = 0;
if (targetIds.length > 0) {
const result = await this.repo.createQueryBuilder()
.update()
.set({ status: 'archived' })
.where('id IN (:...ids)', { ids: targetIds })
.execute();
affected = result.affected || 0;
}
const message = skipped.length > 0
? `成功归档 ${affected} 人;${skipped.length} 人已是归档状态被跳过(${skipped.slice(0, 5).join('、')}${skipped.length > 5 ? '…' : ''}`
: `已批量归档 ${affected} 人(数据已保留,可随时恢复)`;
return { message, archived: affected, skipped: skipped.length };
}
async restore(id: number) {
const student = await this.findOne(id);
if (student.status !== 'archived') {
throw new BadRequestException('该学生未被归档');
}
await this.repo.update(id, { status: 'active' });
return { message: '已恢复' };
}
async batchImport(rows: { name: string; phone?: string; idNumber?: string; gender?: string; ethnicity?: string; emergencyContact?: string; emergencyPhone?: string; organization?: string; supervisor?: string }[]) {
let imported = 0;
let skipped = 0;
for (const row of rows) {
if (!row.name || !row.name.trim()) { skipped++; continue; }
const exists = await this.repo.findOne({ where: { name: row.name.trim() } });
if (exists) { skipped++; continue; }
await this.repo.save(this.repo.create({
name: row.name.trim(),
phone: row.phone?.trim() || undefined,
idNumber: row.idNumber?.trim() || undefined,
gender: row.gender || undefined,
ethnicity: row.ethnicity || undefined,
emergencyContact: row.emergencyContact || undefined,
emergencyPhone: row.emergencyPhone || undefined,
organization: row.organization || undefined,
supervisor: row.supervisor || undefined,
}));
imported++;
}
return { message: `成功导入 ${imported} 名学生,跳过 ${skipped} 条(重复或空行)`, imported, skipped };
}
}