变更摘要:考试安排注入课表

问题根因

  TimetableDataService.LoadExamEntriesAsync 只查询旧的 ExamSession(legacy 直接分配教室),完全忽略自动编排生成的
  ExamRoomAssignment(混排考场),导致:

  ┌──────────┬──────────────────────────┬───────────────────────────────────┐
  │ 课表类型 │           之前           │               之后                │
  ├──────────┼──────────────────────────┼───────────────────────────────────┤
  │ 教室课表 │ return [] 硬编码空数组   │ 查询 legacy + mixed room 两类数据 │
  ├──────────┼──────────────────────────┼───────────────────────────────────┤
  │ 教师课表 │ 只显示监考               │ 监考 + 自己授课的考试             │
  ├──────────┼──────────────────────────┼───────────────────────────────────┤
  │ 学生课表 │ 只通过 CourseEnrollments │ 同时查 ExamSeats(座位分配)      │
  ├──────────┼──────────────────────────┼───────────────────────────────────┤
  │ 空闲教室 │ 只排除 legacy 考试占用   │ 同时排除混排考场占用              │
  └──────────┴──────────────────────────┴───────────────────────────────────┘

  修改的文件

  1. TimetableDataService.cs — 重写 LoadExamEntriesAsync

  - 拆分为两个数据源:legacy ExamSessions + mixed ExamRoomAssignment(通过 ExamRoomSession 关联)
  - 新增 ApplyLegacyResourceFilter / ApplyMixedResourceFilter
  两个过滤方法,按资源类型(Classroom/Teacher/Student/Class)分别过滤
  - 新增 MapToEntryDto 统一映射到 TimetableEntryDto
  - 新增 ExamSessionProjection record 统一两个数据源的投影

  各资源类型的数据来源:

  ┌───────────┬────────────────────────────────┬──────────────────────────────────────┐
  │ 资源类型  │          Legacy 过滤           │           Mixed-room 过滤            │
  ├───────────┼────────────────────────────────┼──────────────────────────────────────┤
  │ Classroom │ ExamSessions.ClassroomId == id │ ExamRoom.ClassroomId == id           │
  ├───────────┼────────────────────────────────┼──────────────────────────────────────┤
  │ Teacher   │ 监考 + 授课教师                │ 监考 + 授课教师                      │
  ├───────────┼────────────────────────────────┼──────────────────────────────────────┤
  │ Student   │ CourseEnrollments + 行政班     │ ExamSeats.StudentId                  │
  ├───────────┼────────────────────────────────┼──────────────────────────────────────┤
  │ Class     │ TeachingTask.Classes           │ SessionLinks → ExamSession → Classes │
  └───────────┴────────────────────────────────┴──────────────────────────────────────┘

  2. ClassroomReservationAvailabilityService.cs — 补充混排考场占用

  在 GetOccupiedClassroomIdsAsync 新增 ExamRoomAssignment 查询,空闲教室查询现在会正确排除混排考场占用的教室。

  3. TimetablesController.cs — 课表选项含考试学期

  - 学期级的 HasPublishedTimetable 增加 ExamPlans.Status == Published 判断
  - 班级级的 HasPublishedTimetable 增加 ExamSessions 关联判断
This commit is contained in:
2026-07-28 08:27:22 +08:00 Unverified
parent 3ec0f117d3
commit 7e14b22296
3 changed files with 208 additions and 69 deletions
@@ -367,7 +367,10 @@ public sealed class TimetablesController(
db.TeachingTasks.Any(task =>
task.AcademicTermId == x.Id &&
task.Status == TeachingTaskStatus.Published &&
task.SchedulingMode == TeachingTaskSchedulingMode.Flexible)))
task.SchedulingMode == TeachingTaskSchedulingMode.Flexible) ||
db.ExamPlans.Any(plan =>
plan.AcademicTermId == x.Id &&
plan.Status == ExamPlanStatus.Published)))
.ToListAsync(cancellationToken);
var defaultTermId = terms.FirstOrDefault(x => x.IsCurrent)?.Id
?? terms.FirstOrDefault(x => x.HasPublishedTimetable)?.Id;
@@ -394,7 +397,12 @@ public sealed class TimetablesController(
task.AcademicTermId == defaultTermId.Value &&
task.Status == TeachingTaskStatus.Published &&
task.SchedulingMode == TeachingTaskSchedulingMode.Flexible &&
task.Classes.Any(item => item.AdministrativeClassId == x.Id)))))
task.Classes.Any(item => item.AdministrativeClassId == x.Id)) ||
db.ExamSessions.Any(session =>
session.ExamPlan!.AcademicTermId == defaultTermId.Value &&
session.ExamPlan.Status == ExamPlanStatus.Published &&
session.TeachingTask!.Classes.Any(item =>
item.AdministrativeClassId == x.Id)))))
.ToListAsync(cancellationToken);
var colleges = await db.Colleges.AsNoTracking()
.Where(x => x.IsEnabled)