Files
biss 2d1e2cb697 “教务总览”改成可实际工作的分级工作台:
校级管理员查看全校数据,学院管理员仅查看本学院数据。
待办按当前审批阶段统计,涵盖授课资格、成绩、调停课、学籍异动、教室借用等。
增加学期进度、教学任务发布、课表覆盖、成绩发布状态。
快捷入口根据管理员角色自动调整。
完善未配置学期、无待办、加载失败等状态。
适配桌面和 390px 移动端,无横向溢出。
2026-07-27 17:11:51 +08:00

262 lines
8.9 KiB
C#

using Jiaowu.Api.Controllers;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Tests;
public sealed class DashboardControllerTests
{
[Fact]
public async Task College_dashboard_is_scoped_and_only_counts_actionable_stage()
{
await using var database = await DashboardDatabase.CreateAsync();
var controller = new DashboardController(
database.Db,
new TestDataScope(
database.FirstCollegeId,
DataScope.College,
SystemRoles.CollegeAdmin));
var result = await controller.Get(CancellationToken.None);
var response = Assert.IsType<DashboardResponse>(
Assert.IsType<OkObjectResult>(result.Result).Value);
Assert.Equal("College", response.Audience.Level);
Assert.Equal("第一学院", response.Audience.ScopeName);
Assert.Equal(1, response.Counts.Students);
Assert.Equal(1, response.Counts.Teachers);
Assert.Equal(1, response.Counts.TeachingTasks);
Assert.Equal(1, response.Pending.TeacherApplications);
Assert.Equal(1, response.Pending.StudentStatusChanges);
}
[Fact]
public async Task School_dashboard_uses_school_stage_and_all_colleges()
{
await using var database = await DashboardDatabase.CreateAsync();
var controller = new DashboardController(
database.Db,
new TestDataScope(
null,
DataScope.All,
SystemRoles.AcademicAdmin));
var result = await controller.Get(CancellationToken.None);
var response = Assert.IsType<DashboardResponse>(
Assert.IsType<OkObjectResult>(result.Result).Value);
Assert.Equal("School", response.Audience.Level);
Assert.Equal(2, response.Counts.Students);
Assert.Equal(2, response.Counts.Teachers);
Assert.Equal(2, response.Counts.TeachingTasks);
Assert.Equal(2, response.Pending.TeacherApplications);
Assert.Equal(1, response.Pending.StudentStatusChanges);
}
private sealed class DashboardDatabase : IAsyncDisposable
{
private readonly SqliteConnection connection;
private DashboardDatabase(
SqliteConnection connection,
AppDbContext db,
Guid firstCollegeId)
{
this.connection = connection;
Db = db;
FirstCollegeId = firstCollegeId;
}
public AppDbContext Db { get; }
public Guid FirstCollegeId { get; }
public static async Task<DashboardDatabase> CreateAsync()
{
var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(connection)
.Options;
var db = new AppDbContext(options);
await db.Database.EnsureCreatedAsync();
var term = new AcademicTerm
{
Code = "2026-1",
Name = "2026—2027 学年第一学期",
AcademicYear = "2026-2027",
Season = TermSeason.Autumn,
StartDate = new DateOnly(2026, 9, 1),
EndDate = new DateOnly(2027, 1, 15),
IsCurrent = true
};
var firstCollege = new College
{
Code = "C01",
Name = "第一学院"
};
var secondCollege = new College
{
Code = "C02",
Name = "第二学院"
};
var firstMajor = CreateMajor("M01", firstCollege);
var secondMajor = CreateMajor("M02", secondCollege);
var firstClass = CreateClass("CL01", firstMajor);
var secondClass = CreateClass("CL02", secondMajor);
var firstStudent = CreateStudent("S01", firstClass);
var secondStudent = CreateStudent("S02", secondClass);
var firstTeacher = CreateTeacher("T01", firstCollege);
var secondTeacher = CreateTeacher("T02", secondCollege);
var firstCourse = CreateCourse("COURSE01", firstCollege);
var secondCourse = CreateCourse("COURSE02", secondCollege);
var firstTask = CreateTask("TASK01", term, firstCourse);
var secondTask = CreateTask("TASK02", term, secondCourse);
db.AddRange(
term,
firstCollege,
secondCollege,
firstMajor,
secondMajor,
firstClass,
secondClass,
firstStudent,
secondStudent,
firstTeacher,
secondTeacher,
firstCourse,
secondCourse,
firstTask,
secondTask,
new TeacherCourseApplication
{
AcademicTerm = term,
Teacher = firstTeacher,
Course = firstCourse,
Status = TeacherCourseApplicationStatus.Pending
},
new TeacherCourseApplication
{
AcademicTerm = term,
Teacher = secondTeacher,
Course = secondCourse,
Status = TeacherCourseApplicationStatus.Pending
},
CreateStatusChange(
firstStudent,
StudentStatusChangeState.CounselorApproved),
CreateStatusChange(
firstStudent,
StudentStatusChangeState.CollegeApproved),
CreateStatusChange(
secondStudent,
StudentStatusChangeState.Submitted));
await db.SaveChangesAsync();
return new DashboardDatabase(connection, db, firstCollege.Id);
}
public async ValueTask DisposeAsync()
{
await Db.DisposeAsync();
await connection.DisposeAsync();
}
private static Major CreateMajor(string code, College college) => new()
{
Code = code,
Name = $"专业 {code}",
College = college,
DegreeType = "本科"
};
private static AdministrativeClass CreateClass(
string code,
Major major) => new()
{
Code = code,
Name = $"班级 {code}",
Major = major,
Grade = 2026
};
private static Student CreateStudent(
string number,
AdministrativeClass administrativeClass) => new()
{
StudentNumber = number,
Name = $"学生 {number}",
AdministrativeClass = administrativeClass,
EnrollmentYear = 2026,
EnrollmentDate = new DateOnly(2026, 9, 1),
Status = StudentStatus.Active
};
private static Teacher CreateTeacher(
string number,
College college) => new()
{
TeacherNumber = number,
Name = $"教师 {number}",
College = college,
Status = TeacherStatus.Active
};
private static Course CreateCourse(string code, College college) => new()
{
Code = code,
Name = $"课程 {code}",
College = college,
Nature = CourseNature.MajorRequired,
Credits = 2,
TotalHours = 32,
LectureHours = 32,
AssessmentMethod = AssessmentMethod.Examination
};
private static TeachingTask CreateTask(
string number,
AcademicTerm term,
Course course) => new()
{
TaskNumber = number,
Name = $"教学班 {number}",
AcademicTerm = term,
Course = course,
Capacity = 40,
Status = TeachingTaskStatus.Published
};
private static StudentStatusChange CreateStatusChange(
Student student,
StudentStatusChangeState state) => new()
{
Student = student,
Type = StudentStatusChangeType.Suspension,
OriginalStatus = StudentStatus.Active,
TargetStatus = StudentStatus.Suspended,
Reason = "测试学籍异动流程",
State = state
};
}
private sealed class TestDataScope(
Guid? collegeId,
DataScope dataScope,
string role) : ICurrentUserDataScope
{
public CurrentUserScope Current { get; } = new(
Guid.NewGuid(),
"测试管理员",
collegeId,
dataScope,
new HashSet<string> { role });
}
}