课程/教学班归档
This commit is contained in:
1 parent
df87aa16d3
commit
e165964a77
6 files changed
+111
-27
No files matched your search
@@ -44,7 +44,8 @@ public sealed class AttendanceController(
|
|||||||
page = Math.Max(1, page);
|
page = Math.Max(1, page);
|
||||||
pageSize = Math.Clamp(pageSize, 10, 50);
|
pageSize = Math.Clamp(pageSize, 10, 50);
|
||||||
var tasks = AccessibleTasks().AsNoTracking()
|
var tasks = AccessibleTasks().AsNoTracking()
|
||||||
.Where(x => x.Status == TeachingTaskStatus.Published);
|
.Where(x => x.Status == TeachingTaskStatus.Published ||
|
||||||
|
x.Status == TeachingTaskStatus.Closed);
|
||||||
if (academicTermId.HasValue)
|
if (academicTermId.HasValue)
|
||||||
tasks = tasks.Where(x => x.AcademicTermId == academicTermId);
|
tasks = tasks.Where(x => x.AcademicTermId == academicTermId);
|
||||||
if (!string.IsNullOrWhiteSpace(keyword))
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
@@ -73,6 +74,7 @@ public sealed class AttendanceController(
|
|||||||
.Select(x => new
|
.Select(x => new
|
||||||
{
|
{
|
||||||
x.Id,
|
x.Id,
|
||||||
|
x.Status,
|
||||||
x.TaskNumber,
|
x.TaskNumber,
|
||||||
x.Name,
|
x.Name,
|
||||||
x.AcademicTermId,
|
x.AcademicTermId,
|
||||||
@@ -162,6 +164,8 @@ public sealed class AttendanceController(
|
|||||||
var task = await AccessibleTasks().AsNoTracking()
|
var task = await AccessibleTasks().AsNoTracking()
|
||||||
.FirstOrDefaultAsync(x => x.Id == request.TeachingTaskId, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == request.TeachingTaskId, cancellationToken);
|
||||||
if (task is null) return NotFound();
|
if (task is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(task))
|
||||||
|
return ConflictProblem("教学任务已归档,不能新建考勤表。");
|
||||||
|
|
||||||
var studentIds = await TeachingTaskRosterQuery
|
var studentIds = await TeachingTaskRosterQuery
|
||||||
.ForTask(db, request.TeachingTaskId)
|
.ForTask(db, request.TeachingTaskId)
|
||||||
@@ -258,6 +262,7 @@ public sealed class AttendanceController(
|
|||||||
x.LocationRadiusMeters,
|
x.LocationRadiusMeters,
|
||||||
x.Notes,
|
x.Notes,
|
||||||
x.SubmittedAt,
|
x.SubmittedAt,
|
||||||
|
TaskStatus = x.TeachingTask!.Status,
|
||||||
TaskNumber = x.TeachingTask!.TaskNumber,
|
TaskNumber = x.TeachingTask!.TaskNumber,
|
||||||
TaskName = x.TeachingTask.Name,
|
TaskName = x.TeachingTask.Name,
|
||||||
CourseCode = x.TeachingTask.Course!.Code,
|
CourseCode = x.TeachingTask.Course!.Code,
|
||||||
@@ -295,7 +300,8 @@ public sealed class AttendanceController(
|
|||||||
var canManage = await CanManageTaskAsync(
|
var canManage = await CanManageTaskAsync(
|
||||||
sheet.TeachingTaskId,
|
sheet.TeachingTaskId,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
var canEdit = sheet.Status == AttendanceSheetStatus.Draft && canManage;
|
var canEdit = sheet.Status == AttendanceSheetStatus.Draft &&
|
||||||
|
sheet.TaskStatus == TeachingTaskStatus.Published && canManage;
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var attempts = await db.AttendanceCheckInAttempts.AsNoTracking()
|
var attempts = await db.AttendanceCheckInAttempts.AsNoTracking()
|
||||||
.Where(x => x.AttendanceSheetId == id)
|
.Where(x => x.AttendanceSheetId == id)
|
||||||
@@ -758,6 +764,7 @@ public sealed class AttendanceController(
|
|||||||
.Where(x =>
|
.Where(x =>
|
||||||
x.StudentId == studentId.Value &&
|
x.StudentId == studentId.Value &&
|
||||||
x.AttendanceSheet!.Status == AttendanceSheetStatus.Draft &&
|
x.AttendanceSheet!.Status == AttendanceSheetStatus.Draft &&
|
||||||
|
x.AttendanceSheet.TeachingTask!.Status == TeachingTaskStatus.Published &&
|
||||||
x.AttendanceSheet.CheckInMethod == AttendanceCheckInMethod.Location &&
|
x.AttendanceSheet.CheckInMethod == AttendanceCheckInMethod.Location &&
|
||||||
x.AttendanceSheet.CheckInStartsAt <= now &&
|
x.AttendanceSheet.CheckInStartsAt <= now &&
|
||||||
x.AttendanceSheet.CheckInEndsAt >= now)
|
x.AttendanceSheet.CheckInEndsAt >= now)
|
||||||
@@ -791,6 +798,7 @@ public sealed class AttendanceController(
|
|||||||
|
|
||||||
var source = db.AttendanceRecords
|
var source = db.AttendanceRecords
|
||||||
.Include(x => x.AttendanceSheet)
|
.Include(x => x.AttendanceSheet)
|
||||||
|
.ThenInclude(x => x!.TeachingTask)
|
||||||
.Where(x => x.StudentId == studentId.Value);
|
.Where(x => x.StudentId == studentId.Value);
|
||||||
if (!string.IsNullOrWhiteSpace(request.Token))
|
if (!string.IsNullOrWhiteSpace(request.Token))
|
||||||
{
|
{
|
||||||
@@ -813,6 +821,8 @@ public sealed class AttendanceController(
|
|||||||
var record = await source.FirstOrDefaultAsync(cancellationToken);
|
var record = await source.FirstOrDefaultAsync(cancellationToken);
|
||||||
if (record?.AttendanceSheet is null) return NotFound();
|
if (record?.AttendanceSheet is null) return NotFound();
|
||||||
var sheet = record.AttendanceSheet;
|
var sheet = record.AttendanceSheet;
|
||||||
|
if (!IsTaskOperable(sheet.TeachingTask!))
|
||||||
|
return ConflictProblem("教学任务已归档,签到活动已停止。");
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
async Task<ActionResult> RejectAttemptAsync(
|
async Task<ActionResult> RejectAttemptAsync(
|
||||||
@@ -1011,11 +1021,15 @@ public sealed class AttendanceController(
|
|||||||
return ConflictProblem("当前账号未关联学生档案。");
|
return ConflictProblem("当前账号未关联学生档案。");
|
||||||
|
|
||||||
var record = await db.AttendanceRecords
|
var record = await db.AttendanceRecords
|
||||||
|
.Include(r => r.AttendanceSheet)
|
||||||
|
.ThenInclude(s => s!.TeachingTask)
|
||||||
.FirstOrDefaultAsync(r =>
|
.FirstOrDefaultAsync(r =>
|
||||||
r.AttendanceSheetId == request.AttendanceSheetId &&
|
r.AttendanceSheetId == request.AttendanceSheetId &&
|
||||||
r.StudentId == studentId.Value,
|
r.StudentId == studentId.Value,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
if (record is null) return NotFound();
|
if (record is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(record.AttendanceSheet!.TeachingTask!))
|
||||||
|
return ConflictProblem("教学任务已归档,不能提交考勤申诉。");
|
||||||
if (record.AppealStatus == AttendanceAppealStatus.Pending)
|
if (record.AppealStatus == AttendanceAppealStatus.Pending)
|
||||||
return ConflictProblem("已有申诉正在处理中。");
|
return ConflictProblem("已有申诉正在处理中。");
|
||||||
if (record.AppealStatus == AttendanceAppealStatus.Approved)
|
if (record.AppealStatus == AttendanceAppealStatus.Approved)
|
||||||
@@ -1202,10 +1216,14 @@ public sealed class AttendanceController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool CanManageSheet(AttendanceSheet sheet) =>
|
private bool CanManageSheet(AttendanceSheet sheet) =>
|
||||||
currentUserDataScope.Current.IsInRole(SystemRoles.SuperAdmin) ||
|
IsTaskOperable(sheet.TeachingTask!) &&
|
||||||
|
(currentUserDataScope.Current.IsInRole(SystemRoles.SuperAdmin) ||
|
||||||
currentUserDataScope.Current.IsInRole(SystemRoles.AcademicAdmin) ||
|
currentUserDataScope.Current.IsInRole(SystemRoles.AcademicAdmin) ||
|
||||||
sheet.TeachingTask!.Teachers.Any(x =>
|
sheet.TeachingTask!.Teachers.Any(x =>
|
||||||
x.Teacher?.UserId == currentUserDataScope.Current.UserId);
|
x.Teacher?.UserId == currentUserDataScope.Current.UserId));
|
||||||
|
|
||||||
|
private static bool IsTaskOperable(TeachingTask task) =>
|
||||||
|
task.Status == TeachingTaskStatus.Published;
|
||||||
|
|
||||||
private async Task<bool> CanManageTaskAsync(
|
private async Task<bool> CanManageTaskAsync(
|
||||||
Guid teachingTaskId,
|
Guid teachingTaskId,
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public sealed class ExperimentGradesController(
|
|||||||
.GroupBy(x => new
|
.GroupBy(x => new
|
||||||
{
|
{
|
||||||
x.TeachingTaskId,
|
x.TeachingTaskId,
|
||||||
|
TaskStatus = x.TeachingTask!.Status,
|
||||||
StartDate = x.TeachingTask!.AcademicTerm!.StartDate,
|
StartDate = x.TeachingTask!.AcademicTerm!.StartDate,
|
||||||
x.TeachingTask.TaskNumber
|
x.TeachingTask.TaskNumber
|
||||||
})
|
})
|
||||||
@@ -114,6 +115,7 @@ public sealed class ExperimentGradesController(
|
|||||||
x.ScheduleWeek,
|
x.ScheduleWeek,
|
||||||
ProjectStatus = x.Status,
|
ProjectStatus = x.Status,
|
||||||
x.TeachingTaskId,
|
x.TeachingTaskId,
|
||||||
|
TaskStatus = x.TeachingTask!.Status,
|
||||||
x.TeachingTask!.TaskNumber,
|
x.TeachingTask!.TaskNumber,
|
||||||
TaskName = x.TeachingTask.Name,
|
TaskName = x.TeachingTask.Name,
|
||||||
AcademicTermId = x.TeachingTask.AcademicTermId,
|
AcademicTermId = x.TeachingTask.AcademicTermId,
|
||||||
@@ -567,7 +569,8 @@ public sealed class ExperimentGradesController(
|
|||||||
.SingleAsync(
|
.SingleAsync(
|
||||||
x => x.Id == sheet.TeachingTaskId,
|
x => x.Id == sheet.TeachingTaskId,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
var canReview = CanReviewCollege(sheet.CourseCollegeId) &&
|
var isTaskOperable = IsTaskOperable(task);
|
||||||
|
var canReview = isTaskOperable && CanReviewCollege(sheet.CourseCollegeId) &&
|
||||||
sheet.Status ==
|
sheet.Status ==
|
||||||
ExperimentGradeSheetStatus.Submitted;
|
ExperimentGradeSheetStatus.Submitted;
|
||||||
return Ok(new
|
return Ok(new
|
||||||
@@ -603,12 +606,12 @@ public sealed class ExperimentGradesController(
|
|||||||
sheet.CreatedAt,
|
sheet.CreatedAt,
|
||||||
sheet.UpdatedAt
|
sheet.UpdatedAt
|
||||||
},
|
},
|
||||||
CanEdit = (CanEdit(task) || isBoundInstructor) &&
|
CanEdit = isTaskOperable && (CanEdit(task) || isBoundInstructor) &&
|
||||||
sheet.Status is
|
sheet.Status is
|
||||||
ExperimentGradeSheetStatus.Draft or
|
ExperimentGradeSheetStatus.Draft or
|
||||||
ExperimentGradeSheetStatus.Returned,
|
ExperimentGradeSheetStatus.Returned,
|
||||||
CanReview = canReview,
|
CanReview = canReview,
|
||||||
CanPublish = IsPublisher() &&
|
CanPublish = isTaskOperable && IsPublisher() &&
|
||||||
sheet.Status == ExperimentGradeSheetStatus.Approved
|
sheet.Status == ExperimentGradeSheetStatus.Approved
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1074,6 +1077,8 @@ public sealed class ExperimentGradesController(
|
|||||||
{
|
{
|
||||||
var sheet = await LoadForWorkflowAsync(id, cancellationToken);
|
var sheet = await LoadForWorkflowAsync(id, cancellationToken);
|
||||||
if (sheet is null) return NotFound();
|
if (sheet is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(sheet.ExperimentProject!.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
var collegeId = sheet.ExperimentProject!.TeachingTask!.Course!.CollegeId;
|
var collegeId = sheet.ExperimentProject!.TeachingTask!.Course!.CollegeId;
|
||||||
if (!CanReviewCollege(collegeId)) return Forbid();
|
if (!CanReviewCollege(collegeId)) return Forbid();
|
||||||
if (sheet.Status != ExperimentGradeSheetStatus.Submitted)
|
if (sheet.Status != ExperimentGradeSheetStatus.Submitted)
|
||||||
@@ -1100,6 +1105,8 @@ public sealed class ExperimentGradesController(
|
|||||||
{
|
{
|
||||||
var sheet = await LoadForWorkflowAsync(id, cancellationToken);
|
var sheet = await LoadForWorkflowAsync(id, cancellationToken);
|
||||||
if (sheet is null) return NotFound();
|
if (sheet is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(sheet.ExperimentProject!.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
var collegeId = sheet.ExperimentProject!.TeachingTask!.Course!.CollegeId;
|
var collegeId = sheet.ExperimentProject!.TeachingTask!.Course!.CollegeId;
|
||||||
if (!CanReviewCollege(collegeId)) return Forbid();
|
if (!CanReviewCollege(collegeId)) return Forbid();
|
||||||
if (sheet.Status != ExperimentGradeSheetStatus.Submitted)
|
if (sheet.Status != ExperimentGradeSheetStatus.Submitted)
|
||||||
@@ -1127,6 +1134,8 @@ public sealed class ExperimentGradesController(
|
|||||||
{
|
{
|
||||||
var sheet = await LoadForWorkflowAsync(id, cancellationToken);
|
var sheet = await LoadForWorkflowAsync(id, cancellationToken);
|
||||||
if (sheet is null) return NotFound();
|
if (sheet is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(sheet.ExperimentProject!.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (!IsPublisher()) return Forbid();
|
if (!IsPublisher()) return Forbid();
|
||||||
if (sheet.Status != ExperimentGradeSheetStatus.Approved)
|
if (sheet.Status != ExperimentGradeSheetStatus.Approved)
|
||||||
return ConflictProblem("只有审核通过的实验成绩单可以发布。");
|
return ConflictProblem("只有审核通过的实验成绩单可以发布。");
|
||||||
@@ -1291,21 +1300,23 @@ public sealed class ExperimentGradesController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool CanInitialize(TeachingTask task) =>
|
private bool CanInitialize(TeachingTask task) =>
|
||||||
currentUserDataScope.Current.IsInRole(SystemRoles.SuperAdmin) ||
|
IsTaskOperable(task) &&
|
||||||
|
(currentUserDataScope.Current.IsInRole(SystemRoles.SuperAdmin) ||
|
||||||
currentUserDataScope.Current.IsInRole(SystemRoles.AcademicAdmin) ||
|
currentUserDataScope.Current.IsInRole(SystemRoles.AcademicAdmin) ||
|
||||||
currentUserDataScope.Current.IsInRole(SystemRoles.CollegeAdmin) ||
|
currentUserDataScope.Current.IsInRole(SystemRoles.CollegeAdmin) ||
|
||||||
IsAssignedTeacher(task);
|
IsAssignedTeacher(task));
|
||||||
|
|
||||||
private bool CanEdit(TeachingTask task) =>
|
private bool CanEdit(TeachingTask task) =>
|
||||||
currentUserDataScope.Current.IsInRole(SystemRoles.SuperAdmin) ||
|
IsTaskOperable(task) && (currentUserDataScope.Current.IsInRole(SystemRoles.SuperAdmin) ||
|
||||||
IsAssignedTeacher(task);
|
IsAssignedTeacher(task));
|
||||||
|
|
||||||
private bool CanEditBoundSessionRecords(
|
private bool CanEditBoundSessionRecords(
|
||||||
ExperimentGradeSheet sheet,
|
ExperimentGradeSheet sheet,
|
||||||
IReadOnlyCollection<ExperimentGradeRecordRequest> requestedRecords)
|
IReadOnlyCollection<ExperimentGradeRecordRequest> requestedRecords)
|
||||||
{
|
{
|
||||||
var scope = currentUserDataScope.Current;
|
var scope = currentUserDataScope.Current;
|
||||||
if (sheet.ExperimentProject!.ArrangementMode != ExperimentArrangementMode.SelfScheduled ||
|
if (!IsTaskOperable(sheet.ExperimentProject!.TeachingTask!) ||
|
||||||
|
sheet.ExperimentProject!.ArrangementMode != ExperimentArrangementMode.SelfScheduled ||
|
||||||
scope.Scope != DataScope.Self || !scope.IsInRole(SystemRoles.Teacher) ||
|
scope.Scope != DataScope.Self || !scope.IsInRole(SystemRoles.Teacher) ||
|
||||||
requestedRecords.Count == 0)
|
requestedRecords.Count == 0)
|
||||||
return false;
|
return false;
|
||||||
@@ -1320,6 +1331,12 @@ public sealed class ExperimentGradesController(
|
|||||||
task.Teachers.Any(x =>
|
task.Teachers.Any(x =>
|
||||||
x.Teacher?.UserId == currentUserDataScope.Current.UserId);
|
x.Teacher?.UserId == currentUserDataScope.Current.UserId);
|
||||||
|
|
||||||
|
private static bool IsTaskOperable(TeachingTask task) =>
|
||||||
|
task.Status == TeachingTaskStatus.Published;
|
||||||
|
|
||||||
|
private ActionResult TaskArchivedProblem() =>
|
||||||
|
ConflictProblem("教学任务已归档,实验成绩仅可查看或导出,不能再操作。");
|
||||||
|
|
||||||
private bool CanReviewCollege(Guid collegeId)
|
private bool CanReviewCollege(Guid collegeId)
|
||||||
{
|
{
|
||||||
var scope = currentUserDataScope.Current;
|
var scope = currentUserDataScope.Current;
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ public sealed class ExperimentsController(
|
|||||||
x.Status,
|
x.Status,
|
||||||
x.PublishedAt,
|
x.PublishedAt,
|
||||||
x.ClosedAt,
|
x.ClosedAt,
|
||||||
|
TeachingTaskStatus = x.TeachingTask!.Status,
|
||||||
AcademicTermId = x.TeachingTask!.AcademicTermId,
|
AcademicTermId = x.TeachingTask!.AcademicTermId,
|
||||||
TermName = x.TeachingTask.AcademicTerm!.Name,
|
TermName = x.TeachingTask.AcademicTerm!.Name,
|
||||||
TaskNumber = x.TeachingTask.TaskNumber,
|
TaskNumber = x.TeachingTask.TaskNumber,
|
||||||
@@ -368,6 +369,7 @@ public sealed class ExperimentsController(
|
|||||||
x.SelectionStartsAt,
|
x.SelectionStartsAt,
|
||||||
x.SelectionEndsAt,
|
x.SelectionEndsAt,
|
||||||
x.Status,
|
x.Status,
|
||||||
|
TeachingTaskStatus = x.TeachingTask!.Status,
|
||||||
AcademicTermId = x.TeachingTask!.AcademicTermId,
|
AcademicTermId = x.TeachingTask!.AcademicTermId,
|
||||||
TermName = x.TeachingTask.AcademicTerm!.Name,
|
TermName = x.TeachingTask.AcademicTerm!.Name,
|
||||||
TaskNumber = x.TeachingTask.TaskNumber,
|
TaskNumber = x.TeachingTask.TaskNumber,
|
||||||
@@ -687,6 +689,8 @@ public sealed class ExperimentsController(
|
|||||||
.ThenInclude(x => x!.AcademicTerm)
|
.ThenInclude(x => x!.AcademicTerm)
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (project is null) return NotFound();
|
if (project is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(project.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (project.Status != ExperimentProjectStatus.Draft)
|
if (project.Status != ExperimentProjectStatus.Draft)
|
||||||
return ConflictProblem("只有草稿实验项目可以修改。");
|
return ConflictProblem("只有草稿实验项目可以修改。");
|
||||||
if (project.TeachingTaskId != request.TeachingTaskId)
|
if (project.TeachingTaskId != request.TeachingTaskId)
|
||||||
@@ -729,8 +733,11 @@ public sealed class ExperimentsController(
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var project = await ScopedProjects()
|
var project = await ScopedProjects()
|
||||||
|
.Include(x => x.TeachingTask)
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (project is null) return NotFound();
|
if (project is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(project.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (project.Status != ExperimentProjectStatus.Draft)
|
if (project.Status != ExperimentProjectStatus.Draft)
|
||||||
return ConflictProblem("只有草稿实验项目可以删除。");
|
return ConflictProblem("只有草稿实验项目可以删除。");
|
||||||
|
|
||||||
@@ -754,6 +761,8 @@ public sealed class ExperimentsController(
|
|||||||
.ThenInclude(x => x!.Course)
|
.ThenInclude(x => x!.Course)
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (project is null) return NotFound();
|
if (project is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(project.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (project.Status != ExperimentProjectStatus.Published)
|
if (project.Status != ExperimentProjectStatus.Published)
|
||||||
return ConflictProblem("只有已发布实验项目可以修正教学内容。");
|
return ConflictProblem("只有已发布实验项目可以修正教学内容。");
|
||||||
if (!CanCorrectPublishedProject(project.TeachingTask!)) return Forbid();
|
if (!CanCorrectPublishedProject(project.TeachingTask!)) return Forbid();
|
||||||
@@ -786,11 +795,14 @@ public sealed class ExperimentsController(
|
|||||||
return ValidationProblem("请为每个已选实验项目填写一个名称,每行一个且数量必须一致。");
|
return ValidationProblem("请为每个已选实验项目填写一个名称,每行一个且数量必须一致。");
|
||||||
|
|
||||||
var projects = await ScopedProjects()
|
var projects = await ScopedProjects()
|
||||||
|
.Include(x => x.TeachingTask)
|
||||||
.Where(x => ids.Contains(x.Id))
|
.Where(x => ids.Contains(x.Id))
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
if (projects.Count != ids.Count) return NotFound();
|
if (projects.Count != ids.Count) return NotFound();
|
||||||
if (projects.Any(x => x.Status != ExperimentProjectStatus.Draft))
|
if (projects.Any(x => x.Status != ExperimentProjectStatus.Draft))
|
||||||
return ConflictProblem("批量修改名称只能包含草稿实验项目。");
|
return ConflictProblem("批量修改名称只能包含草稿实验项目。");
|
||||||
|
if (projects.Any(x => !IsTaskOperable(x.TeachingTask!)))
|
||||||
|
return TaskArchivedProblem();
|
||||||
|
|
||||||
var projectsById = projects.ToDictionary(x => x.Id);
|
var projectsById = projects.ToDictionary(x => x.Id);
|
||||||
for (var index = 0; index < ids.Count; index++)
|
for (var index = 0; index < ids.Count; index++)
|
||||||
@@ -809,11 +821,14 @@ public sealed class ExperimentsController(
|
|||||||
var ids = ValidateBulkProjectIds(request.ProjectIds);
|
var ids = ValidateBulkProjectIds(request.ProjectIds);
|
||||||
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
|
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
|
||||||
var projects = await ScopedProjects()
|
var projects = await ScopedProjects()
|
||||||
|
.Include(x => x.TeachingTask)
|
||||||
.Where(x => ids.Contains(x.Id))
|
.Where(x => ids.Contains(x.Id))
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
if (projects.Count != ids.Count) return NotFound();
|
if (projects.Count != ids.Count) return NotFound();
|
||||||
if (projects.Any(x => x.Status != ExperimentProjectStatus.Draft))
|
if (projects.Any(x => x.Status != ExperimentProjectStatus.Draft))
|
||||||
return ConflictProblem("批量删除只能包含草稿实验项目。");
|
return ConflictProblem("批量删除只能包含草稿实验项目。");
|
||||||
|
if (projects.Any(x => !IsTaskOperable(x.TeachingTask!)))
|
||||||
|
return TaskArchivedProblem();
|
||||||
|
|
||||||
db.ExperimentProjects.RemoveRange(projects);
|
db.ExperimentProjects.RemoveRange(projects);
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
@@ -833,6 +848,8 @@ public sealed class ExperimentsController(
|
|||||||
.ThenInclude(x => x!.Course)
|
.ThenInclude(x => x!.Course)
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (project is null) return NotFound();
|
if (project is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(project.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (project.Status != ExperimentProjectStatus.Draft)
|
if (project.Status != ExperimentProjectStatus.Draft)
|
||||||
return ConflictProblem("只有草稿实验项目可以发布。");
|
return ConflictProblem("只有草稿实验项目可以发布。");
|
||||||
var hasSchedule = project.ArrangementMode == ExperimentArrangementMode.Centralized
|
var hasSchedule = project.ArrangementMode == ExperimentArrangementMode.Centralized
|
||||||
@@ -872,8 +889,14 @@ public sealed class ExperimentsController(
|
|||||||
{
|
{
|
||||||
var ids = ValidateBulkProjectIds(request.ProjectIds);
|
var ids = ValidateBulkProjectIds(request.ProjectIds);
|
||||||
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
|
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
|
||||||
if (await ScopedProjects().CountAsync(x => ids.Contains(x.Id), cancellationToken) != ids.Count)
|
var projects = await ScopedProjects().AsNoTracking()
|
||||||
|
.Where(x => ids.Contains(x.Id))
|
||||||
|
.Select(x => new { x.Id, x.TeachingTask!.Status })
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
if (projects.Count != ids.Count)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
if (projects.Any(x => x.Status != TeachingTaskStatus.Published))
|
||||||
|
return TaskArchivedProblem();
|
||||||
var userId = currentUserDataScope.Current.UserId;
|
var userId = currentUserDataScope.Current.UserId;
|
||||||
var job = new ExamPublishJob
|
var job = new ExamPublishJob
|
||||||
{
|
{
|
||||||
@@ -908,8 +931,11 @@ public sealed class ExperimentsController(
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var project = await ScopedProjects()
|
var project = await ScopedProjects()
|
||||||
|
.Include(x => x.TeachingTask)
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (project is null) return NotFound();
|
if (project is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(project.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (project.Status != ExperimentProjectStatus.Published)
|
if (project.Status != ExperimentProjectStatus.Published)
|
||||||
return ConflictProblem("只有已发布实验项目可以关闭。");
|
return ConflictProblem("只有已发布实验项目可以关闭。");
|
||||||
project.Status = ExperimentProjectStatus.Closed;
|
project.Status = ExperimentProjectStatus.Closed;
|
||||||
@@ -930,6 +956,8 @@ public sealed class ExperimentsController(
|
|||||||
.ThenInclude(x => x!.AcademicTerm)
|
.ThenInclude(x => x!.AcademicTerm)
|
||||||
.FirstOrDefaultAsync(x => x.Id == projectId, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == projectId, cancellationToken);
|
||||||
if (project is null) return NotFound();
|
if (project is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(project.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (project.Status == ExperimentProjectStatus.Closed)
|
if (project.Status == ExperimentProjectStatus.Closed)
|
||||||
return ConflictProblem("已关闭实验项目不能再增加场次。");
|
return ConflictProblem("已关闭实验项目不能再增加场次。");
|
||||||
if (project.ArrangementMode == ExperimentArrangementMode.Centralized)
|
if (project.ArrangementMode == ExperimentArrangementMode.Centralized)
|
||||||
@@ -1009,6 +1037,8 @@ public sealed class ExperimentsController(
|
|||||||
if (projects.Count != projectIds.Count)
|
if (projects.Count != projectIds.Count)
|
||||||
return ValidationProblem(
|
return ValidationProblem(
|
||||||
"部分实验项目不存在或不在当前管理范围内。");
|
"部分实验项目不存在或不在当前管理范围内。");
|
||||||
|
if (projects.Values.Any(x => !IsTaskOperable(x.TeachingTask!)))
|
||||||
|
return TaskArchivedProblem();
|
||||||
|
|
||||||
var createdSessions = new List<(ExperimentProject Project, ExperimentSession Session)>(
|
var createdSessions = new List<(ExperimentProject Project, ExperimentSession Session)>(
|
||||||
request.Items.Count);
|
request.Items.Count);
|
||||||
@@ -1100,6 +1130,8 @@ public sealed class ExperimentsController(
|
|||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (session is null) return NotFound();
|
if (session is null) return NotFound();
|
||||||
if (!CanManageSession(session)) return Forbid();
|
if (!CanManageSession(session)) return Forbid();
|
||||||
|
if (!IsTaskOperable(session.ExperimentProject!.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (session.Status == ExperimentSessionStatus.Cancelled)
|
if (session.Status == ExperimentSessionStatus.Cancelled)
|
||||||
return NoContent();
|
return NoContent();
|
||||||
|
|
||||||
@@ -1253,6 +1285,8 @@ public sealed class ExperimentsController(
|
|||||||
var session = await LoadManagedSessionAsync(id, cancellationToken);
|
var session = await LoadManagedSessionAsync(id, cancellationToken);
|
||||||
if (session is null) return NotFound();
|
if (session is null) return NotFound();
|
||||||
if (!CanManageSession(session)) return Forbid();
|
if (!CanManageSession(session)) return Forbid();
|
||||||
|
if (!IsTaskOperable(session.ExperimentProject!.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (session.Status != ExperimentSessionStatus.Scheduled ||
|
if (session.Status != ExperimentSessionStatus.Scheduled ||
|
||||||
session.ExperimentProject!.Status != ExperimentProjectStatus.Published ||
|
session.ExperimentProject!.Status != ExperimentProjectStatus.Published ||
|
||||||
session.ExperimentProject.ArrangementMode != ExperimentArrangementMode.SelfScheduled)
|
session.ExperimentProject.ArrangementMode != ExperimentArrangementMode.SelfScheduled)
|
||||||
@@ -1327,6 +1361,8 @@ public sealed class ExperimentsController(
|
|||||||
var session = await LoadManagedSessionAsync(id, cancellationToken);
|
var session = await LoadManagedSessionAsync(id, cancellationToken);
|
||||||
if (session is null) return NotFound();
|
if (session is null) return NotFound();
|
||||||
if (!CanManageSession(session)) return Forbid();
|
if (!CanManageSession(session)) return Forbid();
|
||||||
|
if (!IsTaskOperable(session.ExperimentProject!.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (session.Status != ExperimentSessionStatus.Scheduled ||
|
if (session.Status != ExperimentSessionStatus.Scheduled ||
|
||||||
session.ExperimentProject!.Status != ExperimentProjectStatus.Published ||
|
session.ExperimentProject!.Status != ExperimentProjectStatus.Published ||
|
||||||
session.ExperimentProject.ArrangementMode != ExperimentArrangementMode.SelfScheduled)
|
session.ExperimentProject.ArrangementMode != ExperimentArrangementMode.SelfScheduled)
|
||||||
@@ -1429,6 +1465,8 @@ public sealed class ExperimentsController(
|
|||||||
session.ExperimentProject!.Status !=
|
session.ExperimentProject!.Status !=
|
||||||
ExperimentProjectStatus.Published)
|
ExperimentProjectStatus.Published)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
if (!IsTaskOperable(session.ExperimentProject.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (session.ExperimentProject.ArrangementMode !=
|
if (session.ExperimentProject.ArrangementMode !=
|
||||||
ExperimentArrangementMode.SelfScheduled)
|
ExperimentArrangementMode.SelfScheduled)
|
||||||
return ConflictProblem("集中安排实验无需学生预约。");
|
return ConflictProblem("集中安排实验无需学生预约。");
|
||||||
@@ -1517,11 +1555,15 @@ public sealed class ExperimentsController(
|
|||||||
|
|
||||||
var booking = await db.ExperimentBookings
|
var booking = await db.ExperimentBookings
|
||||||
.Include(x => x.ExperimentSession)
|
.Include(x => x.ExperimentSession)
|
||||||
|
.ThenInclude(x => x!.ExperimentProject)
|
||||||
|
.ThenInclude(x => x!.TeachingTask)
|
||||||
.FirstOrDefaultAsync(x =>
|
.FirstOrDefaultAsync(x =>
|
||||||
x.Id == id &&
|
x.Id == id &&
|
||||||
x.StudentId == student.Id,
|
x.StudentId == student.Id,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
if (booking is null) return NotFound();
|
if (booking is null) return NotFound();
|
||||||
|
if (!IsTaskOperable(booking.ExperimentSession!.ExperimentProject!.TeachingTask!))
|
||||||
|
return TaskArchivedProblem();
|
||||||
if (booking.Status == ExperimentBookingStatus.Cancelled)
|
if (booking.Status == ExperimentBookingStatus.Cancelled)
|
||||||
return NoContent();
|
return NoContent();
|
||||||
|
|
||||||
@@ -1847,6 +1889,12 @@ public sealed class ExperimentsController(
|
|||||||
item.Teacher?.UserId == scope.UserId);
|
item.Teacher?.UserId == scope.UserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsTaskOperable(TeachingTask task) =>
|
||||||
|
task.Status == TeachingTaskStatus.Published;
|
||||||
|
|
||||||
|
private ActionResult TaskArchivedProblem() =>
|
||||||
|
ConflictProblem("教学任务已归档,实验项目、场次和预约仅可查看,不能再操作。");
|
||||||
|
|
||||||
private Task<Student?> CurrentStudentAsync(
|
private Task<Student?> CurrentStudentAsync(
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
db.Students.FirstOrDefaultAsync(x =>
|
db.Students.FirstOrDefaultAsync(x =>
|
||||||
|
|||||||
Loaded 3 of 6 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user