Merge branch 'codex/shiyan' into master

# Conflicts:
#	web/package-lock.json
This commit is contained in:
2026-08-09 14:51:24 +08:00 Unverified
25 changed files with 21324 additions and 1196 deletions
@@ -619,6 +619,10 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
entity.HasOne(x => x.TeachingTask).WithMany()
.HasForeignKey(x => x.TeachingTaskId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasIndex(x => x.ScheduleEntryId);
entity.HasOne(x => x.ScheduleEntry).WithMany()
.HasForeignKey(x => x.ScheduleEntryId)
.OnDelete(DeleteBehavior.Restrict);
});
builder.Entity<ExperimentSession>(entity =>
@@ -0,0 +1,50 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
/// <inheritdoc />
public partial class BindCentralizedExperimentProjectsToSchedules : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "ScheduleEntryId",
table: "ExperimentProjects",
type: "char(36)",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_ExperimentProjects_ScheduleEntryId",
table: "ExperimentProjects",
column: "ScheduleEntryId");
migrationBuilder.AddForeignKey(
name: "FK_ExperimentProjects_ScheduleEntries_ScheduleEntryId",
table: "ExperimentProjects",
column: "ScheduleEntryId",
principalTable: "ScheduleEntries",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ExperimentProjects_ScheduleEntries_ScheduleEntryId",
table: "ExperimentProjects");
migrationBuilder.DropIndex(
name: "IX_ExperimentProjects_ScheduleEntryId",
table: "ExperimentProjects");
migrationBuilder.DropColumn(
name: "ScheduleEntryId",
table: "ExperimentProjects");
}
}
}
@@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
/// <inheritdoc />
public partial class AddTeachingVenueNatures : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "TeachingVenueNature",
table: "Classrooms",
type: "int",
nullable: false,
defaultValue: 1);
migrationBuilder.Sql("""
UPDATE `Classrooms`
SET `TeachingVenueNature` = CASE
WHEN `RoomType` LIKE '%%' THEN 10
WHEN `RoomType` LIKE '%%' THEN 18
WHEN `RoomType` LIKE '%%' THEN 4
WHEN `RoomType` LIKE '%%' THEN 2
WHEN `RoomType` LIKE '%%' THEN 32
WHEN `RoomType` LIKE '%%' THEN 64
ELSE 1
END;
""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TeachingVenueNature",
table: "Classrooms");
}
}
}
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
/// <inheritdoc />
public partial class AddExperimentVenueNatureConstraints : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "AllowedExperimentVenueNatures",
table: "TeachingTaskScheduleConstraints",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AllowedExperimentVenueNatures",
table: "TeachingTaskScheduleConstraints");
}
}
}
@@ -525,6 +525,9 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<int>("TeachingVenueNature")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
@@ -2364,6 +2367,9 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
.HasMaxLength(1000)
.HasColumnType("varchar(1000)");
b.Property<Guid?>("ScheduleEntryId")
.HasColumnType("char(36)");
b.Property<DateTime>("StartDate")
.HasColumnType("date");
@@ -2378,6 +2384,8 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasKey("Id");
b.HasIndex("ScheduleEntryId");
b.HasIndex("TeachingTaskId", "Code")
.IsUnique();
@@ -4240,6 +4248,9 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<int>("AllowedExperimentVenueNatures")
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
@@ -5649,12 +5660,19 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentProject", b =>
{
b.HasOne("Jiaowu.Api.Domain.Academic.ScheduleEntry", "ScheduleEntry")
.WithMany()
.HasForeignKey("ScheduleEntryId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask")
.WithMany()
.HasForeignKey("TeachingTaskId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("ScheduleEntry");
b.Navigation("TeachingTask");
});
@@ -291,15 +291,14 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
(constraint?.RequiredBuildingId is not Guid requiredBuildingId ||
room.BuildingId == requiredBuildingId) &&
(allowedRoomIds.Count == 0 || allowedRoomIds.Contains(room.Id)) &&
(kind != ScheduleEntryKind.Experiment || IsExperimentRoom(room.RoomType)))
(kind != ScheduleEntryKind.Experiment ||
TeachingVenueNatureRules.SupportsExperiment(room.TeachingVenueNature)) &&
(kind != ScheduleEntryKind.Experiment || constraint is null ||
constraint.AllowedExperimentVenueNatures == 0 ||
(room.TeachingVenueNature & constraint.AllowedExperimentVenueNatures) != 0))
.ToList();
}
private static bool IsExperimentRoom(string roomType) =>
roomType.Contains("实验", StringComparison.OrdinalIgnoreCase) ||
roomType.Contains("实训", StringComparison.OrdinalIgnoreCase) ||
roomType.Contains("机房", StringComparison.OrdinalIgnoreCase) ||
roomType.Contains("语音", StringComparison.OrdinalIgnoreCase);
private static int[] ParseAllowedDays(string? value)
{