已把选课的“年级划分”和限制规则补成完整闭环:
选课批次可指定多个适用年级;留空兼容原有“全部年级”。 新增可选的“最多课程门数”,与原有学分上限并行。 年级、门数限制统一作用于学生可见批次、自主选课、候补、管理员代选和自动递补。 普通候选名单遵守年级及行政班范围;强制选课可绕过业务限制,但仍严格遵守管理员学院权限。 管理页面会展示“2026 级 · 最多 6 门 · 最多 30 学分”,名单和候补列表也增加年级列。 新增 MySQL 迁移:[CourseSelectionGradeLimits.cs](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260726180000_CourseSelectionGradeLimits.cs)。
This commit is contained in:
@@ -21,6 +21,16 @@ public static class CourseSelectionRules
|
||||
public static bool SupportsProxyEnrollment(CourseNature nature) =>
|
||||
nature == CourseNature.GeneralRequired;
|
||||
|
||||
public static bool IsGradeEligible(
|
||||
IEnumerable<int> eligibleGrades,
|
||||
int studentGrade) =>
|
||||
!eligibleGrades.Any() || eligibleGrades.Contains(studentGrade);
|
||||
|
||||
public static bool HasReachedCourseLimit(
|
||||
int? maxCourseCount,
|
||||
int selectedCourseCount) =>
|
||||
maxCourseCount.HasValue && selectedCourseCount >= maxCourseCount.Value;
|
||||
|
||||
public static bool RequiresPublishedSchedule(TeachingTaskSchedulingMode schedulingMode) =>
|
||||
schedulingMode == TeachingTaskSchedulingMode.Standard;
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
Set<SchedulePublishJob>();
|
||||
public DbSet<CourseSelectionRound> CourseSelectionRounds =>
|
||||
Set<CourseSelectionRound>();
|
||||
public DbSet<CourseSelectionRoundGrade> CourseSelectionRoundGrades =>
|
||||
Set<CourseSelectionRoundGrade>();
|
||||
public DbSet<CourseSelectionOffering> CourseSelectionOfferings =>
|
||||
Set<CourseSelectionOffering>();
|
||||
public DbSet<CourseEnrollment> CourseEnrollments => Set<CourseEnrollment>();
|
||||
@@ -477,6 +479,17 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<CourseSelectionRoundGrade>(entity =>
|
||||
{
|
||||
entity.HasIndex(x => new { x.CourseSelectionRoundId, x.Grade })
|
||||
.IsUnique();
|
||||
entity.HasIndex(x => x.Grade);
|
||||
entity.HasOne(x => x.CourseSelectionRound)
|
||||
.WithMany(x => x.EligibleGrades)
|
||||
.HasForeignKey(x => x.CourseSelectionRoundId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
builder.Entity<CourseSelectionOffering>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||
|
||||
+4390
File diff suppressed because it is too large
Load Diff
+65
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CourseSelectionGradeLimits : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "MaxCourseCount",
|
||||
table: "CourseSelectionRounds",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CourseSelectionRoundGrades",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
CourseSelectionRoundId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Grade = table.Column<int>(type: "int", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CourseSelectionRoundGrades", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CourseSelectionRoundGrades_CourseSelectionRounds_CourseSelec~",
|
||||
column: x => x.CourseSelectionRoundId,
|
||||
principalTable: "CourseSelectionRounds",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CourseSelectionRoundGrades_CourseSelectionRoundId_Grade",
|
||||
table: "CourseSelectionRoundGrades",
|
||||
columns: new[] { "CourseSelectionRoundId", "Grade" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CourseSelectionRoundGrades_Grade",
|
||||
table: "CourseSelectionRoundGrades",
|
||||
column: "Grade");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CourseSelectionRoundGrades");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MaxCourseCount",
|
||||
table: "CourseSelectionRounds");
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
-17
@@ -40,13 +40,13 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateOnly>("EndDate")
|
||||
b.Property<DateTime>("EndDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<bool>("IsCurrent")
|
||||
b.Property<bool>("IsArchived")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsArchived")
|
||||
b.Property<bool>("IsCurrent")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
@@ -63,7 +63,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateOnly>("StartDate")
|
||||
b.Property<DateTime>("StartDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
@@ -74,10 +74,10 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.HasIndex("Code")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("IsCurrent");
|
||||
|
||||
b.HasIndex("IsArchived");
|
||||
|
||||
b.HasIndex("IsCurrent");
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("AcademicTerms");
|
||||
@@ -595,7 +595,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<Guid>("ApplicantUserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateOnly?>("CancelDate")
|
||||
b.Property<DateTime?>("CancelDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<int?>("CancelWeek")
|
||||
@@ -640,7 +640,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<Guid?>("SubstituteTeacherId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateOnly?>("TargetDate")
|
||||
b.Property<DateTime?>("TargetDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<Guid>("TeachingTaskId")
|
||||
@@ -743,10 +743,10 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.HasIndex("CourseSelectionOfferingId", "StudentId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("CourseSelectionOfferingId", "Status", "WaitlistedAt");
|
||||
|
||||
b.HasIndex("StudentId", "Status");
|
||||
|
||||
b.HasIndex("CourseSelectionOfferingId", "Status", "WaitlistedAt");
|
||||
|
||||
b.ToTable("CourseEnrollments");
|
||||
});
|
||||
|
||||
@@ -854,6 +854,9 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<DateTime>("EndsAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int?>("MaxCourseCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("MaxCredits")
|
||||
.HasPrecision(6, 1)
|
||||
.HasColumnType("decimal(6,1)");
|
||||
@@ -886,6 +889,34 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.ToTable("CourseSelectionRounds");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionRoundGrade", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("CourseSelectionRoundId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("Grade")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Grade");
|
||||
|
||||
b.HasIndex("CourseSelectionRoundId", "Grade")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CourseSelectionRoundGrades");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSubstitution", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1407,7 +1438,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<DateTime>("EndsAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateOnly>("ExamDate")
|
||||
b.Property<DateTime>("ExamDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<Guid>("ExamPlanId")
|
||||
@@ -2117,7 +2148,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<DateTime>("EndsAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateOnly>("ExamDate")
|
||||
b.Property<DateTime>("ExamDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<Guid>("MakeupExamPlanId")
|
||||
@@ -2391,7 +2422,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<TimeOnly>("EndsAt")
|
||||
b.Property<TimeSpan>("EndsAt")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
@@ -2405,7 +2436,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<int>("PeriodNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<TimeOnly>("StartsAt")
|
||||
b.Property<TimeSpan>("StartsAt")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
@@ -2431,14 +2462,14 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateOnly?>("DateOfBirth")
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<DateOnly>("EnrollmentDate")
|
||||
b.Property<DateTime>("EnrollmentDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<int>("EnrollmentYear")
|
||||
@@ -2559,7 +2590,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<int>("Gender")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateOnly?>("HireDate")
|
||||
b.Property<DateTime?>("HireDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<bool>("IsExternal")
|
||||
@@ -3412,6 +3443,17 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("AcademicTerm");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionRoundGrade", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.CourseSelectionRound", "CourseSelectionRound")
|
||||
.WithMany("EligibleGrades")
|
||||
.HasForeignKey("CourseSelectionRoundId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("CourseSelectionRound");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSubstitution", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Course", "OriginalCourse")
|
||||
@@ -4232,6 +4274,8 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionRound", b =>
|
||||
{
|
||||
b.Navigation("EligibleGrades");
|
||||
|
||||
b.Navigation("Offerings");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user