学籍异动:休学、复学、退学申请及辅导员→学院→教务处分级审批。
毕业审核:批次计算、缺失课程检查、人工复核、结果发布。 学位授予:毕业资格与 GPA 计算、人工调整、发布授予结果。 毕业离校:离校事项配置、责任角色分工、逐项办理及批次关闭。
This commit is contained in:
@@ -51,6 +51,31 @@ public sealed class DashboardController(AppDbContext db) : ControllerBase
|
||||
GradeRecords = await db.GradeRecords.CountAsync(cancellationToken),
|
||||
ExamPlans = await db.ExamPlans.CountAsync(cancellationToken),
|
||||
ExamSessions = await db.ExamSessions.CountAsync(cancellationToken),
|
||||
StudentStatusChanges = await db.StudentStatusChanges
|
||||
.CountAsync(cancellationToken),
|
||||
PendingStudentStatusChanges = await db.StudentStatusChanges.CountAsync(
|
||||
x => x.State == StudentStatusChangeState.Submitted ||
|
||||
x.State == StudentStatusChangeState.CounselorApproved ||
|
||||
x.State == StudentStatusChangeState.CollegeApproved,
|
||||
cancellationToken),
|
||||
GraduationAuditBatches = await db.GraduationAuditBatches
|
||||
.CountAsync(cancellationToken),
|
||||
PublishedGraduationAuditBatches = await db.GraduationAuditBatches
|
||||
.CountAsync(
|
||||
x => x.Status == GraduationAuditBatchStatus.Published,
|
||||
cancellationToken),
|
||||
DegreeAwardBatches = await db.DegreeAwardBatches
|
||||
.CountAsync(cancellationToken),
|
||||
PublishedDegreeAwardBatches = await db.DegreeAwardBatches
|
||||
.CountAsync(
|
||||
x => x.Status == DegreeAwardBatchStatus.Published,
|
||||
cancellationToken),
|
||||
GraduationClearanceBatches = await db.GraduationClearanceBatches
|
||||
.CountAsync(cancellationToken),
|
||||
OpenGraduationClearanceBatches = await db.GraduationClearanceBatches
|
||||
.CountAsync(
|
||||
x => x.Status == GraduationClearanceBatchStatus.Open,
|
||||
cancellationToken),
|
||||
Users = await db.Users.CountAsync(cancellationToken)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Auth;
|
||||
using Jiaowu.Api.Infrastructure.Graduation;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/degree-awards")]
|
||||
public sealed class DegreeAwardsController(
|
||||
AppDbContext db,
|
||||
ICurrentUserDataScope currentUserDataScope) : ControllerBase
|
||||
{
|
||||
private const string Managers =
|
||||
SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin;
|
||||
private const string Reviewers = Managers + "," + SystemRoles.CollegeAdmin;
|
||||
|
||||
[HttpGet("batches")]
|
||||
[Authorize(Roles = Reviewers)]
|
||||
public async Task<ActionResult> GetBatches(CancellationToken token)
|
||||
{
|
||||
var collegeId = RestrictedCollegeId();
|
||||
return Ok(await db.DegreeAwardBatches.AsNoTracking()
|
||||
.OrderByDescending(x => x.GraduationYear)
|
||||
.ThenByDescending(x => x.CreatedAt)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.Name, x.GraduationYear, x.DegreeName,
|
||||
x.MinimumGradePoint, x.Status, x.Notes,
|
||||
x.CalculatedAt, x.PublishedAt,
|
||||
ResultCount = x.Results.Count(result =>
|
||||
!collegeId.HasValue ||
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId == collegeId),
|
||||
GrantedCount = x.Results.Count(result =>
|
||||
(!collegeId.HasValue ||
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId == collegeId) &&
|
||||
result.Conclusion == DegreeAwardConclusion.Granted),
|
||||
NotGrantedCount = x.Results.Count(result =>
|
||||
(!collegeId.HasValue ||
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId == collegeId) &&
|
||||
result.Conclusion == DegreeAwardConclusion.NotGranted),
|
||||
OverrideCount = x.Results.Count(result =>
|
||||
(!collegeId.HasValue ||
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId == collegeId) &&
|
||||
result.IsOverridden)
|
||||
}).ToListAsync(token));
|
||||
}
|
||||
|
||||
[HttpGet("batches/{id:guid}")]
|
||||
[Authorize(Roles = Reviewers)]
|
||||
public async Task<ActionResult> GetBatch(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.DegreeAwardBatches.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (batch is null) return NotFound();
|
||||
var collegeId = RestrictedCollegeId();
|
||||
var source = db.DegreeAwardResults.AsNoTracking()
|
||||
.Where(x => x.DegreeAwardBatchId == id);
|
||||
if (collegeId.HasValue)
|
||||
source = source.Where(x =>
|
||||
x.Student!.AdministrativeClass!.Major!.CollegeId == collegeId);
|
||||
return Ok(new
|
||||
{
|
||||
batch.Id, batch.Name, batch.GraduationYear, batch.DegreeName,
|
||||
batch.MinimumGradePoint, batch.Status, batch.Notes,
|
||||
batch.CalculatedAt, batch.PublishedAt,
|
||||
Results = await source.OrderBy(x => x.Student!.StudentNumber)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.StudentId, x.Student!.StudentNumber, x.Student.Name,
|
||||
ClassName = x.Student.AdministrativeClass!.Name,
|
||||
MajorName = x.Student.AdministrativeClass.Major!.Name,
|
||||
CollegeName = x.Student.AdministrativeClass.Major.College!.Name,
|
||||
x.AverageGradePoint, x.CalculatedConclusion, x.Conclusion,
|
||||
x.ExceptionReason, x.IsOverridden,
|
||||
x.ReviewComment, x.ReviewedAt
|
||||
}).ToListAsync(token)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("batches")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> Create(
|
||||
DegreeAwardBatchRequest request,
|
||||
CancellationToken token)
|
||||
{
|
||||
var batch = new DegreeAwardBatch
|
||||
{
|
||||
Name = request.Name.Trim(),
|
||||
GraduationYear = request.GraduationYear,
|
||||
DegreeName = request.DegreeName.Trim(),
|
||||
MinimumGradePoint = request.MinimumGradePoint,
|
||||
Notes = request.Notes?.Trim()
|
||||
};
|
||||
db.DegreeAwardBatches.Add(batch);
|
||||
await db.SaveChangesAsync(token);
|
||||
return Created(string.Empty, new { batch.Id });
|
||||
}
|
||||
|
||||
[HttpPost("batches/{id:guid}/calculate")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> Calculate(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.DegreeAwardBatches
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (batch is null) return NotFound();
|
||||
if (batch.Status != DegreeAwardBatchStatus.Draft)
|
||||
return ConflictProblem("已发布批次不能重新计算。");
|
||||
|
||||
var auditCandidates = await db.GraduationAuditResults.AsNoTracking()
|
||||
.Include(x => x.GraduationAuditBatch)
|
||||
.Include(x => x.Student)
|
||||
.ThenInclude(x => x!.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Major)
|
||||
.Where(x =>
|
||||
x.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
|
||||
x.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
|
||||
x.Conclusion == GraduationAuditConclusion.Eligible)
|
||||
.OrderByDescending(x => x.GraduationAuditBatch!.PublishedAt)
|
||||
.ToListAsync(token);
|
||||
var audits = auditCandidates
|
||||
.GroupBy(x => x.StudentId)
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
var studentIds = audits.Select(x => x.StudentId).ToArray();
|
||||
var gradePoints = await db.GradeRecords.AsNoTracking()
|
||||
.Where(x => studentIds.Contains(x.StudentId) &&
|
||||
x.GradeSheet!.Status == GradeSheetStatus.Published &&
|
||||
x.GradePoint.HasValue)
|
||||
.Select(x => new
|
||||
{
|
||||
x.StudentId,
|
||||
GradePoint = x.GradePoint!.Value,
|
||||
x.GradeSheet!.TeachingTask!.Course!.Credits
|
||||
}).ToListAsync(token);
|
||||
|
||||
var oldResults = await db.DegreeAwardResults
|
||||
.Where(x => x.DegreeAwardBatchId == id).ToListAsync(token);
|
||||
db.DegreeAwardResults.RemoveRange(oldResults);
|
||||
await db.SaveChangesAsync(token);
|
||||
|
||||
foreach (var audit in audits)
|
||||
{
|
||||
var grades = gradePoints.Where(x => x.StudentId == audit.StudentId).ToList();
|
||||
var credits = grades.Sum(x => x.Credits);
|
||||
var average = credits == 0
|
||||
? 0
|
||||
: Math.Round(
|
||||
grades.Sum(x => x.GradePoint * x.Credits) / credits,
|
||||
2,
|
||||
MidpointRounding.AwayFromZero);
|
||||
var conclusion = DegreeAwardRules.Evaluate(
|
||||
true, audit.Student!.Status, average, batch.MinimumGradePoint);
|
||||
var reason = audit.Student.Status != StudentStatus.Graduated
|
||||
? "学籍状态尚未转为毕业"
|
||||
: average < batch.MinimumGradePoint
|
||||
? $"平均绩点 {average:0.00},低于批次要求 {batch.MinimumGradePoint:0.00}"
|
||||
: string.Empty;
|
||||
db.DegreeAwardResults.Add(new DegreeAwardResult
|
||||
{
|
||||
DegreeAwardBatchId = batch.Id,
|
||||
StudentId = audit.StudentId,
|
||||
GraduationAuditResultId = audit.Id,
|
||||
AverageGradePoint = average,
|
||||
CalculatedConclusion = conclusion,
|
||||
Conclusion = conclusion,
|
||||
ExceptionReason = reason
|
||||
});
|
||||
}
|
||||
batch.CalculatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(token);
|
||||
return Ok(new { ResultCount = audits.Count });
|
||||
}
|
||||
|
||||
[HttpPut("results/{id:guid}")]
|
||||
[Authorize(Roles = Reviewers)]
|
||||
public async Task<ActionResult> Review(
|
||||
Guid id,
|
||||
DegreeAwardDecisionRequest request,
|
||||
CancellationToken token)
|
||||
{
|
||||
var result = await db.DegreeAwardResults
|
||||
.Include(x => x.DegreeAwardBatch)
|
||||
.Include(x => x.Student)
|
||||
.ThenInclude(x => x!.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Major)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (result is null) return NotFound();
|
||||
if (result.DegreeAwardBatch!.Status != DegreeAwardBatchStatus.Draft)
|
||||
return ConflictProblem("已发布的授予结果不能修改。");
|
||||
var collegeId = RestrictedCollegeId();
|
||||
if (collegeId.HasValue &&
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId != collegeId)
|
||||
return Forbid();
|
||||
|
||||
result.Conclusion = request.Conclusion;
|
||||
result.IsOverridden = request.Conclusion != result.CalculatedConclusion;
|
||||
result.ReviewComment = request.Comment.Trim();
|
||||
result.ReviewedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(token);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("batches/{id:guid}/publish")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> Publish(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.DegreeAwardBatches.Include(x => x.Results)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (batch is null) return NotFound();
|
||||
if (batch.Status != DegreeAwardBatchStatus.Draft)
|
||||
return ConflictProblem("该批次已经发布。");
|
||||
if (batch.Results.Count == 0)
|
||||
return ConflictProblem("没有可发布的学位授予审核结果。");
|
||||
batch.Status = DegreeAwardBatchStatus.Published;
|
||||
batch.PublishedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(token);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("my-result")]
|
||||
[Authorize(Roles = SystemRoles.Student)]
|
||||
public async Task<ActionResult> GetMyResult(CancellationToken token)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
var result = await db.DegreeAwardResults.AsNoTracking()
|
||||
.Where(x => x.Student!.UserId == userId &&
|
||||
x.DegreeAwardBatch!.Status == DegreeAwardBatchStatus.Published)
|
||||
.OrderByDescending(x => x.DegreeAwardBatch!.GraduationYear)
|
||||
.ThenByDescending(x => x.DegreeAwardBatch!.PublishedAt)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
BatchName = x.DegreeAwardBatch!.Name,
|
||||
x.DegreeAwardBatch.GraduationYear,
|
||||
x.DegreeAwardBatch.DegreeName,
|
||||
x.DegreeAwardBatch.MinimumGradePoint,
|
||||
x.Student!.StudentNumber, x.Student.Name,
|
||||
MajorName = x.Student.AdministrativeClass!.Major!.Name,
|
||||
x.AverageGradePoint, x.Conclusion, x.ExceptionReason,
|
||||
x.IsOverridden, x.ReviewComment,
|
||||
x.DegreeAwardBatch.PublishedAt
|
||||
}).FirstOrDefaultAsync(token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private Guid? RestrictedCollegeId()
|
||||
{
|
||||
var scope = currentUserDataScope.Current;
|
||||
return scope.IsInRole(SystemRoles.CollegeAdmin)
|
||||
? scope.CollegeId ?? Guid.Empty
|
||||
: null;
|
||||
}
|
||||
|
||||
private ActionResult ConflictProblem(string detail) => Conflict(new ProblemDetails
|
||||
{
|
||||
Title = "无法完成学位授予操作",
|
||||
Detail = detail,
|
||||
Status = StatusCodes.Status409Conflict
|
||||
});
|
||||
}
|
||||
|
||||
public sealed record DegreeAwardBatchRequest(
|
||||
[Required, MinLength(3), MaxLength(120)] string Name,
|
||||
[Range(2000, 2200)] int GraduationYear,
|
||||
[Required, MinLength(2), MaxLength(80)] string DegreeName,
|
||||
[Range(typeof(decimal), "0", "5")] decimal MinimumGradePoint,
|
||||
[MaxLength(500)] string? Notes);
|
||||
|
||||
public sealed record DegreeAwardDecisionRequest(
|
||||
DegreeAwardConclusion Conclusion,
|
||||
[Required, MinLength(5), MaxLength(500)] string Comment);
|
||||
@@ -0,0 +1,332 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Auth;
|
||||
using Jiaowu.Api.Infrastructure.Graduation;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/graduation-audits")]
|
||||
public sealed class GraduationAuditsController(
|
||||
AppDbContext db,
|
||||
ICurrentUserDataScope currentUserDataScope) : ControllerBase
|
||||
{
|
||||
private const string Managers =
|
||||
SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin;
|
||||
private const string Reviewers =
|
||||
Managers + "," + SystemRoles.CollegeAdmin;
|
||||
|
||||
[HttpGet("batches")]
|
||||
[Authorize(Roles = Reviewers)]
|
||||
public async Task<ActionResult> GetBatches(CancellationToken token)
|
||||
{
|
||||
var collegeId = RestrictedCollegeId();
|
||||
return Ok(await db.GraduationAuditBatches.AsNoTracking()
|
||||
.OrderByDescending(x => x.GraduationYear)
|
||||
.ThenByDescending(x => x.CreatedAt)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.Name, x.GraduationYear, x.EnrollmentYear, x.Status,
|
||||
x.Notes, x.CalculatedAt, x.PublishedAt, x.CreatedAt,
|
||||
ResultCount = x.Results.Count(result =>
|
||||
!collegeId.HasValue ||
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId == collegeId),
|
||||
EligibleCount = x.Results.Count(result =>
|
||||
(!collegeId.HasValue ||
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId == collegeId) &&
|
||||
result.Conclusion == GraduationAuditConclusion.Eligible),
|
||||
IneligibleCount = x.Results.Count(result =>
|
||||
(!collegeId.HasValue ||
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId == collegeId) &&
|
||||
result.Conclusion == GraduationAuditConclusion.Ineligible),
|
||||
OverrideCount = x.Results.Count(result =>
|
||||
(!collegeId.HasValue ||
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId == collegeId) &&
|
||||
result.IsOverridden)
|
||||
}).ToListAsync(token));
|
||||
}
|
||||
|
||||
[HttpGet("batches/{id:guid}")]
|
||||
[Authorize(Roles = Reviewers)]
|
||||
public async Task<ActionResult> GetBatch(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.GraduationAuditBatches.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (batch is null) return NotFound();
|
||||
|
||||
var collegeId = RestrictedCollegeId();
|
||||
var results = db.GraduationAuditResults.AsNoTracking()
|
||||
.Where(x => x.GraduationAuditBatchId == id);
|
||||
if (collegeId.HasValue)
|
||||
results = results.Where(x =>
|
||||
x.Student!.AdministrativeClass!.Major!.CollegeId == collegeId);
|
||||
return Ok(new
|
||||
{
|
||||
batch.Id, batch.Name, batch.GraduationYear, batch.EnrollmentYear,
|
||||
batch.Status, batch.Notes, batch.CalculatedAt, batch.PublishedAt,
|
||||
Results = await results
|
||||
.OrderBy(x => x.Student!.StudentNumber)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.StudentId, x.Student!.StudentNumber, x.Student.Name,
|
||||
ClassName = x.Student.AdministrativeClass!.Name,
|
||||
MajorName = x.Student.AdministrativeClass.Major!.Name,
|
||||
CollegeName = x.Student.AdministrativeClass.Major.College!.Name,
|
||||
x.StudentStatusSnapshot,
|
||||
PlanName = x.CurriculumPlan != null ? x.CurriculumPlan.Name : null,
|
||||
x.RequiredCredits, x.EarnedCredits,
|
||||
x.RequiredCourseCount, x.PassedRequiredCourseCount,
|
||||
x.FailedCourseCount, x.MissingCourseNames,
|
||||
x.CalculatedConclusion, x.Conclusion, x.IsOverridden,
|
||||
x.ReviewComment, x.ReviewedAt
|
||||
}).ToListAsync(token)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("batches")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> CreateBatch(
|
||||
GraduationAuditBatchRequest request,
|
||||
CancellationToken token)
|
||||
{
|
||||
if (request.EnrollmentYear > request.GraduationYear)
|
||||
return ValidationProblem("入学年级不能晚于毕业年份。");
|
||||
var batch = new GraduationAuditBatch
|
||||
{
|
||||
Name = request.Name.Trim(),
|
||||
GraduationYear = request.GraduationYear,
|
||||
EnrollmentYear = request.EnrollmentYear,
|
||||
Notes = request.Notes?.Trim()
|
||||
};
|
||||
db.GraduationAuditBatches.Add(batch);
|
||||
await db.SaveChangesAsync(token);
|
||||
return Created(string.Empty, new { batch.Id });
|
||||
}
|
||||
|
||||
[HttpPost("batches/{id:guid}/calculate")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> Calculate(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.GraduationAuditBatches
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (batch is null) return NotFound();
|
||||
if (batch.Status != GraduationAuditBatchStatus.Draft)
|
||||
return ConflictProblem("已发布批次不能重新计算。");
|
||||
|
||||
var students = await db.Students.AsNoTracking()
|
||||
.Include(x => x.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Major)
|
||||
.Where(x => x.EnrollmentYear == batch.EnrollmentYear &&
|
||||
(x.Status == StudentStatus.Active ||
|
||||
x.Status == StudentStatus.Suspended))
|
||||
.OrderBy(x => x.StudentNumber)
|
||||
.ToListAsync(token);
|
||||
var plans = await db.CurriculumPlans.AsNoTracking()
|
||||
.Include(x => x.Modules)
|
||||
.ThenInclude(x => x.Courses)
|
||||
.ThenInclude(x => x.Course)
|
||||
.Where(x => x.EffectiveGrade == batch.EnrollmentYear &&
|
||||
x.Status == CurriculumPlanStatus.Published)
|
||||
.ToListAsync(token);
|
||||
var studentIds = students.Select(x => x.Id).ToArray();
|
||||
var grades = await db.GradeRecords.AsNoTracking()
|
||||
.Where(x => studentIds.Contains(x.StudentId) &&
|
||||
x.GradeSheet!.Status == GradeSheetStatus.Published)
|
||||
.Select(x => new GradeSnapshot(
|
||||
x.StudentId,
|
||||
x.GradeSheet!.TeachingTask!.CourseId,
|
||||
x.GradeSheet.TeachingTask.Course!.Name,
|
||||
x.GradeSheet.TeachingTask.Course.Credits,
|
||||
x.TotalScore,
|
||||
x.ExamStatus))
|
||||
.ToListAsync(token);
|
||||
|
||||
var oldResults = await db.GraduationAuditResults
|
||||
.Where(x => x.GraduationAuditBatchId == id).ToListAsync(token);
|
||||
db.GraduationAuditResults.RemoveRange(oldResults);
|
||||
await db.SaveChangesAsync(token);
|
||||
|
||||
foreach (var student in students)
|
||||
{
|
||||
var plan = plans.FirstOrDefault(x =>
|
||||
x.MajorId == student.AdministrativeClass!.MajorId);
|
||||
var requiredCourses = plan?.Modules
|
||||
.SelectMany(x => x.Courses)
|
||||
.Where(x => x.Type == CurriculumCourseType.Required)
|
||||
.ToList() ?? [];
|
||||
var studentGrades = grades.Where(x => x.StudentId == student.Id).ToList();
|
||||
var passedCourseIds = studentGrades
|
||||
.Where(IsPassed)
|
||||
.Select(x => x.CourseId)
|
||||
.Distinct()
|
||||
.ToHashSet();
|
||||
var earnedCredits = studentGrades
|
||||
.Where(IsPassed)
|
||||
.GroupBy(x => x.CourseId)
|
||||
.Sum(x => x.Max(item => item.Credits));
|
||||
var missingCourses = requiredCourses
|
||||
.Where(x => !passedCourseIds.Contains(x.CourseId))
|
||||
.Select(x => x.Course!.Name)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var missingCourseNames = plan is null
|
||||
? "未匹配已发布的培养方案"
|
||||
: string.Join("、", missingCourses);
|
||||
if (missingCourseNames.Length > 2000)
|
||||
missingCourseNames = missingCourseNames[..2000];
|
||||
var failedCourseCount = studentGrades
|
||||
.GroupBy(x => x.CourseId)
|
||||
.Count(x => !x.Any(IsPassed));
|
||||
var conclusion = GraduationAuditRules.Evaluate(
|
||||
plan is not null,
|
||||
student.Status,
|
||||
plan?.TotalCredits ?? 0,
|
||||
earnedCredits,
|
||||
requiredCourses.Count,
|
||||
requiredCourses.Count - missingCourses.Length,
|
||||
failedCourseCount);
|
||||
db.GraduationAuditResults.Add(new GraduationAuditResult
|
||||
{
|
||||
GraduationAuditBatchId = batch.Id,
|
||||
StudentId = student.Id,
|
||||
CurriculumPlanId = plan?.Id,
|
||||
StudentStatusSnapshot = student.Status,
|
||||
RequiredCredits = plan?.TotalCredits ?? 0,
|
||||
EarnedCredits = earnedCredits,
|
||||
RequiredCourseCount = requiredCourses.Count,
|
||||
PassedRequiredCourseCount = requiredCourses.Count - missingCourses.Length,
|
||||
FailedCourseCount = failedCourseCount,
|
||||
MissingCourseNames = missingCourseNames,
|
||||
CalculatedConclusion = conclusion,
|
||||
Conclusion = conclusion
|
||||
});
|
||||
}
|
||||
batch.CalculatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(token);
|
||||
return Ok(new { ResultCount = students.Count });
|
||||
}
|
||||
|
||||
[HttpPut("results/{id:guid}")]
|
||||
[Authorize(Roles = Reviewers)]
|
||||
public async Task<ActionResult> ReviewResult(
|
||||
Guid id,
|
||||
GraduationAuditDecisionRequest request,
|
||||
CancellationToken token)
|
||||
{
|
||||
var result = await db.GraduationAuditResults
|
||||
.Include(x => x.GraduationAuditBatch)
|
||||
.Include(x => x.Student)
|
||||
.ThenInclude(x => x!.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Major)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (result is null) return NotFound();
|
||||
if (result.GraduationAuditBatch!.Status != GraduationAuditBatchStatus.Draft)
|
||||
return ConflictProblem("已发布的审核结果不能修改。");
|
||||
var collegeId = RestrictedCollegeId();
|
||||
if (collegeId.HasValue &&
|
||||
result.Student!.AdministrativeClass!.Major!.CollegeId != collegeId)
|
||||
return Forbid();
|
||||
|
||||
result.Conclusion = request.Conclusion;
|
||||
result.IsOverridden = request.Conclusion != result.CalculatedConclusion;
|
||||
result.ReviewComment = request.Comment.Trim();
|
||||
result.ReviewedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(token);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("batches/{id:guid}/publish")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> Publish(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.GraduationAuditBatches
|
||||
.Include(x => x.Results)
|
||||
.ThenInclude(x => x.Student)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (batch is null) return NotFound();
|
||||
if (batch.Status != GraduationAuditBatchStatus.Draft)
|
||||
return ConflictProblem("该批次已经发布。");
|
||||
if (batch.Results.Count == 0)
|
||||
return ConflictProblem("请先计算毕业资格,再发布结果。");
|
||||
|
||||
batch.Status = GraduationAuditBatchStatus.Published;
|
||||
batch.PublishedAt = DateTime.UtcNow;
|
||||
foreach (var result in batch.Results.Where(x =>
|
||||
x.Conclusion == GraduationAuditConclusion.Eligible &&
|
||||
x.Student!.Status == StudentStatus.Active))
|
||||
result.Student!.Status = StudentStatus.Graduated;
|
||||
await db.SaveChangesAsync(token);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("my-result")]
|
||||
[Authorize(Roles = SystemRoles.Student)]
|
||||
public async Task<ActionResult> GetMyResult(CancellationToken token)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
var result = await db.GraduationAuditResults.AsNoTracking()
|
||||
.Where(x => x.Student!.UserId == userId &&
|
||||
x.GraduationAuditBatch!.Status ==
|
||||
GraduationAuditBatchStatus.Published)
|
||||
.OrderByDescending(x => x.GraduationAuditBatch!.GraduationYear)
|
||||
.ThenByDescending(x => x.GraduationAuditBatch!.PublishedAt)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
BatchName = x.GraduationAuditBatch!.Name,
|
||||
x.GraduationAuditBatch.GraduationYear,
|
||||
x.Student!.StudentNumber, x.Student.Name,
|
||||
MajorName = x.Student.AdministrativeClass!.Major!.Name,
|
||||
PlanName = x.CurriculumPlan != null ? x.CurriculumPlan.Name : null,
|
||||
x.RequiredCredits, x.EarnedCredits,
|
||||
x.RequiredCourseCount, x.PassedRequiredCourseCount,
|
||||
x.FailedCourseCount, x.MissingCourseNames,
|
||||
x.Conclusion, x.IsOverridden, x.ReviewComment,
|
||||
x.GraduationAuditBatch.PublishedAt
|
||||
}).FirstOrDefaultAsync(token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private Guid? RestrictedCollegeId()
|
||||
{
|
||||
var scope = currentUserDataScope.Current;
|
||||
return scope.IsInRole(SystemRoles.CollegeAdmin)
|
||||
? scope.CollegeId ?? Guid.Empty
|
||||
: null;
|
||||
}
|
||||
|
||||
private static bool IsPassed(GradeSnapshot grade) =>
|
||||
grade.ExamStatus == GradeExamStatus.Exempt ||
|
||||
grade.TotalScore is decimal score && score >= 60;
|
||||
|
||||
private sealed record GradeSnapshot(
|
||||
Guid StudentId,
|
||||
Guid CourseId,
|
||||
string CourseName,
|
||||
decimal Credits,
|
||||
decimal? TotalScore,
|
||||
GradeExamStatus ExamStatus);
|
||||
|
||||
private ActionResult ConflictProblem(string detail) => Conflict(new ProblemDetails
|
||||
{
|
||||
Title = "无法完成毕业审核操作",
|
||||
Detail = detail,
|
||||
Status = StatusCodes.Status409Conflict
|
||||
});
|
||||
}
|
||||
|
||||
public sealed record GraduationAuditBatchRequest(
|
||||
[Required, MinLength(3), MaxLength(120)] string Name,
|
||||
[Range(2000, 2200)] int GraduationYear,
|
||||
[Range(2000, 2200)] int EnrollmentYear,
|
||||
[MaxLength(500)] string? Notes);
|
||||
|
||||
public sealed record GraduationAuditDecisionRequest(
|
||||
GraduationAuditConclusion Conclusion,
|
||||
[Required, MinLength(5), MaxLength(500)] string Comment);
|
||||
@@ -0,0 +1,318 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Auth;
|
||||
using Jiaowu.Api.Infrastructure.Graduation;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/graduation-clearance")]
|
||||
public sealed class GraduationClearanceController(
|
||||
AppDbContext db,
|
||||
ICurrentUserDataScope currentUserDataScope) : ControllerBase
|
||||
{
|
||||
private const string Managers =
|
||||
SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin;
|
||||
private const string Workers =
|
||||
Managers + "," + SystemRoles.CollegeAdmin + "," + SystemRoles.Counselor;
|
||||
|
||||
[HttpGet("batches")]
|
||||
[Authorize(Roles = Workers)]
|
||||
public async Task<ActionResult> GetBatches(CancellationToken token)
|
||||
{
|
||||
var batches = await db.GraduationClearanceBatches.AsNoTracking()
|
||||
.OrderByDescending(x => x.GraduationYear)
|
||||
.ThenByDescending(x => x.CreatedAt)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.Name, x.GraduationYear, x.Status, x.Notes,
|
||||
x.ClosedAt, x.CreatedAt, ItemCount = x.Items.Count
|
||||
}).ToListAsync(token);
|
||||
var records = await ScopedRecords().AsNoTracking()
|
||||
.Select(x => new
|
||||
{
|
||||
BatchId = x.GraduationClearanceItem!.GraduationClearanceBatchId,
|
||||
x.StudentId, x.Status
|
||||
}).ToListAsync(token);
|
||||
return Ok(batches.Select(batch =>
|
||||
{
|
||||
var scoped = records.Where(x => x.BatchId == batch.Id).ToList();
|
||||
return new
|
||||
{
|
||||
batch.Id, batch.Name, batch.GraduationYear, batch.Status,
|
||||
batch.Notes, batch.ClosedAt, batch.CreatedAt, batch.ItemCount,
|
||||
StudentCount = scoped.Select(x => x.StudentId).Distinct().Count(),
|
||||
RecordCount = scoped.Count,
|
||||
CompletedCount = scoped.Count(x =>
|
||||
x.Status != GraduationClearanceRecordStatus.Pending)
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet("batches/{id:guid}")]
|
||||
[Authorize(Roles = Workers)]
|
||||
public async Task<ActionResult> GetBatch(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.GraduationClearanceBatches.AsNoTracking()
|
||||
.Where(x => x.Id == id)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.Name, x.GraduationYear, x.Status,
|
||||
x.Notes, x.ClosedAt, x.CreatedAt,
|
||||
Items = x.Items.OrderBy(item => item.SortOrder).Select(item => new
|
||||
{
|
||||
item.Id, item.Code, item.Name, item.ResponsibleUnit,
|
||||
item.ResponsibleRole, item.IsRequired, item.SortOrder
|
||||
})
|
||||
}).FirstOrDefaultAsync(token);
|
||||
if (batch is null) return NotFound();
|
||||
var records = await ScopedRecords().AsNoTracking()
|
||||
.Where(x => x.GraduationClearanceItem!.GraduationClearanceBatchId == id)
|
||||
.OrderBy(x => x.Student!.StudentNumber)
|
||||
.ThenBy(x => x.GraduationClearanceItem!.SortOrder)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.StudentId, x.Student!.StudentNumber, x.Student.Name,
|
||||
ClassName = x.Student.AdministrativeClass!.Name,
|
||||
MajorName = x.Student.AdministrativeClass.Major!.Name,
|
||||
CollegeName = x.Student.AdministrativeClass.Major.College!.Name,
|
||||
ItemId = x.GraduationClearanceItemId,
|
||||
ItemName = x.GraduationClearanceItem!.Name,
|
||||
x.GraduationClearanceItem.ResponsibleUnit,
|
||||
x.GraduationClearanceItem.ResponsibleRole,
|
||||
x.GraduationClearanceItem.IsRequired,
|
||||
x.Status, x.Notes, x.CompletedAt
|
||||
}).ToListAsync(token);
|
||||
return Ok(new
|
||||
{
|
||||
batch.Id, batch.Name, batch.GraduationYear, batch.Status,
|
||||
batch.Notes, batch.ClosedAt, batch.CreatedAt, batch.Items,
|
||||
Records = records
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("batches")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> Create(
|
||||
GraduationClearanceBatchRequest request,
|
||||
CancellationToken token)
|
||||
{
|
||||
var allowedRoles = new[]
|
||||
{
|
||||
SystemRoles.AcademicAdmin,
|
||||
SystemRoles.CollegeAdmin,
|
||||
SystemRoles.Counselor
|
||||
};
|
||||
if (request.Items.Count == 0)
|
||||
return ValidationProblem("至少配置一个离校事项。");
|
||||
if (request.Items.Select(x => x.Code.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase).Count() != request.Items.Count)
|
||||
return ValidationProblem("离校事项编码不能重复。");
|
||||
if (request.Items.Any(x => !allowedRoles.Contains(
|
||||
x.ResponsibleRole, StringComparer.OrdinalIgnoreCase)))
|
||||
return ValidationProblem("离校事项责任角色无效。");
|
||||
|
||||
var batch = new GraduationClearanceBatch
|
||||
{
|
||||
Name = request.Name.Trim(),
|
||||
GraduationYear = request.GraduationYear,
|
||||
Notes = request.Notes?.Trim(),
|
||||
Items = request.Items.Select((item, index) =>
|
||||
new GraduationClearanceItem
|
||||
{
|
||||
Code = item.Code.Trim(),
|
||||
Name = item.Name.Trim(),
|
||||
ResponsibleUnit = item.ResponsibleUnit.Trim(),
|
||||
ResponsibleRole = item.ResponsibleRole,
|
||||
IsRequired = item.IsRequired,
|
||||
SortOrder = index + 1
|
||||
}).ToList()
|
||||
};
|
||||
db.GraduationClearanceBatches.Add(batch);
|
||||
await db.SaveChangesAsync(token);
|
||||
return Created(string.Empty, new { batch.Id });
|
||||
}
|
||||
|
||||
[HttpPost("batches/{id:guid}/generate")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> Generate(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.GraduationClearanceBatches
|
||||
.Include(x => x.Items)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (batch is null) return NotFound();
|
||||
if (batch.Status != GraduationClearanceBatchStatus.Open)
|
||||
return ConflictProblem("已关闭批次不能重新生成办理记录。");
|
||||
|
||||
var eligibleAudits = await db.GraduationAuditResults.AsNoTracking()
|
||||
.Where(x =>
|
||||
x.GraduationAuditBatch!.GraduationYear == batch.GraduationYear &&
|
||||
x.GraduationAuditBatch.Status == GraduationAuditBatchStatus.Published &&
|
||||
x.Conclusion == GraduationAuditConclusion.Eligible &&
|
||||
x.Student!.Status == StudentStatus.Graduated)
|
||||
.OrderByDescending(x => x.GraduationAuditBatch!.PublishedAt)
|
||||
.Select(x => new { x.StudentId })
|
||||
.ToListAsync(token);
|
||||
var studentIds = eligibleAudits.Select(x => x.StudentId).Distinct().ToArray();
|
||||
var existing = await db.GraduationClearanceRecords.AsNoTracking()
|
||||
.Where(x => x.GraduationClearanceItem!.GraduationClearanceBatchId == id)
|
||||
.Select(x => new { x.GraduationClearanceItemId, x.StudentId })
|
||||
.ToListAsync(token);
|
||||
var existingKeys = existing
|
||||
.Select(x => (x.GraduationClearanceItemId, x.StudentId))
|
||||
.ToHashSet();
|
||||
var created = 0;
|
||||
foreach (var item in batch.Items)
|
||||
foreach (var studentId in studentIds)
|
||||
{
|
||||
if (existingKeys.Contains((item.Id, studentId))) continue;
|
||||
db.GraduationClearanceRecords.Add(new GraduationClearanceRecord
|
||||
{
|
||||
GraduationClearanceItemId = item.Id,
|
||||
StudentId = studentId
|
||||
});
|
||||
created++;
|
||||
}
|
||||
await db.SaveChangesAsync(token);
|
||||
return Ok(new { StudentCount = studentIds.Length, CreatedRecordCount = created });
|
||||
}
|
||||
|
||||
[HttpPut("records/{id:guid}")]
|
||||
[Authorize(Roles = Workers)]
|
||||
public async Task<ActionResult> UpdateRecord(
|
||||
Guid id,
|
||||
GraduationClearanceRecordRequest request,
|
||||
CancellationToken token)
|
||||
{
|
||||
var record = await db.GraduationClearanceRecords
|
||||
.Include(x => x.GraduationClearanceItem)
|
||||
.ThenInclude(x => x!.GraduationClearanceBatch)
|
||||
.Include(x => x.Student)
|
||||
.ThenInclude(x => x!.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Major)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (record is null) return NotFound();
|
||||
if (record.GraduationClearanceItem!.GraduationClearanceBatch!.Status !=
|
||||
GraduationClearanceBatchStatus.Open)
|
||||
return ConflictProblem("批次已关闭,不能修改办理记录。");
|
||||
if (!CanManage(record)) return Forbid();
|
||||
|
||||
record.Status = request.Status;
|
||||
record.Notes = request.Notes?.Trim();
|
||||
record.CompletedAt = request.Status == GraduationClearanceRecordStatus.Pending
|
||||
? null
|
||||
: DateTime.UtcNow;
|
||||
record.CompletedByUserId =
|
||||
request.Status == GraduationClearanceRecordStatus.Pending
|
||||
? null
|
||||
: currentUserDataScope.Current.UserId;
|
||||
await db.SaveChangesAsync(token);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("batches/{id:guid}/close")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> Close(Guid id, CancellationToken token)
|
||||
{
|
||||
var batch = await db.GraduationClearanceBatches
|
||||
.Include(x => x.Items)
|
||||
.ThenInclude(x => x.Records)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (batch is null) return NotFound();
|
||||
if (batch.Status != GraduationClearanceBatchStatus.Open)
|
||||
return ConflictProblem("该批次已经关闭。");
|
||||
var records = batch.Items.SelectMany(item => item.Records.Select(record =>
|
||||
(item.IsRequired, record.Status)));
|
||||
if (!GraduationClearanceRules.CanClose(records))
|
||||
return ConflictProblem("仍有必办离校事项尚未完成。");
|
||||
batch.Status = GraduationClearanceBatchStatus.Closed;
|
||||
batch.ClosedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(token);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("my-clearance")]
|
||||
[Authorize(Roles = SystemRoles.Student)]
|
||||
public async Task<ActionResult> GetMyClearance(CancellationToken token)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
var batch = await db.GraduationClearanceBatches.AsNoTracking()
|
||||
.Where(x => x.Items.Any(item =>
|
||||
item.Records.Any(record => record.Student!.UserId == userId)))
|
||||
.OrderByDescending(x => x.GraduationYear)
|
||||
.ThenByDescending(x => x.CreatedAt)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id, x.Name, x.GraduationYear, x.Status, x.Notes, x.ClosedAt,
|
||||
Items = x.Items.OrderBy(item => item.SortOrder)
|
||||
.SelectMany(item => item.Records
|
||||
.Where(record => record.Student!.UserId == userId)
|
||||
.Select(record => new
|
||||
{
|
||||
record.Id, ItemName = item.Name, item.ResponsibleUnit,
|
||||
item.IsRequired, record.Status,
|
||||
record.Notes, record.CompletedAt
|
||||
}))
|
||||
}).FirstOrDefaultAsync(token);
|
||||
return Ok(batch);
|
||||
}
|
||||
|
||||
private IQueryable<GraduationClearanceRecord> ScopedRecords()
|
||||
{
|
||||
var scope = currentUserDataScope.Current;
|
||||
var source = db.GraduationClearanceRecords.AsQueryable();
|
||||
if (scope.IsInRole(SystemRoles.Counselor))
|
||||
return source.Where(x =>
|
||||
x.Student!.AdministrativeClass!.CounselorUserId == scope.UserId);
|
||||
if (scope.IsInRole(SystemRoles.CollegeAdmin))
|
||||
return source.Where(x =>
|
||||
x.Student!.AdministrativeClass!.Major!.CollegeId == scope.CollegeId);
|
||||
return source;
|
||||
}
|
||||
|
||||
private bool CanManage(GraduationClearanceRecord record)
|
||||
{
|
||||
var scope = currentUserDataScope.Current;
|
||||
if (scope.IsInRole(SystemRoles.SuperAdmin)) return true;
|
||||
var role = record.GraduationClearanceItem!.ResponsibleRole;
|
||||
if (role == SystemRoles.AcademicAdmin &&
|
||||
scope.IsInRole(SystemRoles.AcademicAdmin)) return true;
|
||||
if (role == SystemRoles.CollegeAdmin &&
|
||||
scope.IsInRole(SystemRoles.CollegeAdmin))
|
||||
return record.Student!.AdministrativeClass!.Major!.CollegeId ==
|
||||
scope.CollegeId;
|
||||
return role == SystemRoles.Counselor &&
|
||||
scope.IsInRole(SystemRoles.Counselor) &&
|
||||
record.Student!.AdministrativeClass!.CounselorUserId == scope.UserId;
|
||||
}
|
||||
|
||||
private ActionResult ConflictProblem(string detail) => Conflict(new ProblemDetails
|
||||
{
|
||||
Title = "无法完成毕业离校操作",
|
||||
Detail = detail,
|
||||
Status = StatusCodes.Status409Conflict
|
||||
});
|
||||
}
|
||||
|
||||
public sealed record GraduationClearanceBatchRequest(
|
||||
[Required, MinLength(3), MaxLength(120)] string Name,
|
||||
[Range(2000, 2200)] int GraduationYear,
|
||||
[MaxLength(500)] string? Notes,
|
||||
IReadOnlyList<GraduationClearanceItemRequest> Items);
|
||||
|
||||
public sealed record GraduationClearanceItemRequest(
|
||||
[Required, MinLength(2), MaxLength(30)] string Code,
|
||||
[Required, MinLength(2), MaxLength(100)] string Name,
|
||||
[Required, MinLength(2), MaxLength(100)] string ResponsibleUnit,
|
||||
[Required, MaxLength(30)] string ResponsibleRole,
|
||||
bool IsRequired);
|
||||
|
||||
public sealed record GraduationClearanceRecordRequest(
|
||||
GraduationClearanceRecordStatus Status,
|
||||
[MaxLength(500)] string? Notes);
|
||||
@@ -30,6 +30,37 @@ public sealed class StudentStatusChangesController(
|
||||
}).ToListAsync(token));
|
||||
}
|
||||
|
||||
[HttpGet("options")]
|
||||
[Authorize(Roles = SystemRoles.Student)]
|
||||
public async Task<ActionResult> GetOptions(CancellationToken token)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
var student = await db.Students.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId, token);
|
||||
if (student is null) return ConflictProblem("当前账号未关联学生档案。");
|
||||
|
||||
var types = student.Status switch
|
||||
{
|
||||
StudentStatus.Active => new[]
|
||||
{
|
||||
StudentStatusChangeType.Suspension,
|
||||
StudentStatusChangeType.Withdrawal
|
||||
},
|
||||
StudentStatus.Suspended => new[]
|
||||
{
|
||||
StudentStatusChangeType.Resumption,
|
||||
StudentStatusChangeType.Withdrawal
|
||||
},
|
||||
_ => []
|
||||
};
|
||||
var hasPending = await db.StudentStatusChanges.AnyAsync(x =>
|
||||
x.StudentId == student.Id &&
|
||||
(x.State == StudentStatusChangeState.Submitted ||
|
||||
x.State == StudentStatusChangeState.CounselorApproved ||
|
||||
x.State == StudentStatusChangeState.CollegeApproved), token);
|
||||
return Ok(new { student.Status, Types = types, HasPending = hasPending });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Roles = SystemRoles.Student)]
|
||||
public async Task<ActionResult> Create(
|
||||
@@ -76,14 +107,19 @@ public sealed class StudentStatusChangesController(
|
||||
.FirstOrDefaultAsync(x => x.Id == id, token);
|
||||
if (change is null) return NotFound();
|
||||
var scope = currentUserDataScope.Current;
|
||||
if (!CanReviewCurrentStage(scope, change.State))
|
||||
return ConflictProblem("当前角色或审核阶段不允许执行该操作。");
|
||||
|
||||
if (!request.Approved)
|
||||
{
|
||||
if (change.State is StudentStatusChangeState.Approved or
|
||||
StudentStatusChangeState.Rejected or StudentStatusChangeState.Cancelled)
|
||||
return ConflictProblem("该申请已经结束。");
|
||||
if (string.IsNullOrWhiteSpace(request.Comment))
|
||||
return BadRequest(new ProblemDetails
|
||||
{
|
||||
Title = "审核意见不完整",
|
||||
Detail = "驳回申请时必须填写审核意见。",
|
||||
Status = StatusCodes.Status400BadRequest
|
||||
});
|
||||
change.State = StudentStatusChangeState.Rejected;
|
||||
change.ReviewComment = request.Comment?.Trim();
|
||||
change.ReviewedAt = DateTime.UtcNow;
|
||||
}
|
||||
else if (scope.IsInRole(SystemRoles.Counselor) &&
|
||||
change.State == StudentStatusChangeState.Submitted)
|
||||
@@ -100,12 +136,30 @@ public sealed class StudentStatusChangesController(
|
||||
change.ApprovedAt = DateTime.UtcNow;
|
||||
}
|
||||
else return ConflictProblem("当前角色或审核阶段不允许执行该操作。");
|
||||
|
||||
change.ReviewComment = request.Comment?.Trim();
|
||||
change.ReviewedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(token);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/cancel")]
|
||||
[Authorize(Roles = SystemRoles.Student)]
|
||||
public async Task<ActionResult> Cancel(Guid id, CancellationToken token)
|
||||
{
|
||||
var userId = currentUserDataScope.Current.UserId;
|
||||
var change = await db.StudentStatusChanges.FirstOrDefaultAsync(
|
||||
x => x.Id == id && x.Student!.UserId == userId, token);
|
||||
if (change is null) return NotFound();
|
||||
if (change.State != StudentStatusChangeState.Submitted)
|
||||
return ConflictProblem("只有尚未进入审核的申请可以撤回。");
|
||||
|
||||
change.State = StudentStatusChangeState.Cancelled;
|
||||
change.ReviewedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(token);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private IQueryable<StudentStatusChange> ScopedChanges()
|
||||
{
|
||||
var scope = currentUserDataScope.Current;
|
||||
@@ -123,6 +177,21 @@ public sealed class StudentStatusChangesController(
|
||||
return source.Where(_ => false);
|
||||
}
|
||||
|
||||
private static bool CanReviewCurrentStage(
|
||||
CurrentUserScope scope,
|
||||
StudentStatusChangeState state) =>
|
||||
state switch
|
||||
{
|
||||
StudentStatusChangeState.Submitted =>
|
||||
scope.IsInRole(SystemRoles.Counselor),
|
||||
StudentStatusChangeState.CounselorApproved =>
|
||||
scope.IsInRole(SystemRoles.CollegeAdmin),
|
||||
StudentStatusChangeState.CollegeApproved =>
|
||||
scope.IsInRole(SystemRoles.AcademicAdmin) ||
|
||||
scope.IsInRole(SystemRoles.SuperAdmin),
|
||||
_ => false
|
||||
};
|
||||
|
||||
private ActionResult ConflictProblem(string detail) => Conflict(new ProblemDetails
|
||||
{
|
||||
Title = "无法完成学籍异动操作", Detail = detail,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using Jiaowu.Api.Domain.Common;
|
||||
|
||||
namespace Jiaowu.Api.Domain.Academic;
|
||||
|
||||
public sealed class DegreeAwardBatch : EntityBase
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public int GraduationYear { get; set; }
|
||||
public required string DegreeName { get; set; }
|
||||
public decimal MinimumGradePoint { get; set; } = 2.0m;
|
||||
public DegreeAwardBatchStatus Status { get; set; } = DegreeAwardBatchStatus.Draft;
|
||||
public string? Notes { get; set; }
|
||||
public DateTime? CalculatedAt { get; set; }
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public ICollection<DegreeAwardResult> Results { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class DegreeAwardResult : EntityBase
|
||||
{
|
||||
public Guid DegreeAwardBatchId { get; set; }
|
||||
public DegreeAwardBatch? DegreeAwardBatch { get; set; }
|
||||
public Guid StudentId { get; set; }
|
||||
public Student? Student { get; set; }
|
||||
public Guid GraduationAuditResultId { get; set; }
|
||||
public GraduationAuditResult? GraduationAuditResult { get; set; }
|
||||
public decimal AverageGradePoint { get; set; }
|
||||
public DegreeAwardConclusion CalculatedConclusion { get; set; }
|
||||
public DegreeAwardConclusion Conclusion { get; set; }
|
||||
public required string ExceptionReason { get; set; }
|
||||
public bool IsOverridden { get; set; }
|
||||
public string? ReviewComment { get; set; }
|
||||
public DateTime? ReviewedAt { get; set; }
|
||||
}
|
||||
|
||||
public enum DegreeAwardBatchStatus
|
||||
{
|
||||
Draft = 1,
|
||||
Published = 2
|
||||
}
|
||||
|
||||
public enum DegreeAwardConclusion
|
||||
{
|
||||
NotGranted = 1,
|
||||
Granted = 2
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Jiaowu.Api.Domain.Common;
|
||||
|
||||
namespace Jiaowu.Api.Domain.Academic;
|
||||
|
||||
public sealed class GraduationAuditBatch : EntityBase
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public int GraduationYear { get; set; }
|
||||
public int EnrollmentYear { get; set; }
|
||||
public GraduationAuditBatchStatus Status { get; set; } =
|
||||
GraduationAuditBatchStatus.Draft;
|
||||
public string? Notes { get; set; }
|
||||
public DateTime? CalculatedAt { get; set; }
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public ICollection<GraduationAuditResult> Results { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class GraduationAuditResult : EntityBase
|
||||
{
|
||||
public Guid GraduationAuditBatchId { get; set; }
|
||||
public GraduationAuditBatch? GraduationAuditBatch { get; set; }
|
||||
public Guid StudentId { get; set; }
|
||||
public Student? Student { get; set; }
|
||||
public Guid? CurriculumPlanId { get; set; }
|
||||
public CurriculumPlan? CurriculumPlan { get; set; }
|
||||
public StudentStatus StudentStatusSnapshot { get; set; }
|
||||
public decimal RequiredCredits { get; set; }
|
||||
public decimal EarnedCredits { get; set; }
|
||||
public int RequiredCourseCount { get; set; }
|
||||
public int PassedRequiredCourseCount { get; set; }
|
||||
public int FailedCourseCount { get; set; }
|
||||
public required string MissingCourseNames { get; set; }
|
||||
public GraduationAuditConclusion CalculatedConclusion { get; set; }
|
||||
public GraduationAuditConclusion Conclusion { get; set; }
|
||||
public bool IsOverridden { get; set; }
|
||||
public string? ReviewComment { get; set; }
|
||||
public DateTime? ReviewedAt { get; set; }
|
||||
}
|
||||
|
||||
public enum GraduationAuditBatchStatus
|
||||
{
|
||||
Draft = 1,
|
||||
Published = 2
|
||||
}
|
||||
|
||||
public enum GraduationAuditConclusion
|
||||
{
|
||||
Ineligible = 1,
|
||||
Eligible = 2
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Jiaowu.Api.Domain.Common;
|
||||
|
||||
namespace Jiaowu.Api.Domain.Academic;
|
||||
|
||||
public sealed class GraduationClearanceBatch : EntityBase
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public int GraduationYear { get; set; }
|
||||
public GraduationClearanceBatchStatus Status { get; set; } =
|
||||
GraduationClearanceBatchStatus.Open;
|
||||
public string? Notes { get; set; }
|
||||
public DateTime? ClosedAt { get; set; }
|
||||
public ICollection<GraduationClearanceItem> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class GraduationClearanceItem : EntityBase
|
||||
{
|
||||
public Guid GraduationClearanceBatchId { get; set; }
|
||||
public GraduationClearanceBatch? GraduationClearanceBatch { get; set; }
|
||||
public required string Code { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string ResponsibleUnit { get; set; }
|
||||
public required string ResponsibleRole { get; set; }
|
||||
public bool IsRequired { get; set; } = true;
|
||||
public int SortOrder { get; set; }
|
||||
public ICollection<GraduationClearanceRecord> Records { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class GraduationClearanceRecord : EntityBase
|
||||
{
|
||||
public Guid GraduationClearanceItemId { get; set; }
|
||||
public GraduationClearanceItem? GraduationClearanceItem { get; set; }
|
||||
public Guid StudentId { get; set; }
|
||||
public Student? Student { get; set; }
|
||||
public GraduationClearanceRecordStatus Status { get; set; } =
|
||||
GraduationClearanceRecordStatus.Pending;
|
||||
public string? Notes { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public Guid? CompletedByUserId { get; set; }
|
||||
}
|
||||
|
||||
public enum GraduationClearanceBatchStatus
|
||||
{
|
||||
Open = 1,
|
||||
Closed = 2
|
||||
}
|
||||
|
||||
public enum GraduationClearanceRecordStatus
|
||||
{
|
||||
Pending = 1,
|
||||
Completed = 2,
|
||||
Waived = 3
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Graduation;
|
||||
|
||||
public static class DegreeAwardRules
|
||||
{
|
||||
public static DegreeAwardConclusion Evaluate(
|
||||
bool graduationEligible,
|
||||
StudentStatus studentStatus,
|
||||
decimal averageGradePoint,
|
||||
decimal minimumGradePoint) =>
|
||||
graduationEligible &&
|
||||
studentStatus == StudentStatus.Graduated &&
|
||||
averageGradePoint >= minimumGradePoint
|
||||
? DegreeAwardConclusion.Granted
|
||||
: DegreeAwardConclusion.NotGranted;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Graduation;
|
||||
|
||||
public static class GraduationAuditRules
|
||||
{
|
||||
public static GraduationAuditConclusion Evaluate(
|
||||
bool hasPublishedPlan,
|
||||
StudentStatus studentStatus,
|
||||
decimal requiredCredits,
|
||||
decimal earnedCredits,
|
||||
int requiredCourseCount,
|
||||
int passedRequiredCourseCount,
|
||||
int failedCourseCount)
|
||||
{
|
||||
if (!hasPublishedPlan || studentStatus != StudentStatus.Active)
|
||||
return GraduationAuditConclusion.Ineligible;
|
||||
if (earnedCredits < requiredCredits ||
|
||||
passedRequiredCourseCount < requiredCourseCount ||
|
||||
failedCourseCount > 0)
|
||||
return GraduationAuditConclusion.Ineligible;
|
||||
return GraduationAuditConclusion.Eligible;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Graduation;
|
||||
|
||||
public static class GraduationClearanceRules
|
||||
{
|
||||
public static bool CanClose(
|
||||
IEnumerable<(bool IsRequired, GraduationClearanceRecordStatus Status)> records)
|
||||
{
|
||||
var snapshot = records.ToArray();
|
||||
return snapshot.Length > 0 && snapshot
|
||||
.Where(x => x.IsRequired)
|
||||
.All(x => x.Status is GraduationClearanceRecordStatus.Completed or
|
||||
GraduationClearanceRecordStatus.Waived);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,18 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
Set<ExamSessionInvigilator>();
|
||||
public DbSet<StudentStatusChange> StudentStatusChanges =>
|
||||
Set<StudentStatusChange>();
|
||||
public DbSet<GraduationAuditBatch> GraduationAuditBatches =>
|
||||
Set<GraduationAuditBatch>();
|
||||
public DbSet<GraduationAuditResult> GraduationAuditResults =>
|
||||
Set<GraduationAuditResult>();
|
||||
public DbSet<DegreeAwardBatch> DegreeAwardBatches => Set<DegreeAwardBatch>();
|
||||
public DbSet<DegreeAwardResult> DegreeAwardResults => Set<DegreeAwardResult>();
|
||||
public DbSet<GraduationClearanceBatch> GraduationClearanceBatches =>
|
||||
Set<GraduationClearanceBatch>();
|
||||
public DbSet<GraduationClearanceItem> GraduationClearanceItems =>
|
||||
Set<GraduationClearanceItem>();
|
||||
public DbSet<GraduationClearanceRecord> GraduationClearanceRecords =>
|
||||
Set<GraduationClearanceRecord>();
|
||||
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
@@ -396,6 +408,82 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
entity.HasOne(x => x.Student).WithMany()
|
||||
.HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
builder.Entity<GraduationAuditBatch>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Name).HasMaxLength(120);
|
||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||
entity.HasIndex(x => new { x.GraduationYear, x.EnrollmentYear });
|
||||
entity.HasIndex(x => x.Status);
|
||||
});
|
||||
builder.Entity<GraduationAuditResult>(entity =>
|
||||
{
|
||||
entity.Property(x => x.RequiredCredits).HasPrecision(6, 2);
|
||||
entity.Property(x => x.EarnedCredits).HasPrecision(6, 2);
|
||||
entity.Property(x => x.MissingCourseNames).HasMaxLength(2000);
|
||||
entity.Property(x => x.ReviewComment).HasMaxLength(500);
|
||||
entity.HasIndex(x => new { x.GraduationAuditBatchId, x.StudentId })
|
||||
.IsUnique();
|
||||
entity.HasIndex(x => new { x.Conclusion, x.IsOverridden });
|
||||
entity.HasOne(x => x.GraduationAuditBatch).WithMany(x => x.Results)
|
||||
.HasForeignKey(x => x.GraduationAuditBatchId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(x => x.Student).WithMany()
|
||||
.HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict);
|
||||
entity.HasOne(x => x.CurriculumPlan).WithMany()
|
||||
.HasForeignKey(x => x.CurriculumPlanId).OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
builder.Entity<DegreeAwardBatch>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Name).HasMaxLength(120);
|
||||
entity.Property(x => x.DegreeName).HasMaxLength(80);
|
||||
entity.Property(x => x.MinimumGradePoint).HasPrecision(3, 2);
|
||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||
entity.HasIndex(x => new { x.GraduationYear, x.Status });
|
||||
});
|
||||
builder.Entity<DegreeAwardResult>(entity =>
|
||||
{
|
||||
entity.Property(x => x.AverageGradePoint).HasPrecision(4, 2);
|
||||
entity.Property(x => x.ExceptionReason).HasMaxLength(500);
|
||||
entity.Property(x => x.ReviewComment).HasMaxLength(500);
|
||||
entity.HasIndex(x => new { x.DegreeAwardBatchId, x.StudentId }).IsUnique();
|
||||
entity.HasIndex(x => new { x.Conclusion, x.IsOverridden });
|
||||
entity.HasOne(x => x.DegreeAwardBatch).WithMany(x => x.Results)
|
||||
.HasForeignKey(x => x.DegreeAwardBatchId).OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(x => x.Student).WithMany()
|
||||
.HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict);
|
||||
entity.HasOne(x => x.GraduationAuditResult).WithMany()
|
||||
.HasForeignKey(x => x.GraduationAuditResultId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
builder.Entity<GraduationClearanceBatch>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Name).HasMaxLength(120);
|
||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||
entity.HasIndex(x => new { x.GraduationYear, x.Status });
|
||||
});
|
||||
builder.Entity<GraduationClearanceItem>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Code).HasMaxLength(30);
|
||||
entity.Property(x => x.Name).HasMaxLength(100);
|
||||
entity.Property(x => x.ResponsibleUnit).HasMaxLength(100);
|
||||
entity.Property(x => x.ResponsibleRole).HasMaxLength(30);
|
||||
entity.HasIndex(x => new { x.GraduationClearanceBatchId, x.Code }).IsUnique();
|
||||
entity.HasOne(x => x.GraduationClearanceBatch).WithMany(x => x.Items)
|
||||
.HasForeignKey(x => x.GraduationClearanceBatchId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
builder.Entity<GraduationClearanceRecord>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||
entity.HasIndex(x => new { x.GraduationClearanceItemId, x.StudentId })
|
||||
.IsUnique();
|
||||
entity.HasIndex(x => new { x.StudentId, x.Status });
|
||||
entity.HasOne(x => x.GraduationClearanceItem).WithMany(x => x.Records)
|
||||
.HasForeignKey(x => x.GraduationClearanceItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(x => x.Student).WithMany()
|
||||
.HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<AuditLog>(entity =>
|
||||
{
|
||||
|
||||
@@ -15,6 +15,9 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
private const string GradesMigration = "20260724_07_grades";
|
||||
private const string ExamsMigration = "20260724_08_exams";
|
||||
private const string StudentStatusChangesMigration = "20260724_09_student_status_changes";
|
||||
private const string GraduationAuditsMigration = "20260724_10_graduation_audits";
|
||||
private const string DegreeAwardsMigration = "20260724_11_degree_awards";
|
||||
private const string GraduationClearanceMigration = "20260724_12_graduation_clearance";
|
||||
|
||||
public async Task MigrateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -75,6 +78,18 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
StudentStatusChangesMigration,
|
||||
StudentStatusChangesStatements,
|
||||
cancellationToken);
|
||||
await ApplyMigrationAsync(
|
||||
GraduationAuditsMigration,
|
||||
GraduationAuditsStatements,
|
||||
cancellationToken);
|
||||
await ApplyMigrationAsync(
|
||||
DegreeAwardsMigration,
|
||||
DegreeAwardsStatements,
|
||||
cancellationToken);
|
||||
await ApplyMigrationAsync(
|
||||
GraduationClearanceMigration,
|
||||
GraduationClearanceStatements,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ApplyMigrationAsync(
|
||||
@@ -637,4 +652,119 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_StudentStatusChanges_StudentId_State" ON "StudentStatusChanges" ("StudentId", "State");"""
|
||||
];
|
||||
|
||||
private static readonly string[] GraduationAuditsStatements =
|
||||
[
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "GraduationAuditBatches" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_GraduationAuditBatches" PRIMARY KEY,
|
||||
"Name" TEXT NOT NULL, "GraduationYear" INTEGER NOT NULL,
|
||||
"EnrollmentYear" INTEGER NOT NULL, "Status" INTEGER NOT NULL,
|
||||
"Notes" TEXT NULL, "CalculatedAt" TEXT NULL, "PublishedAt" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL, "UpdatedAt" TEXT NOT NULL
|
||||
);
|
||||
""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_GraduationAuditBatches_GraduationYear_EnrollmentYear" ON "GraduationAuditBatches" ("GraduationYear", "EnrollmentYear");""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_GraduationAuditBatches_Status" ON "GraduationAuditBatches" ("Status");""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "GraduationAuditResults" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_GraduationAuditResults" PRIMARY KEY,
|
||||
"GraduationAuditBatchId" TEXT NOT NULL, "StudentId" TEXT NOT NULL,
|
||||
"CurriculumPlanId" TEXT NULL, "StudentStatusSnapshot" INTEGER NOT NULL,
|
||||
"RequiredCredits" TEXT NOT NULL, "EarnedCredits" TEXT NOT NULL,
|
||||
"RequiredCourseCount" INTEGER NOT NULL,
|
||||
"PassedRequiredCourseCount" INTEGER NOT NULL,
|
||||
"FailedCourseCount" INTEGER NOT NULL,
|
||||
"MissingCourseNames" TEXT NOT NULL,
|
||||
"CalculatedConclusion" INTEGER NOT NULL, "Conclusion" INTEGER NOT NULL,
|
||||
"IsOverridden" INTEGER NOT NULL, "ReviewComment" TEXT NULL,
|
||||
"ReviewedAt" TEXT NULL, "CreatedAt" TEXT NOT NULL, "UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_GraduationAuditResults_GraduationAuditBatches_GraduationAuditBatchId"
|
||||
FOREIGN KEY ("GraduationAuditBatchId") REFERENCES "GraduationAuditBatches" ("Id") ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_GraduationAuditResults_Students_StudentId"
|
||||
FOREIGN KEY ("StudentId") REFERENCES "Students" ("Id") ON DELETE RESTRICT,
|
||||
CONSTRAINT "FK_GraduationAuditResults_CurriculumPlans_CurriculumPlanId"
|
||||
FOREIGN KEY ("CurriculumPlanId") REFERENCES "CurriculumPlans" ("Id") ON DELETE SET NULL
|
||||
);
|
||||
""",
|
||||
"""CREATE UNIQUE INDEX IF NOT EXISTS "IX_GraduationAuditResults_GraduationAuditBatchId_StudentId" ON "GraduationAuditResults" ("GraduationAuditBatchId", "StudentId");""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_GraduationAuditResults_StudentId" ON "GraduationAuditResults" ("StudentId");""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_GraduationAuditResults_CurriculumPlanId" ON "GraduationAuditResults" ("CurriculumPlanId");""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_GraduationAuditResults_Conclusion_IsOverridden" ON "GraduationAuditResults" ("Conclusion", "IsOverridden");"""
|
||||
];
|
||||
|
||||
private static readonly string[] DegreeAwardsStatements =
|
||||
[
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "DegreeAwardBatches" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_DegreeAwardBatches" PRIMARY KEY,
|
||||
"Name" TEXT NOT NULL, "GraduationYear" INTEGER NOT NULL,
|
||||
"DegreeName" TEXT NOT NULL, "MinimumGradePoint" TEXT NOT NULL,
|
||||
"Status" INTEGER NOT NULL, "Notes" TEXT NULL,
|
||||
"CalculatedAt" TEXT NULL, "PublishedAt" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL, "UpdatedAt" TEXT NOT NULL
|
||||
);
|
||||
""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_DegreeAwardBatches_GraduationYear_Status" ON "DegreeAwardBatches" ("GraduationYear", "Status");""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "DegreeAwardResults" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_DegreeAwardResults" PRIMARY KEY,
|
||||
"DegreeAwardBatchId" TEXT NOT NULL, "StudentId" TEXT NOT NULL,
|
||||
"GraduationAuditResultId" TEXT NOT NULL, "AverageGradePoint" TEXT NOT NULL,
|
||||
"CalculatedConclusion" INTEGER NOT NULL, "Conclusion" INTEGER NOT NULL,
|
||||
"ExceptionReason" TEXT NOT NULL, "IsOverridden" INTEGER NOT NULL,
|
||||
"ReviewComment" TEXT NULL, "ReviewedAt" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL, "UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_DegreeAwardResults_DegreeAwardBatches_DegreeAwardBatchId"
|
||||
FOREIGN KEY ("DegreeAwardBatchId") REFERENCES "DegreeAwardBatches" ("Id") ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_DegreeAwardResults_Students_StudentId"
|
||||
FOREIGN KEY ("StudentId") REFERENCES "Students" ("Id") ON DELETE RESTRICT,
|
||||
CONSTRAINT "FK_DegreeAwardResults_GraduationAuditResults_GraduationAuditResultId"
|
||||
FOREIGN KEY ("GraduationAuditResultId") REFERENCES "GraduationAuditResults" ("Id") ON DELETE RESTRICT
|
||||
);
|
||||
""",
|
||||
"""CREATE UNIQUE INDEX IF NOT EXISTS "IX_DegreeAwardResults_DegreeAwardBatchId_StudentId" ON "DegreeAwardResults" ("DegreeAwardBatchId", "StudentId");""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_DegreeAwardResults_StudentId" ON "DegreeAwardResults" ("StudentId");""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_DegreeAwardResults_GraduationAuditResultId" ON "DegreeAwardResults" ("GraduationAuditResultId");""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_DegreeAwardResults_Conclusion_IsOverridden" ON "DegreeAwardResults" ("Conclusion", "IsOverridden");"""
|
||||
];
|
||||
|
||||
private static readonly string[] GraduationClearanceStatements =
|
||||
[
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "GraduationClearanceBatches" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_GraduationClearanceBatches" PRIMARY KEY,
|
||||
"Name" TEXT NOT NULL, "GraduationYear" INTEGER NOT NULL,
|
||||
"Status" INTEGER NOT NULL, "Notes" TEXT NULL, "ClosedAt" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL, "UpdatedAt" TEXT NOT NULL
|
||||
);
|
||||
""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_GraduationClearanceBatches_GraduationYear_Status" ON "GraduationClearanceBatches" ("GraduationYear", "Status");""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "GraduationClearanceItems" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_GraduationClearanceItems" PRIMARY KEY,
|
||||
"GraduationClearanceBatchId" TEXT NOT NULL, "Code" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL, "ResponsibleUnit" TEXT NOT NULL,
|
||||
"ResponsibleRole" TEXT NOT NULL, "IsRequired" INTEGER NOT NULL,
|
||||
"SortOrder" INTEGER NOT NULL, "CreatedAt" TEXT NOT NULL, "UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_GraduationClearanceItems_GraduationClearanceBatches_GraduationClearanceBatchId"
|
||||
FOREIGN KEY ("GraduationClearanceBatchId") REFERENCES "GraduationClearanceBatches" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
""",
|
||||
"""CREATE UNIQUE INDEX IF NOT EXISTS "IX_GraduationClearanceItems_GraduationClearanceBatchId_Code" ON "GraduationClearanceItems" ("GraduationClearanceBatchId", "Code");""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "GraduationClearanceRecords" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_GraduationClearanceRecords" PRIMARY KEY,
|
||||
"GraduationClearanceItemId" TEXT NOT NULL, "StudentId" TEXT NOT NULL,
|
||||
"Status" INTEGER NOT NULL, "Notes" TEXT NULL, "CompletedAt" TEXT NULL,
|
||||
"CompletedByUserId" TEXT NULL, "CreatedAt" TEXT NOT NULL, "UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_GraduationClearanceRecords_GraduationClearanceItems_GraduationClearanceItemId"
|
||||
FOREIGN KEY ("GraduationClearanceItemId") REFERENCES "GraduationClearanceItems" ("Id") ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_GraduationClearanceRecords_Students_StudentId"
|
||||
FOREIGN KEY ("StudentId") REFERENCES "Students" ("Id") ON DELETE RESTRICT
|
||||
);
|
||||
""",
|
||||
"""CREATE UNIQUE INDEX IF NOT EXISTS "IX_GraduationClearanceRecords_GraduationClearanceItemId_StudentId" ON "GraduationClearanceRecords" ("GraduationClearanceItemId", "StudentId");""",
|
||||
"""CREATE INDEX IF NOT EXISTS "IX_GraduationClearanceRecords_StudentId_Status" ON "GraduationClearanceRecords" ("StudentId", "Status");"""
|
||||
];
|
||||
}
|
||||
|
||||
+2183
File diff suppressed because it is too large
Load Diff
+124
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class GraduationAudits : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GraduationAuditBatches",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false),
|
||||
GraduationYear = table.Column<int>(type: "int", nullable: false),
|
||||
EnrollmentYear = table.Column<int>(type: "int", nullable: false),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
Notes = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
|
||||
CalculatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
PublishedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GraduationAuditBatches", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GraduationAuditResults",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
GraduationAuditBatchId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
StudentId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
CurriculumPlanId = table.Column<Guid>(type: "char(36)", nullable: true),
|
||||
StudentStatusSnapshot = table.Column<int>(type: "int", nullable: false),
|
||||
RequiredCredits = table.Column<decimal>(type: "decimal(6,2)", precision: 6, scale: 2, nullable: false),
|
||||
EarnedCredits = table.Column<decimal>(type: "decimal(6,2)", precision: 6, scale: 2, nullable: false),
|
||||
RequiredCourseCount = table.Column<int>(type: "int", nullable: false),
|
||||
PassedRequiredCourseCount = table.Column<int>(type: "int", nullable: false),
|
||||
FailedCourseCount = table.Column<int>(type: "int", nullable: false),
|
||||
MissingCourseNames = table.Column<string>(type: "varchar(2000)", maxLength: 2000, nullable: false),
|
||||
CalculatedConclusion = table.Column<int>(type: "int", nullable: false),
|
||||
Conclusion = table.Column<int>(type: "int", nullable: false),
|
||||
IsOverridden = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
ReviewComment = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
|
||||
ReviewedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GraduationAuditResults", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_GraduationAuditResults_CurriculumPlans_CurriculumPlanId",
|
||||
column: x => x.CurriculumPlanId,
|
||||
principalTable: "CurriculumPlans",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_GraduationAuditResults_GraduationAuditBatches_GraduationAudi~",
|
||||
column: x => x.GraduationAuditBatchId,
|
||||
principalTable: "GraduationAuditBatches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_GraduationAuditResults_Students_StudentId",
|
||||
column: x => x.StudentId,
|
||||
principalTable: "Students",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationAuditBatches_GraduationYear_EnrollmentYear",
|
||||
table: "GraduationAuditBatches",
|
||||
columns: new[] { "GraduationYear", "EnrollmentYear" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationAuditBatches_Status",
|
||||
table: "GraduationAuditBatches",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationAuditResults_Conclusion_IsOverridden",
|
||||
table: "GraduationAuditResults",
|
||||
columns: new[] { "Conclusion", "IsOverridden" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationAuditResults_CurriculumPlanId",
|
||||
table: "GraduationAuditResults",
|
||||
column: "CurriculumPlanId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationAuditResults_GraduationAuditBatchId_StudentId",
|
||||
table: "GraduationAuditResults",
|
||||
columns: new[] { "GraduationAuditBatchId", "StudentId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationAuditResults_StudentId",
|
||||
table: "GraduationAuditResults",
|
||||
column: "StudentId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GraduationAuditResults");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "GraduationAuditBatches");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2324
File diff suppressed because it is too large
Load Diff
+115
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DegreeAwards : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DegreeAwardBatches",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false),
|
||||
GraduationYear = table.Column<int>(type: "int", nullable: false),
|
||||
DegreeName = table.Column<string>(type: "varchar(80)", maxLength: 80, nullable: false),
|
||||
MinimumGradePoint = table.Column<decimal>(type: "decimal(3,2)", precision: 3, scale: 2, nullable: false),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
Notes = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
|
||||
CalculatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
PublishedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DegreeAwardBatches", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DegreeAwardResults",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
DegreeAwardBatchId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
StudentId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
GraduationAuditResultId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
AverageGradePoint = table.Column<decimal>(type: "decimal(4,2)", precision: 4, scale: 2, nullable: false),
|
||||
CalculatedConclusion = table.Column<int>(type: "int", nullable: false),
|
||||
Conclusion = table.Column<int>(type: "int", nullable: false),
|
||||
ExceptionReason = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: false),
|
||||
IsOverridden = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
ReviewComment = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
|
||||
ReviewedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DegreeAwardResults", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DegreeAwardResults_DegreeAwardBatches_DegreeAwardBatchId",
|
||||
column: x => x.DegreeAwardBatchId,
|
||||
principalTable: "DegreeAwardBatches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DegreeAwardResults_GraduationAuditResults_GraduationAuditRes~",
|
||||
column: x => x.GraduationAuditResultId,
|
||||
principalTable: "GraduationAuditResults",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_DegreeAwardResults_Students_StudentId",
|
||||
column: x => x.StudentId,
|
||||
principalTable: "Students",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DegreeAwardBatches_GraduationYear_Status",
|
||||
table: "DegreeAwardBatches",
|
||||
columns: new[] { "GraduationYear", "Status" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DegreeAwardResults_Conclusion_IsOverridden",
|
||||
table: "DegreeAwardResults",
|
||||
columns: new[] { "Conclusion", "IsOverridden" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DegreeAwardResults_DegreeAwardBatchId_StudentId",
|
||||
table: "DegreeAwardResults",
|
||||
columns: new[] { "DegreeAwardBatchId", "StudentId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DegreeAwardResults_GraduationAuditResultId",
|
||||
table: "DegreeAwardResults",
|
||||
column: "GraduationAuditResultId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DegreeAwardResults_StudentId",
|
||||
table: "DegreeAwardResults",
|
||||
column: "StudentId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DegreeAwardResults");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "DegreeAwardBatches");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2491
File diff suppressed because it is too large
Load Diff
+128
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class GraduationClearance : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GraduationClearanceBatches",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false),
|
||||
GraduationYear = table.Column<int>(type: "int", nullable: false),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
Notes = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
|
||||
ClosedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GraduationClearanceBatches", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GraduationClearanceItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
GraduationClearanceBatchId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Code = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false),
|
||||
Name = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false),
|
||||
ResponsibleUnit = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false),
|
||||
ResponsibleRole = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false),
|
||||
IsRequired = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
SortOrder = table.Column<int>(type: "int", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GraduationClearanceItems", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_GraduationClearanceItems_GraduationClearanceBatches_Graduati~",
|
||||
column: x => x.GraduationClearanceBatchId,
|
||||
principalTable: "GraduationClearanceBatches",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GraduationClearanceRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
GraduationClearanceItemId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
StudentId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
Notes = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: true),
|
||||
CompletedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
CompletedByUserId = table.Column<Guid>(type: "char(36)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GraduationClearanceRecords", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_GraduationClearanceRecords_GraduationClearanceItems_Graduati~",
|
||||
column: x => x.GraduationClearanceItemId,
|
||||
principalTable: "GraduationClearanceItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_GraduationClearanceRecords_Students_StudentId",
|
||||
column: x => x.StudentId,
|
||||
principalTable: "Students",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationClearanceBatches_GraduationYear_Status",
|
||||
table: "GraduationClearanceBatches",
|
||||
columns: new[] { "GraduationYear", "Status" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationClearanceItems_GraduationClearanceBatchId_Code",
|
||||
table: "GraduationClearanceItems",
|
||||
columns: new[] { "GraduationClearanceBatchId", "Code" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationClearanceRecords_GraduationClearanceItemId_Student~",
|
||||
table: "GraduationClearanceRecords",
|
||||
columns: new[] { "GraduationClearanceItemId", "StudentId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GraduationClearanceRecords_StudentId_Status",
|
||||
table: "GraduationClearanceRecords",
|
||||
columns: new[] { "StudentId", "Status" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GraduationClearanceRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "GraduationClearanceItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "GraduationClearanceBatches");
|
||||
}
|
||||
}
|
||||
}
|
||||
+460
@@ -634,6 +634,115 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.ToTable("CurriculumPlans");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DegreeAwardBatch", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("CalculatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("DegreeName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("varchar(80)");
|
||||
|
||||
b.Property<int>("GraduationYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("MinimumGradePoint")
|
||||
.HasPrecision(3, 2)
|
||||
.HasColumnType("decimal(3,2)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<DateTime?>("PublishedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GraduationYear", "Status");
|
||||
|
||||
b.ToTable("DegreeAwardBatches");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DegreeAwardResult", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<decimal>("AverageGradePoint")
|
||||
.HasPrecision(4, 2)
|
||||
.HasColumnType("decimal(4,2)");
|
||||
|
||||
b.Property<int>("CalculatedConclusion")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Conclusion")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("DegreeAwardBatchId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("ExceptionReason")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<Guid>("GraduationAuditResultId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsOverridden")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("ReviewComment")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<DateTime?>("ReviewedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("StudentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GraduationAuditResultId");
|
||||
|
||||
b.HasIndex("StudentId");
|
||||
|
||||
b.HasIndex("Conclusion", "IsOverridden");
|
||||
|
||||
b.HasIndex("DegreeAwardBatchId", "StudentId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DegreeAwardResults");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamPlan", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -836,6 +945,254 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.ToTable("GradeSheets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationAuditBatch", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("CalculatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("EnrollmentYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("GraduationYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<DateTime?>("PublishedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("GraduationYear", "EnrollmentYear");
|
||||
|
||||
b.ToTable("GraduationAuditBatches");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationAuditResult", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("CalculatedConclusion")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Conclusion")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid?>("CurriculumPlanId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<decimal>("EarnedCredits")
|
||||
.HasPrecision(6, 2)
|
||||
.HasColumnType("decimal(6,2)");
|
||||
|
||||
b.Property<int>("FailedCourseCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("GraduationAuditBatchId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsOverridden")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("MissingCourseNames")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)");
|
||||
|
||||
b.Property<int>("PassedRequiredCourseCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RequiredCourseCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("RequiredCredits")
|
||||
.HasPrecision(6, 2)
|
||||
.HasColumnType("decimal(6,2)");
|
||||
|
||||
b.Property<string>("ReviewComment")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<DateTime?>("ReviewedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("StudentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("StudentStatusSnapshot")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CurriculumPlanId");
|
||||
|
||||
b.HasIndex("StudentId");
|
||||
|
||||
b.HasIndex("Conclusion", "IsOverridden");
|
||||
|
||||
b.HasIndex("GraduationAuditBatchId", "StudentId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("GraduationAuditResults");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceBatch", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("ClosedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("GraduationYear")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GraduationYear", "Status");
|
||||
|
||||
b.ToTable("GraduationClearanceBatches");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("GraduationClearanceBatchId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsRequired")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<string>("ResponsibleRole")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<string>("ResponsibleUnit")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GraduationClearanceBatchId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("GraduationClearanceItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceRecord", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime?>("CompletedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid?>("CompletedByUserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("GraduationClearanceItemId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("StudentId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GraduationClearanceItemId", "StudentId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("StudentId", "Status");
|
||||
|
||||
b.ToTable("GraduationClearanceRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1684,6 +2041,33 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("Major");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DegreeAwardResult", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.DegreeAwardBatch", "DegreeAwardBatch")
|
||||
.WithMany("Results")
|
||||
.HasForeignKey("DegreeAwardBatchId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.GraduationAuditResult", "GraduationAuditResult")
|
||||
.WithMany()
|
||||
.HasForeignKey("GraduationAuditResultId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student")
|
||||
.WithMany()
|
||||
.HasForeignKey("StudentId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DegreeAwardBatch");
|
||||
|
||||
b.Navigation("GraduationAuditResult");
|
||||
|
||||
b.Navigation("Student");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamPlan", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm")
|
||||
@@ -1771,6 +2155,62 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("TeachingTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationAuditResult", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.CurriculumPlan", "CurriculumPlan")
|
||||
.WithMany()
|
||||
.HasForeignKey("CurriculumPlanId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.GraduationAuditBatch", "GraduationAuditBatch")
|
||||
.WithMany("Results")
|
||||
.HasForeignKey("GraduationAuditBatchId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student")
|
||||
.WithMany()
|
||||
.HasForeignKey("StudentId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("CurriculumPlan");
|
||||
|
||||
b.Navigation("GraduationAuditBatch");
|
||||
|
||||
b.Navigation("Student");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceItem", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.GraduationClearanceBatch", "GraduationClearanceBatch")
|
||||
.WithMany("Items")
|
||||
.HasForeignKey("GraduationClearanceBatchId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("GraduationClearanceBatch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceRecord", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.GraduationClearanceItem", "GraduationClearanceItem")
|
||||
.WithMany("Records")
|
||||
.HasForeignKey("GraduationClearanceItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student")
|
||||
.WithMany()
|
||||
.HasForeignKey("StudentId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("GraduationClearanceItem");
|
||||
|
||||
b.Navigation("Student");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.College", "College")
|
||||
@@ -1996,6 +2436,11 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("Modules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DegreeAwardBatch", b =>
|
||||
{
|
||||
b.Navigation("Results");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamPlan", b =>
|
||||
{
|
||||
b.Navigation("Sessions");
|
||||
@@ -2011,6 +2456,21 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("Records");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationAuditBatch", b =>
|
||||
{
|
||||
b.Navigation("Results");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceBatch", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceItem", b =>
|
||||
{
|
||||
b.Navigation("Records");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.SchedulePlan", b =>
|
||||
{
|
||||
b.Navigation("Entries");
|
||||
|
||||
Reference in New Issue
Block a user