已改成“普通排课与实验学时分离”:
普通课表学时 = 总学时 − 实践学时。 自动排课、手工排课、课表发布都会阻止把实践学时重复排入。 全部为实践学时的课程应设为“非排时课程”,再由实验管理安排。 教学任务和排课页面会显示“常规 / 实践学时”。 已有教学任务即使仍保存旧周学时,排课时也会按拆分后的课程学时计算。 无需数据库迁移。
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Jiaowu.Api.Infrastructure.Teaching;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Scheduling;
|
||||
@@ -34,6 +35,7 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
.Include(x => x.Classes)
|
||||
.ThenInclude(x => x.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Students)
|
||||
.Include(x => x.Course)
|
||||
.OrderByDescending(x => x.Classes.Count + x.Teachers.Count)
|
||||
.ThenByDescending(x => x.Capacity)
|
||||
.ThenBy(x => x.TaskNumber)
|
||||
@@ -72,10 +74,43 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
constraints.TryGetValue(task.Id, out var constraint);
|
||||
if (!TeachingTaskHours.TryResolveRegularWeeklyHours(
|
||||
task.Course!,
|
||||
task.StartWeek,
|
||||
task.EndWeek,
|
||||
out var requiredWeeklyHours))
|
||||
{
|
||||
messages.Add(
|
||||
$"{task.TaskNumber} · {task.Name} 的普通排课学时不能按授课周次整除,请调整教学任务周次。");
|
||||
processedTasks++;
|
||||
if (reportProgress is not null)
|
||||
{
|
||||
await reportProgress(
|
||||
new(tasks.Count, processedTasks, created, completedTasks),
|
||||
cancellationToken);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var scheduledHours = entries
|
||||
.Where(x => x.TeachingTaskId == task.Id)
|
||||
.Sum(x => x.PeriodCount);
|
||||
var remainingHours = Math.Max(0, task.WeeklyHours - scheduledHours);
|
||||
if (scheduledHours > requiredWeeklyHours)
|
||||
{
|
||||
messages.Add(
|
||||
$"{task.TaskNumber} · {task.Name} 已安排每周 {scheduledHours} 学时," +
|
||||
$"普通课表只需 {requiredWeeklyHours} 学时;请删除已包含的实践学时。");
|
||||
processedTasks++;
|
||||
if (reportProgress is not null)
|
||||
{
|
||||
await reportProgress(
|
||||
new(tasks.Count, processedTasks, created, completedTasks),
|
||||
cancellationToken);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var remainingHours = requiredWeeklyHours - scheduledHours;
|
||||
if (remainingHours == 0)
|
||||
{
|
||||
completedTasks++;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Infrastructure.Caching;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Jiaowu.Api.Infrastructure.Teaching;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Scheduling;
|
||||
@@ -179,19 +180,68 @@ public sealed class SchedulePlanPublisher(AppDbContext db)
|
||||
x.AcademicTermId == plan.AcademicTermId &&
|
||||
x.Status == TeachingTaskStatus.Published &&
|
||||
x.SchedulingMode == TeachingTaskSchedulingMode.Standard)
|
||||
.Select(x => new { x.Id, x.TaskNumber, x.Name, x.WeeklyHours })
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.TaskNumber,
|
||||
x.Name,
|
||||
x.StartWeek,
|
||||
x.EndWeek,
|
||||
CourseTotalHours = x.Course!.TotalHours,
|
||||
CoursePracticeHours = x.Course.PracticeHours
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
var invalidHours = requiredTasks.FirstOrDefault(task =>
|
||||
!TeachingTaskHours.TryResolveRegularWeeklyHours(
|
||||
task.CourseTotalHours,
|
||||
task.CoursePracticeHours,
|
||||
task.StartWeek,
|
||||
task.EndWeek,
|
||||
out _));
|
||||
if (invalidHours is not null)
|
||||
{
|
||||
throw new SchedulePublishValidationException(
|
||||
$"{invalidHours.TaskNumber} · {invalidHours.Name} 的普通排课学时" +
|
||||
"不能按授课周次整除,请先调整教学任务周次。");
|
||||
}
|
||||
|
||||
var requiredWeeklyHours = requiredTasks
|
||||
.Select(task =>
|
||||
{
|
||||
TeachingTaskHours.TryResolveRegularWeeklyHours(
|
||||
task.CourseTotalHours,
|
||||
task.CoursePracticeHours,
|
||||
task.StartWeek,
|
||||
task.EndWeek,
|
||||
out var hours);
|
||||
return new
|
||||
{
|
||||
Task = task,
|
||||
Hours = hours
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
var scheduledHours = plan.Entries
|
||||
.GroupBy(x => x.TeachingTaskId)
|
||||
.ToDictionary(group => group.Key, group => group.Sum(x => x.PeriodCount));
|
||||
var incomplete = requiredTasks.FirstOrDefault(task =>
|
||||
!scheduledHours.TryGetValue(task.Id, out var hours) ||
|
||||
hours < task.WeeklyHours);
|
||||
var incomplete = requiredWeeklyHours.FirstOrDefault(item =>
|
||||
scheduledHours.GetValueOrDefault(item.Task.Id) < item.Hours);
|
||||
if (incomplete is not null)
|
||||
{
|
||||
throw new SchedulePublishValidationException(
|
||||
$"{incomplete.TaskNumber} · {incomplete.Name} 尚未达到每周 " +
|
||||
$"{incomplete.WeeklyHours} 学时,不能发布。");
|
||||
$"{incomplete.Task.TaskNumber} · {incomplete.Task.Name} 尚未达到每周 " +
|
||||
$"{incomplete.Hours} 个普通排课学时,不能发布。");
|
||||
}
|
||||
|
||||
var excessive = requiredWeeklyHours.FirstOrDefault(item =>
|
||||
scheduledHours.GetValueOrDefault(item.Task.Id) > item.Hours);
|
||||
if (excessive is not null)
|
||||
{
|
||||
var actualHours = scheduledHours.GetValueOrDefault(excessive.Task.Id);
|
||||
throw new SchedulePublishValidationException(
|
||||
$"{excessive.Task.TaskNumber} · {excessive.Task.Name} 已安排每周 " +
|
||||
$"{actualHours} 学时,普通课表应为 {excessive.Hours} 学时;" +
|
||||
"请删除已包含的实践学时后再发布。");
|
||||
}
|
||||
|
||||
await reportProgress(3, "检查教师、行政班和教室冲突", cancellationToken);
|
||||
|
||||
@@ -7,15 +7,75 @@ public static class TeachingTaskHours
|
||||
public static int Calculate(int startWeek, int endWeek, int weeklyHours) =>
|
||||
endWeek < startWeek ? 0 : (endWeek - startWeek + 1) * weeklyHours;
|
||||
|
||||
public static int RegularScheduleHours(Course course) =>
|
||||
RegularScheduleHours(course.TotalHours, course.PracticeHours);
|
||||
|
||||
public static int RegularScheduleHours(int totalHours, int practiceHours) =>
|
||||
Math.Max(0, totalHours - practiceHours);
|
||||
|
||||
public static int TargetHours(
|
||||
Course course,
|
||||
TeachingTaskSchedulingMode schedulingMode) =>
|
||||
schedulingMode == TeachingTaskSchedulingMode.Flexible
|
||||
? course.TotalHours
|
||||
: RegularScheduleHours(course);
|
||||
|
||||
public static bool TryResolveRegularWeeklyHours(
|
||||
Course course,
|
||||
int startWeek,
|
||||
int endWeek,
|
||||
out int weeklyHours) =>
|
||||
TryResolveRegularWeeklyHours(
|
||||
course.TotalHours,
|
||||
course.PracticeHours,
|
||||
startWeek,
|
||||
endWeek,
|
||||
out weeklyHours);
|
||||
|
||||
public static bool TryResolveRegularWeeklyHours(
|
||||
int totalHours,
|
||||
int practiceHours,
|
||||
int startWeek,
|
||||
int endWeek,
|
||||
out int weeklyHours)
|
||||
{
|
||||
weeklyHours = 0;
|
||||
var weekCount = endWeek - startWeek + 1;
|
||||
var regularHours = RegularScheduleHours(totalHours, practiceHours);
|
||||
if (weekCount <= 0 || regularHours % weekCount != 0) return false;
|
||||
|
||||
weeklyHours = regularHours / weekCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string? Validate(
|
||||
Course course,
|
||||
int startWeek,
|
||||
int endWeek,
|
||||
int weeklyHours)
|
||||
int weeklyHours,
|
||||
TeachingTaskSchedulingMode schedulingMode =
|
||||
TeachingTaskSchedulingMode.Standard)
|
||||
{
|
||||
var plannedHours = Calculate(startWeek, endWeek, weeklyHours);
|
||||
return plannedHours == course.TotalHours
|
||||
? null
|
||||
: $"课程“{course.Name}”总学时为 {course.TotalHours};当前第 {startWeek}—{endWeek} 周、每周 {weeklyHours} 学时,共 {plannedHours} 学时。请调整授课周次或周学时。";
|
||||
var targetHours = TargetHours(course, schedulingMode);
|
||||
if (plannedHours == targetHours) return null;
|
||||
|
||||
if (schedulingMode == TeachingTaskSchedulingMode.Standard)
|
||||
{
|
||||
if (targetHours == 0)
|
||||
{
|
||||
return $"课程“{course.Name}”的 {course.TotalHours} 学时均为实践学时," +
|
||||
"无需进入普通课表;请将授课方式设为“非排时课程”,并在实验管理中安排。";
|
||||
}
|
||||
|
||||
return $"课程“{course.Name}”总学时为 {course.TotalHours},其中实践学时 " +
|
||||
$"{course.PracticeHours},普通课表应安排 {targetHours} 学时;当前第 " +
|
||||
$"{startWeek}—{endWeek} 周、每周 {weeklyHours} 学时,共 " +
|
||||
$"{plannedHours} 学时。请调整授课周次或周学时。";
|
||||
}
|
||||
|
||||
return $"课程“{course.Name}”总学时为 {course.TotalHours};当前第 " +
|
||||
$"{startWeek}—{endWeek} 周、每周 {weeklyHours} 学时,共 " +
|
||||
$"{plannedHours} 学时。请调整授课周次或周学时。";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user