forked from wangziqi/gongxue-base
fix: repair 5 sites where StudentDingMapping.studentId was misused as User FK
- dingtalk.service.ts syncOneUser: query studentRepo (not userRepo) by mapping.studentId - sync.service.ts importDingTalkUsers: skip User role lookup for existing mappings - attendance.service.ts autoMatchDingRecords: use studentId directly, remove second-hop query - schedule-sync.service.ts syncAll: skip teacher mapping block (deprecated) - rbac.service.ts getUnboundUsers: return [] (method deleted in Task 4)
This commit is contained in:
@@ -362,28 +362,16 @@ export class AttendanceService {
|
|||||||
|
|
||||||
if (unmatched.length === 0) return { matched: 0, total: 0 };
|
if (unmatched.length === 0) return { matched: 0, total: 0 };
|
||||||
|
|
||||||
// Build dingUserId → userId map from the mapping table
|
// Build dingUserId → studentId map from the mapping table
|
||||||
const mappings = await this.studentDingMappingRepo.find();
|
const mappings = await this.studentDingMappingRepo.find();
|
||||||
const dingToUserId = new Map<string, number>();
|
const dingToStudentId = new Map<string, number>();
|
||||||
for (const m of mappings) {
|
for (const m of mappings) {
|
||||||
dingToUserId.set(m.dingUserId, m.studentId);
|
dingToStudentId.set(m.dingUserId, m.studentId);
|
||||||
}
|
|
||||||
|
|
||||||
// Build userId → studentId map (only students linked to a user)
|
|
||||||
const students = await this.studentRepo.find({
|
|
||||||
where: { userId: In([...dingToUserId.values()]) },
|
|
||||||
select: ['id', 'userId'],
|
|
||||||
});
|
|
||||||
const userIdToStudentId = new Map<number, number>();
|
|
||||||
for (const s of students) {
|
|
||||||
if (s.userId != null) userIdToStudentId.set(s.userId, s.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let matched = 0;
|
let matched = 0;
|
||||||
for (const record of unmatched) {
|
for (const record of unmatched) {
|
||||||
const userId = dingToUserId.get(record.dingUserId);
|
const studentId = dingToStudentId.get(record.dingUserId);
|
||||||
if (userId == null) continue;
|
|
||||||
const studentId = userIdToStudentId.get(userId);
|
|
||||||
if (studentId == null) continue;
|
if (studentId == null) continue;
|
||||||
|
|
||||||
record.matchedStudentId = studentId;
|
record.matchedStudentId = studentId;
|
||||||
|
|||||||
@@ -612,10 +612,10 @@ export class DingTalkService {
|
|||||||
let user: User | null = null;
|
let user: User | null = null;
|
||||||
|
|
||||||
if (mapping) {
|
if (mapping) {
|
||||||
user = await this.userRepo.findOne({ where: { id: mapping.studentId } });
|
const student = await this.studentRepo.findOne({ where: { id: mapping.studentId } });
|
||||||
if (user) {
|
if (student) {
|
||||||
user.name = du.name;
|
student.name = du.name;
|
||||||
await this.userRepo.save(user);
|
await this.studentRepo.save(student);
|
||||||
}
|
}
|
||||||
await this.studentDingMappingRepo.save(mapping);
|
await this.studentDingMappingRepo.save(mapping);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -269,14 +269,9 @@ export class RbacService {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ponytail: StudentDingMapping.studentId is Student FK, not User; method deleted in Task 4
|
||||||
async getUnboundUsers(): Promise<{ id: number; username: string; name: string }[]> {
|
async getUnboundUsers(): Promise<{ id: number; username: string; name: string }[]> {
|
||||||
return this.userRepo
|
return [];
|
||||||
.createQueryBuilder('u')
|
|
||||||
.leftJoin(StudentDingMapping, 'm', 'm.studentId = u.id')
|
|
||||||
.where('m.id IS NULL')
|
|
||||||
.andWhere('u.isArchived = false')
|
|
||||||
.select(['u.id', 'u.username', 'u.name'])
|
|
||||||
.getMany();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllRoles(): Promise<Role[]> {
|
async findAllRoles(): Promise<Role[]> {
|
||||||
|
|||||||
@@ -87,11 +87,8 @@ export class ScheduleSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 2: 获取教师→钉钉用户ID映射 ──
|
// ── Step 2: 获取教师→钉钉用户ID映射 ──
|
||||||
const teacherIds = [...new Set(schedules.map((s) => s.teacherId!).filter(Boolean))];
|
// ponytail: teacher scheduling deprecated; StudentDingMapping.studentId is Student FK, not User
|
||||||
const mappings = await this.studentDingMappingRepo.find({
|
const userIdToDingId = new Map<number, string>();
|
||||||
where: { studentId: In(teacherIds) },
|
|
||||||
});
|
|
||||||
const userIdToDingId = new Map(mappings.map((m) => [m.studentId, m.dingUserId]));
|
|
||||||
|
|
||||||
// ── Step 3: 按 (startTime, endTime) 创建/匹配班次 ──
|
// ── Step 3: 按 (startTime, endTime) 创建/匹配班次 ──
|
||||||
const shiftKey = (start: string, end: string) => `${start}-${end}`;
|
const shiftKey = (start: string, end: string) => `${start}-${end}`;
|
||||||
|
|||||||
@@ -199,13 +199,9 @@ export class SyncService {
|
|||||||
|
|
||||||
if (existingMapping) {
|
if (existingMapping) {
|
||||||
skipped++;
|
skipped++;
|
||||||
|
// ponytail: studentDingMapping.studentId is Student FK, not User; full rewrite in Task 4
|
||||||
userId = existingMapping.studentId;
|
userId = existingMapping.studentId;
|
||||||
// 判断是老师还是学生:查角色
|
isTeacher = false;
|
||||||
const existingUser = await manager.findOne(User, {
|
|
||||||
where: { id: userId },
|
|
||||||
relations: ['roles'],
|
|
||||||
});
|
|
||||||
isTeacher = (existingUser?.roles?.length ?? 0) > 0;
|
|
||||||
} else {
|
} else {
|
||||||
// 检查是否已存在(syncAll 或历史导入),避免撞 username 唯一约束
|
// 检查是否已存在(syncAll 或历史导入),避免撞 username 唯一约束
|
||||||
const username = `dd_${u.dingUserId}`;
|
const username = `dd_${u.dingUserId}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user