补考、缓考成绩自动替换

This commit is contained in:
biss committed 2026-08-15 10:17:57 +08:00
1 parent d7d2560587
commit 51b83e9d78
7 files changed
+122 -20

No files matched your search

@@ -754,6 +754,27 @@ public sealed class MakeupExamsController(
}
var enrollmentByStudent = session.Enrollments.ToDictionary(e => e.StudentId);
var unresolvedStudentIds = scores
.Where(x => enrollmentByStudent.TryGetValue(x.StudentId, out var enrollment) &&
!enrollment.SourceGradeRecordId.HasValue)
.Select(x => x.StudentId)
.Distinct()
.ToArray();
var fallbackGradeRecordIds = unresolvedStudentIds.Length == 0
? new Dictionary<Guid, Guid>()
: (await db.GradeRecords.AsNoTracking()
.Where(x => x.GradeSheet!.TeachingTaskId == session.TeachingTaskId)
.WhereIn(unresolvedStudentIds, x => x.StudentId)
.OrderByDescending(x => x.UpdatedAt)
.Select(x => new { x.StudentId, x.Id })
.ToListAsync(cancellationToken))
.GroupBy(x => x.StudentId)
.ToDictionary(x => x.Key, x => x.First().Id);
var missingReplacementTargets = unresolvedStudentIds
.Where(studentId => !fallbackGradeRecordIds.ContainsKey(studentId))
.ToArray();
if (missingReplacementTargets.Length > 0)
return ConflictProblem("存在无法关联原课程成绩的补考考生,不能录入补考成绩。请先建立该教学班成绩单后重试。");
foreach (var entry in scores)
{
@@ -762,22 +783,54 @@ public sealed class MakeupExamsController(
enrollment.MakeupScore = entry.Score;
// Backfill legacy deferred-exam enrollments created before they
// carried SourceGradeRecordId, then use the same replacement
// path as absent and failed candidates.
if (!enrollment.SourceGradeRecordId.HasValue &&
fallbackGradeRecordIds.TryGetValue(entry.StudentId, out var fallbackGradeRecordId))
enrollment.SourceGradeRecordId = fallbackGradeRecordId;
// Update the grade record — 补考合格按60分记
if (enrollment.SourceGradeRecordId.HasValue)
{
var record = await db.GradeRecords
.Include(x => x.GradeSheet)
.ThenInclude(x => x!.Items)
.Include(x => x.ItemScores)
.FirstOrDefaultAsync(x => x.Id == enrollment.SourceGradeRecordId.Value,
cancellationToken);
if (record is not null)
{
record.ExamStatus = GradeExamStatus.Makeup;
// Cap passing score at 60
var cappedScore = entry.Score >= 60 ? 60m : entry.Score;
record.TotalScore = cappedScore;
record.GradePoint = GradeCalculator.CalculateGradePoint(cappedScore);
record.Notes = entry.Score >= 60
? $"补考合格(原始{entry.Score}分,按60分记)"
: $"补考不合格({entry.Score}分)";
if (enrollment.Reason == MakeupReason.DeferredApproved &&
record.GradeSheet is not null)
{
// A deferred exam is a later sitting of the normal
// final. Its score therefore combines with the
// already-entered regular and component scores.
record.ExamStatus = GradeExamStatus.Deferred;
record.FinalScore = entry.Score;
record.TotalScore = GradeCalculator.CalculateTotal(
record.RegularScore,
record.FinalScore,
record.ItemScores.ToList(),
record.GradeSheet.RegularWeight,
record.GradeSheet.FinalWeight,
record.GradeSheet.Items.ToList(),
record.ExamStatus);
record.GradePoint = GradeCalculator.CalculateGradePoint(record.TotalScore);
record.Notes = $"缓考成绩(期末{entry.Score}分,已按正常权重计入平时分)";
}
else
{
record.ExamStatus = GradeExamStatus.Makeup;
// Cap passing score at 60
var cappedScore = entry.Score >= 60 ? 60m : entry.Score;
record.TotalScore = cappedScore;
record.GradePoint = GradeCalculator.CalculateGradePoint(cappedScore);
record.Notes = entry.Score >= 60
? $"补考合格(原始{entry.Score}分,按60分记)"
: $"补考不合格({entry.Score}分)";
}
}
}
}
@@ -44,13 +44,33 @@ public sealed class MakeupExamEligibilityService(AppDbContext db)
})
.ToListAsync(cancellationToken);
// A deferred exam replaces the same course-grade record after the
// makeup score is entered. Keep that link at enrollment time instead
// of leaving deferred candidates without a score-replacement target.
var deferredStudentIds = deferredStudents.Select(x => x.StudentId).ToArray();
var deferredGradeRecordIds = deferredStudentIds.Length == 0
? new Dictionary<Guid, Guid>()
: (await db.GradeRecords.AsNoTracking()
.Where(x => x.GradeSheet!.TeachingTaskId == teachingTaskId)
.WhereIn(deferredStudentIds, x => x.StudentId)
.OrderByDescending(x => x.UpdatedAt)
.Select(x => new { x.StudentId, x.Id })
.ToListAsync(cancellationToken))
.GroupBy(x => x.StudentId)
.ToDictionary(x => x.Key, x => x.First().Id);
foreach (var s in deferredStudents)
{
if (!results.ContainsKey(s.StudentId))
{
var sourceGradeRecordId = deferredGradeRecordIds.TryGetValue(
s.StudentId, out var gradeRecordId)
? gradeRecordId
: (Guid?)null;
results[s.StudentId] = new EligibleStudent(
s.StudentId, s.StudentNumber, s.Name, s.ClassName,
MakeupReason.DeferredApproved, null,
MakeupReason.DeferredApproved,
SourceGradeRecordId: sourceGradeRecordId,
SourceDeferredExamId: s.Id);
}
}
@@ -22,7 +22,7 @@ public static class GradeCalculator
IReadOnlyCollection<GradeItem> items,
GradeExamStatus examStatus)
{
if (examStatus != GradeExamStatus.Normal && examStatus != GradeExamStatus.Makeup)
if (examStatus is not (GradeExamStatus.Normal or GradeExamStatus.Deferred or GradeExamStatus.Makeup))
return null;
if (regularWeight > 0 && !regularScore.HasValue)
Loaded 3 of 7 files, more files were not shown because too many files have changed in this diff. Show more