forked from wangziqi/gongxue-base
feat: DingTalk attendance import + integration config + expense types + UI polish
Server: - Add DingTalk attendance import service with SSE progress streaming - Add IntegrationConfig entity & module for multi-tenant DingTalk setup - Add ExpenseType entity & ExpenseTypesModule - Add SeedModule for DB initialization - Add UserDingMapping entity for DingTalk user linkage - Attendance service: import flow with dedup & student auto-mapping - Rooms service: time-range overlap queries - Sync controller/service: DingTalk integration wiring - Permission guard: refactor to pure re-export - Campus scope middleware: tenant-aware filtering Admin UI: - Attendance page: import UI with progress & result summary - All pages: tableStyle/tablePagination standardization - Login page: responsive styling - Sensitive data: useViewSensitive hook for masked viewing - Vite config: path aliases, build optimization - Test infra: vitest config, test utilities Docs: PRD DingTalk batch 1 & 2 design docs
This commit is contained in:
@@ -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 } from '../entities';
|
||||
import { AttendanceRecord, DingAttendanceRaw, Class, Student, ClassSchedule, ClassStudent, ScheduleType, UserDingMapping } from '../entities';
|
||||
import { CampusScope } from '../common/campus-scope';
|
||||
import {
|
||||
BatchCreateAttendanceDto,
|
||||
@@ -34,6 +34,8 @@ export class AttendanceService {
|
||||
private scheduleRepo: Repository<ClassSchedule>,
|
||||
@InjectRepository(ClassStudent)
|
||||
private classStudentRepo: Repository<ClassStudent>,
|
||||
@InjectRepository(UserDingMapping)
|
||||
private userDingMappingRepo: Repository<UserDingMapping>,
|
||||
private readonly scope: CampusScope,
|
||||
) {}
|
||||
|
||||
@@ -366,7 +368,7 @@ export class AttendanceService {
|
||||
return this.dingRawRepo.save(record);
|
||||
}
|
||||
|
||||
// ── Auto-match unmatched dingtalk records by phone/idCard/name ──
|
||||
// ── Auto-match unmatched dingtalk records via dingUserId → userId mapping chain ──
|
||||
async autoMatchDingRecords(): Promise<{ matched: number; total: number }> {
|
||||
const unmatched = await this.dingRawRepo.find({
|
||||
where: { matchStatus: '未处理' },
|
||||
@@ -374,15 +376,34 @@ 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 dingToUserId = new Map<string, number>();
|
||||
for (const m of mappings) {
|
||||
dingToUserId.set(m.dingUserId, m.userId);
|
||||
}
|
||||
|
||||
// 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;
|
||||
for (const record of unmatched) {
|
||||
const student = await this.studentRepo.findOne({ where: { name: record.userName } });
|
||||
if (student) {
|
||||
record.matchStatus = '已匹配';
|
||||
record.matchedStudentId = student.id;
|
||||
await this.dingRawRepo.save(record);
|
||||
matched++;
|
||||
}
|
||||
const userId = dingToUserId.get(record.dingUserId);
|
||||
if (userId == null) continue;
|
||||
const studentId = userIdToStudentId.get(userId);
|
||||
if (studentId == null) continue;
|
||||
|
||||
record.matchedStudentId = studentId;
|
||||
record.matchStatus = '已匹配';
|
||||
await this.dingRawRepo.save(record);
|
||||
matched++;
|
||||
}
|
||||
|
||||
return { matched, total: unmatched.length };
|
||||
|
||||
Reference in New Issue
Block a user