实验预约

This commit is contained in:
2026-07-28 21:04:18 +08:00 Unverified
parent 865378dbdf
commit ec979f640d
12 changed files with 8750 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,79 @@
using Jiaowu.Api.Domain.Common;
namespace Jiaowu.Api.Domain.Academic;
public sealed class ExperimentProject : EntityBase
{
public Guid TeachingTaskId { get; set; }
public TeachingTask? TeachingTask { get; set; }
public required string Code { get; set; }
public required string Name { get; set; }
public ExperimentArrangementMode ArrangementMode { get; set; }
public string? Description { get; set; }
public string? Requirements { get; set; }
public DateOnly StartDate { get; set; }
public DateOnly EndDate { get; set; }
public ExperimentProjectStatus Status { get; set; } =
ExperimentProjectStatus.Draft;
public DateTime? PublishedAt { get; set; }
public DateTime? ClosedAt { get; set; }
public ICollection<ExperimentSession> Sessions { get; set; } = [];
public ICollection<ExperimentBooking> Bookings { get; set; } = [];
}
public sealed class ExperimentSession : EntityBase
{
public Guid ExperimentProjectId { get; set; }
public ExperimentProject? ExperimentProject { get; set; }
public Guid ClassroomId { get; set; }
public Classroom? Classroom { get; set; }
public DateOnly SessionDate { get; set; }
public int StartPeriod { get; set; }
public int PeriodCount { get; set; }
public int Capacity { get; set; }
public int ReservedCount { get; set; }
public string? Notes { get; set; }
public ExperimentSessionStatus Status { get; set; } =
ExperimentSessionStatus.Scheduled;
public DateTime? CancelledAt { get; set; }
public ICollection<ExperimentBooking> Bookings { get; set; } = [];
}
public sealed class ExperimentBooking : EntityBase
{
public Guid ExperimentProjectId { get; set; }
public ExperimentProject? ExperimentProject { get; set; }
public Guid ExperimentSessionId { get; set; }
public ExperimentSession? ExperimentSession { get; set; }
public Guid StudentId { get; set; }
public Student? Student { get; set; }
public ExperimentBookingStatus Status { get; set; } =
ExperimentBookingStatus.Booked;
public DateTime BookedAt { get; set; } = DateTime.UtcNow;
public DateTime? CancelledAt { get; set; }
}
public enum ExperimentArrangementMode
{
Centralized = 1,
SelfScheduled = 2
}
public enum ExperimentProjectStatus
{
Draft = 1,
Published = 2,
Closed = 3
}
public enum ExperimentSessionStatus
{
Scheduled = 1,
Cancelled = 2
}
public enum ExperimentBookingStatus
{
Booked = 1,
Cancelled = 2
}
@@ -44,6 +44,9 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
Set<SchedulePublishJob>();
public DbSet<ClassroomReservation> ClassroomReservations =>
Set<ClassroomReservation>();
public DbSet<ExperimentProject> ExperimentProjects => Set<ExperimentProject>();
public DbSet<ExperimentSession> ExperimentSessions => Set<ExperimentSession>();
public DbSet<ExperimentBooking> ExperimentBookings => Set<ExperimentBooking>();
public DbSet<CourseSelectionRound> CourseSelectionRounds =>
Set<CourseSelectionRound>();
public DbSet<CourseSelectionRoundGrade> CourseSelectionRoundGrades =>
@@ -547,6 +550,64 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
.OnDelete(DeleteBehavior.SetNull);
});
builder.Entity<ExperimentProject>(entity =>
{
entity.Property(x => x.Code).HasMaxLength(40);
entity.Property(x => x.Name).HasMaxLength(120);
entity.Property(x => x.Description).HasMaxLength(1000);
entity.Property(x => x.Requirements).HasMaxLength(1000);
entity.HasIndex(x => new { x.TeachingTaskId, x.Code }).IsUnique();
entity.HasIndex(x => new { x.Status, x.StartDate, x.EndDate });
entity.HasOne(x => x.TeachingTask).WithMany()
.HasForeignKey(x => x.TeachingTaskId)
.OnDelete(DeleteBehavior.Restrict);
});
builder.Entity<ExperimentSession>(entity =>
{
entity.Property(x => x.Notes).HasMaxLength(500);
entity.HasIndex(x => new
{
x.ExperimentProjectId,
x.SessionDate,
x.StartPeriod
});
entity.HasIndex(x => new
{
x.ClassroomId,
x.SessionDate,
x.Status,
x.StartPeriod
});
entity.HasOne(x => x.ExperimentProject).WithMany(x => x.Sessions)
.HasForeignKey(x => x.ExperimentProjectId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(x => x.Classroom).WithMany()
.HasForeignKey(x => x.ClassroomId)
.OnDelete(DeleteBehavior.Restrict);
});
builder.Entity<ExperimentBooking>(entity =>
{
entity.HasIndex(x => new { x.ExperimentProjectId, x.StudentId })
.IsUnique();
entity.HasIndex(x => new
{
x.ExperimentSessionId,
x.Status,
x.BookedAt
});
entity.HasOne(x => x.ExperimentProject).WithMany(x => x.Bookings)
.HasForeignKey(x => x.ExperimentProjectId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(x => x.ExperimentSession).WithMany(x => x.Bookings)
.HasForeignKey(x => x.ExperimentSessionId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(x => x.Student).WithMany()
.HasForeignKey(x => x.StudentId)
.OnDelete(DeleteBehavior.Restrict);
});
builder.Entity<CourseSelectionRound>(entity =>
{
entity.Property(x => x.Name).HasMaxLength(120);
@@ -68,6 +68,10 @@ public sealed class DevelopmentSqliteMigrator(
"20260727_36_exam_room_mixing";
private const string AttendanceCheckInAuditMigration =
"20260728_37_attendance_check_in_audit";
private const string ExamPublishJobsMigration =
"20260728_38_exam_publish_jobs";
private const string ExperimentManagementMigration =
"20260728_39_experiment_management";
public async Task MigrateAsync(CancellationToken cancellationToken = default)
{
@@ -513,6 +517,32 @@ public sealed class DevelopmentSqliteMigrator(
ExamRoomMixingMigration,
examRoomsExist ? [] : ExamRoomMixingStatements,
cancellationToken);
var examPublishJobsExist = await db.Database
.SqlQueryRaw<int>(
"""
SELECT COUNT(*) AS "Value"
FROM sqlite_master
WHERE type = 'table' AND name = 'ExamPublishJobs'
""")
.AnyAsync(value => value > 0, cancellationToken);
await ApplyMigrationAsync(
ExamPublishJobsMigration,
examPublishJobsExist ? [] : ExamPublishJobStatements,
cancellationToken);
var experimentProjectsExist = await db.Database
.SqlQueryRaw<int>(
"""
SELECT COUNT(*) AS "Value"
FROM sqlite_master
WHERE type = 'table' AND name = 'ExperimentProjects'
""")
.AnyAsync(value => value > 0, cancellationToken);
await ApplyMigrationAsync(
ExperimentManagementMigration,
experimentProjectsExist ? [] : ExperimentManagementStatements,
cancellationToken);
}
private async Task ApplyMigrationAsync(
@@ -2351,4 +2381,134 @@ public sealed class DevelopmentSqliteMigrator(
ON "ExamRoomInvigilators" ("TeacherId");
"""
];
private static readonly string[] ExamPublishJobStatements =
[
"""
CREATE TABLE "ExamPublishJobs" (
"Id" TEXT NOT NULL CONSTRAINT "PK_ExamPublishJobs" PRIMARY KEY,
"Kind" INTEGER NOT NULL,
"PlanId" TEXT NOT NULL,
"ActivePlanId" TEXT NULL,
"RequestedByUserId" TEXT NULL,
"Status" INTEGER NOT NULL,
"CurrentStep" TEXT NULL,
"ErrorMessage" TEXT NULL,
"StartedAt" TEXT NULL,
"CompletedAt" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
"""
];
private static readonly string[] ExperimentManagementStatements =
[
"""
CREATE TABLE "ExperimentProjects" (
"Id" TEXT NOT NULL CONSTRAINT "PK_ExperimentProjects" PRIMARY KEY,
"TeachingTaskId" TEXT NOT NULL,
"Code" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"ArrangementMode" INTEGER NOT NULL,
"Description" TEXT NULL,
"Requirements" TEXT NULL,
"StartDate" TEXT NOT NULL,
"EndDate" TEXT NOT NULL,
"Status" INTEGER NOT NULL,
"PublishedAt" TEXT NULL,
"ClosedAt" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_ExperimentProjects_TeachingTasks_TeachingTaskId"
FOREIGN KEY ("TeachingTaskId") REFERENCES "TeachingTasks" ("Id")
ON DELETE RESTRICT
);
""",
"""
CREATE UNIQUE INDEX "IX_ExperimentProjects_TeachingTaskId_Code"
ON "ExperimentProjects" ("TeachingTaskId", "Code");
""",
"""
CREATE INDEX "IX_ExperimentProjects_Status_StartDate_EndDate"
ON "ExperimentProjects" ("Status", "StartDate", "EndDate");
""",
"""
CREATE TABLE "ExperimentSessions" (
"Id" TEXT NOT NULL CONSTRAINT "PK_ExperimentSessions" PRIMARY KEY,
"ExperimentProjectId" TEXT NOT NULL,
"ClassroomId" TEXT NOT NULL,
"SessionDate" TEXT NOT NULL,
"StartPeriod" INTEGER NOT NULL,
"PeriodCount" INTEGER NOT NULL,
"Capacity" INTEGER NOT NULL,
"ReservedCount" INTEGER NOT NULL,
"Notes" TEXT NULL,
"Status" INTEGER NOT NULL,
"CancelledAt" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_ExperimentSessions_ExperimentProjects_ExperimentProjectId"
FOREIGN KEY ("ExperimentProjectId")
REFERENCES "ExperimentProjects" ("Id")
ON DELETE CASCADE,
CONSTRAINT "FK_ExperimentSessions_Classrooms_ClassroomId"
FOREIGN KEY ("ClassroomId") REFERENCES "Classrooms" ("Id")
ON DELETE RESTRICT
);
""",
"""
CREATE INDEX "IX_ExperimentSessions_ExperimentProjectId_SessionDate_StartPeriod"
ON "ExperimentSessions" (
"ExperimentProjectId",
"SessionDate",
"StartPeriod"
);
""",
"""
CREATE INDEX "IX_ExperimentSessions_ClassroomId_SessionDate_Status_StartPeriod"
ON "ExperimentSessions" (
"ClassroomId",
"SessionDate",
"Status",
"StartPeriod"
);
""",
"""
CREATE TABLE "ExperimentBookings" (
"Id" TEXT NOT NULL CONSTRAINT "PK_ExperimentBookings" PRIMARY KEY,
"ExperimentProjectId" TEXT NOT NULL,
"ExperimentSessionId" TEXT NOT NULL,
"StudentId" TEXT NOT NULL,
"Status" INTEGER NOT NULL,
"BookedAt" TEXT NOT NULL,
"CancelledAt" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_ExperimentBookings_ExperimentProjects_ExperimentProjectId"
FOREIGN KEY ("ExperimentProjectId")
REFERENCES "ExperimentProjects" ("Id")
ON DELETE CASCADE,
CONSTRAINT "FK_ExperimentBookings_ExperimentSessions_ExperimentSessionId"
FOREIGN KEY ("ExperimentSessionId")
REFERENCES "ExperimentSessions" ("Id")
ON DELETE RESTRICT,
CONSTRAINT "FK_ExperimentBookings_Students_StudentId"
FOREIGN KEY ("StudentId") REFERENCES "Students" ("Id")
ON DELETE RESTRICT
);
""",
"""
CREATE UNIQUE INDEX "IX_ExperimentBookings_ExperimentProjectId_StudentId"
ON "ExperimentBookings" ("ExperimentProjectId", "StudentId");
""",
"""
CREATE INDEX "IX_ExperimentBookings_ExperimentSessionId_Status_BookedAt"
ON "ExperimentBookings" (
"ExperimentSessionId",
"Status",
"BookedAt"
);
"""
];
}
@@ -0,0 +1,170 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
/// <inheritdoc />
public partial class ExperimentManagement : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ExperimentProjects",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
TeachingTaskId = table.Column<Guid>(type: "char(36)", nullable: false),
Code = table.Column<string>(type: "varchar(40)", maxLength: 40, nullable: false),
Name = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false),
ArrangementMode = table.Column<int>(type: "int", nullable: false),
Description = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: true),
Requirements = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: true),
StartDate = table.Column<DateTime>(type: "date", nullable: false),
EndDate = table.Column<DateTime>(type: "date", nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
PublishedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
ClosedAt = table.Column<DateTime>(type: "datetime(6)", 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_ExperimentProjects", x => x.Id);
table.ForeignKey(
name: "FK_ExperimentProjects_TeachingTasks_TeachingTaskId",
column: x => x.TeachingTaskId,
principalTable: "TeachingTasks",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "ExperimentSessions",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
ExperimentProjectId = table.Column<Guid>(type: "char(36)", nullable: false),
ClassroomId = table.Column<Guid>(type: "char(36)", nullable: false),
SessionDate = table.Column<DateTime>(type: "date", nullable: false),
StartPeriod = table.Column<int>(type: "int", nullable: false),
PeriodCount = table.Column<int>(type: "int", nullable: false),
Capacity = table.Column<int>(type: "int", nullable: false),
ReservedCount = table.Column<int>(type: "int", nullable: false),
Notes = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
CancelledAt = table.Column<DateTime>(type: "datetime(6)", 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_ExperimentSessions", x => x.Id);
table.ForeignKey(
name: "FK_ExperimentSessions_Classrooms_ClassroomId",
column: x => x.ClassroomId,
principalTable: "Classrooms",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_ExperimentSessions_ExperimentProjects_ExperimentProjectId",
column: x => x.ExperimentProjectId,
principalTable: "ExperimentProjects",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateTable(
name: "ExperimentBookings",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false),
ExperimentProjectId = table.Column<Guid>(type: "char(36)", nullable: false),
ExperimentSessionId = table.Column<Guid>(type: "char(36)", nullable: false),
StudentId = table.Column<Guid>(type: "char(36)", nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
BookedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CancelledAt = table.Column<DateTime>(type: "datetime(6)", 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_ExperimentBookings", x => x.Id);
table.ForeignKey(
name: "FK_ExperimentBookings_ExperimentProjects_ExperimentProjectId",
column: x => x.ExperimentProjectId,
principalTable: "ExperimentProjects",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ExperimentBookings_ExperimentSessions_ExperimentSessionId",
column: x => x.ExperimentSessionId,
principalTable: "ExperimentSessions",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_ExperimentBookings_Students_StudentId",
column: x => x.StudentId,
principalTable: "Students",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySQL:Charset", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_ExperimentBookings_ExperimentProjectId_StudentId",
table: "ExperimentBookings",
columns: new[] { "ExperimentProjectId", "StudentId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ExperimentBookings_ExperimentSessionId_Status_BookedAt",
table: "ExperimentBookings",
columns: new[] { "ExperimentSessionId", "Status", "BookedAt" });
migrationBuilder.CreateIndex(
name: "IX_ExperimentBookings_StudentId",
table: "ExperimentBookings",
column: "StudentId");
migrationBuilder.CreateIndex(
name: "IX_ExperimentProjects_Status_StartDate_EndDate",
table: "ExperimentProjects",
columns: new[] { "Status", "StartDate", "EndDate" });
migrationBuilder.CreateIndex(
name: "IX_ExperimentProjects_TeachingTaskId_Code",
table: "ExperimentProjects",
columns: new[] { "TeachingTaskId", "Code" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ExperimentSessions_ClassroomId_SessionDate_Status_StartPeriod",
table: "ExperimentSessions",
columns: new[] { "ClassroomId", "SessionDate", "Status", "StartPeriod" });
migrationBuilder.CreateIndex(
name: "IX_ExperimentSessions_ExperimentProjectId_SessionDate_StartPeri~",
table: "ExperimentSessions",
columns: new[] { "ExperimentProjectId", "SessionDate", "StartPeriod" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ExperimentBookings");
migrationBuilder.DropTable(
name: "ExperimentSessions");
migrationBuilder.DropTable(
name: "ExperimentProjects");
}
}
}
@@ -2001,6 +2001,161 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.ToTable("ExamSignInExportJobs");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentBooking", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("BookedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime?>("CancelledAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid>("ExperimentProjectId")
.HasColumnType("char(36)");
b.Property<Guid>("ExperimentSessionId")
.HasColumnType("char(36)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<Guid>("StudentId")
.HasColumnType("char(36)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("StudentId");
b.HasIndex("ExperimentProjectId", "StudentId")
.IsUnique();
b.HasIndex("ExperimentSessionId", "Status", "BookedAt");
b.ToTable("ExperimentBookings");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentProject", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<int>("ArrangementMode")
.HasColumnType("int");
b.Property<DateTime?>("ClosedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("varchar(40)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)");
b.Property<DateTime>("EndDate")
.HasColumnType("date");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("varchar(120)");
b.Property<DateTime?>("PublishedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Requirements")
.HasMaxLength(1000)
.HasColumnType("varchar(1000)");
b.Property<DateTime>("StartDate")
.HasColumnType("date");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<Guid>("TeachingTaskId")
.HasColumnType("char(36)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("TeachingTaskId", "Code")
.IsUnique();
b.HasIndex("Status", "StartDate", "EndDate");
b.ToTable("ExperimentProjects");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentSession", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime?>("CancelledAt")
.HasColumnType("datetime(6)");
b.Property<int>("Capacity")
.HasColumnType("int");
b.Property<Guid>("ClassroomId")
.HasColumnType("char(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid>("ExperimentProjectId")
.HasColumnType("char(36)");
b.Property<string>("Notes")
.HasMaxLength(500)
.HasColumnType("varchar(500)");
b.Property<int>("PeriodCount")
.HasColumnType("int");
b.Property<int>("ReservedCount")
.HasColumnType("int");
b.Property<DateTime>("SessionDate")
.HasColumnType("date");
b.Property<int>("StartPeriod")
.HasColumnType("int");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("ExperimentProjectId", "SessionDate", "StartPeriod");
b.HasIndex("ClassroomId", "SessionDate", "Status", "StartPeriod");
b.ToTable("ExperimentSessions");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeItem", b =>
{
b.Property<Guid>("Id")
@@ -4630,6 +4785,63 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.Navigation("Teacher");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentBooking", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentProject", "ExperimentProject")
.WithMany("Bookings")
.HasForeignKey("ExperimentProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentSession", "ExperimentSession")
.WithMany("Bookings")
.HasForeignKey("ExperimentSessionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student")
.WithMany()
.HasForeignKey("StudentId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("ExperimentProject");
b.Navigation("ExperimentSession");
b.Navigation("Student");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentProject", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask")
.WithMany()
.HasForeignKey("TeachingTaskId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("TeachingTask");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentSession", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.Classroom", "Classroom")
.WithMany()
.HasForeignKey("ClassroomId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentProject", "ExperimentProject")
.WithMany("Sessions")
.HasForeignKey("ExperimentProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Classroom");
b.Navigation("ExperimentProject");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeItem", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.GradeSheet", "GradeSheet")
@@ -5338,6 +5550,18 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.Navigation("SeatAssignments");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentProject", b =>
{
b.Navigation("Bookings");
b.Navigation("Sessions");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentSession", b =>
{
b.Navigation("Bookings");
});
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeItem", b =>
{
b.Navigation("Scores");
@@ -81,6 +81,20 @@ public sealed class ClassroomReservationAvailabilityService(AppDbContext db)
.ToListAsync(cancellationToken);
occupiedIds.UnionWith(makeupExamRoomIds);
var experimentRoomIds = await db.ExperimentSessions.AsNoTracking()
.Where(session =>
session.ExperimentProject!.TeachingTask!.AcademicTermId ==
term.Id &&
session.ExperimentProject.Status !=
ExperimentProjectStatus.Closed &&
session.Status == ExperimentSessionStatus.Scheduled &&
session.SessionDate == reservationDate &&
session.StartPeriod < startPeriod + periodCount &&
startPeriod < session.StartPeriod + session.PeriodCount)
.Select(session => session.ClassroomId)
.ToListAsync(cancellationToken);
occupiedIds.UnionWith(experimentRoomIds);
var reservationQuery = db.ClassroomReservations.AsNoTracking()
.Where(reservation =>
reservation.AcademicTermId == term.Id &&