feat: add importDingTalkUsers and org-tree-with-users to SyncService

This commit is contained in:
2026-07-09 11:04:24 +08:00
parent 2b60a10927
commit cd7f9c90b3
3 changed files with 360 additions and 0 deletions

View File

@@ -7,6 +7,17 @@ import { DingTalkService } from '../integration/dingtalk.service';
import { WeComService } from '../integration/wecom.service';
import { AttendanceImportService } from '../attendance/attendance-import.service';
import { ScheduleSyncService } from './schedule-sync.service';
import { User } from '../entities/user.entity';
import { Student } from '../entities/student.entity';
import { Role } from '../entities/role.entity';
import * as bcrypt from 'bcryptjs';
export interface ImportUserDto {
dingUserId: string;
name: string;
mobile: string;
roleId: number | null;
}
@Injectable()
export class SyncService {
@@ -18,6 +29,12 @@ export class SyncService {
private readonly syncStateRepo: Repository<SyncState>,
@InjectRepository(UserDingMapping)
private readonly mappingRepo: Repository<UserDingMapping>,
@InjectRepository(User)
private readonly userRepo: Repository<User>,
@InjectRepository(Student)
private readonly studentRepo: Repository<Student>,
@InjectRepository(Role)
private readonly roleRepo: Repository<Role>,
private readonly dingTalkService: DingTalkService,
private readonly weComService: WeComService,
private readonly attendanceImportService: AttendanceImportService,
@@ -92,6 +109,88 @@ export class SyncService {
return this.dingTalkService.fetchOrgTree(rootDeptId);
}
/** 获取钉钉组织部门树(含用户),供前端同步用户选择器使用 */
async getDingTalkOrgTreeWithUsers(rootDeptId = 1) {
return this.dingTalkService.fetchOrgTreeWithUsers(rootDeptId);
}
/**
* 从钉钉导入用户roleId 非 null → 老师User + 指定角色roleId null → 学生User + Student
* 已存在 UserDingMapping 的记录跳过。
*/
async importDingTalkUsers(users: ImportUserDto[]): Promise<{
teacherCount: number;
studentCount: number;
skipped: number;
}> {
let teacherCount = 0;
let studentCount = 0;
let skipped = 0;
for (const u of users) {
// 检查是否已存在映射
const existing = await this.mappingRepo.findOne({
where: { dingUserId: u.dingUserId },
});
if (existing) {
skipped++;
continue;
}
try {
const username = u.mobile || `dd_${u.dingUserId}`;
const passwordHash = await bcrypt.hash('123456', 10);
const user = this.userRepo.create({
username,
name: u.name,
passwordHash,
isActive: true,
});
await this.userRepo.save(user);
if (u.roleId != null) {
// 老师:分配角色
const role = await this.roleRepo.findOne({ where: { id: u.roleId } });
if (role) {
user.roles = [role];
await this.userRepo.save(user);
} else {
this.logger.warn(`角色 id=${u.roleId} 不存在,用户 ${u.name} 未分配角色`);
}
teacherCount++;
} else {
// 学生:创建 Student 记录
const student = this.studentRepo.create({
name: u.name,
phone: u.mobile || undefined,
userId: user.id,
status: 'active',
});
await this.studentRepo.save(student);
studentCount++;
}
// 创建映射
const mapping = this.mappingRepo.create({
dingUserId: u.dingUserId,
userId: user.id,
dingName: u.name,
dingMobile: u.mobile,
});
await this.mappingRepo.save(mapping);
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
this.logger.error(`导入用户 ${u.name}(${u.dingUserId}) 失败: ${msg}`);
}
}
this.logger.log(
`钉钉用户导入完成: ${teacherCount} 位老师, ${studentCount} 位学生, ${skipped} 跳过`,
);
return { teacherCount, studentCount, skipped };
}
// ── 排班同步 ──
/** 将本地排课同步到钉钉考勤排班 */