feat: add class roster export and attendance stats export

- GET /classes/:id/roster/export — Excel export with 姓名/学号/加入日期/状态
- GET /attendance-records/export — Excel export with 姓名/班级/日期/时段/状态/来源/备注/打卡时间
- Add findAllForExport to AttendanceService (no pagination)
- Both use ExcelJS following classrooms controller downloadTemplate pattern
This commit is contained in:
2026-07-05 20:35:11 +08:00
parent 66aadfe3ff
commit 194f25c3ad
3 changed files with 137 additions and 0 deletions

View File

@@ -218,4 +218,42 @@ export class AttendanceService {
record.matchStatus = '已匹配';
return this.dingRawRepo.save(record);
}
// ── Export all attendance records with filters (no pagination) ──
async findAllForExport(query: {
classId?: number;
dateFrom?: string;
dateTo?: string;
session?: string;
status?: string;
source?: string;
}) {
const qb = this.attendanceRepo
.createQueryBuilder('ar')
.leftJoinAndSelect('ar.student', 'student')
.leftJoinAndSelect('ar.class', 'class');
if (query.classId) {
qb.andWhere('ar.classId = :classId', { classId: query.classId });
}
if (query.dateFrom) {
qb.andWhere('ar.attendanceDate >= :dateFrom', { dateFrom: query.dateFrom });
}
if (query.dateTo) {
qb.andWhere('ar.attendanceDate <= :dateTo', { dateTo: query.dateTo });
}
if (query.session) {
qb.andWhere('ar.session = :session', { session: query.session });
}
if (query.status) {
qb.andWhere('ar.status = :status', { status: query.status });
}
if (query.source) {
qb.andWhere('ar.source = :source', { source: query.source });
}
qb.orderBy('ar.attendanceDate', 'DESC').addOrderBy('ar.createdAt', 'DESC');
return qb.getMany();
}
}