补考、缓考成绩自动替换
This commit is contained in:
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
Reference in new issue
Block a user