优化
This commit is contained in:
1 parent
dd1b752fa3
commit
bc6ffa2ada
7 files changed
+562
-126
No files matched your search
@@ -60,8 +60,16 @@ public sealed class DegreeAwardsController(
|
|||||||
|
|
||||||
[HttpGet("batches/{id:guid}")]
|
[HttpGet("batches/{id:guid}")]
|
||||||
[Authorize(Roles = Reviewers)]
|
[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()
|
var batch = await db.DegreeAwardBatches.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||||
if (batch is null) return NotFound();
|
if (batch is null) return NotFound();
|
||||||
@@ -71,18 +79,34 @@ public sealed class DegreeAwardsController(
|
|||||||
if (collegeId.HasValue)
|
if (collegeId.HasValue)
|
||||||
source = source.Where(x =>
|
source = source.Where(x =>
|
||||||
x.Student!.AdministrativeClass!.Major!.CollegeId == collegeId);
|
x.Student!.AdministrativeClass!.Major!.CollegeId == collegeId);
|
||||||
return Ok(new
|
|
||||||
|
var summary = await source
|
||||||
|
.GroupBy(_ => 1)
|
||||||
|
.Select(group => new
|
||||||
{
|
{
|
||||||
batch.Id,
|
ResultCount = group.Count(),
|
||||||
batch.Name,
|
GrantedCount = group.Count(x =>
|
||||||
batch.GraduationYear,
|
x.Conclusion == DegreeAwardConclusion.Granted),
|
||||||
batch.DegreeName,
|
NotGrantedCount = group.Count(x =>
|
||||||
batch.MinimumGradePoint,
|
x.Conclusion == DegreeAwardConclusion.NotGranted),
|
||||||
batch.Status,
|
OverrideCount = group.Count(x => x.IsOverridden)
|
||||||
batch.Notes,
|
})
|
||||||
batch.CalculatedAt,
|
.FirstOrDefaultAsync(token);
|
||||||
batch.PublishedAt,
|
|
||||||
Results = await source.OrderBy(x => x.Student!.StudentNumber)
|
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
|
.Select(x => new
|
||||||
{
|
{
|
||||||
x.Id,
|
x.Id,
|
||||||
@@ -99,7 +123,30 @@ public sealed class DegreeAwardsController(
|
|||||||
x.IsOverridden,
|
x.IsOverridden,
|
||||||
x.ReviewComment,
|
x.ReviewComment,
|
||||||
x.ReviewedAt
|
x.ReviewedAt
|
||||||
}).ToListAsync(token)
|
}).ToListAsync(token);
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
batch.Id,
|
||||||
|
batch.Name,
|
||||||
|
batch.GraduationYear,
|
||||||
|
batch.DegreeName,
|
||||||
|
batch.MinimumGradePoint,
|
||||||
|
batch.Status,
|
||||||
|
batch.Notes,
|
||||||
|
batch.CalculatedAt,
|
||||||
|
batch.PublishedAt,
|
||||||
|
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("已发布批次不能重新计算。");
|
return ConflictProblem("已发布批次不能重新计算。");
|
||||||
|
|
||||||
var auditCandidates = await db.GraduationAuditResults.AsNoTracking()
|
var auditCandidates = await db.GraduationAuditResults.AsNoTracking()
|
||||||
.Include(x => x.GraduationAuditBatch)
|
|
||||||
.Include(x => x.Student)
|
|
||||||
.ThenInclude(x => x!.AdministrativeClass)
|
|
||||||
.ThenInclude(x => x!.Major)
|
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
x.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
|
x.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
|
||||||
x.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
|
x.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
|
||||||
x.Conclusion == GraduationAuditConclusion.Eligible)
|
x.Conclusion == GraduationAuditConclusion.Eligible)
|
||||||
.OrderByDescending(x => x.GraduationAuditBatch!.PublishedAt)
|
.OrderByDescending(x => x.GraduationAuditBatch!.PublishedAt)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.Id,
|
||||||
|
x.StudentId,
|
||||||
|
StudentStatus = x.Student!.Status
|
||||||
|
})
|
||||||
.ToListAsync(token);
|
.ToListAsync(token);
|
||||||
var audits = auditCandidates
|
var audits = auditCandidates
|
||||||
.GroupBy(x => x.StudentId)
|
.GroupBy(x => x.StudentId)
|
||||||
.Select(x => x.First())
|
.Select(x => x.First())
|
||||||
.ToList();
|
.ToList();
|
||||||
var studentIds = audits.Select(x => x.StudentId).ToArray();
|
|
||||||
var gradePoints = await db.GradeRecords.AsNoTracking()
|
var gradePoints = await db.GradeRecords.AsNoTracking()
|
||||||
.Where(x => x.GradeSheet!.Status == GradeSheetStatus.Published &&
|
.Where(x => x.GradeSheet!.Status == GradeSheetStatus.Published &&
|
||||||
x.GradePoint.HasValue)
|
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
|
.Select(x => new
|
||||||
{
|
{
|
||||||
x.StudentId,
|
x.StudentId,
|
||||||
@@ -159,14 +211,17 @@ public sealed class DegreeAwardsController(
|
|||||||
x.GradeSheet!.TeachingTask!.Course!.Credits
|
x.GradeSheet!.TeachingTask!.Course!.Credits
|
||||||
}).ToListAsync(token);
|
}).ToListAsync(token);
|
||||||
|
|
||||||
var oldResults = await db.DegreeAwardResults
|
var gradePointsByStudent = gradePoints
|
||||||
.Where(x => x.DegreeAwardBatchId == id).ToListAsync(token);
|
.GroupBy(x => x.StudentId)
|
||||||
db.DegreeAwardResults.RemoveRange(oldResults);
|
.ToDictionary(group => group.Key, group => group.ToList());
|
||||||
await db.SaveChangesAsync(token);
|
|
||||||
|
await db.DegreeAwardResults
|
||||||
|
.Where(x => x.DegreeAwardBatchId == id)
|
||||||
|
.ExecuteDeleteAsync(token);
|
||||||
|
|
||||||
foreach (var audit in audits)
|
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 credits = grades.Sum(x => x.Credits);
|
||||||
var average = credits == 0
|
var average = credits == 0
|
||||||
? 0
|
? 0
|
||||||
@@ -175,8 +230,8 @@ public sealed class DegreeAwardsController(
|
|||||||
2,
|
2,
|
||||||
MidpointRounding.AwayFromZero);
|
MidpointRounding.AwayFromZero);
|
||||||
var conclusion = DegreeAwardRules.Evaluate(
|
var conclusion = DegreeAwardRules.Evaluate(
|
||||||
true, audit.Student!.Status, average, batch.MinimumGradePoint);
|
true, audit.StudentStatus, average, batch.MinimumGradePoint);
|
||||||
var reason = audit.Student.Status != StudentStatus.Graduated
|
var reason = audit.StudentStatus != StudentStatus.Graduated
|
||||||
? "学籍状态尚未转为毕业"
|
? "学籍状态尚未转为毕业"
|
||||||
: average < batch.MinimumGradePoint
|
: average < batch.MinimumGradePoint
|
||||||
? $"平均绩点 {average:0.00},低于批次要求 {batch.MinimumGradePoint:0.00}"
|
? $"平均绩点 {average:0.00},低于批次要求 {batch.MinimumGradePoint:0.00}"
|
||||||
|
|||||||
@@ -61,8 +61,16 @@ public sealed class GraduationAuditsController(
|
|||||||
|
|
||||||
[HttpGet("batches/{id:guid}")]
|
[HttpGet("batches/{id:guid}")]
|
||||||
[Authorize(Roles = Reviewers)]
|
[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()
|
var batch = await db.GraduationAuditBatches.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||||
if (batch is null) return NotFound();
|
if (batch is null) return NotFound();
|
||||||
@@ -73,18 +81,35 @@ public sealed class GraduationAuditsController(
|
|||||||
if (collegeId.HasValue)
|
if (collegeId.HasValue)
|
||||||
results = results.Where(x =>
|
results = results.Where(x =>
|
||||||
x.Student!.AdministrativeClass!.Major!.CollegeId == collegeId);
|
x.Student!.AdministrativeClass!.Major!.CollegeId == collegeId);
|
||||||
return Ok(new
|
|
||||||
|
var summary = await results
|
||||||
|
.GroupBy(_ => 1)
|
||||||
|
.Select(group => new
|
||||||
{
|
{
|
||||||
batch.Id,
|
ResultCount = group.Count(),
|
||||||
batch.Name,
|
EligibleCount = group.Count(x =>
|
||||||
batch.GraduationYear,
|
x.Conclusion == GraduationAuditConclusion.Eligible),
|
||||||
batch.EnrollmentYear,
|
IneligibleCount = group.Count(x =>
|
||||||
batch.Status,
|
x.Conclusion == GraduationAuditConclusion.Ineligible),
|
||||||
batch.Notes,
|
OverrideCount = group.Count(x => x.IsOverridden)
|
||||||
batch.CalculatedAt,
|
})
|
||||||
batch.PublishedAt,
|
.FirstOrDefaultAsync(token);
|
||||||
Results = await results
|
|
||||||
|
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)
|
.OrderBy(x => x.Student!.StudentNumber)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
.Select(x => new
|
.Select(x => new
|
||||||
{
|
{
|
||||||
x.Id,
|
x.Id,
|
||||||
@@ -107,7 +132,29 @@ public sealed class GraduationAuditsController(
|
|||||||
x.IsOverridden,
|
x.IsOverridden,
|
||||||
x.ReviewComment,
|
x.ReviewComment,
|
||||||
x.ReviewedAt
|
x.ReviewedAt
|
||||||
}).ToListAsync(token)
|
}).ToListAsync(token);
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
batch.Id,
|
||||||
|
batch.Name,
|
||||||
|
batch.GraduationYear,
|
||||||
|
batch.EnrollmentYear,
|
||||||
|
batch.Status,
|
||||||
|
batch.Notes,
|
||||||
|
batch.CalculatedAt,
|
||||||
|
batch.PublishedAt,
|
||||||
|
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 &&
|
.Where(x => x.EffectiveGrade == batch.EnrollmentYear &&
|
||||||
x.Status == CurriculumPlanStatus.Published)
|
x.Status == CurriculumPlanStatus.Published)
|
||||||
.ToListAsync(token);
|
.ToListAsync(token);
|
||||||
var studentIds = students.Select(x => x.Id).ToArray();
|
|
||||||
var grades = await db.GradeRecords.AsNoTracking()
|
var grades = await db.GradeRecords.AsNoTracking()
|
||||||
.Where(x => x.GradeSheet!.Status == GradeSheetStatus.Published)
|
.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(
|
.Select(x => new GradeSnapshot(
|
||||||
x.StudentId,
|
x.StudentId,
|
||||||
x.GradeSheet!.TeachingTask!.CourseId,
|
x.GradeSheet!.TeachingTask!.CourseId,
|
||||||
@@ -169,16 +217,23 @@ public sealed class GraduationAuditsController(
|
|||||||
x.ExamStatus))
|
x.ExamStatus))
|
||||||
.ToListAsync(token);
|
.ToListAsync(token);
|
||||||
|
|
||||||
var oldResults = await db.GraduationAuditResults
|
var plansByMajor = plans
|
||||||
.Where(x => x.GraduationAuditBatchId == id).ToListAsync(token);
|
.GroupBy(x => x.MajorId)
|
||||||
db.GraduationAuditResults.RemoveRange(oldResults);
|
.ToDictionary(group => group.Key, group => group.First());
|
||||||
await db.SaveChangesAsync(token);
|
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)
|
foreach (var student in students)
|
||||||
{
|
{
|
||||||
var plan = plans.FirstOrDefault(x =>
|
plansByMajor.TryGetValue(
|
||||||
x.MajorId == student.AdministrativeClass!.MajorId);
|
student.AdministrativeClass!.MajorId,
|
||||||
var studentGrades = grades.Where(x => x.StudentId == student.Id).ToList();
|
out var plan);
|
||||||
|
var studentGrades = gradesByStudent.GetValueOrDefault(student.Id) ?? [];
|
||||||
var passedCourseIds = studentGrades
|
var passedCourseIds = studentGrades
|
||||||
.Where(IsPassed)
|
.Where(IsPassed)
|
||||||
.Select(x => x.CourseId)
|
.Select(x => x.CourseId)
|
||||||
|
|||||||
@@ -34,31 +34,46 @@ public sealed class GraduationClearanceController(
|
|||||||
x.Id, x.Name, x.GraduationYear, x.Status, x.Notes,
|
x.Id, x.Name, x.GraduationYear, x.Status, x.Notes,
|
||||||
x.ClosedAt, x.CreatedAt, ItemCount = x.Items.Count
|
x.ClosedAt, x.CreatedAt, ItemCount = x.Items.Count
|
||||||
}).ToListAsync(token);
|
}).ToListAsync(token);
|
||||||
var records = await ScopedRecords().AsNoTracking()
|
var statistics = await ScopedRecords().AsNoTracking()
|
||||||
.Select(x => new
|
.GroupBy(x => x.GraduationClearanceItem!.GraduationClearanceBatchId)
|
||||||
|
.Select(group => new
|
||||||
{
|
{
|
||||||
BatchId = x.GraduationClearanceItem!.GraduationClearanceBatchId,
|
BatchId = group.Key,
|
||||||
x.StudentId, x.Status
|
StudentCount = group.Select(x => x.StudentId).Distinct().Count(),
|
||||||
}).ToListAsync(token);
|
RecordCount = group.Count(),
|
||||||
|
CompletedCount = group.Count(x =>
|
||||||
|
x.Status != GraduationClearanceRecordStatus.Pending)
|
||||||
|
}).ToDictionaryAsync(x => x.BatchId, token);
|
||||||
return Ok(batches.Select(batch =>
|
return Ok(batches.Select(batch =>
|
||||||
{
|
{
|
||||||
var scoped = records.Where(x => x.BatchId == batch.Id).ToList();
|
statistics.TryGetValue(batch.Id, out var summary);
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
batch.Id, batch.Name, batch.GraduationYear, batch.Status,
|
batch.Id, batch.Name, batch.GraduationYear, batch.Status,
|
||||||
batch.Notes, batch.ClosedAt, batch.CreatedAt, batch.ItemCount,
|
batch.Notes, batch.ClosedAt, batch.CreatedAt, batch.ItemCount,
|
||||||
StudentCount = scoped.Select(x => x.StudentId).Distinct().Count(),
|
StudentCount = summary?.StudentCount ?? 0,
|
||||||
RecordCount = scoped.Count,
|
RecordCount = summary?.RecordCount ?? 0,
|
||||||
CompletedCount = scoped.Count(x =>
|
CompletedCount = summary?.CompletedCount ?? 0
|
||||||
x.Status != GraduationClearanceRecordStatus.Pending)
|
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("batches/{id:guid}")]
|
[HttpGet("batches/{id:guid}")]
|
||||||
[Authorize(Roles = Workers)]
|
[Authorize(Roles = Workers)]
|
||||||
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] string? progress = null,
|
||||||
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
page = Math.Max(page, 1);
|
||||||
|
pageSize = Math.Clamp(pageSize, 10, 100);
|
||||||
|
var normalizedProgress = progress?.Trim();
|
||||||
|
if (normalizedProgress is not null and not "Pending" and not "Completed")
|
||||||
|
return ValidationProblem("离校办理进度筛选值无效。");
|
||||||
|
|
||||||
var batch = await db.GraduationClearanceBatches.AsNoTracking()
|
var batch = await db.GraduationClearanceBatches.AsNoTracking()
|
||||||
.Where(x => x.Id == id)
|
.Where(x => x.Id == id)
|
||||||
.Select(x => new
|
.Select(x => new
|
||||||
@@ -72,8 +87,56 @@ public sealed class GraduationClearanceController(
|
|||||||
})
|
})
|
||||||
}).FirstOrDefaultAsync(token);
|
}).FirstOrDefaultAsync(token);
|
||||||
if (batch is null) return NotFound();
|
if (batch is null) return NotFound();
|
||||||
var records = await ScopedRecords().AsNoTracking()
|
|
||||||
.Where(x => x.GraduationClearanceItem!.GraduationClearanceBatchId == id)
|
var records = ScopedRecords().AsNoTracking()
|
||||||
|
.Where(x => x.GraduationClearanceItem!.GraduationClearanceBatchId == id);
|
||||||
|
var summary = await records
|
||||||
|
.GroupBy(_ => 1)
|
||||||
|
.Select(group => new
|
||||||
|
{
|
||||||
|
StudentCount = group.Select(x => x.StudentId).Distinct().Count(),
|
||||||
|
RecordCount = group.Count(),
|
||||||
|
CompletedCount = group.Count(x =>
|
||||||
|
x.Status != GraduationClearanceRecordStatus.Pending)
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync(token);
|
||||||
|
|
||||||
|
var students = db.Students.AsNoTracking()
|
||||||
|
.Where(student => records.Any(record => record.StudentId == student.Id));
|
||||||
|
var normalizedKeyword = keyword?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(normalizedKeyword))
|
||||||
|
students = students.Where(student =>
|
||||||
|
student.StudentNumber.Contains(normalizedKeyword) ||
|
||||||
|
student.Name.Contains(normalizedKeyword) ||
|
||||||
|
student.AdministrativeClass!.Name.Contains(normalizedKeyword) ||
|
||||||
|
student.AdministrativeClass.Major!.Name.Contains(normalizedKeyword));
|
||||||
|
if (normalizedProgress == "Pending")
|
||||||
|
students = students.Where(student => records.Any(record =>
|
||||||
|
record.StudentId == student.Id &&
|
||||||
|
record.GraduationClearanceItem!.IsRequired &&
|
||||||
|
record.Status == GraduationClearanceRecordStatus.Pending));
|
||||||
|
else if (normalizedProgress == "Completed")
|
||||||
|
students = students.Where(student => !records.Any(record =>
|
||||||
|
record.StudentId == student.Id &&
|
||||||
|
record.GraduationClearanceItem!.IsRequired &&
|
||||||
|
record.Status == GraduationClearanceRecordStatus.Pending));
|
||||||
|
|
||||||
|
var total = await students.CountAsync(token);
|
||||||
|
var studentPage = await students
|
||||||
|
.OrderBy(student => student.StudentNumber)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.Select(student => new
|
||||||
|
{
|
||||||
|
student.Id,
|
||||||
|
student.StudentNumber,
|
||||||
|
student.Name,
|
||||||
|
ClassName = student.AdministrativeClass!.Name,
|
||||||
|
MajorName = student.AdministrativeClass.Major!.Name
|
||||||
|
}).ToListAsync(token);
|
||||||
|
var studentIds = studentPage.Select(student => student.Id).ToArray();
|
||||||
|
var pageRecords = await records
|
||||||
|
.WhereIn(studentIds, x => x.StudentId)
|
||||||
.OrderBy(x => x.Student!.StudentNumber)
|
.OrderBy(x => x.Student!.StudentNumber)
|
||||||
.ThenBy(x => x.GraduationClearanceItem!.SortOrder)
|
.ThenBy(x => x.GraduationClearanceItem!.SortOrder)
|
||||||
.Select(x => new
|
.Select(x => new
|
||||||
@@ -89,11 +152,32 @@ public sealed class GraduationClearanceController(
|
|||||||
x.GraduationClearanceItem.IsRequired,
|
x.GraduationClearanceItem.IsRequired,
|
||||||
x.Status, x.Notes, x.CompletedAt
|
x.Status, x.Notes, x.CompletedAt
|
||||||
}).ToListAsync(token);
|
}).ToListAsync(token);
|
||||||
|
var recordsByStudent = pageRecords
|
||||||
|
.GroupBy(record => record.StudentId)
|
||||||
|
.ToDictionary(group => group.Key, group => group.ToList());
|
||||||
|
var studentItems = studentPage.Select(student => new
|
||||||
|
{
|
||||||
|
StudentId = student.Id,
|
||||||
|
student.StudentNumber,
|
||||||
|
student.Name,
|
||||||
|
student.ClassName,
|
||||||
|
student.MajorName,
|
||||||
|
Records = recordsByStudent.GetValueOrDefault(student.Id) ?? []
|
||||||
|
}).ToList();
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
batch.Id, batch.Name, batch.GraduationYear, batch.Status,
|
batch.Id, batch.Name, batch.GraduationYear, batch.Status,
|
||||||
batch.Notes, batch.ClosedAt, batch.CreatedAt, batch.Items,
|
batch.Notes, batch.ClosedAt, batch.CreatedAt, batch.Items,
|
||||||
Records = records
|
StudentCount = summary?.StudentCount ?? 0,
|
||||||
|
RecordCount = summary?.RecordCount ?? 0,
|
||||||
|
CompletedCount = summary?.CompletedCount ?? 0,
|
||||||
|
Students = new
|
||||||
|
{
|
||||||
|
Items = studentItems,
|
||||||
|
Total = total,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,16 +234,15 @@ public sealed class GraduationClearanceController(
|
|||||||
if (batch.Status != GraduationClearanceBatchStatus.Open)
|
if (batch.Status != GraduationClearanceBatchStatus.Open)
|
||||||
return ConflictProblem("已关闭批次不能重新生成办理记录。");
|
return ConflictProblem("已关闭批次不能重新生成办理记录。");
|
||||||
|
|
||||||
var eligibleAudits = await db.GraduationAuditResults.AsNoTracking()
|
var studentIds = await db.GraduationAuditResults.AsNoTracking()
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
x.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
|
x.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
|
||||||
x.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
|
x.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
|
||||||
x.Conclusion == GraduationAuditConclusion.Eligible &&
|
x.Conclusion == GraduationAuditConclusion.Eligible &&
|
||||||
x.Student!.Status == StudentStatus.Graduated)
|
x.Student!.Status == StudentStatus.Graduated)
|
||||||
.OrderByDescending(x => x.GraduationAuditBatch!.PublishedAt)
|
.Select(x => x.StudentId)
|
||||||
.Select(x => new { x.StudentId })
|
.Distinct()
|
||||||
.ToListAsync(token);
|
.ToListAsync(token);
|
||||||
var studentIds = eligibleAudits.Select(x => x.StudentId).Distinct().ToArray();
|
|
||||||
var existing = await db.GraduationClearanceRecords.AsNoTracking()
|
var existing = await db.GraduationClearanceRecords.AsNoTracking()
|
||||||
.Where(x => x.GraduationClearanceItem!.GraduationClearanceBatchId == id)
|
.Where(x => x.GraduationClearanceItem!.GraduationClearanceBatchId == id)
|
||||||
.Select(x => new { x.GraduationClearanceItemId, x.StudentId })
|
.Select(x => new { x.GraduationClearanceItemId, x.StudentId })
|
||||||
@@ -180,7 +263,7 @@ public sealed class GraduationClearanceController(
|
|||||||
created++;
|
created++;
|
||||||
}
|
}
|
||||||
await db.SaveChangesAsync(token);
|
await db.SaveChangesAsync(token);
|
||||||
return Ok(new { StudentCount = studentIds.Length, CreatedRecordCount = created });
|
return Ok(new { StudentCount = studentIds.Count, CreatedRecordCount = created });
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("records/{id:guid}")]
|
[HttpPut("records/{id:guid}")]
|
||||||
@@ -221,15 +304,17 @@ public sealed class GraduationClearanceController(
|
|||||||
public async Task<ActionResult> Close(Guid id, CancellationToken token)
|
public async Task<ActionResult> Close(Guid id, CancellationToken token)
|
||||||
{
|
{
|
||||||
var batch = await db.GraduationClearanceBatches
|
var batch = await db.GraduationClearanceBatches
|
||||||
.Include(x => x.Items)
|
|
||||||
.ThenInclude(x => x.Records)
|
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||||
if (batch is null) return NotFound();
|
if (batch is null) return NotFound();
|
||||||
if (batch.Status != GraduationClearanceBatchStatus.Open)
|
if (batch.Status != GraduationClearanceBatchStatus.Open)
|
||||||
return ConflictProblem("该批次已经关闭。");
|
return ConflictProblem("该批次已经关闭。");
|
||||||
var records = batch.Items.SelectMany(item => item.Records.Select(record =>
|
var hasIncompleteRequiredItem = await db.GraduationClearanceRecords
|
||||||
(item.IsRequired, record.Status)));
|
.AnyAsync(record =>
|
||||||
if (!GraduationClearanceRules.CanClose(records))
|
record.GraduationClearanceItem!.GraduationClearanceBatchId == id &&
|
||||||
|
record.GraduationClearanceItem.IsRequired &&
|
||||||
|
record.Status == GraduationClearanceRecordStatus.Pending,
|
||||||
|
token);
|
||||||
|
if (hasIncompleteRequiredItem)
|
||||||
return ConflictProblem("仍有必办离校事项尚未完成。");
|
return ConflictProblem("仍有必办离校事项尚未完成。");
|
||||||
batch.Status = GraduationClearanceBatchStatus.Closed;
|
batch.Status = GraduationClearanceBatchStatus.Closed;
|
||||||
batch.ClosedAt = DateTime.UtcNow;
|
batch.ClosedAt = DateTime.UtcNow;
|
||||||
|
|||||||
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