优化排课

This commit is contained in:
2026-07-24 19:35:49 +08:00 Unverified
parent 6538b6080a
commit e6d8eb0662
17 changed files with 4073 additions and 6 deletions
@@ -27,6 +27,8 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
public DbSet<TeachingTask> TeachingTasks => Set<TeachingTask>();
public DbSet<TeachingTaskTeacher> TeachingTaskTeachers => Set<TeachingTaskTeacher>();
public DbSet<TeachingTaskClass> TeachingTaskClasses => Set<TeachingTaskClass>();
public DbSet<TeacherCourseApplication> TeacherCourseApplications =>
Set<TeacherCourseApplication>();
public DbSet<SchedulePlan> SchedulePlans => Set<SchedulePlan>();
public DbSet<ScheduleEntry> ScheduleEntries => Set<ScheduleEntry>();
public DbSet<ScheduleTimeSlot> ScheduleTimeSlots => Set<ScheduleTimeSlot>();
@@ -229,6 +231,7 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
{
entity.Property(x => x.TaskNumber).HasMaxLength(40);
entity.Property(x => x.Name).HasMaxLength(120);
entity.Property(x => x.GenerationBatchCode).HasMaxLength(40);
entity.Property(x => x.Notes).HasMaxLength(500);
entity.HasIndex(x => x.TaskNumber).IsUnique();
entity.HasIndex(x => new { x.AcademicTermId, x.Status });
@@ -268,6 +271,35 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
.OnDelete(DeleteBehavior.Restrict);
});
builder.Entity<TeacherCourseApplication>(entity =>
{
entity.Property(x => x.Statement).HasMaxLength(500);
entity.Property(x => x.ReviewComment).HasMaxLength(500);
entity.HasIndex(x => new
{
x.AcademicTermId,
x.TeacherId,
x.CourseId
}).IsUnique();
entity.HasIndex(x => new { x.Status, x.AcademicTermId });
entity.HasOne(x => x.AcademicTerm)
.WithMany()
.HasForeignKey(x => x.AcademicTermId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(x => x.Teacher)
.WithMany()
.HasForeignKey(x => x.TeacherId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(x => x.Course)
.WithMany()
.HasForeignKey(x => x.CourseId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne<ApplicationUser>()
.WithMany()
.HasForeignKey(x => x.ReviewedByUserId)
.OnDelete(DeleteBehavior.SetNull);
});
builder.Entity<SchedulePlan>(entity =>
{
entity.Property(x => x.Name).HasMaxLength(120);
@@ -21,6 +21,8 @@ public sealed class DevelopmentSqliteMigrator(
private const string CourseCategoriesMigration = "20260724_13_course_categories";
private const string SchedulingOptimizationMigration =
"20260724_14_scheduling_optimization";
private const string TeacherCourseApplicationsMigration =
"20260724_15_teacher_course_applications";
public async Task MigrateAsync(CancellationToken cancellationToken = default)
{
@@ -121,6 +123,18 @@ public sealed class DevelopmentSqliteMigrator(
SchedulingOptimizationMigration,
schedulingOptimizationExists ? [] : SchedulingOptimizationStatements,
cancellationToken);
var teacherCourseApplicationsExist = await db.Database
.SqlQueryRaw<int>(
"""
SELECT COUNT(*) AS "Value"
FROM sqlite_master
WHERE type = 'table' AND name = 'TeacherCourseApplications'
""")
.AnyAsync(value => value > 0, cancellationToken);
await ApplyMigrationAsync(
TeacherCourseApplicationsMigration,
teacherCourseApplicationsExist ? [] : TeacherCourseApplicationStatements,
cancellationToken);
}
private async Task ApplyMigrationAsync(
@@ -953,4 +967,59 @@ public sealed class DevelopmentSqliteMigrator(
ON "TeachingTaskAllowedClassrooms" ("ClassroomId");
"""
];
private static readonly string[] TeacherCourseApplicationStatements =
[
"""
ALTER TABLE "TeachingTasks" ADD COLUMN "GenerationBatchCode" TEXT NULL;
""",
"""
CREATE TABLE "TeacherCourseApplications" (
"Id" TEXT NOT NULL CONSTRAINT "PK_TeacherCourseApplications" PRIMARY KEY,
"AcademicTermId" TEXT NOT NULL,
"TeacherId" TEXT NOT NULL,
"CourseId" TEXT NOT NULL,
"Status" INTEGER NOT NULL,
"Statement" TEXT NULL,
"ReviewComment" TEXT NULL,
"SubmittedAt" TEXT NOT NULL,
"ReviewedAt" TEXT NULL,
"ReviewedByUserId" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_TeacherCourseApplications_AcademicTerms"
FOREIGN KEY ("AcademicTermId") REFERENCES "AcademicTerms" ("Id")
ON DELETE RESTRICT,
CONSTRAINT "FK_TeacherCourseApplications_Teachers"
FOREIGN KEY ("TeacherId") REFERENCES "Teachers" ("Id")
ON DELETE CASCADE,
CONSTRAINT "FK_TeacherCourseApplications_Courses"
FOREIGN KEY ("CourseId") REFERENCES "Courses" ("Id")
ON DELETE RESTRICT,
CONSTRAINT "FK_TeacherCourseApplications_ReviewedBy"
FOREIGN KEY ("ReviewedByUserId") REFERENCES "AspNetUsers" ("Id")
ON DELETE SET NULL
);
""",
"""
CREATE UNIQUE INDEX "IX_TeacherCourseApplications_Term_Teacher_Course"
ON "TeacherCourseApplications" ("AcademicTermId", "TeacherId", "CourseId");
""",
"""
CREATE INDEX "IX_TeacherCourseApplications_Status_AcademicTermId"
ON "TeacherCourseApplications" ("Status", "AcademicTermId");
""",
"""
CREATE INDEX "IX_TeacherCourseApplications_TeacherId"
ON "TeacherCourseApplications" ("TeacherId");
""",
"""
CREATE INDEX "IX_TeacherCourseApplications_CourseId"
ON "TeacherCourseApplications" ("CourseId");
""",
"""
CREATE INDEX "IX_TeacherCourseApplications_ReviewedByUserId"
ON "TeacherCourseApplications" ("ReviewedByUserId");
"""
];
}
@@ -0,0 +1,106 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
/// <inheritdoc />
public partial class TeacherCourseApplications : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "GenerationBatchCode",
table: "TeachingTasks",
type: "varchar(40)",
maxLength: 40,
nullable: true);
migrationBuilder.CreateTable(
name: "TeacherCourseApplications",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
AcademicTermId = table.Column<Guid>(type: "char(36)", nullable: false),
TeacherId = table.Column<Guid>(type: "char(36)", nullable: false),
CourseId = table.Column<Guid>(type: "char(36)", nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
Statement = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
ReviewComment = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
SubmittedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
ReviewedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
ReviewedByUserId = table.Column<Guid>(type: "char(36)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TeacherCourseApplications", x => x.Id);
table.ForeignKey(
name: "FK_TeacherCourseApplications_AcademicTerms_AcademicTermId",
column: x => x.AcademicTermId,
principalTable: "AcademicTerms",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_TeacherCourseApplications_AspNetUsers_ReviewedByUserId",
column: x => x.ReviewedByUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_TeacherCourseApplications_Courses_CourseId",
column: x => x.CourseId,
principalTable: "Courses",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_TeacherCourseApplications_Teachers_TeacherId",
column: x => x.TeacherId,
principalTable: "Teachers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_TeacherCourseApplications_AcademicTermId_TeacherId_CourseId",
table: "TeacherCourseApplications",
columns: new[] { "AcademicTermId", "TeacherId", "CourseId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_TeacherCourseApplications_CourseId",
table: "TeacherCourseApplications",
column: "CourseId");
migrationBuilder.CreateIndex(
name: "IX_TeacherCourseApplications_ReviewedByUserId",
table: "TeacherCourseApplications",
column: "ReviewedByUserId");
migrationBuilder.CreateIndex(
name: "IX_TeacherCourseApplications_Status_AcademicTermId",
table: "TeacherCourseApplications",
columns: new[] { "Status", "AcademicTermId" });
migrationBuilder.CreateIndex(
name: "IX_TeacherCourseApplications_TeacherId",
table: "TeacherCourseApplications",
column: "TeacherId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TeacherCourseApplications");
migrationBuilder.DropColumn(
name: "GenerationBatchCode",
table: "TeachingTasks");
}
}
}
@@ -1614,6 +1614,63 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.ToTable("Teachers");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeacherCourseApplication", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("AcademicTermId")
.HasColumnType("char(36)");
b.Property<Guid>("CourseId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("ReviewComment")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<DateTime?>("ReviewedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("ReviewedByUserId")
.HasColumnType("char(36)");
b.Property<string>("Statement")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<DateTime>("SubmittedAt")
.HasColumnType("datetime(6)");
b.Property<Guid>("TeacherId")
.HasColumnType("char(36)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("CourseId");
b.HasIndex("ReviewedByUserId");
b.HasIndex("TeacherId");
b.HasIndex("Status", "AcademicTermId");
b.HasIndex("AcademicTermId", "TeacherId", "CourseId")
.IsUnique();
b.ToTable("TeacherCourseApplications");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTask", b =>
{
b.Property<Guid>("Id")
@@ -1635,6 +1692,10 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.Property<int>("EndWeek")
.HasColumnType("int");
b.Property<string>("GenerationBatchCode")
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(120)
@@ -2464,6 +2525,38 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.Navigation("College");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeacherCourseApplication", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm")
.WithMany()
.HasForeignKey("AcademicTermId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Academic.Course", "Course")
.WithMany()
.HasForeignKey("CourseId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("ReviewedByUserId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("Jiaowu.Api.Domain.Academic.Teacher", "Teacher")
.WithMany()
.HasForeignKey("TeacherId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AcademicTerm");
b.Navigation("Course");
b.Navigation("Teacher");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTask", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm")