自动排考
This commit is contained in:
@@ -108,8 +108,21 @@ public sealed class ExamsController(
|
||||
}
|
||||
|
||||
[HttpGet("plans/{id:guid}")]
|
||||
public async Task<ActionResult> GetPlan(Guid id, CancellationToken cancellationToken)
|
||||
public async Task<ActionResult> GetPlan(
|
||||
Guid id,
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
string? keyword = null,
|
||||
string? allocation = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
page = Math.Max(1, page);
|
||||
pageSize = Math.Clamp(pageSize, 10, 100);
|
||||
keyword = Normalize(keyword);
|
||||
allocation = Normalize(allocation)?.ToLowerInvariant();
|
||||
if (allocation is not null and not ("room" or "invigilator" or "complete"))
|
||||
return ValidationProblem("分配状态筛选值无效。");
|
||||
|
||||
var manager = IsManager();
|
||||
var plan = await db.ExamPlans.AsNoTracking()
|
||||
.Where(x => x.Id == id && (manager || x.Status == ExamPlanStatus.Published))
|
||||
@@ -122,71 +135,157 @@ public sealed class ExamsController(
|
||||
x.Status,
|
||||
x.Notes,
|
||||
x.PublishedAt,
|
||||
Sessions = x.Sessions.OrderBy(item => item.ExamDate)
|
||||
.ThenBy(item => item.StartPeriod).Select(item => new
|
||||
{
|
||||
item.Id,
|
||||
item.TeachingTaskId,
|
||||
item.TeachingTask!.TaskNumber,
|
||||
TaskName = item.TeachingTask.Name,
|
||||
CourseCode = item.TeachingTask.Course!.Code,
|
||||
CourseName = item.TeachingTask.Course.Name,
|
||||
item.ClassroomId,
|
||||
ClassroomName = item.Classroom != null ? item.Classroom.Name : null,
|
||||
BuildingName = item.Classroom != null ? item.Classroom.Building!.Name : null,
|
||||
ClassroomCapacity = item.Classroom != null ? (int?)item.Classroom.Capacity : null,
|
||||
item.ExamDate,
|
||||
item.StartPeriod,
|
||||
item.PeriodCount,
|
||||
item.StartsAt,
|
||||
item.EndsAt,
|
||||
item.RequiredBuildingId,
|
||||
RequiredBuildingName = item.RequiredBuilding != null
|
||||
? item.RequiredBuilding.Name : null,
|
||||
item.RequiredInvigilatorCount,
|
||||
item.Notes,
|
||||
InvigilatorIds = item.Invigilators.Select(i => i.TeacherId),
|
||||
InvigilatorNames = item.Invigilators.Select(i => i.Teacher!.Name)
|
||||
})
|
||||
TotalSessionCount = x.Sessions.Count
|
||||
}).FirstOrDefaultAsync(cancellationToken);
|
||||
if (plan is null) return NotFound();
|
||||
|
||||
var roomEntities = await db.ExamRooms.AsNoTracking()
|
||||
.Include(x => x.Classroom)
|
||||
.ThenInclude(x => x!.Building)
|
||||
.Include(x => x.SessionLinks)
|
||||
.Include(x => x.Seats)
|
||||
.Include(x => x.Invigilators)
|
||||
.ThenInclude(x => x.Teacher)
|
||||
.Where(x => x.ExamPlanId == id)
|
||||
var sessionQuery = db.ExamSessions.AsNoTracking()
|
||||
.Where(x => x.ExamPlanId == id);
|
||||
if (keyword is not null)
|
||||
{
|
||||
sessionQuery = sessionQuery.Where(x =>
|
||||
x.TeachingTask!.TaskNumber.Contains(keyword) ||
|
||||
x.TeachingTask.Name.Contains(keyword) ||
|
||||
x.TeachingTask.Course!.Code.Contains(keyword) ||
|
||||
x.TeachingTask.Course.Name.Contains(keyword));
|
||||
}
|
||||
|
||||
sessionQuery = allocation switch
|
||||
{
|
||||
"room" => sessionQuery.Where(x =>
|
||||
!x.RoomLinks.Any() && !x.ClassroomId.HasValue),
|
||||
"invigilator" => sessionQuery.Where(x =>
|
||||
(x.RoomLinks.Any() && x.RoomLinks.Any(link =>
|
||||
link.ExamRoom!.Invigilators.Count <
|
||||
x.RequiredInvigilatorCount)) ||
|
||||
(!x.RoomLinks.Any() &&
|
||||
x.Invigilators.Count < x.RequiredInvigilatorCount)),
|
||||
"complete" => sessionQuery.Where(x =>
|
||||
(x.RoomLinks.Any() || x.ClassroomId.HasValue) &&
|
||||
((x.RoomLinks.Any() && x.RoomLinks.All(link =>
|
||||
link.ExamRoom!.Invigilators.Count >=
|
||||
x.RequiredInvigilatorCount)) ||
|
||||
(!x.RoomLinks.Any() &&
|
||||
x.Invigilators.Count >= x.RequiredInvigilatorCount))),
|
||||
_ => sessionQuery
|
||||
};
|
||||
|
||||
var filteredSessionCount = await sessionQuery.CountAsync(cancellationToken);
|
||||
var lastPage = Math.Max(1,
|
||||
(int)Math.Ceiling(filteredSessionCount / (double)pageSize));
|
||||
page = Math.Min(page, lastPage);
|
||||
var sessions = await sessionQuery
|
||||
.OrderBy(item => item.ExamDate)
|
||||
.ThenBy(item => item.StartPeriod)
|
||||
.ThenBy(item => item.TeachingTask!.TaskNumber)
|
||||
.ThenBy(item => item.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(item => new
|
||||
{
|
||||
item.Id,
|
||||
item.TeachingTaskId,
|
||||
item.TeachingTask!.TaskNumber,
|
||||
TaskName = item.TeachingTask.Name,
|
||||
CourseCode = item.TeachingTask.Course!.Code,
|
||||
CourseName = item.TeachingTask.Course.Name,
|
||||
item.ClassroomId,
|
||||
ClassroomName = item.Classroom != null ? item.Classroom.Name : null,
|
||||
BuildingName = item.Classroom != null
|
||||
? item.Classroom.Building!.Name
|
||||
: null,
|
||||
ClassroomCapacity = item.Classroom != null
|
||||
? (int?)item.Classroom.Capacity
|
||||
: null,
|
||||
item.ExamDate,
|
||||
item.StartPeriod,
|
||||
item.PeriodCount,
|
||||
item.StartsAt,
|
||||
item.EndsAt,
|
||||
item.RequiredBuildingId,
|
||||
RequiredBuildingName = item.RequiredBuilding != null
|
||||
? item.RequiredBuilding.Name
|
||||
: null,
|
||||
item.RequiredInvigilatorCount,
|
||||
item.Notes,
|
||||
InvigilatorIds = item.Invigilators.Select(i => i.TeacherId),
|
||||
InvigilatorNames = item.Invigilators.Select(i => i.Teacher!.Name)
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
var roomsBySession = roomEntities
|
||||
.SelectMany(room => room.SessionLinks.Select(link => new
|
||||
var pageSessionIds = sessions.Select(x => x.Id).ToArray();
|
||||
var scheduledTeachingTaskIds = await db.ExamSessions.AsNoTracking()
|
||||
.Where(x => x.ExamPlanId == id)
|
||||
.OrderBy(x => x.TeachingTaskId)
|
||||
.Select(x => x.TeachingTaskId)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var roomSessionLinks = await db.ExamRoomSessions.AsNoTracking()
|
||||
.WhereIn(pageSessionIds, x => x.ExamSessionId)
|
||||
.Select(x => new
|
||||
{
|
||||
x.ExamRoomId,
|
||||
x.ExamSessionId,
|
||||
SeatCount = x.ExamRoom!.Seats.Count(seat =>
|
||||
seat.ExamSessionId == x.ExamSessionId)
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
var pageRoomIds = roomSessionLinks.Select(x => x.ExamRoomId).Distinct().ToArray();
|
||||
var roomSummaries = await db.ExamRooms.AsNoTracking()
|
||||
.WhereIn(pageRoomIds, x => x.Id)
|
||||
.OrderBy(x => x.Classroom!.Building!.Name)
|
||||
.ThenBy(x => x.Classroom!.Name)
|
||||
.ThenBy(x => x.Id)
|
||||
.Select(x => new
|
||||
{
|
||||
ExamRoomId = x.Id,
|
||||
x.ClassroomId,
|
||||
ClassroomName = x.Classroom!.Name,
|
||||
BuildingName = x.Classroom.Building!.Name,
|
||||
ClassroomCapacity = x.Classroom.Capacity,
|
||||
TotalSeatCount = x.Seats.Count,
|
||||
SessionCount = x.SessionLinks.Count
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
var roomInvigilators = await db.ExamRoomInvigilators.AsNoTracking()
|
||||
.WhereIn(pageRoomIds, x => x.ExamRoomId)
|
||||
.OrderBy(x => x.Teacher!.TeacherNumber)
|
||||
.ThenBy(x => x.Teacher!.Name)
|
||||
.Select(x => new
|
||||
{
|
||||
x.ExamRoomId,
|
||||
x.TeacherId,
|
||||
TeacherName = x.Teacher!.Name
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var linksByRoom = roomSessionLinks.ToLookup(x => x.ExamRoomId);
|
||||
var invigilatorsByRoom = roomInvigilators.ToLookup(x => x.ExamRoomId);
|
||||
var roomsBySession = roomSummaries
|
||||
.SelectMany(room => linksByRoom[room.ExamRoomId].Select(link => new
|
||||
{
|
||||
link.ExamSessionId,
|
||||
Room = new
|
||||
{
|
||||
link.ExamRoomId,
|
||||
room.ExamRoomId,
|
||||
room.ClassroomId,
|
||||
ClassroomName = room.Classroom!.Name,
|
||||
BuildingName = room.Classroom.Building!.Name,
|
||||
ClassroomCapacity = room.Classroom.Capacity,
|
||||
SeatCount = room.Seats.Count(seat =>
|
||||
seat.ExamSessionId == link.ExamSessionId),
|
||||
TotalSeatCount = room.Seats.Count,
|
||||
IsMixed = room.SessionLinks.Count > 1,
|
||||
InvigilatorIds = room.Invigilators
|
||||
room.ClassroomName,
|
||||
room.BuildingName,
|
||||
room.ClassroomCapacity,
|
||||
link.SeatCount,
|
||||
room.TotalSeatCount,
|
||||
IsMixed = room.SessionCount > 1,
|
||||
InvigilatorIds = invigilatorsByRoom[room.ExamRoomId]
|
||||
.Select(invigilator => invigilator.TeacherId)
|
||||
.ToList(),
|
||||
InvigilatorNames = room.Invigilators
|
||||
.Select(invigilator => invigilator.Teacher!.Name)
|
||||
InvigilatorNames = invigilatorsByRoom[room.ExamRoomId]
|
||||
.Select(invigilator => invigilator.TeacherName)
|
||||
.ToList()
|
||||
}
|
||||
}))
|
||||
.ToLookup(x => x.ExamSessionId, x => x.Room);
|
||||
var rosterCounts = (await TeachingTaskRosterQuery.LoadForTasksAsync(
|
||||
db,
|
||||
plan.Sessions.Select(x => x.TeachingTaskId),
|
||||
sessions.Select(x => x.TeachingTaskId),
|
||||
cancellationToken))
|
||||
.GroupBy(x => x.TeachingTaskId)
|
||||
.ToDictionary(x => x.Key, x => x.Count());
|
||||
@@ -199,7 +298,12 @@ public sealed class ExamsController(
|
||||
plan.Status,
|
||||
plan.Notes,
|
||||
plan.PublishedAt,
|
||||
Sessions = plan.Sessions.Select(item => new
|
||||
plan.TotalSessionCount,
|
||||
FilteredSessionCount = filteredSessionCount,
|
||||
SessionPage = page,
|
||||
SessionPageSize = pageSize,
|
||||
ScheduledTeachingTaskIds = scheduledTeachingTaskIds,
|
||||
Sessions = sessions.Select(item => new
|
||||
{
|
||||
item.Id,
|
||||
item.TeachingTaskId,
|
||||
|
||||
Reference in New Issue
Block a user