fix: code-review 审查问题修复 + A2UI 测试补齐
Standards 轴: - 移除 uiArtifacts.ts 的 payloadOf 死代码残留 - AttendanceDevices 残留 any 类型化(补 ClassroomOption.status 字段) - 批量考勤纠错区分业务失败(已结算/无权限)与系统错误, 前端提示精确到两类数量 - Dashboard queryFn 六段重复校验块收敛为 safeValidate 助手 Spec 轴: - 补齐阶段 3.4 A2UI 测试:图表空数据占位、ArtifactErrorBoundary 降级隔离、useSubmissionState/useXCardSurface 单测(7 用例) - 阶段 2.2 补两处引导:教师工作台区分「今日无课」与「未分配班级」、 班级花名册空态带「添加学员」动作 - 契约文档修正 DynamicReview 状态管理描述(多提交点如实说明) aislop 剩余 16 警告均为必要豁免(类型边界/声明式 SQL 配置/既有文件规模)
This commit is contained in:
@@ -29,6 +29,19 @@ function can(context: AgentToolContext, permission: string): boolean {
|
||||
return context.isSuperAdmin || context.permissions.includes(permission);
|
||||
}
|
||||
|
||||
/** 教师作用域:仅统计该教师任课班级的数据 */
|
||||
function teacherScoped(scope: StudentAccessScope): boolean {
|
||||
return scope.type === 'teacher';
|
||||
}
|
||||
|
||||
/** 教师作用域下的班级过滤 SQL 片段(需配合 class_student cs 别名) */
|
||||
const TEACHER_CLASS_FILTER_SQL =
|
||||
'AND cs.class_id IN (SELECT ct.class_id FROM class_teacher ct WHERE ct.user_id = ?)';
|
||||
|
||||
function teacherParams(scope: StudentAccessScope): unknown[] {
|
||||
return scope.type === 'teacher' ? [scope.userId] : [];
|
||||
}
|
||||
|
||||
function today(): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
@@ -78,10 +91,10 @@ const TASKS: readonly TaskDefinition[] = [
|
||||
INNER JOIN class_student cs ON cs.student_id = s.id AND cs.status = 'active'
|
||||
LEFT JOIN occupancies o ON o.student_id = s.id AND o.status = 'active'
|
||||
WHERE s.status = 'active'
|
||||
${scope.type === 'teacher' ? 'AND cs.class_id IN (SELECT ct.class_id FROM class_teacher ct WHERE ct.user_id = ?)' : ''}
|
||||
${teacherScoped(scope) ? TEACHER_CLASS_FILTER_SQL : ''}
|
||||
AND o.id IS NULL
|
||||
`,
|
||||
params: scope.type === 'teacher' ? [scope.userId] : [],
|
||||
params: teacherParams(scope),
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -93,13 +106,13 @@ const TASKS: readonly TaskDefinition[] = [
|
||||
sql: (scope) => ({
|
||||
sql: `
|
||||
SELECT COUNT(DISTINCT o.id) AS cnt FROM occupancies o
|
||||
${scope.type === 'teacher' ? 'INNER JOIN class_student cs ON cs.student_id = o.student_id AND cs.status = \'active\'' : ''}
|
||||
${teacherScoped(scope) ? "INNER JOIN class_student cs ON cs.student_id = o.student_id AND cs.status = 'active'" : ''}
|
||||
LEFT JOIN bills b ON b.student_id = o.student_id
|
||||
WHERE o.status = 'active'
|
||||
${scope.type === 'teacher' ? 'AND cs.class_id IN (SELECT ct.class_id FROM class_teacher ct WHERE ct.user_id = ?)' : ''}
|
||||
${teacherScoped(scope) ? TEACHER_CLASS_FILTER_SQL : ''}
|
||||
AND b.id IS NULL
|
||||
`,
|
||||
params: scope.type === 'teacher' ? [scope.userId] : [],
|
||||
params: teacherParams(scope),
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Post, Put, Delete, Body, Param, Query, Request, Res, BadRequestException, ForbiddenException, ParseIntPipe } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Put, Delete, Body, Param, Query, Request, Res, BadRequestException, ForbiddenException, NotFoundException, ParseIntPipe } from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
import { AttendanceControllerBase, RequestUser } from './attendance.controller-base';
|
||||
import { AttendanceService } from './attendance.service';
|
||||
@@ -246,6 +246,7 @@ export class AttendanceRecordsController extends AttendanceControllerBase {
|
||||
) {
|
||||
const failedIds: number[] = [];
|
||||
let updated = 0;
|
||||
let systemFailed = 0;
|
||||
for (const id of dto.ids) {
|
||||
try {
|
||||
const existing = await this.service.findAttendanceRecord(id);
|
||||
@@ -255,15 +256,24 @@ export class AttendanceRecordsController extends AttendanceControllerBase {
|
||||
if (existing.classId != null) await this.assertClassAccess(req, existing.classId);
|
||||
await this.service.update(id, { status: dto.status, remark: dto.remark });
|
||||
updated += 1;
|
||||
} catch {
|
||||
} catch (error) {
|
||||
failedIds.push(id);
|
||||
// 业务失败(已结算/无权限等)与系统错误区分开,便于前端给出准确提示
|
||||
if (
|
||||
error instanceof BadRequestException ||
|
||||
error instanceof NotFoundException ||
|
||||
error instanceof ForbiddenException
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
systemFailed += 1;
|
||||
}
|
||||
}
|
||||
await logAudit(this.logService, req, {
|
||||
module: '考勤管理', action: '批量修改考勤状态', targetId: 0, targetType: 'attendanceRecord',
|
||||
detail: `批量 ${dto.ids.length} 条 → ${dto.status},成功 ${updated},失败 ${failedIds.length}`,
|
||||
detail: `批量 ${dto.ids.length} 条 → ${dto.status},成功 ${updated},失败 ${failedIds.length},系统错误 ${systemFailed}`,
|
||||
});
|
||||
return { updated, failed: failedIds.length, failedIds };
|
||||
return { updated, failed: failedIds.length, failedIds, systemFailed };
|
||||
}
|
||||
|
||||
// ── Update a single attendance record ──
|
||||
|
||||
Reference in New Issue
Block a user