This commit is contained in:
2026-07-26 22:16:58 +08:00 Unverified
parent dd1b752fa3
commit bc6ffa2ada
7 changed files with 582 additions and 146 deletions
@@ -60,8 +60,16 @@ public sealed class DegreeAwardsController(
[HttpGet("batches/{id:guid}")]
[Authorize(Roles = Reviewers)]
public async Task<ActionResult> GetBatch(Guid id, CancellationToken token)
public async Task<ActionResult> GetBatch(
Guid id,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? keyword = null,
[FromQuery] DegreeAwardConclusion? conclusion = null,
CancellationToken token = default)
{
page = Math.Max(page, 1);
pageSize = Math.Clamp(pageSize, 10, 100);
var batch = await db.DegreeAwardBatches.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == id, token);
if (batch is null) return NotFound();
@@ -71,6 +79,52 @@ public sealed class DegreeAwardsController(
if (collegeId.HasValue)
source = source.Where(x =>
x.Student!.AdministrativeClass!.Major!.CollegeId == collegeId);
var summary = await source
.GroupBy(_ => 1)
.Select(group => new
{
ResultCount = group.Count(),
GrantedCount = group.Count(x =>
x.Conclusion == DegreeAwardConclusion.Granted),
NotGrantedCount = group.Count(x =>
x.Conclusion == DegreeAwardConclusion.NotGranted),
OverrideCount = group.Count(x => x.IsOverridden)
})
.FirstOrDefaultAsync(token);
var normalizedKeyword = keyword?.Trim();
if (!string.IsNullOrWhiteSpace(normalizedKeyword))
source = source.Where(x =>
x.Student!.StudentNumber.Contains(normalizedKeyword) ||
x.Student.Name.Contains(normalizedKeyword) ||
x.Student.AdministrativeClass!.Name.Contains(normalizedKeyword) ||
x.Student.AdministrativeClass.Major!.Name.Contains(normalizedKeyword));
if (conclusion.HasValue)
source = source.Where(x => x.Conclusion == conclusion.Value);
var total = await source.CountAsync(token);
var items = await source.OrderBy(x => x.Student!.StudentNumber)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new
{
x.Id,
x.StudentId,
x.Student!.StudentNumber,
x.Student.Name,
ClassName = x.Student.AdministrativeClass!.Name,
MajorName = x.Student.AdministrativeClass.Major!.Name,
CollegeName = x.Student.AdministrativeClass.Major.College!.Name,
x.AverageGradePoint,
x.CalculatedConclusion,
x.Conclusion,
x.ExceptionReason,
x.IsOverridden,
x.ReviewComment,
x.ReviewedAt
}).ToListAsync(token);
return Ok(new
{
batch.Id,
@@ -82,24 +136,17 @@ public sealed class DegreeAwardsController(
batch.Notes,
batch.CalculatedAt,
batch.PublishedAt,
Results = await source.OrderBy(x => x.Student!.StudentNumber)
.Select(x => new
{
x.Id,
x.StudentId,
x.Student!.StudentNumber,
x.Student.Name,
ClassName = x.Student.AdministrativeClass!.Name,
MajorName = x.Student.AdministrativeClass.Major!.Name,
CollegeName = x.Student.AdministrativeClass.Major.College!.Name,
x.AverageGradePoint,
x.CalculatedConclusion,
x.Conclusion,
x.ExceptionReason,
x.IsOverridden,
x.ReviewComment,
x.ReviewedAt
}).ToListAsync(token)
ResultCount = summary?.ResultCount ?? 0,
GrantedCount = summary?.GrantedCount ?? 0,
NotGrantedCount = summary?.NotGrantedCount ?? 0,
OverrideCount = summary?.OverrideCount ?? 0,
Results = new
{
Items = items,
Total = total,
Page = page,
PageSize = pageSize
}
});
}
@@ -133,25 +180,30 @@ public sealed class DegreeAwardsController(
return ConflictProblem("已发布批次不能重新计算。");
var auditCandidates = await db.GraduationAuditResults.AsNoTracking()
.Include(x => x.GraduationAuditBatch)
.Include(x => x.Student)
.ThenInclude(x => x!.AdministrativeClass)
.ThenInclude(x => x!.Major)
.Where(x =>
x.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
x.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
x.Conclusion == GraduationAuditConclusion.Eligible)
.OrderByDescending(x => x.GraduationAuditBatch!.PublishedAt)
.Select(x => new
{
x.Id,
x.StudentId,
StudentStatus = x.Student!.Status
})
.ToListAsync(token);
var audits = auditCandidates
.GroupBy(x => x.StudentId)
.Select(x => x.First())
.ToList();
var studentIds = audits.Select(x => x.StudentId).ToArray();
var gradePoints = await db.GradeRecords.AsNoTracking()
.Where(x => x.GradeSheet!.Status == GradeSheetStatus.Published &&
x.GradePoint.HasValue)
.WhereIn(studentIds, x => x.StudentId)
.Where(x => db.GraduationAuditResults.Any(audit =>
audit.StudentId == x.StudentId &&
audit.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
audit.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
audit.Conclusion == GraduationAuditConclusion.Eligible))
.Select(x => new
{
x.StudentId,
@@ -159,14 +211,17 @@ public sealed class DegreeAwardsController(
x.GradeSheet!.TeachingTask!.Course!.Credits
}).ToListAsync(token);
var oldResults = await db.DegreeAwardResults
.Where(x => x.DegreeAwardBatchId == id).ToListAsync(token);
db.DegreeAwardResults.RemoveRange(oldResults);
await db.SaveChangesAsync(token);
var gradePointsByStudent = gradePoints
.GroupBy(x => x.StudentId)
.ToDictionary(group => group.Key, group => group.ToList());
await db.DegreeAwardResults
.Where(x => x.DegreeAwardBatchId == id)
.ExecuteDeleteAsync(token);
foreach (var audit in audits)
{
var grades = gradePoints.Where(x => x.StudentId == audit.StudentId).ToList();
var grades = gradePointsByStudent.GetValueOrDefault(audit.StudentId) ?? [];
var credits = grades.Sum(x => x.Credits);
var average = credits == 0
? 0
@@ -175,8 +230,8 @@ public sealed class DegreeAwardsController(
2,
MidpointRounding.AwayFromZero);
var conclusion = DegreeAwardRules.Evaluate(
true, audit.Student!.Status, average, batch.MinimumGradePoint);
var reason = audit.Student.Status != StudentStatus.Graduated
true, audit.StudentStatus, average, batch.MinimumGradePoint);
var reason = audit.StudentStatus != StudentStatus.Graduated
? "学籍状态尚未转为毕业"
: average < batch.MinimumGradePoint
? $"平均绩点 {average:0.00},低于批次要求 {batch.MinimumGradePoint:0.00}"