feat(attendance,elective): 考勤与选修课模块审计重构 — P0 修复 + i18n + Error Boundary

审计报告:docs/architecture/audit/attendance-elective-audit-report.md

P0 修复:
- attendance: getAttendanceStats 统计失真(仅基于前 20 条记录)改为 SQL 聚合查询
- attendance: getClassStudentsForAttendance 跨模块直查 classEnrollments 改为调用 classes data-access
- attendance: update/delete Action 新增资源归属校验(assertRecordOwnership)
- elective: update/delete/openSelection/closeSelection/runLottery Action 新增资源归属校验(assertCourseOwnership)

i18n 接入:
- 新增 attendance/elective 命名空间(zh-CN + en)
- attendance-stats-cards 接入 useTranslations
- elective-course-list/form 接入 useTranslations

类型安全(P1):
- elective-course-form: 移除 as 断言,改用类型守卫 isSelectionMode
- elective-course-list: 移除 null as never 类型逃逸,改用泛型

Error Boundary:
- 新增 admin/teacher attendance error.tsx
- 新增 admin/student elective error.tsx

架构图同步:
- 004: 修正 attendance/elective/parent 章节的导出函数、文件清单、已知问题
- 005: 修正 actions 的 usedBy(标记无调用方的死代码)、新增 issues 字段、更新依赖矩阵
This commit is contained in:
SpecialX
2026-06-22 16:17:00 +08:00
parent 5d42495480
commit 4833930834
16 changed files with 1431 additions and 48 deletions

View File

@@ -221,6 +221,22 @@ export const getActiveStudentIdsByClassId = async (classId: string): Promise<str
return rows.map((r) => r.studentId)
}
/**
* 获取班级所有活跃学生基本信息id/name/email按姓名升序。
* 供跨模块调用使用(如考勤点名),避免直接查询 classEnrollments 表。
*/
export const getClassActiveStudentsWithInfo = async (
classId: string
): Promise<Array<{ id: string; name: string; email: string }>> => {
const rows = await db
.select({ id: users.id, name: users.name, email: users.email })
.from(classEnrollments)
.innerJoin(users, eq(users.id, classEnrollments.studentId))
.where(and(eq(classEnrollments.classId, classId), eq(classEnrollments.status, "active")))
.orderBy(asc(users.name))
return rows.map((r) => ({ id: r.id, name: r.name ?? "Unknown", email: r.email }))
}
/**
* 获取教师在一个班级所教的科目 ID 列表。
* 参数顺序为 (classId, teacherId),供跨模块调用使用。