Compare commits
23
Commits
v2.4.2
...
5959585864
No files matched your search
@@ -1,96 +1,102 @@
|
|||||||
一、系统角色
|
# 教务系统功能清单
|
||||||
超级管理员:系统配置、权限、数据字典、日志
|
|
||||||
教务处:校级教学计划、排课、选课、考试、成绩、学籍管理
|
|
||||||
院系教务员:本院专业、课程、教学任务、成绩审核
|
|
||||||
教师:课表、学生名单、考勤、成绩录入、调停课申请
|
|
||||||
辅导员:学生信息、考勤、学业预警
|
|
||||||
学生:课表、选课、成绩、考试、学籍申请
|
|
||||||
校领导:统计分析和决策看板
|
|
||||||
可选角色:财务人员、宿管人员、用人单位或家长
|
|
||||||
权限建议采用“角色 + 数据范围”模式,例如院系教务员只能管理本学院数据。
|
|
||||||
二、核心功能模块
|
|
||||||
模块 主要功能
|
|
||||||
基础数据 校区、学院、系部、专业、班级、年级、教室、学期、课程性质、成绩等级等
|
|
||||||
用户与权限 用户、角色、菜单权限、操作权限、数据权限、登录日志、操作审计
|
|
||||||
学籍管理 学生档案、照片、班级、学籍状态、转专业、休学、复学、退学、留级、异动审批
|
|
||||||
课程库 课程编号、学分、学时、课程性质、先修课程、课程简介、开课学院
|
|
||||||
培养方案 专业培养方案、课程模块、必修/选修要求、学分要求、版本管理
|
|
||||||
教学任务 学期课程开设、授课教师、教学班、容量、授课周次、合班拆班
|
|
||||||
智能排课 教师、班级、教室、时间冲突检测,手工调整,课表发布
|
|
||||||
选课管理 选课轮次、选课限制、容量、退补选、冲突校验、先修条件、候补名单
|
|
||||||
课表管理 学生课表、教师课表、班级课表、教室课表、周次课表
|
|
||||||
调停课管理 调课、停课、补课、代课申请,审批及消息通知
|
|
||||||
考勤管理 点名、迟到、早退、请假、缺勤,批量导入和统计
|
|
||||||
考试管理 考试安排、考场、监考教师、座位表、缓考、补考、重修考试
|
|
||||||
成绩管理 成绩项目、权重、录入、导入、提交、审核、发布、修改留痕
|
|
||||||
补考与重修 补考资格、报名、重修选课、成绩认定、历史记录
|
|
||||||
教学评价 学生评教、同行评价、督导评价、问卷配置、匿名统计
|
|
||||||
教师管理 教师档案、职称、所属院系、授课资格、教学工作量
|
|
||||||
教室资源 教室容量、类型、设备、占用情况、借用申请
|
|
||||||
毕业审核 学分完成度、必修课程、实践环节、毕业资格、学位资格
|
|
||||||
学业预警 不及格学分、低绩点、缺勤、延毕风险,预警规则与处理记录
|
|
||||||
审批中心 学籍异动、调课、缓考、成绩修改、免修、课程替代等统一审批
|
|
||||||
消息通知 站内消息、公告、待办提醒,可扩展短信、邮件、企业微信
|
|
||||||
统计报表 学生、课程、成绩、通过率、教师工作量、教室利用率等
|
|
||||||
数据导入导出 Excel模板、批量校验、错误报告、导入记录、报表导出
|
|
||||||
系统运维 参数配置、字典管理、任务调度、日志、备份、异常监控
|
|
||||||
|
|
||||||
三、学生端主要功能
|
> 说明:`[x]` 表示主业务链路已实现;`[-]` 表示已覆盖部分能力,仍需补齐;`[ ]` 表示尚未纳入当前实现。勾选依据为当前 API、前端路由和已合并功能,不以文档设想代替实际功能。
|
||||||
查看个人信息和学籍状态
|
|
||||||
查看培养方案及学分完成情况
|
## 一、角色与权限
|
||||||
在线选课、退课和候补
|
|
||||||
查看课表、考试安排和教室
|
- [x] 超级管理员:系统配置、权限、数据字典、日志与运维
|
||||||
查看成绩、绩点及成绩单
|
- [x] 教务处:校级培养方案、排课、选课、考试、成绩、学籍管理
|
||||||
发起缓考、免修、重修、学籍异动等申请
|
- [x] 院系教务员:按学院数据范围管理专业、课程、教学任务及成绩审核
|
||||||
查看审批进度和消息通知
|
- [x] 教师:课表、教学班名单、考勤、成绩录入、调停课申请
|
||||||
教学评价
|
- [x] 辅导员:学生信息、考勤记录与学业预警处理
|
||||||
查看学业预警和毕业审核进度
|
- [x] 学生:课表、选课、成绩、考试、学籍申请与个人档案
|
||||||
四、教师端主要功能
|
- [x] 校领导:统计报表、运行看板与决策数据
|
||||||
查看个人课表和教学任务
|
- [ ] 可选角色:财务人员、宿管人员、用人单位或家长
|
||||||
查看教学班学生名单
|
- [x] 权限采用“角色 + 数据范围”模式,学院管理员的查询、导出与写入均受学院范围约束
|
||||||
考勤和请假确认
|
|
||||||
平时成绩、考试成绩录入及Excel导入
|
## 二、核心功能模块
|
||||||
设置成绩构成与权重
|
|
||||||
提交成绩并查看审核状态
|
- [x] 基础数据:组织、校区、学院、专业、班级、教室、学期、课程分类等
|
||||||
调课、停课、补课、代课申请
|
- [x] 用户与权限:用户、角色、菜单与操作权限、登录日志、操作审计
|
||||||
查看评教结果和教学工作量
|
- [x] 学籍管理:学生档案、班级、学籍状态及转专业、休复退留等异动审批
|
||||||
导出点名册、成绩单等材料
|
- [x] 课程库:编号、学分、学时、课程性质、先修课程、课程简介与开课学院
|
||||||
五、关键业务规则
|
- [x] 培养方案:专业培养方案、课程模块、必修/选修与学分要求、版本管理
|
||||||
系统设计时应重点保证:
|
- [x] 教学任务:开课、授课教师、教学班、容量、授课周次、合班拆班
|
||||||
排课不能造成教师、班级或教室冲突
|
- [x] 智能排课:教师/班级/教室冲突校验、手工调整、发布与可配置优化偏好
|
||||||
选课需要校验时间冲突、容量、先修课程和选课资格
|
- [x] 选课管理:选课轮次、资格/容量/时间冲突/先修校验、退补选与候补
|
||||||
已发布成绩不能直接修改,必须申请、审批并保留修改记录
|
- [x] 课表管理:学生、教师、班级、教室课表及空教室查询、场地展示
|
||||||
培养方案需要版本化,不能因新方案影响旧年级学生
|
- [x] 调停课管理:调课、停课、补课、代课申请、审批和通知
|
||||||
学籍异动必须保留完整历史
|
- [x] 考勤管理:点名、请假、迟到早退缺勤、二维码签到、导入导出与统计
|
||||||
重要发布操作应支持撤回限制、二次确认和审计
|
- [x] 考试管理:考试计划、考场、监考教师、座位、签到表、自动排考与发布
|
||||||
Excel导入应先预检,确认无误后再正式写入
|
- [x] 成绩管理:成绩构成/权重、录入、导入、提交、审核、发布、修改留痕与分析
|
||||||
权限不仅控制菜单,还要控制学院、专业、班级等数据范围
|
- [x] 补考与重修:补考资格、报名、考场安排、成绩录入与历史记录
|
||||||
六、推荐建设阶段
|
- [x] 教学评价:学生评教、问卷配置、匿名提交、教师查看结果与统计
|
||||||
第一阶段:核心可用版
|
- [x] 教师管理:教师档案、院系归属、授课任务与教学工作量统计
|
||||||
组织、用户、权限和基础数据
|
- [x] 教室资源:容量、类型、设备、占用、空闲查询与借用预约
|
||||||
学生、教师、课程、班级、教室管理
|
- [x] 毕业与学位审核:学分完成度、必修课、实践环节、毕业资格和学位资格
|
||||||
培养方案
|
- [x] 学业预警:规则配置、不及格学分/低绩点/缺勤识别、处理记录与学生确认
|
||||||
教学任务
|
- [x] 审批中心:统一处理学籍异动、调停课、缓考、成绩修改等申请
|
||||||
排课与课表
|
- [-] 消息通知:站内消息、公告、待办提醒与审批深链;短信、邮件、企业微信尚未接入
|
||||||
学生选课
|
- [x] 统计报表:学生、课程、成绩、通过率、教师工作量、教室利用率及 Excel 导出
|
||||||
成绩录入、审核和查询
|
- [x] 数据导入导出:Excel 模板、校验、错误反馈、导入记录与报表导出
|
||||||
基础学籍管理
|
- [x] 系统运维:参数配置、任务调度、审计日志、备份、健康与异常任务监控
|
||||||
通知、审批、Excel导入导出
|
|
||||||
操作日志
|
## 三、学生端
|
||||||
第二阶段:完整教务版
|
|
||||||
考试与考场安排
|
- [x] 查看个人信息和学籍状态
|
||||||
补考、缓考、重修
|
- [x] 查看培养方案、课程修读情况、学分完成度和毕业审核结果
|
||||||
考勤与请假
|
- [x] 在线选课、退课和候补
|
||||||
调停课
|
- [x] 查看个人课表、考试安排、考场与座位
|
||||||
教学评价
|
- [x] 查看成绩、绩点、成绩明细与成绩单
|
||||||
学业预警
|
- [x] 发起缓考、重修、学籍异动等申请并查看审批进度
|
||||||
教师工作量
|
- [x] 完成教学评价
|
||||||
毕业与学位审核
|
- [x] 查看学业预警并确认/申诉考勤异常
|
||||||
综合统计报表
|
|
||||||
第三阶段:智慧教务版
|
## 四、教师端
|
||||||
智能排课优化
|
|
||||||
移动端或微信端
|
- [x] 查看个人课表和教学任务
|
||||||
电子成绩单与电子证明
|
- [x] 查看教学班学生名单
|
||||||
数据驾驶舱
|
- [x] 创建考勤、二维码签到、处理请假与导出统计
|
||||||
校园统一身份认证
|
- [x] 录入平时/考试/实验成绩,支持 Excel 导入与自动保存
|
||||||
对接财务、一卡通、宿舍、图书馆和教育厅平台
|
- [x] 设置成绩构成与权重,提交成绩并查看审核状态
|
||||||
|
- [x] 发起调课、停课、补课、代课申请
|
||||||
|
- [x] 查看评教结果和教学工作量
|
||||||
|
- [x] 导出点名册、成绩单、考试签到表等材料
|
||||||
|
|
||||||
|
## 五、已落实的关键规则
|
||||||
|
|
||||||
|
- [x] 排课校验教师、班级和教室冲突;容量与数据范围属于硬约束
|
||||||
|
- [x] 选课校验时间冲突、容量、先修条件与选课资格,并支持候补
|
||||||
|
- [x] 已发布成绩的修改受审批、审计记录与数据范围控制
|
||||||
|
- [x] 培养方案按版本管理,避免新方案影响历史年级
|
||||||
|
- [x] 学籍异动保留历史并进入审批链路
|
||||||
|
- [x] 重要发布操作支持确认、异步进度/撤回边界和审计
|
||||||
|
- [x] Excel 导入提供模板、字段校验与错误反馈后再落库
|
||||||
|
- [x] 菜单权限之外,服务端同时校验学院等数据范围
|
||||||
|
|
||||||
|
## 六、建议新增功能(按优先级)
|
||||||
|
|
||||||
|
### P0:补齐教学闭环
|
||||||
|
|
||||||
|
- [ ] **课程替代、免修与学分认定**:学生线上申请,教务员审批;用可追溯的等价课程规则自动重新计算培养方案和毕业审核结果。
|
||||||
|
- [ ] **调停课影响确认**:调停课获批后自动识别受影响学生、教室和后续考试;学生可确认已知悉,未确认者自动提醒辅导员。
|
||||||
|
- [ ] **候补自动递补可解释化**:明确候补队列、递补触发时间、递补失败原因与通知记录,避免人工解释和争议。
|
||||||
|
|
||||||
|
### P1:提升教学质量与服务体验
|
||||||
|
|
||||||
|
- [ ] **教学评价闭环**:在现有学生评教之外加入同行/督导评价、改进计划、复评和按学期追踪,避免评价只形成分数。
|
||||||
|
- [ ] **学业风险处置工作流**:预警后生成辅导员跟进任务、谈话记录、帮扶措施与复核日期;仅向授权人员可见敏感信息。
|
||||||
|
- [ ] **学期校历与关键节点中心**:统一配置选课、成绩、评教、考试、毕业审核等截止时间,自动显示角色待办并在临期提醒。
|
||||||
|
- [ ] **教室设备与故障闭环**:场地二维码报修、设备可用状态、维修工单和排课时的不可用自动避让。
|
||||||
|
|
||||||
|
### P2:数据治理与开放能力
|
||||||
|
|
||||||
|
- [ ] **数据质量中心**:发现重复人员、缺失学籍、异常选课、超容量、孤立数据等问题,提供责任人、修复建议和处理进度。
|
||||||
|
- [ ] **集成中心**:为统一身份认证、财务、一卡通、宿舍、图书馆及教育厅提供可配置的同步任务、字段映射、重试和对账日志。
|
||||||
|
- [ ] **隐私与数据留存策略**:敏感字段脱敏、导出水印/审批、访问审计检索和按规则归档或清理。
|
||||||
|
|
||||||
|
## 七、建设节奏
|
||||||
|
|
||||||
|
- [x] 第一阶段:核心可用版(基础数据、权限、培养方案、教学任务、排课课表、选课、成绩、学籍、通知审批、Excel、审计)
|
||||||
|
- [x] 第二阶段:完整教务版(考试、补考重修、考勤、调停课、评教、预警、工作量、毕业学位审核、统计)
|
||||||
|
- [-] 第三阶段:智慧教务版(智能排课优化、移动端、电子成绩单/证明、数据看板、统一身份认证已具备;跨系统集成待完善)
|
||||||
@@ -93,6 +93,46 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
return Ok(new PagedResult<ApprovalItem>(pageItems, total, page, pageSize));
|
return Ok(new PagedResult<ApprovalItem>(pageItems, total, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("pending/{type}/{id:guid}")]
|
||||||
|
[Authorize(Roles = Managers)]
|
||||||
|
public async Task<ActionResult<ApprovalItem>> GetPendingItem(
|
||||||
|
string type,
|
||||||
|
Guid id,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
ApprovalItem? item = type switch
|
||||||
|
{
|
||||||
|
"CourseExemption" => await Scoped<CourseExemption>(x =>
|
||||||
|
x.Id == id && x.Status == ApprovalStatus.Submitted)
|
||||||
|
.Select(x => new ApprovalItem(x.Id, "CourseExemption", "免修",
|
||||||
|
$"{x.Student!.Name} — 《{x.TeachingTask!.Course!.Name}》", x.Reason,
|
||||||
|
x.SubmittedAt, x.TeachingTask.Course!.College!.Name))
|
||||||
|
.FirstOrDefaultAsync(ct),
|
||||||
|
"DeferredExam" => await Scoped<DeferredExam>(x =>
|
||||||
|
x.Id == id && x.Status == ApprovalStatus.Submitted)
|
||||||
|
.Select(x => new ApprovalItem(x.Id, "DeferredExam", "缓考",
|
||||||
|
$"{x.Student!.Name} — 《{x.TeachingTask!.Course!.Name}》", x.Reason,
|
||||||
|
x.SubmittedAt, x.TeachingTask.Course!.College!.Name))
|
||||||
|
.FirstOrDefaultAsync(ct),
|
||||||
|
"CourseSubstitution" => await Scoped<CourseSubstitution>(x =>
|
||||||
|
x.Id == id && x.Status == ApprovalStatus.Submitted)
|
||||||
|
.Select(x => new ApprovalItem(x.Id, "CourseSubstitution", "课程替代",
|
||||||
|
$"{x.Student!.Name} — 《{x.SubstituteCourse!.Name}》→《{x.OriginalCourse!.Name}》",
|
||||||
|
x.Reason, x.SubmittedAt, x.Student.AdministrativeClass!.Major!.College!.Name))
|
||||||
|
.FirstOrDefaultAsync(ct),
|
||||||
|
"GradeModification" => await Scoped<GradeModification>(x =>
|
||||||
|
x.Id == id && (x.Status == GradeModificationStatus.TeacherSubmitted ||
|
||||||
|
x.Status == GradeModificationStatus.CollegeApproved))
|
||||||
|
.Select(x => new ApprovalItem(x.Id, "GradeModification", "成绩修改",
|
||||||
|
$"{x.GradeRecord!.Student!.Name} — 《{x.GradeRecord.GradeSheet!.TeachingTask!.Course!.Name}》",
|
||||||
|
$"{x.CurrentScore} → {x.RequestedScore}:{x.Reason}", x.SubmittedAt,
|
||||||
|
x.GradeRecord.GradeSheet.TeachingTask.Course!.College!.Name))
|
||||||
|
.FirstOrDefaultAsync(ct),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
return item is null ? NotFound() : Ok(item);
|
||||||
|
}
|
||||||
|
|
||||||
// ═══════════════ Course Exemption ═══════════════
|
// ═══════════════ Course Exemption ═══════════════
|
||||||
[HttpGet("exemptions/mine")]
|
[HttpGet("exemptions/mine")]
|
||||||
[Authorize(Roles = SystemRoles.Student)]
|
[Authorize(Roles = SystemRoles.Student)]
|
||||||
@@ -124,7 +164,7 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
var info = await db.CourseExemptions.Where(x => x.Id == ex.Id)
|
var info = await db.CourseExemptions.Where(x => x.Id == ex.Id)
|
||||||
.Select(x => new { StudentName = x.Student!.Name, CourseName = x.TeachingTask!.Course!.Name })
|
.Select(x => new { StudentName = x.Student!.Name, CourseName = x.TeachingTask!.Course!.Name })
|
||||||
.FirstAsync(ct);
|
.FirstAsync(ct);
|
||||||
await NotifyManagers("免修申请", $"{info.StudentName} 申请免修《{info.CourseName}》", ct);
|
await NotifyManagers("CourseExemption", ex.Id, "免修申请", $"{info.StudentName} 申请免修《{info.CourseName}》", ct);
|
||||||
return Created("", new { ex.Id });
|
return Created("", new { ex.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +227,7 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
var info = await db.DeferredExams.Where(x => x.Id == d.Id)
|
var info = await db.DeferredExams.Where(x => x.Id == d.Id)
|
||||||
.Select(x => new { StudentName = x.Student!.Name, CourseName = x.TeachingTask!.Course!.Name })
|
.Select(x => new { StudentName = x.Student!.Name, CourseName = x.TeachingTask!.Course!.Name })
|
||||||
.FirstAsync(ct);
|
.FirstAsync(ct);
|
||||||
await NotifyManagers("缓考申请", $"{info.StudentName} 申请《{info.CourseName}》缓考", ct);
|
await NotifyManagers("DeferredExam", d.Id, "缓考申请", $"{info.StudentName} 申请《{info.CourseName}》缓考", ct);
|
||||||
return Created("", new { d.Id });
|
return Created("", new { d.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +279,7 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
.FirstAsync(ct);
|
.FirstAsync(ct);
|
||||||
await NotificationService.SendToRoleAsync(db, SystemRoles.CollegeAdmin, "成绩修改待审核",
|
await NotificationService.SendToRoleAsync(db, SystemRoles.CollegeAdmin, "成绩修改待审核",
|
||||||
$"{gmInfo.StudentName} — 《{gmInfo.CourseName}》{gmInfo.CurrentScore}→{gmInfo.RequestedScore}",
|
$"{gmInfo.StudentName} — 《{gmInfo.CourseName}》{gmInfo.CurrentScore}→{gmInfo.RequestedScore}",
|
||||||
|
linkUrl: $"/approvals?tab=pending&type=GradeModification&id={gm.Id}",
|
||||||
cancellationToken: ct,
|
cancellationToken: ct,
|
||||||
category: NotificationCategory.Grade);
|
category: NotificationCategory.Grade);
|
||||||
return Created("", new { gm.Id });
|
return Created("", new { gm.Id });
|
||||||
@@ -258,7 +299,7 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
SystemRoles.AcademicAdmin,
|
SystemRoles.AcademicAdmin,
|
||||||
"成绩修改待校级审核",
|
"成绩修改待校级审核",
|
||||||
$"{gm.GradeRecord!.Student!.Name} — 《{gm.GradeRecord.GradeSheet!.TeachingTask!.Course!.Name}》",
|
$"{gm.GradeRecord!.Student!.Name} — 《{gm.GradeRecord.GradeSheet!.TeachingTask!.Course!.Name}》",
|
||||||
linkUrl: "/approvals",
|
linkUrl: $"/approvals?tab=pending&type=GradeModification&id={gm.Id}",
|
||||||
cancellationToken: ct,
|
cancellationToken: ct,
|
||||||
category: NotificationCategory.Grade);
|
category: NotificationCategory.Grade);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
@@ -406,7 +447,7 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
var info = await db.CourseSubstitutions.Where(x => x.Id == cs.Id)
|
var info = await db.CourseSubstitutions.Where(x => x.Id == cs.Id)
|
||||||
.Select(x => new { StudentName = x.Student!.Name, SubName = x.SubstituteCourse!.Name, OrigName = x.OriginalCourse!.Name })
|
.Select(x => new { StudentName = x.Student!.Name, SubName = x.SubstituteCourse!.Name, OrigName = x.OriginalCourse!.Name })
|
||||||
.FirstAsync(ct);
|
.FirstAsync(ct);
|
||||||
await NotifyManagers("课程替代申请", $"{info.StudentName} 申请用《{info.SubName}》替代《{info.OrigName}》", ct);
|
await NotifyManagers("CourseSubstitution", cs.Id, "课程替代申请", $"{info.StudentName} 申请用《{info.SubName}》替代《{info.OrigName}》", ct);
|
||||||
return Created("", new { cs.Id });
|
return Created("", new { cs.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +629,7 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
ct);
|
ct);
|
||||||
private ActionResult StudentNotFound() => Conflict(new ProblemDetails { Title = "未关联学生档案", Detail = "当前账号未关联有效学生档案。", Status = 409 });
|
private ActionResult StudentNotFound() => Conflict(new ProblemDetails { Title = "未关联学生档案", Detail = "当前账号未关联有效学生档案。", Status = 409 });
|
||||||
|
|
||||||
private async Task NotifyManagers(string title, string content, CancellationToken ct) { await NotificationService.SendToRoleAsync(db, SystemRoles.CollegeAdmin, title, content, cancellationToken: ct, category: NotificationCategory.Approval); await NotificationService.SendToRoleAsync(db, SystemRoles.AcademicAdmin, title, content, cancellationToken: ct, category: NotificationCategory.Approval); }
|
private async Task NotifyManagers(string type, Guid id, string title, string content, CancellationToken ct) { var linkUrl = $"/approvals?tab=pending&type={type}&id={id}"; await NotificationService.SendToRoleAsync(db, SystemRoles.CollegeAdmin, title, content, linkUrl: linkUrl, cancellationToken: ct, category: NotificationCategory.Approval); await NotificationService.SendToRoleAsync(db, SystemRoles.AcademicAdmin, title, content, linkUrl: linkUrl, cancellationToken: ct, category: NotificationCategory.Approval); }
|
||||||
private async Task NotifyStudent(Guid sid, string title, string content, CancellationToken ct) { var uid = await db.Students.Where(s => s.Id == sid).Select(s => s.UserId).FirstOrDefaultAsync(ct); if (uid.HasValue) await NotificationService.SendAsync(db, uid.Value, title, content, "/approvals", ct, NotificationCategory.Approval); }
|
private async Task NotifyStudent(Guid sid, string title, string content, CancellationToken ct) { var uid = await db.Students.Where(s => s.Id == sid).Select(s => s.UserId).FirstOrDefaultAsync(ct); if (uid.HasValue) await NotificationService.SendAsync(db, uid.Value, title, content, "/approvals", ct, NotificationCategory.Approval); }
|
||||||
private async Task NotifyCollege(GradeModification gm, CancellationToken ct) { await NotificationService.SendToRoleAsync(db, SystemRoles.CollegeAdmin, "成绩修改待审核", $"{gm.GradeRecord!.Student!.Name} — 《{gm.GradeRecord.GradeSheet!.TeachingTask!.Course!.Name}》{gm.CurrentScore}→{gm.RequestedScore}", cancellationToken: ct, category: NotificationCategory.Grade); }
|
private async Task NotifyCollege(GradeModification gm, CancellationToken ct) { await NotificationService.SendToRoleAsync(db, SystemRoles.CollegeAdmin, "成绩修改待审核", $"{gm.GradeRecord!.Student!.Name} — 《{gm.GradeRecord.GradeSheet!.TeachingTask!.Course!.Name}》{gm.CurrentScore}→{gm.RequestedScore}", cancellationToken: ct, category: NotificationCategory.Grade); }
|
||||||
private async Task NotifyRole(string role, string title, string content, CancellationToken ct) { await NotificationService.SendToRoleAsync(db, role, title, content, cancellationToken: ct, category: NotificationCategory.Approval); }
|
private async Task NotifyRole(string role, string title, string content, CancellationToken ct) { await NotificationService.SendToRoleAsync(db, role, title, content, cancellationToken: ct, category: NotificationCategory.Approval); }
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
.Select(x => new ClassroomListItem(
|
.Select(x => new ClassroomListItem(
|
||||||
x.Id, x.Code, x.Name, x.BuildingId, x.Building!.CampusId,
|
x.Id, x.Code, x.Name, x.BuildingId, x.Building!.CampusId,
|
||||||
x.Building.Name, x.Building.Campus!.Name, x.Capacity,
|
x.Building.Name, x.Building.Campus!.Name, x.Capacity,
|
||||||
x.RoomType, x.TeachingVenueNature, x.Equipment,
|
x.Floor, x.RoomType, x.TeachingVenueNature, x.Equipment,
|
||||||
x.IsEnabled, x.SortOrder))
|
x.IsEnabled, x.SortOrder))
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
return Ok(new PagedResult<ClassroomListItem>(
|
return Ok(new PagedResult<ClassroomListItem>(
|
||||||
@@ -578,6 +578,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
x.Building.Name,
|
x.Building.Name,
|
||||||
x.Building.Campus!.Name,
|
x.Building.Campus!.Name,
|
||||||
x.Capacity,
|
x.Capacity,
|
||||||
|
x.Floor,
|
||||||
x.RoomType,
|
x.RoomType,
|
||||||
x.TeachingVenueNature,
|
x.TeachingVenueNature,
|
||||||
x.Equipment,
|
x.Equipment,
|
||||||
@@ -645,6 +646,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
Name = request.Name.Trim(),
|
Name = request.Name.Trim(),
|
||||||
BuildingId = request.BuildingId,
|
BuildingId = request.BuildingId,
|
||||||
Capacity = request.Capacity,
|
Capacity = request.Capacity,
|
||||||
|
Floor = request.Floor,
|
||||||
RoomType = request.RoomType.Trim(),
|
RoomType = request.RoomType.Trim(),
|
||||||
TeachingVenueNature = request.TeachingVenueNature == 0
|
TeachingVenueNature = request.TeachingVenueNature == 0
|
||||||
? TeachingVenueNature.GeneralClassroom
|
? TeachingVenueNature.GeneralClassroom
|
||||||
@@ -668,6 +670,7 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
|
|||||||
ApplyCatalog(entity, request);
|
ApplyCatalog(entity, request);
|
||||||
entity.BuildingId = request.BuildingId;
|
entity.BuildingId = request.BuildingId;
|
||||||
entity.Capacity = request.Capacity;
|
entity.Capacity = request.Capacity;
|
||||||
|
entity.Floor = request.Floor;
|
||||||
entity.RoomType = request.RoomType.Trim();
|
entity.RoomType = request.RoomType.Trim();
|
||||||
entity.TeachingVenueNature = request.TeachingVenueNature == 0
|
entity.TeachingVenueNature = request.TeachingVenueNature == 0
|
||||||
? TeachingVenueNature.GeneralClassroom
|
? TeachingVenueNature.GeneralClassroom
|
||||||
@@ -822,6 +825,7 @@ public sealed record ClassroomRequest(
|
|||||||
string Code, string Name, int SortOrder, bool IsEnabled,
|
string Code, string Name, int SortOrder, bool IsEnabled,
|
||||||
Guid BuildingId,
|
Guid BuildingId,
|
||||||
[Range(1, 1000)] int Capacity,
|
[Range(1, 1000)] int Capacity,
|
||||||
|
[Range(1, 99)] int Floor,
|
||||||
[Required, MaxLength(40)] string RoomType,
|
[Required, MaxLength(40)] string RoomType,
|
||||||
TeachingVenueNature TeachingVenueNature,
|
TeachingVenueNature TeachingVenueNature,
|
||||||
[MaxLength(300)] string? Equipment)
|
[MaxLength(300)] string? Equipment)
|
||||||
@@ -879,6 +883,7 @@ public sealed record ClassroomListItem(
|
|||||||
string BuildingName,
|
string BuildingName,
|
||||||
string CampusName,
|
string CampusName,
|
||||||
int Capacity,
|
int Capacity,
|
||||||
|
int Floor,
|
||||||
string RoomType,
|
string RoomType,
|
||||||
TeachingVenueNature TeachingVenueNature,
|
TeachingVenueNature TeachingVenueNature,
|
||||||
string? Equipment,
|
string? Equipment,
|
||||||
|
|||||||
Loaded 3 of 52 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user