教学任务按 授课周数 × 周学时 = 课程总学时 双端校验,不匹配会显示具体差额并阻止保存;公共课批量生成同样校验。

排课约束增加课程、教师、学院、授课方式、场地要求、约束状态筛选,并支持批量修改当前筛选结果。
新增“非排时课程”授课方式:不进入自动排课
不占星期、节次和教室
不阻塞课表发布
允许正常选课
在班级课表和学生个人课表中单独展示
This commit is contained in:
2026-07-25 08:18:25 +08:00 Unverified
parent 30c15f89e5
commit fe508054d0
20 changed files with 3785 additions and 136 deletions
@@ -631,11 +631,12 @@ public sealed class CourseSelectionsController(
x.Enrollments.Count(item =>
item.Status == CourseEnrollmentStatus.Enrolled),
x.IsOpenToAll,
x.Enrollments
.Where(item => item.StudentId == student.Id)
.Select(item => (CourseEnrollmentStatus?)item.Status)
.FirstOrDefault(),
db.ScheduleEntries
x.Enrollments
.Where(item => item.StudentId == student.Id)
.Select(item => (CourseEnrollmentStatus?)item.Status)
.FirstOrDefault(),
x.TeachingTask.SchedulingMode == TeachingTaskSchedulingMode.Flexible,
db.ScheduleEntries
.Where(entry =>
entry.TeachingTaskId == x.TeachingTaskId &&
entry.SchedulePlan!.AcademicTermId == round.AcademicTermId &&
@@ -803,8 +804,9 @@ public sealed class CourseSelectionsController(
round.AcademicTermId,
[task.Id],
cancellationToken);
if (candidateEntries.Count == 0)
return ConflictProblem("该教学班尚未发布课表,暂时不能选课。");
if (task.SchedulingMode == TeachingTaskSchedulingMode.Standard &&
candidateEntries.Count == 0)
return ConflictProblem("该教学班尚未发布课表,暂时不能选课。");
var selectedTaskIds = await db.CourseEnrollments
.Where(x =>
x.StudentId == student.Id &&
@@ -1008,9 +1010,10 @@ public sealed record StudentOfferingDto(
IEnumerable<string> TeacherNames,
int Capacity,
int EnrolledCount,
bool IsOpenToAll,
CourseEnrollmentStatus? EnrollmentStatus,
IEnumerable<StudentScheduleDto> Schedules);
bool IsOpenToAll,
CourseEnrollmentStatus? EnrollmentStatus,
bool IsFlexible,
IEnumerable<StudentScheduleDto> Schedules);
public sealed record StudentScheduleDto(
int DayOfWeek,
@@ -128,10 +128,11 @@ public sealed class CoursesController(
x.Name,
x.CollegeId,
CollegeName = x.College!.Name,
x.CourseCategoryId,
CategoryName = x.CourseCategory != null ? x.CourseCategory.Name : null,
x.Credits,
x.Nature,
x.CourseCategoryId,
CategoryName = x.CourseCategory != null ? x.CourseCategory.Name : null,
x.Credits,
x.TotalHours,
x.Nature,
x.AssessmentMethod
})
.ToListAsync(cancellationToken));
@@ -82,13 +82,17 @@ public sealed class ScheduleSettingsController(AppDbContext db) : ControllerBase
{
x.Id,
x.TaskNumber,
x.Name,
CourseName = x.Course!.Name,
TeacherNames = x.Teachers
x.Name,
CourseCode = x.Course!.Code,
CourseName = x.Course!.Name,
CollegeId = x.Course.CollegeId,
CollegeName = x.Course.College!.Name,
TeacherNames = x.Teachers
.OrderByDescending(item => item.IsPrimary)
.Select(item => item.Teacher!.Name),
x.Capacity,
x.WeeklyHours
x.Capacity,
x.WeeklyHours,
x.SchedulingMode
})
.ToListAsync(cancellationToken);
var taskIds = tasks.Select(x => x.Id).ToList();
@@ -103,12 +107,19 @@ public sealed class ScheduleSettingsController(AppDbContext db) : ControllerBase
{
task.Id,
task.TaskNumber,
task.Name,
task.CourseName,
task.TeacherNames,
task.Capacity,
task.WeeklyHours,
RequiresClassroom = constraint?.RequiresClassroom ?? true,
task.Name,
task.CourseCode,
task.CourseName,
task.CollegeId,
task.CollegeName,
task.TeacherNames,
task.Capacity,
task.WeeklyHours,
task.SchedulingMode,
HasCustomConstraint = constraint is not null,
RequiresClassroom = task.SchedulingMode == TeachingTaskSchedulingMode.Flexible
? false
: constraint?.RequiresClassroom ?? true,
constraint?.RequiredCampusId,
constraint?.RequiredBuildingId,
AllowedDayOfWeeks = ParseDays(constraint?.AllowedDayOfWeeks),
@@ -128,15 +139,34 @@ public sealed class ScheduleSettingsController(AppDbContext db) : ControllerBase
{
if (request.AllowedDayOfWeeks.Any(day => day is < 1 or > 7))
return ValidationProblem("允许上课日必须位于星期一至星期日。");
if (request.EarliestPeriod.HasValue &&
request.LatestPeriod.HasValue &&
request.EarliestPeriod > request.LatestPeriod)
return ValidationProblem("最早节次不能晚于最晚节次。");
var task = await db.TeachingTasks
.FirstOrDefaultAsync(x => x.Id == teachingTaskId, cancellationToken);
if (task is null) return NotFound();
if (request.EarliestPeriod.HasValue &&
request.LatestPeriod.HasValue &&
request.EarliestPeriod > request.LatestPeriod)
return ValidationProblem("最早节次不能晚于最晚节次。");
if (!Enum.IsDefined(request.SchedulingMode))
return ValidationProblem("授课方式无效。");
var task = await db.TeachingTasks
.FirstOrDefaultAsync(x => x.Id == teachingTaskId, cancellationToken);
if (task is null) return NotFound();
if (request.SchedulingMode == TeachingTaskSchedulingMode.Flexible &&
await HasScheduleEntriesAsync([teachingTaskId], cancellationToken))
return ConflictProblem("该教学任务已有正常排课记录,请先删除排课记录后再改为非排时课程。");
Building? building = null;
task.SchedulingMode = request.SchedulingMode;
if (request.SchedulingMode == TeachingTaskSchedulingMode.Flexible)
{
var flexibleConstraint = await db.TeachingTaskScheduleConstraints
.Include(x => x.AllowedClassrooms)
.FirstOrDefaultAsync(x => x.TeachingTaskId == teachingTaskId, cancellationToken);
if (flexibleConstraint is not null)
{
ClearConstraint(flexibleConstraint);
}
await db.SaveChangesAsync(cancellationToken);
return NoContent();
}
Building? building = null;
if (request.RequiredBuildingId.HasValue)
{
building = await db.Buildings.AsNoTracking()
@@ -191,10 +221,128 @@ public sealed class ScheduleSettingsController(AppDbContext db) : ControllerBase
? request.AllowedClassroomIds.Distinct().Select(classroomId =>
new TeachingTaskAllowedClassroom { ClassroomId = classroomId }).ToList()
: [];
await db.SaveChangesAsync(cancellationToken);
return NoContent();
}
[HttpPut("constraints/batch")]
public async Task<ActionResult> SaveConstraintsBatch(
TeachingTaskScheduleConstraintBatchRequest request,
CancellationToken cancellationToken)
{
var taskIds = request.TeachingTaskIds.Distinct().ToArray();
if (taskIds.Length == 0)
return ValidationProblem("请至少选择一个教学任务。");
if (request.AllowedDayOfWeeks?.Any(day => day is < 1 or > 7) == true)
return ValidationProblem("允许上课日必须位于星期一至星期日。");
if (request.EarliestPeriod.HasValue &&
request.LatestPeriod.HasValue &&
request.EarliestPeriod > request.LatestPeriod)
return ValidationProblem("最早节次不能晚于最晚节次。");
if (request.SchedulingMode.HasValue &&
!Enum.IsDefined(request.SchedulingMode.Value))
return ValidationProblem("授课方式无效。");
if (!request.SchedulingMode.HasValue &&
!request.RequiresClassroom.HasValue &&
request.AllowedDayOfWeeks is null &&
!request.UpdatePeriodRange &&
!request.EarliestPeriod.HasValue &&
!request.LatestPeriod.HasValue)
return ValidationProblem("请至少选择一项需要批量修改的设置。");
var tasks = await db.TeachingTasks
.Where(x =>
taskIds.Contains(x.Id) &&
x.AcademicTermId == request.AcademicTermId &&
x.Status == TeachingTaskStatus.Published)
.ToListAsync(cancellationToken);
if (tasks.Count != taskIds.Length)
return ValidationProblem("部分教学任务不存在、未发布或不属于当前学期。");
if (request.SchedulingMode == TeachingTaskSchedulingMode.Flexible &&
await HasScheduleEntriesAsync(taskIds, cancellationToken))
return ConflictProblem("所选教学任务中存在已有正常排课记录的课程,请先删除这些排课记录。");
var constraints = await db.TeachingTaskScheduleConstraints
.Where(x => taskIds.Contains(x.TeachingTaskId))
.Include(x => x.AllowedClassrooms)
.ToDictionaryAsync(x => x.TeachingTaskId, cancellationToken);
foreach (var task in tasks)
{
if (request.SchedulingMode.HasValue)
task.SchedulingMode = request.SchedulingMode.Value;
if (task.SchedulingMode == TeachingTaskSchedulingMode.Flexible)
{
if (constraints.TryGetValue(task.Id, out var flexibleConstraint))
ClearConstraint(flexibleConstraint);
continue;
}
if (!constraints.TryGetValue(task.Id, out var constraint))
{
var changesConstraint =
request.RequiresClassroom.HasValue ||
request.AllowedDayOfWeeks is not null ||
request.UpdatePeriodRange;
if (!changesConstraint) continue;
constraint = new TeachingTaskScheduleConstraint { TeachingTaskId = task.Id };
constraints[task.Id] = constraint;
db.TeachingTaskScheduleConstraints.Add(constraint);
}
if (request.RequiresClassroom.HasValue)
{
constraint.RequiresClassroom = request.RequiresClassroom.Value;
if (!request.RequiresClassroom.Value)
{
constraint.RequiredCampusId = null;
constraint.RequiredBuildingId = null;
db.TeachingTaskAllowedClassrooms.RemoveRange(constraint.AllowedClassrooms);
constraint.AllowedClassrooms = [];
}
}
if (request.AllowedDayOfWeeks is not null)
{
constraint.AllowedDayOfWeeks = request.AllowedDayOfWeeks.Count == 0
? null
: string.Join(',', request.AllowedDayOfWeeks.Distinct().Order());
}
if (request.UpdatePeriodRange)
{
constraint.EarliestPeriod = request.EarliestPeriod;
constraint.LatestPeriod = request.LatestPeriod;
}
}
await db.SaveChangesAsync(cancellationToken);
return NoContent();
return Ok(new { AffectedCount = tasks.Count });
}
private Task<bool> HasScheduleEntriesAsync(
IReadOnlyCollection<Guid> taskIds,
CancellationToken cancellationToken) =>
db.ScheduleEntries.AsNoTracking()
.AnyAsync(x => taskIds.Contains(x.TeachingTaskId), cancellationToken);
private void ClearConstraint(TeachingTaskScheduleConstraint constraint)
{
constraint.RequiresClassroom = false;
constraint.RequiredCampusId = null;
constraint.RequiredBuildingId = null;
constraint.AllowedDayOfWeeks = null;
constraint.EarliestPeriod = null;
constraint.LatestPeriod = null;
db.TeachingTaskAllowedClassrooms.RemoveRange(constraint.AllowedClassrooms);
constraint.AllowedClassrooms = [];
}
private ActionResult ConflictProblem(string detail) =>
Conflict(new ProblemDetails
{
Title = "操作冲突",
Detail = detail,
Status = StatusCodes.Status409Conflict
});
private static int[] ParseDays(string? value) =>
string.IsNullOrWhiteSpace(value)
? []
@@ -211,6 +359,7 @@ public sealed record ScheduleTimeSlotRequest(
bool IsEnabled);
public sealed record TeachingTaskScheduleConstraintRequest(
TeachingTaskSchedulingMode SchedulingMode,
bool RequiresClassroom,
Guid? RequiredCampusId,
Guid? RequiredBuildingId,
@@ -218,3 +367,13 @@ public sealed record TeachingTaskScheduleConstraintRequest(
IReadOnlyList<int> AllowedDayOfWeeks,
[Range(1, 30)] int? EarliestPeriod,
[Range(1, 30)] int? LatestPeriod);
public sealed record TeachingTaskScheduleConstraintBatchRequest(
Guid AcademicTermId,
[MinLength(1), MaxLength(500)] IReadOnlyCollection<Guid> TeachingTaskIds,
TeachingTaskSchedulingMode? SchedulingMode,
bool? RequiresClassroom,
IReadOnlyList<int>? AllowedDayOfWeeks,
bool UpdatePeriodRange,
[Range(1, 30)] int? EarliestPeriod,
[Range(1, 30)] int? LatestPeriod);
@@ -232,9 +232,10 @@ public sealed class SchedulesController(
}
var requiredTasks = await db.TeachingTasks.AsNoTracking()
.Where(x =>
x.AcademicTermId == plan.AcademicTermId &&
x.Status == TeachingTaskStatus.Published)
.Where(x =>
x.AcademicTermId == plan.AcademicTermId &&
x.Status == TeachingTaskStatus.Published &&
x.SchedulingMode == TeachingTaskSchedulingMode.Standard)
.Select(x => new { x.Id, x.TaskNumber, x.Name, x.WeeklyHours })
.ToListAsync(cancellationToken);
var scheduledHours = plan.Entries
@@ -449,10 +450,12 @@ public sealed class SchedulesController(
.ThenInclude(x => x.AdministrativeClass)
.ThenInclude(x => x!.Students)
.FirstOrDefaultAsync(x => x.Id == request.TeachingTaskId, cancellationToken);
if (task is null ||
task.Status != TeachingTaskStatus.Published ||
task.AcademicTermId != plan.AcademicTermId)
return ValidationProblem("只能安排同一学期内已发布的教学任务。");
if (task is null ||
task.Status != TeachingTaskStatus.Published ||
task.AcademicTermId != plan.AcademicTermId)
return ValidationProblem("只能安排同一学期内已发布的教学任务。");
if (task.SchedulingMode == TeachingTaskSchedulingMode.Flexible)
return ValidationProblem("非排时课程不进入正常课表,无需设置星期、节次或教室。");
if (request.StartWeek < task.StartWeek || request.EndWeek > task.EndWeek)
return ValidationProblem("排课周次必须位于教学任务的授课周次内。");
@@ -76,9 +76,11 @@ public sealed class TeachingTasksController(
CollegeName = x.Course.College!.Name,
x.Capacity,
x.StartWeek,
x.EndWeek,
x.WeeklyHours,
x.GenerationBatchCode,
x.EndWeek,
x.WeeklyHours,
x.SchedulingMode,
CourseTotalHours = x.Course.TotalHours,
x.GenerationBatchCode,
x.Status,
TeacherNames = x.Teachers
.OrderByDescending(item => item.IsPrimary)
@@ -114,9 +116,11 @@ public sealed class TeachingTasksController(
CollegeName = x.Course.College!.Name,
x.Capacity,
x.StartWeek,
x.EndWeek,
x.WeeklyHours,
x.GenerationBatchCode,
x.EndWeek,
x.WeeklyHours,
x.SchedulingMode,
CourseTotalHours = x.Course.TotalHours,
x.GenerationBatchCode,
x.Status,
x.Notes,
x.PublishedAt,
@@ -170,9 +174,10 @@ public sealed class TeachingTasksController(
CourseId = request.CourseId,
Capacity = request.Capacity,
StartWeek = request.StartWeek,
EndWeek = request.EndWeek,
WeeklyHours = request.WeeklyHours,
Notes = Normalize(request.Notes)
EndWeek = request.EndWeek,
WeeklyHours = request.WeeklyHours,
SchedulingMode = request.SchedulingMode,
Notes = Normalize(request.Notes)
};
SetAssignments(task, request);
db.TeachingTasks.Add(task);
@@ -203,9 +208,10 @@ public sealed class TeachingTasksController(
task.CourseId = request.CourseId;
task.Capacity = request.Capacity;
task.StartWeek = request.StartWeek;
task.EndWeek = request.EndWeek;
task.WeeklyHours = request.WeeklyHours;
task.Notes = Normalize(request.Notes);
task.EndWeek = request.EndWeek;
task.WeeklyHours = request.WeeklyHours;
task.SchedulingMode = request.SchedulingMode;
task.Notes = Normalize(request.Notes);
db.TeachingTaskTeachers.RemoveRange(task.Teachers);
db.TeachingTaskClasses.RemoveRange(task.Classes);
task.Teachers = [];
@@ -342,8 +348,14 @@ public sealed class TeachingTasksController(
.FirstOrDefaultAsync(
x => x.Id == request.CourseId && x.IsEnabled,
cancellationToken);
if (course is null) return ValidationProblem("所选课程不存在或已停用。");
if (!CanManage(course)) return Forbid();
if (course is null) return ValidationProblem("所选课程不存在或已停用。");
if (!CanManage(course)) return Forbid();
var hoursProblem = TeachingTaskHours.Validate(
course,
request.StartWeek,
request.EndWeek,
request.WeeklyHours);
if (hoursProblem is not null) return ValidationProblem(hoursProblem);
if (course.Nature is not (CourseNature.GeneralRequired or CourseNature.GeneralElective))
return ValidationProblem("批量合班生成仅用于公共必修课或公共选修课。");
var scopedCollegeId = ScopedCollegeId();
@@ -501,9 +513,17 @@ public sealed class TeachingTasksController(
return ValidationProblem("开始周不能晚于结束周。");
var course = await db.Courses.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == request.CourseId && x.IsEnabled, cancellationToken);
if (course is null) return ValidationProblem("所选课程不存在或已停用。");
if (!CanManage(course)) return Forbid();
var collegeId = ScopedCollegeId();
if (course is null) return ValidationProblem("所选课程不存在或已停用。");
if (!CanManage(course)) return Forbid();
if (!Enum.IsDefined(request.SchedulingMode))
return ValidationProblem("授课方式无效。");
var hoursProblem = TeachingTaskHours.Validate(
course,
request.StartWeek,
request.EndWeek,
request.WeeklyHours);
if (hoursProblem is not null) return ValidationProblem(hoursProblem);
var collegeId = ScopedCollegeId();
if (!await db.AcademicTerms.AnyAsync(
x => x.Id == request.AcademicTermId && x.IsEnabled,
cancellationToken))
@@ -695,11 +715,12 @@ public sealed record TeachingTaskRequest(
[Range(1, 10000)] int Capacity,
[Range(1, 30)] int StartWeek,
[Range(1, 30)] int EndWeek,
[Range(1, 40)] int WeeklyHours,
IReadOnlyCollection<Guid> TeacherIds,
Guid? PrimaryTeacherId,
IReadOnlyCollection<Guid> ClassIds,
[MaxLength(500)] string? Notes);
[Range(1, 40)] int WeeklyHours,
IReadOnlyCollection<Guid> TeacherIds,
Guid? PrimaryTeacherId,
IReadOnlyCollection<Guid> ClassIds,
[MaxLength(500)] string? Notes,
TeachingTaskSchedulingMode SchedulingMode = TeachingTaskSchedulingMode.Standard);
public sealed record PublicCourseTaskGenerationRequest(
Guid AcademicTermId,
@@ -28,9 +28,13 @@ public sealed class TimetablesController(AppDbContext db) : ControllerBase
x.StartDate,
x.EndDate,
x.IsCurrent,
HasPublishedTimetable = db.SchedulePlans.Any(plan =>
plan.AcademicTermId == x.Id &&
plan.Status == SchedulePlanStatus.Published)
HasPublishedTimetable = db.SchedulePlans.Any(plan =>
plan.AcademicTermId == x.Id &&
plan.Status == SchedulePlanStatus.Published) ||
db.TeachingTasks.Any(task =>
task.AcademicTermId == x.Id &&
task.Status == TeachingTaskStatus.Published &&
task.SchedulingMode == TeachingTaskSchedulingMode.Flexible)
})
.ToListAsync(cancellationToken);
var defaultTermId = terms.FirstOrDefault(x => x.IsCurrent)?.Id
@@ -47,12 +51,17 @@ public sealed class TimetablesController(AppDbContext db) : ControllerBase
x.Grade,
MajorName = x.Major!.Name,
CollegeName = x.Major.College!.Name,
HasPublishedTimetable = defaultTermId.HasValue &&
db.ScheduleEntries.Any(entry =>
entry.SchedulePlan!.AcademicTermId == defaultTermId.Value &&
entry.SchedulePlan.Status == SchedulePlanStatus.Published &&
entry.TeachingTask!.Classes.Any(item =>
item.AdministrativeClassId == x.Id))
HasPublishedTimetable = defaultTermId.HasValue &&
(db.ScheduleEntries.Any(entry =>
entry.SchedulePlan!.AcademicTermId == defaultTermId.Value &&
entry.SchedulePlan.Status == SchedulePlanStatus.Published &&
entry.TeachingTask!.Classes.Any(item =>
item.AdministrativeClassId == x.Id)) ||
db.TeachingTasks.Any(task =>
task.AcademicTermId == defaultTermId.Value &&
task.Status == TeachingTaskStatus.Published &&
task.SchedulingMode == TeachingTaskSchedulingMode.Flexible &&
task.Classes.Any(item => item.AdministrativeClassId == x.Id)))
})
.ToListAsync(cancellationToken);
return Ok(new { Terms = terms, Classes = classes });
@@ -148,21 +157,67 @@ public sealed class TimetablesController(AppDbContext db) : ControllerBase
.Select(x => new { x.Id, x.Name, x.Version, x.PublishedAt })
.FirstOrDefaultAsync(cancellationToken);
var slots = await db.ScheduleTimeSlots.AsNoTracking()
var slots = await db.ScheduleTimeSlots.AsNoTracking()
.Where(x => x.AcademicTermId == term.Id && x.IsEnabled)
.OrderBy(x => x.PeriodNumber)
.Select(x => new { x.PeriodNumber, x.Name, x.StartsAt, x.EndsAt })
.Select(x => new { x.PeriodNumber, x.Name, x.StartsAt, x.EndsAt })
.ToListAsync(cancellationToken);
var flexibleTasks = db.TeachingTasks.AsNoTracking()
.Where(x =>
x.AcademicTermId == term.Id &&
x.Status == TeachingTaskStatus.Published &&
x.SchedulingMode == TeachingTaskSchedulingMode.Flexible &&
x.Classes.Any(item => item.AdministrativeClassId == classId));
if (studentId.HasValue)
{
var selectedFlexibleTaskIds = db.CourseEnrollments.AsNoTracking()
.Where(x =>
x.StudentId == studentId.Value &&
x.Status == CourseEnrollmentStatus.Enrolled &&
x.CourseSelectionOffering!.CourseSelectionRound!.AcademicTermId == term.Id)
.Select(x => x.CourseSelectionOffering!.TeachingTaskId);
flexibleTasks = db.TeachingTasks.AsNoTracking()
.Where(x =>
x.AcademicTermId == term.Id &&
x.Status == TeachingTaskStatus.Published &&
x.SchedulingMode == TeachingTaskSchedulingMode.Flexible &&
(x.Classes.Any(item => item.AdministrativeClassId == classId) ||
selectedFlexibleTaskIds.Contains(x.Id)));
}
var flexibleCourses = await flexibleTasks
.OrderBy(x => x.Course!.Code)
.ThenBy(x => x.TaskNumber)
.Select(x => new
{
x.Id,
x.TaskNumber,
x.Name,
CourseCode = x.Course!.Code,
CourseName = x.Course.Name,
x.Course.Credits,
x.Course.TotalHours,
x.StartWeek,
x.EndWeek,
x.WeeklyHours,
TeacherNames = x.Teachers
.OrderByDescending(item => item.IsPrimary)
.Select(item => item.Teacher!.Name),
ClassNames = x.Classes.Select(item => item.AdministrativeClass!.Name),
x.Notes
})
.ToListAsync(cancellationToken);
if (plan is null)
return Ok(new
if (plan is null)
return Ok(new
{
Term = term,
Class = administrativeClass,
Student = student,
Plan = (object?)null,
Slots = slots,
Entries = Array.Empty<object>()
Plan = (object?)null,
Slots = slots,
Entries = Array.Empty<object>(),
FlexibleCourses = flexibleCourses
});
var entries = db.ScheduleEntries.AsNoTracking()
@@ -223,9 +278,10 @@ public sealed class TimetablesController(AppDbContext db) : ControllerBase
Term = term,
Class = administrativeClass,
Student = student,
Plan = plan,
Slots = slots,
Entries = result
});
Plan = plan,
Slots = slots,
Entries = result,
FlexibleCourses = flexibleCourses
});
}
}