refactor(server): remove Department/UserDepartment entities, CampusScope, and departmentId from all entities
- Delete department.entity.ts, user-department.entity.ts - Remove Department/UserDepartment from entities/index.ts - Remove departmentId column from 18 entities (AttendanceRecord, ArchiveAttachment, Bill, ClassSchedule, Classroom, ClassroomRental, Deposit, DepositInstallment, ExamScore, LearningRecord, Occupancy, PersonalExpense, ResultArchive, Room, RoomExpense, Student, StudentEnrollment, StudentProfile, StudentReport) - Remove departments/ module entirely - Delete campus-scope.ts, campus-scope.middleware.ts (request-utils.ts kept — it's just IP extraction) - Simplify common.module.ts to empty module - Remove CampusScopeMiddleware from app.module.ts - Remove all CampusScope injections and filter calls across all services - Remove departmentId from all DTOs and controllers - Simplify dingtalk/wecom sync to only sync users (no dept table) - Update seed module to remove department seeding - Clean frontend compilation
This commit is contained in:
@@ -3,10 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, Not, IsNull } from 'typeorm';
|
||||
import {
|
||||
ClassSchedule,
|
||||
ClassStudent,
|
||||
ClassTeacher,
|
||||
Department,
|
||||
UserDepartment,
|
||||
} from '../entities';
|
||||
import { DingTalkService, DingTalkScheduleItem } from '../integration/dingtalk.service';
|
||||
|
||||
@@ -48,10 +45,6 @@ export class ScheduleSyncService {
|
||||
private readonly scheduleRepo: Repository<ClassSchedule>,
|
||||
@InjectRepository(ClassTeacher)
|
||||
private readonly classTeacherRepo: Repository<ClassTeacher>,
|
||||
@InjectRepository(Department)
|
||||
private readonly deptRepo: Repository<Department>,
|
||||
@InjectRepository(UserDepartment)
|
||||
private readonly userDeptRepo: Repository<UserDepartment>,
|
||||
private readonly dingTalkService: DingTalkService,
|
||||
) {}
|
||||
|
||||
@@ -75,7 +68,6 @@ export class ScheduleSyncService {
|
||||
status: 'active',
|
||||
teacherId: Not(IsNull()),
|
||||
},
|
||||
relations: ['department'],
|
||||
});
|
||||
|
||||
if (schedules.length === 0) {
|
||||
@@ -110,102 +102,84 @@ export class ScheduleSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 4: 按部门分组 ──
|
||||
const deptGroups = new Map<
|
||||
number,
|
||||
{ deptName: string; schedules: ClassSchedule[]; teacherIds: Set<number> }
|
||||
>();
|
||||
|
||||
// ── Step 4: Collect teacher IDs (no department entity) ──
|
||||
const teacherIds = new Set<number>();
|
||||
for (const s of schedules) {
|
||||
const deptId = s.departmentId || 0;
|
||||
if (!deptGroups.has(deptId)) {
|
||||
deptGroups.set(deptId, {
|
||||
deptName: (s.department as Department)?.name || `部门${deptId}`,
|
||||
schedules: [],
|
||||
teacherIds: new Set(),
|
||||
});
|
||||
}
|
||||
const group = deptGroups.get(deptId)!;
|
||||
group.schedules.push(s);
|
||||
if (s.teacherId) group.teacherIds.add(s.teacherId);
|
||||
if (s.teacherId) teacherIds.add(s.teacherId);
|
||||
}
|
||||
|
||||
// ── Step 5: 每个部门 → 考勤组 → 排班 ──
|
||||
// ── Step 5: Single group → attendance group → scheduling ──
|
||||
let syncedItems = 0;
|
||||
let skippedNoMapping = 0;
|
||||
let groupCount = 0;
|
||||
const groupDetails: ScheduleSyncResult['groups'] = [];
|
||||
|
||||
for (const [deptId, group] of deptGroups) {
|
||||
// 获取该部门教师的钉钉 userIds
|
||||
const dingUserIds: string[] = [];
|
||||
const teacherDingMap = new Map<number, string>(); // local teacherId → dingUserId
|
||||
for (const tid of group.teacherIds) {
|
||||
const dingId = userIdToDingId.get(tid);
|
||||
if (dingId) {
|
||||
dingUserIds.push(dingId);
|
||||
teacherDingMap.set(tid, dingId);
|
||||
}
|
||||
// Collect teacher→ding mapping
|
||||
const dingUserIds: string[] = [];
|
||||
const teacherDingMap = new Map<number, string>();
|
||||
for (const tid of teacherIds) {
|
||||
const dingId = userIdToDingId.get(tid);
|
||||
if (dingId) {
|
||||
dingUserIds.push(dingId);
|
||||
teacherDingMap.set(tid, dingId);
|
||||
}
|
||||
|
||||
if (dingUserIds.length === 0) {
|
||||
skippedNoMapping += group.schedules.length;
|
||||
this.logger.warn(`部门 ${group.deptName}: 无钉钉用户映射,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 该部门使用的班次 IDs
|
||||
const deptShiftIds = new Set<number>();
|
||||
for (const s of group.schedules) {
|
||||
const key = shiftKey(s.startTime, s.endTime);
|
||||
const sid = timeToShiftId.get(key);
|
||||
if (sid) deptShiftIds.add(sid);
|
||||
}
|
||||
|
||||
// 创建/匹配考勤组
|
||||
const groupName = `排课_${group.deptName}`;
|
||||
let attendanceGroupId: number;
|
||||
try {
|
||||
attendanceGroupId = await this.dingTalkService.findOrCreateAttendanceGroup(
|
||||
groupName,
|
||||
opUserId,
|
||||
dingUserIds,
|
||||
[...deptShiftIds],
|
||||
);
|
||||
groupCount++;
|
||||
} catch (e) {
|
||||
this.logger.error(`创建考勤组 ${groupName} 失败: ${(e as Error).message}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 展开排课为每日排班
|
||||
const items = this.expandSchedules(
|
||||
group.schedules,
|
||||
teacherDingMap,
|
||||
timeToShiftId,
|
||||
startDate,
|
||||
endDate,
|
||||
);
|
||||
skippedNoMapping += group.schedules.length - new Set(items.map((i) => i.userid)).size;
|
||||
|
||||
// 分批写入(每次最多200条)
|
||||
for (let i = 0; i < items.length; i += 200) {
|
||||
const batch = items.slice(i, i + 200);
|
||||
try {
|
||||
await this.dingTalkService.scheduleUsers(attendanceGroupId, batch, opUserId);
|
||||
syncedItems += batch.length;
|
||||
} catch (e) {
|
||||
this.logger.error(`排班写入失败 (groupId=${attendanceGroupId}, offset=${i}): ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
groupDetails.push({
|
||||
deptName: group.deptName,
|
||||
groupId: attendanceGroupId,
|
||||
itemCount: items.length,
|
||||
});
|
||||
}
|
||||
|
||||
if (dingUserIds.length === 0) {
|
||||
this.logger.warn('无钉钉用户映射,跳过全部排班');
|
||||
return { scheduleCount: schedules.length, shiftCount, groupCount: 0, syncedItems: 0, skippedNoMapping: schedules.length, groups: [] };
|
||||
}
|
||||
|
||||
// All used shift IDs
|
||||
const allShiftIds = new Set<number>();
|
||||
for (const s of schedules) {
|
||||
const key = shiftKey(s.startTime, s.endTime);
|
||||
const sid = timeToShiftId.get(key);
|
||||
if (sid) allShiftIds.add(sid);
|
||||
}
|
||||
|
||||
// Create/find attendance group
|
||||
const groupName = '排课_全部';
|
||||
let attendanceGroupId: number;
|
||||
try {
|
||||
attendanceGroupId = await this.dingTalkService.findOrCreateAttendanceGroup(
|
||||
groupName,
|
||||
opUserId,
|
||||
dingUserIds,
|
||||
[...allShiftIds],
|
||||
);
|
||||
groupCount++;
|
||||
} catch (e) {
|
||||
this.logger.error(`创建考勤组 ${groupName} 失败: ${(e as Error).message}`);
|
||||
return { scheduleCount: schedules.length, shiftCount, groupCount: 0, syncedItems: 0, skippedNoMapping: schedules.length, groups: [] };
|
||||
}
|
||||
|
||||
// Expand schedules to daily items
|
||||
const items = this.expandSchedules(
|
||||
schedules,
|
||||
teacherDingMap,
|
||||
timeToShiftId,
|
||||
startDate,
|
||||
endDate,
|
||||
);
|
||||
skippedNoMapping = schedules.length - new Set(items.map((i) => i.userid)).size;
|
||||
|
||||
// Batch write (max 200 per batch)
|
||||
for (let i = 0; i < items.length; i += 200) {
|
||||
const batch = items.slice(i, i + 200);
|
||||
try {
|
||||
await this.dingTalkService.scheduleUsers(attendanceGroupId, batch, opUserId);
|
||||
syncedItems += batch.length;
|
||||
} catch (e) {
|
||||
this.logger.error(`排班写入失败 (groupId=${attendanceGroupId}, offset=${i}): ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
groupDetails.push({
|
||||
deptName: groupName,
|
||||
groupId: attendanceGroupId,
|
||||
itemCount: items.length,
|
||||
});
|
||||
this.logger.log(
|
||||
`排班同步完成: ${schedules.length} 条排课 → ${syncedItems} 条钉钉排班, ` +
|
||||
`${shiftCount} 班次, ${groupCount} 考勤组, 跳过 ${skippedNoMapping} 条无映射`,
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
SyncState,
|
||||
StudentDingMapping,
|
||||
ClassSchedule,
|
||||
Department,
|
||||
UserDepartment,
|
||||
ClassTeacher,
|
||||
User,
|
||||
Student,
|
||||
@@ -26,8 +24,6 @@ import { ScheduleSyncService } from './schedule-sync.service';
|
||||
SyncState,
|
||||
StudentDingMapping,
|
||||
ClassSchedule,
|
||||
Department,
|
||||
UserDepartment,
|
||||
ClassTeacher,
|
||||
User,
|
||||
Student,
|
||||
|
||||
@@ -214,6 +214,6 @@ export class SyncService {
|
||||
|
||||
private async performWeComSync(_lastSyncAt: Date | null): Promise<number> {
|
||||
const result = await this.weComService.syncAll();
|
||||
return result.deptCount + result.userCount;
|
||||
return result.userCount;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user