修复实验排课

This commit is contained in:
biss committed 2026-07-29 09:40:11 +08:00
1 parent 8f56548b54
commit 356c410788
4 files changed
+187 -27

No files matched your search

@@ -3,6 +3,7 @@ using System.Data;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Experiments;
using Jiaowu.Api.Infrastructure.Persistence;
using Jiaowu.Api.Infrastructure.Teaching;
using Jiaowu.Api.Infrastructure.Timetables;
@@ -833,38 +834,38 @@ public sealed class ExperimentsController(
term,
date);
var scheduleConflicts = await db.ScheduleEntries.AsNoTracking()
.Where(x =>
x.SchedulePlan!.Status == SchedulePlanStatus.Published &&
x.SchedulePlan.AcademicTermId == term.Id &&
x.DayOfWeek == dayOfWeek &&
x.StartWeek <= week &&
x.EndWeek >= week &&
x.StartPeriod < startPeriod + periodCount &&
startPeriod < x.StartPeriod + x.PeriodCount &&
(x.TeachingTask!.Teachers.Any(item =>
teacherIds.Contains(item.TeacherId)) ||
x.TeachingTask.Classes.Any(item =>
classIds.Contains(item.AdministrativeClassId))))
var relatedTaskIds = (await CentralizedExperimentConflictQuery
.TaskIdsForTeachers(db, teacherIds)
.ToListAsync(cancellationToken))
.Concat(await CentralizedExperimentConflictQuery
.TaskIdsForClasses(db, classIds)
.ToListAsync(cancellationToken))
.Distinct()
.ToArray();
var scheduleConflicts = await CentralizedExperimentConflictQuery
.ScheduleEntries(
db,
relatedTaskIds,
term.Id,
dayOfWeek,
week,
startPeriod,
periodCount)
.Select(x => new { x.TeachingTaskId, x.WeekPattern })
.ToListAsync(cancellationToken);
if (scheduleConflicts.Any(x =>
FreeClassroomRules.MatchesWeek(x.WeekPattern, week)))
return "集中实验与相关教师或行政班的已发布课表冲突。";
var experimentConflict = await db.ExperimentSessions.AsNoTracking()
.AnyAsync(x =>
x.Status == ExperimentSessionStatus.Scheduled &&
x.SessionDate == date &&
x.StartPeriod < startPeriod + periodCount &&
startPeriod < x.StartPeriod + x.PeriodCount &&
x.ExperimentProject!.ArrangementMode ==
ExperimentArrangementMode.Centralized &&
(x.ExperimentProject.TeachingTask!.Teachers.Any(item =>
teacherIds.Contains(item.TeacherId)) ||
x.ExperimentProject.TeachingTask.Classes.Any(item =>
classIds.Contains(item.AdministrativeClassId))),
cancellationToken);
var experimentConflict = await CentralizedExperimentConflictQuery
.ExperimentSessions(
db,
relatedTaskIds,
date,
startPeriod,
periodCount)
.AnyAsync(cancellationToken);
return experimentConflict
? "集中实验与相关教师或行政班的其他实验安排冲突。"
: null;
@@ -0,0 +1,59 @@
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Infrastructure.Experiments;
internal static class CentralizedExperimentConflictQuery
{
public static IQueryable<Guid> TaskIdsForTeachers(
AppDbContext db,
IReadOnlyCollection<Guid> teacherIds) =>
db.TeachingTaskTeachers.AsNoTracking()
.WhereIn(teacherIds, x => x.TeacherId)
.Select(x => x.TeachingTaskId);
public static IQueryable<Guid> TaskIdsForClasses(
AppDbContext db,
IReadOnlyCollection<Guid> classIds) =>
db.TeachingTaskClasses.AsNoTracking()
.WhereIn(classIds, x => x.AdministrativeClassId)
.Select(x => x.TeachingTaskId);
public static IQueryable<ScheduleEntry> ScheduleEntries(
AppDbContext db,
IReadOnlyCollection<Guid> relatedTaskIds,
Guid academicTermId,
int dayOfWeek,
int week,
int startPeriod,
int periodCount) =>
db.ScheduleEntries.AsNoTracking()
.Where(x =>
x.SchedulePlan!.Status == SchedulePlanStatus.Published &&
x.SchedulePlan.AcademicTermId == academicTermId &&
x.DayOfWeek == dayOfWeek &&
x.StartWeek <= week &&
x.EndWeek >= week &&
x.StartPeriod < startPeriod + periodCount &&
startPeriod < x.StartPeriod + x.PeriodCount)
.WhereIn(relatedTaskIds, x => x.TeachingTaskId);
public static IQueryable<ExperimentSession> ExperimentSessions(
AppDbContext db,
IReadOnlyCollection<Guid> relatedTaskIds,
DateOnly date,
int startPeriod,
int periodCount) =>
db.ExperimentSessions.AsNoTracking()
.Where(x =>
x.Status == ExperimentSessionStatus.Scheduled &&
x.SessionDate == date &&
x.StartPeriod < startPeriod + periodCount &&
startPeriod < x.StartPeriod + x.PeriodCount &&
x.ExperimentProject!.ArrangementMode ==
ExperimentArrangementMode.Centralized)
.WhereIn(
relatedTaskIds,
x => x.ExperimentProject!.TeachingTaskId);
}
@@ -138,6 +138,47 @@ public sealed class ExperimentsControllerTests
Assert.Empty(fixture.Db.ExperimentBookings);
}
[Fact]
public async Task CentralizedSession_RejectsRelatedPublishedTimetableConflict()
{
await using var fixture = await ExperimentFixture.CreateAsync();
var manager = fixture.Controller(fixture.ManagerScope);
await manager.CreateProject(
fixture.ProjectRequest(ExperimentArrangementMode.Centralized),
CancellationToken.None);
var project = await fixture.Db.ExperimentProjects.SingleAsync();
fixture.Db.SchedulePlans.Add(new SchedulePlan
{
AcademicTermId = fixture.Term.Id,
Name = "正式课表",
Version = "V1",
Status = SchedulePlanStatus.Published,
Entries =
[
new ScheduleEntry
{
TeachingTaskId = fixture.Task.Id,
ClassroomId = fixture.SecondClassroom.Id,
DayOfWeek = 1,
StartPeriod = 1,
PeriodCount = 2,
StartWeek = 1,
EndWeek = 18,
WeekPattern = WeekPattern.All
}
]
});
await fixture.Db.SaveChangesAsync();
var result = await manager.CreateSession(
project.Id,
fixture.SessionRequest(1, 2),
CancellationToken.None);
Assert.IsType<ConflictObjectResult>(result);
Assert.Empty(fixture.Db.ExperimentSessions);
}
[Fact]
public async Task StudentTimetable_IncludesCentralizedAndBookedSelfScheduledSessions()
{
Loaded 3 of 4 files, more files were not shown because too many files have changed in this diff. Show more