fix: defer attendance settlement until lesson end
This commit is contained in:
@@ -80,6 +80,49 @@ describe('AttendanceSettlementService', () => {
|
||||
expect(attendanceService.createLessonAttendanceFromDingTalk).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not settle an in-progress lesson before its end time', async () => {
|
||||
const { service, scheduleRepo, sessionRepo, attendanceService, importService } = createService();
|
||||
scheduleRepo.find.mockResolvedValue([schedule]);
|
||||
sessionRepo.find.mockResolvedValue([
|
||||
{
|
||||
id: 90,
|
||||
scheduleId: 2,
|
||||
lessonDate: '2026-07-13',
|
||||
status: 'in_progress',
|
||||
schedule,
|
||||
},
|
||||
]);
|
||||
|
||||
await service.settleEndedLessons(new Date('2026-07-13T09:30:00+08:00'));
|
||||
|
||||
expect(importService.importFromDingTalk).not.toHaveBeenCalled();
|
||||
expect(attendanceService.createLessonAttendanceFromDingTalk).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('settles an in-progress lesson when its end time is reached', async () => {
|
||||
const { service, scheduleRepo, sessionRepo, attendanceService } = createService();
|
||||
scheduleRepo.find.mockResolvedValue([schedule]);
|
||||
sessionRepo.find.mockResolvedValue([
|
||||
{
|
||||
id: 90,
|
||||
scheduleId: 2,
|
||||
lessonDate: '2026-07-13',
|
||||
status: 'in_progress',
|
||||
schedule,
|
||||
},
|
||||
]);
|
||||
|
||||
await service.settleEndedLessons(new Date('2026-07-13T10:00:00+08:00'));
|
||||
|
||||
expect(attendanceService.createLessonAttendanceFromDingTalk).toHaveBeenCalledTimes(1);
|
||||
expect(attendanceService.createLessonAttendanceFromDingTalk).toHaveBeenCalledWith(
|
||||
2,
|
||||
'2026-07-13',
|
||||
21,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('continues with the next lesson when one settlement fails', async () => {
|
||||
const { service, scheduleRepo, sessionRepo, attendanceService, importService } = createService();
|
||||
scheduleRepo.find.mockResolvedValue([schedule, { ...schedule, id: 3 }]);
|
||||
@@ -163,6 +206,32 @@ describe('AttendanceSettlementService', () => {
|
||||
expect(attendanceService.createLessonAttendanceFromDingTalk).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not settle an in-progress overnight lesson before its next-day end time', async () => {
|
||||
const { service, scheduleRepo, sessionRepo, attendanceService, importService } = createService();
|
||||
const overnightSchedule = {
|
||||
...schedule,
|
||||
id: 4,
|
||||
weekDay: 7,
|
||||
startTime: '22:00',
|
||||
endTime: '01:00',
|
||||
};
|
||||
scheduleRepo.find.mockResolvedValue([]);
|
||||
sessionRepo.find.mockResolvedValue([
|
||||
{
|
||||
id: 91,
|
||||
scheduleId: 4,
|
||||
lessonDate: '2026-07-12',
|
||||
status: 'in_progress',
|
||||
schedule: overnightSchedule,
|
||||
},
|
||||
]);
|
||||
|
||||
await service.settleEndedLessons(new Date('2026-07-13T00:30:00+08:00'));
|
||||
|
||||
expect(importService.importFromDingTalk).not.toHaveBeenCalled();
|
||||
expect(attendanceService.createLessonAttendanceFromDingTalk).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('settles an overnight lesson after its next-day end time', async () => {
|
||||
const { service, scheduleRepo, sessionRepo, attendanceService } = createService();
|
||||
scheduleRepo.find.mockResolvedValue([
|
||||
|
||||
@@ -61,7 +61,11 @@ export class AttendanceSettlementService {
|
||||
}
|
||||
}
|
||||
for (const session of sessions) {
|
||||
if (session.status === 'in_progress' && session.schedule) {
|
||||
if (
|
||||
session.status === 'in_progress' &&
|
||||
session.schedule &&
|
||||
this.hasOccurrenceEnded(session.schedule, session.lessonDate, clock)
|
||||
) {
|
||||
candidates.set(`${session.scheduleId}|${session.lessonDate}`, {
|
||||
schedule: session.schedule,
|
||||
lessonDate: session.lessonDate,
|
||||
@@ -163,6 +167,18 @@ export class AttendanceSettlementService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private hasOccurrenceEnded(
|
||||
schedule: ClassSchedule,
|
||||
lessonDate: string,
|
||||
clock: { date: string; minutes: number },
|
||||
): boolean {
|
||||
const occurrenceEndDate = this.isOvernight(schedule)
|
||||
? this.shiftDate(lessonDate, 1)
|
||||
: lessonDate;
|
||||
if (clock.date !== occurrenceEndDate) return clock.date > occurrenceEndDate;
|
||||
return clock.minutes >= this.toMinutes(schedule.endTime);
|
||||
}
|
||||
|
||||
private isOvernight(schedule: ClassSchedule): boolean {
|
||||
return this.toMinutes(schedule.endTime) <= this.toMinutes(schedule.startTime);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user