This commit is contained in:
biss committed 2026-08-12 11:39:30 +08:00
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.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Academic;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@@ -302,6 +303,30 @@ public sealed class PersonnelController(
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")]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> CreateStudent(
@@ -2,6 +2,7 @@ using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Academic;
using Jiaowu.Api.Infrastructure.Excel;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
@@ -49,6 +50,11 @@ public sealed class PersonnelExcelController(
"紧急联系电话", "特殊标记", "特殊情况说明", "个人简介", "备注"
];
private static readonly string[] StudentCurrentCourseHeaders =
[
"学号", "姓名", "学院", "专业", "行政班", "正在修读课程", "学分", "总学分"
];
[HttpGet("{kind}/template")]
[Authorize(Roles = WriteRoles)]
public IActionResult DownloadTemplate(string kind)
@@ -117,6 +123,60 @@ public sealed class PersonnelExcelController(
$"{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")]
[Authorize(Roles = WriteRoles)]
[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