feat: 拆分学号/身份证字段 + 考勤教师展示 + 代码优化

- 入住导入模板:学号和身份证号拆为独立字段,前后端对齐
- 排课查询关联教师,考勤归档页展示教师姓名
- 抽查时段增加 IsIn 校验
- 抽取 withPessimisticWriteLock 去重悲观锁查询
- import 增加文件空 buffer 校验
- 测试 mock 补全,适配事务 manager
- MySQL init.sql VALUES() 语法兼容修复
This commit is contained in:
2026-07-20 11:54:01 +08:00
parent 375c7ec60b
commit c7ba2799b9
12 changed files with 772 additions and 365 deletions

View File

@@ -12,6 +12,7 @@ import {
ClassSchedule,
ClassStudent,
ClassTeacher,
TeacherRoleType,
ScheduleType,
StudentDingMapping,
} from '../entities';
@@ -970,8 +971,12 @@ export class AttendanceService {
async getScheduleOptionsForAttendance(classId: number, date: string) {
const weekDay = this.getWeekDayForDate(date);
return this.scheduleRepo
const { entities, raw } = await this.scheduleRepo
.createQueryBuilder('cs')
.leftJoin('cs.teacher', 'teacher')
.addSelect('cs.id', 'scheduleIdForTeacherMap')
.addSelect('teacher.username', 'teacherUsername')
.addSelect('teacher.name', 'teacherName')
.where('cs.classId = :classId', { classId })
.andWhere('cs.weekDay = :weekDay', { weekDay })
.andWhere('cs.startDate <= :date', { date })
@@ -979,7 +984,25 @@ export class AttendanceService {
.andWhere('cs.status = :status', { status: 'active' })
.orderBy('cs.startTime', 'ASC')
.addOrderBy('cs.subject', 'ASC')
.getMany();
.getRawAndEntities();
const teacherByScheduleId = new Map(
raw.map((row) => [
Number(row.scheduleIdForTeacherMap),
{
teacherName: row.teacherName || null,
teacherUsername: row.teacherUsername || null,
},
]),
);
return entities.map((schedule, index) => {
const teacher = teacherByScheduleId.get(schedule.id) ?? {
teacherName: raw[index]?.teacherName || null,
teacherUsername: raw[index]?.teacherUsername || null,
};
return { ...schedule, ...teacher };
});
}
private async buildCalendar(classId: number, weekStart: string) {
@@ -1095,9 +1118,51 @@ export class AttendanceService {
if (classIds.length === 0) return [];
const where = { id: In(classIds) };
const classes = await this.classRepo.find({ where });
const [classes, teachers] = await Promise.all([
this.classRepo.find({ where }),
this.classTeacherRepo.find({
where: {
classId: In(classIds),
roleType: In([
TeacherRoleType.HEAD_TEACHER,
TeacherRoleType.LIFE_TEACHER,
TeacherRoleType.SUBJECT_TEACHER,
]),
},
relations: ['user'],
order: { roleType: 'ASC', id: 'ASC' },
}),
]);
const nameMap = new Map(classes.map((c) => [c.id, c.name]));
return classIds.map((id) => ({ classId: id, className: nameMap.get(id) || `班级${id}` }));
const teacherMap = new Map<
number,
Array<{
userId: number;
username: string | null;
name: string | null;
roleType: string;
subject: string | null;
}>
>();
for (const teacher of teachers) {
const user = teacher.user as { username?: string | null; name?: string | null } | undefined;
const items = teacherMap.get(teacher.classId) ?? [];
items.push({
userId: teacher.userId,
username: user?.username || null,
name: user?.name || null,
roleType: teacher.roleType,
subject: teacher.subject || null,
});
teacherMap.set(teacher.classId, items);
}
return classIds.map((id) => ({
classId: id,
className: nameMap.get(id) || `班级${id}`,
teachers: teacherMap.get(id) ?? [],
}));
}
// ── DingAttendance raw records ──