diff --git a/apps/server/src/sync/sync.service.ts b/apps/server/src/sync/sync.service.ts index 2a7469c..fdc05fe 100644 --- a/apps/server/src/sync/sync.service.ts +++ b/apps/server/src/sync/sync.service.ts @@ -1,7 +1,7 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { DataSource, Repository } from 'typeorm'; -import { SyncLog, SyncState, UserDingMapping, ClassStudent, ClassTeacher } from '../entities'; +import { SyncLog, SyncState, UserDingMapping, ClassStudent, ClassTeacher, Department } from '../entities'; import { Class as ClassEntity } from '../entities/class.entity'; import type { SyncPlatform, SyncType, SyncStatus } from '../entities/sync-log.entity'; import { DingTalkService } from '../integration/dingtalk.service'; @@ -153,13 +153,32 @@ export class SyncService { let skipped = 0; await this.dataSource.transaction(async (manager) => { + // 0. 同步部门:为每个班级标记的钉钉部门创建/查找本地 Department + const deptLocalIdMap = new Map(); // dingDeptId -> local deptId + for (const c of classItems) { + let dept = await manager.findOne(Department, { + where: { source: 'dingtalk', sourceId: String(c.deptId) }, + }); + if (!dept) { + dept = manager.create(Department, { + name: c.name, + type: 'department', + source: 'dingtalk', + sourceId: String(c.deptId), + }); + dept = await manager.save(dept); + } + deptLocalIdMap.set(c.deptId, dept.id); + } + // 1. 创建班级 const deptClassMap = new Map(); // deptId -> classId for (const c of classItems) { - // ponytail: Class keyword conflicts with TypeORM DeepPartial resolution + const deptId = deptLocalIdMap.get(c.deptId) ?? null; const result = await manager.save(ClassEntity, { name: c.name, code: c.code, + departmentId: deptId, classType: c.classType, startDate: c.startDate ?? null, endDate: c.endDate ?? null, @@ -174,75 +193,119 @@ export class SyncService { const existingMapping = await manager.findOne(UserDingMapping, { where: { dingUserId: u.dingUserId }, }); - if (existingMapping) { - skipped++; - continue; - } - - const username = `dd_${u.dingUserId}`; - const passwordHash = await bcrypt.hash('123456', 10); - - const user = manager.create(User, { - username, - name: u.name, - passwordHash, - isActive: true, - }); - await manager.save(user); + let userId: number | null = null; let isTeacher = false; - if (u.roleId != null) { - const role = await manager.findOne(Role, { where: { id: u.roleId } }); - if (!role) { - throw new BadRequestException(`角色 id=${u.roleId} 不存在`); - } - user.roles = [role]; - await manager.save(user); - isTeacher = true; - teacherCount++; - } else { - const student = manager.create(Student, { - name: u.name, - phone: u.mobile || undefined, - userId: user.id, - status: 'active', + if (existingMapping) { + skipped++; + userId = existingMapping.userId; + // 判断是老师还是学生:查角色 + const existingUser = await manager.findOne(User, { + where: { id: userId }, + relations: ['roles'], }); - await manager.save(student); - studentCount++; + isTeacher = (existingUser?.roles?.length ?? 0) > 0; + } else { + // 检查是否已存在(syncAll 或历史导入),避免撞 username 唯一约束 + const username = `dd_${u.dingUserId}`; + const whereConditions: Record[] = [{ username }]; + if (u.mobile) whereConditions.push({ username: u.mobile }); + let user = await manager.findOne(User, { where: whereConditions }); + + if (!user) { + const passwordHash = await bcrypt.hash('123456', 10); + user = manager.create(User, { + username, + name: u.name, + passwordHash, + isActive: true, + }); + await manager.save(user); + } else { + // 已存在:更新姓名 + user.name = u.name; + await manager.save(user); + } + userId = user.id; + + // 解析学生所属部门 + const studentDeptId = u.roleId == null && classItems.length > 0 && u.dingDeptIds?.length > 0 + ? deptLocalIdMap.get(u.dingDeptIds.find((d) => deptLocalIdMap.has(d)) ?? -1) ?? undefined + : undefined; + + if (u.roleId != null) { + const role = await manager.findOne(Role, { where: { id: u.roleId } }); + if (!role) throw new BadRequestException(`角色 id=${u.roleId} 不存在`); + user.roles = [role]; + await manager.save(user); + isTeacher = true; + teacherCount++; + } else { + // 学生:检查 Student 是否已存在,不存在则创建 + let student = await manager.findOne(Student, { where: { userId: user.id } }); + if (!student) { + student = manager.create(Student, { + name: u.name, + phone: u.mobile || undefined, + userId: user.id, + departmentId: studentDeptId, + status: 'active', + }); + await manager.save(student); + } + studentCount++; + } + + // 钉钉映射(幂等:可能已被 syncAll 创建) + const existingMappingForUser = await manager.findOne(UserDingMapping, { + where: { dingUserId: u.dingUserId }, + }); + if (!existingMappingForUser) { + const mapping = manager.create(UserDingMapping, { + dingUserId: u.dingUserId, + userId: user.id, + dingName: u.name, + dingMobile: u.mobile, + }); + await manager.save(mapping); + } } - // 钉钉映射 - const mapping = manager.create(UserDingMapping, { - dingUserId: u.dingUserId, - userId: user.id, - dingName: u.name, - dingMobile: u.mobile, - }); - await manager.save(mapping); - - // 3. 建立班级关联 + // 3. 建立班级关联(新建/已存在用户都处理) if (classItems.length > 0 && u.dingDeptIds?.length > 0) { for (const deptId of u.dingDeptIds) { const classId = deptClassMap.get(deptId); - if (!classId) continue; // 该部门未标为班级 + if (!classId) continue; if (isTeacher) { - const ct = manager.create(ClassTeacher, { - classId, - userId: user.id, - roleType: 'teacher', + const existingCT = await manager.findOne(ClassTeacher, { + where: { classId, userId }, }); - await manager.save(ct); + if (!existingCT) { + const ct = manager.create(ClassTeacher, { + classId, + userId, + roleType: 'teacher', + }); + await manager.save(ct); + } } else { - const studentRecord = await manager.findOne(Student, { where: { userId: user.id } }); - if (!studentRecord) continue; - const cs = manager.create(ClassStudent, { - classId, - studentId: studentRecord.id, - status: 'active', + const studentRecord = await manager.findOne(Student, { + where: { userId }, }); - await manager.save(cs); + if (!studentRecord) continue; + const existingCS = await manager.findOne(ClassStudent, { + where: { classId, studentId: studentRecord.id }, + }); + if (!existingCS) { + const cs = manager.create(ClassStudent, { + classId, + studentId: studentRecord.id, + status: 'active', + }); + await manager.save(cs); + } } } }