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
@@ -61,8 +61,16 @@ public sealed class GraduationAuditsController(
[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] GraduationAuditConclusion? conclusion = null,
CancellationToken token = default)
{
page = Math.Max(page, 1);
pageSize = Math.Clamp(pageSize, 10, 100);
var batch = await db.GraduationAuditBatches.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == id, token);
if (batch is null) return NotFound();
@@ -73,6 +81,59 @@ public sealed class GraduationAuditsController(
if (collegeId.HasValue)
results = results.Where(x =>
x.Student!.AdministrativeClass!.Major!.CollegeId == collegeId);
var summary = await results
.GroupBy(_ => 1)
.Select(group => new
{
ResultCount = group.Count(),
EligibleCount = group.Count(x =>
x.Conclusion == GraduationAuditConclusion.Eligible),
IneligibleCount = group.Count(x =>
x.Conclusion == GraduationAuditConclusion.Ineligible),
OverrideCount = group.Count(x => x.IsOverridden)
})
.FirstOrDefaultAsync(token);
var normalizedKeyword = keyword?.Trim();
if (!string.IsNullOrWhiteSpace(normalizedKeyword))
results = results.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)
results = results.Where(x => x.Conclusion == conclusion.Value);
var total = await results.CountAsync(token);
var items = await results
.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.StudentStatusSnapshot,
PlanName = x.CurriculumPlan != null ? x.CurriculumPlan.Name : null,
x.RequiredCredits,
x.EarnedCredits,
x.RequiredCourseCount,
x.PassedRequiredCourseCount,
x.FailedCourseCount,
x.MissingCourseNames,
x.CalculatedConclusion,
x.Conclusion,
x.IsOverridden,
x.ReviewComment,
x.ReviewedAt
}).ToListAsync(token);
return Ok(new
{
batch.Id,
@@ -83,31 +144,17 @@ public sealed class GraduationAuditsController(
batch.Notes,
batch.CalculatedAt,
batch.PublishedAt,
Results = await results
.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.StudentStatusSnapshot,
PlanName = x.CurriculumPlan != null ? x.CurriculumPlan.Name : null,
x.RequiredCredits,
x.EarnedCredits,
x.RequiredCourseCount,
x.PassedRequiredCourseCount,
x.FailedCourseCount,
x.MissingCourseNames,
x.CalculatedConclusion,
x.Conclusion,
x.IsOverridden,
x.ReviewComment,
x.ReviewedAt
}).ToListAsync(token)
ResultCount = summary?.ResultCount ?? 0,
EligibleCount = summary?.EligibleCount ?? 0,
IneligibleCount = summary?.IneligibleCount ?? 0,
OverrideCount = summary?.OverrideCount ?? 0,
Results = new
{
Items = items,
Total = total,
Page = page,
PageSize = pageSize
}
});
}
@@ -156,10 +203,11 @@ public sealed class GraduationAuditsController(
.Where(x => x.EffectiveGrade == batch.EnrollmentYear &&
x.Status == CurriculumPlanStatus.Published)
.ToListAsync(token);
var studentIds = students.Select(x => x.Id).ToArray();
var grades = await db.GradeRecords.AsNoTracking()
.Where(x => x.GradeSheet!.Status == GradeSheetStatus.Published)
.WhereIn(studentIds, x => x.StudentId)
.Where(x => x.Student!.EnrollmentYear == batch.EnrollmentYear &&
(x.Student.Status == StudentStatus.Active ||
x.Student.Status == StudentStatus.Suspended))
.Select(x => new GradeSnapshot(
x.StudentId,
x.GradeSheet!.TeachingTask!.CourseId,
@@ -169,16 +217,23 @@ public sealed class GraduationAuditsController(
x.ExamStatus))
.ToListAsync(token);
var oldResults = await db.GraduationAuditResults
.Where(x => x.GraduationAuditBatchId == id).ToListAsync(token);
db.GraduationAuditResults.RemoveRange(oldResults);
await db.SaveChangesAsync(token);
var plansByMajor = plans
.GroupBy(x => x.MajorId)
.ToDictionary(group => group.Key, group => group.First());
var gradesByStudent = grades
.GroupBy(x => x.StudentId)
.ToDictionary(group => group.Key, group => group.ToList());
await db.GraduationAuditResults
.Where(x => x.GraduationAuditBatchId == id)
.ExecuteDeleteAsync(token);
foreach (var student in students)
{
var plan = plans.FirstOrDefault(x =>
x.MajorId == student.AdministrativeClass!.MajorId);
var studentGrades = grades.Where(x => x.StudentId == student.Id).ToList();
plansByMajor.TryGetValue(
student.AdministrativeClass!.MajorId,
out var plan);
var studentGrades = gradesByStudent.GetValueOrDefault(student.Id) ?? [];
var passedCourseIds = studentGrades
.Where(IsPassed)
.Select(x => x.CourseId)