成绩+点名

This commit is contained in:
2026-07-25 14:08:50 +08:00 Unverified
parent 7800951aee
commit 36049af692
16 changed files with 1809 additions and 98 deletions
@@ -4,6 +4,7 @@ using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.CourseSelection;
using Jiaowu.Api.Infrastructure.Excel;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -868,6 +869,100 @@ public sealed class CourseSelectionsController(
return await SaveAsync(id, false, cancellationToken);
}
[HttpGet("my-offerings")]
[Authorize(Roles = SystemRoles.Teacher)]
public async Task<ActionResult> GetMyOfferings(
Guid? academicTermId,
CancellationToken cancellationToken)
{
var userId = currentUserDataScope.Current.UserId;
var offeringsQuery = db.CourseSelectionOfferings.AsNoTracking()
.Where(x =>
x.TeachingTask!.Teachers.Any(t =>
t.Teacher!.UserId == userId) &&
x.CourseSelectionRound!.Status != CourseSelectionRoundStatus.Draft);
if (academicTermId.HasValue)
offeringsQuery = offeringsQuery.Where(x =>
x.CourseSelectionRound!.AcademicTermId == academicTermId);
var offerings = await offeringsQuery
.OrderByDescending(x => x.CourseSelectionRound!.AcademicTerm!.StartDate)
.ThenBy(x => x.TeachingTask!.Course!.Code)
.Select(x => new
{
x.Id,
x.TeachingTaskId,
x.TeachingTask!.TaskNumber,
TaskName = x.TeachingTask.Name,
x.TeachingTask.AcademicTermId,
TermName = x.TeachingTask.AcademicTerm!.Name,
CourseCode = x.TeachingTask.Course!.Code,
CourseName = x.TeachingTask.Course.Name,
RoundName = x.CourseSelectionRound!.Name,
x.Capacity,
EnrolledCount = x.Enrollments.Count(e =>
e.Status == CourseEnrollmentStatus.Enrolled),
RoundStatus = x.CourseSelectionRound.Status
})
.ToListAsync(cancellationToken);
return Ok(offerings);
}
[HttpGet("offerings/{id:guid}/roster/export.xlsx")]
[Authorize(Roles = RosterReaders)]
public async Task<ActionResult> ExportRoster(
Guid id,
CancellationToken cancellationToken)
{
var offering = await db.CourseSelectionOfferings.AsNoTracking()
.Where(x => x.Id == id)
.Select(x => new
{
x.Id,
x.TeachingTask!.TaskNumber,
CourseCode = x.TeachingTask.Course!.Code,
CourseName = x.TeachingTask.Course.Name,
RoundName = x.CourseSelectionRound!.Name,
CollegeId = x.TeachingTask.Course.CollegeId,
TeacherUserIds = x.TeachingTask.Teachers
.Select(item => item.Teacher!.UserId)
})
.FirstOrDefaultAsync(cancellationToken);
if (offering is null) return NotFound();
var scope = currentUserDataScope.Current;
var isAssignedTeacher =
scope.IsInRole(SystemRoles.Teacher) &&
offering.TeacherUserIds.Contains(scope.UserId);
if (!isAssignedTeacher && !scope.CanAccessCollege(offering.CollegeId))
return Forbid();
var students = await db.CourseEnrollments.AsNoTracking()
.Where(x =>
x.CourseSelectionOfferingId == id &&
x.Status == CourseEnrollmentStatus.Enrolled)
.OrderBy(x => x.Student!.StudentNumber)
.Select(x => new
{
x.Student!.StudentNumber,
x.Student.Name,
ClassName = x.Student.AdministrativeClass!.Name,
MajorName = x.Student.AdministrativeClass.Major!.Name,
x.EnrolledAt
})
.ToListAsync(cancellationToken);
var bytes = ExcelWorkbookHelper.Create(
"选课名单",
["学号", "姓名", "班级", "专业", "选课时间"],
students.Select(s => new List<object?>
{
s.StudentNumber, s.Name, s.ClassName, s.MajorName,
s.EnrolledAt.ToString("yyyy-MM-dd HH:mm")
}).ToList<IReadOnlyList<object?>>());
return File(bytes, ExcelWorkbookHelper.ContentType,
$"选课名单-{offering.TaskNumber}.xlsx");
}
private IQueryable<CourseSelectionOffering> ScopedOfferings()
{
var source = db.CourseSelectionOfferings.AsQueryable();