refactor: replace UserDingMapping with StudentDingMapping entity

This commit is contained in:
2026-07-09 16:53:25 +08:00
parent 1fa336331c
commit ef1b46b9f4
18 changed files with 104 additions and 146 deletions

View File

@@ -5,7 +5,7 @@ import { Subject, Observable } from 'rxjs';
import {
DingAttendanceRaw,
Student,
UserDingMapping,
StudentDingMapping,
} from '../entities';
import { DingTalkService, DingTalkAttendanceResult } from '../integration/dingtalk.service';
import { AttendanceService } from './attendance.service';
@@ -33,8 +33,8 @@ export class AttendanceImportService {
private readonly dingRawRepo: Repository<DingAttendanceRaw>,
@InjectRepository(Student)
private readonly studentRepo: Repository<Student>,
@InjectRepository(UserDingMapping)
private readonly userDingMappingRepo: Repository<UserDingMapping>,
@InjectRepository(StudentDingMapping)
private readonly studentDingMappingRepo: Repository<StudentDingMapping>,
private readonly dingTalkService: DingTalkService,
private readonly attendanceService: AttendanceService,
) {}

View File

@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AttendanceRecord, DingAttendanceRaw, Student, Class, ClassSchedule, ClassStudent, UserDingMapping } from '../entities';
import { AttendanceRecord, DingAttendanceRaw, Student, Class, ClassSchedule, ClassStudent, StudentDingMapping } from '../entities';
import { AttendanceService } from './attendance.service';
import { AttendanceImportService } from './attendance-import.service';
import { AttendanceController } from './attendance.controller';
@@ -10,7 +10,7 @@ import { IntegrationModule } from '../integration/integration.module';
@Module({
imports: [
TypeOrmModule.forFeature([AttendanceRecord, DingAttendanceRaw, Student, Class, ClassSchedule, ClassStudent, UserDingMapping]),
TypeOrmModule.forFeature([AttendanceRecord, DingAttendanceRaw, Student, Class, ClassSchedule, ClassStudent, StudentDingMapping]),
OperationLogsModule,
CommonModule,
IntegrationModule,

View File

@@ -9,7 +9,7 @@ import { Class } from '../entities/class.entity';
import { Student } from '../entities/student.entity';
import { ClassSchedule } from '../entities/class-schedule.entity';
import { ClassStudent } from '../entities/class-student.entity';
import { UserDingMapping } from '../entities/user-ding-mapping.entity';
import { StudentDingMapping } from '../entities/student-ding-mapping.entity';
import { BatchCreateAttendanceDto } from './dto/attendance.dto';
describe('AttendanceService — batchCreate', () => {
@@ -39,7 +39,7 @@ describe('AttendanceService — batchCreate', () => {
// Reserved for future tests (auto-match, schedule-based attendance, etc.)
const mockScheduleRepo = { find: jest.fn().mockResolvedValue([]) };
const mockClassStudentRepo = { find: jest.fn().mockResolvedValue([]) };
const mockUserDingMappingRepo = { find: jest.fn().mockResolvedValue([]) };
const mockStudentDingMappingRepo = { find: jest.fn().mockResolvedValue([]) };
const module: TestingModule = await Test.createTestingModule({
providers: [
@@ -49,7 +49,7 @@ describe('AttendanceService — batchCreate', () => {
{ provide: getRepositoryToken(Class), useValue: mockClassRepo },
{ provide: getRepositoryToken(Student), useValue: mockStudentRepo },
{ provide: getRepositoryToken(ClassSchedule), useValue: mockScheduleRepo },
{ provide: getRepositoryToken(UserDingMapping), useValue: mockUserDingMappingRepo },
{ provide: getRepositoryToken(StudentDingMapping), useValue: mockStudentDingMappingRepo },
{ provide: getRepositoryToken(ClassStudent), useValue: mockClassStudentRepo },
],
}).compile();
@@ -99,9 +99,9 @@ describe('AttendanceService — batchCreate', () => {
await expect(service.batchCreate(dto)).rejects.toThrow(BadRequestException);
});
it.skip('autoMatchDingRecords with UserDingMapping chain', async () => {
// TODO: match dingtalk raw records to students via UserDingMapping lookup,
it.skip('autoMatchDingRecords with StudentDingMapping chain', async () => {
// TODO: match dingtalk raw records to students via StudentDingMapping lookup,
// then to class schedules → ClassStudent association, producing attendance records.
// Requires mock setup for UserDingMapping, ClassSchedule, ClassStudent, and DingAttendanceRaw repos.
// Requires mock setup for StudentDingMapping, ClassSchedule, ClassStudent, and DingAttendanceRaw repos.
});
});

View File

@@ -5,7 +5,7 @@ import {
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, In, Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { AttendanceRecord, DingAttendanceRaw, Class, Student, ClassSchedule, ClassStudent, ScheduleType, UserDingMapping } from '../entities';
import { AttendanceRecord, DingAttendanceRaw, Class, Student, ClassSchedule, ClassStudent, ScheduleType, StudentDingMapping } from '../entities';
import {
BatchCreateAttendanceDto,
AttendanceSummaryQueryDto,
@@ -33,8 +33,8 @@ export class AttendanceService {
private scheduleRepo: Repository<ClassSchedule>,
@InjectRepository(ClassStudent)
private classStudentRepo: Repository<ClassStudent>,
@InjectRepository(UserDingMapping)
private userDingMappingRepo: Repository<UserDingMapping>,
@InjectRepository(StudentDingMapping)
private studentDingMappingRepo: Repository<StudentDingMapping>,
) {}
// ── Batch create attendance records ──
@@ -363,10 +363,10 @@ export class AttendanceService {
if (unmatched.length === 0) return { matched: 0, total: 0 };
// Build dingUserId → userId map from the mapping table
const mappings = await this.userDingMappingRepo.find();
const mappings = await this.studentDingMappingRepo.find();
const dingToUserId = new Map<string, number>();
for (const m of mappings) {
dingToUserId.set(m.dingUserId, m.userId);
dingToUserId.set(m.dingUserId, m.studentId);
}
// Build userId → studentId map (only students linked to a user)