feat: improve attendance scheduling and API validation

This commit is contained in:
2026-07-14 23:12:14 +08:00
parent c75a08affe
commit e45da7f998
33 changed files with 869 additions and 297 deletions

View File

@@ -59,6 +59,7 @@ const endedSchedule = {
subject: '\u6570\u5B66',
status: 'active',
scheduleType: 'INTERNAL',
attendanceAdvanceMinutes: 30,
};
describe('AttendanceService \u2014 DingTalk course attendance', () => {
@@ -186,6 +187,40 @@ describe('AttendanceService \u2014 DingTalk course attendance', () => {
]);
});
it('counts both OnDuty and OffDuty punches only inside the configured window', async () => {
const { service, attendanceRepo, dingRawRepo, scheduleRepo, classStudentRepo, sessionRepo } =
createService();
scheduleRepo.findOne.mockResolvedValue({ ...endedSchedule, attendanceAdvanceMinutes: 20 });
sessionRepo.findOne.mockResolvedValue(null);
classStudentRepo.find.mockResolvedValue([
{ studentId: 1, student: { id: 1, name: '张三' } },
{ studentId: 2, student: { id: 2, name: '李四' } },
{ studentId: 3, student: { id: 3, name: '王五' } },
]);
dingRawRepo.find.mockResolvedValue([
{ matchedStudentId: 1, attendanceType: 'OffDuty', checkOutTime: new Date('2026-07-11T08:40:00+08:00') },
{ matchedStudentId: 2, attendanceType: 'OffDuty', checkOutTime: new Date('2026-07-11T10:00:00+08:00') },
{ matchedStudentId: 3, attendanceType: 'OnDuty', checkInTime: new Date('2026-07-11T08:39:59+08:00') },
{ matchedStudentId: 3, attendanceType: 'OffDuty', checkOutTime: new Date('2026-07-11T10:00:01+08:00') },
]);
await service.createLessonAttendanceFromDingTalk(4, '2026-07-11', 21, true);
expect(attendanceRepo.save).toHaveBeenCalledWith([
expect.objectContaining({ studentId: 1, status: 'present' }),
expect.objectContaining({ studentId: 2, status: 'present' }),
expect.objectContaining({ studentId: 3, status: 'absent' }),
]);
});
it('expands import dates when the pre-class window crosses midnight', () => {
const { service } = createService();
expect(service.getLessonAttendanceImportDateRange(
{ startTime: '00:15', endTime: '01:00', attendanceAdvanceMinutes: 30 },
'2026-07-11',
)).toEqual({ startDate: '2026-07-10', endDate: '2026-07-11' });
});
it('creates local attendance after the lesson starts', async () => {
const { service, attendanceRepo, dingRawRepo, scheduleRepo, classStudentRepo, sessionRepo } =
createService();