forked from wangziqi/gongxue-base
125 lines
4.3 KiB
TypeScript
125 lines
4.3 KiB
TypeScript
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<string, number>;
|
|
conflicts: DingTalkStudentConflict[];
|
|
}
|
|
|
|
export async function syncDingTalkStudents(
|
|
manager: EntityManager,
|
|
inputs: DingTalkStudentInput[],
|
|
): Promise<DingTalkStudentSyncResult> {
|
|
const users = new Map<string, DingTalkStudentInput>();
|
|
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<string, number>();
|
|
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 };
|
|
}
|