教学任务页面优化
This commit is contained in:
@@ -240,26 +240,8 @@ public sealed class TeachingTasksController(
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
if (task is null) return NotFound();
|
||||
if (!CanManage(task.Course!)) return Forbid();
|
||||
if (task.Status != TeachingTaskStatus.Draft)
|
||||
return ConflictProblem("只有草稿教学任务可以发布。");
|
||||
if (task.Teachers.Count == 0 || task.Teachers.Count(x => x.IsPrimary) != 1)
|
||||
return ConflictProblem("发布前必须指定且只能指定一名主讲教师。");
|
||||
if (task.Classes.Count == 0)
|
||||
return ConflictProblem("发布前至少需要关联一个行政班。");
|
||||
var teacherIds = task.Teachers.Select(x => x.TeacherId).ToArray();
|
||||
if (await db.TeacherCourseApplications.CountAsync(
|
||||
x =>
|
||||
x.AcademicTermId == task.AcademicTermId &&
|
||||
x.CourseId == task.CourseId &&
|
||||
teacherIds.Contains(x.TeacherId) &&
|
||||
x.Status == TeacherCourseApplicationStatus.Approved,
|
||||
cancellationToken) != teacherIds.Length)
|
||||
return ConflictProblem("授课教师尚未完成该课程申报,或学院审核尚未通过。");
|
||||
var studentCount = task.Classes.Sum(x =>
|
||||
x.AdministrativeClass!.Students.Count(student =>
|
||||
student.Status == StudentStatus.Active));
|
||||
if (studentCount > task.Capacity)
|
||||
return ConflictProblem($"教学班容量不足:关联班级共有 {studentCount} 名在籍学生。");
|
||||
var validation = await ValidatePublishingTasksAsync([task], cancellationToken);
|
||||
if (validation is not null) return ConflictProblem(validation);
|
||||
|
||||
task.Status = TeachingTaskStatus.Published;
|
||||
task.PublishedAt = DateTime.UtcNow;
|
||||
@@ -280,6 +262,69 @@ public sealed class TeachingTasksController(
|
||||
return await SaveAsync(id, false, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("batch")]
|
||||
public async Task<ActionResult> Batch(
|
||||
TeachingTaskBatchRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var ids = request.Ids.Distinct().ToArray();
|
||||
if (ids.Length == 0) return ValidationProblem("请至少选择一个教学任务。");
|
||||
if (ids.Length > 100) return ValidationProblem("每次最多处理 100 个教学任务。");
|
||||
|
||||
var tasks = await ScopedTasks()
|
||||
.Include(x => x.Course)
|
||||
.Include(x => x.Teachers)
|
||||
.Include(x => x.Classes)
|
||||
.ThenInclude(x => x.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Students)
|
||||
.Where(x => ids.Contains(x.Id))
|
||||
.OrderBy(x => x.TaskNumber)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (tasks.Count != ids.Length)
|
||||
return ConflictProblem("部分教学任务不存在,或已超出当前账号的数据范围。");
|
||||
if (tasks.Any(x => !CanManage(x.Course!))) return Forbid();
|
||||
|
||||
switch (request.Operation)
|
||||
{
|
||||
case TeachingTaskBatchOperation.Publish:
|
||||
{
|
||||
var validation = await ValidatePublishingTasksAsync(tasks, cancellationToken);
|
||||
if (validation is not null) return ConflictProblem(validation);
|
||||
var publishedAt = DateTime.UtcNow;
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
task.Status = TeachingTaskStatus.Published;
|
||||
task.PublishedAt = publishedAt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TeachingTaskBatchOperation.Delete:
|
||||
if (tasks.Any(x => x.Status != TeachingTaskStatus.Draft))
|
||||
return ConflictProblem(
|
||||
$"教学任务“{tasks.First(x => x.Status != TeachingTaskStatus.Draft).Name}”不是草稿,整批未删除。");
|
||||
db.TeachingTasks.RemoveRange(tasks);
|
||||
break;
|
||||
case TeachingTaskBatchOperation.Archive:
|
||||
if (tasks.Any(x => x.Status != TeachingTaskStatus.Published))
|
||||
return ConflictProblem(
|
||||
$"教学任务“{tasks.First(x => x.Status != TeachingTaskStatus.Published).Name}”不是已发布状态,整批未归档。");
|
||||
foreach (var task in tasks) task.Status = TeachingTaskStatus.Closed;
|
||||
break;
|
||||
default:
|
||||
return ValidationProblem("不支持的批量操作。");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return Ok(new { AffectedCount = tasks.Count });
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
return ConflictProblem("批量操作未执行:教学任务仍被其他业务引用,或关联数据已失效。");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("generate-public-course")]
|
||||
[Authorize(Roles = SchoolManagementRoles)]
|
||||
public async Task<ActionResult> GeneratePublicCourseTasks(
|
||||
@@ -511,6 +556,82 @@ public sealed class TeachingTasksController(
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string?> ValidatePublishingTasksAsync(
|
||||
IReadOnlyCollection<TeachingTask> tasks,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var invalidStatus = tasks.FirstOrDefault(x => x.Status != TeachingTaskStatus.Draft);
|
||||
if (invalidStatus is not null)
|
||||
return PublishingProblem(
|
||||
tasks,
|
||||
invalidStatus,
|
||||
"只有草稿教学任务可以发布。");
|
||||
|
||||
var invalidPrimaryTeacher = tasks.FirstOrDefault(x =>
|
||||
x.Teachers.Count == 0 || x.Teachers.Count(item => item.IsPrimary) != 1);
|
||||
if (invalidPrimaryTeacher is not null)
|
||||
return PublishingProblem(
|
||||
tasks,
|
||||
invalidPrimaryTeacher,
|
||||
"发布前必须指定且只能指定一名主讲教师。");
|
||||
|
||||
var missingClass = tasks.FirstOrDefault(x => x.Classes.Count == 0);
|
||||
if (missingClass is not null)
|
||||
return PublishingProblem(
|
||||
tasks,
|
||||
missingClass,
|
||||
"发布前至少需要关联一个行政班。");
|
||||
|
||||
var termIds = tasks.Select(x => x.AcademicTermId).Distinct().ToArray();
|
||||
var courseIds = tasks.Select(x => x.CourseId).Distinct().ToArray();
|
||||
var teacherIds = tasks
|
||||
.SelectMany(x => x.Teachers)
|
||||
.Select(x => x.TeacherId)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var approvedApplications = await db.TeacherCourseApplications
|
||||
.AsNoTracking()
|
||||
.Where(x =>
|
||||
termIds.Contains(x.AcademicTermId) &&
|
||||
courseIds.Contains(x.CourseId) &&
|
||||
teacherIds.Contains(x.TeacherId) &&
|
||||
x.Status == TeacherCourseApplicationStatus.Approved)
|
||||
.Select(x => new { x.AcademicTermId, x.CourseId, x.TeacherId })
|
||||
.ToListAsync(cancellationToken);
|
||||
var approvedAssignments = approvedApplications
|
||||
.Select(x => (x.AcademicTermId, x.CourseId, x.TeacherId))
|
||||
.ToHashSet();
|
||||
var unapprovedTeacher = tasks.FirstOrDefault(task =>
|
||||
task.Teachers.Any(teacher =>
|
||||
!approvedAssignments.Contains(
|
||||
(task.AcademicTermId, task.CourseId, teacher.TeacherId))));
|
||||
if (unapprovedTeacher is not null)
|
||||
return PublishingProblem(
|
||||
tasks,
|
||||
unapprovedTeacher,
|
||||
"授课教师尚未完成该课程申报,或学院审核尚未通过。");
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
var studentCount = task.Classes.Sum(x =>
|
||||
x.AdministrativeClass!.Students.Count(student =>
|
||||
student.Status == StudentStatus.Active));
|
||||
if (studentCount > task.Capacity)
|
||||
return PublishingProblem(
|
||||
tasks,
|
||||
task,
|
||||
$"教学班容量不足:关联班级共有 {studentCount} 名在籍学生。");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string PublishingProblem(
|
||||
IReadOnlyCollection<TeachingTask> tasks,
|
||||
TeachingTask task,
|
||||
string detail) =>
|
||||
tasks.Count == 1 ? detail : $"教学任务“{task.Name}”:{detail}整批未发布。";
|
||||
|
||||
private Guid? ScopedCollegeId()
|
||||
=> currentUserDataScope.Current.RestrictedCollegeId;
|
||||
|
||||
@@ -588,3 +709,14 @@ public sealed record PublicCourseTaskGenerationRequest(
|
||||
[Range(1, 30)] int EndWeek,
|
||||
[Range(1, 40)] int WeeklyHours,
|
||||
IReadOnlyCollection<Guid> ClassIds);
|
||||
|
||||
public sealed record TeachingTaskBatchRequest(
|
||||
TeachingTaskBatchOperation Operation,
|
||||
[MinLength(1), MaxLength(100)] IReadOnlyCollection<Guid> Ids);
|
||||
|
||||
public enum TeachingTaskBatchOperation
|
||||
{
|
||||
Publish = 1,
|
||||
Delete = 2,
|
||||
Archive = 3
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user