feat: 集成 AI 对话与只读查询工具
This commit is contained in:
@@ -29,6 +29,14 @@ import {
|
||||
SaveAttendancePeriodConfigsDto,
|
||||
} from './dto/attendance.dto';
|
||||
|
||||
interface AgentAttendanceSummaryRow {
|
||||
date: string;
|
||||
classId: string | number;
|
||||
className: string;
|
||||
status: string;
|
||||
count: string | number;
|
||||
}
|
||||
|
||||
/** Keyed mutex serializing operations on the same attendance session. */
|
||||
class SessionMutex {
|
||||
private queueTails = new Map<number, Promise<void>>();
|
||||
@@ -156,6 +164,40 @@ export class AttendanceService {
|
||||
if (!assignment) throw new BadRequestException('只能访问自己任教班级的考勤');
|
||||
}
|
||||
|
||||
async agentGetAttendanceSummary(
|
||||
userId: number,
|
||||
canManageAll: boolean,
|
||||
query: { classId?: number; dateFrom?: string; dateTo?: string; limit?: number },
|
||||
) {
|
||||
const accessibleClassIds = await this.getAccessibleClassIds(userId, canManageAll);
|
||||
if (accessibleClassIds?.length === 0) return [];
|
||||
if (query.classId && accessibleClassIds && !accessibleClassIds.includes(query.classId)) return [];
|
||||
|
||||
const qb = this.attendanceRepo
|
||||
.createQueryBuilder('attendance')
|
||||
.leftJoin('attendance.class', 'class')
|
||||
.select('attendance.attendanceDate', 'date')
|
||||
.addSelect('attendance.classId', 'classId')
|
||||
.addSelect('class.name', 'className')
|
||||
.addSelect('attendance.status', 'status')
|
||||
.addSelect('COUNT(attendance.id)', 'count')
|
||||
.where('attendance.classId IS NOT NULL');
|
||||
if (query.classId) qb.andWhere('attendance.classId = :classId', { classId: query.classId });
|
||||
else if (accessibleClassIds) qb.andWhere('attendance.classId IN (:...accessibleClassIds)', { accessibleClassIds });
|
||||
if (query.dateFrom) qb.andWhere('attendance.attendanceDate >= :dateFrom', { dateFrom: query.dateFrom });
|
||||
if (query.dateTo) qb.andWhere('attendance.attendanceDate <= :dateTo', { dateTo: query.dateTo });
|
||||
const rows = await qb
|
||||
.groupBy('attendance.attendanceDate')
|
||||
.addGroupBy('attendance.classId')
|
||||
.addGroupBy('class.name')
|
||||
.addGroupBy('attendance.status')
|
||||
.orderBy('attendance.attendanceDate', 'DESC')
|
||||
.addOrderBy('class.name', 'ASC')
|
||||
.limit(query.limit ?? 30)
|
||||
.getRawMany<AgentAttendanceSummaryRow>();
|
||||
return rows.map((row) => ({ ...row, classId: Number(row.classId), count: Number(row.count || 0) }));
|
||||
}
|
||||
|
||||
private isClassStudentActiveOnDate(classStudent: Pick<ClassStudent, 'joinDate' | 'leaveDate' | 'status'>, lessonDate: string): boolean {
|
||||
const status = classStudent.status ?? 'active';
|
||||
if (!['active', 'left'].includes(status)) return false;
|
||||
|
||||
Reference in New Issue
Block a user