import { EntityManager, In } from 'typeorm'; import { Organization, Student, StudentDingMapping } from '../entities'; export interface DingTalkStudentInput { dingUserId: string; name: string; mobile?: string; } export interface DingTalkStudentConflict { dingUserId: string; name: string; reason: string; } export interface DingTalkStudentSyncResult { created: number; updated: number; studentIds: Map; conflicts: DingTalkStudentConflict[]; } export async function syncDingTalkStudents( manager: EntityManager, inputs: DingTalkStudentInput[], ): Promise { const users = new Map(); const conflicts: DingTalkStudentConflict[] = []; for (const input of inputs) { const dingUserId = input.dingUserId?.trim(); const name = input.name?.trim(); const mobile = input.mobile?.trim() || undefined; if (!dingUserId || dingUserId.length > 100 || !name || name.length > 50) { conflicts.push({ dingUserId: dingUserId || '', name: name || '', reason: '钉钉用户ID或姓名无效' }); continue; } if (mobile && mobile.length > 20) { conflicts.push({ dingUserId, name, reason: '手机号超过20个字符' }); continue; } if (!users.has(dingUserId)) users.set(dingUserId, { dingUserId, name, mobile }); } if (users.size === 0) { return { created: 0, updated: 0, studentIds: new Map(), conflicts }; } const dingUserIds = [...users.keys()]; const mappings = await manager.find(StudentDingMapping, { where: { dingUserId: In(dingUserIds) }, }); const mappingByDingId = new Map(mappings.map((mapping) => [mapping.dingUserId, mapping])); const mappedStudentIds = mappings.map((mapping) => mapping.studentId); const mappedStudents = mappedStudentIds.length ? await manager.find(Student, { where: { id: In(mappedStudentIds) } }) : []; const studentById = new Map(mappedStudents.map((student) => [student.id, student])); const studentIds = new Map(); const updates: Student[] = []; for (const mapping of mappings) { const input = users.get(mapping.dingUserId); const student = studentById.get(mapping.studentId); if (!input || !student) { conflicts.push({ dingUserId: mapping.dingUserId, name: input?.name || '', reason: '钉钉映射对应的学生不存在', }); continue; } studentIds.set(mapping.dingUserId, student.id); student.name = input.name; if (input.mobile) student.phone = input.mobile; updates.push(student); } const newUsers = [...users.values()].filter((user) => !mappingByDingId.has(user.dingUserId)); const mobiles = [...new Set(newUsers.map((user) => user.mobile).filter((mobile): mobile is string => !!mobile))]; const occupiedPhones = mobiles.length ? await manager.find(Student, { where: { phone: In(mobiles) } }) : []; const studentByPhone = new Map(occupiedPhones.map((student) => [student.phone, student])); const creatable = newUsers.filter((user) => { if (!user.mobile || !studentByPhone.has(user.mobile)) return true; conflicts.push({ dingUserId: user.dingUserId, name: user.name, reason: '手机号已属于其他学生,请人工绑定' }); return false; }); const host = creatable.length ? await manager.findOne(Organization, { where: { isHost: true, status: 'active' } }) : null; if (creatable.length && !host) throw new Error('尚未配置本机构'); if (updates.length) await manager.save(Student, updates); const createdStudents = creatable.length ? await manager.save( Student, creatable.map((user) => manager.create(Student, { name: user.name, phone: user.mobile, status: 'active', organizationId: host!.id, }), ), ) : []; if (createdStudents.length) { await manager.save( StudentDingMapping, createdStudents.map((student, index) => manager.create(StudentDingMapping, { dingUserId: creatable[index].dingUserId, studentId: student.id, }), ), ); createdStudents.forEach((student, index) => studentIds.set(creatable[index].dingUserId, student.id)); } return { created: createdStudents.length, updated: updates.length, studentIds, conflicts }; }