学年学期页面新增“设为当前”“归档”“撤销归档”,当前学期不能归档、删除或直接取消;已归档学期需先撤销才能重新设为当前。[BaseDataController.cs (line 286)](/E:/jiaowu/src/Jiaowu.Api/Controllers/BaseDataController.cs:286)
切换后,其他学期自动变为历史态;已归档学期进一步弱化。所有主要学期选择器统一默认当前,并显示“当前 / 历史 / 已归档”。[academicTerms.ts (line 1)](/E:/jiaowu/web/src/utils/academicTerms.ts:1) 桌面使用分层表格,390px 手机端改为独立学期卡片,操作不会再挤压表格。[BaseDataView.vue (line 211)](/E:/jiaowu/web/src/views/BaseDataView.vue:211) 归档定义为“历史显示状态”,不是成绩冻结:补考成绩仍可录入并回写,已发布成绩仍可走成绩更正审批。 已加入 SQLite 开发迁移、MySQL 正式迁移和切换/归档/撤销测试。[20260726143000_AcademicTermArchiving.cs (line 1)](/E:/jiaowu/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260726143000_AcademicTermArchiving.cs:1)
This commit is contained in:
1 parent
3b73292d93
commit
c91d826169
32 files changed
+752
-73
No files matched your search
@@ -287,7 +287,9 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
|
|||||||
public async Task<ActionResult<IReadOnlyCollection<AcademicTerm>>> GetTerms(
|
public async Task<ActionResult<IReadOnlyCollection<AcademicTerm>>> GetTerms(
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
await db.AcademicTerms.AsNoTracking()
|
await db.AcademicTerms.AsNoTracking()
|
||||||
.OrderByDescending(x => x.StartDate)
|
.OrderByDescending(x => x.IsCurrent)
|
||||||
|
.ThenBy(x => x.IsArchived)
|
||||||
|
.ThenByDescending(x => x.StartDate)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
[HttpPost("terms")]
|
[HttpPost("terms")]
|
||||||
@@ -298,11 +300,20 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
|
|||||||
{
|
{
|
||||||
if (request.EndDate <= request.StartDate)
|
if (request.EndDate <= request.StartDate)
|
||||||
return ValidationProblem("学期结束日期必须晚于开始日期。");
|
return ValidationProblem("学期结束日期必须晚于开始日期。");
|
||||||
|
if (request.IsCurrent && !request.IsEnabled)
|
||||||
|
return ValidationProblem("当前学期必须保持启用。");
|
||||||
|
|
||||||
if (request.IsCurrent)
|
var hasCurrentTerm = await db.AcademicTerms
|
||||||
await db.AcademicTerms.ExecuteUpdateAsync(
|
.AnyAsync(x => x.IsCurrent, cancellationToken);
|
||||||
setters => setters.SetProperty(x => x.IsCurrent, false),
|
var shouldBeCurrent = request.IsCurrent || (!hasCurrentTerm && request.IsEnabled);
|
||||||
cancellationToken);
|
if (shouldBeCurrent)
|
||||||
|
{
|
||||||
|
var currentTerms = await db.AcademicTerms
|
||||||
|
.Where(x => x.IsCurrent)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
foreach (var currentTerm in currentTerms)
|
||||||
|
currentTerm.IsCurrent = false;
|
||||||
|
}
|
||||||
|
|
||||||
var entity = new AcademicTerm
|
var entity = new AcademicTerm
|
||||||
{
|
{
|
||||||
@@ -312,7 +323,7 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
|
|||||||
Season = request.Season,
|
Season = request.Season,
|
||||||
StartDate = request.StartDate,
|
StartDate = request.StartDate,
|
||||||
EndDate = request.EndDate,
|
EndDate = request.EndDate,
|
||||||
IsCurrent = request.IsCurrent,
|
IsCurrent = shouldBeCurrent,
|
||||||
IsEnabled = request.IsEnabled
|
IsEnabled = request.IsEnabled
|
||||||
};
|
};
|
||||||
return await CreateAsync(entity, "GetTerms", cancellationToken);
|
return await CreateAsync(entity, "GetTerms", cancellationToken);
|
||||||
@@ -327,10 +338,22 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
|
|||||||
{
|
{
|
||||||
var entity = await db.AcademicTerms.FindAsync([id], cancellationToken);
|
var entity = await db.AcademicTerms.FindAsync([id], cancellationToken);
|
||||||
if (entity is null) return NotFound();
|
if (entity is null) return NotFound();
|
||||||
|
if (request.EndDate <= request.StartDate)
|
||||||
|
return ValidationProblem("学期结束日期必须晚于开始日期。");
|
||||||
|
if (entity.IsCurrent && !request.IsCurrent)
|
||||||
|
return ConflictProblem("不能直接取消当前学期,请将另一个学期设为当前。");
|
||||||
|
if (request.IsCurrent && entity.IsArchived)
|
||||||
|
return ConflictProblem("已归档学期不能直接设为当前,请先撤销归档。");
|
||||||
|
if (request.IsCurrent && !request.IsEnabled)
|
||||||
|
return ValidationProblem("当前学期必须保持启用。");
|
||||||
if (request.IsCurrent)
|
if (request.IsCurrent)
|
||||||
await db.AcademicTerms.Where(x => x.Id != id).ExecuteUpdateAsync(
|
{
|
||||||
setters => setters.SetProperty(x => x.IsCurrent, false),
|
var currentTerms = await db.AcademicTerms
|
||||||
cancellationToken);
|
.Where(x => x.Id != id && x.IsCurrent)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
foreach (var currentTerm in currentTerms)
|
||||||
|
currentTerm.IsCurrent = false;
|
||||||
|
}
|
||||||
ApplyCatalog(entity, request);
|
ApplyCatalog(entity, request);
|
||||||
entity.AcademicYear = request.AcademicYear.Trim();
|
entity.AcademicYear = request.AcademicYear.Trim();
|
||||||
entity.Season = request.Season;
|
entity.Season = request.Season;
|
||||||
@@ -341,6 +364,64 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("terms/{id:guid}/set-current")]
|
||||||
|
[Authorize(Roles = Administrators)]
|
||||||
|
public async Task<ActionResult<AcademicTerm>> SetCurrentTerm(
|
||||||
|
Guid id,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await db.AcademicTerms.FindAsync([id], cancellationToken);
|
||||||
|
if (entity is null) return NotFound();
|
||||||
|
if (entity.IsArchived)
|
||||||
|
return ConflictProblem("已归档学期不能直接设为当前,请先撤销归档。");
|
||||||
|
if (!entity.IsEnabled)
|
||||||
|
return ConflictProblem("停用学期不能设为当前,请先启用。");
|
||||||
|
if (entity.IsCurrent) return entity;
|
||||||
|
|
||||||
|
var currentTerms = await db.AcademicTerms
|
||||||
|
.Where(x => x.Id != id && x.IsCurrent)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
foreach (var currentTerm in currentTerms)
|
||||||
|
currentTerm.IsCurrent = false;
|
||||||
|
entity.IsCurrent = true;
|
||||||
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("terms/{id:guid}/archive")]
|
||||||
|
[Authorize(Roles = Administrators)]
|
||||||
|
public async Task<ActionResult<AcademicTerm>> ArchiveTerm(
|
||||||
|
Guid id,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await db.AcademicTerms.FindAsync([id], cancellationToken);
|
||||||
|
if (entity is null) return NotFound();
|
||||||
|
if (entity.IsCurrent)
|
||||||
|
return ConflictProblem("当前学期不能归档,请先切换到新的当前学期。");
|
||||||
|
if (entity.IsArchived) return entity;
|
||||||
|
|
||||||
|
entity.IsArchived = true;
|
||||||
|
entity.ArchivedAt = DateTime.UtcNow;
|
||||||
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("terms/{id:guid}/unarchive")]
|
||||||
|
[Authorize(Roles = Administrators)]
|
||||||
|
public async Task<ActionResult<AcademicTerm>> UnarchiveTerm(
|
||||||
|
Guid id,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await db.AcademicTerms.FindAsync([id], cancellationToken);
|
||||||
|
if (entity is null) return NotFound();
|
||||||
|
if (!entity.IsArchived) return entity;
|
||||||
|
|
||||||
|
entity.IsArchived = false;
|
||||||
|
entity.ArchivedAt = null;
|
||||||
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("classrooms")]
|
[HttpGet("classrooms")]
|
||||||
public async Task<ActionResult<object>> GetClassrooms(CancellationToken cancellationToken) =>
|
public async Task<ActionResult<object>> GetClassrooms(CancellationToken cancellationToken) =>
|
||||||
Ok(await db.Classrooms.AsNoTracking()
|
Ok(await db.Classrooms.AsNoTracking()
|
||||||
@@ -447,6 +528,8 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (entity is null) return NotFound();
|
if (entity is null) return NotFound();
|
||||||
|
if (entity is AcademicTerm { IsCurrent: true })
|
||||||
|
return ConflictProblem("当前学期不能删除,请先切换到其他学期。");
|
||||||
db.Remove(entity);
|
db.Remove(entity);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -508,6 +591,14 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
|
|||||||
entity.SortOrder = request.SortOrder;
|
entity.SortOrder = request.SortOrder;
|
||||||
entity.IsEnabled = request.IsEnabled;
|
entity.IsEnabled = request.IsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ObjectResult ConflictProblem(string detail) =>
|
||||||
|
Conflict(new ProblemDetails
|
||||||
|
{
|
||||||
|
Title = "当前操作无法完成",
|
||||||
|
Detail = detail,
|
||||||
|
Status = StatusCodes.Status409Conflict
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public record CatalogRequest(
|
public record CatalogRequest(
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ public sealed class CourseSelectionsController(
|
|||||||
x.Name,
|
x.Name,
|
||||||
x.AcademicTermId,
|
x.AcademicTermId,
|
||||||
TermName = x.AcademicTerm!.Name,
|
TermName = x.AcademicTerm!.Name,
|
||||||
|
TermIsCurrent = x.AcademicTerm.IsCurrent,
|
||||||
|
TermIsArchived = x.AcademicTerm.IsArchived,
|
||||||
x.StartsAt,
|
x.StartsAt,
|
||||||
x.EndsAt,
|
x.EndsAt,
|
||||||
x.WithdrawalEndsAt,
|
x.WithdrawalEndsAt,
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ public sealed class EvaluationsController(
|
|||||||
x.Status,
|
x.Status,
|
||||||
x.AcademicTermId,
|
x.AcademicTermId,
|
||||||
TermName = x.AcademicTerm!.Name,
|
TermName = x.AcademicTerm!.Name,
|
||||||
|
TermIsCurrent = x.AcademicTerm.IsCurrent,
|
||||||
|
TermIsArchived = x.AcademicTerm.IsArchived,
|
||||||
x.StartsAt,
|
x.StartsAt,
|
||||||
x.EndsAt,
|
x.EndsAt,
|
||||||
Dimensions = x.Dimensions.Select(d => new
|
Dimensions = x.Dimensions.Select(d => new
|
||||||
|
|||||||
Loaded 3 of 32 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user