feat: 学生档案与报告生成

This commit is contained in:
2026-08-05 17:11:15 +08:00
parent 0e6e3e2d96
commit c622e40a12
23 changed files with 2491 additions and 2148 deletions

View File

@@ -74,15 +74,15 @@ export class ArchiveService {
attendances,
] = await Promise.all([
this.profileRepo.findOne({ where: { studentId } }),
this.enrollmentRepo.find({ where: { studentId, status: 'active' }, order: { createdAt: 'DESC' } }),
this.enrollmentRepo.find({ where: { studentId }, order: { createdAt: 'DESC' } }),
this.examScoreRepo.find({
where: { studentId, status: 'active' },
where: { studentId },
relations: ['exam', 'exam.class'],
order: { examDate: 'DESC' },
}),
this.learningRecordRepo.find({ where: { studentId, status: 'active' }, order: { recordDate: 'DESC' } }),
this.learningRecordRepo.find({ where: { studentId }, order: { recordDate: 'DESC' } }),
this.resultRepo.findOne({ where: { studentId } }),
this.attachmentRepo.find({ where: { studentId, status: 'active' }, order: { createdAt: 'DESC' } }),
this.attachmentRepo.find({ where: { studentId }, order: { createdAt: 'DESC' } }),
this.attendanceRepo.find({
where: { studentId },
relations: ['schedule', 'class'],
@@ -138,6 +138,20 @@ export class ArchiveService {
return { message: '已归档' };
}
async purgeEnrollment(id: number) {
const entity = await this.enrollmentRepo.findOne({ where: { id } });
if (!entity) throw new NotFoundException('报名记录不存在');
if (entity.status !== 'archived') {
throw new BadRequestException('仅已归档报名记录可以永久删除,请先归档');
}
const scoreCount = await this.examScoreRepo.count({ where: { enrollmentId: id } });
if (scoreCount > 0) {
throw new BadRequestException('该报名记录已被考试成绩引用,无法永久删除');
}
await this.enrollmentRepo.delete(id);
return { message: '已永久删除报名记录(不可恢复)' };
}
private async assertEnrollmentBelongsToStudent(studentId: number, enrollmentId?: number) {
if (enrollmentId === undefined) return;
const enrollment = await this.enrollmentRepo.findOne({
@@ -173,6 +187,16 @@ export class ArchiveService {
return { message: '已归档' };
}
async purgeExamScore(id: number) {
const entity = await this.examScoreRepo.findOne({ where: { id } });
if (!entity) throw new NotFoundException('考试成绩不存在');
if (entity.status !== 'archived') {
throw new BadRequestException('仅已归档考试成绩可以永久删除,请先归档');
}
await this.examScoreRepo.delete(id);
return { message: '已永久删除考试成绩(不可恢复)' };
}
async addLearningRecord(studentId: number, dto: CreateLearningRecordDto) {
const student = await this.studentRepo.findOne({ where: { id: studentId } });
if (!student) throw new NotFoundException('学生不存在');
@@ -196,6 +220,16 @@ export class ArchiveService {
return { message: '已归档' };
}
async purgeLearningRecord(id: number) {
const entity = await this.learningRecordRepo.findOne({ where: { id } });
if (!entity) throw new NotFoundException('学习记录不存在');
if (entity.status !== 'archived') {
throw new BadRequestException('仅已归档学习记录可以永久删除,请先归档');
}
await this.learningRecordRepo.delete(id);
return { message: '已永久删除学习记录(不可恢复)' };
}
async upsertResult(studentId: number, dto: UpsertResultDto) {
const student = await this.studentRepo.findOne({ where: { id: studentId } });
if (!student) throw new NotFoundException('学生不存在');
@@ -257,4 +291,23 @@ export class ArchiveService {
await this.attachmentRepo.update(id, { status: 'archived' });
return { message: '已归档' };
}
async purgeAttachment(id: number) {
const entity = await this.attachmentRepo.findOne({ where: { id } });
if (!entity) throw new NotFoundException('附件不存在');
if (entity.status !== 'archived') {
throw new BadRequestException('仅已归档附件可以永久删除,请先归档');
}
if (entity.filePath) {
try {
const fullPath = this.resolveAttachmentPath(entity.filePath);
if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
} catch (error) {
// 磁盘文件删除失败仅告警,不阻塞数据库删除
console.warn(`[ArchiveService] 附件文件删除失败: ${entity.filePath}`, error);
}
}
await this.attachmentRepo.delete(id);
return { message: '已永久删除附件(不可恢复)' };
}
}