已按“实验成绩独立管理”完成修改。
每个实验项目独立建成绩单,支持评分项、参与状态、补做次数、安全违规、报告地址和教师评语。 流程为:录入 → 学院审核 → 教务发布;学生只能查看已发布成绩。 自主预约实验支持同步有效预约名单,已评分记录不会被误删。 课程成绩可将已发布实验成绩汇总为快照导入指定分项;导入后锁定,不能手工篡改。 实验项目成绩不完整时,对应课程分项保持空白,不按 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);
|
||||
Reference in New Issue
Block a user