feat(task1): restructure directories for turborepo monorepo
- Move backend/ to apps/server/ via git mv - Move frontend/ to apps/admin/ via git mv - Create packages/typescript-config/ with base, nestjs, and react-vite presets
This commit is contained in:
103
apps/server/src/students/students.service.ts
Normal file
103
apps/server/src/students/students.service.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user