选课名单/课程修读情况
This commit is contained in:
1 parent
4575758812
commit
ef3fbf92f0
7 files changed
+241
-139
No files matched your search
@@ -48,10 +48,7 @@ public sealed class CourseSelectionsController(
|
||||
var total = await source.CountAsync(cancellationToken);
|
||||
var page = Math.Max(1, query.Page);
|
||||
var pageSize = Math.Clamp(query.PageSize, 1, 100);
|
||||
var items = await ProjectEnrollments(source)
|
||||
.OrderByDescending(x => x.TermStartDate)
|
||||
.ThenBy(x => x.CourseCode)
|
||||
.ThenBy(x => x.StudentNumber)
|
||||
var items = await ProjectEnrollments(OrderEnrollments(source))
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -64,10 +61,8 @@ public sealed class CourseSelectionsController(
|
||||
[FromQuery] CourseEnrollmentQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var items = await ProjectEnrollments(ApplyEnrollmentFilters(ScopedEnrollments(), query))
|
||||
.OrderByDescending(x => x.TermStartDate)
|
||||
.ThenBy(x => x.CourseCode)
|
||||
.ThenBy(x => x.StudentNumber)
|
||||
var items = await ProjectEnrollments(OrderEnrollments(
|
||||
ApplyEnrollmentFilters(ScopedEnrollments(), query)))
|
||||
.ToListAsync(cancellationToken);
|
||||
var rows = items.Select(x => (IReadOnlyList<object?>)[
|
||||
x.TermName, x.CollegeName, x.CourseCode, x.CourseName, x.TaskNumber,
|
||||
@@ -83,6 +78,66 @@ public sealed class CourseSelectionsController(
|
||||
$"修读课程名单-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
|
||||
}
|
||||
|
||||
[HttpGet("enrollments/students")]
|
||||
[Authorize(Roles = OfferingManagers)]
|
||||
public async Task<ActionResult> GetStudentEnrollmentSummaries(
|
||||
[FromQuery] CourseEnrollmentQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var source = ApplyEnrollmentFilters(ScopedEnrollments(), query);
|
||||
var students = source.Select(x => new EnrollmentStudentIdentity(
|
||||
x.StudentId, x.Student!.StudentNumber, x.Student.Name,
|
||||
x.Student.AdministrativeClass!.Major!.Name,
|
||||
x.Student.AdministrativeClass.Name, x.Student.AdministrativeClass.Grade));
|
||||
var total = await students.Distinct().CountAsync(cancellationToken);
|
||||
var page = Math.Max(1, query.Page);
|
||||
var pageSize = Math.Clamp(query.PageSize, 1, 100);
|
||||
var pageStudents = await students.Distinct()
|
||||
.OrderBy(x => x.StudentNumber).Skip((page - 1) * pageSize).Take(pageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
var studentIds = pageStudents.Select(x => x.StudentId).ToArray();
|
||||
var enrollments = await ProjectEnrollments(OrderEnrollments(source.Where(x =>
|
||||
studentIds.Contains(x.StudentId))))
|
||||
.ToListAsync(cancellationToken);
|
||||
var summaries = pageStudents.Select(student =>
|
||||
{
|
||||
var courses = enrollments.Where(x => x.StudentId == student.StudentId).ToList();
|
||||
return new StudentEnrollmentSummary(
|
||||
student.StudentId, student.StudentNumber, student.StudentName,
|
||||
student.MajorName, student.ClassName, student.Grade, courses,
|
||||
courses.Count, courses.Where(x => x.Status == CourseEnrollmentStatus.Enrolled)
|
||||
.Sum(x => x.Credits));
|
||||
}).ToList();
|
||||
return Ok(new PagedResult<StudentEnrollmentSummary>(summaries, total, page, pageSize));
|
||||
}
|
||||
|
||||
[HttpGet("enrollments/students/export.xlsx")]
|
||||
[Authorize(Roles = OfferingManagers)]
|
||||
public async Task<ActionResult> ExportStudentEnrollmentSummaries(
|
||||
[FromQuery] CourseEnrollmentQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var items = await ProjectEnrollments(OrderEnrollments(
|
||||
ApplyEnrollmentFilters(ScopedEnrollments(), query)))
|
||||
.ToListAsync(cancellationToken);
|
||||
var rows = items.GroupBy(x => new { x.StudentId, x.StudentNumber, x.StudentName,
|
||||
x.MajorName, x.ClassName, x.Grade })
|
||||
.OrderBy(x => x.Key.StudentNumber)
|
||||
.Select(group => (IReadOnlyList<object?>)[
|
||||
group.Key.StudentNumber, group.Key.StudentName, group.Key.MajorName,
|
||||
group.Key.ClassName, group.Key.Grade,
|
||||
string.Join(";", group.Select(x =>
|
||||
$"{x.TermName} {x.CourseCode} {x.CourseName}({x.Status})")),
|
||||
group.Where(x => x.Status == CourseEnrollmentStatus.Enrolled)
|
||||
.Sum(x => x.Credits)
|
||||
]).ToList();
|
||||
var bytes = ExcelWorkbookHelper.Create("学生修读汇总", [
|
||||
"学号", "姓名", "专业", "行政班", "年级", "修读课程", "在修学分"
|
||||
], rows);
|
||||
return File(bytes, ExcelWorkbookHelper.ContentType,
|
||||
$"学生修读汇总-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
|
||||
}
|
||||
|
||||
[HttpGet("rounds")]
|
||||
[Authorize(Roles = SelectionUsers)]
|
||||
public async Task<ActionResult> GetRounds(
|
||||
@@ -2282,7 +2337,7 @@ public sealed class CourseSelectionsController(
|
||||
scope.RestrictedCollegeId);
|
||||
}
|
||||
|
||||
private static IQueryable<CourseEnrollment> ApplyEnrollmentFilters(
|
||||
private IQueryable<CourseEnrollment> ApplyEnrollmentFilters(
|
||||
IQueryable<CourseEnrollment> source,
|
||||
CourseEnrollmentQuery query)
|
||||
{
|
||||
@@ -2295,8 +2350,27 @@ public sealed class CourseSelectionsController(
|
||||
if (query.CollegeId.HasValue)
|
||||
source = source.Where(x => x.CourseSelectionOffering!.TeachingTask!.Course!.CollegeId ==
|
||||
query.CollegeId.Value);
|
||||
if (query.MajorId.HasValue)
|
||||
source = source.Where(x => x.Student!.AdministrativeClass!.MajorId ==
|
||||
query.MajorId.Value);
|
||||
if (query.AdministrativeClassId.HasValue)
|
||||
source = source.Where(x => x.Student!.AdministrativeClassId ==
|
||||
query.AdministrativeClassId.Value);
|
||||
if (query.Grade.HasValue)
|
||||
source = source.Where(x => x.Student!.AdministrativeClass!.Grade == query.Grade.Value);
|
||||
if (query.EnrollmentYear.HasValue)
|
||||
source = source.Where(x => x.Student!.EnrollmentYear == query.EnrollmentYear.Value);
|
||||
if (query.Status.HasValue)
|
||||
source = source.Where(x => x.Status == query.Status.Value);
|
||||
if (query.EnrollmentType.HasValue)
|
||||
source = source.Where(x => x.EnrollmentType == query.EnrollmentType.Value);
|
||||
if (query.ExemptionStatus.HasValue)
|
||||
source = source.Where(x => db.CourseExemptions
|
||||
.Where(exemption => exemption.StudentId == x.StudentId &&
|
||||
exemption.TeachingTaskId == x.CourseSelectionOffering!.TeachingTaskId)
|
||||
.OrderByDescending(exemption => exemption.CreatedAt)
|
||||
.Select(exemption => (ApprovalStatus?)exemption.Status)
|
||||
.FirstOrDefault() == query.ExemptionStatus.Value);
|
||||
if (!string.IsNullOrWhiteSpace(query.Keyword))
|
||||
{
|
||||
var keyword = query.Keyword.Trim();
|
||||
@@ -2310,9 +2384,17 @@ public sealed class CourseSelectionsController(
|
||||
return source;
|
||||
}
|
||||
|
||||
private static IOrderedQueryable<CourseEnrollment> OrderEnrollments(
|
||||
IQueryable<CourseEnrollment> source) => source
|
||||
.OrderByDescending(x => x.CourseSelectionOffering!.CourseSelectionRound!
|
||||
.AcademicTerm!.StartDate)
|
||||
.ThenBy(x => x.CourseSelectionOffering!.TeachingTask!.Course!.Code)
|
||||
.ThenBy(x => x.Student!.StudentNumber);
|
||||
|
||||
private IQueryable<CourseEnrollmentListItem> ProjectEnrollments(
|
||||
IQueryable<CourseEnrollment> source) => source.Select(x => new CourseEnrollmentListItem(
|
||||
x.Id,
|
||||
x.StudentId,
|
||||
x.CourseSelectionOfferingId,
|
||||
x.CourseSelectionOffering!.CourseSelectionRound!.AcademicTerm!.Name,
|
||||
x.CourseSelectionOffering.CourseSelectionRound.AcademicTerm.StartDate,
|
||||
@@ -2320,6 +2402,7 @@ public sealed class CourseSelectionsController(
|
||||
x.CourseSelectionOffering.TeachingTask.Course.Code,
|
||||
x.CourseSelectionOffering.TeachingTask.Course.Name,
|
||||
x.CourseSelectionOffering.TeachingTask.TaskNumber,
|
||||
x.CourseSelectionOffering.TeachingTask.Course.Credits,
|
||||
x.Student!.StudentNumber,
|
||||
x.Student.Name,
|
||||
x.Student.AdministrativeClass!.Major!.Name,
|
||||
@@ -2409,13 +2492,20 @@ public sealed record CourseEnrollmentQuery(
|
||||
Guid? AcademicTermId,
|
||||
Guid? CourseId,
|
||||
Guid? CollegeId,
|
||||
Guid? MajorId,
|
||||
Guid? AdministrativeClassId,
|
||||
int? Grade,
|
||||
int? EnrollmentYear,
|
||||
CourseEnrollmentStatus? Status,
|
||||
EnrollmentType? EnrollmentType,
|
||||
ApprovalStatus? ExemptionStatus,
|
||||
string? Keyword,
|
||||
int Page = 1,
|
||||
int PageSize = 20);
|
||||
|
||||
public sealed record CourseEnrollmentListItem(
|
||||
Guid Id,
|
||||
Guid StudentId,
|
||||
Guid CourseSelectionOfferingId,
|
||||
string TermName,
|
||||
DateOnly TermStartDate,
|
||||
@@ -2423,6 +2513,7 @@ public sealed record CourseEnrollmentListItem(
|
||||
string CourseCode,
|
||||
string CourseName,
|
||||
string TaskNumber,
|
||||
decimal Credits,
|
||||
string StudentNumber,
|
||||
string StudentName,
|
||||
string MajorName,
|
||||
@@ -2434,6 +2525,25 @@ public sealed record CourseEnrollmentListItem(
|
||||
DateTime? WithdrawnAt,
|
||||
ApprovalStatus? ExemptionStatus);
|
||||
|
||||
public sealed record EnrollmentStudentIdentity(
|
||||
Guid StudentId,
|
||||
string StudentNumber,
|
||||
string StudentName,
|
||||
string MajorName,
|
||||
string ClassName,
|
||||
int Grade);
|
||||
|
||||
public sealed record StudentEnrollmentSummary(
|
||||
Guid StudentId,
|
||||
string StudentNumber,
|
||||
string StudentName,
|
||||
string MajorName,
|
||||
string ClassName,
|
||||
int Grade,
|
||||
IReadOnlyList<CourseEnrollmentListItem> Courses,
|
||||
int CourseCount,
|
||||
decimal ActiveCredits);
|
||||
|
||||
public sealed record StudentOfferingDto(
|
||||
Guid Id,
|
||||
Guid TeachingTaskId,
|
||||
|
||||
Reference in new issue
Block a user