Implement requested workflow and UI updates
This commit is contained in:
1 parent
5959585864
commit
390d0b123f
18 files changed
+7800
-35
No files matched your search
@@ -78,9 +78,8 @@
|
|||||||
|
|
||||||
### P0:补齐教学闭环
|
### P0:补齐教学闭环
|
||||||
|
|
||||||
- [ ] **课程替代、免修与学分认定**:学生线上申请,教务员审批;用可追溯的等价课程规则自动重新计算培养方案和毕业审核结果。
|
- [x] **课程替代、免修与学分认定**:学生线上申请,教务员审批;用可追溯的等价课程规则自动重新计算培养方案和毕业审核结果。
|
||||||
- [ ] **调停课影响确认**:调停课获批后自动识别受影响学生、教室和后续考试;学生可确认已知悉,未确认者自动提醒辅导员。
|
- [x] **候补自动递补可解释化**:明确候补队列、递补触发时间、递补失败原因与通知记录,避免人工解释和争议。
|
||||||
- [ ] **候补自动递补可解释化**:明确候补队列、递补触发时间、递补失败原因与通知记录,避免人工解释和争议。
|
|
||||||
|
|
||||||
### P1:提升教学质量与服务体验
|
### P1:提升教学质量与服务体验
|
||||||
|
|
||||||
|
|||||||
@@ -446,6 +446,15 @@ public sealed class AcademicPlanningController(
|
|||||||
.Select(x => x.CourseId)
|
.Select(x => x.CourseId)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
|
var approvedSubstitutions = await db.CourseSubstitutions.AsNoTracking()
|
||||||
|
.Where(x => x.StudentId == student.Id && x.Status == ApprovalStatus.Approved)
|
||||||
|
.Select(x => new { x.OriginalCourseId, x.SubstituteCourseId })
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
foreach (var substitution in approvedSubstitutions)
|
||||||
|
{
|
||||||
|
if (passedCourseIds.Contains(substitution.SubstituteCourseId))
|
||||||
|
passedCourseIds.Add(substitution.OriginalCourseId);
|
||||||
|
}
|
||||||
var failedCourseIds = gradeAttempts
|
var failedCourseIds = gradeAttempts
|
||||||
.GroupBy(x => x.CourseId)
|
.GroupBy(x => x.CourseId)
|
||||||
.Where(group => !group.Any(x =>
|
.Where(group => !group.Any(x =>
|
||||||
|
|||||||
@@ -439,9 +439,22 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
ct);
|
ct);
|
||||||
if (!hasPassedSubstitute)
|
if (!hasPassedSubstitute)
|
||||||
return ConflictProblem("替代课程必须具有已发布且及格的成绩。");
|
return ConflictProblem("替代课程必须具有已发布且及格的成绩。");
|
||||||
|
var applicant = await db.Students.AsNoTracking().Where(x => x.Id == sid.Value)
|
||||||
|
.Select(x => new { MajorId = x.AdministrativeClass!.MajorId, x.EnrollmentYear })
|
||||||
|
.FirstAsync(ct);
|
||||||
|
var rule = await db.CourseEquivalencyRules.AsNoTracking()
|
||||||
|
.Where(x => x.IsEnabled && x.OriginalCourseId == req.OriginalCourseId &&
|
||||||
|
x.SubstituteCourseId == req.SubstituteCourseId)
|
||||||
|
.Where(x => !x.MajorId.HasValue || x.MajorId == applicant.MajorId)
|
||||||
|
.Where(x => !x.EffectiveGrade.HasValue || x.EffectiveGrade == applicant.EnrollmentYear)
|
||||||
|
.OrderByDescending(x => x.MajorId.HasValue)
|
||||||
|
.ThenByDescending(x => x.EffectiveGrade.HasValue)
|
||||||
|
.FirstOrDefaultAsync(ct);
|
||||||
|
if (rule is null)
|
||||||
|
return ConflictProblem("该课程组合未配置适用的等价课程规则,暂不能申请替代。");
|
||||||
if (await db.CourseSubstitutions.AnyAsync(x => x.StudentId == sid && x.OriginalCourseId == req.OriginalCourseId && x.Status == ApprovalStatus.Submitted, ct))
|
if (await db.CourseSubstitutions.AnyAsync(x => x.StudentId == sid && x.OriginalCourseId == req.OriginalCourseId && x.Status == ApprovalStatus.Submitted, ct))
|
||||||
return ConflictProblem("该课程已有替代申请在审核中。");
|
return ConflictProblem("该课程已有替代申请在审核中。");
|
||||||
var cs = new CourseSubstitution { StudentId = sid.Value, OriginalCourseId = req.OriginalCourseId, SubstituteCourseId = req.SubstituteCourseId, Reason = req.Reason.Trim() };
|
var cs = new CourseSubstitution { StudentId = sid.Value, OriginalCourseId = req.OriginalCourseId, SubstituteCourseId = req.SubstituteCourseId, CourseEquivalencyRuleId = rule.Id, Reason = req.Reason.Trim() };
|
||||||
db.CourseSubstitutions.Add(cs);
|
db.CourseSubstitutions.Add(cs);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
var info = await db.CourseSubstitutions.Where(x => x.Id == cs.Id)
|
var info = await db.CourseSubstitutions.Where(x => x.Id == cs.Id)
|
||||||
@@ -458,35 +471,8 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
|
|||||||
var cs = await db.CourseSubstitutions.Include(x => x.Student).Include(x => x.OriginalCourse).Include(x => x.SubstituteCourse).FirstOrDefaultAsync(x => x.Id == id, ct);
|
var cs = await db.CourseSubstitutions.Include(x => x.Student).Include(x => x.OriginalCourse).Include(x => x.SubstituteCourse).FirstOrDefaultAsync(x => x.Id == id, ct);
|
||||||
if (cs is null) return NotFound();
|
if (cs is null) return NotFound();
|
||||||
cs.Status = ApprovalStatus.Approved; cs.ReviewedAt = DateTime.UtcNow; cs.ReviewedByUserId = scope.Current.UserId;
|
cs.Status = ApprovalStatus.Approved; cs.ReviewedAt = DateTime.UtcNow; cs.ReviewedByUserId = scope.Current.UserId;
|
||||||
// Auto-apply: copy passed grade to original course
|
// Do not fabricate or edit a grade record. Formal curriculum and
|
||||||
var currentTerm = await db.AcademicTerms.FirstOrDefaultAsync(x => x.IsCurrent, ct);
|
// graduation calculations resolve this approved rule from source data.
|
||||||
if (currentTerm is not null)
|
|
||||||
{
|
|
||||||
var passedRecord = await db.GradeRecords
|
|
||||||
.Where(x => x.StudentId == cs.StudentId && x.GradeSheet!.TeachingTask!.CourseId == cs.SubstituteCourseId && x.TotalScore >= 60)
|
|
||||||
.OrderByDescending(x => x.GradeSheet!.TeachingTask!.AcademicTerm!.StartDate)
|
|
||||||
.FirstOrDefaultAsync(ct);
|
|
||||||
if (passedRecord is not null)
|
|
||||||
{
|
|
||||||
// Find or create grade record for original course
|
|
||||||
var origTask = await db.TeachingTasks.FirstOrDefaultAsync(x => x.CourseId == cs.OriginalCourseId && x.AcademicTermId == currentTerm.Id, ct);
|
|
||||||
if (origTask is not null)
|
|
||||||
{
|
|
||||||
var origSheet = await db.GradeSheets.FirstOrDefaultAsync(x => x.TeachingTaskId == origTask.Id, ct);
|
|
||||||
if (origSheet is not null)
|
|
||||||
{
|
|
||||||
var origRecord = await db.GradeRecords.FirstOrDefaultAsync(x => x.GradeSheetId == origSheet.Id && x.StudentId == cs.StudentId, ct);
|
|
||||||
if (origRecord is not null)
|
|
||||||
{
|
|
||||||
origRecord.TotalScore = passedRecord.TotalScore;
|
|
||||||
origRecord.GradePoint = passedRecord.GradePoint;
|
|
||||||
origRecord.Notes =
|
|
||||||
$"课程替代:{cs.SubstituteCourse!.Code} {cs.SubstituteCourse.Name}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
await NotifyStudent(cs.StudentId, "课程替代申请已通过", $"《{cs.SubstituteCourse!.Name}》替代《{cs.OriginalCourse!.Name}》已生效。", ct);
|
await NotifyStudent(cs.StudentId, "课程替代申请已通过", $"《{cs.SubstituteCourse!.Name}》替代《{cs.OriginalCourse!.Name}》已生效。", ct);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
|
|||||||
Loaded 3 of 18 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user