fix permissions and teacher attendance workflows

This commit is contained in:
2026-07-10 20:40:52 +08:00
parent 247879f276
commit 8ed1682b90
95 changed files with 4745 additions and 1237 deletions

View File

@@ -0,0 +1,120 @@
import { ScheduleSyncService } from './schedule-sync.service';
import { ClassSchedule } from '../entities';
describe('ScheduleSyncService — absence threshold', () => {
it('updates an existing 16:00-17:00 shift so checking in before 17:00 is not absent', async () => {
const scheduleRepo = {
find: jest.fn().mockResolvedValue([
{
id: 1,
classId: 10,
classroomId: 1,
weekDay: 5,
startTime: '16:00',
endTime: '17:00',
startDate: '2026-07-01',
endDate: '2026-07-31',
status: 'active',
} as ClassSchedule,
]),
};
const classStudentRepo = {
find: jest.fn().mockResolvedValue([{ classId: 10, studentId: 20, status: 'active' }]),
};
const mappingRepo = {
find: jest.fn().mockResolvedValue([{ studentId: 20, dingUserId: 'student-1' }]),
};
const classRepo = {
find: jest.fn().mockResolvedValue([{ id: 10, name: '冲刺班' }]),
};
const dingTalkService = {
queryShifts: jest.fn().mockResolvedValue([{ id: 456, name: '排课_16:00-17:00' }]),
upsertShift: jest.fn().mockResolvedValue(456),
queryAttendanceGroups: jest.fn().mockResolvedValue([
{ group_id: 123, group_name: '排课_冲刺班', type: 'TURN', member_count: 1 },
]),
updateAttendanceGroup: jest.fn().mockResolvedValue(undefined),
createAttendanceGroup: jest.fn(),
scheduleUsers: jest.fn().mockResolvedValue(undefined),
};
const service = new ScheduleSyncService(
scheduleRepo as never,
classStudentRepo as never,
mappingRepo as never,
classRepo as never,
dingTalkService as never,
);
await service.syncAll('2026-07-10', 1);
expect(dingTalkService.upsertShift).toHaveBeenCalledWith(expect.objectContaining({
id: 456,
name: '排课_16:00-17:00',
setting: expect.objectContaining({ absenteeism_late_minutes: 60 }),
}));
});
});
describe('ScheduleSyncService — attendance machine only', () => {
it('updates a reused attendance group with machine-only restrictions', async () => {
const scheduleRepo = {
find: jest.fn().mockResolvedValue([
{
id: 1,
classId: 10,
classroomId: 1,
weekDay: 1,
startTime: '09:00',
endTime: '16:00',
startDate: '2026-07-01',
endDate: '2026-07-31',
status: 'active',
} as ClassSchedule,
]),
};
const classStudentRepo = {
find: jest.fn().mockResolvedValue([{ classId: 10, studentId: 20, status: 'active' }]),
};
const mappingRepo = {
find: jest.fn().mockResolvedValue([{ studentId: 20, dingUserId: 'student-1' }]),
};
const classRepo = {
find: jest.fn().mockResolvedValue([{ id: 10, name: '冲刺班' }]),
};
const dingTalkService = {
queryShifts: jest.fn().mockResolvedValue([{ id: 456, name: '排课_09:00-16:00' }]),
upsertShift: jest.fn().mockResolvedValue(456),
queryAttendanceGroups: jest.fn().mockResolvedValue([
{ group_id: 123, group_name: '排课_冲刺班', type: 'TURN', member_count: 1 },
]),
updateAttendanceGroup: jest.fn().mockResolvedValue(undefined),
createAttendanceGroup: jest.fn(),
scheduleUsers: jest.fn().mockResolvedValue(undefined),
};
const service = new ScheduleSyncService(
scheduleRepo as never,
classStudentRepo as never,
mappingRepo as never,
classRepo as never,
dingTalkService as never,
);
await (service.syncAll as unknown as (
dateFrom: string,
days: number,
opUserId: string,
attendanceMachineOnly: boolean,
) => Promise<unknown>)('2026-07-13', 1, 'manager', true);
expect(dingTalkService.updateAttendanceGroup).toHaveBeenCalledWith(expect.objectContaining({
id: 123,
name: '排课_冲刺班',
owner: 'manager',
shift_ids: [456],
attendance_machine_only: true,
}));
expect(dingTalkService.createAttendanceGroup).not.toHaveBeenCalled();
});
});