forked from wangziqi/gongxue-base
feat(classes): add schedule and attendance-summary endpoints + frontend tabs
- Add GET /classes/:id/schedule returning ClassSchedule list with classroom name - Add GET /classes/:id/attendance-summary returning attendance/absence/late rates - Add 课表 and 出勤汇总 tabs to Classes detail page - Register ClassSchedule and AttendanceRecord in ClassesModule
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, In, Like } from 'typeorm';
|
||||
import { Class, ClassStudent, ClassTeacher } from '../entities';
|
||||
import { CreateClassDto, UpdateClassDto, QueryClassDto, AddTeacherDto } from './dto/class.dto';
|
||||
import { Class, ClassStudent, ClassTeacher, ClassSchedule, AttendanceRecord, Classroom } from '../entities';
|
||||
import { CreateClassDto, UpdateClassDto, QueryClassDto, AddTeacherDto, QueryClassScheduleDto, QueryClassAttendanceSummaryDto } from './dto/class.dto';
|
||||
import { CampusScope } from '../common/campus-scope';
|
||||
|
||||
interface RawStudentCount {
|
||||
@@ -19,6 +19,10 @@ export class ClassesService {
|
||||
private classStudentRepo: Repository<ClassStudent>,
|
||||
@InjectRepository(ClassTeacher)
|
||||
private classTeacherRepo: Repository<ClassTeacher>,
|
||||
@InjectRepository(ClassSchedule)
|
||||
private scheduleRepo: Repository<ClassSchedule>,
|
||||
@InjectRepository(AttendanceRecord)
|
||||
private attendanceRepo: Repository<AttendanceRecord>,
|
||||
private readonly scope: CampusScope,
|
||||
) {}
|
||||
|
||||
@@ -118,7 +122,7 @@ export class ClassesService {
|
||||
async update(id: number, dto: UpdateClassDto) {
|
||||
const cls = await this.classRepo.findOne({ where: { id } });
|
||||
if (!cls) throw new NotFoundException('班级不存在');
|
||||
await this.classRepo.update(id, dto as Record<string, unknown>);
|
||||
await this.classRepo.update(id, dto);
|
||||
return this.findOne(id);
|
||||
}
|
||||
|
||||
@@ -196,4 +200,61 @@ export class ClassesService {
|
||||
await this.classRepo.update(classId, updates);
|
||||
}
|
||||
}
|
||||
|
||||
async getSchedule(classId: number, query: QueryClassScheduleDto) {
|
||||
const qb = this.scheduleRepo
|
||||
.createQueryBuilder('cs')
|
||||
.leftJoinAndSelect('cs.classroom', 'classroom')
|
||||
.where('cs.classId = :classId', { classId });
|
||||
|
||||
if (query.startDate) {
|
||||
qb.andWhere('cs.endDate >= :startDate', { startDate: query.startDate });
|
||||
}
|
||||
if (query.endDate) {
|
||||
qb.andWhere('cs.startDate <= :endDate', { endDate: query.endDate });
|
||||
}
|
||||
|
||||
const schedules = await qb
|
||||
.orderBy('cs.weekDay', 'ASC')
|
||||
.addOrderBy('cs.startTime', 'ASC')
|
||||
.getMany();
|
||||
|
||||
return schedules.map((s) => ({
|
||||
...s,
|
||||
classroomName: (s.classroom as Classroom | undefined)?.name || null,
|
||||
}));
|
||||
}
|
||||
|
||||
async getAttendanceSummary(classId: number, query: QueryClassAttendanceSummaryDto) {
|
||||
const qb = this.attendanceRepo
|
||||
.createQueryBuilder('ar')
|
||||
.where('ar.classId = :classId', { classId });
|
||||
|
||||
if (query.startDate) {
|
||||
qb.andWhere('ar.attendanceDate >= :startDate', { startDate: query.startDate });
|
||||
}
|
||||
if (query.endDate) {
|
||||
qb.andWhere('ar.attendanceDate <= :endDate', { endDate: query.endDate });
|
||||
}
|
||||
|
||||
const rows = await qb.getMany();
|
||||
|
||||
const total = rows.length;
|
||||
const present = rows.filter((r) => r.status === 'present').length;
|
||||
const late = rows.filter((r) => r.status === 'late').length;
|
||||
const absent = rows.filter((r) => r.status === 'absent').length;
|
||||
const leave = rows.filter((r) => r.status === 'leave').length;
|
||||
|
||||
return {
|
||||
total,
|
||||
present,
|
||||
late,
|
||||
absent,
|
||||
leave,
|
||||
presentRate: total > 0 ? Number(((present / total) * 100).toFixed(1)) : 0,
|
||||
absentRate: total > 0 ? Number(((absent / total) * 100).toFixed(1)) : 0,
|
||||
lateRate: total > 0 ? Number(((late / total) * 100).toFixed(1)) : 0,
|
||||
leaveRate: total > 0 ? Number(((leave / total) * 100).toFixed(1)) : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user