feat(sync): sync class schedules to DingTalk students

Group active schedules by class, resolve enrolled students through DingTalk mappings, reuse shifts and attendance groups, and expose class-based sync status in the admin UI.
This commit is contained in:
2026-07-10 14:11:39 +08:00
parent bc6b8b0095
commit 55881863c1
5 changed files with 246 additions and 192 deletions

View File

@@ -562,24 +562,6 @@ export class DingTalkService {
return (data.result ?? []).map((s) => ({ id: s.id, name: s.name }));
}
/** 按名称查找班次,不存在则创建 */
async findOrCreateShift(name: string, startTime: string, endTime: string, opUserId = 'manager'): Promise<number> {
const existing = await this.queryShifts(opUserId);
const found = existing.find((s) => s.name === name);
if (found) return found.id;
return this.upsertShift({
name,
owner: opUserId,
sections: [{
times: [
{ check_type: 'OnDuty', across: 0, check_time: `1970-01-01 ${startTime}:00`, free_check: false },
{ check_type: 'OffDuty', across: 0, check_time: `1970-01-01 ${endTime}:00`, free_check: false },
],
}],
setting: { is_flexible: false, serious_late_minutes: -1, absenteeism_late_minutes: -1 },
});
}
// ═══════════════════════════════════════════
// 考勤排班 — 考勤组管理
@@ -672,29 +654,6 @@ export class DingTalkService {
return all;
}
/** 按名称查找考勤组,不存在则创建 */
async findOrCreateAttendanceGroup(
name: string, ownerUserId: string, memberUserIds: string[], shiftIds: number[],
): Promise<number> {
const existing = await this.queryAttendanceGroups(ownerUserId);
const found = existing.find((g) => g.group_name === name);
if (found) return found.group_id;
return this.createAttendanceGroup({
name,
type: 'TURN',
owner: ownerUserId,
members: memberUserIds.map((uid) => ({
role: 'Attendance',
type: 'StaffMember',
user_id: uid,
})),
shift_ids: shiftIds,
enable_emp_select_class: true,
disable_check_without_schedule: false,
disable_check_when_rest: true,
});
}
// ═══════════════════════════════════════════
// 考勤排班 — 排班分配