已按“实验成绩独立管理”完成修改。
每个实验项目独立建成绩单,支持评分项、参与状态、补做次数、安全违规、报告地址和教师评语。 流程为:录入 → 学院审核 → 教务发布;学生只能查看已发布成绩。 自主预约实验支持同步有效预约名单,已评分记录不会被误删。 课程成绩可将已发布实验成绩汇总为快照导入指定分项;导入后锁定,不能手工篡改。 实验项目成绩不完整时,对应课程分项保持空白,不按 0 分处理。 一级“实验管理”菜单下增加“项目与场次”“实验成绩”;学生端增加“实验安排”“实验成绩”。 已生成 MySQL 正式迁移及 SQLite 开发迁移,尚未操作生产数据库。 界面采用“实验项目 → 独立评分 → 审核发布”工作台设计。
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Grades;
|
||||
|
||||
public static class ExperimentGradeAggregationService
|
||||
{
|
||||
public static async Task<ExperimentGradeAggregateResult> CalculateAsync(
|
||||
AppDbContext db,
|
||||
Guid teachingTaskId,
|
||||
IReadOnlyCollection<Guid> studentIds,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var sheets = await db.ExperimentGradeSheets.AsNoTracking()
|
||||
.Where(x =>
|
||||
x.Status ==
|
||||
Domain.Academic.ExperimentGradeSheetStatus.Published &&
|
||||
x.ExperimentProject!.TeachingTaskId == teachingTaskId)
|
||||
.OrderBy(x => x.ExperimentProject!.Code)
|
||||
.Select(x => new PublishedExperimentSheet(
|
||||
x.Id,
|
||||
x.ExperimentProject!.Code,
|
||||
x.ExperimentProject.Name,
|
||||
x.ContributionWeight,
|
||||
x.Records.Select(record => new PublishedExperimentScore(
|
||||
record.StudentId,
|
||||
record.TotalScore)).ToList()))
|
||||
.AsSplitQuery()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var scores = new Dictionary<Guid, decimal?>();
|
||||
foreach (var studentId in studentIds.Distinct())
|
||||
{
|
||||
decimal weightedTotal = 0;
|
||||
decimal totalWeight = 0;
|
||||
var complete = sheets.Count > 0;
|
||||
foreach (var sheet in sheets)
|
||||
{
|
||||
var score = sheet.Scores.FirstOrDefault(x =>
|
||||
x.StudentId == studentId);
|
||||
if (score?.TotalScore is not decimal totalScore)
|
||||
{
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
weightedTotal += totalScore * sheet.ContributionWeight;
|
||||
totalWeight += sheet.ContributionWeight;
|
||||
}
|
||||
scores[studentId] = complete && totalWeight > 0
|
||||
? Math.Round(
|
||||
weightedTotal / totalWeight,
|
||||
1,
|
||||
MidpointRounding.AwayFromZero)
|
||||
: null;
|
||||
}
|
||||
|
||||
return new ExperimentGradeAggregateResult(
|
||||
sheets.Count,
|
||||
sheets.Select(x => new ExperimentGradeAggregateProject(
|
||||
x.Id,
|
||||
x.Code,
|
||||
x.Name,
|
||||
x.ContributionWeight)).ToList(),
|
||||
scores);
|
||||
}
|
||||
|
||||
private sealed record PublishedExperimentSheet(
|
||||
Guid Id,
|
||||
string Code,
|
||||
string Name,
|
||||
decimal ContributionWeight,
|
||||
List<PublishedExperimentScore> Scores);
|
||||
|
||||
private sealed record PublishedExperimentScore(
|
||||
Guid StudentId,
|
||||
decimal? TotalScore);
|
||||
}
|
||||
|
||||
public sealed record ExperimentGradeAggregateResult(
|
||||
int PublishedProjectCount,
|
||||
IReadOnlyList<ExperimentGradeAggregateProject> Projects,
|
||||
IReadOnlyDictionary<Guid, decimal?> Scores);
|
||||
|
||||
public sealed record ExperimentGradeAggregateProject(
|
||||
Guid Id,
|
||||
string Code,
|
||||
string Name,
|
||||
decimal ContributionWeight);
|
||||
@@ -0,0 +1,45 @@
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Grades;
|
||||
|
||||
public static class ExperimentGradeCalculator
|
||||
{
|
||||
public static bool AreWeightsValid(
|
||||
decimal contributionWeight,
|
||||
decimal passScore,
|
||||
IEnumerable<ExperimentGradeItem> items)
|
||||
{
|
||||
var itemList = items.ToList();
|
||||
return contributionWeight is > 0 and <= 100 &&
|
||||
passScore is >= 0 and <= 100 &&
|
||||
itemList.Count > 0 &&
|
||||
itemList.All(item => item.Weight is > 0 and <= 100) &&
|
||||
itemList.Sum(item => item.Weight) == 100;
|
||||
}
|
||||
|
||||
public static decimal? CalculateTotal(
|
||||
ExperimentGradeRecord record,
|
||||
IReadOnlyCollection<ExperimentGradeItem> items)
|
||||
{
|
||||
if (record.SafetyViolation ||
|
||||
record.ParticipationStatus == ExperimentParticipationStatus.Absent)
|
||||
return 0;
|
||||
if (record.ParticipationStatus is
|
||||
ExperimentParticipationStatus.Pending or
|
||||
ExperimentParticipationStatus.Excused or
|
||||
ExperimentParticipationStatus.Exempt)
|
||||
return null;
|
||||
|
||||
var scores = record.ItemScores.ToDictionary(
|
||||
x => x.ExperimentGradeItemId);
|
||||
decimal total = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!scores.TryGetValue(item.Id, out var score) ||
|
||||
!score.Score.HasValue)
|
||||
return null;
|
||||
total += score.Score.Value * item.Weight / 100;
|
||||
}
|
||||
return Math.Round(total, 1, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,14 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
public DbSet<ExperimentProject> ExperimentProjects => Set<ExperimentProject>();
|
||||
public DbSet<ExperimentSession> ExperimentSessions => Set<ExperimentSession>();
|
||||
public DbSet<ExperimentBooking> ExperimentBookings => Set<ExperimentBooking>();
|
||||
public DbSet<ExperimentGradeSheet> ExperimentGradeSheets =>
|
||||
Set<ExperimentGradeSheet>();
|
||||
public DbSet<ExperimentGradeItem> ExperimentGradeItems =>
|
||||
Set<ExperimentGradeItem>();
|
||||
public DbSet<ExperimentGradeRecord> ExperimentGradeRecords =>
|
||||
Set<ExperimentGradeRecord>();
|
||||
public DbSet<ExperimentGradeItemScore> ExperimentGradeItemScores =>
|
||||
Set<ExperimentGradeItemScore>();
|
||||
public DbSet<CourseSelectionRound> CourseSelectionRounds =>
|
||||
Set<CourseSelectionRound>();
|
||||
public DbSet<CourseSelectionRoundGrade> CourseSelectionRoundGrades =>
|
||||
@@ -608,6 +616,75 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<ExperimentGradeSheet>(entity =>
|
||||
{
|
||||
entity.Property(x => x.ContributionWeight).HasPrecision(5, 1);
|
||||
entity.Property(x => x.PassScore).HasPrecision(5, 1);
|
||||
entity.Property(x => x.ReviewComment).HasMaxLength(500);
|
||||
entity.HasIndex(x => x.ExperimentProjectId).IsUnique();
|
||||
entity.HasOne(x => x.ExperimentProject)
|
||||
.WithOne(x => x.GradeSheet)
|
||||
.HasForeignKey<ExperimentGradeSheet>(
|
||||
x => x.ExperimentProjectId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
builder.Entity<ExperimentGradeItem>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Name).HasMaxLength(60);
|
||||
entity.Property(x => x.Weight).HasPrecision(5, 1);
|
||||
entity.HasIndex(x => new
|
||||
{
|
||||
x.ExperimentGradeSheetId,
|
||||
x.SortOrder
|
||||
});
|
||||
entity.HasOne(x => x.ExperimentGradeSheet)
|
||||
.WithMany(x => x.Items)
|
||||
.HasForeignKey(x => x.ExperimentGradeSheetId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
builder.Entity<ExperimentGradeRecord>(entity =>
|
||||
{
|
||||
entity.Property(x => x.TotalScore).HasPrecision(5, 1);
|
||||
entity.Property(x => x.SubmissionReference).HasMaxLength(500);
|
||||
entity.Property(x => x.TeacherComment).HasMaxLength(500);
|
||||
entity.HasIndex(x => new
|
||||
{
|
||||
x.ExperimentGradeSheetId,
|
||||
x.StudentId
|
||||
}).IsUnique();
|
||||
entity.HasOne(x => x.ExperimentGradeSheet)
|
||||
.WithMany(x => x.Records)
|
||||
.HasForeignKey(x => x.ExperimentGradeSheetId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(x => x.Student).WithMany()
|
||||
.HasForeignKey(x => x.StudentId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
entity.HasOne(x => x.ExperimentSession).WithMany()
|
||||
.HasForeignKey(x => x.ExperimentSessionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<ExperimentGradeItemScore>(entity =>
|
||||
{
|
||||
entity.HasKey(x => new
|
||||
{
|
||||
x.ExperimentGradeRecordId,
|
||||
x.ExperimentGradeItemId
|
||||
});
|
||||
entity.Property(x => x.Score).HasPrecision(5, 1);
|
||||
entity.Property(x => x.Comment).HasMaxLength(300);
|
||||
entity.HasOne(x => x.ExperimentGradeRecord)
|
||||
.WithMany(x => x.ItemScores)
|
||||
.HasForeignKey(x => x.ExperimentGradeRecordId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(x => x.ExperimentGradeItem)
|
||||
.WithMany(x => x.Scores)
|
||||
.HasForeignKey(x => x.ExperimentGradeItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
builder.Entity<CourseSelectionRound>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Name).HasMaxLength(120);
|
||||
@@ -687,6 +764,8 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
{
|
||||
entity.Property(x => x.Name).HasMaxLength(60);
|
||||
entity.Property(x => x.Weight).HasPrecision(5, 1);
|
||||
entity.Property(x => x.SourceType)
|
||||
.HasDefaultValue(GradeItemSourceType.Manual);
|
||||
entity.HasIndex(x => new { x.GradeSheetId, x.SortOrder });
|
||||
entity.HasOne(x => x.GradeSheet)
|
||||
.WithMany(x => x.Items)
|
||||
|
||||
@@ -72,6 +72,8 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
"20260728_38_exam_publish_jobs";
|
||||
private const string ExperimentManagementMigration =
|
||||
"20260728_39_experiment_management";
|
||||
private const string ExperimentGradeManagementMigration =
|
||||
"20260728_40_experiment_grade_management";
|
||||
|
||||
public async Task MigrateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -543,6 +545,32 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
ExperimentManagementMigration,
|
||||
experimentProjectsExist ? [] : ExperimentManagementStatements,
|
||||
cancellationToken);
|
||||
|
||||
var gradeItemSourceExists = await db.Database
|
||||
.SqlQueryRaw<int>(
|
||||
"""
|
||||
SELECT COUNT(*) AS "Value"
|
||||
FROM pragma_table_info('GradeItems')
|
||||
WHERE name = 'SourceType'
|
||||
""")
|
||||
.AnyAsync(value => value > 0, cancellationToken);
|
||||
var experimentGradeSheetsExist = await db.Database
|
||||
.SqlQueryRaw<int>(
|
||||
"""
|
||||
SELECT COUNT(*) AS "Value"
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table' AND name = 'ExperimentGradeSheets'
|
||||
""")
|
||||
.AnyAsync(value => value > 0, cancellationToken);
|
||||
await ApplyMigrationAsync(
|
||||
ExperimentGradeManagementMigration,
|
||||
(gradeItemSourceExists
|
||||
? []
|
||||
: ExperimentGradeItemSourceStatements)
|
||||
.Concat(experimentGradeSheetsExist
|
||||
? []
|
||||
: ExperimentGradeManagementStatements),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ApplyMigrationAsync(
|
||||
@@ -2511,4 +2539,133 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
);
|
||||
"""
|
||||
];
|
||||
|
||||
private static readonly string[] ExperimentGradeItemSourceStatements =
|
||||
[
|
||||
"""
|
||||
ALTER TABLE "GradeItems"
|
||||
ADD "SourceType" INTEGER NOT NULL DEFAULT 1;
|
||||
""",
|
||||
"""
|
||||
ALTER TABLE "GradeItems"
|
||||
ADD "SourceSnapshotAt" TEXT NULL;
|
||||
"""
|
||||
];
|
||||
|
||||
private static readonly string[] ExperimentGradeManagementStatements =
|
||||
[
|
||||
"""
|
||||
CREATE TABLE "ExperimentGradeSheets" (
|
||||
"Id" TEXT NOT NULL
|
||||
CONSTRAINT "PK_ExperimentGradeSheets" PRIMARY KEY,
|
||||
"ExperimentProjectId" TEXT NOT NULL,
|
||||
"ContributionWeight" TEXT NOT NULL,
|
||||
"PassScore" TEXT NOT NULL,
|
||||
"Status" INTEGER NOT NULL,
|
||||
"ReviewComment" TEXT NULL,
|
||||
"SubmittedAt" TEXT NULL,
|
||||
"ReviewedAt" TEXT NULL,
|
||||
"PublishedAt" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL,
|
||||
"UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_ExperimentGradeSheets_ExperimentProjects_ExperimentProjectId"
|
||||
FOREIGN KEY ("ExperimentProjectId")
|
||||
REFERENCES "ExperimentProjects" ("Id")
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE UNIQUE INDEX "IX_ExperimentGradeSheets_ExperimentProjectId"
|
||||
ON "ExperimentGradeSheets" ("ExperimentProjectId");
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE "ExperimentGradeItems" (
|
||||
"Id" TEXT NOT NULL
|
||||
CONSTRAINT "PK_ExperimentGradeItems" PRIMARY KEY,
|
||||
"ExperimentGradeSheetId" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"Kind" INTEGER NOT NULL,
|
||||
"Weight" TEXT NOT NULL,
|
||||
"SortOrder" INTEGER NOT NULL,
|
||||
"CreatedAt" TEXT NOT NULL,
|
||||
"UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_ExperimentGradeItems_ExperimentGradeSheets_ExperimentGradeSheetId"
|
||||
FOREIGN KEY ("ExperimentGradeSheetId")
|
||||
REFERENCES "ExperimentGradeSheets" ("Id")
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX "IX_ExperimentGradeItems_ExperimentGradeSheetId_SortOrder"
|
||||
ON "ExperimentGradeItems" ("ExperimentGradeSheetId", "SortOrder");
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE "ExperimentGradeRecords" (
|
||||
"Id" TEXT NOT NULL
|
||||
CONSTRAINT "PK_ExperimentGradeRecords" PRIMARY KEY,
|
||||
"ExperimentGradeSheetId" TEXT NOT NULL,
|
||||
"StudentId" TEXT NOT NULL,
|
||||
"ExperimentSessionId" TEXT NULL,
|
||||
"ParticipationStatus" INTEGER NOT NULL,
|
||||
"TotalScore" TEXT NULL,
|
||||
"IsPassed" INTEGER NULL,
|
||||
"SafetyViolation" INTEGER NOT NULL,
|
||||
"AttemptNumber" INTEGER NOT NULL,
|
||||
"SubmissionReference" TEXT NULL,
|
||||
"SubmittedAt" TEXT NULL,
|
||||
"IsLate" INTEGER NOT NULL,
|
||||
"TeacherComment" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL,
|
||||
"UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_ExperimentGradeRecords_ExperimentGradeSheets_ExperimentGradeSheetId"
|
||||
FOREIGN KEY ("ExperimentGradeSheetId")
|
||||
REFERENCES "ExperimentGradeSheets" ("Id")
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_ExperimentGradeRecords_Students_StudentId"
|
||||
FOREIGN KEY ("StudentId") REFERENCES "Students" ("Id")
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT "FK_ExperimentGradeRecords_ExperimentSessions_ExperimentSessionId"
|
||||
FOREIGN KEY ("ExperimentSessionId")
|
||||
REFERENCES "ExperimentSessions" ("Id")
|
||||
ON DELETE RESTRICT
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE UNIQUE INDEX "IX_ExperimentGradeRecords_ExperimentGradeSheetId_StudentId"
|
||||
ON "ExperimentGradeRecords" ("ExperimentGradeSheetId", "StudentId");
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX "IX_ExperimentGradeRecords_StudentId"
|
||||
ON "ExperimentGradeRecords" ("StudentId");
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX "IX_ExperimentGradeRecords_ExperimentSessionId"
|
||||
ON "ExperimentGradeRecords" ("ExperimentSessionId");
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE "ExperimentGradeItemScores" (
|
||||
"ExperimentGradeRecordId" TEXT NOT NULL,
|
||||
"ExperimentGradeItemId" TEXT NOT NULL,
|
||||
"Score" TEXT NULL,
|
||||
"Comment" TEXT NULL,
|
||||
CONSTRAINT "PK_ExperimentGradeItemScores"
|
||||
PRIMARY KEY (
|
||||
"ExperimentGradeRecordId",
|
||||
"ExperimentGradeItemId"
|
||||
),
|
||||
CONSTRAINT "FK_ExperimentGradeItemScores_ExperimentGradeRecords_ExperimentGradeRecordId"
|
||||
FOREIGN KEY ("ExperimentGradeRecordId")
|
||||
REFERENCES "ExperimentGradeRecords" ("Id")
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_ExperimentGradeItemScores_ExperimentGradeItems_ExperimentGradeItemId"
|
||||
FOREIGN KEY ("ExperimentGradeItemId")
|
||||
REFERENCES "ExperimentGradeItems" ("Id")
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX "IX_ExperimentGradeItemScores_ExperimentGradeItemId"
|
||||
ON "ExperimentGradeItemScores" ("ExperimentGradeItemId");
|
||||
"""
|
||||
];
|
||||
}
|
||||
|
||||
+5905
File diff suppressed because it is too large
Load Diff
+208
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ExperimentGradeManagement : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "SourceSnapshotAt",
|
||||
table: "GradeItems",
|
||||
type: "datetime(6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SourceType",
|
||||
table: "GradeItems",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 1);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExperimentGradeSheets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
ExperimentProjectId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
ContributionWeight = table.Column<decimal>(type: "decimal(5,1)", precision: 5, scale: 1, nullable: false),
|
||||
PassScore = table.Column<decimal>(type: "decimal(5,1)", precision: 5, scale: 1, nullable: false),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
ReviewComment = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
|
||||
SubmittedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
ReviewedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
PublishedAt = 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_ExperimentGradeSheets", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExperimentGradeSheets_ExperimentProjects_ExperimentProjectId",
|
||||
column: x => x.ExperimentProjectId,
|
||||
principalTable: "ExperimentProjects",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExperimentGradeItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
ExperimentGradeSheetId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "varchar(60)", maxLength: 60, nullable: false),
|
||||
Kind = table.Column<int>(type: "int", nullable: false),
|
||||
Weight = table.Column<decimal>(type: "decimal(5,1)", precision: 5, scale: 1, nullable: false),
|
||||
SortOrder = 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_ExperimentGradeItems", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExperimentGradeItems_ExperimentGradeSheets_ExperimentGradeSh~",
|
||||
column: x => x.ExperimentGradeSheetId,
|
||||
principalTable: "ExperimentGradeSheets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExperimentGradeRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
ExperimentGradeSheetId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
StudentId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
ExperimentSessionId = table.Column<Guid>(type: "char(36)", nullable: true),
|
||||
ParticipationStatus = table.Column<int>(type: "int", nullable: false),
|
||||
TotalScore = table.Column<decimal>(type: "decimal(5,1)", precision: 5, scale: 1, nullable: true),
|
||||
IsPassed = table.Column<bool>(type: "tinyint(1)", nullable: true),
|
||||
SafetyViolation = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
AttemptNumber = table.Column<int>(type: "int", nullable: false),
|
||||
SubmissionReference = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
|
||||
SubmittedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
IsLate = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
TeacherComment = table.Column<string>(type: "varchar(500)", maxLength: 500, 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_ExperimentGradeRecords", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExperimentGradeRecords_ExperimentGradeSheets_ExperimentGrade~",
|
||||
column: x => x.ExperimentGradeSheetId,
|
||||
principalTable: "ExperimentGradeSheets",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExperimentGradeRecords_ExperimentSessions_ExperimentSessionId",
|
||||
column: x => x.ExperimentSessionId,
|
||||
principalTable: "ExperimentSessions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExperimentGradeRecords_Students_StudentId",
|
||||
column: x => x.StudentId,
|
||||
principalTable: "Students",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExperimentGradeItemScores",
|
||||
columns: table => new
|
||||
{
|
||||
ExperimentGradeRecordId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
ExperimentGradeItemId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Score = table.Column<decimal>(type: "decimal(5,1)", precision: 5, scale: 1, nullable: true),
|
||||
Comment = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ExperimentGradeItemScores", x => new { x.ExperimentGradeRecordId, x.ExperimentGradeItemId });
|
||||
table.ForeignKey(
|
||||
name: "FK_ExperimentGradeItemScores_ExperimentGradeItems_ExperimentGra~",
|
||||
column: x => x.ExperimentGradeItemId,
|
||||
principalTable: "ExperimentGradeItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExperimentGradeItemScores_ExperimentGradeRecords_ExperimentG~",
|
||||
column: x => x.ExperimentGradeRecordId,
|
||||
principalTable: "ExperimentGradeRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExperimentGradeItems_ExperimentGradeSheetId_SortOrder",
|
||||
table: "ExperimentGradeItems",
|
||||
columns: new[] { "ExperimentGradeSheetId", "SortOrder" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExperimentGradeItemScores_ExperimentGradeItemId",
|
||||
table: "ExperimentGradeItemScores",
|
||||
column: "ExperimentGradeItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExperimentGradeRecords_ExperimentGradeSheetId_StudentId",
|
||||
table: "ExperimentGradeRecords",
|
||||
columns: new[] { "ExperimentGradeSheetId", "StudentId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExperimentGradeRecords_ExperimentSessionId",
|
||||
table: "ExperimentGradeRecords",
|
||||
column: "ExperimentSessionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExperimentGradeRecords_StudentId",
|
||||
table: "ExperimentGradeRecords",
|
||||
column: "StudentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExperimentGradeSheets_ExperimentProjectId",
|
||||
table: "ExperimentGradeSheets",
|
||||
column: "ExperimentProjectId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExperimentGradeItemScores");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExperimentGradeItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExperimentGradeRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExperimentGradeSheets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SourceSnapshotAt",
|
||||
table: "GradeItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SourceType",
|
||||
table: "GradeItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
+264
@@ -2043,6 +2043,176 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.ToTable("ExperimentBookings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("ExperimentGradeSheetId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("varchar(60)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<decimal>("Weight")
|
||||
.HasPrecision(5, 1)
|
||||
.HasColumnType("decimal(5,1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExperimentGradeSheetId", "SortOrder");
|
||||
|
||||
b.ToTable("ExperimentGradeItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeItemScore", b =>
|
||||
{
|
||||
b.Property<Guid>("ExperimentGradeRecordId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("ExperimentGradeItemId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("varchar(300)");
|
||||
|
||||
b.Property<decimal?>("Score")
|
||||
.HasPrecision(5, 1)
|
||||
.HasColumnType("decimal(5,1)");
|
||||
|
||||
b.HasKey("ExperimentGradeRecordId", "ExperimentGradeItemId");
|
||||
|
||||
b.HasIndex("ExperimentGradeItemId");
|
||||
|
||||
b.ToTable("ExperimentGradeItemScores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeRecord", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("AttemptNumber")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("ExperimentGradeSheetId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("ExperimentSessionId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsLate")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool?>("IsPassed")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("ParticipationStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("SafetyViolation")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<Guid>("StudentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("SubmissionReference")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<DateTime?>("SubmittedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("TeacherComment")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<decimal?>("TotalScore")
|
||||
.HasPrecision(5, 1)
|
||||
.HasColumnType("decimal(5,1)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExperimentSessionId");
|
||||
|
||||
b.HasIndex("StudentId");
|
||||
|
||||
b.HasIndex("ExperimentGradeSheetId", "StudentId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ExperimentGradeRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeSheet", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<decimal>("ContributionWeight")
|
||||
.HasPrecision(5, 1)
|
||||
.HasColumnType("decimal(5,1)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("ExperimentProjectId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<decimal>("PassScore")
|
||||
.HasPrecision(5, 1)
|
||||
.HasColumnType("decimal(5,1)");
|
||||
|
||||
b.Property<DateTime?>("PublishedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("ReviewComment")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<DateTime?>("ReviewedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("SubmittedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExperimentProjectId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ExperimentGradeSheets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentProject", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -2176,6 +2346,14 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("SourceSnapshotAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("SourceType")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@@ -4812,6 +4990,73 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("Student");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeItem", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentGradeSheet", "ExperimentGradeSheet")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("ExperimentGradeSheetId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExperimentGradeSheet");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeItemScore", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentGradeItem", "ExperimentGradeItem")
|
||||
.WithMany("Scores")
|
||||
.HasForeignKey("ExperimentGradeItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentGradeRecord", "ExperimentGradeRecord")
|
||||
.WithMany("ItemScores")
|
||||
.HasForeignKey("ExperimentGradeRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExperimentGradeItem");
|
||||
|
||||
b.Navigation("ExperimentGradeRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeRecord", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentGradeSheet", "ExperimentGradeSheet")
|
||||
.WithMany("Records")
|
||||
.HasForeignKey("ExperimentGradeSheetId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentSession", "ExperimentSession")
|
||||
.WithMany()
|
||||
.HasForeignKey("ExperimentSessionId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student")
|
||||
.WithMany()
|
||||
.HasForeignKey("StudentId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExperimentGradeSheet");
|
||||
|
||||
b.Navigation("ExperimentSession");
|
||||
|
||||
b.Navigation("Student");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeSheet", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.ExperimentProject", "ExperimentProject")
|
||||
.WithOne("GradeSheet")
|
||||
.HasForeignKey("Jiaowu.Api.Domain.Academic.ExperimentGradeSheet", "ExperimentProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExperimentProject");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentProject", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask")
|
||||
@@ -5550,10 +5795,29 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("SeatAssignments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeItem", b =>
|
||||
{
|
||||
b.Navigation("Scores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeRecord", b =>
|
||||
{
|
||||
b.Navigation("ItemScores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentGradeSheet", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
||||
b.Navigation("Records");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExperimentProject", b =>
|
||||
{
|
||||
b.Navigation("Bookings");
|
||||
|
||||
b.Navigation("GradeSheet");
|
||||
|
||||
b.Navigation("Sessions");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user