Merge branch 'codex/shiyan' into master
# Conflicts: # web/package-lock.json
This commit is contained in:
commit
bb2b43b0d9
25 files changed
+21321
-1193
No files matched your search
@@ -491,6 +491,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
x.Building.Campus!.Name,
|
x.Building.Campus!.Name,
|
||||||
x.Capacity,
|
x.Capacity,
|
||||||
x.RoomType,
|
x.RoomType,
|
||||||
|
x.TeachingVenueNature,
|
||||||
x.Equipment,
|
x.Equipment,
|
||||||
x.IsEnabled,
|
x.IsEnabled,
|
||||||
x.SortOrder))
|
x.SortOrder))
|
||||||
@@ -557,6 +558,9 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
BuildingId = request.BuildingId,
|
BuildingId = request.BuildingId,
|
||||||
Capacity = request.Capacity,
|
Capacity = request.Capacity,
|
||||||
RoomType = request.RoomType.Trim(),
|
RoomType = request.RoomType.Trim(),
|
||||||
|
TeachingVenueNature = request.TeachingVenueNature == 0
|
||||||
|
? TeachingVenueNature.GeneralClassroom
|
||||||
|
: request.TeachingVenueNature,
|
||||||
Equipment = request.Equipment?.Trim(),
|
Equipment = request.Equipment?.Trim(),
|
||||||
SortOrder = request.SortOrder,
|
SortOrder = request.SortOrder,
|
||||||
IsEnabled = request.IsEnabled
|
IsEnabled = request.IsEnabled
|
||||||
@@ -577,6 +581,9 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
entity.BuildingId = request.BuildingId;
|
entity.BuildingId = request.BuildingId;
|
||||||
entity.Capacity = request.Capacity;
|
entity.Capacity = request.Capacity;
|
||||||
entity.RoomType = request.RoomType.Trim();
|
entity.RoomType = request.RoomType.Trim();
|
||||||
|
entity.TeachingVenueNature = request.TeachingVenueNature == 0
|
||||||
|
? TeachingVenueNature.GeneralClassroom
|
||||||
|
: request.TeachingVenueNature;
|
||||||
entity.Equipment = request.Equipment?.Trim();
|
entity.Equipment = request.Equipment?.Trim();
|
||||||
await SaveAndInvalidateAsync(cancellationToken);
|
await SaveAndInvalidateAsync(cancellationToken);
|
||||||
return entity;
|
return entity;
|
||||||
@@ -728,6 +735,7 @@ public sealed record ClassroomRequest(
|
|||||||
Guid BuildingId,
|
Guid BuildingId,
|
||||||
[Range(1, 1000)] int Capacity,
|
[Range(1, 1000)] int Capacity,
|
||||||
[Required, MaxLength(40)] string RoomType,
|
[Required, MaxLength(40)] string RoomType,
|
||||||
|
TeachingVenueNature TeachingVenueNature,
|
||||||
[MaxLength(300)] string? Equipment)
|
[MaxLength(300)] string? Equipment)
|
||||||
: CatalogRequest(Code, Name, SortOrder, IsEnabled);
|
: CatalogRequest(Code, Name, SortOrder, IsEnabled);
|
||||||
|
|
||||||
@@ -784,6 +792,7 @@ public sealed record ClassroomListItem(
|
|||||||
string CampusName,
|
string CampusName,
|
||||||
int Capacity,
|
int Capacity,
|
||||||
string RoomType,
|
string RoomType,
|
||||||
|
TeachingVenueNature TeachingVenueNature,
|
||||||
string? Equipment,
|
string? Equipment,
|
||||||
bool IsEnabled,
|
bool IsEnabled,
|
||||||
int SortOrder);
|
int SortOrder);
|
||||||
@@ -27,7 +27,7 @@ public sealed class BaseDataExcelController(AppDbContext db, IAppCache cache) :
|
|||||||
["classes"] = ["编码", "名称", "所属专业编码", "年级", "辅导员工号", "排序", "状态"],
|
["classes"] = ["编码", "名称", "所属专业编码", "年级", "辅导员工号", "排序", "状态"],
|
||||||
["terms"] = ["编码", "名称", "学年", "学期季", "开始日期", "结束日期", "当前学期", "状态"],
|
["terms"] = ["编码", "名称", "学年", "学期季", "开始日期", "结束日期", "当前学期", "状态"],
|
||||||
["buildings"] = ["编码", "名称", "所属校区编码", "排序", "状态"],
|
["buildings"] = ["编码", "名称", "所属校区编码", "排序", "状态"],
|
||||||
["classrooms"] = ["编码", "名称", "所属教学楼编码", "容量", "教室类型", "设备", "排序", "状态"],
|
["classrooms"] = ["编码", "名称", "所属教学楼编码", "容量", "教室类型", "教学场地性质", "设备", "排序", "状态"],
|
||||||
["course-categories"] = ["编码", "名称", "排序", "状态"]
|
["course-categories"] = ["编码", "名称", "排序", "状态"]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,7 +70,10 @@ public sealed class BaseDataExcelController(AppDbContext db, IAppCache cache) :
|
|||||||
IReadOnlyList<ExcelRow> rows;
|
IReadOnlyList<ExcelRow> rows;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
rows = await ExcelWorkbookHelper.ReadAsync(file, headers, cancellationToken);
|
var requiredHeaders = kind.Equals("classrooms", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? headers.Where(x => x != "教学场地性质").ToArray()
|
||||||
|
: headers;
|
||||||
|
rows = await ExcelWorkbookHelper.ReadAsync(file, requiredHeaders, cancellationToken);
|
||||||
}
|
}
|
||||||
catch (InvalidDataException exception)
|
catch (InvalidDataException exception)
|
||||||
{
|
{
|
||||||
@@ -165,6 +168,7 @@ public sealed class BaseDataExcelController(AppDbContext db, IAppCache cache) :
|
|||||||
"classrooms" => (await db.Classrooms.AsNoTracking().Include(x => x.Building)
|
"classrooms" => (await db.Classrooms.AsNoTracking().Include(x => x.Building)
|
||||||
.OrderBy(x => x.Code).ToListAsync(cancellationToken))
|
.OrderBy(x => x.Code).ToListAsync(cancellationToken))
|
||||||
.Select(x => Row(x.Code, x.Name, x.Building!.Code, x.Capacity, x.RoomType,
|
.Select(x => Row(x.Code, x.Name, x.Building!.Code, x.Capacity, x.RoomType,
|
||||||
|
VenueNatureName(x.TeachingVenueNature),
|
||||||
x.Equipment, x.SortOrder, Status(x.IsEnabled))).ToList(),
|
x.Equipment, x.SortOrder, Status(x.IsEnabled))).ToList(),
|
||||||
"course-categories" => (await db.CourseCategories.AsNoTracking()
|
"course-categories" => (await db.CourseCategories.AsNoTracking()
|
||||||
.OrderBy(x => x.SortOrder).ThenBy(x => x.Code)
|
.OrderBy(x => x.SortOrder).ThenBy(x => x.Code)
|
||||||
@@ -473,8 +477,9 @@ public sealed class BaseDataExcelController(AppDbContext db, IAppCache cache) :
|
|||||||
var buildingCode = Required(row, "所属教学楼编码", errors);
|
var buildingCode = Required(row, "所属教学楼编码", errors);
|
||||||
var capacity = ParseInt(row, "容量", 1, 1000, errors);
|
var capacity = ParseInt(row, "容量", 1, 1000, errors);
|
||||||
var roomType = Required(row, "教室类型", errors);
|
var roomType = Required(row, "教室类型", errors);
|
||||||
|
var venueNature = ParseVenueNature(row, roomType, errors);
|
||||||
if (code is null || name is null || buildingCode is null ||
|
if (code is null || name is null || buildingCode is null ||
|
||||||
capacity is null || roomType is null) continue;
|
capacity is null || roomType is null || venueNature is null) continue;
|
||||||
if (!buildings.TryGetValue(buildingCode, out var building))
|
if (!buildings.TryGetValue(buildingCode, out var building))
|
||||||
{
|
{
|
||||||
errors.Add($"第 {row.RowNumber} 行:所属教学楼编码“{buildingCode}”不存在。");
|
errors.Add($"第 {row.RowNumber} 行:所属教学楼编码“{buildingCode}”不存在。");
|
||||||
@@ -488,7 +493,8 @@ public sealed class BaseDataExcelController(AppDbContext db, IAppCache cache) :
|
|||||||
Code = code,
|
Code = code,
|
||||||
Name = name,
|
Name = name,
|
||||||
BuildingId = building.Id,
|
BuildingId = building.Id,
|
||||||
RoomType = roomType
|
RoomType = roomType,
|
||||||
|
TeachingVenueNature = venueNature.Value
|
||||||
};
|
};
|
||||||
db.Classrooms.Add(entity);
|
db.Classrooms.Add(entity);
|
||||||
existing[code] = entity;
|
existing[code] = entity;
|
||||||
@@ -499,6 +505,7 @@ public sealed class BaseDataExcelController(AppDbContext db, IAppCache cache) :
|
|||||||
entity.BuildingId = building.Id;
|
entity.BuildingId = building.Id;
|
||||||
entity.Capacity = capacity.Value;
|
entity.Capacity = capacity.Value;
|
||||||
entity.RoomType = roomType;
|
entity.RoomType = roomType;
|
||||||
|
entity.TeachingVenueNature = venueNature.Value;
|
||||||
entity.Equipment = Optional(row, "设备");
|
entity.Equipment = Optional(row, "设备");
|
||||||
}
|
}
|
||||||
return new(created, updated, rows.Count);
|
return new(created, updated, rows.Count);
|
||||||
@@ -597,6 +604,57 @@ public sealed class BaseDataExcelController(AppDbContext db, IAppCache cache) :
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TeachingVenueNature? ParseVenueNature(
|
||||||
|
ExcelRow row,
|
||||||
|
string? roomType,
|
||||||
|
List<string> errors)
|
||||||
|
{
|
||||||
|
var value = Optional(row, "教学场地性质");
|
||||||
|
if (value is null) return InferVenueNature(roomType ?? string.Empty);
|
||||||
|
var result = (TeachingVenueNature)0;
|
||||||
|
foreach (var part in value.Split(['、', ',', ',', ';', ';'],
|
||||||
|
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
result |= part switch
|
||||||
|
{
|
||||||
|
"普通教室" => TeachingVenueNature.GeneralClassroom,
|
||||||
|
"实验室" => TeachingVenueNature.Laboratory,
|
||||||
|
"实训室" => TeachingVenueNature.TrainingRoom,
|
||||||
|
"计算机机房" or "机房" => TeachingVenueNature.ComputerLab,
|
||||||
|
"语音室" => TeachingVenueNature.LanguageLab,
|
||||||
|
"体育场地" => TeachingVenueNature.SportsVenue,
|
||||||
|
"艺术场地" => TeachingVenueNature.ArtsVenue,
|
||||||
|
_ => (TeachingVenueNature)0
|
||||||
|
};
|
||||||
|
if (part is not ("普通教室" or "实验室" or "实训室" or "计算机机房" or "机房" or "语音室" or "体育场地" or "艺术场地"))
|
||||||
|
errors.Add($"第 {row.RowNumber} 行:“教学场地性质”包含不支持的值“{part}”。");
|
||||||
|
}
|
||||||
|
return result == 0 ? null : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TeachingVenueNature InferVenueNature(string roomType) =>
|
||||||
|
roomType.Contains("机房", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? TeachingVenueNature.Laboratory | TeachingVenueNature.ComputerLab
|
||||||
|
: roomType.Contains("语音", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? TeachingVenueNature.Laboratory | TeachingVenueNature.LanguageLab
|
||||||
|
: roomType.Contains("实训", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? TeachingVenueNature.TrainingRoom
|
||||||
|
: roomType.Contains("实验", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? TeachingVenueNature.Laboratory
|
||||||
|
: TeachingVenueNature.GeneralClassroom;
|
||||||
|
|
||||||
|
private static string VenueNatureName(TeachingVenueNature value) => string.Join("、",
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
(TeachingVenueNature.GeneralClassroom, "普通教室"),
|
||||||
|
(TeachingVenueNature.Laboratory, "实验室"),
|
||||||
|
(TeachingVenueNature.TrainingRoom, "实训室"),
|
||||||
|
(TeachingVenueNature.ComputerLab, "计算机机房"),
|
||||||
|
(TeachingVenueNature.LanguageLab, "语音室"),
|
||||||
|
(TeachingVenueNature.SportsVenue, "体育场地"),
|
||||||
|
(TeachingVenueNature.ArtsVenue, "艺术场地")
|
||||||
|
}.Where(x => (value & x.Item1) != 0).Select(x => x.Item2));
|
||||||
|
|
||||||
private static bool ParseBoolean(
|
private static bool ParseBoolean(
|
||||||
ExcelRow row, string header, bool defaultValue, List<string> errors)
|
ExcelRow row, string header, bool defaultValue, List<string> errors)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -91,6 +91,36 @@ public sealed class ExperimentsController(
|
|||||||
.Select(item => item.AdministrativeClass!.Name)
|
.Select(item => item.AdministrativeClass!.Name)
|
||||||
})
|
})
|
||||||
.ToListAsync(cancellationToken),
|
.ToListAsync(cancellationToken),
|
||||||
|
ScheduleEntries = await db.ScheduleEntries.AsNoTracking()
|
||||||
|
.Where(x => x.SchedulePlan!.Status == SchedulePlanStatus.Published &&
|
||||||
|
x.Kind == ScheduleEntryKind.Experiment &&
|
||||||
|
x.ClassroomId.HasValue &&
|
||||||
|
AccessibleTeachingTasks().Select(task => task.Id)
|
||||||
|
.Contains(x.TeachingTaskId))
|
||||||
|
.Where(x => !academicTermId.HasValue ||
|
||||||
|
x.SchedulePlan!.AcademicTermId == academicTermId.Value)
|
||||||
|
.OrderBy(x => x.TeachingTask!.Course!.Code)
|
||||||
|
.ThenBy(x => x.TeachingTask!.TaskNumber)
|
||||||
|
.ThenBy(x => x.DayOfWeek)
|
||||||
|
.ThenBy(x => x.StartPeriod)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.Id,
|
||||||
|
x.TeachingTaskId,
|
||||||
|
TaskNumber = x.TeachingTask!.TaskNumber,
|
||||||
|
CourseCode = x.TeachingTask.Course!.Code,
|
||||||
|
CourseName = x.TeachingTask.Course.Name,
|
||||||
|
x.DayOfWeek,
|
||||||
|
x.StartPeriod,
|
||||||
|
x.PeriodCount,
|
||||||
|
x.StartWeek,
|
||||||
|
x.EndWeek,
|
||||||
|
x.WeekPattern,
|
||||||
|
ClassroomName = x.Classroom!.Name,
|
||||||
|
BuildingName = x.Classroom.Building!.Name,
|
||||||
|
CampusName = x.Classroom.Building.Campus!.Name
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken),
|
||||||
Classrooms = await db.Classrooms.AsNoTracking()
|
Classrooms = await db.Classrooms.AsNoTracking()
|
||||||
.Where(x => x.IsEnabled)
|
.Where(x => x.IsEnabled)
|
||||||
.OrderBy(x => x.Building!.Campus!.SortOrder)
|
.OrderBy(x => x.Building!.Campus!.SortOrder)
|
||||||
@@ -103,6 +133,7 @@ public sealed class ExperimentsController(
|
|||||||
BuildingName = x.Building!.Name,
|
BuildingName = x.Building!.Name,
|
||||||
CampusName = x.Building.Campus!.Name,
|
CampusName = x.Building.Campus!.Name,
|
||||||
x.Capacity
|
x.Capacity
|
||||||
|
,x.TeachingVenueNature
|
||||||
})
|
})
|
||||||
.ToListAsync(cancellationToken),
|
.ToListAsync(cancellationToken),
|
||||||
Periods = periodItems
|
Periods = periodItems
|
||||||
@@ -135,6 +166,7 @@ public sealed class ExperimentsController(
|
|||||||
{
|
{
|
||||||
x.Id,
|
x.Id,
|
||||||
x.TeachingTaskId,
|
x.TeachingTaskId,
|
||||||
|
x.ScheduleEntryId,
|
||||||
x.Code,
|
x.Code,
|
||||||
x.Name,
|
x.Name,
|
||||||
x.ArrangementMode,
|
x.ArrangementMode,
|
||||||
@@ -157,6 +189,18 @@ public sealed class ExperimentsController(
|
|||||||
ClassNames = x.TeachingTask.Classes
|
ClassNames = x.TeachingTask.Classes
|
||||||
.OrderBy(item => item.AdministrativeClass!.Code)
|
.OrderBy(item => item.AdministrativeClass!.Code)
|
||||||
.Select(item => item.AdministrativeClass!.Name),
|
.Select(item => item.AdministrativeClass!.Name),
|
||||||
|
ScheduleEntry = x.ScheduleEntryId == null ? null : new
|
||||||
|
{
|
||||||
|
x.ScheduleEntry!.DayOfWeek,
|
||||||
|
x.ScheduleEntry.StartPeriod,
|
||||||
|
x.ScheduleEntry.PeriodCount,
|
||||||
|
x.ScheduleEntry.StartWeek,
|
||||||
|
x.ScheduleEntry.EndWeek,
|
||||||
|
x.ScheduleEntry.WeekPattern,
|
||||||
|
ClassroomName = x.ScheduleEntry.Classroom!.Name,
|
||||||
|
BuildingName = x.ScheduleEntry.Classroom.Building!.Name,
|
||||||
|
CampusName = x.ScheduleEntry.Classroom.Building.Campus!.Name
|
||||||
|
},
|
||||||
Sessions = x.Sessions
|
Sessions = x.Sessions
|
||||||
.OrderBy(item => item.SessionDate)
|
.OrderBy(item => item.SessionDate)
|
||||||
.ThenBy(item => item.StartPeriod)
|
.ThenBy(item => item.StartPeriod)
|
||||||
@@ -209,6 +253,7 @@ public sealed class ExperimentsController(
|
|||||||
x.Code,
|
x.Code,
|
||||||
x.Name,
|
x.Name,
|
||||||
x.ArrangementMode,
|
x.ArrangementMode,
|
||||||
|
x.ScheduleEntryId,
|
||||||
x.Description,
|
x.Description,
|
||||||
x.Requirements,
|
x.Requirements,
|
||||||
x.StartDate,
|
x.StartDate,
|
||||||
@@ -222,6 +267,18 @@ public sealed class ExperimentsController(
|
|||||||
TeacherNames = x.TeachingTask.Teachers
|
TeacherNames = x.TeachingTask.Teachers
|
||||||
.OrderByDescending(item => item.IsPrimary)
|
.OrderByDescending(item => item.IsPrimary)
|
||||||
.Select(item => item.Teacher!.Name),
|
.Select(item => item.Teacher!.Name),
|
||||||
|
ScheduleEntry = x.ScheduleEntryId == null ? null : new
|
||||||
|
{
|
||||||
|
x.ScheduleEntry!.DayOfWeek,
|
||||||
|
x.ScheduleEntry.StartPeriod,
|
||||||
|
x.ScheduleEntry.PeriodCount,
|
||||||
|
x.ScheduleEntry.StartWeek,
|
||||||
|
x.ScheduleEntry.EndWeek,
|
||||||
|
x.ScheduleEntry.WeekPattern,
|
||||||
|
ClassroomName = x.ScheduleEntry.Classroom!.Name,
|
||||||
|
BuildingName = x.ScheduleEntry.Classroom.Building!.Name,
|
||||||
|
CampusName = x.ScheduleEntry.Classroom.Building.Campus!.Name
|
||||||
|
},
|
||||||
Sessions = x.Sessions
|
Sessions = x.Sessions
|
||||||
.Where(item => item.Status == ExperimentSessionStatus.Scheduled)
|
.Where(item => item.Status == ExperimentSessionStatus.Scheduled)
|
||||||
.OrderBy(item => item.SessionDate)
|
.OrderBy(item => item.SessionDate)
|
||||||
@@ -272,6 +329,9 @@ public sealed class ExperimentsController(
|
|||||||
|
|
||||||
var problem = ValidateProjectRequest(request, task.AcademicTerm!);
|
var problem = ValidateProjectRequest(request, task.AcademicTerm!);
|
||||||
if (problem is not null) return ValidationProblem(problem);
|
if (problem is not null) return ValidationProblem(problem);
|
||||||
|
var scheduleEntry = await ValidateScheduleEntryAsync(
|
||||||
|
request, task.Id, cancellationToken);
|
||||||
|
if (scheduleEntry.Problem is not null) return ValidationProblem(scheduleEntry.Problem);
|
||||||
|
|
||||||
var code = request.Code.Trim();
|
var code = request.Code.Trim();
|
||||||
if (await db.ExperimentProjects.AnyAsync(x =>
|
if (await db.ExperimentProjects.AnyAsync(x =>
|
||||||
@@ -283,6 +343,7 @@ public sealed class ExperimentsController(
|
|||||||
var project = new ExperimentProject
|
var project = new ExperimentProject
|
||||||
{
|
{
|
||||||
TeachingTaskId = request.TeachingTaskId,
|
TeachingTaskId = request.TeachingTaskId,
|
||||||
|
ScheduleEntryId = scheduleEntry.Entry?.Id,
|
||||||
Code = code,
|
Code = code,
|
||||||
Name = request.Name.Trim(),
|
Name = request.Name.Trim(),
|
||||||
ArrangementMode = request.ArrangementMode,
|
ArrangementMode = request.ArrangementMode,
|
||||||
@@ -302,6 +363,8 @@ public sealed class ExperimentsController(
|
|||||||
ExperimentProjectBatchRequest request,
|
ExperimentProjectBatchRequest request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (request.ArrangementMode == ExperimentArrangementMode.Centralized)
|
||||||
|
return ValidationProblem("集中安排的实验项目请逐项绑定已发布课表中的实验课。");
|
||||||
var taskIds = request.TeachingTaskIds
|
var taskIds = request.TeachingTaskIds
|
||||||
.Where(x => x != Guid.Empty)
|
.Where(x => x != Guid.Empty)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
@@ -387,6 +450,9 @@ public sealed class ExperimentsController(
|
|||||||
request,
|
request,
|
||||||
project.TeachingTask!.AcademicTerm!);
|
project.TeachingTask!.AcademicTerm!);
|
||||||
if (problem is not null) return ValidationProblem(problem);
|
if (problem is not null) return ValidationProblem(problem);
|
||||||
|
var scheduleEntry = await ValidateScheduleEntryAsync(
|
||||||
|
request, project.TeachingTaskId, cancellationToken);
|
||||||
|
if (scheduleEntry.Problem is not null) return ValidationProblem(scheduleEntry.Problem);
|
||||||
|
|
||||||
var code = request.Code.Trim();
|
var code = request.Code.Trim();
|
||||||
if (await db.ExperimentProjects.AnyAsync(x =>
|
if (await db.ExperimentProjects.AnyAsync(x =>
|
||||||
@@ -399,6 +465,7 @@ public sealed class ExperimentsController(
|
|||||||
project.Code = code;
|
project.Code = code;
|
||||||
project.Name = request.Name.Trim();
|
project.Name = request.Name.Trim();
|
||||||
project.ArrangementMode = request.ArrangementMode;
|
project.ArrangementMode = request.ArrangementMode;
|
||||||
|
project.ScheduleEntryId = scheduleEntry.Entry?.Id;
|
||||||
project.Description = Normalize(request.Description);
|
project.Description = Normalize(request.Description);
|
||||||
project.Requirements = Normalize(request.Requirements);
|
project.Requirements = Normalize(request.Requirements);
|
||||||
project.StartDate = request.StartDate;
|
project.StartDate = request.StartDate;
|
||||||
@@ -438,9 +505,14 @@ public sealed class ExperimentsController(
|
|||||||
if (project is null) return NotFound();
|
if (project is null) return NotFound();
|
||||||
if (project.Status != ExperimentProjectStatus.Draft)
|
if (project.Status != ExperimentProjectStatus.Draft)
|
||||||
return ConflictProblem("只有草稿实验项目可以发布。");
|
return ConflictProblem("只有草稿实验项目可以发布。");
|
||||||
if (!project.Sessions.Any(x =>
|
var hasSchedule = project.ArrangementMode == ExperimentArrangementMode.Centralized
|
||||||
x.Status == ExperimentSessionStatus.Scheduled))
|
? project.ScheduleEntryId.HasValue || project.Sessions.Any(x =>
|
||||||
return ConflictProblem("请至少安排一个有效实验场次后再发布。");
|
x.Status == ExperimentSessionStatus.Scheduled)
|
||||||
|
: project.Sessions.Any(x => x.Status == ExperimentSessionStatus.Scheduled);
|
||||||
|
if (!hasSchedule)
|
||||||
|
return ConflictProblem(project.ArrangementMode == ExperimentArrangementMode.Centralized
|
||||||
|
? "请先绑定已发布课表中的实验课后再发布。"
|
||||||
|
: "请至少安排一个有效实验场次后再发布。");
|
||||||
if (project.Sessions.Any(x =>
|
if (project.Sessions.Any(x =>
|
||||||
x.Status == ExperimentSessionStatus.Scheduled &&
|
x.Status == ExperimentSessionStatus.Scheduled &&
|
||||||
(x.SessionDate < project.StartDate ||
|
(x.SessionDate < project.StartDate ||
|
||||||
@@ -506,6 +578,8 @@ public sealed class ExperimentsController(
|
|||||||
if (project is null) return NotFound();
|
if (project is null) return NotFound();
|
||||||
if (project.Status == ExperimentProjectStatus.Closed)
|
if (project.Status == ExperimentProjectStatus.Closed)
|
||||||
return ConflictProblem("已关闭实验项目不能再增加场次。");
|
return ConflictProblem("已关闭实验项目不能再增加场次。");
|
||||||
|
if (project.ArrangementMode == ExperimentArrangementMode.Centralized)
|
||||||
|
return ConflictProblem("集中安排的实验项目直接使用已发布课表中的实验课,不能在此重复排时派地点。");
|
||||||
|
|
||||||
var problem = await ValidateSessionAsync(
|
var problem = await ValidateSessionAsync(
|
||||||
project,
|
project,
|
||||||
@@ -588,6 +662,9 @@ public sealed class ExperimentsController(
|
|||||||
if (project.Status == ExperimentProjectStatus.Closed)
|
if (project.Status == ExperimentProjectStatus.Closed)
|
||||||
return ConflictProblem(
|
return ConflictProblem(
|
||||||
$"实验项目“{project.Name}”已关闭,不能再增加场次。");
|
$"实验项目“{project.Name}”已关闭,不能再增加场次。");
|
||||||
|
if (project.ArrangementMode == ExperimentArrangementMode.Centralized)
|
||||||
|
return ConflictProblem(
|
||||||
|
$"实验项目“{project.Name}”为集中安排,请直接使用已发布课表中的实验课。");
|
||||||
|
|
||||||
var sessionRequest = item.ToSessionRequest();
|
var sessionRequest = item.ToSessionRequest();
|
||||||
var problem = await ValidateSessionAsync(
|
var problem = await ValidateSessionAsync(
|
||||||
@@ -930,6 +1007,8 @@ public sealed class ExperimentsController(
|
|||||||
x.Id == request.ClassroomId && x.IsEnabled,
|
x.Id == request.ClassroomId && x.IsEnabled,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
if (classroom is null) return "实验教室不存在或已停用。";
|
if (classroom is null) return "实验教室不存在或已停用。";
|
||||||
|
if (!TeachingVenueNatureRules.SupportsExperiment(classroom.TeachingVenueNature))
|
||||||
|
return "所选场地未标注实验教学性质。";
|
||||||
if (request.Capacity > classroom.Capacity)
|
if (request.Capacity > classroom.Capacity)
|
||||||
return $"场次容量不能超过教室容量 {classroom.Capacity} 人。";
|
return $"场次容量不能超过教室容量 {classroom.Capacity} 人。";
|
||||||
if (project.ArrangementMode ==
|
if (project.ArrangementMode ==
|
||||||
@@ -1173,6 +1252,30 @@ public sealed class ExperimentsController(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<(ScheduleEntry? Entry, string? Problem)> ValidateScheduleEntryAsync(
|
||||||
|
ExperimentProjectRequest request,
|
||||||
|
Guid teachingTaskId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (request.ArrangementMode == ExperimentArrangementMode.SelfScheduled)
|
||||||
|
return request.ScheduleEntryId.HasValue
|
||||||
|
? (null, "自行安排的实验项目不能绑定课表实验课。")
|
||||||
|
: (null, null);
|
||||||
|
if (!request.ScheduleEntryId.HasValue)
|
||||||
|
return (null, "集中安排的实验项目必须绑定已发布课表中的实验课。");
|
||||||
|
|
||||||
|
var entry = await db.ScheduleEntries
|
||||||
|
.Include(x => x.SchedulePlan)
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == request.ScheduleEntryId, cancellationToken);
|
||||||
|
if (entry is null || entry.SchedulePlan!.Status != SchedulePlanStatus.Published ||
|
||||||
|
entry.Kind != ScheduleEntryKind.Experiment || !entry.ClassroomId.HasValue ||
|
||||||
|
entry.TeachingTaskId != teachingTaskId)
|
||||||
|
return (null, "只能绑定本教学任务已发布、已安排实验室的实验课。");
|
||||||
|
if (!await AccessibleTeachingTasks().AnyAsync(x => x.Id == teachingTaskId, cancellationToken))
|
||||||
|
return (null, "该教学任务不在当前管理范围内。");
|
||||||
|
return (entry, null);
|
||||||
|
}
|
||||||
|
|
||||||
private static string? Normalize(string? value) =>
|
private static string? Normalize(string? value) =>
|
||||||
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||||
|
|
||||||
@@ -1193,7 +1296,8 @@ public sealed record ExperimentProjectRequest(
|
|||||||
[MaxLength(1000)] string? Description,
|
[MaxLength(1000)] string? Description,
|
||||||
[MaxLength(1000)] string? Requirements,
|
[MaxLength(1000)] string? Requirements,
|
||||||
DateOnly StartDate,
|
DateOnly StartDate,
|
||||||
DateOnly EndDate);
|
DateOnly EndDate,
|
||||||
|
Guid? ScheduleEntryId = null);
|
||||||
|
|
||||||
public sealed record ExperimentProjectBatchRequest(
|
public sealed record ExperimentProjectBatchRequest(
|
||||||
[Required] IReadOnlyList<Guid> TeachingTaskIds,
|
[Required] IReadOnlyList<Guid> TeachingTaskIds,
|
||||||
|
|||||||
Loaded 3 of 25 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user