forked from wangziqi/gongxue-base
- 后端: NestJS 11 + TypeORM + JWT认证 + SQLite/MySQL - 前端: React 19 + Ant Design 6 + Vite 8 + ECharts - 功能模块: 数据面板、学生管理、宿舍管理、入住管理、费用录入、账单管理、教室管理、押金管理、操作日志、账号管理 - 支持Docker一键部署
104 lines
4.1 KiB
TypeScript
104 lines
4.1 KiB
TypeScript
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 };
|
||
}
|
||
}
|