Compare commits
19
Commits
v2.4.2
...
4c95edb678
No files matched your search
@@ -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,
|
||||||
|
|||||||
@@ -212,7 +212,8 @@ public sealed class CourseAdjustmentsController(
|
|||||||
Guid? academicTermId,
|
Guid? academicTermId,
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int pageSize = 20,
|
int pageSize = 20,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default,
|
||||||
|
Guid? adjustmentId = null)
|
||||||
{
|
{
|
||||||
if (page < 1 || pageSize is < 1 or > 100)
|
if (page < 1 || pageSize is < 1 or > 100)
|
||||||
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||||
@@ -223,7 +224,9 @@ public sealed class CourseAdjustmentsController(
|
|||||||
if (scope.Scope == DataScope.College)
|
if (scope.Scope == DataScope.College)
|
||||||
source = source.Where(x =>
|
source = source.Where(x =>
|
||||||
x.TeachingTask!.Course!.CollegeId == scope.RestrictedCollegeId);
|
x.TeachingTask!.Course!.CollegeId == scope.RestrictedCollegeId);
|
||||||
if (academicTermId.HasValue)
|
if (adjustmentId.HasValue)
|
||||||
|
source = source.Where(x => x.Id == adjustmentId.Value);
|
||||||
|
else if (academicTermId.HasValue)
|
||||||
source = source.Where(x =>
|
source = source.Where(x =>
|
||||||
x.TeachingTask!.AcademicTermId == academicTermId);
|
x.TeachingTask!.AcademicTermId == academicTermId);
|
||||||
|
|
||||||
@@ -324,7 +327,7 @@ public sealed class CourseAdjustmentsController(
|
|||||||
SystemRoles.CollegeAdmin,
|
SystemRoles.CollegeAdmin,
|
||||||
$"新的{tl}申请",
|
$"新的{tl}申请",
|
||||||
$"《{taskInfo.Name}》提交了{tl}申请,请及时审核。",
|
$"《{taskInfo.Name}》提交了{tl}申请,请及时审核。",
|
||||||
taskInfo.CollegeId, "/course-adjustments", cancellationToken,
|
taskInfo.CollegeId, $"/course-adjustments?adjustmentId={adj.Id}", cancellationToken,
|
||||||
NotificationCategory.Schedule);
|
NotificationCategory.Schedule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,13 +380,13 @@ public sealed class CourseAdjustmentsController(
|
|||||||
$"新的{TypeLabel(adj.Type)}申请",
|
$"新的{TypeLabel(adj.Type)}申请",
|
||||||
$"《{courseName}》提交了{TypeLabel(adj.Type)}申请,请及时审核。",
|
$"《{courseName}》提交了{TypeLabel(adj.Type)}申请,请及时审核。",
|
||||||
adj.TeachingTask.Course.CollegeId,
|
adj.TeachingTask.Course.CollegeId,
|
||||||
"/course-adjustments", cancellationToken,
|
$"/course-adjustments?adjustmentId={adj.Id}", cancellationToken,
|
||||||
NotificationCategory.Schedule);
|
NotificationCategory.Schedule);
|
||||||
await NotificationService.SendToRoleAsync(db,
|
await NotificationService.SendToRoleAsync(db,
|
||||||
SystemRoles.AcademicAdmin,
|
SystemRoles.AcademicAdmin,
|
||||||
$"新的{TypeLabel(adj.Type)}申请",
|
$"新的{TypeLabel(adj.Type)}申请",
|
||||||
$"《{courseName}》提交了{TypeLabel(adj.Type)}申请。",
|
$"《{courseName}》提交了{TypeLabel(adj.Type)}申请。",
|
||||||
null, "/course-adjustments", cancellationToken,
|
null, $"/course-adjustments?adjustmentId={adj.Id}", cancellationToken,
|
||||||
NotificationCategory.Schedule);
|
NotificationCategory.Schedule);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 3 of 51 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user