diff --git a/YD_Event.Application/Event/JudgeAssignmentService.cs b/YD_Event.Application/Event/JudgeAssignmentService.cs
index ffb0119..ef9460b 100644
--- a/YD_Event.Application/Event/JudgeAssignmentService.cs
+++ b/YD_Event.Application/Event/JudgeAssignmentService.cs
@@ -163,15 +163,18 @@ public class JudgeAssignmentService : IDynamicApiController, ITransient
{
throw Oops.Bah("场次不存在或已取消");
}
+ var eventInfo = await _eventRepository.GetFirstAsync(x => x.Id == input.EventId);
+ if (eventInfo == null) throw Oops.Bah("赛事不存在");
var eventSessions = await _sessionRepository.AsQueryable()
.Where(x => x.EventId == input.EventId && x.SessionStatus != 90)
.ToListAsync();
var lockedSession = eventSessions.FirstOrDefault(
- x => !CompetitionStagePolicy.CanModifyAssignment(x.SessionStatus));
+ x => !CompetitionStagePolicy.CanModifyAssignment(x.SessionStatus, eventInfo.EventStatus));
if (lockedSession != null)
{
- throw Oops.Bah(CompetitionStagePolicy.AssignmentLockReason(lockedSession.SessionStatus));
+ throw Oops.Bah(CompetitionStagePolicy.AssignmentLockReason(
+ lockedSession.SessionStatus, eventInfo.EventStatus));
}
if (input.Assignments.Any(x => string.IsNullOrWhiteSpace(x.JudgeRole) || (x.EventRefereeId <= 0 && x.RefereeId <= 0)))
@@ -219,6 +222,8 @@ public class JudgeAssignmentService : IDynamicApiController, ITransient
[HttpPost("submit"), DisplayName("提交裁判分配"), UnitOfWork]
public async Task Submit([FromBody] SubmitJudgeAssignmentsInput input)
{
+ var eventInfo = await _eventRepository.GetFirstAsync(x => x.Id == input.EventId);
+ if (eventInfo == null) throw Oops.Bah("赛事不存在");
var sessions = await _sessionRepository.AsQueryable()
.Where(x => x.EventId == input.EventId && x.SessionStatus != 90)
.ToListAsync();
@@ -228,10 +233,11 @@ public class JudgeAssignmentService : IDynamicApiController, ITransient
}
var lockedSession = sessions.FirstOrDefault(
- x => !CompetitionStagePolicy.CanModifyAssignment(x.SessionStatus));
+ x => !CompetitionStagePolicy.CanModifyAssignment(x.SessionStatus, eventInfo.EventStatus));
if (lockedSession != null)
{
- throw Oops.Bah(CompetitionStagePolicy.AssignmentLockReason(lockedSession.SessionStatus));
+ throw Oops.Bah(CompetitionStagePolicy.AssignmentLockReason(
+ lockedSession.SessionStatus, eventInfo.EventStatus));
}
// 重提时只有修改过的场次是草稿,其余场次仍是已提交;完整性校验必须同时考虑两者。
diff --git a/YD_Event.Application/Judge/CompetitionStagePolicy.cs b/YD_Event.Application/Judge/CompetitionStagePolicy.cs
index 2e686aa..5e50677 100644
--- a/YD_Event.Application/Judge/CompetitionStagePolicy.cs
+++ b/YD_Event.Application/Judge/CompetitionStagePolicy.cs
@@ -1,5 +1,7 @@
namespace YD_Event.Application.Scoring;
+using YD_Event.Core.Enum;
+
///
/// 场次阶段与操作权限的统一判定。管理端分配和裁判端评分必须共用同一口径。
///
@@ -27,6 +29,14 @@ public static class CompetitionStagePolicy
/// 允许修改返回 ,否则返回 。
public static bool CanModifyAssignment(int sessionStatus) => sessionStatus is 0 or 10;
+ ///
+ /// 结合赛事状态判断是否允许修改裁判分配。
+ /// 赛事进入比赛中或已结束后,即使场次仍停留在草稿/就绪状态也必须锁定。
+ ///
+ public static bool CanModifyAssignment(int sessionStatus, EventStatus eventStatus) =>
+ CanModifyAssignment(sessionStatus)
+ && eventStatus is not EventStatus.Ongoing and not EventStatus.Ended;
+
///
/// 判断指定场次状态是否允许评分。
///
@@ -34,6 +44,16 @@ public static class CompetitionStagePolicy
/// 允许评分返回 ,否则返回 。
public static bool CanScore(int sessionStatus) => sessionStatus is 20 or 30;
+ ///
+ /// 结合赛事状态判断指定场次是否允许评分。
+ /// 已提交裁判分配的场次处于“就绪”状态;赛事进入比赛中后,该状态即可评分。
+ ///
+ /// 场次状态。
+ /// 赛事状态。
+ /// 允许评分返回 ,否则返回 。
+ public static bool CanScore(int sessionStatus, EventStatus eventStatus) =>
+ eventStatus == EventStatus.Ongoing && sessionStatus is 10 or 20 or 30;
+
///
/// 根据场次状态获取赛事阶段编码。
///
@@ -46,6 +66,18 @@ public static class CompetitionStagePolicy
_ => PHASE_POST_MATCH
};
+ ///
+ /// 结合赛事状态获取赛事阶段编码。
+ ///
+ public static int PhaseOf(int sessionStatus, EventStatus eventStatus)
+ {
+ if (eventStatus == EventStatus.Ended) return PHASE_POST_MATCH;
+ if (eventStatus != EventStatus.Ongoing) return PHASE_PRE_MATCH;
+ return sessionStatus is 0 ? PHASE_PRE_MATCH : PhaseOf(sessionStatus) == PHASE_POST_MATCH
+ ? PHASE_POST_MATCH
+ : PHASE_IN_PROGRESS;
+ }
+
///
/// 根据场次状态获取赛事阶段名称。
///
@@ -58,6 +90,17 @@ public static class CompetitionStagePolicy
_ => "赛后"
};
+ ///
+ /// 结合赛事状态获取赛事阶段名称。
+ ///
+ public static string PhaseName(int sessionStatus, EventStatus eventStatus) =>
+ PhaseOf(sessionStatus, eventStatus) switch
+ {
+ PHASE_PRE_MATCH => "赛前",
+ PHASE_IN_PROGRESS => "赛中",
+ _ => "赛后"
+ };
+
///
/// 获取指定场次状态下禁止评分的提示信息。
///
@@ -73,6 +116,17 @@ public static class CompetitionStagePolicy
_ => "当前赛事状态不可评分"
};
+ ///
+ /// 结合赛事状态获取禁止评分的提示信息。
+ ///
+ public static string ScoreLockReason(int sessionStatus, EventStatus eventStatus)
+ {
+ if (eventStatus == EventStatus.Ended) return "赛事已结束,无法评分";
+ if (eventStatus != EventStatus.Ongoing) return "赛事未开始";
+
+ return ScoreLockReason(sessionStatus);
+ }
+
///
/// 获取指定场次状态下禁止修改裁判分配的提示信息。
///
@@ -85,4 +139,14 @@ public static class CompetitionStagePolicy
90 => "赛事已取消,不能修改裁判分配",
_ => "当前赛事状态不能修改裁判分配"
};
+
+ ///
+ /// 结合赛事状态获取禁止修改裁判分配的提示信息。
+ ///
+ public static string AssignmentLockReason(int sessionStatus, EventStatus eventStatus)
+ {
+ if (eventStatus == EventStatus.Ended) return "赛事已结束,不能修改裁判分配";
+ if (eventStatus == EventStatus.Ongoing) return "赛事已开始,不能修改裁判分配";
+ return AssignmentLockReason(sessionStatus);
+ }
}
diff --git a/YD_Event.Application/Judge/JudgeScoringController.cs b/YD_Event.Application/Judge/JudgeScoringController.cs
index 74a96c9..aa6c78c 100644
--- a/YD_Event.Application/Judge/JudgeScoringController.cs
+++ b/YD_Event.Application/Judge/JudgeScoringController.cs
@@ -181,19 +181,20 @@ public class JudgeScoringController : IDynamicApiController
await GetMyAssignmentAsync(sessionId, referee.Id);
var session = await _sessionRepository.GetFirstAsync(x => x.Id == sessionId);
if (session == null) throw Oops.Bah("比赛场次不存在");
+ var eventInfo = await GetEventAsync(session.EventId);
- var canScore = CompetitionStagePolicy.CanScore(session.SessionStatus);
+ var canScore = CompetitionStagePolicy.CanScore(session.SessionStatus, eventInfo.EventStatus);
return new JudgeEventStatusDto
{
EventId = session.EventId,
SessionId = session.Id,
SessionStatus = session.SessionStatus,
SessionStatusName = SessionStatusName(session.SessionStatus),
- Phase = CompetitionStagePolicy.PhaseOf(session.SessionStatus),
- PhaseName = CompetitionStagePolicy.PhaseName(session.SessionStatus),
+ Phase = CompetitionStagePolicy.PhaseOf(session.SessionStatus, eventInfo.EventStatus),
+ PhaseName = CompetitionStagePolicy.PhaseName(session.SessionStatus, eventInfo.EventStatus),
CanScore = canScore,
- CanModifyAssignment = CompetitionStagePolicy.CanModifyAssignment(session.SessionStatus),
- Message = canScore ? null : CompetitionStagePolicy.ScoreLockReason(session.SessionStatus)
+ CanModifyAssignment = CompetitionStagePolicy.CanModifyAssignment(session.SessionStatus, eventInfo.EventStatus),
+ Message = canScore ? null : CompetitionStagePolicy.ScoreLockReason(session.SessionStatus, eventInfo.EventStatus)
};
}
@@ -255,7 +256,7 @@ public class JudgeScoringController : IDynamicApiController
public async Task GetSheet(long sessionEntryId)
{
var referee = await EnsureRefereeAsync();
- var (entry, ctx, assignment, roleType) = await LoadSheetContextAsync(sessionEntryId, referee.Id);
+ var (entry, ctx, eventInfo, assignment, roleType) = await LoadSheetContextAsync(sessionEntryId, referee.Id);
var definitions = ScoreSheetCalculationService.ItemsOf(ctx, roleType);
if (definitions.Count == 0) throw Oops.Bah($"计分规则“{ctx.Rule.RuleName}”未配置{JudgeRoleSchemaService.RoleNameOf(ctx.RoleSlots, roleType)}的评分项");
@@ -279,12 +280,11 @@ public class JudgeScoringController : IDynamicApiController
// 草稿允许留空,因此按非必填口径试算,只汇总已录入的部分。
var preview = ScoreSheetCalculationService.Calculate(definitions, inputs, ctx.Config, false);
- var stageAllowsScoring = CompetitionStagePolicy.CanScore(ctx.Session.SessionStatus);
+ var stageAllowsScoring = CompetitionStagePolicy.CanScore(ctx.Session.SessionStatus, eventInfo.EventStatus);
var scoreSheetAllowsEditing = submitted == null || correction != null;
var canEdit = stageAllowsScoring && scoreSheetAllowsEditing;
var members = await LoadMemberNamesAsync(entry);
var venue = await _venueRepository.GetFirstAsync(x => x.Id == ctx.Session.VenueId);
- var eventInfo = await _eventRepository.GetFirstAsync(x => x.Id == ctx.Session.EventId);
var nextVersion = (submitted?.SheetVersion ?? 0) + 1;
return new JudgeScoreSheetOutput
@@ -297,7 +297,7 @@ public class JudgeScoringController : IDynamicApiController
SheetStatus = source?.SheetStatus ?? SheetStatusDraft,
CanEdit = canEdit,
LockReason = canEdit ? null : !stageAllowsScoring
- ? CompetitionStagePolicy.ScoreLockReason(ctx.Session.SessionStatus)
+ ? CompetitionStagePolicy.ScoreLockReason(ctx.Session.SessionStatus, eventInfo.EventStatus)
: "已提交评分,如需修改请先申请成绩修正",
IsCorrection = correction != null,
// 幂等键随评分单版本递增,弱网重复提交同一版本只会落一张评分单。
@@ -372,8 +372,8 @@ public class JudgeScoringController : IDynamicApiController
public async Task SaveDraft([FromBody] SaveJudgeScoreInput input)
{
var referee = await EnsureRefereeAsync();
- var (entry, ctx, assignment, roleType) = await LoadSheetContextAsync(input.SessionEntryId, referee.Id);
- EnsureCanScore(ctx.Session.SessionStatus);
+ var (entry, ctx, eventInfo, assignment, roleType) = await LoadSheetContextAsync(input.SessionEntryId, referee.Id);
+ EnsureCanScore(ctx.Session.SessionStatus, eventInfo.EventStatus);
var definitions = ScoreSheetCalculationService.ItemsOf(ctx, roleType);
if (definitions.Count == 0) throw Oops.Bah($"计分规则“{ctx.Rule.RuleName}”未配置{JudgeRoleSchemaService.RoleNameOf(ctx.RoleSlots, roleType)}的评分项");
@@ -415,8 +415,8 @@ public class JudgeScoringController : IDynamicApiController
public async Task Submit([FromBody] SubmitJudgeScoreInput input)
{
var referee = await EnsureRefereeAsync();
- var (entry, ctx, assignment, roleType) = await LoadSheetContextAsync(input.SessionEntryId, referee.Id);
- EnsureCanScore(ctx.Session.SessionStatus);
+ var (entry, ctx, eventInfo, assignment, roleType) = await LoadSheetContextAsync(input.SessionEntryId, referee.Id);
+ EnsureCanScore(ctx.Session.SessionStatus, eventInfo.EventStatus);
if (entry.IsVoided) throw Oops.Bah("该出场成绩已作废,无法提交评分");
if (entry.EntryStatus == EntryStatusWithdrawn) throw Oops.Bah("该运动员已退赛,无需评分");
var definitions = ScoreSheetCalculationService.ItemsOf(ctx, roleType);
@@ -616,7 +616,8 @@ public class JudgeScoringController : IDynamicApiController
if (sheet == null) throw Oops.Bah("评分单不存在");
var session = await _sessionRepository.GetFirstAsync(x => x.Id == sheet.SessionId);
if (session == null) throw Oops.Bah("比赛场次不存在");
- EnsureCanScore(session.SessionStatus);
+ var eventInfo = await GetEventAsync(session.EventId);
+ EnsureCanScore(session.SessionStatus, eventInfo.EventStatus);
if (!sheet.IsCurrent || sheet.SheetStatus < EventScoreCalculationService.SheetStatusSubmitted)
throw Oops.Bah("该评分单尚未提交,可直接修改后提交");
if (sheet.SheetStatus == SheetStatusConfirmed) throw Oops.Bah("成绩已确认,请联系赛事管理员处理");
@@ -810,10 +811,21 @@ public class JudgeScoringController : IDynamicApiController
/// 校验当前场次状态是否允许评分。
///
/// 场次状态。
- private static void EnsureCanScore(int sessionStatus)
+ /// 赛事状态。
+ private static void EnsureCanScore(int sessionStatus, EventStatus eventStatus)
{
- if (!CompetitionStagePolicy.CanScore(sessionStatus))
- throw Oops.Bah(CompetitionStagePolicy.ScoreLockReason(sessionStatus));
+ if (!CompetitionStagePolicy.CanScore(sessionStatus, eventStatus))
+ throw Oops.Bah(CompetitionStagePolicy.ScoreLockReason(sessionStatus, eventStatus));
+ }
+
+ ///
+ /// 获取场次所属赛事,确保评分权限校验与后台展示使用同一赛事状态。
+ ///
+ private async Task GetEventAsync(long eventId)
+ {
+ var eventInfo = await _eventRepository.GetFirstAsync(x => x.Id == eventId);
+ if (eventInfo == null) throw Oops.Bah("赛事不存在");
+ return eventInfo;
}
///
@@ -830,16 +842,18 @@ public class JudgeScoringController : IDynamicApiController
///
/// 加载评分表所需的出场、计分上下文与我的裁判席位
///
- private async Task<(EventSessionEntry Entry, ScoringContext Context, EventJudgeAssignment Assignment, string RoleType)>
+ private async Task<(EventSessionEntry Entry, ScoringContext Context, EventInfo Event,
+ EventJudgeAssignment Assignment, string RoleType)>
LoadSheetContextAsync(long sessionEntryId, long refereeId)
{
var entry = await _entryRepository.GetFirstAsync(x => x.Id == sessionEntryId);
if (entry == null) throw Oops.Bah("出场记录不存在");
var assignment = await GetMyAssignmentAsync(entry.SessionId, refereeId);
var ctx = await _calculationService.LoadContextAsync(entry.SessionId);
+ var eventInfo = await GetEventAsync(ctx.Session.EventId);
var roleType = JudgeRoleSchemaService.NormalizeRoleType(
string.IsNullOrWhiteSpace(assignment.JudgeRoleType) ? assignment.JudgeRole : assignment.JudgeRoleType);
- return (entry, ctx, assignment, roleType);
+ return (entry, ctx, eventInfo, assignment, roleType);
}
///