培养方案:专业/年级版本、课程模块、学分校验、复制版本、发布锁定、旧版本归档。
教学任务:学期开课、授课教师、唯一主讲、合班、容量与周次校验、发布及结课。 SQLite 开发库已自动升级,无需删除原数据库。 新增对应 MySQL EF Core 迁移。 Vue 已编译进 wwwroot,仍可只运行 ASP.NET Core 单服务。 桌面端及 390px 窄屏页面验证通过,无前端控制台错误。
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Security.Claims;
|
||||
using Jiaowu.Api.Contracts;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Roles = ReadRoles)]
|
||||
[Route("api/curriculum-plans")]
|
||||
public sealed class CurriculumPlansController(AppDbContext db) : ControllerBase
|
||||
{
|
||||
private const string ReadRoles =
|
||||
SystemRoles.SuperAdmin + "," +
|
||||
SystemRoles.AcademicAdmin + "," +
|
||||
SystemRoles.CollegeAdmin;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<PagedResult<object>>> GetPlans(
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
string? keyword = null,
|
||||
Guid? majorId = null,
|
||||
int? effectiveGrade = null,
|
||||
CurriculumPlanStatus? status = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
page = Math.Max(1, page);
|
||||
pageSize = Math.Clamp(pageSize, 10, 100);
|
||||
var source = ScopedPlans().AsNoTracking();
|
||||
if (majorId.HasValue) source = source.Where(x => x.MajorId == majorId);
|
||||
if (effectiveGrade.HasValue)
|
||||
source = source.Where(x => x.EffectiveGrade == effectiveGrade);
|
||||
if (status.HasValue) source = source.Where(x => x.Status == status);
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
keyword = keyword.Trim();
|
||||
source = source.Where(x =>
|
||||
x.Name.Contains(keyword) ||
|
||||
x.Version.Contains(keyword) ||
|
||||
x.Major!.Name.Contains(keyword));
|
||||
}
|
||||
|
||||
var total = await source.CountAsync(cancellationToken);
|
||||
var items = await source
|
||||
.OrderByDescending(x => x.EffectiveGrade)
|
||||
.ThenByDescending(x => x.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.Name,
|
||||
x.Version,
|
||||
x.MajorId,
|
||||
MajorName = x.Major!.Name,
|
||||
CollegeId = x.Major.CollegeId,
|
||||
CollegeName = x.Major.College!.Name,
|
||||
x.EffectiveGrade,
|
||||
x.TotalCredits,
|
||||
x.Status,
|
||||
x.PublishedAt,
|
||||
ModuleCount = x.Modules.Count,
|
||||
CourseCount = x.Modules.SelectMany(module => module.Courses).Count(),
|
||||
x.UpdatedAt
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ActionResult> GetPlan(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var plan = await ScopedPlans()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Id == id)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.Name,
|
||||
x.Version,
|
||||
x.MajorId,
|
||||
MajorName = x.Major!.Name,
|
||||
SchoolingYears = x.Major.SchoolingYears,
|
||||
CollegeId = x.Major.CollegeId,
|
||||
CollegeName = x.Major.College!.Name,
|
||||
x.EffectiveGrade,
|
||||
x.TotalCredits,
|
||||
x.Status,
|
||||
x.Description,
|
||||
x.PublishedAt,
|
||||
x.CreatedAt,
|
||||
x.UpdatedAt,
|
||||
Modules = x.Modules
|
||||
.OrderBy(module => module.SortOrder)
|
||||
.ThenBy(module => module.Code)
|
||||
.Select(module => new
|
||||
{
|
||||
module.Id,
|
||||
module.Code,
|
||||
module.Name,
|
||||
module.RequiredCredits,
|
||||
module.SortOrder,
|
||||
AssignedCredits = module.Courses.Sum(course => course.Course!.Credits),
|
||||
Courses = module.Courses
|
||||
.OrderBy(course => course.RecommendedSemester)
|
||||
.ThenBy(course => course.Course!.Code)
|
||||
.Select(course => new
|
||||
{
|
||||
course.Id,
|
||||
course.CourseId,
|
||||
CourseCode = course.Course!.Code,
|
||||
CourseName = course.Course.Name,
|
||||
course.Course.Credits,
|
||||
course.Course.TotalHours,
|
||||
course.Course.Nature,
|
||||
course.RecommendedSemester,
|
||||
course.Type,
|
||||
course.Notes
|
||||
})
|
||||
})
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return plan is null ? NotFound() : Ok(plan);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Create(
|
||||
CurriculumPlanRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var major = await FindAccessibleMajorAsync(request.MajorId, cancellationToken);
|
||||
if (major is null) return ValidationProblem("所选专业不存在或不在当前数据范围内。");
|
||||
|
||||
var plan = new CurriculumPlan
|
||||
{
|
||||
MajorId = request.MajorId,
|
||||
Name = request.Name.Trim(),
|
||||
Version = request.Version.Trim(),
|
||||
EffectiveGrade = request.EffectiveGrade,
|
||||
TotalCredits = request.TotalCredits,
|
||||
Description = Normalize(request.Description)
|
||||
};
|
||||
db.CurriculumPlans.Add(plan);
|
||||
return await SaveCreatedAsync(plan.Id, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<ActionResult> Update(
|
||||
Guid id,
|
||||
CurriculumPlanRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var plan = await EditablePlanAsync(id, cancellationToken);
|
||||
if (plan is null) return NotFound();
|
||||
if (plan.Status != CurriculumPlanStatus.Draft)
|
||||
return ConflictProblem("已发布或已归档的培养方案不可直接修改,请复制为新版本。");
|
||||
var major = await FindAccessibleMajorAsync(request.MajorId, cancellationToken);
|
||||
if (major is null) return ValidationProblem("所选专业不存在或不在当前数据范围内。");
|
||||
|
||||
plan.MajorId = request.MajorId;
|
||||
plan.Name = request.Name.Trim();
|
||||
plan.Version = request.Version.Trim();
|
||||
plan.EffectiveGrade = request.EffectiveGrade;
|
||||
plan.TotalCredits = request.TotalCredits;
|
||||
plan.Description = Normalize(request.Description);
|
||||
return await SaveNoContentAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<ActionResult> Delete(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var plan = await EditablePlanAsync(id, cancellationToken);
|
||||
if (plan is null) return NotFound();
|
||||
if (plan.Status != CurriculumPlanStatus.Draft)
|
||||
return ConflictProblem("仅草稿方案可以删除。");
|
||||
db.CurriculumPlans.Remove(plan);
|
||||
return await SaveNoContentAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/clone")]
|
||||
public async Task<ActionResult> Clone(
|
||||
Guid id,
|
||||
CloneCurriculumPlanRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var source = await ScopedPlans()
|
||||
.AsNoTracking()
|
||||
.Include(x => x.Modules)
|
||||
.ThenInclude(x => x.Courses)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
if (source is null) return NotFound();
|
||||
|
||||
var copy = new CurriculumPlan
|
||||
{
|
||||
MajorId = source.MajorId,
|
||||
Name = request.Name.Trim(),
|
||||
Version = request.Version.Trim(),
|
||||
EffectiveGrade = request.EffectiveGrade,
|
||||
TotalCredits = source.TotalCredits,
|
||||
Description = source.Description,
|
||||
Modules = source.Modules.Select(module => new CurriculumModule
|
||||
{
|
||||
Code = module.Code,
|
||||
Name = module.Name,
|
||||
RequiredCredits = module.RequiredCredits,
|
||||
SortOrder = module.SortOrder,
|
||||
Courses = module.Courses.Select(course => new CurriculumCourse
|
||||
{
|
||||
CourseId = course.CourseId,
|
||||
RecommendedSemester = course.RecommendedSemester,
|
||||
Type = course.Type,
|
||||
Notes = course.Notes
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
};
|
||||
db.CurriculumPlans.Add(copy);
|
||||
return await SaveCreatedAsync(copy.Id, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/publish")]
|
||||
public async Task<ActionResult> Publish(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var plan = await ScopedPlans()
|
||||
.Include(x => x.Major)
|
||||
.Include(x => x.Modules)
|
||||
.ThenInclude(x => x.Courses)
|
||||
.ThenInclude(x => x.Course)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
if (plan is null) return NotFound();
|
||||
if (plan.Status != CurriculumPlanStatus.Draft)
|
||||
return ConflictProblem("只有草稿方案可以发布。");
|
||||
if (plan.Modules.Count == 0 || plan.Modules.Any(x => x.Courses.Count == 0))
|
||||
return ConflictProblem("发布前每个课程模块都必须配置课程。");
|
||||
if (plan.Modules.Sum(x => x.RequiredCredits) != plan.TotalCredits)
|
||||
return ConflictProblem("各模块最低学分之和必须等于方案总学分。");
|
||||
if (plan.Modules.Any(module =>
|
||||
module.Courses.Sum(x => x.Course!.Credits) < module.RequiredCredits))
|
||||
return ConflictProblem("存在课程学分合计低于最低学分要求的模块。");
|
||||
if (plan.Modules.SelectMany(x => x.Courses).GroupBy(x => x.CourseId).Any(x => x.Count() > 1))
|
||||
return ConflictProblem("同一门课程不能重复加入多个模块。");
|
||||
if (plan.Modules.SelectMany(x => x.Courses).Any(x =>
|
||||
x.RecommendedSemester > plan.Major!.SchoolingYears * 2))
|
||||
return ConflictProblem("建议学期超出了该专业学制。");
|
||||
|
||||
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
|
||||
var previousPlans = await ScopedPlans()
|
||||
.Where(x =>
|
||||
x.Id != plan.Id &&
|
||||
x.MajorId == plan.MajorId &&
|
||||
x.EffectiveGrade == plan.EffectiveGrade &&
|
||||
x.Status == CurriculumPlanStatus.Published)
|
||||
.ToListAsync(cancellationToken);
|
||||
foreach (var previous in previousPlans)
|
||||
{
|
||||
previous.Status = CurriculumPlanStatus.Archived;
|
||||
}
|
||||
|
||||
plan.Status = CurriculumPlanStatus.Published;
|
||||
plan.PublishedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("{planId:guid}/modules")]
|
||||
public async Task<ActionResult> CreateModule(
|
||||
Guid planId,
|
||||
CurriculumModuleRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var plan = await DraftPlanAsync(planId, cancellationToken);
|
||||
if (plan is null) return NotFound();
|
||||
var module = new CurriculumModule
|
||||
{
|
||||
CurriculumPlanId = plan.Id,
|
||||
Code = request.Code.Trim(),
|
||||
Name = request.Name.Trim(),
|
||||
RequiredCredits = request.RequiredCredits,
|
||||
SortOrder = request.SortOrder
|
||||
};
|
||||
db.CurriculumModules.Add(module);
|
||||
return await SaveCreatedAsync(module.Id, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{planId:guid}/modules/{moduleId:guid}")]
|
||||
public async Task<ActionResult> UpdateModule(
|
||||
Guid planId,
|
||||
Guid moduleId,
|
||||
CurriculumModuleRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (await DraftPlanAsync(planId, cancellationToken) is null) return NotFound();
|
||||
var module = await db.CurriculumModules
|
||||
.FirstOrDefaultAsync(x => x.Id == moduleId && x.CurriculumPlanId == planId, cancellationToken);
|
||||
if (module is null) return NotFound();
|
||||
module.Code = request.Code.Trim();
|
||||
module.Name = request.Name.Trim();
|
||||
module.RequiredCredits = request.RequiredCredits;
|
||||
module.SortOrder = request.SortOrder;
|
||||
return await SaveNoContentAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{planId:guid}/modules/{moduleId:guid}")]
|
||||
public async Task<ActionResult> DeleteModule(
|
||||
Guid planId,
|
||||
Guid moduleId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (await DraftPlanAsync(planId, cancellationToken) is null) return NotFound();
|
||||
var module = await db.CurriculumModules
|
||||
.FirstOrDefaultAsync(x => x.Id == moduleId && x.CurriculumPlanId == planId, cancellationToken);
|
||||
if (module is null) return NotFound();
|
||||
db.CurriculumModules.Remove(module);
|
||||
return await SaveNoContentAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("{planId:guid}/modules/{moduleId:guid}/courses")]
|
||||
public async Task<ActionResult> AddCourse(
|
||||
Guid planId,
|
||||
Guid moduleId,
|
||||
CurriculumCourseRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var plan = await DraftPlanAsync(planId, cancellationToken);
|
||||
if (plan is null) return NotFound();
|
||||
if (!await db.CurriculumModules.AnyAsync(
|
||||
x => x.Id == moduleId && x.CurriculumPlanId == planId,
|
||||
cancellationToken))
|
||||
return NotFound();
|
||||
if (!await db.Courses.AnyAsync(
|
||||
x => x.Id == request.CourseId && x.IsEnabled,
|
||||
cancellationToken))
|
||||
return ValidationProblem("所选课程不存在或已停用。");
|
||||
if (request.RecommendedSemester > plan.Major!.SchoolingYears * 2)
|
||||
return ValidationProblem("建议学期超出了该专业学制。");
|
||||
if (await db.CurriculumCourses.AnyAsync(
|
||||
x => x.CurriculumModule!.CurriculumPlanId == planId &&
|
||||
x.CourseId == request.CourseId,
|
||||
cancellationToken))
|
||||
return ConflictProblem("该课程已存在于本方案中。");
|
||||
|
||||
var item = new CurriculumCourse
|
||||
{
|
||||
CurriculumModuleId = moduleId,
|
||||
CourseId = request.CourseId,
|
||||
RecommendedSemester = request.RecommendedSemester,
|
||||
Type = request.Type,
|
||||
Notes = Normalize(request.Notes)
|
||||
};
|
||||
db.CurriculumCourses.Add(item);
|
||||
return await SaveCreatedAsync(item.Id, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{planId:guid}/modules/{moduleId:guid}/courses/{itemId:guid}")]
|
||||
public async Task<ActionResult> UpdateCourse(
|
||||
Guid planId,
|
||||
Guid moduleId,
|
||||
Guid itemId,
|
||||
CurriculumCourseRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var plan = await DraftPlanAsync(planId, cancellationToken);
|
||||
if (plan is null) return NotFound();
|
||||
var item = await db.CurriculumCourses
|
||||
.FirstOrDefaultAsync(x =>
|
||||
x.Id == itemId &&
|
||||
x.CurriculumModuleId == moduleId &&
|
||||
x.CurriculumModule!.CurriculumPlanId == planId,
|
||||
cancellationToken);
|
||||
if (item is null) return NotFound();
|
||||
if (!await db.Courses.AnyAsync(
|
||||
x => x.Id == request.CourseId && x.IsEnabled,
|
||||
cancellationToken))
|
||||
return ValidationProblem("所选课程不存在或已停用。");
|
||||
if (request.RecommendedSemester > plan.Major!.SchoolingYears * 2)
|
||||
return ValidationProblem("建议学期超出了该专业学制。");
|
||||
if (await db.CurriculumCourses.AnyAsync(
|
||||
x => x.Id != itemId &&
|
||||
x.CurriculumModule!.CurriculumPlanId == planId &&
|
||||
x.CourseId == request.CourseId,
|
||||
cancellationToken))
|
||||
return ConflictProblem("该课程已存在于本方案中。");
|
||||
|
||||
item.CourseId = request.CourseId;
|
||||
item.RecommendedSemester = request.RecommendedSemester;
|
||||
item.Type = request.Type;
|
||||
item.Notes = Normalize(request.Notes);
|
||||
return await SaveNoContentAsync(cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{planId:guid}/modules/{moduleId:guid}/courses/{itemId:guid}")]
|
||||
public async Task<ActionResult> DeleteCourse(
|
||||
Guid planId,
|
||||
Guid moduleId,
|
||||
Guid itemId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (await DraftPlanAsync(planId, cancellationToken) is null) return NotFound();
|
||||
var item = await db.CurriculumCourses
|
||||
.FirstOrDefaultAsync(x =>
|
||||
x.Id == itemId &&
|
||||
x.CurriculumModuleId == moduleId &&
|
||||
x.CurriculumModule!.CurriculumPlanId == planId,
|
||||
cancellationToken);
|
||||
if (item is null) return NotFound();
|
||||
db.CurriculumCourses.Remove(item);
|
||||
return await SaveNoContentAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private IQueryable<CurriculumPlan> ScopedPlans()
|
||||
{
|
||||
var source = db.CurriculumPlans.AsQueryable();
|
||||
var collegeId = ScopedCollegeId();
|
||||
return collegeId.HasValue
|
||||
? source.Where(x => x.Major!.CollegeId == collegeId.Value)
|
||||
: source;
|
||||
}
|
||||
|
||||
private async Task<Major?> FindAccessibleMajorAsync(Guid majorId, CancellationToken cancellationToken)
|
||||
{
|
||||
var source = db.Majors.AsQueryable();
|
||||
var collegeId = ScopedCollegeId();
|
||||
if (collegeId.HasValue) source = source.Where(x => x.CollegeId == collegeId.Value);
|
||||
return await source.FirstOrDefaultAsync(x => x.Id == majorId, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<CurriculumPlan?> EditablePlanAsync(
|
||||
Guid id,
|
||||
CancellationToken cancellationToken) =>
|
||||
await ScopedPlans().FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
|
||||
private async Task<CurriculumPlan?> DraftPlanAsync(
|
||||
Guid id,
|
||||
CancellationToken cancellationToken) =>
|
||||
await ScopedPlans()
|
||||
.Include(x => x.Major)
|
||||
.FirstOrDefaultAsync(
|
||||
x => x.Id == id && x.Status == CurriculumPlanStatus.Draft,
|
||||
cancellationToken);
|
||||
|
||||
private Guid? ScopedCollegeId()
|
||||
{
|
||||
if (!User.IsInRole(SystemRoles.CollegeAdmin)) return null;
|
||||
return Guid.TryParse(User.FindFirstValue("college_id"), out var collegeId)
|
||||
? collegeId
|
||||
: Guid.Empty;
|
||||
}
|
||||
|
||||
private async Task<ActionResult> SaveCreatedAsync(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return Created(string.Empty, new { id });
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
return ConflictProblem("版本、模块编码或课程配置重复。");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ActionResult> SaveNoContentAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
return ConflictProblem("数据重复,或记录已被其他业务引用。");
|
||||
}
|
||||
}
|
||||
|
||||
private ActionResult ConflictProblem(string detail) =>
|
||||
Conflict(new ProblemDetails
|
||||
{
|
||||
Title = "无法完成操作",
|
||||
Detail = detail,
|
||||
Status = StatusCodes.Status409Conflict
|
||||
});
|
||||
|
||||
private static string? Normalize(string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
}
|
||||
|
||||
public sealed record CurriculumPlanRequest(
|
||||
Guid MajorId,
|
||||
[Required, MaxLength(120)] string Name,
|
||||
[Required, MaxLength(30)] string Version,
|
||||
[Range(2000, 2200)] int EffectiveGrade,
|
||||
[Range(typeof(decimal), "0.1", "999")] decimal TotalCredits,
|
||||
[MaxLength(1000)] string? Description);
|
||||
|
||||
public sealed record CloneCurriculumPlanRequest(
|
||||
[Required, MaxLength(120)] string Name,
|
||||
[Required, MaxLength(30)] string Version,
|
||||
[Range(2000, 2200)] int EffectiveGrade);
|
||||
|
||||
public sealed record CurriculumModuleRequest(
|
||||
[Required, MaxLength(30)] string Code,
|
||||
[Required, MaxLength(100)] string Name,
|
||||
[Range(typeof(decimal), "0", "999")] decimal RequiredCredits,
|
||||
[Range(0, 9999)] int SortOrder);
|
||||
|
||||
public sealed record CurriculumCourseRequest(
|
||||
Guid CourseId,
|
||||
[Range(1, 20)] int RecommendedSemester,
|
||||
CurriculumCourseType Type,
|
||||
[MaxLength(500)] string? Notes);
|
||||
@@ -32,6 +32,8 @@ public sealed class DashboardController(AppDbContext db) : ControllerBase
|
||||
Teachers = await db.Teachers.CountAsync(cancellationToken),
|
||||
Students = await db.Students.CountAsync(cancellationToken),
|
||||
Courses = await db.Courses.CountAsync(cancellationToken),
|
||||
CurriculumPlans = await db.CurriculumPlans.CountAsync(cancellationToken),
|
||||
TeachingTasks = await db.TeachingTasks.CountAsync(cancellationToken),
|
||||
Users = await db.Users.CountAsync(cancellationToken)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Security.Claims;
|
||||
using Jiaowu.Api.Contracts;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Roles = ManagementRoles)]
|
||||
[Route("api/teaching-tasks")]
|
||||
public sealed class TeachingTasksController(AppDbContext db) : ControllerBase
|
||||
{
|
||||
private const string ManagementRoles =
|
||||
SystemRoles.SuperAdmin + "," +
|
||||
SystemRoles.AcademicAdmin + "," +
|
||||
SystemRoles.CollegeAdmin;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<PagedResult<object>>> Get(
|
||||
int page = 1,
|
||||
int pageSize = 20,
|
||||
string? keyword = null,
|
||||
Guid? academicTermId = null,
|
||||
Guid? collegeId = null,
|
||||
TeachingTaskStatus? status = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
page = Math.Max(1, page);
|
||||
pageSize = Math.Clamp(pageSize, 10, 100);
|
||||
var source = ScopedTasks().AsNoTracking();
|
||||
if (academicTermId.HasValue)
|
||||
source = source.Where(x => x.AcademicTermId == academicTermId);
|
||||
if (collegeId.HasValue)
|
||||
source = source.Where(x => x.Course!.CollegeId == collegeId);
|
||||
if (status.HasValue) source = source.Where(x => x.Status == status);
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
keyword = keyword.Trim();
|
||||
source = source.Where(x =>
|
||||
x.TaskNumber.Contains(keyword) ||
|
||||
x.Name.Contains(keyword) ||
|
||||
x.Course!.Code.Contains(keyword) ||
|
||||
x.Course.Name.Contains(keyword));
|
||||
}
|
||||
|
||||
var total = await source.CountAsync(cancellationToken);
|
||||
var items = await source
|
||||
.OrderByDescending(x => x.AcademicTerm!.StartDate)
|
||||
.ThenBy(x => x.TaskNumber)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.TaskNumber,
|
||||
x.Name,
|
||||
x.AcademicTermId,
|
||||
TermName = x.AcademicTerm!.Name,
|
||||
x.CourseId,
|
||||
CourseCode = x.Course!.Code,
|
||||
CourseName = x.Course.Name,
|
||||
CollegeId = x.Course.CollegeId,
|
||||
CollegeName = x.Course.College!.Name,
|
||||
x.Capacity,
|
||||
x.StartWeek,
|
||||
x.EndWeek,
|
||||
x.WeeklyHours,
|
||||
x.Status,
|
||||
TeacherNames = x.Teachers
|
||||
.OrderByDescending(item => item.IsPrimary)
|
||||
.Select(item => item.Teacher!.Name),
|
||||
ClassNames = x.Classes.Select(item => item.AdministrativeClass!.Name),
|
||||
StudentCount = x.Classes.Sum(item =>
|
||||
item.AdministrativeClass!.Students.Count(student =>
|
||||
student.Status == StudentStatus.Active)),
|
||||
x.UpdatedAt
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ActionResult> GetOne(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await ScopedTasks()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Id == id)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.TaskNumber,
|
||||
x.Name,
|
||||
x.AcademicTermId,
|
||||
TermName = x.AcademicTerm!.Name,
|
||||
x.CourseId,
|
||||
CourseCode = x.Course!.Code,
|
||||
CourseName = x.Course.Name,
|
||||
CollegeId = x.Course.CollegeId,
|
||||
CollegeName = x.Course.College!.Name,
|
||||
x.Capacity,
|
||||
x.StartWeek,
|
||||
x.EndWeek,
|
||||
x.WeeklyHours,
|
||||
x.Status,
|
||||
x.Notes,
|
||||
x.PublishedAt,
|
||||
TeacherIds = x.Teachers.Select(item => item.TeacherId),
|
||||
PrimaryTeacherId = x.Teachers
|
||||
.Where(item => item.IsPrimary)
|
||||
.Select(item => (Guid?)item.TeacherId)
|
||||
.FirstOrDefault(),
|
||||
Teachers = x.Teachers
|
||||
.OrderByDescending(item => item.IsPrimary)
|
||||
.ThenBy(item => item.Teacher!.TeacherNumber)
|
||||
.Select(item => new
|
||||
{
|
||||
item.TeacherId,
|
||||
item.Teacher!.TeacherNumber,
|
||||
item.Teacher.Name,
|
||||
item.Teacher.Title,
|
||||
item.IsPrimary
|
||||
}),
|
||||
ClassIds = x.Classes.Select(item => item.AdministrativeClassId),
|
||||
Classes = x.Classes
|
||||
.OrderBy(item => item.AdministrativeClass!.Code)
|
||||
.Select(item => new
|
||||
{
|
||||
item.AdministrativeClassId,
|
||||
item.AdministrativeClass!.Code,
|
||||
item.AdministrativeClass.Name,
|
||||
item.AdministrativeClass.Grade,
|
||||
StudentCount = item.AdministrativeClass.Students.Count(student =>
|
||||
student.Status == StudentStatus.Active)
|
||||
}),
|
||||
x.CreatedAt,
|
||||
x.UpdatedAt
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
return task is null ? NotFound() : Ok(task);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Create(
|
||||
TeachingTaskRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var validation = await ValidateRequestAsync(request, cancellationToken);
|
||||
if (validation is not null) return validation;
|
||||
var task = new TeachingTask
|
||||
{
|
||||
TaskNumber = request.TaskNumber.Trim(),
|
||||
Name = request.Name.Trim(),
|
||||
AcademicTermId = request.AcademicTermId,
|
||||
CourseId = request.CourseId,
|
||||
Capacity = request.Capacity,
|
||||
StartWeek = request.StartWeek,
|
||||
EndWeek = request.EndWeek,
|
||||
WeeklyHours = request.WeeklyHours,
|
||||
Notes = Normalize(request.Notes)
|
||||
};
|
||||
SetAssignments(task, request);
|
||||
db.TeachingTasks.Add(task);
|
||||
return await SaveAsync(task.Id, true, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<ActionResult> Update(
|
||||
Guid id,
|
||||
TeachingTaskRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await ScopedTasks()
|
||||
.Include(x => x.Teachers)
|
||||
.Include(x => x.Classes)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
if (task is null) return NotFound();
|
||||
if (task.Status != TeachingTaskStatus.Draft)
|
||||
return ConflictProblem("已发布或已结课的教学任务不可直接修改。");
|
||||
var validation = await ValidateRequestAsync(request, cancellationToken);
|
||||
if (validation is not null) return validation;
|
||||
|
||||
task.TaskNumber = request.TaskNumber.Trim();
|
||||
task.Name = request.Name.Trim();
|
||||
task.AcademicTermId = request.AcademicTermId;
|
||||
task.CourseId = request.CourseId;
|
||||
task.Capacity = request.Capacity;
|
||||
task.StartWeek = request.StartWeek;
|
||||
task.EndWeek = request.EndWeek;
|
||||
task.WeeklyHours = request.WeeklyHours;
|
||||
task.Notes = Normalize(request.Notes);
|
||||
db.TeachingTaskTeachers.RemoveRange(task.Teachers);
|
||||
db.TeachingTaskClasses.RemoveRange(task.Classes);
|
||||
task.Teachers = [];
|
||||
task.Classes = [];
|
||||
SetAssignments(task, request);
|
||||
return await SaveAsync(task.Id, false, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<ActionResult> Delete(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await ScopedTasks().FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
if (task is null) return NotFound();
|
||||
if (task.Status != TeachingTaskStatus.Draft)
|
||||
return ConflictProblem("仅草稿教学任务可以删除。");
|
||||
db.TeachingTasks.Remove(task);
|
||||
return await SaveAsync(id, false, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/publish")]
|
||||
public async Task<ActionResult> Publish(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await ScopedTasks()
|
||||
.Include(x => x.Teachers)
|
||||
.Include(x => x.Classes)
|
||||
.ThenInclude(x => x.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Students)
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
if (task is null) return NotFound();
|
||||
if (task.Status != TeachingTaskStatus.Draft)
|
||||
return ConflictProblem("只有草稿教学任务可以发布。");
|
||||
if (task.Teachers.Count == 0 || task.Teachers.Count(x => x.IsPrimary) != 1)
|
||||
return ConflictProblem("发布前必须指定且只能指定一名主讲教师。");
|
||||
if (task.Classes.Count == 0)
|
||||
return ConflictProblem("发布前至少需要关联一个行政班。");
|
||||
var studentCount = task.Classes.Sum(x =>
|
||||
x.AdministrativeClass!.Students.Count(student =>
|
||||
student.Status == StudentStatus.Active));
|
||||
if (studentCount > task.Capacity)
|
||||
return ConflictProblem($"教学班容量不足:关联班级共有 {studentCount} 名在籍学生。");
|
||||
|
||||
task.Status = TeachingTaskStatus.Published;
|
||||
task.PublishedAt = DateTime.UtcNow;
|
||||
return await SaveAsync(id, false, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/close")]
|
||||
public async Task<ActionResult> Close(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await ScopedTasks().FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
if (task is null) return NotFound();
|
||||
if (task.Status != TeachingTaskStatus.Published)
|
||||
return ConflictProblem("只有已发布的教学任务可以结课。");
|
||||
task.Status = TeachingTaskStatus.Closed;
|
||||
return await SaveAsync(id, false, cancellationToken);
|
||||
}
|
||||
|
||||
private IQueryable<TeachingTask> ScopedTasks()
|
||||
{
|
||||
var source = db.TeachingTasks.AsQueryable();
|
||||
var collegeId = ScopedCollegeId();
|
||||
return collegeId.HasValue
|
||||
? source.Where(x => x.Course!.CollegeId == collegeId.Value)
|
||||
: source;
|
||||
}
|
||||
|
||||
private async Task<ActionResult?> ValidateRequestAsync(
|
||||
TeachingTaskRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.StartWeek > request.EndWeek)
|
||||
return ValidationProblem("开始周不能晚于结束周。");
|
||||
var course = await db.Courses.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.Id == request.CourseId && x.IsEnabled, cancellationToken);
|
||||
if (course is null) return ValidationProblem("所选课程不存在或已停用。");
|
||||
var collegeId = ScopedCollegeId();
|
||||
if (collegeId.HasValue && course.CollegeId != collegeId.Value) return Forbid();
|
||||
if (!await db.AcademicTerms.AnyAsync(
|
||||
x => x.Id == request.AcademicTermId && x.IsEnabled,
|
||||
cancellationToken))
|
||||
return ValidationProblem("所选学期不存在或已停用。");
|
||||
|
||||
var teacherIds = request.TeacherIds.Distinct().ToArray();
|
||||
if (request.PrimaryTeacherId.HasValue &&
|
||||
!teacherIds.Contains(request.PrimaryTeacherId.Value))
|
||||
return ValidationProblem("主讲教师必须包含在授课教师中。");
|
||||
if (await db.Teachers.CountAsync(
|
||||
x => teacherIds.Contains(x.Id) && x.Status == TeacherStatus.Active,
|
||||
cancellationToken) != teacherIds.Length)
|
||||
return ValidationProblem("存在无效或非在职授课教师。");
|
||||
|
||||
var classIds = request.ClassIds.Distinct().ToArray();
|
||||
var classes = db.AdministrativeClasses.AsNoTracking()
|
||||
.Where(x => classIds.Contains(x.Id));
|
||||
if (collegeId.HasValue)
|
||||
classes = classes.Where(x => x.Major!.CollegeId == collegeId.Value);
|
||||
if (await classes.CountAsync(cancellationToken) != classIds.Length)
|
||||
return ValidationProblem("存在无效或不在当前数据范围内的行政班。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void SetAssignments(TeachingTask task, TeachingTaskRequest request)
|
||||
{
|
||||
foreach (var teacherId in request.TeacherIds.Distinct())
|
||||
{
|
||||
task.Teachers.Add(new TeachingTaskTeacher
|
||||
{
|
||||
TeacherId = teacherId,
|
||||
IsPrimary = teacherId == request.PrimaryTeacherId
|
||||
});
|
||||
}
|
||||
foreach (var classId in request.ClassIds.Distinct())
|
||||
{
|
||||
task.Classes.Add(new TeachingTaskClass
|
||||
{
|
||||
AdministrativeClassId = classId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Guid? ScopedCollegeId()
|
||||
{
|
||||
if (!User.IsInRole(SystemRoles.CollegeAdmin)) return null;
|
||||
return Guid.TryParse(User.FindFirstValue("college_id"), out var collegeId)
|
||||
? collegeId
|
||||
: Guid.Empty;
|
||||
}
|
||||
|
||||
private async Task<ActionResult> SaveAsync(
|
||||
Guid id,
|
||||
bool created,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return created ? Created(string.Empty, new { id }) : NoContent();
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
return ConflictProblem("教学任务编号重复,或关联数据已失效。");
|
||||
}
|
||||
}
|
||||
|
||||
private ActionResult ConflictProblem(string detail) =>
|
||||
Conflict(new ProblemDetails
|
||||
{
|
||||
Title = "无法完成操作",
|
||||
Detail = detail,
|
||||
Status = StatusCodes.Status409Conflict
|
||||
});
|
||||
|
||||
private static string? Normalize(string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
}
|
||||
|
||||
public sealed record TeachingTaskRequest(
|
||||
[Required, MaxLength(40)] string TaskNumber,
|
||||
[Required, MaxLength(120)] string Name,
|
||||
Guid AcademicTermId,
|
||||
Guid CourseId,
|
||||
[Range(1, 10000)] int Capacity,
|
||||
[Range(1, 30)] int StartWeek,
|
||||
[Range(1, 30)] int EndWeek,
|
||||
[Range(1, 40)] int WeeklyHours,
|
||||
IReadOnlyCollection<Guid> TeacherIds,
|
||||
Guid? PrimaryTeacherId,
|
||||
IReadOnlyCollection<Guid> ClassIds,
|
||||
[MaxLength(500)] string? Notes);
|
||||
@@ -0,0 +1,52 @@
|
||||
using Jiaowu.Api.Domain.Common;
|
||||
|
||||
namespace Jiaowu.Api.Domain.Academic;
|
||||
|
||||
public sealed class CurriculumPlan : EntityBase
|
||||
{
|
||||
public Guid MajorId { get; set; }
|
||||
public Major? Major { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string Version { get; set; }
|
||||
public int EffectiveGrade { get; set; }
|
||||
public decimal TotalCredits { get; set; }
|
||||
public CurriculumPlanStatus Status { get; set; } = CurriculumPlanStatus.Draft;
|
||||
public string? Description { get; set; }
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public ICollection<CurriculumModule> Modules { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class CurriculumModule : EntityBase
|
||||
{
|
||||
public Guid CurriculumPlanId { get; set; }
|
||||
public CurriculumPlan? CurriculumPlan { get; set; }
|
||||
public required string Code { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public decimal RequiredCredits { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public ICollection<CurriculumCourse> Courses { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class CurriculumCourse : EntityBase
|
||||
{
|
||||
public Guid CurriculumModuleId { get; set; }
|
||||
public CurriculumModule? CurriculumModule { get; set; }
|
||||
public Guid CourseId { get; set; }
|
||||
public Course? Course { get; set; }
|
||||
public int RecommendedSemester { get; set; }
|
||||
public CurriculumCourseType Type { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
|
||||
public enum CurriculumPlanStatus
|
||||
{
|
||||
Draft = 1,
|
||||
Published = 2,
|
||||
Archived = 3
|
||||
}
|
||||
|
||||
public enum CurriculumCourseType
|
||||
{
|
||||
Required = 1,
|
||||
Elective = 2
|
||||
}
|
||||
@@ -28,6 +28,7 @@ public sealed class AdministrativeClass : CatalogEntity
|
||||
public Major? Major { get; set; }
|
||||
public int Grade { get; set; }
|
||||
public string? CounselorName { get; set; }
|
||||
public ICollection<Student> Students { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class Building : CatalogEntity
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Jiaowu.Api.Domain.Common;
|
||||
|
||||
namespace Jiaowu.Api.Domain.Academic;
|
||||
|
||||
public sealed class TeachingTask : EntityBase
|
||||
{
|
||||
public required string TaskNumber { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public Guid AcademicTermId { get; set; }
|
||||
public AcademicTerm? AcademicTerm { get; set; }
|
||||
public Guid CourseId { get; set; }
|
||||
public Course? Course { get; set; }
|
||||
public int Capacity { get; set; }
|
||||
public int StartWeek { get; set; } = 1;
|
||||
public int EndWeek { get; set; } = 16;
|
||||
public int WeeklyHours { get; set; } = 2;
|
||||
public TeachingTaskStatus Status { get; set; } = TeachingTaskStatus.Draft;
|
||||
public string? Notes { get; set; }
|
||||
public DateTime? PublishedAt { get; set; }
|
||||
public ICollection<TeachingTaskTeacher> Teachers { get; set; } = [];
|
||||
public ICollection<TeachingTaskClass> Classes { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class TeachingTaskTeacher
|
||||
{
|
||||
public Guid TeachingTaskId { get; set; }
|
||||
public TeachingTask? TeachingTask { get; set; }
|
||||
public Guid TeacherId { get; set; }
|
||||
public Teacher? Teacher { get; set; }
|
||||
public bool IsPrimary { get; set; }
|
||||
}
|
||||
|
||||
public sealed class TeachingTaskClass
|
||||
{
|
||||
public Guid TeachingTaskId { get; set; }
|
||||
public TeachingTask? TeachingTask { get; set; }
|
||||
public Guid AdministrativeClassId { get; set; }
|
||||
public AdministrativeClass? AdministrativeClass { get; set; }
|
||||
}
|
||||
|
||||
public enum TeachingTaskStatus
|
||||
{
|
||||
Draft = 1,
|
||||
Published = 2,
|
||||
Closed = 3
|
||||
}
|
||||
@@ -20,6 +20,12 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
public DbSet<Teacher> Teachers => Set<Teacher>();
|
||||
public DbSet<Student> Students => Set<Student>();
|
||||
public DbSet<Course> Courses => Set<Course>();
|
||||
public DbSet<CurriculumPlan> CurriculumPlans => Set<CurriculumPlan>();
|
||||
public DbSet<CurriculumModule> CurriculumModules => Set<CurriculumModule>();
|
||||
public DbSet<CurriculumCourse> CurriculumCourses => Set<CurriculumCourse>();
|
||||
public DbSet<TeachingTask> TeachingTasks => Set<TeachingTask>();
|
||||
public DbSet<TeachingTaskTeacher> TeachingTaskTeachers => Set<TeachingTaskTeacher>();
|
||||
public DbSet<TeachingTaskClass> TeachingTaskClasses => Set<TeachingTaskClass>();
|
||||
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
@@ -111,7 +117,7 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
entity.HasIndex(x => new { x.AdministrativeClassId, x.Status });
|
||||
entity.HasIndex(x => x.EnrollmentYear);
|
||||
entity.HasOne(x => x.AdministrativeClass)
|
||||
.WithMany()
|
||||
.WithMany(x => x.Students)
|
||||
.HasForeignKey(x => x.AdministrativeClassId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
entity.HasOne<ApplicationUser>()
|
||||
@@ -132,6 +138,89 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<CurriculumPlan>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Name).HasMaxLength(120);
|
||||
entity.Property(x => x.Version).HasMaxLength(30);
|
||||
entity.Property(x => x.TotalCredits).HasPrecision(6, 2);
|
||||
entity.Property(x => x.Description).HasMaxLength(1000);
|
||||
entity.HasIndex(x => new { x.MajorId, x.EffectiveGrade, x.Version }).IsUnique();
|
||||
entity.HasIndex(x => new { x.Status, x.EffectiveGrade });
|
||||
entity.HasOne(x => x.Major)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.MajorId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<CurriculumModule>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Code).HasMaxLength(30);
|
||||
entity.Property(x => x.Name).HasMaxLength(100);
|
||||
entity.Property(x => x.RequiredCredits).HasPrecision(6, 2);
|
||||
entity.HasIndex(x => new { x.CurriculumPlanId, x.Code }).IsUnique();
|
||||
entity.HasOne(x => x.CurriculumPlan)
|
||||
.WithMany(x => x.Modules)
|
||||
.HasForeignKey(x => x.CurriculumPlanId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
builder.Entity<CurriculumCourse>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||
entity.HasIndex(x => new { x.CurriculumModuleId, x.CourseId }).IsUnique();
|
||||
entity.HasOne(x => x.CurriculumModule)
|
||||
.WithMany(x => x.Courses)
|
||||
.HasForeignKey(x => x.CurriculumModuleId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(x => x.Course)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.CourseId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<TeachingTask>(entity =>
|
||||
{
|
||||
entity.Property(x => x.TaskNumber).HasMaxLength(40);
|
||||
entity.Property(x => x.Name).HasMaxLength(120);
|
||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||
entity.HasIndex(x => x.TaskNumber).IsUnique();
|
||||
entity.HasIndex(x => new { x.AcademicTermId, x.Status });
|
||||
entity.HasOne(x => x.AcademicTerm)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.AcademicTermId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
entity.HasOne(x => x.Course)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.CourseId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<TeachingTaskTeacher>(entity =>
|
||||
{
|
||||
entity.HasKey(x => new { x.TeachingTaskId, x.TeacherId });
|
||||
entity.HasOne(x => x.TeachingTask)
|
||||
.WithMany(x => x.Teachers)
|
||||
.HasForeignKey(x => x.TeachingTaskId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(x => x.Teacher)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.TeacherId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<TeachingTaskClass>(entity =>
|
||||
{
|
||||
entity.HasKey(x => new { x.TeachingTaskId, x.AdministrativeClassId });
|
||||
entity.HasOne(x => x.TeachingTask)
|
||||
.WithMany(x => x.Classes)
|
||||
.HasForeignKey(x => x.TeachingTaskId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(x => x.AdministrativeClass)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.AdministrativeClassId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
builder.Entity<AuditLog>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Method).HasMaxLength(10);
|
||||
|
||||
@@ -288,6 +288,107 @@ public sealed class DatabaseInitializer(
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
if (!await db.CurriculumPlans.AnyAsync())
|
||||
{
|
||||
var seededCourses = await db.Courses
|
||||
.Where(x => x.Code == "CS101" || x.Code == "CS201" || x.Code == "CS305")
|
||||
.ToDictionaryAsync(x => x.Code);
|
||||
if (seededCourses.Count == 3)
|
||||
{
|
||||
db.CurriculumPlans.Add(new CurriculumPlan
|
||||
{
|
||||
MajorId = (await db.Majors.SingleAsync(x => x.Code == "080901")).Id,
|
||||
Name = "计算机科学与技术专业培养方案",
|
||||
Version = "2026版",
|
||||
EffectiveGrade = 2026,
|
||||
TotalCredits = 9.5m,
|
||||
Status = CurriculumPlanStatus.Published,
|
||||
PublishedAt = DateTime.UtcNow,
|
||||
Description = "用于本地开发的精简培养方案示例。",
|
||||
Modules =
|
||||
[
|
||||
new CurriculumModule
|
||||
{
|
||||
Code = "BASIC",
|
||||
Name = "专业基础课程",
|
||||
RequiredCredits = 7.5m,
|
||||
SortOrder = 10,
|
||||
Courses =
|
||||
[
|
||||
new CurriculumCourse
|
||||
{
|
||||
CourseId = seededCourses["CS101"].Id,
|
||||
RecommendedSemester = 1,
|
||||
Type = CurriculumCourseType.Required
|
||||
},
|
||||
new CurriculumCourse
|
||||
{
|
||||
CourseId = seededCourses["CS201"].Id,
|
||||
RecommendedSemester = 3,
|
||||
Type = CurriculumCourseType.Required
|
||||
}
|
||||
]
|
||||
},
|
||||
new CurriculumModule
|
||||
{
|
||||
Code = "PRACTICE",
|
||||
Name = "实践教学",
|
||||
RequiredCredits = 2m,
|
||||
SortOrder = 20,
|
||||
Courses =
|
||||
[
|
||||
new CurriculumCourse
|
||||
{
|
||||
CourseId = seededCourses["CS305"].Id,
|
||||
RecommendedSemester = 5,
|
||||
Type = CurriculumCourseType.Required
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
if (!await db.TeachingTasks.AnyAsync())
|
||||
{
|
||||
var term = await db.AcademicTerms.SingleAsync(x => x.IsCurrent);
|
||||
var course = await db.Courses.SingleAsync(x => x.Code == "CS101");
|
||||
var teacher = await db.Teachers.SingleAsync(x => x.TeacherNumber == "T2026001");
|
||||
var administrativeClass = await db.AdministrativeClasses
|
||||
.SingleAsync(x => x.Code == "CS2026-01");
|
||||
db.TeachingTasks.Add(new TeachingTask
|
||||
{
|
||||
TaskNumber = "2026-1-CS101-01",
|
||||
Name = "程序设计基础教学班 01",
|
||||
AcademicTermId = term.Id,
|
||||
CourseId = course.Id,
|
||||
Capacity = 60,
|
||||
StartWeek = 1,
|
||||
EndWeek = 16,
|
||||
WeeklyHours = 4,
|
||||
Status = TeachingTaskStatus.Published,
|
||||
PublishedAt = DateTime.UtcNow,
|
||||
Teachers =
|
||||
[
|
||||
new TeachingTaskTeacher
|
||||
{
|
||||
TeacherId = teacher.Id,
|
||||
IsPrimary = true
|
||||
}
|
||||
],
|
||||
Classes =
|
||||
[
|
||||
new TeachingTaskClass
|
||||
{
|
||||
AdministrativeClassId = administrativeClass.Id
|
||||
}
|
||||
]
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureSucceeded(IdentityResult result, string action)
|
||||
|
||||
@@ -7,6 +7,8 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
ILogger<DevelopmentSqliteMigrator> logger)
|
||||
{
|
||||
private const string PeopleAndCoursesMigration = "20260724_01_people_courses";
|
||||
private const string CurriculumPlansMigration = "20260724_02_curriculum_plans";
|
||||
private const string TeachingTasksMigration = "20260724_03_teaching_tasks";
|
||||
|
||||
public async Task MigrateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -24,12 +26,31 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
""",
|
||||
cancellationToken);
|
||||
|
||||
await ApplyMigrationAsync(
|
||||
PeopleAndCoursesMigration,
|
||||
PeopleAndCoursesStatements,
|
||||
cancellationToken);
|
||||
await ApplyMigrationAsync(
|
||||
CurriculumPlansMigration,
|
||||
CurriculumPlansStatements,
|
||||
cancellationToken);
|
||||
await ApplyMigrationAsync(
|
||||
TeachingTasksMigration,
|
||||
TeachingTasksStatements,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ApplyMigrationAsync(
|
||||
string migrationId,
|
||||
IEnumerable<string> statements,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var applied = await db.Database
|
||||
.SqlQuery<string>(
|
||||
$"""
|
||||
SELECT "MigrationId" AS "Value"
|
||||
FROM "__DevelopmentSchemaHistory"
|
||||
WHERE "MigrationId" = {PeopleAndCoursesMigration}
|
||||
WHERE "MigrationId" = {migrationId}
|
||||
""")
|
||||
.AnyAsync(cancellationToken);
|
||||
if (applied)
|
||||
@@ -38,7 +59,7 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
}
|
||||
|
||||
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
|
||||
foreach (var statement in PeopleAndCoursesStatements)
|
||||
foreach (var statement in statements)
|
||||
{
|
||||
await db.Database.ExecuteSqlRawAsync(statement, cancellationToken);
|
||||
}
|
||||
@@ -46,13 +67,13 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
await db.Database.ExecuteSqlInterpolatedAsync(
|
||||
$"""
|
||||
INSERT INTO "__DevelopmentSchemaHistory" ("MigrationId", "AppliedAt")
|
||||
VALUES ({PeopleAndCoursesMigration}, {DateTime.UtcNow});
|
||||
VALUES ({migrationId}, {DateTime.UtcNow});
|
||||
""",
|
||||
cancellationToken);
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
logger.LogInformation(
|
||||
"Applied SQLite development schema migration {MigrationId}.",
|
||||
PeopleAndCoursesMigration);
|
||||
migrationId);
|
||||
}
|
||||
|
||||
private static readonly string[] PeopleAndCoursesStatements =
|
||||
@@ -166,4 +187,149 @@ public sealed class DevelopmentSqliteMigrator(
|
||||
ON "Courses" ("CollegeId", "Nature");
|
||||
"""
|
||||
];
|
||||
|
||||
private static readonly string[] CurriculumPlansStatements =
|
||||
[
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "CurriculumPlans" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_CurriculumPlans" PRIMARY KEY,
|
||||
"MajorId" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"Version" TEXT NOT NULL,
|
||||
"EffectiveGrade" INTEGER NOT NULL,
|
||||
"TotalCredits" TEXT NOT NULL,
|
||||
"Status" INTEGER NOT NULL,
|
||||
"Description" TEXT NULL,
|
||||
"PublishedAt" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL,
|
||||
"UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_CurriculumPlans_Majors_MajorId"
|
||||
FOREIGN KEY ("MajorId") REFERENCES "Majors" ("Id") ON DELETE RESTRICT
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IX_CurriculumPlans_MajorId_EffectiveGrade_Version"
|
||||
ON "CurriculumPlans" ("MajorId", "EffectiveGrade", "Version");
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_CurriculumPlans_Status_EffectiveGrade"
|
||||
ON "CurriculumPlans" ("Status", "EffectiveGrade");
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "CurriculumModules" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_CurriculumModules" PRIMARY KEY,
|
||||
"CurriculumPlanId" TEXT NOT NULL,
|
||||
"Code" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"RequiredCredits" TEXT NOT NULL,
|
||||
"SortOrder" INTEGER NOT NULL,
|
||||
"CreatedAt" TEXT NOT NULL,
|
||||
"UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_CurriculumModules_CurriculumPlans_CurriculumPlanId"
|
||||
FOREIGN KEY ("CurriculumPlanId") REFERENCES "CurriculumPlans" ("Id")
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IX_CurriculumModules_CurriculumPlanId_Code"
|
||||
ON "CurriculumModules" ("CurriculumPlanId", "Code");
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "CurriculumCourses" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_CurriculumCourses" PRIMARY KEY,
|
||||
"CurriculumModuleId" TEXT NOT NULL,
|
||||
"CourseId" TEXT NOT NULL,
|
||||
"RecommendedSemester" INTEGER NOT NULL,
|
||||
"Type" INTEGER NOT NULL,
|
||||
"Notes" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL,
|
||||
"UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_CurriculumCourses_CurriculumModules_CurriculumModuleId"
|
||||
FOREIGN KEY ("CurriculumModuleId") REFERENCES "CurriculumModules" ("Id")
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_CurriculumCourses_Courses_CourseId"
|
||||
FOREIGN KEY ("CourseId") REFERENCES "Courses" ("Id") ON DELETE RESTRICT
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IX_CurriculumCourses_CurriculumModuleId_CourseId"
|
||||
ON "CurriculumCourses" ("CurriculumModuleId", "CourseId");
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_CurriculumCourses_CourseId"
|
||||
ON "CurriculumCourses" ("CourseId");
|
||||
"""
|
||||
];
|
||||
|
||||
private static readonly string[] TeachingTasksStatements =
|
||||
[
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "TeachingTasks" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_TeachingTasks" PRIMARY KEY,
|
||||
"TaskNumber" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"AcademicTermId" TEXT NOT NULL,
|
||||
"CourseId" TEXT NOT NULL,
|
||||
"Capacity" INTEGER NOT NULL,
|
||||
"StartWeek" INTEGER NOT NULL,
|
||||
"EndWeek" INTEGER NOT NULL,
|
||||
"WeeklyHours" INTEGER NOT NULL,
|
||||
"Status" INTEGER NOT NULL,
|
||||
"Notes" TEXT NULL,
|
||||
"PublishedAt" TEXT NULL,
|
||||
"CreatedAt" TEXT NOT NULL,
|
||||
"UpdatedAt" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_TeachingTasks_AcademicTerms_AcademicTermId"
|
||||
FOREIGN KEY ("AcademicTermId") REFERENCES "AcademicTerms" ("Id") ON DELETE RESTRICT,
|
||||
CONSTRAINT "FK_TeachingTasks_Courses_CourseId"
|
||||
FOREIGN KEY ("CourseId") REFERENCES "Courses" ("Id") ON DELETE RESTRICT
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IX_TeachingTasks_TaskNumber"
|
||||
ON "TeachingTasks" ("TaskNumber");
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_TeachingTasks_AcademicTermId_Status"
|
||||
ON "TeachingTasks" ("AcademicTermId", "Status");
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_TeachingTasks_CourseId"
|
||||
ON "TeachingTasks" ("CourseId");
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "TeachingTaskTeachers" (
|
||||
"TeachingTaskId" TEXT NOT NULL,
|
||||
"TeacherId" TEXT NOT NULL,
|
||||
"IsPrimary" INTEGER NOT NULL,
|
||||
CONSTRAINT "PK_TeachingTaskTeachers"
|
||||
PRIMARY KEY ("TeachingTaskId", "TeacherId"),
|
||||
CONSTRAINT "FK_TeachingTaskTeachers_TeachingTasks_TeachingTaskId"
|
||||
FOREIGN KEY ("TeachingTaskId") REFERENCES "TeachingTasks" ("Id") ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_TeachingTaskTeachers_Teachers_TeacherId"
|
||||
FOREIGN KEY ("TeacherId") REFERENCES "Teachers" ("Id") ON DELETE RESTRICT
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_TeachingTaskTeachers_TeacherId"
|
||||
ON "TeachingTaskTeachers" ("TeacherId");
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "TeachingTaskClasses" (
|
||||
"TeachingTaskId" TEXT NOT NULL,
|
||||
"AdministrativeClassId" TEXT NOT NULL,
|
||||
CONSTRAINT "PK_TeachingTaskClasses"
|
||||
PRIMARY KEY ("TeachingTaskId", "AdministrativeClassId"),
|
||||
CONSTRAINT "FK_TeachingTaskClasses_TeachingTasks_TeachingTaskId"
|
||||
FOREIGN KEY ("TeachingTaskId") REFERENCES "TeachingTasks" ("Id") ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_TeachingTaskClasses_AdministrativeClasses_AdministrativeClassId"
|
||||
FOREIGN KEY ("AdministrativeClassId") REFERENCES "AdministrativeClasses" ("Id")
|
||||
ON DELETE RESTRICT
|
||||
);
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_TeachingTaskClasses_AdministrativeClassId"
|
||||
ON "TeachingTaskClasses" ("AdministrativeClassId");
|
||||
"""
|
||||
];
|
||||
}
|
||||
|
||||
+1165
File diff suppressed because it is too large
Load Diff
+140
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CurriculumPlans : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CurriculumPlans",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
MajorId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false),
|
||||
Version = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false),
|
||||
EffectiveGrade = table.Column<int>(type: "int", nullable: false),
|
||||
TotalCredits = table.Column<decimal>(type: "decimal(6,2)", precision: 6, scale: 2, nullable: false),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
Description = table.Column<string>(type: "varchar(1000)", maxLength: 1000, 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_CurriculumPlans", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CurriculumPlans_Majors_MajorId",
|
||||
column: x => x.MajorId,
|
||||
principalTable: "Majors",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CurriculumModules",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
CurriculumPlanId = 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),
|
||||
RequiredCredits = table.Column<decimal>(type: "decimal(6,2)", precision: 6, scale: 2, 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_CurriculumModules", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CurriculumModules_CurriculumPlans_CurriculumPlanId",
|
||||
column: x => x.CurriculumPlanId,
|
||||
principalTable: "CurriculumPlans",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CurriculumCourses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
CurriculumModuleId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
CourseId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
RecommendedSemester = table.Column<int>(type: "int", nullable: false),
|
||||
Type = table.Column<int>(type: "int", nullable: false),
|
||||
Notes = table.Column<string>(type: "varchar(500)", maxLength: 500, 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_CurriculumCourses", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CurriculumCourses_Courses_CourseId",
|
||||
column: x => x.CourseId,
|
||||
principalTable: "Courses",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_CurriculumCourses_CurriculumModules_CurriculumModuleId",
|
||||
column: x => x.CurriculumModuleId,
|
||||
principalTable: "CurriculumModules",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CurriculumCourses_CourseId",
|
||||
table: "CurriculumCourses",
|
||||
column: "CourseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CurriculumCourses_CurriculumModuleId_CourseId",
|
||||
table: "CurriculumCourses",
|
||||
columns: new[] { "CurriculumModuleId", "CourseId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CurriculumModules_CurriculumPlanId_Code",
|
||||
table: "CurriculumModules",
|
||||
columns: new[] { "CurriculumPlanId", "Code" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CurriculumPlans_MajorId_EffectiveGrade_Version",
|
||||
table: "CurriculumPlans",
|
||||
columns: new[] { "MajorId", "EffectiveGrade", "Version" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CurriculumPlans_Status_EffectiveGrade",
|
||||
table: "CurriculumPlans",
|
||||
columns: new[] { "Status", "EffectiveGrade" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CurriculumCourses");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CurriculumModules");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CurriculumPlans");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1329
File diff suppressed because it is too large
Load Diff
+142
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class TeachingTasks : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TeachingTasks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
TaskNumber = table.Column<string>(type: "varchar(40)", maxLength: 40, nullable: false),
|
||||
Name = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false),
|
||||
AcademicTermId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
CourseId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Capacity = table.Column<int>(type: "int", nullable: false),
|
||||
StartWeek = table.Column<int>(type: "int", nullable: false),
|
||||
EndWeek = table.Column<int>(type: "int", nullable: false),
|
||||
WeeklyHours = 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),
|
||||
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_TeachingTasks", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TeachingTasks_AcademicTerms_AcademicTermId",
|
||||
column: x => x.AcademicTermId,
|
||||
principalTable: "AcademicTerms",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_TeachingTasks_Courses_CourseId",
|
||||
column: x => x.CourseId,
|
||||
principalTable: "Courses",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TeachingTaskClasses",
|
||||
columns: table => new
|
||||
{
|
||||
TeachingTaskId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
AdministrativeClassId = table.Column<Guid>(type: "char(36)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TeachingTaskClasses", x => new { x.TeachingTaskId, x.AdministrativeClassId });
|
||||
table.ForeignKey(
|
||||
name: "FK_TeachingTaskClasses_AdministrativeClasses_AdministrativeClas~",
|
||||
column: x => x.AdministrativeClassId,
|
||||
principalTable: "AdministrativeClasses",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_TeachingTaskClasses_TeachingTasks_TeachingTaskId",
|
||||
column: x => x.TeachingTaskId,
|
||||
principalTable: "TeachingTasks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TeachingTaskTeachers",
|
||||
columns: table => new
|
||||
{
|
||||
TeachingTaskId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
TeacherId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
IsPrimary = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TeachingTaskTeachers", x => new { x.TeachingTaskId, x.TeacherId });
|
||||
table.ForeignKey(
|
||||
name: "FK_TeachingTaskTeachers_Teachers_TeacherId",
|
||||
column: x => x.TeacherId,
|
||||
principalTable: "Teachers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_TeachingTaskTeachers_TeachingTasks_TeachingTaskId",
|
||||
column: x => x.TeachingTaskId,
|
||||
principalTable: "TeachingTasks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TeachingTaskClasses_AdministrativeClassId",
|
||||
table: "TeachingTaskClasses",
|
||||
column: "AdministrativeClassId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TeachingTasks_AcademicTermId_Status",
|
||||
table: "TeachingTasks",
|
||||
columns: new[] { "AcademicTermId", "Status" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TeachingTasks_CourseId",
|
||||
table: "TeachingTasks",
|
||||
column: "CourseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TeachingTasks_TaskNumber",
|
||||
table: "TeachingTasks",
|
||||
column: "TaskNumber",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TeachingTaskTeachers_TeacherId",
|
||||
table: "TeachingTaskTeachers",
|
||||
column: "TeacherId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "TeachingTaskClasses");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TeachingTaskTeachers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TeachingTasks");
|
||||
}
|
||||
}
|
||||
}
|
||||
+357
-12
@@ -72,7 +72,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("AcademicTerms", (string)null);
|
||||
b.ToTable("AcademicTerms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AdministrativeClass", b =>
|
||||
@@ -121,7 +121,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("AdministrativeClasses", (string)null);
|
||||
b.ToTable("AdministrativeClasses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Building", b =>
|
||||
@@ -164,7 +164,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("Buildings", (string)null);
|
||||
b.ToTable("Buildings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Campus", b =>
|
||||
@@ -205,7 +205,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("Campuses", (string)null);
|
||||
b.ToTable("Campuses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Classroom", b =>
|
||||
@@ -258,7 +258,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("Classrooms", (string)null);
|
||||
b.ToTable("Classrooms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.College", b =>
|
||||
@@ -304,7 +304,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("Colleges", (string)null);
|
||||
b.ToTable("Colleges");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Course", b =>
|
||||
@@ -374,7 +374,137 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("Courses", (string)null);
|
||||
b.ToTable("Courses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumCourse", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("CourseId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("CurriculumModuleId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("varchar(500)");
|
||||
|
||||
b.Property<int>("RecommendedSemester")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CourseId");
|
||||
|
||||
b.HasIndex("CurriculumModuleId", "CourseId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CurriculumCourses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumModule", 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>("CurriculumPlanId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<decimal>("RequiredCredits")
|
||||
.HasPrecision(6, 2)
|
||||
.HasColumnType("decimal(6,2)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CurriculumPlanId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CurriculumModules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumPlan", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<int>("EffectiveGrade")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("MajorId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<DateTime?>("PublishedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("TotalCredits")
|
||||
.HasPrecision(6, 2)
|
||||
.HasColumnType("decimal(6,2)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status", "EffectiveGrade");
|
||||
|
||||
b.HasIndex("MajorId", "EffectiveGrade", "Version")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CurriculumPlans");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b =>
|
||||
@@ -424,7 +554,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("IsEnabled", "SortOrder");
|
||||
|
||||
b.ToTable("Majors", (string)null);
|
||||
b.ToTable("Majors");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Student", b =>
|
||||
@@ -493,7 +623,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("AdministrativeClassId", "Status");
|
||||
|
||||
b.ToTable("Students", (string)null);
|
||||
b.ToTable("Students");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Teacher", b =>
|
||||
@@ -561,7 +691,102 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("CollegeId", "Status");
|
||||
|
||||
b.ToTable("Teachers", (string)null);
|
||||
b.ToTable("Teachers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTask", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("AcademicTermId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("Capacity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("CourseId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("EndWeek")
|
||||
.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>("StartWeek")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TaskNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int>("WeeklyHours")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CourseId");
|
||||
|
||||
b.HasIndex("TaskNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("AcademicTermId", "Status");
|
||||
|
||||
b.ToTable("TeachingTasks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskClass", b =>
|
||||
{
|
||||
b.Property<Guid>("TeachingTaskId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("AdministrativeClassId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("TeachingTaskId", "AdministrativeClassId");
|
||||
|
||||
b.HasIndex("AdministrativeClassId");
|
||||
|
||||
b.ToTable("TeachingTaskClasses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskTeacher", b =>
|
||||
{
|
||||
b.Property<Guid>("TeachingTaskId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("TeacherId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsPrimary")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("TeachingTaskId", "TeacherId");
|
||||
|
||||
b.HasIndex("TeacherId");
|
||||
|
||||
b.ToTable("TeachingTaskTeachers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Identity.ApplicationRole", b =>
|
||||
@@ -726,7 +951,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.ToTable("AuditLogs", (string)null);
|
||||
b.ToTable("AuditLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
@@ -882,6 +1107,47 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("College");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumCourse", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Course", "Course")
|
||||
.WithMany()
|
||||
.HasForeignKey("CourseId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.CurriculumModule", "CurriculumModule")
|
||||
.WithMany("Courses")
|
||||
.HasForeignKey("CurriculumModuleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Course");
|
||||
|
||||
b.Navigation("CurriculumModule");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumModule", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.CurriculumPlan", "CurriculumPlan")
|
||||
.WithMany("Modules")
|
||||
.HasForeignKey("CurriculumPlanId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("CurriculumPlan");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumPlan", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Major", "Major")
|
||||
.WithMany()
|
||||
.HasForeignKey("MajorId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Major");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.College", "College")
|
||||
@@ -896,7 +1162,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Student", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.AdministrativeClass", "AdministrativeClass")
|
||||
.WithMany()
|
||||
.WithMany("Students")
|
||||
.HasForeignKey("AdministrativeClassId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
@@ -925,6 +1191,63 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
b.Navigation("College");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTask", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm")
|
||||
.WithMany()
|
||||
.HasForeignKey("AcademicTermId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Course", "Course")
|
||||
.WithMany()
|
||||
.HasForeignKey("CourseId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AcademicTerm");
|
||||
|
||||
b.Navigation("Course");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskClass", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.AdministrativeClass", "AdministrativeClass")
|
||||
.WithMany()
|
||||
.HasForeignKey("AdministrativeClassId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask")
|
||||
.WithMany("Classes")
|
||||
.HasForeignKey("TeachingTaskId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AdministrativeClass");
|
||||
|
||||
b.Navigation("TeachingTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskTeacher", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.Teacher", "Teacher")
|
||||
.WithMany()
|
||||
.HasForeignKey("TeacherId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask")
|
||||
.WithMany("Teachers")
|
||||
.HasForeignKey("TeachingTaskId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Teacher");
|
||||
|
||||
b.Navigation("TeachingTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationRole", null)
|
||||
@@ -975,6 +1298,28 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AdministrativeClass", b =>
|
||||
{
|
||||
b.Navigation("Students");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumModule", b =>
|
||||
{
|
||||
b.Navigation("Courses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumPlan", b =>
|
||||
{
|
||||
b.Navigation("Modules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTask", b =>
|
||||
{
|
||||
b.Navigation("Classes");
|
||||
|
||||
b.Navigation("Teachers");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user