“教务总览”改成可实际工作的分级工作台:
校级管理员查看全校数据,学院管理员仅查看本学院数据。 待办按当前审批阶段统计,涵盖授课资格、成绩、调停课、学籍异动、教室借用等。 增加学期进度、教学任务发布、课表覆盖、成绩发布状态。 快捷入口根据管理员角色自动调整。 完善未配置学期、无待办、加载失败等状态。 适配桌面和 390px 移动端,无横向溢出。
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user