优化
This commit is contained in:
1 parent
e5afe60a11
commit
7994ed1ed3
8 files changed
+408
-13
No files matched your search
@@ -4,6 +4,7 @@ using Jiaowu.Api.Domain.Academic;
|
|||||||
using Jiaowu.Api.Domain.Identity;
|
using Jiaowu.Api.Domain.Identity;
|
||||||
using Jiaowu.Api.Infrastructure.Auth;
|
using Jiaowu.Api.Infrastructure.Auth;
|
||||||
using Jiaowu.Api.Infrastructure.Caching;
|
using Jiaowu.Api.Infrastructure.Caching;
|
||||||
|
using Jiaowu.Api.Infrastructure.Academic;
|
||||||
using Jiaowu.Api.Infrastructure.Persistence;
|
using Jiaowu.Api.Infrastructure.Persistence;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
@@ -302,6 +303,30 @@ public sealed class PersonnelController(
|
|||||||
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("students/current-courses")]
|
||||||
|
public async Task<ActionResult> GetStudentCurrentCourses(
|
||||||
|
[FromQuery] Guid[] studentIds,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var requestedIds = studentIds.Distinct().Take(100).ToArray();
|
||||||
|
if (requestedIds.Length == 0) return Ok(Array.Empty<object>());
|
||||||
|
|
||||||
|
var students = await ApplyStudentScope(db.Students.AsNoTracking())
|
||||||
|
.WhereIn(requestedIds, x => x.Id)
|
||||||
|
.Select(x => new CurrentCourseStudent(x.Id, x.AdministrativeClassId))
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
var summaries = await StudentCurrentCourseQuery.GetAsync(
|
||||||
|
db,
|
||||||
|
students,
|
||||||
|
cancellationToken);
|
||||||
|
return Ok(students.Select(student => new
|
||||||
|
{
|
||||||
|
student.Id,
|
||||||
|
CurrentCourses = summaries[student.Id].Courses,
|
||||||
|
summaries[student.Id].TotalCredits
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("students")]
|
[HttpPost("students")]
|
||||||
[Authorize(Roles = WriteRoles)]
|
[Authorize(Roles = WriteRoles)]
|
||||||
public async Task<ActionResult> CreateStudent(
|
public async Task<ActionResult> CreateStudent(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Jiaowu.Api.Domain.Academic;
|
|||||||
using Jiaowu.Api.Domain.Identity;
|
using Jiaowu.Api.Domain.Identity;
|
||||||
using Jiaowu.Api.Infrastructure.Auth;
|
using Jiaowu.Api.Infrastructure.Auth;
|
||||||
using Jiaowu.Api.Infrastructure.Caching;
|
using Jiaowu.Api.Infrastructure.Caching;
|
||||||
|
using Jiaowu.Api.Infrastructure.Academic;
|
||||||
using Jiaowu.Api.Infrastructure.Excel;
|
using Jiaowu.Api.Infrastructure.Excel;
|
||||||
using Jiaowu.Api.Infrastructure.Persistence;
|
using Jiaowu.Api.Infrastructure.Persistence;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -49,6 +50,11 @@ public sealed class PersonnelExcelController(
|
|||||||
"紧急联系电话", "特殊标记", "特殊情况说明", "个人简介", "备注"
|
"紧急联系电话", "特殊标记", "特殊情况说明", "个人简介", "备注"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static readonly string[] StudentCurrentCourseHeaders =
|
||||||
|
[
|
||||||
|
"学号", "姓名", "学院", "专业", "行政班", "正在修读课程", "学分", "总学分"
|
||||||
|
];
|
||||||
|
|
||||||
[HttpGet("{kind}/template")]
|
[HttpGet("{kind}/template")]
|
||||||
[Authorize(Roles = WriteRoles)]
|
[Authorize(Roles = WriteRoles)]
|
||||||
public IActionResult DownloadTemplate(string kind)
|
public IActionResult DownloadTemplate(string kind)
|
||||||
@@ -117,6 +123,60 @@ public sealed class PersonnelExcelController(
|
|||||||
$"{RegistryName(kind)}-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
|
$"{RegistryName(kind)}-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("students/current-courses/export")]
|
||||||
|
[Authorize(Roles = ExportRoles)]
|
||||||
|
public async Task<IActionResult> ExportStudentCurrentCourses(
|
||||||
|
[FromQuery] PersonnelQuery query,
|
||||||
|
[FromQuery] Guid[]? studentIds,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var source = ApplyStudentFilters(
|
||||||
|
ApplyStudentScope(db.Students.AsNoTracking()),
|
||||||
|
query);
|
||||||
|
var selectedIds = studentIds?.Distinct().ToArray() ?? [];
|
||||||
|
if (selectedIds.Length > 0)
|
||||||
|
source = source.WhereIn(selectedIds, x => x.Id);
|
||||||
|
|
||||||
|
var students = await source.OrderBy(x => x.StudentNumber)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.Id,
|
||||||
|
x.StudentNumber,
|
||||||
|
x.Name,
|
||||||
|
x.AdministrativeClassId,
|
||||||
|
ClassName = x.AdministrativeClass!.Name,
|
||||||
|
MajorName = x.AdministrativeClass.Major!.Name,
|
||||||
|
CollegeName = x.AdministrativeClass.Major.College!.Name
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
var summaries = await StudentCurrentCourseQuery.GetAsync(
|
||||||
|
db,
|
||||||
|
students.Select(x => new CurrentCourseStudent(x.Id, x.AdministrativeClassId))
|
||||||
|
.ToList(),
|
||||||
|
cancellationToken);
|
||||||
|
var rows = students.Select(student =>
|
||||||
|
{
|
||||||
|
var summary = summaries[student.Id];
|
||||||
|
return Row(
|
||||||
|
student.StudentNumber,
|
||||||
|
student.Name,
|
||||||
|
student.CollegeName,
|
||||||
|
student.MajorName,
|
||||||
|
student.ClassName,
|
||||||
|
string.Join(";", summary.Courses.Select(course =>
|
||||||
|
$"{course.CourseCode} {course.CourseName}")),
|
||||||
|
string.Join(";", summary.Courses.Select(course =>
|
||||||
|
$"{course.Credits:0.##}")),
|
||||||
|
summary.TotalCredits);
|
||||||
|
}).ToList();
|
||||||
|
var bytes = ExcelWorkbookHelper.Create(
|
||||||
|
"学生正在修读课程",
|
||||||
|
StudentCurrentCourseHeaders,
|
||||||
|
rows);
|
||||||
|
return File(bytes, ExcelWorkbookHelper.ContentType,
|
||||||
|
$"学生正在修读课程-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("{kind}/import")]
|
[HttpPost("{kind}/import")]
|
||||||
[Authorize(Roles = WriteRoles)]
|
[Authorize(Roles = WriteRoles)]
|
||||||
[RequestSizeLimit(10 * 1024 * 1024)]
|
[RequestSizeLimit(10 * 1024 * 1024)]
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using Jiaowu.Api.Domain.Academic;
|
||||||
|
using Jiaowu.Api.Infrastructure.Persistence;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Jiaowu.Api.Infrastructure.Academic;
|
||||||
|
|
||||||
|
public sealed record CurrentCourseStudent(Guid Id, Guid AdministrativeClassId);
|
||||||
|
|
||||||
|
public sealed record CurrentCourseItem(
|
||||||
|
Guid CourseId,
|
||||||
|
string CourseCode,
|
||||||
|
string CourseName,
|
||||||
|
decimal Credits);
|
||||||
|
|
||||||
|
public sealed record StudentCurrentCourseSummary(
|
||||||
|
IReadOnlyList<CurrentCourseItem> Courses,
|
||||||
|
decimal TotalCredits);
|
||||||
|
|
||||||
|
public static class StudentCurrentCourseQuery
|
||||||
|
{
|
||||||
|
public static async Task<IReadOnlyDictionary<Guid, StudentCurrentCourseSummary>>
|
||||||
|
GetAsync(
|
||||||
|
AppDbContext db,
|
||||||
|
IReadOnlyList<CurrentCourseStudent> students,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (students.Count == 0)
|
||||||
|
return new Dictionary<Guid, StudentCurrentCourseSummary>();
|
||||||
|
|
||||||
|
var studentIds = students.Select(x => x.Id).Distinct().ToArray();
|
||||||
|
var classIds = students.Select(x => x.AdministrativeClassId).Distinct().ToArray();
|
||||||
|
var currentTasks = db.TeachingTasks.AsNoTracking()
|
||||||
|
.Where(x => x.Status == TeachingTaskStatus.Published && x.AcademicTerm!.IsCurrent);
|
||||||
|
var taskClasses = db.TeachingTaskClasses.AsNoTracking()
|
||||||
|
.WhereIn(classIds, x => x.AdministrativeClassId);
|
||||||
|
var enrollments = db.CourseEnrollments.AsNoTracking()
|
||||||
|
.Where(x => x.Status == CourseEnrollmentStatus.Enrolled)
|
||||||
|
.WhereIn(studentIds, x => x.StudentId);
|
||||||
|
|
||||||
|
var assigned = await (
|
||||||
|
from link in taskClasses
|
||||||
|
join task in currentTasks on link.TeachingTaskId equals task.Id
|
||||||
|
join course in db.Courses.AsNoTracking() on task.CourseId equals course.Id
|
||||||
|
select new CurrentCourseAssignment(
|
||||||
|
null,
|
||||||
|
link.AdministrativeClassId,
|
||||||
|
course.Id,
|
||||||
|
course.Code,
|
||||||
|
course.Name,
|
||||||
|
course.Credits))
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var enrolled = await (
|
||||||
|
from enrollment in enrollments
|
||||||
|
join offering in db.CourseSelectionOfferings.AsNoTracking()
|
||||||
|
on enrollment.CourseSelectionOfferingId equals offering.Id
|
||||||
|
join task in currentTasks on offering.TeachingTaskId equals task.Id
|
||||||
|
join course in db.Courses.AsNoTracking() on task.CourseId equals course.Id
|
||||||
|
select new CurrentCourseAssignment(
|
||||||
|
enrollment.StudentId,
|
||||||
|
null,
|
||||||
|
course.Id,
|
||||||
|
course.Code,
|
||||||
|
course.Name,
|
||||||
|
course.Credits))
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var assignedByClass = assigned.GroupBy(x => x.AdministrativeClassId!.Value)
|
||||||
|
.ToDictionary(x => x.Key, x => x.ToList());
|
||||||
|
var enrolledByStudent = enrolled.GroupBy(x => x.StudentId!.Value)
|
||||||
|
.ToDictionary(x => x.Key, x => x.ToList());
|
||||||
|
|
||||||
|
return students.ToDictionary(
|
||||||
|
student => student.Id,
|
||||||
|
student =>
|
||||||
|
{
|
||||||
|
var courses = (assignedByClass.GetValueOrDefault(student.AdministrativeClassId) ?? [])
|
||||||
|
.Concat(enrolledByStudent.GetValueOrDefault(student.Id) ?? [])
|
||||||
|
.GroupBy(x => x.CourseId)
|
||||||
|
.Select(x => x.First())
|
||||||
|
.OrderBy(x => x.CourseCode)
|
||||||
|
.Select(x => new CurrentCourseItem(
|
||||||
|
x.CourseId,
|
||||||
|
x.CourseCode,
|
||||||
|
x.CourseName,
|
||||||
|
x.Credits))
|
||||||
|
.ToList();
|
||||||
|
return new StudentCurrentCourseSummary(courses, courses.Sum(x => x.Credits));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record CurrentCourseAssignment(
|
||||||
|
Guid? StudentId,
|
||||||
|
Guid? AdministrativeClassId,
|
||||||
|
Guid CourseId,
|
||||||
|
string CourseCode,
|
||||||
|
string CourseName,
|
||||||
|
decimal Credits);
|
||||||
|
}
|
||||||
Loaded 3 of 8 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user