已把选课的“年级划分”和限制规则补成完整闭环:

选课批次可指定多个适用年级;留空兼容原有“全部年级”。
新增可选的“最多课程门数”,与原有学分上限并行。
年级、门数限制统一作用于学生可见批次、自主选课、候补、管理员代选和自动递补。
普通候选名单遵守年级及行政班范围;强制选课可绕过业务限制,但仍严格遵守管理员学院权限。
管理页面会展示“2026 级 · 最多 6 门 · 最多 30 学分”,名单和候补列表也增加年级列。
新增 MySQL 迁移:[CourseSelectionGradeLimits.cs](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260726180000_CourseSelectionGradeLimits.cs)。
This commit is contained in:
2026-07-26 16:12:21 +08:00 Unverified
parent 56b13b3adb
commit f62cd21319
11 changed files with 4920 additions and 25 deletions
@@ -46,7 +46,24 @@ public sealed class CourseSelectionsController(
if (academicTermId.HasValue)
source = source.Where(x => x.AcademicTermId == academicTermId);
if (currentUserDataScope.Current.IsInRole(SystemRoles.Student))
{
var userId = currentUserDataScope.Current.UserId;
var studentGrade = await db.Students.AsNoTracking()
.Where(x => x.UserId == userId)
.Select(x => (int?)x.AdministrativeClass!.Grade)
.FirstOrDefaultAsync(cancellationToken);
source = source.Where(x => x.Status != CourseSelectionRoundStatus.Draft);
if (studentGrade.HasValue)
{
source = source.Where(x =>
!x.EligibleGrades.Any() ||
x.EligibleGrades.Any(item => item.Grade == studentGrade.Value));
}
else
{
source = source.Where(_ => false);
}
}
var now = DateTime.UtcNow;
return Ok(await source
@@ -64,6 +81,10 @@ public sealed class CourseSelectionsController(
x.EndsAt,
x.WithdrawalEndsAt,
x.MaxCredits,
x.MaxCourseCount,
EligibleGrades = x.EligibleGrades
.OrderBy(item => item.Grade)
.Select(item => item.Grade),
x.Status,
IsAvailableNow =
x.Status == CourseSelectionRoundStatus.Open &&
@@ -76,6 +97,20 @@ public sealed class CourseSelectionsController(
.ToListAsync(cancellationToken));
}
[HttpGet("configuration-options")]
[Authorize(Roles = RoundManagers)]
public async Task<ActionResult> GetConfigurationOptions(
CancellationToken cancellationToken)
{
var grades = await db.AdministrativeClasses.AsNoTracking()
.Where(x => x.IsEnabled)
.Select(x => x.Grade)
.Distinct()
.OrderByDescending(x => x)
.ToListAsync(cancellationToken);
return Ok(new { Grades = grades });
}
[HttpPost("rounds")]
[Authorize(Roles = RoundManagers)]
public async Task<ActionResult> CreateRound(
@@ -92,6 +127,10 @@ public sealed class CourseSelectionsController(
EndsAt = request.EndsAt.ToUniversalTime(),
WithdrawalEndsAt = request.WithdrawalEndsAt.ToUniversalTime(),
MaxCredits = request.MaxCredits,
MaxCourseCount = request.MaxCourseCount,
EligibleGrades = NormalizeGrades(request.EligibleGrades)
.Select(grade => new CourseSelectionRoundGrade { Grade = grade })
.ToList(),
Notes = Normalize(request.Notes)
};
db.CourseSelectionRounds.Add(round);
@@ -105,7 +144,9 @@ public sealed class CourseSelectionsController(
CourseSelectionRoundRequest request,
CancellationToken cancellationToken)
{
var round = await db.CourseSelectionRounds.FindAsync([id], cancellationToken);
var round = await db.CourseSelectionRounds
.Include(x => x.EligibleGrades)
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
if (round is null) return NotFound();
if (round.Status != CourseSelectionRoundStatus.Draft)
return ConflictProblem("只有草稿选课批次可以修改。");
@@ -117,6 +158,20 @@ public sealed class CourseSelectionsController(
round.EndsAt = request.EndsAt.ToUniversalTime();
round.WithdrawalEndsAt = request.WithdrawalEndsAt.ToUniversalTime();
round.MaxCredits = request.MaxCredits;
round.MaxCourseCount = request.MaxCourseCount;
var requestedGrades = NormalizeGrades(request.EligibleGrades);
var requestedGradeSet = requestedGrades.ToHashSet();
db.CourseSelectionRoundGrades.RemoveRange(
round.EligibleGrades.Where(x => !requestedGradeSet.Contains(x.Grade)));
foreach (var grade in requestedGrades.Where(grade =>
round.EligibleGrades.All(x => x.Grade != grade)))
{
round.EligibleGrades.Add(new CourseSelectionRoundGrade
{
CourseSelectionRoundId = round.Id,
Grade = grade
});
}
round.Notes = Normalize(request.Notes);
return await SaveAsync(id, false, cancellationToken);
}
@@ -356,6 +411,7 @@ public sealed class CourseSelectionsController(
x.Student.Name,
ClassName = x.Student.AdministrativeClass!.Name,
MajorName = x.Student.AdministrativeClass.Major!.Name,
Grade = x.Student.AdministrativeClass.Grade,
x.EnrolledAt
})
.ToListAsync(cancellationToken);
@@ -373,6 +429,7 @@ public sealed class CourseSelectionsController(
x.Student.Name,
ClassName = x.Student.AdministrativeClass!.Name,
MajorName = x.Student.AdministrativeClass.Major!.Name,
Grade = x.Student.AdministrativeClass.Grade,
x.WaitlistedAt
})
.ToListAsync(cancellationToken);
@@ -385,6 +442,7 @@ public sealed class CourseSelectionsController(
item.Name,
item.ClassName,
item.MajorName,
item.Grade,
item.WaitlistedAt,
Position = index + 1
})
@@ -435,12 +493,17 @@ public sealed class CourseSelectionsController(
x.Id,
x.IsOpenToAll,
CourseNature = x.TeachingTask!.Course!.Nature,
CollegeId = x.TeachingTask.Course.CollegeId,
RoundStatus = x.CourseSelectionRound!.Status,
EligibleGrades = x.CourseSelectionRound.EligibleGrades
.Select(item => item.Grade),
ClassIds = x.TeachingTask.Classes
.Select(item => item.AdministrativeClassId)
})
.FirstOrDefaultAsync(cancellationToken);
if (offering is null) return NotFound();
if (!currentUserDataScope.Current.CanAccessCollege(offering.CollegeId))
return Forbid();
if (!forceMode && !CourseSelectionRules.SupportsProxyEnrollment(offering.CourseNature))
return ConflictProblem("管理员代选仅适用于公共必修课。");
if (offering.RoundStatus == CourseSelectionRoundStatus.Draft)
@@ -453,7 +516,13 @@ public sealed class CourseSelectionsController(
enrollment.CourseSelectionOfferingId == id &&
enrollment.StudentId == x.Id &&
enrollment.Status == CourseEnrollmentStatus.Enrolled));
if (!offering.IsOpenToAll)
if (!forceMode && offering.EligibleGrades.Any())
{
source = source.WhereIn(
offering.EligibleGrades,
x => x.AdministrativeClass!.Grade);
}
if (!forceMode && !offering.IsOpenToAll)
source = source.WhereIn(offering.ClassIds, x => x.AdministrativeClassId);
if (!string.IsNullOrWhiteSpace(keyword))
{
@@ -475,6 +544,7 @@ public sealed class CourseSelectionsController(
x.StudentNumber,
x.Name,
ClassName = x.AdministrativeClass!.Name,
Grade = x.AdministrativeClass.Grade,
MajorName = x.AdministrativeClass.Major!.Name,
CollegeName = x.AdministrativeClass.Major.College!.Name
})
@@ -501,6 +571,7 @@ public sealed class CourseSelectionsController(
db.ChangeTracker.Clear();
var offering = await db.CourseSelectionOfferings
.Include(x => x.CourseSelectionRound)
.ThenInclude(x => x!.EligibleGrades)
.Include(x => x.TeachingTask)
.ThenInclude(x => x!.Course)
.Include(x => x.TeachingTask)
@@ -526,6 +597,18 @@ public sealed class CourseSelectionsController(
var inactive = students.FirstOrDefault(x => x.Status != StudentStatus.Active);
if (inactive is not null)
return ConflictProblem($"学生 {inactive.StudentNumber} {inactive.Name} 当前不是在籍状态。");
var eligibleGrades = round.EligibleGrades
.Select(x => x.Grade)
.ToArray();
var wrongGrade = students.FirstOrDefault(student =>
!CourseSelectionRules.IsGradeEligible(
eligibleGrades,
student.AdministrativeClass!.Grade));
if (wrongGrade is not null)
{
return ConflictProblem(
$"学生 {wrongGrade.StudentNumber} {wrongGrade.Name} 所在年级不属于本轮选课对象。");
}
var outOfScope = students.FirstOrDefault(student =>
!offering.IsOpenToAll &&
!task.Classes.Any(item =>
@@ -600,6 +683,20 @@ public sealed class CourseSelectionsController(
return ConflictProblem(
$"学生 {student.StudentNumber} {student.Name} 代选后将超过本轮 {round.MaxCredits:0.#} 学分上限。");
}
var selectedCourseCount = await db.CourseEnrollments.CountAsync(
x =>
x.StudentId == student.Id &&
x.Status == CourseEnrollmentStatus.Enrolled &&
x.CourseSelectionOffering!.CourseSelectionRoundId == round.Id,
cancellationToken);
if (CourseSelectionRules.HasReachedCourseLimit(
round.MaxCourseCount,
selectedCourseCount))
{
return ConflictProblem(
$"学生 {student.StudentNumber} {student.Name} 已达到本轮最多 " +
$"{round.MaxCourseCount} 门课程限制。");
}
var selectedTaskIds = await db.CourseEnrollments.AsNoTracking()
.Where(x =>
@@ -685,6 +782,8 @@ public sealed class CourseSelectionsController(
if (offering is null) return NotFound();
var round = offering.CourseSelectionRound!;
var task = offering.TeachingTask!;
if (!currentUserDataScope.Current.CanAccessCollege(task.Course!.CollegeId))
return Forbid();
if (round.Status == CourseSelectionRoundStatus.Draft)
return ConflictProblem("选课批次开放后才能办理强制选课。");
@@ -769,6 +868,7 @@ public sealed class CourseSelectionsController(
var enrollment = await db.CourseEnrollments
.Include(x => x.CourseSelectionOffering)
.ThenInclude(x => x!.CourseSelectionRound)
.ThenInclude(x => x!.EligibleGrades)
.Include(x => x.CourseSelectionOffering)
.ThenInclude(x => x!.TeachingTask)
.ThenInclude(x => x!.Course)
@@ -860,9 +960,19 @@ public sealed class CourseSelectionsController(
var student = await CurrentStudentAsync(cancellationToken);
if (student is null) return ProfileNotFound();
var round = await db.CourseSelectionRounds.AsNoTracking()
.Include(x => x.EligibleGrades)
.FirstOrDefaultAsync(x => x.Id == roundId, cancellationToken);
if (round is null || round.Status == CourseSelectionRoundStatus.Draft)
return NotFound();
var eligibleGrades = round.EligibleGrades
.Select(x => x.Grade)
.ToArray();
if (!CourseSelectionRules.IsGradeEligible(
eligibleGrades,
student.AdministrativeClass!.Grade))
{
return Forbid();
}
var offerings = await db.CourseSelectionOfferings.AsNoTracking()
.Where(x =>
@@ -968,6 +1078,8 @@ public sealed class CourseSelectionsController(
round.EndsAt,
round.WithdrawalEndsAt,
round.MaxCredits,
round.MaxCourseCount,
EligibleGrades = eligibleGrades,
round.Status,
IsAvailableNow = CourseSelectionRules.IsSelectionOpen(
round,
@@ -978,7 +1090,8 @@ public sealed class CourseSelectionsController(
student.Id,
student.StudentNumber,
student.Name,
ClassName = student.AdministrativeClass!.Name
ClassName = student.AdministrativeClass!.Name,
Grade = student.AdministrativeClass.Grade
},
Offerings = offerings
});
@@ -1050,6 +1163,7 @@ public sealed class CourseSelectionsController(
var offering = await db.CourseSelectionOfferings
.Include(x => x.CourseSelectionRound)
.ThenInclude(x => x!.EligibleGrades)
.Include(x => x.TeachingTask)
.ThenInclude(x => x!.Course)
.Include(x => x.TeachingTask)
@@ -1063,6 +1177,12 @@ public sealed class CourseSelectionsController(
return ConflictProblem("当前不在该选课批次的开放时间内。");
if (task.Status != TeachingTaskStatus.Published)
return ConflictProblem("该教学班当前不可选。");
if (!CourseSelectionRules.IsGradeEligible(
round.EligibleGrades.Select(x => x.Grade),
student.AdministrativeClass!.Grade))
{
return ConflictProblem("你所在的年级不属于本轮选课对象。");
}
if (!offering.IsOpenToAll &&
!task.Classes.Any(x =>
x.AdministrativeClassId == student.AdministrativeClassId))
@@ -1128,6 +1248,19 @@ public sealed class CourseSelectionsController(
$"选课后将达到 {selectedCredits + task.Course.Credits:0.#} 学分," +
$"超过本轮 {round.MaxCredits:0.#} 学分上限。");
}
var selectedCourseCount = await db.CourseEnrollments.CountAsync(
x =>
x.StudentId == student.Id &&
x.Status == CourseEnrollmentStatus.Enrolled &&
x.CourseSelectionOffering!.CourseSelectionRoundId == round.Id,
cancellationToken);
if (CourseSelectionRules.HasReachedCourseLimit(
round.MaxCourseCount,
selectedCourseCount))
{
return ConflictProblem(
$"你已达到本轮最多 {round.MaxCourseCount} 门课程限制。");
}
// Schedule conflict check
var candidateEntries = await PublishedScheduleEntries(
@@ -1209,6 +1342,7 @@ public sealed class CourseSelectionsController(
var offering = await db.CourseSelectionOfferings
.Include(x => x.CourseSelectionRound)
.ThenInclude(x => x!.EligibleGrades)
.Include(x => x.TeachingTask)
.ThenInclude(x => x!.Course)
.Include(x => x.TeachingTask)
@@ -1317,6 +1451,7 @@ public sealed class CourseSelectionsController(
var enrollment = await db.CourseEnrollments
.Include(x => x.CourseSelectionOffering)
.ThenInclude(x => x!.CourseSelectionRound)
.ThenInclude(x => x!.EligibleGrades)
.Include(x => x.CourseSelectionOffering)
.ThenInclude(x => x!.TeachingTask)
.ThenInclude(x => x!.Course)
@@ -1504,6 +1639,11 @@ public sealed class CourseSelectionsController(
return ValidationProblem("选课开始时间必须早于结束时间。");
if (withdrawalEndsAt < endsAt)
return ValidationProblem("退课截止时间不能早于选课结束时间。");
var eligibleGrades = NormalizeGrades(request.EligibleGrades);
if (eligibleGrades.Length > 20)
return ValidationProblem("单个选课批次最多配置 20 个适用年级。");
if (eligibleGrades.Any(grade => grade is < 2000 or > 2200))
return ValidationProblem("适用年级必须在 2000—2200 之间。");
if (!await db.AcademicTerms.AnyAsync(
x => x.Id == request.AcademicTermId && x.IsEnabled,
cancellationToken))
@@ -1545,6 +1685,12 @@ public sealed class CourseSelectionsController(
return new(false, "只有在籍学生可以选课或候补。");
if (task.Status != TeachingTaskStatus.Published)
return new(false, "该教学班当前不可选。");
if (!CourseSelectionRules.IsGradeEligible(
round.EligibleGrades.Select(x => x.Grade),
student.AdministrativeClass!.Grade))
{
return new(false, "你所在的年级不属于本轮选课对象。");
}
if (!offering.IsOpenToAll &&
!await db.TeachingTaskClasses.AnyAsync(
x =>
@@ -1591,6 +1737,20 @@ public sealed class CourseSelectionsController(
$"获得名额后将达到 {selectedCredits + task.Course.Credits:0.#} 学分," +
$"超过本轮 {round.MaxCredits:0.#} 学分上限。");
}
var selectedCourseCount = await db.CourseEnrollments.CountAsync(
x =>
x.StudentId == student.Id &&
x.Status == CourseEnrollmentStatus.Enrolled &&
x.CourseSelectionOffering!.CourseSelectionRoundId == round.Id,
cancellationToken);
if (CourseSelectionRules.HasReachedCourseLimit(
round.MaxCourseCount,
selectedCourseCount))
{
return new(
isRetake,
$"你已达到本轮最多 {round.MaxCourseCount} 门课程限制。");
}
var candidateEntries = await PublishedScheduleEntries(
round.AcademicTermId,
@@ -1772,6 +1932,9 @@ public sealed class CourseSelectionsController(
private static string? Normalize(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static int[] NormalizeGrades(IEnumerable<int>? values) =>
values?.Distinct().OrderBy(x => x).ToArray() ?? [];
private sealed record EnrollmentEligibility(bool IsRetake, string? Error);
}
@@ -1782,6 +1945,8 @@ public sealed record CourseSelectionRoundRequest(
DateTime EndsAt,
DateTime WithdrawalEndsAt,
[Range(typeof(decimal), "0.5", "99")] decimal MaxCredits,
[Range(1, 100)] int? MaxCourseCount,
IReadOnlyCollection<int>? EligibleGrades,
[MaxLength(500)] string? Notes);
public sealed record CourseSelectionOfferingRequest(
@@ -11,12 +11,21 @@ public sealed class CourseSelectionRound : EntityBase
public DateTime EndsAt { get; set; }
public DateTime WithdrawalEndsAt { get; set; }
public decimal MaxCredits { get; set; } = 30;
public int? MaxCourseCount { get; set; }
public CourseSelectionRoundStatus Status { get; set; } =
CourseSelectionRoundStatus.Draft;
public string? Notes { get; set; }
public ICollection<CourseSelectionRoundGrade> EligibleGrades { get; set; } = [];
public ICollection<CourseSelectionOffering> Offerings { get; set; } = [];
}
public sealed class CourseSelectionRoundGrade : EntityBase
{
public Guid CourseSelectionRoundId { get; set; }
public CourseSelectionRound? CourseSelectionRound { get; set; }
public int Grade { get; set; }
}
public sealed class CourseSelectionOffering : EntityBase
{
public Guid CourseSelectionRoundId { get; set; }
@@ -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);
@@ -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");
}
}
}
@@ -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");
});