已改成后台“检查并发布”任务,原来的超时主要就是同步逐条校验导致的。
发布接口立即返回 202 Accepted,页面轮询显示检查进度。 校验改为批量读取,减少重复数据库查询。 检查失败保留草稿并显示具体原因;成功后原子完成旧课表归档和新课表发布。 同一学期禁止两个发布任务并发,服务重启后未完成任务会自动恢复。 发布期间锁定编辑、自动排课等冲突操作。 已补齐 SQLite 开发迁移及 MySQL 生产迁移。
This commit is contained in:
1 parent
fdca6a3edf
commit
b015514115
12 files changed
+4116
-81
No files matched your search
@@ -16,7 +16,8 @@ namespace Jiaowu.Api.Controllers;
|
|||||||
[Route("api/schedules")]
|
[Route("api/schedules")]
|
||||||
public sealed class SchedulesController(
|
public sealed class SchedulesController(
|
||||||
AppDbContext db,
|
AppDbContext db,
|
||||||
AutomaticScheduleJobQueue automaticScheduleJobQueue) : ControllerBase
|
AutomaticScheduleJobQueue automaticScheduleJobQueue,
|
||||||
|
SchedulePublishJobQueue schedulePublishJobQueue) : ControllerBase
|
||||||
{
|
{
|
||||||
private const string ManagementRoles =
|
private const string ManagementRoles =
|
||||||
SystemRoles.SuperAdmin + "," +
|
SystemRoles.SuperAdmin + "," +
|
||||||
@@ -130,8 +131,8 @@ public sealed class SchedulesController(
|
|||||||
{
|
{
|
||||||
var plan = await db.SchedulePlans.FindAsync([id], cancellationToken);
|
var plan = await db.SchedulePlans.FindAsync([id], cancellationToken);
|
||||||
if (plan is null) return NotFound();
|
if (plan is null) return NotFound();
|
||||||
if (await HasActiveAutomaticScheduleJobAsync(id, cancellationToken))
|
if (await HasActiveScheduleJobAsync(id, cancellationToken))
|
||||||
return AutomaticScheduleRunningProblem();
|
return ScheduleJobRunningProblem();
|
||||||
if (plan.Status != SchedulePlanStatus.Draft)
|
if (plan.Status != SchedulePlanStatus.Draft)
|
||||||
return ConflictProblem("已发布或已归档的排课版本不可直接修改。");
|
return ConflictProblem("已发布或已归档的排课版本不可直接修改。");
|
||||||
if (!await db.AcademicTerms.AnyAsync(
|
if (!await db.AcademicTerms.AnyAsync(
|
||||||
@@ -151,8 +152,8 @@ public sealed class SchedulesController(
|
|||||||
CloneSchedulePlanRequest request,
|
CloneSchedulePlanRequest request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (await HasActiveAutomaticScheduleJobAsync(id, cancellationToken))
|
if (await HasActiveScheduleJobAsync(id, cancellationToken))
|
||||||
return AutomaticScheduleRunningProblem();
|
return ScheduleJobRunningProblem();
|
||||||
var source = await db.SchedulePlans.AsNoTracking()
|
var source = await db.SchedulePlans.AsNoTracking()
|
||||||
.Include(x => x.Entries)
|
.Include(x => x.Entries)
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
@@ -185,8 +186,8 @@ public sealed class SchedulesController(
|
|||||||
{
|
{
|
||||||
var plan = await db.SchedulePlans.FindAsync([id], cancellationToken);
|
var plan = await db.SchedulePlans.FindAsync([id], cancellationToken);
|
||||||
if (plan is null) return NotFound();
|
if (plan is null) return NotFound();
|
||||||
if (await HasActiveAutomaticScheduleJobAsync(id, cancellationToken))
|
if (await HasActiveScheduleJobAsync(id, cancellationToken))
|
||||||
return AutomaticScheduleRunningProblem();
|
return ScheduleJobRunningProblem();
|
||||||
if (plan.Status != SchedulePlanStatus.Draft)
|
if (plan.Status != SchedulePlanStatus.Draft)
|
||||||
return ConflictProblem("仅草稿排课版本可以删除。");
|
return ConflictProblem("仅草稿排课版本可以删除。");
|
||||||
db.SchedulePlans.Remove(plan);
|
db.SchedulePlans.Remove(plan);
|
||||||
@@ -194,76 +195,79 @@ public sealed class SchedulesController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("plans/{id:guid}/publish")]
|
[HttpPost("plans/{id:guid}/publish")]
|
||||||
public async Task<ActionResult> PublishPlan(Guid id, CancellationToken cancellationToken)
|
public async Task<ActionResult<SchedulePublishJobResponse>> PublishPlan(
|
||||||
|
Guid id,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (await HasActiveAutomaticScheduleJobAsync(id, cancellationToken))
|
if (await HasActiveAutomaticScheduleJobAsync(id, cancellationToken))
|
||||||
return AutomaticScheduleRunningProblem();
|
return AutomaticScheduleRunningProblem();
|
||||||
var plan = await db.SchedulePlans
|
var plan = await db.SchedulePlans.AsNoTracking()
|
||||||
.Include(x => x.Entries)
|
.Select(x => new
|
||||||
.ThenInclude(x => x.TeachingTask)
|
{
|
||||||
.ThenInclude(x => x!.Teachers)
|
x.Id,
|
||||||
.Include(x => x.Entries)
|
x.AcademicTermId,
|
||||||
.ThenInclude(x => x.TeachingTask)
|
x.Status,
|
||||||
.ThenInclude(x => x!.Classes)
|
HasEntries = x.Entries.Any()
|
||||||
|
})
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (plan is null) return NotFound();
|
if (plan is null) return NotFound();
|
||||||
if (plan.Status != SchedulePlanStatus.Draft)
|
if (plan.Status != SchedulePlanStatus.Draft)
|
||||||
return ConflictProblem("只有草稿排课版本可以发布。");
|
return ConflictProblem("只有草稿排课版本可以发布。");
|
||||||
if (plan.Entries.Count == 0)
|
if (!plan.HasEntries)
|
||||||
return ConflictProblem("排课版本中至少需要一条课表安排。");
|
return ConflictProblem("排课版本中至少需要一条课表安排。");
|
||||||
|
|
||||||
foreach (var entry in plan.Entries)
|
var existing = await db.SchedulePublishJobs.AsNoTracking()
|
||||||
{
|
.FirstOrDefaultAsync(
|
||||||
var validation = await ValidateEntryAsync(
|
x => x.ActiveAcademicTermId == plan.AcademicTermId,
|
||||||
plan,
|
|
||||||
entry.Id,
|
|
||||||
new ScheduleEntryRequest(
|
|
||||||
entry.TeachingTaskId,
|
|
||||||
entry.ClassroomId,
|
|
||||||
entry.DayOfWeek,
|
|
||||||
entry.StartPeriod,
|
|
||||||
entry.PeriodCount,
|
|
||||||
entry.StartWeek,
|
|
||||||
entry.EndWeek,
|
|
||||||
entry.WeekPattern,
|
|
||||||
entry.Notes),
|
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
if (validation is not null) return validation;
|
if (existing is not null)
|
||||||
|
{
|
||||||
|
if (existing.SchedulePlanId != id)
|
||||||
|
return ConflictProblem(
|
||||||
|
"同一学期已有课表正在后台检查并发布,请等待任务完成。");
|
||||||
|
return AcceptedAtAction(
|
||||||
|
nameof(GetSchedulePublishJob),
|
||||||
|
new { jobId = existing.Id },
|
||||||
|
ToResponse(existing));
|
||||||
}
|
}
|
||||||
|
|
||||||
var requiredTasks = await db.TeachingTasks.AsNoTracking()
|
var requestedByUserId = CurrentUserId();
|
||||||
.Where(x =>
|
var job = new SchedulePublishJob
|
||||||
x.AcademicTermId == plan.AcademicTermId &&
|
{
|
||||||
x.Status == TeachingTaskStatus.Published &&
|
SchedulePlanId = id,
|
||||||
x.SchedulingMode == TeachingTaskSchedulingMode.Standard)
|
AcademicTermId = plan.AcademicTermId,
|
||||||
.Select(x => new { x.Id, x.TaskNumber, x.Name, x.WeeklyHours })
|
ActiveAcademicTermId = plan.AcademicTermId,
|
||||||
.ToListAsync(cancellationToken);
|
RequestedByUserId = requestedByUserId,
|
||||||
var scheduledHours = plan.Entries
|
CurrentStep = "等待后台检查"
|
||||||
.GroupBy(x => x.TeachingTaskId)
|
};
|
||||||
.ToDictionary(group => group.Key, group => group.Sum(x => x.PeriodCount));
|
db.SchedulePublishJobs.Add(job);
|
||||||
var incomplete = requiredTasks.FirstOrDefault(task =>
|
try
|
||||||
!scheduledHours.TryGetValue(task.Id, out var hours) ||
|
{
|
||||||
hours < task.WeeklyHours);
|
|
||||||
if (incomplete is not null)
|
|
||||||
return ConflictProblem(
|
|
||||||
$"{incomplete.TaskNumber} · {incomplete.Name} 尚未达到每周 {incomplete.WeeklyHours} 学时,不能发布。");
|
|
||||||
|
|
||||||
var conflict = ScheduleConflictDetector.FindConflict(plan.Entries.ToList());
|
|
||||||
if (conflict is not null) return ConflictProblem(conflict);
|
|
||||||
|
|
||||||
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
|
|
||||||
var previous = await db.SchedulePlans
|
|
||||||
.Where(x =>
|
|
||||||
x.Id != plan.Id &&
|
|
||||||
x.AcademicTermId == plan.AcademicTermId &&
|
|
||||||
x.Status == SchedulePlanStatus.Published)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
foreach (var oldPlan in previous) oldPlan.Status = SchedulePlanStatus.Archived;
|
|
||||||
plan.Status = SchedulePlanStatus.Published;
|
|
||||||
plan.PublishedAt = DateTime.UtcNow;
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
await transaction.CommitAsync(cancellationToken);
|
}
|
||||||
return NoContent();
|
catch (DbUpdateException)
|
||||||
|
{
|
||||||
|
db.Entry(job).State = EntityState.Detached;
|
||||||
|
existing = await db.SchedulePublishJobs.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(
|
||||||
|
x => x.ActiveAcademicTermId == plan.AcademicTermId,
|
||||||
|
cancellationToken);
|
||||||
|
if (existing is null)
|
||||||
|
throw;
|
||||||
|
if (existing.SchedulePlanId != id)
|
||||||
|
return ConflictProblem(
|
||||||
|
"同一学期已有课表正在后台检查并发布,请等待任务完成。");
|
||||||
|
return AcceptedAtAction(
|
||||||
|
nameof(GetSchedulePublishJob),
|
||||||
|
new { jobId = existing.Id },
|
||||||
|
ToResponse(existing));
|
||||||
|
}
|
||||||
|
|
||||||
|
schedulePublishJobQueue.Enqueue(job.Id);
|
||||||
|
return AcceptedAtAction(
|
||||||
|
nameof(GetSchedulePublishJob),
|
||||||
|
new { jobId = job.Id },
|
||||||
|
ToResponse(job));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("plans/{planId:guid}/entries")]
|
[HttpPost("plans/{planId:guid}/entries")]
|
||||||
@@ -272,8 +276,8 @@ public sealed class SchedulesController(
|
|||||||
ScheduleEntryRequest request,
|
ScheduleEntryRequest request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (await HasActiveAutomaticScheduleJobAsync(planId, cancellationToken))
|
if (await HasActiveScheduleJobAsync(planId, cancellationToken))
|
||||||
return AutomaticScheduleRunningProblem();
|
return ScheduleJobRunningProblem();
|
||||||
var plan = await DraftPlanAsync(planId, cancellationToken);
|
var plan = await DraftPlanAsync(planId, cancellationToken);
|
||||||
if (plan is null) return NotFound();
|
if (plan is null) return NotFound();
|
||||||
var validation = await ValidateEntryAsync(plan, null, request, cancellationToken);
|
var validation = await ValidateEntryAsync(plan, null, request, cancellationToken);
|
||||||
@@ -290,6 +294,8 @@ public sealed class SchedulesController(
|
|||||||
{
|
{
|
||||||
var plan = await DraftPlanAsync(planId, cancellationToken);
|
var plan = await DraftPlanAsync(planId, cancellationToken);
|
||||||
if (plan is null) return NotFound();
|
if (plan is null) return NotFound();
|
||||||
|
if (await HasActiveSchedulePublishJobAsync(planId, cancellationToken))
|
||||||
|
return SchedulePublishRunningProblem();
|
||||||
|
|
||||||
var existing = await db.AutomaticScheduleJobs.AsNoTracking()
|
var existing = await db.AutomaticScheduleJobs.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(
|
.FirstOrDefaultAsync(
|
||||||
@@ -364,6 +370,30 @@ public sealed class SchedulesController(
|
|||||||
return Ok(job is null ? null : ToResponse(job));
|
return Ok(job is null ? null : ToResponse(job));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("publish-jobs/{jobId:guid}")]
|
||||||
|
public async Task<ActionResult<SchedulePublishJobResponse>>
|
||||||
|
GetSchedulePublishJob(
|
||||||
|
Guid jobId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var job = await db.SchedulePublishJobs.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == jobId, cancellationToken);
|
||||||
|
return job is null ? NotFound() : Ok(ToResponse(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("plans/{planId:guid}/publish-job")]
|
||||||
|
public async Task<ActionResult<SchedulePublishJobResponse?>>
|
||||||
|
GetLatestSchedulePublishJob(
|
||||||
|
Guid planId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var job = await db.SchedulePublishJobs.AsNoTracking()
|
||||||
|
.Where(x => x.SchedulePlanId == planId)
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return Ok(job is null ? null : ToResponse(job));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPut("plans/{planId:guid}/entries/{entryId:guid}")]
|
[HttpPut("plans/{planId:guid}/entries/{entryId:guid}")]
|
||||||
public async Task<ActionResult> UpdateEntry(
|
public async Task<ActionResult> UpdateEntry(
|
||||||
Guid planId,
|
Guid planId,
|
||||||
@@ -371,8 +401,8 @@ public sealed class SchedulesController(
|
|||||||
ScheduleEntryRequest request,
|
ScheduleEntryRequest request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (await HasActiveAutomaticScheduleJobAsync(planId, cancellationToken))
|
if (await HasActiveScheduleJobAsync(planId, cancellationToken))
|
||||||
return AutomaticScheduleRunningProblem();
|
return ScheduleJobRunningProblem();
|
||||||
var plan = await DraftPlanAsync(planId, cancellationToken);
|
var plan = await DraftPlanAsync(planId, cancellationToken);
|
||||||
if (plan is null) return NotFound();
|
if (plan is null) return NotFound();
|
||||||
var entry = await db.ScheduleEntries
|
var entry = await db.ScheduleEntries
|
||||||
@@ -400,8 +430,8 @@ public sealed class SchedulesController(
|
|||||||
Guid entryId,
|
Guid entryId,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (await HasActiveAutomaticScheduleJobAsync(planId, cancellationToken))
|
if (await HasActiveScheduleJobAsync(planId, cancellationToken))
|
||||||
return AutomaticScheduleRunningProblem();
|
return ScheduleJobRunningProblem();
|
||||||
if (await DraftPlanAsync(planId, cancellationToken) is null) return NotFound();
|
if (await DraftPlanAsync(planId, cancellationToken) is null) return NotFound();
|
||||||
var entry = await db.ScheduleEntries
|
var entry = await db.ScheduleEntries
|
||||||
.FirstOrDefaultAsync(
|
.FirstOrDefaultAsync(
|
||||||
@@ -426,6 +456,19 @@ public sealed class SchedulesController(
|
|||||||
x => x.ActiveSchedulePlanId == planId,
|
x => x.ActiveSchedulePlanId == planId,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
private Task<bool> HasActiveSchedulePublishJobAsync(
|
||||||
|
Guid planId,
|
||||||
|
CancellationToken cancellationToken) =>
|
||||||
|
db.SchedulePublishJobs.AsNoTracking().AnyAsync(
|
||||||
|
x => x.SchedulePlanId == planId && x.ActiveAcademicTermId != null,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
private async Task<bool> HasActiveScheduleJobAsync(
|
||||||
|
Guid planId,
|
||||||
|
CancellationToken cancellationToken) =>
|
||||||
|
await HasActiveAutomaticScheduleJobAsync(planId, cancellationToken) ||
|
||||||
|
await HasActiveSchedulePublishJobAsync(planId, cancellationToken);
|
||||||
|
|
||||||
private async Task<ActionResult?> ValidateEntryAsync(
|
private async Task<ActionResult?> ValidateEntryAsync(
|
||||||
SchedulePlan plan,
|
SchedulePlan plan,
|
||||||
Guid? entryId,
|
Guid? entryId,
|
||||||
@@ -585,6 +628,12 @@ public sealed class SchedulesController(
|
|||||||
private ActionResult AutomaticScheduleRunningProblem() =>
|
private ActionResult AutomaticScheduleRunningProblem() =>
|
||||||
ConflictProblem("自动排课正在后台运行,请等待任务完成后再修改该排课版本。");
|
ConflictProblem("自动排课正在后台运行,请等待任务完成后再修改该排课版本。");
|
||||||
|
|
||||||
|
private ActionResult SchedulePublishRunningProblem() =>
|
||||||
|
ConflictProblem("课表正在后台检查并发布,请等待任务完成后再修改。");
|
||||||
|
|
||||||
|
private ActionResult ScheduleJobRunningProblem() =>
|
||||||
|
ConflictProblem("后台任务正在处理该排课版本,请等待任务完成后再修改。");
|
||||||
|
|
||||||
private static AutomaticScheduleJobResponse ToResponse(AutomaticScheduleJob job)
|
private static AutomaticScheduleJobResponse ToResponse(AutomaticScheduleJob job)
|
||||||
{
|
{
|
||||||
IReadOnlyList<string> messages = [];
|
IReadOnlyList<string> messages = [];
|
||||||
@@ -608,6 +657,24 @@ public sealed class SchedulesController(
|
|||||||
job.CompletedAt);
|
job.CompletedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static SchedulePublishJobResponse ToResponse(SchedulePublishJob job) =>
|
||||||
|
new(
|
||||||
|
job.Id,
|
||||||
|
job.SchedulePlanId,
|
||||||
|
job.Status,
|
||||||
|
job.TotalSteps,
|
||||||
|
job.CompletedSteps,
|
||||||
|
job.CurrentStep,
|
||||||
|
job.ErrorMessage,
|
||||||
|
job.CreatedAt,
|
||||||
|
job.StartedAt,
|
||||||
|
job.CompletedAt);
|
||||||
|
|
||||||
|
private Guid? CurrentUserId() =>
|
||||||
|
Guid.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out var userId)
|
||||||
|
? userId
|
||||||
|
: null;
|
||||||
|
|
||||||
private static string? Normalize(string? value) =>
|
private static string? Normalize(string? value) =>
|
||||||
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||||
}
|
}
|
||||||
@@ -646,3 +713,15 @@ public sealed record AutomaticScheduleJobResponse(
|
|||||||
DateTime CreatedAt,
|
DateTime CreatedAt,
|
||||||
DateTime? StartedAt,
|
DateTime? StartedAt,
|
||||||
DateTime? CompletedAt);
|
DateTime? CompletedAt);
|
||||||
|
|
||||||
|
public sealed record SchedulePublishJobResponse(
|
||||||
|
Guid Id,
|
||||||
|
Guid SchedulePlanId,
|
||||||
|
SchedulePublishJobStatus Status,
|
||||||
|
int TotalSteps,
|
||||||
|
int CompletedSteps,
|
||||||
|
string? CurrentStep,
|
||||||
|
string? ErrorMessage,
|
||||||
|
DateTime CreatedAt,
|
||||||
|
DateTime? StartedAt,
|
||||||
|
DateTime? CompletedAt);
|
||||||
@@ -83,6 +83,23 @@ public sealed class AutomaticScheduleJob : EntityBase
|
|||||||
public DateTime? CompletedAt { get; set; }
|
public DateTime? CompletedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class SchedulePublishJob : EntityBase
|
||||||
|
{
|
||||||
|
public Guid SchedulePlanId { get; set; }
|
||||||
|
public SchedulePlan? SchedulePlan { get; set; }
|
||||||
|
public Guid AcademicTermId { get; set; }
|
||||||
|
public Guid? ActiveAcademicTermId { get; set; }
|
||||||
|
public Guid? RequestedByUserId { get; set; }
|
||||||
|
public SchedulePublishJobStatus Status { get; set; } =
|
||||||
|
SchedulePublishJobStatus.Queued;
|
||||||
|
public int TotalSteps { get; set; } = 5;
|
||||||
|
public int CompletedSteps { get; set; }
|
||||||
|
public string? CurrentStep { get; set; }
|
||||||
|
public string? ErrorMessage { get; set; }
|
||||||
|
public DateTime? StartedAt { get; set; }
|
||||||
|
public DateTime? CompletedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public enum SchedulePlanStatus
|
public enum SchedulePlanStatus
|
||||||
{
|
{
|
||||||
Draft = 1,
|
Draft = 1,
|
||||||
@@ -98,6 +115,14 @@ public enum AutomaticScheduleJobStatus
|
|||||||
Failed = 4
|
Failed = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SchedulePublishJobStatus
|
||||||
|
{
|
||||||
|
Queued = 1,
|
||||||
|
Running = 2,
|
||||||
|
Succeeded = 3,
|
||||||
|
Failed = 4
|
||||||
|
}
|
||||||
|
|
||||||
public enum WeekPattern
|
public enum WeekPattern
|
||||||
{
|
{
|
||||||
All = 1,
|
All = 1,
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
|||||||
Set<TeachingTaskAllowedClassroom>();
|
Set<TeachingTaskAllowedClassroom>();
|
||||||
public DbSet<AutomaticScheduleJob> AutomaticScheduleJobs =>
|
public DbSet<AutomaticScheduleJob> AutomaticScheduleJobs =>
|
||||||
Set<AutomaticScheduleJob>();
|
Set<AutomaticScheduleJob>();
|
||||||
|
public DbSet<SchedulePublishJob> SchedulePublishJobs =>
|
||||||
|
Set<SchedulePublishJob>();
|
||||||
public DbSet<CourseSelectionRound> CourseSelectionRounds =>
|
public DbSet<CourseSelectionRound> CourseSelectionRounds =>
|
||||||
Set<CourseSelectionRound>();
|
Set<CourseSelectionRound>();
|
||||||
public DbSet<CourseSelectionOffering> CourseSelectionOfferings =>
|
public DbSet<CourseSelectionOffering> CourseSelectionOfferings =>
|
||||||
@@ -400,6 +402,24 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
|||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Entity<SchedulePublishJob>(entity =>
|
||||||
|
{
|
||||||
|
entity.Property(x => x.CurrentStep).HasMaxLength(200);
|
||||||
|
entity.Property(x => x.ErrorMessage).HasMaxLength(2000);
|
||||||
|
entity.HasIndex(x => x.ActiveAcademicTermId).IsUnique();
|
||||||
|
entity.HasIndex(x => new { x.SchedulePlanId, x.CreatedAt });
|
||||||
|
entity.HasIndex(x => new { x.Status, x.CreatedAt });
|
||||||
|
entity.HasIndex(x => x.RequestedByUserId);
|
||||||
|
entity.HasOne(x => x.SchedulePlan)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(x => x.SchedulePlanId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
entity.HasOne<ApplicationUser>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(x => x.RequestedByUserId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
builder.Entity<CourseSelectionRound>(entity =>
|
builder.Entity<CourseSelectionRound>(entity =>
|
||||||
{
|
{
|
||||||
entity.Property(x => x.Name).HasMaxLength(120);
|
entity.Property(x => x.Name).HasMaxLength(120);
|
||||||
|
|||||||
Loaded 3 of 12 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user