考试排考

This commit is contained in:
2026-07-25 14:51:17 +08:00 Unverified
parent 36049af692
commit 4e7da6709e
12 changed files with 1306 additions and 118 deletions
@@ -109,6 +109,11 @@ public sealed class TimetableDataService(AppDbContext db)
term.Id,
studentId,
cancellationToken);
// Load exam sessions for student/teacher timetables
var examEntries = await LoadExamEntriesAsync(
resourceType, resourceId, term.Id, studentId, slots, cancellationToken);
return new TimetableData(
term,
subject,
@@ -117,7 +122,8 @@ public sealed class TimetableDataService(AppDbContext db)
plan,
slots,
entries,
flexibleCourses);
flexibleCourses,
examEntries);
}
private async Task<TimetableSubjectDto?> LoadSubjectAsync(
@@ -286,6 +292,101 @@ public sealed class TimetableDataService(AppDbContext db)
x.Notes))
.ToListAsync(cancellationToken);
}
private async Task<List<TimetableEntryDto>> LoadExamEntriesAsync(
TimetableResourceType resourceType,
Guid resourceId,
Guid academicTermId,
Guid? studentId,
IReadOnlyList<TimetableSlotDto> slots,
CancellationToken cancellationToken)
{
if (resourceType == TimetableResourceType.Classroom) return [];
var slotLookup = slots.ToDictionary(x => x.PeriodNumber);
IQueryable<ExamSession> source = db.ExamSessions.AsNoTracking()
.Where(x => x.ExamPlan!.AcademicTermId == academicTermId &&
x.ExamPlan.Status == ExamPlanStatus.Published &&
x.ClassroomId != null);
if (resourceType == TimetableResourceType.Teacher)
{
source = source.Where(x =>
x.Invigilators.Any(i => i.TeacherId == resourceId));
}
else if (studentId.HasValue)
{
source = source.Where(x =>
db.CourseEnrollments.Any(e =>
e.StudentId == studentId.Value &&
e.Status == CourseEnrollmentStatus.Enrolled &&
e.CourseSelectionOffering!.TeachingTaskId == x.TeachingTaskId));
}
else
{
// Class timetable: exams for the class's teaching tasks
source = source.Where(x =>
x.TeachingTask!.Classes.Any(c =>
c.AdministrativeClassId == resourceId));
}
var sessions = await source
.OrderBy(x => x.ExamDate)
.ThenBy(x => x.StartPeriod)
.Select(x => new
{
x.Id,
x.TeachingTaskId,
x.TeachingTask!.TaskNumber,
TaskName = x.TeachingTask.Name,
CourseCode = x.TeachingTask.Course!.Code,
CourseName = x.TeachingTask.Course.Name,
TeacherNames = x.TeachingTask.Teachers
.OrderByDescending(t => t.IsPrimary)
.Select(t => t.Teacher!.Name),
ClassNames = x.TeachingTask.Classes
.Select(c => c.AdministrativeClass!.Name),
ClassroomName = x.Classroom!.Name,
BuildingName = x.Classroom.Building!.Name,
CampusName = x.Classroom.Building.Campus!.Name,
x.ExamDate,
x.StartPeriod,
x.PeriodCount,
x.ExamPlan!.Name,
InvigilatorNames = x.Invigilators
.Select(i => i.Teacher!.Name),
x.Notes
})
.ToListAsync(cancellationToken);
return sessions.Select(x =>
{
var dayOfWeek = x.ExamDate.DayOfWeek == 0 ? 7 : (int)x.ExamDate.DayOfWeek;
return new TimetableEntryDto(
x.Id,
x.TeachingTaskId,
x.TaskNumber,
x.TaskName,
x.CourseCode,
x.CourseName,
x.TeacherNames.Concat(
new[] { "监考:" + string.Join("、", x.InvigilatorNames) }),
x.ClassNames,
x.ClassroomName,
x.BuildingName,
x.CampusName,
dayOfWeek,
x.StartPeriod,
x.PeriodCount,
1, 1,
WeekPattern.All,
x.Notes,
true,
x.Name,
x.ExamDate);
}).ToList();
}
}
public enum TimetableResourceType
@@ -303,7 +404,8 @@ public sealed record TimetableData(
TimetablePlanDto? Plan,
IReadOnlyList<TimetableSlotDto> Slots,
IReadOnlyList<TimetableEntryDto> Entries,
IReadOnlyList<FlexibleCourseDto> FlexibleCourses);
IReadOnlyList<FlexibleCourseDto> FlexibleCourses,
IReadOnlyList<TimetableEntryDto> ExamEntries);
public sealed record TimetableTermDto(
Guid Id,
@@ -362,7 +464,10 @@ public sealed record TimetableEntryDto(
int StartWeek,
int EndWeek,
WeekPattern WeekPattern,
string? Notes);
string? Notes,
bool IsExam = false,
string? ExamPlanName = null,
DateOnly? ExamDate = null);
public sealed record FlexibleCourseDto(
Guid Id,