排课优化 #3
No files matched your search
@@ -557,7 +557,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
.Select(x => new ClassroomListItem(
|
.Select(x => new ClassroomListItem(
|
||||||
x.Id, x.Code, x.Name, x.BuildingId, x.Building!.CampusId,
|
x.Id, x.Code, x.Name, x.BuildingId, x.Building!.CampusId,
|
||||||
x.Building.Name, x.Building.Campus!.Name, x.Capacity,
|
x.Building.Name, x.Building.Campus!.Name, x.Capacity,
|
||||||
x.RoomType, x.TeachingVenueNature, x.Equipment,
|
x.Floor, x.RoomType, x.TeachingVenueNature, x.Equipment,
|
||||||
x.IsEnabled, x.SortOrder))
|
x.IsEnabled, x.SortOrder))
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
return Ok(new PagedResult<ClassroomListItem>(
|
return Ok(new PagedResult<ClassroomListItem>(
|
||||||
@@ -578,6 +578,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
x.Building.Name,
|
x.Building.Name,
|
||||||
x.Building.Campus!.Name,
|
x.Building.Campus!.Name,
|
||||||
x.Capacity,
|
x.Capacity,
|
||||||
|
x.Floor,
|
||||||
x.RoomType,
|
x.RoomType,
|
||||||
x.TeachingVenueNature,
|
x.TeachingVenueNature,
|
||||||
x.Equipment,
|
x.Equipment,
|
||||||
@@ -645,6 +646,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
Name = request.Name.Trim(),
|
Name = request.Name.Trim(),
|
||||||
BuildingId = request.BuildingId,
|
BuildingId = request.BuildingId,
|
||||||
Capacity = request.Capacity,
|
Capacity = request.Capacity,
|
||||||
|
Floor = request.Floor,
|
||||||
RoomType = request.RoomType.Trim(),
|
RoomType = request.RoomType.Trim(),
|
||||||
TeachingVenueNature = request.TeachingVenueNature == 0
|
TeachingVenueNature = request.TeachingVenueNature == 0
|
||||||
? TeachingVenueNature.GeneralClassroom
|
? TeachingVenueNature.GeneralClassroom
|
||||||
@@ -668,6 +670,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
ApplyCatalog(entity, request);
|
ApplyCatalog(entity, request);
|
||||||
entity.BuildingId = request.BuildingId;
|
entity.BuildingId = request.BuildingId;
|
||||||
entity.Capacity = request.Capacity;
|
entity.Capacity = request.Capacity;
|
||||||
|
entity.Floor = request.Floor;
|
||||||
entity.RoomType = request.RoomType.Trim();
|
entity.RoomType = request.RoomType.Trim();
|
||||||
entity.TeachingVenueNature = request.TeachingVenueNature == 0
|
entity.TeachingVenueNature = request.TeachingVenueNature == 0
|
||||||
? TeachingVenueNature.GeneralClassroom
|
? TeachingVenueNature.GeneralClassroom
|
||||||
@@ -822,6 +825,7 @@ public sealed record ClassroomRequest(
|
|||||||
string Code, string Name, int SortOrder, bool IsEnabled,
|
string Code, string Name, int SortOrder, bool IsEnabled,
|
||||||
Guid BuildingId,
|
Guid BuildingId,
|
||||||
[Range(1, 1000)] int Capacity,
|
[Range(1, 1000)] int Capacity,
|
||||||
|
[Range(1, 99)] int Floor,
|
||||||
[Required, MaxLength(40)] string RoomType,
|
[Required, MaxLength(40)] string RoomType,
|
||||||
TeachingVenueNature TeachingVenueNature,
|
TeachingVenueNature TeachingVenueNature,
|
||||||
[MaxLength(300)] string? Equipment)
|
[MaxLength(300)] string? Equipment)
|
||||||
@@ -879,6 +883,7 @@ public sealed record ClassroomListItem(
|
|||||||
string BuildingName,
|
string BuildingName,
|
||||||
string CampusName,
|
string CampusName,
|
||||||
int Capacity,
|
int Capacity,
|
||||||
|
int Floor,
|
||||||
string RoomType,
|
string RoomType,
|
||||||
TeachingVenueNature TeachingVenueNature,
|
TeachingVenueNature TeachingVenueNature,
|
||||||
string? Equipment,
|
string? Equipment,
|
||||||
|
|||||||
@@ -1308,6 +1308,104 @@ public sealed class ExperimentsController(
|
|||||||
IsolationLevel.Serializable);
|
IsolationLevel.Serializable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("sessions/{id:guid}/proxy-bookings/batch")]
|
||||||
|
[Authorize(Roles = Managers)]
|
||||||
|
public async Task<ActionResult> CreateProxyBookings(
|
||||||
|
Guid id,
|
||||||
|
ProxyExperimentBookingBatchRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var studentIds = request.StudentIds.Distinct().ToArray();
|
||||||
|
if (studentIds.Length == 0)
|
||||||
|
return ValidationProblem("请至少选择一名学生。");
|
||||||
|
if (studentIds.Length > 100)
|
||||||
|
return ValidationProblem("单次最多可代选 100 名学生。");
|
||||||
|
|
||||||
|
return await db.ExecuteInRetriableTransactionAsync<ActionResult>(
|
||||||
|
async transaction =>
|
||||||
|
{
|
||||||
|
db.ChangeTracker.Clear();
|
||||||
|
var session = await LoadManagedSessionAsync(id, cancellationToken);
|
||||||
|
if (session is null) return NotFound();
|
||||||
|
if (!CanManageSession(session)) return Forbid();
|
||||||
|
if (session.Status != ExperimentSessionStatus.Scheduled ||
|
||||||
|
session.ExperimentProject!.Status != ExperimentProjectStatus.Published ||
|
||||||
|
session.ExperimentProject.ArrangementMode != ExperimentArrangementMode.SelfScheduled)
|
||||||
|
return ConflictProblem("只能为已发布的自行安排实验场次代选学生。");
|
||||||
|
|
||||||
|
var students = await db.Students
|
||||||
|
.WhereIn(studentIds, x => x.Id)
|
||||||
|
.OrderBy(x => x.StudentNumber)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
if (students.Count != studentIds.Length)
|
||||||
|
return ValidationProblem("存在无效的学生档案。");
|
||||||
|
var inactive = students.FirstOrDefault(x => x.Status != StudentStatus.Active);
|
||||||
|
if (inactive is not null)
|
||||||
|
return ConflictProblem($"学生 {inactive.StudentNumber} {inactive.Name} 当前不是在籍状态。");
|
||||||
|
|
||||||
|
var roster = await TeachingTaskRosterQuery.LoadForTasksAsync(
|
||||||
|
db, [session.ExperimentProject.TeachingTaskId], cancellationToken);
|
||||||
|
var eligibleStudentIds = roster.Select(x => x.StudentId).ToHashSet();
|
||||||
|
var outOfRoster = students.FirstOrDefault(x => !eligibleStudentIds.Contains(x.Id));
|
||||||
|
if (outOfRoster is not null)
|
||||||
|
return ConflictProblem($"学生 {outOfRoster.StudentNumber} {outOfRoster.Name} 不在本实验项目的教学任务名单内。");
|
||||||
|
|
||||||
|
var existingBookings = await db.ExperimentBookings
|
||||||
|
.Where(x => x.ExperimentProjectId == session.ExperimentProjectId)
|
||||||
|
.WhereIn(studentIds, x => x.StudentId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
var booked = existingBookings.FirstOrDefault(x =>
|
||||||
|
x.Status == ExperimentBookingStatus.Booked);
|
||||||
|
if (booked is not null)
|
||||||
|
{
|
||||||
|
var student = students.First(x => x.Id == booked.StudentId);
|
||||||
|
return ConflictProblem(booked.ExperimentSessionId == session.Id
|
||||||
|
? $"学生 {student.StudentNumber} {student.Name} 已预约本实验场次。"
|
||||||
|
: $"学生 {student.StudentNumber} {student.Name} 已预约本实验项目的其他场次。");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var student in students)
|
||||||
|
{
|
||||||
|
var scheduleProblem = await StudentConflictAsync(student.Id, session, cancellationToken);
|
||||||
|
if (scheduleProblem is not null)
|
||||||
|
return ConflictProblem($"学生 {student.StudentNumber} {student.Name}:{scheduleProblem}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var reservedCount = await db.ExperimentBookings.CountAsync(x =>
|
||||||
|
x.ExperimentSessionId == session.Id && x.Status == ExperimentBookingStatus.Booked,
|
||||||
|
cancellationToken);
|
||||||
|
if (reservedCount + students.Count > session.Capacity)
|
||||||
|
return ConflictProblem($"该实验场次仅剩 {Math.Max(0, session.Capacity - reservedCount)} 个名额,无法完成本次代选。");
|
||||||
|
|
||||||
|
foreach (var student in students)
|
||||||
|
{
|
||||||
|
var existing = existingBookings.FirstOrDefault(x => x.StudentId == student.Id);
|
||||||
|
if (existing is null)
|
||||||
|
{
|
||||||
|
db.ExperimentBookings.Add(new ExperimentBooking
|
||||||
|
{
|
||||||
|
ExperimentProjectId = session.ExperimentProjectId,
|
||||||
|
ExperimentSessionId = session.Id,
|
||||||
|
StudentId = student.Id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existing.ExperimentSessionId = session.Id;
|
||||||
|
existing.Status = ExperimentBookingStatus.Booked;
|
||||||
|
existing.BookedAt = DateTime.UtcNow;
|
||||||
|
existing.CancelledAt = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.ReservedCount = reservedCount + students.Count;
|
||||||
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
await transaction.CommitAsync(cancellationToken);
|
||||||
|
return Ok(new { BookedCount = students.Count });
|
||||||
|
},
|
||||||
|
cancellationToken,
|
||||||
|
IsolationLevel.Serializable);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("sessions/{id:guid}/book")]
|
[HttpPost("sessions/{id:guid}/book")]
|
||||||
[Authorize(Roles = SystemRoles.Student)]
|
[Authorize(Roles = SystemRoles.Student)]
|
||||||
public async Task<ActionResult> Book(
|
public async Task<ActionResult> Book(
|
||||||
@@ -1967,6 +2065,9 @@ public sealed record ExperimentSessionRequest(
|
|||||||
|
|
||||||
public sealed record ProxyExperimentBookingRequest(Guid StudentId);
|
public sealed record ProxyExperimentBookingRequest(Guid StudentId);
|
||||||
|
|
||||||
|
public sealed record ProxyExperimentBookingBatchRequest(
|
||||||
|
[Required, MinLength(1)] IReadOnlyCollection<Guid> StudentIds);
|
||||||
|
|
||||||
public sealed record ExperimentSessionBatchRequest(
|
public sealed record ExperimentSessionBatchRequest(
|
||||||
[Required] IReadOnlyList<ExperimentSessionBatchItem> Items);
|
[Required] IReadOnlyList<ExperimentSessionBatchItem> Items);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,50 @@ namespace Jiaowu.Api.Controllers;
|
|||||||
[Route("api/schedules")]
|
[Route("api/schedules")]
|
||||||
public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache) : ControllerBase
|
public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache) : ControllerBase
|
||||||
{
|
{
|
||||||
|
[HttpGet("optimization-settings")]
|
||||||
|
public async Task<ActionResult> GetOptimizationSettings(
|
||||||
|
Guid academicTermId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var settings = await db.ScheduleOptimizationSettings.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(x => x.AcademicTermId == academicTermId, cancellationToken)
|
||||||
|
?? ScheduleOptimizationSettings.CreateDefault(academicTermId);
|
||||||
|
return Ok(ToOptimizationSettingsResponse(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("optimization-settings/{academicTermId:guid}")]
|
||||||
|
public async Task<ActionResult> SaveOptimizationSettings(
|
||||||
|
Guid academicTermId,
|
||||||
|
ScheduleOptimizationSettingsRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!await db.AcademicTerms.AnyAsync(
|
||||||
|
x => x.Id == academicTermId && x.IsEnabled,
|
||||||
|
cancellationToken))
|
||||||
|
return ValidationProblem("学期不存在或已停用。");
|
||||||
|
|
||||||
|
var settings = await db.ScheduleOptimizationSettings
|
||||||
|
.FirstOrDefaultAsync(x => x.AcademicTermId == academicTermId, cancellationToken);
|
||||||
|
if (settings is null)
|
||||||
|
{
|
||||||
|
settings = ScheduleOptimizationSettings.CreateDefault(academicTermId);
|
||||||
|
db.ScheduleOptimizationSettings.Add(settings);
|
||||||
|
}
|
||||||
|
settings.TeacherSameDayWeight = request.TeacherSameDayWeight;
|
||||||
|
settings.ClassSameDayWeight = request.ClassSameDayWeight;
|
||||||
|
settings.ClassGapWeight = request.ClassGapWeight;
|
||||||
|
settings.ConsecutiveClassWeight = request.ConsecutiveClassWeight;
|
||||||
|
settings.EarlyPeriodPenalty = request.EarlyPeriodPenalty;
|
||||||
|
settings.LatePeriodPenalty = request.LatePeriodPenalty;
|
||||||
|
settings.RoomCapacityWasteWeight = request.RoomCapacityWasteWeight;
|
||||||
|
settings.CoursePreferenceWeight = request.CoursePreferenceWeight;
|
||||||
|
settings.GeneticPopulationSize = request.GeneticPopulationSize;
|
||||||
|
settings.GeneticGenerationCount = request.GeneticGenerationCount;
|
||||||
|
settings.GeneticMutationRatePercent = request.GeneticMutationRatePercent;
|
||||||
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
return Ok(ToOptimizationSettingsResponse(settings));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("time-slots")]
|
[HttpGet("time-slots")]
|
||||||
public async Task<ActionResult> GetTimeSlots(
|
public async Task<ActionResult> GetTimeSlots(
|
||||||
Guid academicTermId,
|
Guid academicTermId,
|
||||||
@@ -32,6 +76,21 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
})
|
})
|
||||||
.ToListAsync(cancellationToken));
|
.ToListAsync(cancellationToken));
|
||||||
|
|
||||||
|
private static object ToOptimizationSettingsResponse(ScheduleOptimizationSettings settings) => new
|
||||||
|
{
|
||||||
|
settings.TeacherSameDayWeight,
|
||||||
|
settings.ClassSameDayWeight,
|
||||||
|
settings.ClassGapWeight,
|
||||||
|
settings.ConsecutiveClassWeight,
|
||||||
|
settings.EarlyPeriodPenalty,
|
||||||
|
settings.LatePeriodPenalty,
|
||||||
|
settings.RoomCapacityWasteWeight,
|
||||||
|
settings.CoursePreferenceWeight,
|
||||||
|
settings.GeneticPopulationSize,
|
||||||
|
settings.GeneticGenerationCount,
|
||||||
|
settings.GeneticMutationRatePercent
|
||||||
|
};
|
||||||
|
|
||||||
[HttpPut("time-slots/{academicTermId:guid}")]
|
[HttpPut("time-slots/{academicTermId:guid}")]
|
||||||
public async Task<ActionResult> ReplaceTimeSlots(
|
public async Task<ActionResult> ReplaceTimeSlots(
|
||||||
Guid academicTermId,
|
Guid academicTermId,
|
||||||
@@ -138,6 +197,9 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
AllowedDayOfWeeks = ParseDays(constraint?.AllowedDayOfWeeks),
|
AllowedDayOfWeeks = ParseDays(constraint?.AllowedDayOfWeeks),
|
||||||
constraint?.EarliestPeriod,
|
constraint?.EarliestPeriod,
|
||||||
constraint?.LatestPeriod,
|
constraint?.LatestPeriod,
|
||||||
|
PreferredDayOfWeeks = ParseDays(constraint?.PreferredDayOfWeeks),
|
||||||
|
constraint?.PreferredEarliestPeriod,
|
||||||
|
constraint?.PreferredLatestPeriod,
|
||||||
AllowedClassroomIds = constraint?.AllowedClassrooms
|
AllowedClassroomIds = constraint?.AllowedClassrooms
|
||||||
.Select(x => x.ClassroomId) ?? [],
|
.Select(x => x.ClassroomId) ?? [],
|
||||||
AllowedExperimentClassroomIds = constraint?.AllowedExperimentClassrooms
|
AllowedExperimentClassroomIds = constraint?.AllowedExperimentClassrooms
|
||||||
@@ -159,6 +221,12 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
request.LatestPeriod.HasValue &&
|
request.LatestPeriod.HasValue &&
|
||||||
request.EarliestPeriod > request.LatestPeriod)
|
request.EarliestPeriod > request.LatestPeriod)
|
||||||
return ValidationProblem("最早节次不能晚于最晚节次。");
|
return ValidationProblem("最早节次不能晚于最晚节次。");
|
||||||
|
if (request.PreferredDayOfWeeks?.Any(day => day is < 1 or > 7) == true)
|
||||||
|
return ValidationProblem("偏好上课日必须位于星期一至星期日。");
|
||||||
|
if (request.PreferredEarliestPeriod.HasValue &&
|
||||||
|
request.PreferredLatestPeriod.HasValue &&
|
||||||
|
request.PreferredEarliestPeriod > request.PreferredLatestPeriod)
|
||||||
|
return ValidationProblem("偏好最早节次不能晚于偏好最晚节次。");
|
||||||
if (!Enum.IsDefined(request.SchedulingMode))
|
if (!Enum.IsDefined(request.SchedulingMode))
|
||||||
return ValidationProblem("授课方式无效。");
|
return ValidationProblem("授课方式无效。");
|
||||||
var task = await db.TeachingTasks
|
var task = await db.TeachingTasks
|
||||||
@@ -271,6 +339,11 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
: string.Join(',', request.AllowedDayOfWeeks.Distinct().Order());
|
: string.Join(',', request.AllowedDayOfWeeks.Distinct().Order());
|
||||||
constraint.EarliestPeriod = request.EarliestPeriod;
|
constraint.EarliestPeriod = request.EarliestPeriod;
|
||||||
constraint.LatestPeriod = request.LatestPeriod;
|
constraint.LatestPeriod = request.LatestPeriod;
|
||||||
|
constraint.PreferredDayOfWeeks = request.PreferredDayOfWeeks?.Count > 0
|
||||||
|
? string.Join(',', request.PreferredDayOfWeeks.Distinct().Order())
|
||||||
|
: null;
|
||||||
|
constraint.PreferredEarliestPeriod = request.PreferredEarliestPeriod;
|
||||||
|
constraint.PreferredLatestPeriod = request.PreferredLatestPeriod;
|
||||||
constraint.AllowedExperimentVenueNatures = request.AllowedExperimentVenueNatures;
|
constraint.AllowedExperimentVenueNatures = request.AllowedExperimentVenueNatures;
|
||||||
db.TeachingTaskAllowedClassrooms.RemoveRange(constraint.AllowedClassrooms);
|
db.TeachingTaskAllowedClassrooms.RemoveRange(constraint.AllowedClassrooms);
|
||||||
db.TeachingTaskAllowedExperimentClassrooms.RemoveRange(constraint.AllowedExperimentClassrooms);
|
db.TeachingTaskAllowedExperimentClassrooms.RemoveRange(constraint.AllowedExperimentClassrooms);
|
||||||
@@ -515,6 +588,9 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
constraint.AllowedDayOfWeeks = null;
|
constraint.AllowedDayOfWeeks = null;
|
||||||
constraint.EarliestPeriod = null;
|
constraint.EarliestPeriod = null;
|
||||||
constraint.LatestPeriod = null;
|
constraint.LatestPeriod = null;
|
||||||
|
constraint.PreferredDayOfWeeks = null;
|
||||||
|
constraint.PreferredEarliestPeriod = null;
|
||||||
|
constraint.PreferredLatestPeriod = null;
|
||||||
db.TeachingTaskAllowedClassrooms.RemoveRange(constraint.AllowedClassrooms);
|
db.TeachingTaskAllowedClassrooms.RemoveRange(constraint.AllowedClassrooms);
|
||||||
db.TeachingTaskAllowedExperimentClassrooms.RemoveRange(constraint.AllowedExperimentClassrooms);
|
db.TeachingTaskAllowedExperimentClassrooms.RemoveRange(constraint.AllowedExperimentClassrooms);
|
||||||
constraint.AllowedClassrooms = [];
|
constraint.AllowedClassrooms = [];
|
||||||
@@ -544,6 +620,19 @@ public sealed record ScheduleTimeSlotRequest(
|
|||||||
TimeOnly EndsAt,
|
TimeOnly EndsAt,
|
||||||
bool IsEnabled);
|
bool IsEnabled);
|
||||||
|
|
||||||
|
public sealed record ScheduleOptimizationSettingsRequest(
|
||||||
|
[Range(0, 10_000)] int TeacherSameDayWeight,
|
||||||
|
[Range(0, 10_000)] int ClassSameDayWeight,
|
||||||
|
[Range(0, 10_000)] int ClassGapWeight,
|
||||||
|
[Range(0, 10_000)] int ConsecutiveClassWeight,
|
||||||
|
[Range(0, 10_000)] int EarlyPeriodPenalty,
|
||||||
|
[Range(0, 10_000)] int LatePeriodPenalty,
|
||||||
|
[Range(0, 10_000)] int RoomCapacityWasteWeight,
|
||||||
|
[Range(0, 10_000)] int CoursePreferenceWeight = 180,
|
||||||
|
[Range(12, 64)] int GeneticPopulationSize = 32,
|
||||||
|
[Range(12, 160)] int GeneticGenerationCount = 80,
|
||||||
|
[Range(1, 50)] int GeneticMutationRatePercent = 12);
|
||||||
|
|
||||||
public sealed record TeachingTaskScheduleConstraintRequest(
|
public sealed record TeachingTaskScheduleConstraintRequest(
|
||||||
TeachingTaskSchedulingMode SchedulingMode,
|
TeachingTaskSchedulingMode SchedulingMode,
|
||||||
bool RequiresClassroom,
|
bool RequiresClassroom,
|
||||||
@@ -556,7 +645,10 @@ public sealed record TeachingTaskScheduleConstraintRequest(
|
|||||||
TeachingVenueNature AllowedExperimentVenueNatures = 0,
|
TeachingVenueNature AllowedExperimentVenueNatures = 0,
|
||||||
IReadOnlyList<Guid>? AllowedExperimentClassroomIds = null,
|
IReadOnlyList<Guid>? AllowedExperimentClassroomIds = null,
|
||||||
Guid? ExperimentRequiredCampusId = null,
|
Guid? ExperimentRequiredCampusId = null,
|
||||||
Guid? ExperimentRequiredBuildingId = null);
|
Guid? ExperimentRequiredBuildingId = null,
|
||||||
|
IReadOnlyList<int>? PreferredDayOfWeeks = null,
|
||||||
|
[Range(1, 30)] int? PreferredEarliestPeriod = null,
|
||||||
|
[Range(1, 30)] int? PreferredLatestPeriod = null);
|
||||||
|
|
||||||
public sealed record TeachingTaskScheduleConstraintBatchRequest(
|
public sealed record TeachingTaskScheduleConstraintBatchRequest(
|
||||||
Guid AcademicTermId,
|
Guid AcademicTermId,
|
||||||
|
|||||||
Loaded 3 of 39 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user