实验排课再次修正
This commit is contained in:
@@ -133,6 +133,8 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
: constraint?.RequiresClassroom ?? true,
|
: constraint?.RequiresClassroom ?? true,
|
||||||
constraint?.RequiredCampusId,
|
constraint?.RequiredCampusId,
|
||||||
constraint?.RequiredBuildingId,
|
constraint?.RequiredBuildingId,
|
||||||
|
constraint?.ExperimentRequiredCampusId,
|
||||||
|
constraint?.ExperimentRequiredBuildingId,
|
||||||
AllowedDayOfWeeks = ParseDays(constraint?.AllowedDayOfWeeks),
|
AllowedDayOfWeeks = ParseDays(constraint?.AllowedDayOfWeeks),
|
||||||
constraint?.EarliestPeriod,
|
constraint?.EarliestPeriod,
|
||||||
constraint?.LatestPeriod,
|
constraint?.LatestPeriod,
|
||||||
@@ -199,6 +201,22 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
cancellationToken))
|
cancellationToken))
|
||||||
return ValidationProblem("指定校区不存在或已停用。");
|
return ValidationProblem("指定校区不存在或已停用。");
|
||||||
|
|
||||||
|
Building? experimentBuilding = null;
|
||||||
|
if (request.ExperimentRequiredBuildingId.HasValue)
|
||||||
|
{
|
||||||
|
experimentBuilding = await db.Buildings.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == request.ExperimentRequiredBuildingId && x.IsEnabled,
|
||||||
|
cancellationToken);
|
||||||
|
if (experimentBuilding is null) return ValidationProblem("指定实验教学楼不存在或已停用。");
|
||||||
|
if (request.ExperimentRequiredCampusId.HasValue &&
|
||||||
|
experimentBuilding.CampusId != request.ExperimentRequiredCampusId)
|
||||||
|
return ValidationProblem("指定实验教学楼不属于所选校区。");
|
||||||
|
}
|
||||||
|
if (request.ExperimentRequiredCampusId.HasValue &&
|
||||||
|
!await db.Campuses.AnyAsync(x => x.Id == request.ExperimentRequiredCampusId && x.IsEnabled,
|
||||||
|
cancellationToken))
|
||||||
|
return ValidationProblem("指定实验校区不存在或已停用。");
|
||||||
|
|
||||||
var allowedRooms = await db.Classrooms.AsNoTracking()
|
var allowedRooms = await db.Classrooms.AsNoTracking()
|
||||||
.Where(x => x.IsEnabled)
|
.Where(x => x.IsEnabled)
|
||||||
.WhereIn(request.AllowedClassroomIds, x => x.Id)
|
.WhereIn(request.AllowedClassroomIds, x => x.Id)
|
||||||
@@ -220,11 +238,11 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
if (allowedExperimentRooms.Count != allowedExperimentRoomIds.Length)
|
if (allowedExperimentRooms.Count != allowedExperimentRoomIds.Length)
|
||||||
return ValidationProblem("部分指定实验场地不存在或已停用。");
|
return ValidationProblem("部分指定实验场地不存在或已停用。");
|
||||||
if (building is not null && allowedExperimentRooms.Any(x => x.BuildingId != building.Id))
|
if (experimentBuilding is not null && allowedExperimentRooms.Any(x => x.BuildingId != experimentBuilding.Id))
|
||||||
return ValidationProblem("指定实验场地必须位于所选教学楼。");
|
return ValidationProblem("指定实验场地必须位于所选实验教学楼。");
|
||||||
if (request.RequiredCampusId.HasValue &&
|
if (request.ExperimentRequiredCampusId.HasValue &&
|
||||||
allowedExperimentRooms.Any(x => x.Building!.CampusId != request.RequiredCampusId))
|
allowedExperimentRooms.Any(x => x.Building!.CampusId != request.ExperimentRequiredCampusId))
|
||||||
return ValidationProblem("指定实验场地必须位于所选校区。");
|
return ValidationProblem("指定实验场地必须位于所选实验校区。");
|
||||||
|
|
||||||
var constraint = await db.TeachingTaskScheduleConstraints
|
var constraint = await db.TeachingTaskScheduleConstraints
|
||||||
.Include(x => x.AllowedClassrooms)
|
.Include(x => x.AllowedClassrooms)
|
||||||
@@ -242,6 +260,12 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
constraint.RequiredBuildingId = request.RequiresClassroom
|
constraint.RequiredBuildingId = request.RequiresClassroom
|
||||||
? request.RequiredBuildingId
|
? request.RequiredBuildingId
|
||||||
: null;
|
: null;
|
||||||
|
constraint.ExperimentRequiredCampusId = request.RequiresClassroom
|
||||||
|
? request.ExperimentRequiredCampusId
|
||||||
|
: null;
|
||||||
|
constraint.ExperimentRequiredBuildingId = request.RequiresClassroom
|
||||||
|
? request.ExperimentRequiredBuildingId
|
||||||
|
: null;
|
||||||
constraint.AllowedDayOfWeeks = request.AllowedDayOfWeeks.Count == 0
|
constraint.AllowedDayOfWeeks = request.AllowedDayOfWeeks.Count == 0
|
||||||
? null
|
? null
|
||||||
: string.Join(',', request.AllowedDayOfWeeks.Distinct().Order());
|
: string.Join(',', request.AllowedDayOfWeeks.Distinct().Order());
|
||||||
@@ -461,6 +485,8 @@ public sealed class ScheduleSettingsController(AppDbContext db, IAppCache cache)
|
|||||||
constraint.RequiresClassroom = false;
|
constraint.RequiresClassroom = false;
|
||||||
constraint.RequiredCampusId = null;
|
constraint.RequiredCampusId = null;
|
||||||
constraint.RequiredBuildingId = null;
|
constraint.RequiredBuildingId = null;
|
||||||
|
constraint.ExperimentRequiredCampusId = null;
|
||||||
|
constraint.ExperimentRequiredBuildingId = null;
|
||||||
constraint.AllowedDayOfWeeks = null;
|
constraint.AllowedDayOfWeeks = null;
|
||||||
constraint.EarliestPeriod = null;
|
constraint.EarliestPeriod = null;
|
||||||
constraint.LatestPeriod = null;
|
constraint.LatestPeriod = null;
|
||||||
@@ -503,7 +529,9 @@ public sealed record TeachingTaskScheduleConstraintRequest(
|
|||||||
[Range(1, 30)] int? EarliestPeriod,
|
[Range(1, 30)] int? EarliestPeriod,
|
||||||
[Range(1, 30)] int? LatestPeriod,
|
[Range(1, 30)] int? LatestPeriod,
|
||||||
TeachingVenueNature AllowedExperimentVenueNatures = 0,
|
TeachingVenueNature AllowedExperimentVenueNatures = 0,
|
||||||
IReadOnlyList<Guid>? AllowedExperimentClassroomIds = null);
|
IReadOnlyList<Guid>? AllowedExperimentClassroomIds = null,
|
||||||
|
Guid? ExperimentRequiredCampusId = null,
|
||||||
|
Guid? ExperimentRequiredBuildingId = null);
|
||||||
|
|
||||||
public sealed record TeachingTaskScheduleConstraintBatchRequest(
|
public sealed record TeachingTaskScheduleConstraintBatchRequest(
|
||||||
Guid AcademicTermId,
|
Guid AcademicTermId,
|
||||||
|
|||||||
@@ -581,16 +581,26 @@ public sealed class SchedulesController(
|
|||||||
x => x.Id == request.ClassroomId && x.IsEnabled,
|
x => x.Id == request.ClassroomId && x.IsEnabled,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
if (classroom is null) return ValidationProblem("所选教室不存在或已停用。");
|
if (classroom is null) return ValidationProblem("所选教室不存在或已停用。");
|
||||||
if (constraint?.RequiredCampusId is Guid campusId &&
|
if (request.Kind != ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.RequiredCampusId is Guid campusId &&
|
||||||
classroom.Building!.CampusId != campusId)
|
classroom.Building!.CampusId != campusId)
|
||||||
return ValidationProblem("所选教室不在该课程指定的校区。");
|
return ValidationProblem("所选教室不在该课程指定的校区。");
|
||||||
if (constraint?.RequiredBuildingId is Guid buildingId &&
|
if (request.Kind != ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.RequiredBuildingId is Guid buildingId &&
|
||||||
classroom.BuildingId != buildingId)
|
classroom.BuildingId != buildingId)
|
||||||
return ValidationProblem("所选教室不在该课程指定的教学楼。");
|
return ValidationProblem("所选教室不在该课程指定的教学楼。");
|
||||||
|
if (request.Kind == ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.ExperimentRequiredCampusId is Guid experimentCampusId &&
|
||||||
|
classroom.Building!.CampusId != experimentCampusId)
|
||||||
|
return ValidationProblem("所选场地不在该实验课指定的校区。");
|
||||||
|
if (request.Kind == ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.ExperimentRequiredBuildingId is Guid experimentBuildingId &&
|
||||||
|
classroom.BuildingId != experimentBuildingId)
|
||||||
|
return ValidationProblem("所选场地不在该实验课指定的教学楼。");
|
||||||
var allowedClassroomIds = constraint?.AllowedClassrooms
|
var allowedClassroomIds = constraint?.AllowedClassrooms
|
||||||
.Select(x => x.ClassroomId)
|
.Select(x => x.ClassroomId)
|
||||||
.ToHashSet() ?? [];
|
.ToHashSet() ?? [];
|
||||||
if (allowedClassroomIds.Count > 0 &&
|
if (request.Kind != ScheduleEntryKind.Experiment && allowedClassroomIds.Count > 0 &&
|
||||||
!allowedClassroomIds.Contains(classroom.Id))
|
!allowedClassroomIds.Contains(classroom.Id))
|
||||||
return ValidationProblem("所选教室不在该课程指定的教室范围内。");
|
return ValidationProblem("所选教室不在该课程指定的教室范围内。");
|
||||||
if (request.Kind == ScheduleEntryKind.Experiment &&
|
if (request.Kind == ScheduleEntryKind.Experiment &&
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ public sealed class TeachingTaskScheduleConstraint : EntityBase
|
|||||||
public Campus? RequiredCampus { get; set; }
|
public Campus? RequiredCampus { get; set; }
|
||||||
public Guid? RequiredBuildingId { get; set; }
|
public Guid? RequiredBuildingId { get; set; }
|
||||||
public Building? RequiredBuilding { get; set; }
|
public Building? RequiredBuilding { get; set; }
|
||||||
|
public Guid? ExperimentRequiredCampusId { get; set; }
|
||||||
|
public Campus? ExperimentRequiredCampus { get; set; }
|
||||||
|
public Guid? ExperimentRequiredBuildingId { get; set; }
|
||||||
|
public Building? ExperimentRequiredBuilding { get; set; }
|
||||||
public string? AllowedDayOfWeeks { get; set; }
|
public string? AllowedDayOfWeeks { get; set; }
|
||||||
public int? EarliestPeriod { get; set; }
|
public int? EarliestPeriod { get; set; }
|
||||||
public int? LatestPeriod { get; set; }
|
public int? LatestPeriod { get; set; }
|
||||||
|
|||||||
@@ -513,6 +513,14 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(x => x.RequiredBuildingId)
|
.HasForeignKey(x => x.RequiredBuildingId)
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
entity.HasOne(x => x.ExperimentRequiredCampus)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(x => x.ExperimentRequiredCampusId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
entity.HasOne(x => x.ExperimentRequiredBuilding)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(x => x.ExperimentRequiredBuildingId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<TeachingTaskAllowedClassroom>(entity =>
|
builder.Entity<TeachingTaskAllowedClassroom>(entity =>
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ public sealed class DevelopmentSqliteMigrator(
|
|||||||
"20260809_49_swagger_documentation_setting";
|
"20260809_49_swagger_documentation_setting";
|
||||||
private const string ExperimentClassroomConstraintsMigration =
|
private const string ExperimentClassroomConstraintsMigration =
|
||||||
"20260809_50_experiment_classroom_constraints";
|
"20260809_50_experiment_classroom_constraints";
|
||||||
|
private const string SeparateExperimentClassroomScopeMigration =
|
||||||
|
"20260809_51_separate_experiment_classroom_scope";
|
||||||
|
|
||||||
public async Task MigrateAsync(CancellationToken cancellationToken = default)
|
public async Task MigrateAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -689,6 +691,16 @@ public sealed class DevelopmentSqliteMigrator(
|
|||||||
ExperimentClassroomConstraintsMigration,
|
ExperimentClassroomConstraintsMigration,
|
||||||
experimentClassroomConstraintsExist ? [] : ExperimentClassroomConstraintStatements,
|
experimentClassroomConstraintsExist ? [] : ExperimentClassroomConstraintStatements,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
var experimentScopeColumns = (await db.Database.SqlQueryRaw<string>(
|
||||||
|
"SELECT name AS \"Value\" FROM pragma_table_info('TeachingTaskScheduleConstraints')")
|
||||||
|
.ToListAsync(cancellationToken))
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
await ApplyMigrationAsync(
|
||||||
|
SeparateExperimentClassroomScopeMigration,
|
||||||
|
experimentScopeColumns.Contains("ExperimentRequiredCampusId")
|
||||||
|
? []
|
||||||
|
: SeparateExperimentClassroomScopeStatements,
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ApplyMigrationAsync(
|
private async Task ApplyMigrationAsync(
|
||||||
@@ -2971,4 +2983,24 @@ public sealed class DevelopmentSqliteMigrator(
|
|||||||
ON "TeachingTaskAllowedExperimentClassrooms" ("ClassroomId");
|
ON "TeachingTaskAllowedExperimentClassrooms" ("ClassroomId");
|
||||||
"""
|
"""
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static readonly string[] SeparateExperimentClassroomScopeStatements =
|
||||||
|
[
|
||||||
|
"""
|
||||||
|
ALTER TABLE "TeachingTaskScheduleConstraints"
|
||||||
|
ADD COLUMN "ExperimentRequiredCampusId" TEXT NULL;
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
ALTER TABLE "TeachingTaskScheduleConstraints"
|
||||||
|
ADD COLUMN "ExperimentRequiredBuildingId" TEXT NULL;
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE INDEX "IX_TeachingTaskScheduleConstraints_ExperimentRequiredCampusId"
|
||||||
|
ON "TeachingTaskScheduleConstraints" ("ExperimentRequiredCampusId");
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
CREATE INDEX "IX_TeachingTaskScheduleConstraints_ExperimentRequiredBuildingId"
|
||||||
|
ON "TeachingTaskScheduleConstraints" ("ExperimentRequiredBuildingId");
|
||||||
|
"""
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
+6655
File diff suppressed because it is too large
Load Diff
+81
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class SeparateExperimentClassroomScope : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "ExperimentRequiredBuildingId",
|
||||||
|
table: "TeachingTaskScheduleConstraints",
|
||||||
|
type: "char(36)",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "ExperimentRequiredCampusId",
|
||||||
|
table: "TeachingTaskScheduleConstraints",
|
||||||
|
type: "char(36)",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TeachingTaskScheduleConstraints_ExperimentRequiredBuildingId",
|
||||||
|
table: "TeachingTaskScheduleConstraints",
|
||||||
|
column: "ExperimentRequiredBuildingId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TeachingTaskScheduleConstraints_ExperimentRequiredCampusId",
|
||||||
|
table: "TeachingTaskScheduleConstraints",
|
||||||
|
column: "ExperimentRequiredCampusId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_TeachingTaskScheduleConstraints_Buildings_ExperimentRequired~",
|
||||||
|
table: "TeachingTaskScheduleConstraints",
|
||||||
|
column: "ExperimentRequiredBuildingId",
|
||||||
|
principalTable: "Buildings",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_TeachingTaskScheduleConstraints_Campuses_ExperimentRequiredC~",
|
||||||
|
table: "TeachingTaskScheduleConstraints",
|
||||||
|
column: "ExperimentRequiredCampusId",
|
||||||
|
principalTable: "Campuses",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_TeachingTaskScheduleConstraints_Buildings_ExperimentRequired~",
|
||||||
|
table: "TeachingTaskScheduleConstraints");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_TeachingTaskScheduleConstraints_Campuses_ExperimentRequiredC~",
|
||||||
|
table: "TeachingTaskScheduleConstraints");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_TeachingTaskScheduleConstraints_ExperimentRequiredBuildingId",
|
||||||
|
table: "TeachingTaskScheduleConstraints");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_TeachingTaskScheduleConstraints_ExperimentRequiredCampusId",
|
||||||
|
table: "TeachingTaskScheduleConstraints");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ExperimentRequiredBuildingId",
|
||||||
|
table: "TeachingTaskScheduleConstraints");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ExperimentRequiredCampusId",
|
||||||
|
table: "TeachingTaskScheduleConstraints");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -4272,6 +4272,12 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
|||||||
b.Property<int?>("EarliestPeriod")
|
b.Property<int?>("EarliestPeriod")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ExperimentRequiredBuildingId")
|
||||||
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ExperimentRequiredCampusId")
|
||||||
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
b.Property<int?>("LatestPeriod")
|
b.Property<int?>("LatestPeriod")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
@@ -4292,6 +4298,10 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExperimentRequiredBuildingId");
|
||||||
|
|
||||||
|
b.HasIndex("ExperimentRequiredCampusId");
|
||||||
|
|
||||||
b.HasIndex("RequiredBuildingId");
|
b.HasIndex("RequiredBuildingId");
|
||||||
|
|
||||||
b.HasIndex("RequiredCampusId");
|
b.HasIndex("RequiredCampusId");
|
||||||
@@ -6295,6 +6305,16 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
|||||||
|
|
||||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskScheduleConstraint", b =>
|
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskScheduleConstraint", b =>
|
||||||
{
|
{
|
||||||
|
b.HasOne("Jiaowu.Api.Domain.Academic.Building", "ExperimentRequiredBuilding")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ExperimentRequiredBuildingId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.HasOne("Jiaowu.Api.Domain.Academic.Campus", "ExperimentRequiredCampus")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ExperimentRequiredCampusId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.HasOne("Jiaowu.Api.Domain.Academic.Building", "RequiredBuilding")
|
b.HasOne("Jiaowu.Api.Domain.Academic.Building", "RequiredBuilding")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("RequiredBuildingId")
|
.HasForeignKey("RequiredBuildingId")
|
||||||
@@ -6311,6 +6331,10 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("ExperimentRequiredBuilding");
|
||||||
|
|
||||||
|
b.Navigation("ExperimentRequiredCampus");
|
||||||
|
|
||||||
b.Navigation("RequiredBuilding");
|
b.Navigation("RequiredBuilding");
|
||||||
|
|
||||||
b.Navigation("RequiredCampus");
|
b.Navigation("RequiredCampus");
|
||||||
|
|||||||
@@ -254,8 +254,15 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
|||||||
x.TeachingTaskId == task.Id && x.DayOfWeek == day);
|
x.TeachingTaskId == task.Id && x.DayOfWeek == day);
|
||||||
var dayLoad = entries.Count(x => x.DayOfWeek == day);
|
var dayLoad = entries.Count(x => x.DayOfWeek == day);
|
||||||
var roomWaste = room is null ? 0 : Math.Max(0, room.Capacity - task.Capacity);
|
var roomWaste = room is null ? 0 : Math.Max(0, room.Capacity - task.Capacity);
|
||||||
|
var experimentGeneralClassroomPenalty =
|
||||||
|
kind == ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.AllowedExperimentVenueNatures == 0 &&
|
||||||
|
room?.TeachingVenueNature == TeachingVenueNature.GeneralClassroom
|
||||||
|
? 100_000
|
||||||
|
: 0;
|
||||||
var score = sameTaskDay * 1000 + dayLoad * 10 + start +
|
var score = sameTaskDay * 1000 + dayLoad * 10 + start +
|
||||||
roomWaste / 10 + startWeek;
|
roomWaste / 10 + startWeek +
|
||||||
|
experimentGeneralClassroomPenalty;
|
||||||
candidates.Add((proposed, score));
|
candidates.Add((proposed, score));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,11 +297,20 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
|||||||
student.Status == StudentStatus.Active) ?? 0));
|
student.Status == StudentStatus.Active) ?? 0));
|
||||||
return classrooms.Where(room =>
|
return classrooms.Where(room =>
|
||||||
room.Capacity >= minimumCapacity &&
|
room.Capacity >= minimumCapacity &&
|
||||||
(constraint?.RequiredCampusId is not Guid requiredCampusId ||
|
(kind == ScheduleEntryKind.Experiment ||
|
||||||
|
constraint?.RequiredCampusId is not Guid requiredCampusId ||
|
||||||
room.Building!.CampusId == requiredCampusId) &&
|
room.Building!.CampusId == requiredCampusId) &&
|
||||||
(constraint?.RequiredBuildingId is not Guid requiredBuildingId ||
|
(kind == ScheduleEntryKind.Experiment ||
|
||||||
|
constraint?.RequiredBuildingId is not Guid requiredBuildingId ||
|
||||||
room.BuildingId == requiredBuildingId) &&
|
room.BuildingId == requiredBuildingId) &&
|
||||||
(allowedRoomIds.Count == 0 || allowedRoomIds.Contains(room.Id)) &&
|
(kind != ScheduleEntryKind.Experiment ||
|
||||||
|
constraint?.ExperimentRequiredCampusId is not Guid experimentCampusId ||
|
||||||
|
room.Building!.CampusId == experimentCampusId) &&
|
||||||
|
(kind != ScheduleEntryKind.Experiment ||
|
||||||
|
constraint?.ExperimentRequiredBuildingId is not Guid experimentBuildingId ||
|
||||||
|
room.BuildingId == experimentBuildingId) &&
|
||||||
|
(kind == ScheduleEntryKind.Experiment ||
|
||||||
|
allowedRoomIds.Count == 0 || allowedRoomIds.Contains(room.Id)) &&
|
||||||
(kind != ScheduleEntryKind.Experiment || constraint is null ||
|
(kind != ScheduleEntryKind.Experiment || constraint is null ||
|
||||||
constraint.AllowedExperimentVenueNatures == 0 ||
|
constraint.AllowedExperimentVenueNatures == 0 ||
|
||||||
(room.TeachingVenueNature & constraint.AllowedExperimentVenueNatures) != 0) &&
|
(room.TeachingVenueNature & constraint.AllowedExperimentVenueNatures) != 0) &&
|
||||||
|
|||||||
@@ -271,16 +271,26 @@ public sealed class SchedulePlanPublisher(AppDbContext db)
|
|||||||
{
|
{
|
||||||
if (classroom is null || !classroom.IsEnabled)
|
if (classroom is null || !classroom.IsEnabled)
|
||||||
Fail(entry, "所选教室不存在或已停用");
|
Fail(entry, "所选教室不存在或已停用");
|
||||||
if (constraint?.RequiredCampusId is Guid campusId &&
|
if (entry.Kind != ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.RequiredCampusId is Guid campusId &&
|
||||||
classroom.Building!.CampusId != campusId)
|
classroom.Building!.CampusId != campusId)
|
||||||
Fail(entry, "所选教室不在指定校区");
|
Fail(entry, "所选教室不在指定校区");
|
||||||
if (constraint?.RequiredBuildingId is Guid buildingId &&
|
if (entry.Kind != ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.RequiredBuildingId is Guid buildingId &&
|
||||||
classroom.BuildingId != buildingId)
|
classroom.BuildingId != buildingId)
|
||||||
Fail(entry, "所选教室不在指定教学楼");
|
Fail(entry, "所选教室不在指定教学楼");
|
||||||
|
if (entry.Kind == ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.ExperimentRequiredCampusId is Guid experimentCampusId &&
|
||||||
|
classroom.Building!.CampusId != experimentCampusId)
|
||||||
|
Fail(entry, "所选场地不在实验课指定校区");
|
||||||
|
if (entry.Kind == ScheduleEntryKind.Experiment &&
|
||||||
|
constraint?.ExperimentRequiredBuildingId is Guid experimentBuildingId &&
|
||||||
|
classroom.BuildingId != experimentBuildingId)
|
||||||
|
Fail(entry, "所选场地不在实验课指定教学楼");
|
||||||
var allowedClassroomIds = constraint?.AllowedClassrooms
|
var allowedClassroomIds = constraint?.AllowedClassrooms
|
||||||
.Select(x => x.ClassroomId)
|
.Select(x => x.ClassroomId)
|
||||||
.ToHashSet() ?? [];
|
.ToHashSet() ?? [];
|
||||||
if (allowedClassroomIds.Count > 0 &&
|
if (entry.Kind != ScheduleEntryKind.Experiment && allowedClassroomIds.Count > 0 &&
|
||||||
!allowedClassroomIds.Contains(classroom.Id))
|
!allowedClassroomIds.Contains(classroom.Id))
|
||||||
Fail(entry, "所选教室不在指定教室范围内");
|
Fail(entry, "所选教室不在指定教室范围内");
|
||||||
var allowedExperimentClassroomIds = constraint?.AllowedExperimentClassrooms
|
var allowedExperimentClassroomIds = constraint?.AllowedExperimentClassrooms
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ const experimentVenueNatures = [
|
|||||||
{ value: 16, label: '语音室' }, { value: 32, label: '体育场地' },
|
{ value: 16, label: '语音室' }, { value: 32, label: '体育场地' },
|
||||||
{ value: 64, label: '艺术场地' },
|
{ value: 64, label: '艺术场地' },
|
||||||
]
|
]
|
||||||
|
const venueNatureValue = (value: unknown) => {
|
||||||
|
if (typeof value === 'number') return value
|
||||||
|
if (typeof value !== 'string') return 0
|
||||||
|
const names: Record<string, number> = {
|
||||||
|
GeneralClassroom: 1, Laboratory: 2, TrainingRoom: 4, ComputerLab: 8,
|
||||||
|
LanguageLab: 16, SportsVenue: 32, ArtsVenue: 64,
|
||||||
|
}
|
||||||
|
return value.split(',').reduce((sum, name) => sum | (names[name.trim()] ?? 0), 0)
|
||||||
|
}
|
||||||
const periods = computed(() => {
|
const periods = computed(() => {
|
||||||
const configured = timeSlots.value
|
const configured = timeSlots.value
|
||||||
.filter((item) => item.isEnabled)
|
.filter((item) => item.isEnabled)
|
||||||
@@ -133,12 +142,16 @@ const selectedTaskConstraint = computed(() =>
|
|||||||
)
|
)
|
||||||
const entryClassrooms = computed(() =>
|
const entryClassrooms = computed(() =>
|
||||||
classrooms.value.filter((room) =>
|
classrooms.value.filter((room) =>
|
||||||
(!selectedTaskConstraint.value?.requiredCampusId
|
(entryForm.kind === 'Experiment' || !selectedTaskConstraint.value?.requiredCampusId
|
||||||
|| room.campusId === selectedTaskConstraint.value.requiredCampusId) &&
|
|| room.campusId === selectedTaskConstraint.value.requiredCampusId) &&
|
||||||
(!selectedTaskConstraint.value?.requiredBuildingId
|
(entryForm.kind === 'Experiment' || !selectedTaskConstraint.value?.requiredBuildingId
|
||||||
|| room.buildingId === selectedTaskConstraint.value.requiredBuildingId) &&
|
|| room.buildingId === selectedTaskConstraint.value.requiredBuildingId) &&
|
||||||
(!selectedTaskConstraint.value?.allowedClassroomIds?.length
|
(entryForm.kind === 'Experiment' || !selectedTaskConstraint.value?.allowedClassroomIds?.length
|
||||||
|| selectedTaskConstraint.value.allowedClassroomIds.includes(room.id)) &&
|
|| selectedTaskConstraint.value.allowedClassroomIds.includes(room.id)) &&
|
||||||
|
(entryForm.kind !== 'Experiment' || !selectedTaskConstraint.value?.experimentRequiredCampusId
|
||||||
|
|| room.campusId === selectedTaskConstraint.value.experimentRequiredCampusId) &&
|
||||||
|
(entryForm.kind !== 'Experiment' || !selectedTaskConstraint.value?.experimentRequiredBuildingId
|
||||||
|
|| room.buildingId === selectedTaskConstraint.value.experimentRequiredBuildingId) &&
|
||||||
(entryForm.kind !== 'Experiment' ||
|
(entryForm.kind !== 'Experiment' ||
|
||||||
!selectedTaskConstraint.value?.allowedExperimentClassroomIds?.length ||
|
!selectedTaskConstraint.value?.allowedExperimentClassroomIds?.length ||
|
||||||
selectedTaskConstraint.value.allowedExperimentClassroomIds.includes(room.id)),
|
selectedTaskConstraint.value.allowedExperimentClassroomIds.includes(room.id)),
|
||||||
@@ -194,10 +207,19 @@ const filteredClassrooms = computed(() =>
|
|||||||
(!constraintForm.requiredBuildingId || item.buildingId === constraintForm.requiredBuildingId),
|
(!constraintForm.requiredBuildingId || item.buildingId === constraintForm.requiredBuildingId),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
const filteredExperimentBuildings = computed(() =>
|
||||||
|
constraintForm.experimentRequiredCampusId
|
||||||
|
? buildings.value.filter((item) => item.campusId === constraintForm.experimentRequiredCampusId)
|
||||||
|
: buildings.value,
|
||||||
|
)
|
||||||
const filteredExperimentClassrooms = computed(() =>
|
const filteredExperimentClassrooms = computed(() =>
|
||||||
filteredClassrooms.value.filter((item) =>
|
classrooms.value.filter((item) =>
|
||||||
|
(!constraintForm.experimentRequiredCampusId ||
|
||||||
|
item.campusId === constraintForm.experimentRequiredCampusId) &&
|
||||||
|
(!constraintForm.experimentRequiredBuildingId ||
|
||||||
|
item.buildingId === constraintForm.experimentRequiredBuildingId) &&
|
||||||
(!(constraintForm.allowedExperimentVenueNatures ?? []).length ||
|
(!(constraintForm.allowedExperimentVenueNatures ?? []).length ||
|
||||||
(Number(item.teachingVenueNature) & (constraintForm.allowedExperimentVenueNatures ?? [])
|
(venueNatureValue(item.teachingVenueNature) & (constraintForm.allowedExperimentVenueNatures ?? [])
|
||||||
.reduce((value: number, nature: number) => value | nature, 0)) !== 0),
|
.reduce((value: number, nature: number) => value | nature, 0)) !== 0),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -217,7 +239,7 @@ const batchFilteredClassrooms = computed(() =>
|
|||||||
const batchFilteredExperimentClassrooms = computed(() =>
|
const batchFilteredExperimentClassrooms = computed(() =>
|
||||||
batchFilteredClassrooms.value.filter((item) =>
|
batchFilteredClassrooms.value.filter((item) =>
|
||||||
(!(constraintBatchForm.allowedExperimentVenueNatures ?? []).length ||
|
(!(constraintBatchForm.allowedExperimentVenueNatures ?? []).length ||
|
||||||
(Number(item.teachingVenueNature) &
|
(venueNatureValue(item.teachingVenueNature) &
|
||||||
(constraintBatchForm.allowedExperimentVenueNatures ?? [])
|
(constraintBatchForm.allowedExperimentVenueNatures ?? [])
|
||||||
.reduce((value: number, nature: number) => value | nature, 0)) !== 0),
|
.reduce((value: number, nature: number) => value | nature, 0)) !== 0),
|
||||||
),
|
),
|
||||||
@@ -315,6 +337,8 @@ function openConstraint(item: any) {
|
|||||||
requiresClassroom: item.requiresClassroom,
|
requiresClassroom: item.requiresClassroom,
|
||||||
requiredCampusId: item.requiredCampusId,
|
requiredCampusId: item.requiredCampusId,
|
||||||
requiredBuildingId: item.requiredBuildingId,
|
requiredBuildingId: item.requiredBuildingId,
|
||||||
|
experimentRequiredCampusId: item.experimentRequiredCampusId,
|
||||||
|
experimentRequiredBuildingId: item.experimentRequiredBuildingId,
|
||||||
allowedClassroomIds: [...item.allowedClassroomIds],
|
allowedClassroomIds: [...item.allowedClassroomIds],
|
||||||
allowedExperimentClassroomIds: [...item.allowedExperimentClassroomIds],
|
allowedExperimentClassroomIds: [...item.allowedExperimentClassroomIds],
|
||||||
allowedExperimentVenueNatures: experimentVenueNatures
|
allowedExperimentVenueNatures: experimentVenueNatures
|
||||||
@@ -336,6 +360,8 @@ async function saveConstraint() {
|
|||||||
requiresClassroom: constraintForm.requiresClassroom,
|
requiresClassroom: constraintForm.requiresClassroom,
|
||||||
requiredCampusId: constraintForm.requiredCampusId || null,
|
requiredCampusId: constraintForm.requiredCampusId || null,
|
||||||
requiredBuildingId: constraintForm.requiredBuildingId || null,
|
requiredBuildingId: constraintForm.requiredBuildingId || null,
|
||||||
|
experimentRequiredCampusId: constraintForm.experimentRequiredCampusId || null,
|
||||||
|
experimentRequiredBuildingId: constraintForm.experimentRequiredBuildingId || null,
|
||||||
allowedClassroomIds: constraintForm.allowedClassroomIds ?? [],
|
allowedClassroomIds: constraintForm.allowedClassroomIds ?? [],
|
||||||
allowedExperimentClassroomIds: constraintForm.allowedExperimentClassroomIds ?? [],
|
allowedExperimentClassroomIds: constraintForm.allowedExperimentClassroomIds ?? [],
|
||||||
allowedExperimentVenueNatures: (constraintForm.allowedExperimentVenueNatures ?? [])
|
allowedExperimentVenueNatures: (constraintForm.allowedExperimentVenueNatures ?? [])
|
||||||
@@ -743,9 +769,11 @@ function changeEntryTask() {
|
|||||||
}
|
}
|
||||||
const room = classrooms.value.find((item) => item.id === entryForm.classroomId)
|
const room = classrooms.value.find((item) => item.id === entryForm.classroomId)
|
||||||
if (room && (
|
if (room && (
|
||||||
(task.requiredCampusId && room.campusId !== task.requiredCampusId) ||
|
(entryForm.kind !== 'Experiment' && task.requiredCampusId && room.campusId !== task.requiredCampusId) ||
|
||||||
(task.requiredBuildingId && room.buildingId !== task.requiredBuildingId) ||
|
(entryForm.kind !== 'Experiment' && task.requiredBuildingId && room.buildingId !== task.requiredBuildingId) ||
|
||||||
(task.allowedClassroomIds?.length && !task.allowedClassroomIds.includes(room.id)) ||
|
(entryForm.kind !== 'Experiment' && task.allowedClassroomIds?.length && !task.allowedClassroomIds.includes(room.id)) ||
|
||||||
|
(entryForm.kind === 'Experiment' && task.experimentRequiredCampusId && room.campusId !== task.experimentRequiredCampusId) ||
|
||||||
|
(entryForm.kind === 'Experiment' && task.experimentRequiredBuildingId && room.buildingId !== task.experimentRequiredBuildingId) ||
|
||||||
(entryForm.kind === 'Experiment' && task.allowedExperimentClassroomIds?.length &&
|
(entryForm.kind === 'Experiment' && task.allowedExperimentClassroomIds?.length &&
|
||||||
!task.allowedExperimentClassroomIds.includes(room.id))
|
!task.allowedExperimentClassroomIds.includes(room.id))
|
||||||
)) {
|
)) {
|
||||||
@@ -1272,6 +1300,18 @@ onBeforeUnmount(() => {
|
|||||||
<small class="field-hint">仅约束实验课;不勾选时可使用全部实验教学场地。</small>
|
<small class="field-hint">仅约束实验课;不勾选时可使用全部实验教学场地。</small>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="constraintForm.coursePracticeHours" label="实验课指定可用场地">
|
<el-form-item v-if="constraintForm.coursePracticeHours" label="实验课指定可用场地">
|
||||||
|
<div class="form-grid">
|
||||||
|
<el-form-item label="实验课限定校区">
|
||||||
|
<el-select v-model="constraintForm.experimentRequiredCampusId" clearable @change="constraintForm.experimentRequiredBuildingId = undefined; constraintForm.allowedExperimentClassroomIds = []">
|
||||||
|
<el-option v-for="item in campuses" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="实验课限定教学楼">
|
||||||
|
<el-select v-model="constraintForm.experimentRequiredBuildingId" clearable @change="constraintForm.allowedExperimentClassroomIds = []">
|
||||||
|
<el-option v-for="item in filteredExperimentBuildings" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
<el-select v-model="constraintForm.allowedExperimentClassroomIds" multiple filterable collapse-tags>
|
<el-select v-model="constraintForm.allowedExperimentClassroomIds" multiple filterable collapse-tags>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in filteredExperimentClassrooms"
|
v-for="item in filteredExperimentClassrooms"
|
||||||
|
|||||||
Reference in New Issue
Block a user