同学期、同课程、同教学班的实验项目合并展示,组内连续排列。
管理列表改为数据库筛选、按教学任务服务端分页(10–100 条/页)。 增加开课学院、课程名称/编码、教学班、任课教师、教学任务号筛选。 增加当前页草稿项目的批量选中、批量发布、批量删除;后端会再次校验权限、状态和发布条件。 确认框改为固定居中白色底板与阴影,不再出现只有文字按钮、没有底色的情况。
This commit is contained in:
@@ -31,12 +31,21 @@ public sealed class ExperimentsController(
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> GetOptions(
|
||||
Guid? academicTermId,
|
||||
Guid? offeringCollegeId,
|
||||
string? courseKeyword,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tasks = AccessibleTeachingTasks().AsNoTracking()
|
||||
.Where(x => x.Status == TeachingTaskStatus.Published);
|
||||
if (academicTermId.HasValue)
|
||||
tasks = tasks.Where(x => x.AcademicTermId == academicTermId);
|
||||
if (offeringCollegeId.HasValue)
|
||||
tasks = tasks.Where(x => x.Course!.CollegeId == offeringCollegeId);
|
||||
var normalizedCourseKeyword = Normalize(courseKeyword);
|
||||
if (normalizedCourseKeyword is not null)
|
||||
tasks = tasks.Where(x =>
|
||||
x.Course!.Code.Contains(normalizedCourseKeyword) ||
|
||||
x.Course.Name.Contains(normalizedCourseKeyword));
|
||||
|
||||
var periods = db.ScheduleTimeSlots.AsNoTracking()
|
||||
.Where(x => x.IsEnabled);
|
||||
@@ -90,6 +99,13 @@ public sealed class ExperimentsController(
|
||||
.OrderBy(item => item.AdministrativeClass!.Code)
|
||||
.Select(item => item.AdministrativeClass!.Name)
|
||||
})
|
||||
.Take(200)
|
||||
.ToListAsync(cancellationToken),
|
||||
Colleges = await AccessibleTeachingTasks().AsNoTracking()
|
||||
.Where(x => !academicTermId.HasValue || x.AcademicTermId == academicTermId.Value)
|
||||
.Select(x => new { x.Course!.CollegeId, CollegeName = x.Course.College!.Name })
|
||||
.Distinct()
|
||||
.OrderBy(x => x.CollegeName)
|
||||
.ToListAsync(cancellationToken),
|
||||
ScheduleEntries = await db.ScheduleEntries.AsNoTracking()
|
||||
.Where(x => x.SchedulePlan!.Status == SchedulePlanStatus.Published &&
|
||||
@@ -99,6 +115,11 @@ public sealed class ExperimentsController(
|
||||
.Contains(x.TeachingTaskId))
|
||||
.Where(x => !academicTermId.HasValue ||
|
||||
x.SchedulePlan!.AcademicTermId == academicTermId.Value)
|
||||
.Where(x => !offeringCollegeId.HasValue ||
|
||||
x.TeachingTask!.Course!.CollegeId == offeringCollegeId.Value)
|
||||
.Where(x => normalizedCourseKeyword == null ||
|
||||
x.TeachingTask!.Course!.Code.Contains(normalizedCourseKeyword) ||
|
||||
x.TeachingTask.Course.Name.Contains(normalizedCourseKeyword))
|
||||
.OrderBy(x => x.TeachingTask!.Course!.Code)
|
||||
.ThenBy(x => x.TeachingTask!.TaskNumber)
|
||||
.ThenBy(x => x.DayOfWeek)
|
||||
@@ -116,10 +137,14 @@ public sealed class ExperimentsController(
|
||||
x.StartWeek,
|
||||
x.EndWeek,
|
||||
x.WeekPattern,
|
||||
ClassNames = x.TeachingTask!.Classes
|
||||
.OrderBy(item => item.AdministrativeClass!.Code)
|
||||
.Select(item => item.AdministrativeClass!.Name),
|
||||
ClassroomName = x.Classroom!.Name,
|
||||
BuildingName = x.Classroom.Building!.Name,
|
||||
CampusName = x.Classroom.Building.Campus!.Name
|
||||
})
|
||||
.Take(500)
|
||||
.ToListAsync(cancellationToken),
|
||||
Classrooms = await db.Classrooms.AsNoTracking()
|
||||
.Where(x => x.IsEnabled)
|
||||
@@ -146,7 +171,14 @@ public sealed class ExperimentsController(
|
||||
Guid? academicTermId,
|
||||
ExperimentArrangementMode? arrangementMode,
|
||||
ExperimentProjectStatus? status,
|
||||
CancellationToken cancellationToken)
|
||||
Guid? offeringCollegeId,
|
||||
string? courseKeyword,
|
||||
string? classKeyword,
|
||||
string? teacherKeyword,
|
||||
string? taskKeyword,
|
||||
CancellationToken cancellationToken,
|
||||
int page = 1,
|
||||
int pageSize = 20)
|
||||
{
|
||||
var source = ScopedProjects().AsNoTracking();
|
||||
if (academicTermId.HasValue)
|
||||
@@ -158,7 +190,47 @@ public sealed class ExperimentsController(
|
||||
if (status.HasValue)
|
||||
source = source.Where(x => x.Status == status);
|
||||
|
||||
return Ok(await source
|
||||
var normalizedCourseKeyword = Normalize(courseKeyword);
|
||||
var normalizedClassKeyword = Normalize(classKeyword);
|
||||
var normalizedTeacherKeyword = Normalize(teacherKeyword);
|
||||
var normalizedTaskKeyword = Normalize(taskKeyword);
|
||||
var taskSource = AccessibleTeachingTasks().AsNoTracking()
|
||||
.Where(task => source.Select(project => project.TeachingTaskId).Contains(task.Id));
|
||||
if (academicTermId.HasValue)
|
||||
taskSource = taskSource.Where(x => x.AcademicTermId == academicTermId);
|
||||
if (offeringCollegeId.HasValue)
|
||||
taskSource = taskSource.Where(x => x.Course!.CollegeId == offeringCollegeId);
|
||||
if (normalizedCourseKeyword is not null)
|
||||
taskSource = taskSource.Where(x =>
|
||||
x.Course!.Code.Contains(normalizedCourseKeyword) ||
|
||||
x.Course.Name.Contains(normalizedCourseKeyword));
|
||||
if (normalizedClassKeyword is not null)
|
||||
taskSource = taskSource.Where(x => x.Classes.Any(item =>
|
||||
item.AdministrativeClass!.Code.Contains(normalizedClassKeyword) ||
|
||||
item.AdministrativeClass.Name.Contains(normalizedClassKeyword)));
|
||||
if (normalizedTeacherKeyword is not null)
|
||||
taskSource = taskSource.Where(x => x.Teachers.Any(item =>
|
||||
item.Teacher!.TeacherNumber.Contains(normalizedTeacherKeyword) ||
|
||||
item.Teacher.Name.Contains(normalizedTeacherKeyword)));
|
||||
if (normalizedTaskKeyword is not null)
|
||||
taskSource = taskSource.Where(x =>
|
||||
x.TaskNumber.Contains(normalizedTaskKeyword) ||
|
||||
x.Name.Contains(normalizedTaskKeyword));
|
||||
|
||||
page = Math.Max(1, page);
|
||||
pageSize = Math.Clamp(pageSize, 10, 100);
|
||||
var total = await taskSource.CountAsync(cancellationToken);
|
||||
var taskIds = await taskSource
|
||||
.OrderByDescending(x => x.AcademicTerm!.StartDate)
|
||||
.ThenBy(x => x.Course!.Code)
|
||||
.ThenBy(x => x.TaskNumber)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(x => x.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var items = await source
|
||||
.Where(x => taskIds.Contains(x.TeachingTaskId))
|
||||
.OrderByDescending(x => x.Status == ExperimentProjectStatus.Published)
|
||||
.ThenBy(x => x.StartDate)
|
||||
.ThenBy(x => x.Code)
|
||||
@@ -167,6 +239,7 @@ public sealed class ExperimentsController(
|
||||
x.Id,
|
||||
x.TeachingTaskId,
|
||||
x.ScheduleEntryId,
|
||||
x.ScheduleWeek,
|
||||
x.Code,
|
||||
x.Name,
|
||||
x.ArrangementMode,
|
||||
@@ -197,6 +270,7 @@ public sealed class ExperimentsController(
|
||||
x.ScheduleEntry.StartWeek,
|
||||
x.ScheduleEntry.EndWeek,
|
||||
x.ScheduleEntry.WeekPattern,
|
||||
ProjectWeek = x.ScheduleWeek,
|
||||
ClassroomName = x.ScheduleEntry.Classroom!.Name,
|
||||
BuildingName = x.ScheduleEntry.Classroom.Building!.Name,
|
||||
CampusName = x.ScheduleEntry.Classroom.Building.Campus!.Name
|
||||
@@ -220,7 +294,9 @@ public sealed class ExperimentsController(
|
||||
CampusName = item.Classroom.Building.Campus!.Name
|
||||
})
|
||||
})
|
||||
.ToListAsync(cancellationToken));
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return Ok(new { Items = items, Total = total, Page = page, PageSize = pageSize });
|
||||
}
|
||||
|
||||
[HttpGet("student")]
|
||||
@@ -254,6 +330,7 @@ public sealed class ExperimentsController(
|
||||
x.Name,
|
||||
x.ArrangementMode,
|
||||
x.ScheduleEntryId,
|
||||
x.ScheduleWeek,
|
||||
x.Description,
|
||||
x.Requirements,
|
||||
x.StartDate,
|
||||
@@ -275,6 +352,7 @@ public sealed class ExperimentsController(
|
||||
x.ScheduleEntry.StartWeek,
|
||||
x.ScheduleEntry.EndWeek,
|
||||
x.ScheduleEntry.WeekPattern,
|
||||
ProjectWeek = x.ScheduleWeek,
|
||||
ClassroomName = x.ScheduleEntry.Classroom!.Name,
|
||||
BuildingName = x.ScheduleEntry.Classroom.Building!.Name,
|
||||
CampusName = x.ScheduleEntry.Classroom.Building.Campus!.Name
|
||||
@@ -363,8 +441,6 @@ public sealed class ExperimentsController(
|
||||
ExperimentProjectBatchRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.ArrangementMode == ExperimentArrangementMode.Centralized)
|
||||
return ValidationProblem("集中安排的实验项目请逐项绑定已发布课表中的实验课。");
|
||||
var taskIds = request.TeachingTaskIds
|
||||
.Where(x => x != Guid.Empty)
|
||||
.Distinct()
|
||||
@@ -384,21 +460,72 @@ public sealed class ExperimentsController(
|
||||
return ValidationProblem("部分教学任务不存在、未发布或不在当前管理范围内。");
|
||||
|
||||
var first = tasks[0];
|
||||
if (tasks.Any(x =>
|
||||
if (request.ArrangementMode == ExperimentArrangementMode.SelfScheduled && tasks.Any(x =>
|
||||
x.AcademicTermId != first.AcademicTermId ||
|
||||
x.CourseId != first.CourseId))
|
||||
return ValidationProblem("批量设置仅支持同一学期、同一课程的教学任务。");
|
||||
|
||||
var code = request.Code.Trim();
|
||||
var conflictingTaskNumbers = await db.ExperimentProjects.AsNoTracking()
|
||||
.WhereIn(taskIds, x => x.TeachingTaskId)
|
||||
.Where(x => x.Code == code)
|
||||
.Select(x => x.TeachingTask!.TaskNumber)
|
||||
.OrderBy(x => x)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (conflictingTaskNumbers.Count > 0)
|
||||
return ConflictProblem(
|
||||
$"以下教学任务已存在实验项目编码 {code}:{string.Join("、", conflictingTaskNumbers)}。");
|
||||
if (request.ArrangementMode == ExperimentArrangementMode.SelfScheduled)
|
||||
{
|
||||
var conflictingTaskNumbers = await db.ExperimentProjects.AsNoTracking()
|
||||
.WhereIn(taskIds, x => x.TeachingTaskId)
|
||||
.Where(x => x.Code == code)
|
||||
.Select(x => x.TeachingTask!.TaskNumber)
|
||||
.OrderBy(x => x)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (conflictingTaskNumbers.Count > 0)
|
||||
return ConflictProblem(
|
||||
$"以下教学任务已存在实验项目编码 {code}:{string.Join("、", conflictingTaskNumbers)}。");
|
||||
}
|
||||
|
||||
var scheduledEntries = request.ArrangementMode == ExperimentArrangementMode.Centralized
|
||||
? await db.ScheduleEntries.AsNoTracking()
|
||||
.Where(x => x.SchedulePlan!.Status == SchedulePlanStatus.Published &&
|
||||
x.Kind == ScheduleEntryKind.Experiment &&
|
||||
x.ClassroomId.HasValue &&
|
||||
taskIds.Contains(x.TeachingTaskId))
|
||||
.OrderBy(x => x.TeachingTaskId)
|
||||
.ThenBy(x => x.DayOfWeek)
|
||||
.ThenBy(x => x.StartPeriod)
|
||||
.ToListAsync(cancellationToken)
|
||||
: [];
|
||||
if (request.ArrangementMode == ExperimentArrangementMode.Centralized &&
|
||||
tasks.Any(task => scheduledEntries.All(entry => entry.TeachingTaskId != task.Id)))
|
||||
return ValidationProblem("所选教学班中包含未排入实验室的已发布实验课,请先完成课表安排。");
|
||||
|
||||
var scheduledOccurrences = scheduledEntries
|
||||
.SelectMany(entry => Enumerable.Range(
|
||||
entry.StartWeek,
|
||||
entry.EndWeek - entry.StartWeek + 1)
|
||||
.Where(week => FreeClassroomRules.MatchesWeek(entry.WeekPattern, week))
|
||||
.Select(week => (Entry: entry, Week: week)))
|
||||
.ToList();
|
||||
|
||||
var legacyProjects = scheduledEntries.Count > 0
|
||||
? await db.ExperimentProjects
|
||||
.Where(x => x.Code == code && x.ScheduleEntryId.HasValue &&
|
||||
x.ScheduleWeek == null &&
|
||||
taskIds.Contains(x.TeachingTaskId))
|
||||
.ToListAsync(cancellationToken)
|
||||
: [];
|
||||
if (legacyProjects.Any(x => x.Status != ExperimentProjectStatus.Draft))
|
||||
return ConflictProblem("存在旧版已发布实验项目,不能自动拆分为每周项目;请先关闭后重新设置。");
|
||||
|
||||
if (scheduledOccurrences.Count > 0)
|
||||
{
|
||||
var entryIds = scheduledOccurrences.Select(x => x.Entry.Id).Distinct().ToList();
|
||||
var existingOccurrences = await db.ExperimentProjects.AsNoTracking()
|
||||
.Where(x => x.Code == code && x.ScheduleEntryId.HasValue &&
|
||||
x.ScheduleWeek.HasValue && entryIds.Contains(x.ScheduleEntryId.Value))
|
||||
.Select(x => new { ScheduleEntryId = x.ScheduleEntryId!.Value, ScheduleWeek = x.ScheduleWeek!.Value })
|
||||
.ToListAsync(cancellationToken);
|
||||
var existingKeys = existingOccurrences
|
||||
.Select(x => (x.ScheduleEntryId, x.ScheduleWeek))
|
||||
.ToHashSet();
|
||||
if (scheduledOccurrences.Any(x => existingKeys.Contains((x.Entry.Id, x.Week))))
|
||||
return ConflictProblem("所选实验课中已存在相同实验项目编码和周次,不能重复生成。");
|
||||
}
|
||||
|
||||
var projects = new List<ExperimentProject>(tasks.Count);
|
||||
foreach (var task in tasks)
|
||||
@@ -407,17 +534,42 @@ public sealed class ExperimentsController(
|
||||
var problem = ValidateProjectRequest(item, task.AcademicTerm!);
|
||||
if (problem is not null) return ValidationProblem(problem);
|
||||
|
||||
projects.Add(new ExperimentProject
|
||||
IEnumerable<(ScheduleEntry? Entry, int? Week)> taskOccurrences =
|
||||
request.ArrangementMode == ExperimentArrangementMode.Centralized
|
||||
? scheduledOccurrences
|
||||
.Where(item => item.Entry.TeachingTaskId == task.Id)
|
||||
.Select(item => ((ScheduleEntry?)item.Entry, (int?)item.Week))
|
||||
: [(null, null)];
|
||||
foreach (var occurrence in taskOccurrences)
|
||||
{
|
||||
TeachingTaskId = task.Id,
|
||||
Code = code,
|
||||
Name = request.Name.Trim(),
|
||||
ArrangementMode = request.ArrangementMode,
|
||||
Description = Normalize(request.Description),
|
||||
Requirements = Normalize(request.Requirements),
|
||||
StartDate = request.StartDate,
|
||||
EndDate = request.EndDate
|
||||
});
|
||||
var scheduleEntry = occurrence.Entry;
|
||||
var legacy = scheduleEntry is null ? null : legacyProjects
|
||||
.SingleOrDefault(x => x.ScheduleEntryId == scheduleEntry.Id);
|
||||
if (legacy is not null)
|
||||
{
|
||||
var firstWeek = scheduledOccurrences
|
||||
.Where(item => item.Entry.Id == scheduleEntry!.Id)
|
||||
.Min(item => item.Week);
|
||||
if (occurrence.Week == firstWeek)
|
||||
{
|
||||
legacy.ScheduleWeek = occurrence.Week;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
projects.Add(new ExperimentProject
|
||||
{
|
||||
TeachingTaskId = task.Id,
|
||||
ScheduleEntryId = occurrence.Entry?.Id,
|
||||
ScheduleWeek = occurrence.Week,
|
||||
Code = code,
|
||||
Name = request.Name.Trim(),
|
||||
ArrangementMode = request.ArrangementMode,
|
||||
Description = Normalize(request.Description),
|
||||
Requirements = Normalize(request.Requirements),
|
||||
StartDate = request.StartDate,
|
||||
EndDate = request.EndDate
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
db.ExperimentProjects.AddRange(projects);
|
||||
@@ -491,6 +643,26 @@ public sealed class ExperimentsController(
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("batch")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> DeleteProjects(
|
||||
ExperimentProjectBulkRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var ids = ValidateBulkProjectIds(request.ProjectIds);
|
||||
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
|
||||
var projects = await ScopedProjects()
|
||||
.Where(x => ids.Contains(x.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
if (projects.Count != ids.Count) return NotFound();
|
||||
if (projects.Any(x => x.Status != ExperimentProjectStatus.Draft))
|
||||
return ConflictProblem("批量删除只能包含草稿实验项目。");
|
||||
|
||||
db.ExperimentProjects.RemoveRange(projects);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/publish")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> PublishProject(
|
||||
@@ -522,28 +694,49 @@ public sealed class ExperimentsController(
|
||||
project.Status = ExperimentProjectStatus.Published;
|
||||
project.PublishedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
await NotifyProjectPublishedAsync(project, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
var userIds = await TeachingTaskRosterQuery
|
||||
.ForTask(db, project.TeachingTaskId)
|
||||
.Where(x => x.UserId.HasValue)
|
||||
.Select(x => x.UserId!.Value)
|
||||
.Distinct()
|
||||
[HttpPost("batch/publish")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> PublishProjects(
|
||||
ExperimentProjectBulkRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var ids = ValidateBulkProjectIds(request.ProjectIds);
|
||||
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
|
||||
var projects = await ScopedProjects()
|
||||
.Include(x => x.Sessions)
|
||||
.Include(x => x.TeachingTask)
|
||||
.ThenInclude(x => x!.Course)
|
||||
.Where(x => ids.Contains(x.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
if (userIds.Count > 0)
|
||||
if (projects.Count != ids.Count) return NotFound();
|
||||
|
||||
foreach (var project in projects)
|
||||
{
|
||||
var mode = project.ArrangementMode ==
|
||||
ExperimentArrangementMode.Centralized
|
||||
? "集中安排"
|
||||
: "自行预约";
|
||||
await NotificationService.SendToUserIdsAsync(
|
||||
db,
|
||||
userIds,
|
||||
"实验项目已发布",
|
||||
$"《{project.TeachingTask!.Course!.Name}》已发布“{project.Name}”({mode}),请查看实验安排。",
|
||||
"/experiments",
|
||||
cancellationToken,
|
||||
NotificationCategory.Schedule);
|
||||
if (project.Status != ExperimentProjectStatus.Draft)
|
||||
return ConflictProblem("批量发布只能包含草稿实验项目。");
|
||||
var hasSchedule = project.ArrangementMode == ExperimentArrangementMode.Centralized
|
||||
? project.ScheduleEntryId.HasValue || project.Sessions.Any(x => x.Status == ExperimentSessionStatus.Scheduled)
|
||||
: project.Sessions.Any(x => x.Status == ExperimentSessionStatus.Scheduled);
|
||||
if (!hasSchedule)
|
||||
return ConflictProblem($"“{project.Name}”尚未具备发布条件。");
|
||||
if (project.Sessions.Any(x => x.Status == ExperimentSessionStatus.Scheduled &&
|
||||
(x.SessionDate < project.StartDate || x.SessionDate > project.EndDate)))
|
||||
return ConflictProblem($"“{project.Name}”存在不在开放日期范围内的实验场次。");
|
||||
}
|
||||
|
||||
var publishedAt = DateTime.UtcNow;
|
||||
foreach (var project in projects)
|
||||
{
|
||||
project.Status = ExperimentProjectStatus.Published;
|
||||
project.PublishedAt = publishedAt;
|
||||
}
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
foreach (var project in projects)
|
||||
await NotifyProjectPublishedAsync(project, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
@@ -1234,6 +1427,31 @@ public sealed class ExperimentsController(
|
||||
.Distinct()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
private async Task NotifyProjectPublishedAsync(
|
||||
ExperimentProject project,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var userIds = await RosterUserIdsAsync(project.TeachingTaskId, cancellationToken);
|
||||
if (userIds.Count == 0) return;
|
||||
var mode = project.ArrangementMode == ExperimentArrangementMode.Centralized
|
||||
? "集中安排"
|
||||
: "自行预约";
|
||||
await NotificationService.SendToUserIdsAsync(
|
||||
db,
|
||||
userIds,
|
||||
"实验项目已发布",
|
||||
$"《{project.TeachingTask!.Course!.Name}》已发布“{project.Name}”({mode}),请查看实验安排。",
|
||||
"/experiments",
|
||||
cancellationToken,
|
||||
NotificationCategory.Schedule);
|
||||
}
|
||||
|
||||
private static List<Guid>? ValidateBulkProjectIds(IReadOnlyList<Guid>? projectIds)
|
||||
{
|
||||
var ids = projectIds?.Where(x => x != Guid.Empty).Distinct().ToList();
|
||||
return ids is { Count: > 0 and <= 100 } ? ids : null;
|
||||
}
|
||||
|
||||
private static string? ValidateProjectRequest(
|
||||
ExperimentProjectRequest request,
|
||||
AcademicTerm term)
|
||||
@@ -1321,6 +1539,9 @@ public sealed record ExperimentProjectBatchRequest(
|
||||
EndDate);
|
||||
}
|
||||
|
||||
public sealed record ExperimentProjectBulkRequest(
|
||||
[Required] IReadOnlyList<Guid> ProjectIds);
|
||||
|
||||
public sealed record ExperimentSessionRequest(
|
||||
Guid ClassroomId,
|
||||
DateOnly SessionDate,
|
||||
|
||||
Reference in New Issue
Block a user