升级
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
using System.Security.Claims;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/timetables")]
|
||||
public sealed class TimetablesController(AppDbContext db) : ControllerBase
|
||||
{
|
||||
[HttpGet("options")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult> GetOptions(CancellationToken cancellationToken)
|
||||
{
|
||||
var terms = await db.AcademicTerms.AsNoTracking()
|
||||
.Where(x => x.IsEnabled)
|
||||
.OrderByDescending(x => x.StartDate)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.Name,
|
||||
x.AcademicYear,
|
||||
x.Season,
|
||||
x.StartDate,
|
||||
x.EndDate,
|
||||
x.IsCurrent,
|
||||
HasPublishedTimetable = db.SchedulePlans.Any(plan =>
|
||||
plan.AcademicTermId == x.Id &&
|
||||
plan.Status == SchedulePlanStatus.Published)
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
var defaultTermId = terms.FirstOrDefault(x => x.IsCurrent)?.Id
|
||||
?? terms.FirstOrDefault(x => x.HasPublishedTimetable)?.Id;
|
||||
var classes = await db.AdministrativeClasses.AsNoTracking()
|
||||
.Where(x => x.IsEnabled && x.Major!.IsEnabled && x.Major.College!.IsEnabled)
|
||||
.OrderByDescending(x => x.Grade)
|
||||
.ThenBy(x => x.Code)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.Code,
|
||||
x.Name,
|
||||
x.Grade,
|
||||
MajorName = x.Major!.Name,
|
||||
CollegeName = x.Major.College!.Name,
|
||||
HasPublishedTimetable = defaultTermId.HasValue &&
|
||||
db.ScheduleEntries.Any(entry =>
|
||||
entry.SchedulePlan!.AcademicTermId == defaultTermId.Value &&
|
||||
entry.SchedulePlan.Status == SchedulePlanStatus.Published &&
|
||||
entry.TeachingTask!.Classes.Any(item =>
|
||||
item.AdministrativeClassId == x.Id))
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
return Ok(new { Terms = terms, Classes = classes });
|
||||
}
|
||||
|
||||
[HttpGet("classes/{classId:guid}")]
|
||||
[AllowAnonymous]
|
||||
public Task<ActionResult> GetClassTimetable(
|
||||
Guid classId,
|
||||
Guid? academicTermId,
|
||||
CancellationToken cancellationToken) =>
|
||||
BuildTimetableAsync(classId, academicTermId, null, cancellationToken);
|
||||
|
||||
[HttpGet("mine")]
|
||||
[Authorize(Roles = SystemRoles.Student)]
|
||||
public async Task<ActionResult> GetMyTimetable(
|
||||
Guid? academicTermId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!Guid.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out var userId))
|
||||
return Unauthorized();
|
||||
var student = await db.Students.AsNoTracking()
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.AdministrativeClassId,
|
||||
x.StudentNumber,
|
||||
x.Name
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
if (student is null)
|
||||
return Conflict(new ProblemDetails
|
||||
{
|
||||
Title = "学生档案未关联",
|
||||
Detail = "当前登录账号没有关联学生档案,请联系教务管理员。",
|
||||
Status = StatusCodes.Status409Conflict
|
||||
});
|
||||
|
||||
return await BuildTimetableAsync(
|
||||
student.AdministrativeClassId,
|
||||
academicTermId,
|
||||
student.Id,
|
||||
cancellationToken,
|
||||
new { student.StudentNumber, student.Name });
|
||||
}
|
||||
|
||||
private async Task<ActionResult> BuildTimetableAsync(
|
||||
Guid classId,
|
||||
Guid? academicTermId,
|
||||
Guid? studentId,
|
||||
CancellationToken cancellationToken,
|
||||
object? student = null)
|
||||
{
|
||||
var administrativeClass = await db.AdministrativeClasses.AsNoTracking()
|
||||
.Where(x => x.Id == classId && x.IsEnabled)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.Code,
|
||||
x.Name,
|
||||
x.Grade,
|
||||
MajorName = x.Major!.Name,
|
||||
CollegeName = x.Major.College!.Name
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
if (administrativeClass is null) return NotFound();
|
||||
|
||||
var termQuery = db.AcademicTerms.AsNoTracking().Where(x => x.IsEnabled);
|
||||
if (academicTermId.HasValue)
|
||||
termQuery = termQuery.Where(x => x.Id == academicTermId);
|
||||
else
|
||||
termQuery = termQuery.OrderByDescending(x => x.IsCurrent)
|
||||
.ThenByDescending(x => x.StartDate);
|
||||
var term = await termQuery
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.Name,
|
||||
x.AcademicYear,
|
||||
x.Season,
|
||||
x.StartDate,
|
||||
x.EndDate,
|
||||
x.IsCurrent
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
if (term is null) return NotFound();
|
||||
|
||||
var plan = await db.SchedulePlans.AsNoTracking()
|
||||
.Where(x =>
|
||||
x.AcademicTermId == term.Id &&
|
||||
x.Status == SchedulePlanStatus.Published)
|
||||
.Select(x => new { x.Id, x.Name, x.Version, x.PublishedAt })
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
var slots = await db.ScheduleTimeSlots.AsNoTracking()
|
||||
.Where(x => x.AcademicTermId == term.Id && x.IsEnabled)
|
||||
.OrderBy(x => x.PeriodNumber)
|
||||
.Select(x => new { x.PeriodNumber, x.Name, x.StartsAt, x.EndsAt })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (plan is null)
|
||||
return Ok(new
|
||||
{
|
||||
Term = term,
|
||||
Class = administrativeClass,
|
||||
Student = student,
|
||||
Plan = (object?)null,
|
||||
Slots = slots,
|
||||
Entries = Array.Empty<object>()
|
||||
});
|
||||
|
||||
var entries = db.ScheduleEntries.AsNoTracking()
|
||||
.Where(x =>
|
||||
x.SchedulePlanId == plan.Id &&
|
||||
x.TeachingTask!.Classes.Any(item =>
|
||||
item.AdministrativeClassId == classId));
|
||||
if (studentId.HasValue)
|
||||
{
|
||||
var selectedTaskIds = db.CourseEnrollments.AsNoTracking()
|
||||
.Where(x =>
|
||||
x.StudentId == studentId.Value &&
|
||||
x.Status == CourseEnrollmentStatus.Enrolled &&
|
||||
x.CourseSelectionOffering!.CourseSelectionRound!.AcademicTermId == term.Id)
|
||||
.Select(x => x.CourseSelectionOffering!.TeachingTaskId);
|
||||
entries = db.ScheduleEntries.AsNoTracking()
|
||||
.Where(x =>
|
||||
x.SchedulePlanId == plan.Id &&
|
||||
(x.TeachingTask!.Classes.Any(item =>
|
||||
item.AdministrativeClassId == classId) ||
|
||||
selectedTaskIds.Contains(x.TeachingTaskId)));
|
||||
}
|
||||
|
||||
var result = await entries
|
||||
.OrderBy(x => x.DayOfWeek)
|
||||
.ThenBy(x => x.StartPeriod)
|
||||
.ThenBy(x => x.TeachingTask!.Course!.Code)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.TeachingTaskId,
|
||||
TaskNumber = x.TeachingTask!.TaskNumber,
|
||||
TaskName = x.TeachingTask.Name,
|
||||
CourseCode = x.TeachingTask.Course!.Code,
|
||||
CourseName = x.TeachingTask.Course.Name,
|
||||
TeacherNames = x.TeachingTask.Teachers
|
||||
.OrderByDescending(item => item.IsPrimary)
|
||||
.Select(item => item.Teacher!.Name),
|
||||
ClassNames = x.TeachingTask.Classes
|
||||
.Select(item => item.AdministrativeClass!.Name),
|
||||
ClassroomName = x.Classroom == null ? "不占用教室" : x.Classroom.Name,
|
||||
BuildingName = x.Classroom == null ? null : x.Classroom.Building!.Name,
|
||||
CampusName = x.Classroom == null
|
||||
? null
|
||||
: x.Classroom.Building!.Campus!.Name,
|
||||
x.DayOfWeek,
|
||||
x.StartPeriod,
|
||||
x.PeriodCount,
|
||||
x.StartWeek,
|
||||
x.EndWeek,
|
||||
x.WeekPattern,
|
||||
x.Notes
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Term = term,
|
||||
Class = administrativeClass,
|
||||
Student = student,
|
||||
Plan = plan,
|
||||
Slots = slots,
|
||||
Entries = result
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user