修复bug,完善业务逻辑

This commit is contained in:
马梓彭 2026-09-20 09:40:18 +08:00
parent 00155c596e
commit 8efb6a3707
3 changed files with 278 additions and 55 deletions

View File

@ -1,6 +1,7 @@
using System.Text.RegularExpressions;
using YD_Event.Application.Auth;
using YD_Event.Application.Client.Dtos;
using YD_Event.Application.Event;
namespace YD_Event.Application.Client;
@ -194,13 +195,21 @@ public class CheckinAdminController : IDynamicApiController
if (task == null)
{
task = new CheckinTask
// 兼容历史解绑数据:旧逻辑只禁用了任务,但唯一索引仍将其视为占用。
// 同一赛事、同一检次存在已停用任务时直接复用该任务槽位,避免再次插入触发唯一索引冲突。
task = await _taskRepository.GetFirstAsync(x =>
x.EventId == input.EventId && x.CheckinNo == input.CheckinNo && !x.DeleteMark);
if (task == null)
{
EventId = input.EventId,
CheckerUserId = checker.Id,
CreatedUserId = managerUserId,
CreatedTime = DateTime.Now
};
task = new CheckinTask
{
EventId = input.EventId,
CreatedUserId = managerUserId,
CreatedTime = DateTime.Now
};
}
task.CheckerUserId = checker.Id;
}
ApplyCheckinAssignment(task, input.CheckinNo, assignedNumbers.Count, input.Venue);
@ -230,6 +239,11 @@ public class CheckinAdminController : IDynamicApiController
EnsureManager();
var task = await _taskRepository.GetFirstAsync(x => x.Id == input.TaskId && !x.DeleteMark);
if (task == null) throw Oops.Bah("检录任务不存在");
var eventInfo = await _eventRepository.GetFirstAsync(x => x.Id == task.EventId && !x.DeleteMark);
if (eventInfo == null) throw Oops.Bah("赛事不存在");
var eventStatus = GetEffectiveEventStatus(eventInfo, DateTime.Now);
if (eventStatus == EventStatus.Ongoing) throw Oops.Bah("赛事比赛中,不支持解除检录员绑定");
var laterExists = await _taskRepository.IsAnyAsync(x =>
x.EventId == task.EventId && x.CheckinNo > task.CheckinNo && x.TaskStatus == 1 &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
@ -237,6 +251,7 @@ public class CheckinAdminController : IDynamicApiController
task.TaskStatus = 0;
task.Status = AvailabilityStatus.Disable;
task.DeleteMark = true;
task.UpdatedUserId = _authManager.UserId;
task.UpdatedTime = DateTime.Now;
await _taskRepository.UpdateAsync(task);
@ -320,10 +335,13 @@ public class CheckinAdminController : IDynamicApiController
.ToList();
if (occupiedEventIds.Count > 0)
{
var occupiedEvent = await _eventRepository.AsQueryable()
.Where(x => occupiedEventIds.Contains(x.Id) && !x.DeleteMark &&
x.EventStatus != EventStatus.Ended)
.FirstAsync();
var occupiedEvents = await _eventRepository.AsQueryable()
.Where(x => occupiedEventIds.Contains(x.Id) &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.ToListAsync();
var now = DateTime.Now;
var occupiedEvent = occupiedEvents.FirstOrDefault(x =>
GetEffectiveEventStatus(x, now) != EventStatus.Ended);
if (occupiedEvent != null)
{
return new CheckerAccountAvailabilityDto
@ -378,6 +396,21 @@ public class CheckinAdminController : IDynamicApiController
task.Venue = venue?.Trim();
}
/// <summary>
/// 统一检录管理使用的赛事状态口径。后台状态与比赛时间不一致时按生命周期靠后的状态处理,
/// 避免状态同步延迟导致比赛中仍可解绑,或赛事结束后账号未及时释放。
/// </summary>
private static EventStatus GetEffectiveEventStatus(EventInfo eventInfo, DateTime now)
{
var calculatedStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
if (calculatedStatus == EventStatus.Ended || eventInfo.EventStatus == EventStatus.Ended)
return EventStatus.Ended;
if (calculatedStatus == EventStatus.Ongoing || eventInfo.EventStatus == EventStatus.Ongoing)
return EventStatus.Ongoing;
return calculatedStatus;
}
private static string ToCheckinName(int checkinNo) => checkinNo switch
{
1 => "一检",

View File

@ -1,6 +1,7 @@
using EasyCaching.Core;
using YD_Event.Application.Auth;
using YD_Event.Application.Client.Dtos;
using YD_Event.Application.Event;
namespace YD_Event.Application.Client;
@ -190,7 +191,7 @@ public class RefereeController : IDynamicApiController
{
var userId = await EnsureRefereeAsync();
var applications = await _applicationRepository.AsQueryable()
.Where(x => x.RefereeUserId == userId && x.Status == AvailabilityStatus.Enable)
.Where(x => x.RefereeUserId == userId && x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.OrderByDescending(x => x.CreatedTime)
.ToListAsync();
@ -229,20 +230,29 @@ public class RefereeController : IDynamicApiController
public async Task<object> Apply([FromBody] RefereeApplyInput input)
{
var userId = await EnsureRefereeAsync();
var evt = await _eventRepository.GetFirstAsync(x => x.Id == input.EventId && x.Status == AvailabilityStatus.Enable);
var evt = await _eventRepository.GetFirstAsync(x =>
x.Id == input.EventId && x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
if (evt == null) throw Oops.Bah("赛事不存在");
if (evt.EventStatus != EventStatus.Enrolling) throw Oops.Bah("当前赛事不在执裁报名时间内");
if (GetEffectiveEventStatus(evt) != EventStatus.Enrolling)
throw Oops.Bah("当前赛事不在执裁报名时间内");
var exists = await _applicationRepository.IsAnyAsync(x =>
x.EventId == input.EventId && x.RefereeUserId == userId && x.ApplicationStatus != 4);
if (exists) throw Oops.Bah("已报名执裁当前赛事");
var referee = await _refereeRepository.GetFirstAsync(x => x.AppUserId == userId && !x.DeleteMark);
// 同一裁判的报名请求必须串行化,否则并发报名两场重叠赛事时,
// 两个事务可能都在对方写入前通过撞期校验。锁顺序固定为:裁判 -> 赛事名额。
var referee = await _refereeRepository.AsQueryable()
.With("UPDLOCK, HOLDLOCK")
.Where(x => x.AppUserId == userId && !x.DeleteMark)
.FirstAsync();
if (referee == null) throw Oops.Bah("裁判档案不存在,请先完善裁判资料");
if (referee.AuditStatus != AuditStatus.Pass) throw Oops.Bah("裁判认证尚未通过审核,暂不能报名执裁");
var quota = await BuildQuotaAsync(evt, userId);
if (quota.IsFull) throw Oops.Bah("当前赛事裁判报名名额已满");
var exists = await _applicationRepository.IsAnyAsync(x =>
x.EventId == input.EventId && x.RefereeUserId == userId &&
(x.ApplicationStatus == 1 || x.ApplicationStatus == 2) &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
if (exists) throw Oops.Bah("已报名执裁当前赛事");
var quota = await BuildQuotaAsync(evt, userId, true, referee.Id);
if (!quota.CanApply) throw Oops.Bah(quota.Message);
var application = new RefereeApplication
{
@ -257,7 +267,8 @@ public class RefereeController : IDynamicApiController
};
await _applicationRepository.InsertAsync(application);
if (!await _eventRefereeRepository.IsAnyAsync(x => x.EventId == input.EventId && x.RefereeId == referee.Id))
if (!await _eventRefereeRepository.IsAnyAsync(x =>
x.EventId == input.EventId && x.RefereeId == referee.Id && !x.DeleteMark))
{
await _eventRefereeRepository.InsertAsync(new EventReferee
{
@ -278,23 +289,40 @@ public class RefereeController : IDynamicApiController
/// <summary>
/// 报名名额按赛事报名限制中的裁判限报数量控制,为 0 时不限名额。
/// </summary>
private async Task<RefereeQuotaDto> BuildQuotaAsync(EventInfo evt, long userId)
private async Task<RefereeQuotaDto> BuildQuotaAsync(
EventInfo evt, long userId, bool lockForUpdate = false, long? refereeId = null)
{
var limit = await _limitRepository.GetFirstAsync(x => x.EventId == evt.Id && !x.DeleteMark);
var max = limit?.RefereeLimit ?? 0;
var applied = await _applicationRepository.AsQueryable()
.Where(x => x.EventId == evt.Id && x.ApplicationStatus != 4 && x.Status == AvailabilityStatus.Enable)
.CountAsync();
var appliedByMe = await _applicationRepository.IsAnyAsync(x =>
x.EventId == evt.Id && x.RefereeUserId == userId && x.ApplicationStatus != 4);
var applicationsQuery = _applicationRepository.AsQueryable();
if (lockForUpdate)
{
// Apply 由 UnitOfWork 包裹。更新锁和范围锁会把同一赛事的“统计名额并新增报名”串行化,
// 防止最后一个名额被并发请求同时占用。
applicationsQuery = applicationsQuery.With("UPDLOCK, HOLDLOCK");
}
var activeApplications = await applicationsQuery
.Where(x => x.EventId == evt.Id &&
(x.ApplicationStatus == 1 || x.ApplicationStatus == 2) &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.Select(x => new { x.Id, x.RefereeUserId })
.ToListAsync();
var applied = activeApplications.Count;
var appliedByMe = activeApplications.Any(x => x.RefereeUserId == userId);
var conflictingEvent = appliedByMe
? null
: await GetConflictingCommittedEventAsync(userId, refereeId, evt);
var isFull = max > 0 && applied >= max && !appliedByMe;
var canApply = evt.EventStatus == EventStatus.Enrolling && !isFull && !appliedByMe;
var eventStatus = GetEffectiveEventStatus(evt);
var canApply = eventStatus == EventStatus.Enrolling && !isFull && !appliedByMe && conflictingEvent == null;
string message;
if (appliedByMe) message = "已报名执裁当前赛事";
else if (isFull) message = "当前赛事裁判报名名额已满";
else if (evt.EventStatus != EventStatus.Enrolling) message = "当前赛事不在执裁报名时间内";
else if (eventStatus != EventStatus.Enrolling) message = "当前赛事不在执裁报名时间内";
else if (conflictingEvent != null) message = BuildTimeConflictMessage(conflictingEvent, evt);
else message = max > 0 ? $"裁判名额未满,剩余{max - applied}个" : "裁判名额未满";
return new RefereeQuotaDto
@ -311,6 +339,64 @@ public class RefereeController : IDynamicApiController
};
}
/// <summary>
/// 查找当前裁判已报名、后台添加或秩序册导入,且执裁时间与目标赛事重叠的赛事。
/// 时间区间按左闭右开处理,相邻赛事首尾时间相等时不视为冲突。
/// </summary>
private async Task<EventInfo?> GetConflictingCommittedEventAsync(
long userId, long? refereeId, EventInfo targetEvent)
{
var appliedEventIds = await _applicationRepository.AsQueryable()
.Where(x => x.RefereeUserId == userId && x.EventId != targetEvent.Id &&
(x.ApplicationStatus == 1 || x.ApplicationStatus == 2) &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.Select(x => x.EventId)
.Distinct()
.ToListAsync();
if (!refereeId.HasValue)
{
refereeId = (await _refereeRepository.GetFirstAsync(x =>
x.AppUserId == userId && !x.DeleteMark))?.Id;
}
if (refereeId.HasValue)
{
var relatedEventIds = await _eventRefereeRepository.AsQueryable()
.Where(x => x.RefereeId == refereeId.Value && x.EventId != targetEvent.Id && !x.DeleteMark)
.Select(x => x.EventId)
.Distinct()
.ToListAsync();
appliedEventIds = appliedEventIds.Concat(relatedEventIds).Distinct().ToList();
}
if (appliedEventIds.Count == 0) return null;
return await _eventRepository.AsQueryable()
.Where(x => appliedEventIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable && !x.DeleteMark &&
x.MatchStartTime < targetEvent.MatchEndTime &&
x.MatchEndTime > targetEvent.MatchStartTime)
.OrderBy(x => x.MatchStartTime)
.FirstAsync();
}
private static string BuildTimeConflictMessage(EventInfo appliedEvent, EventInfo targetEvent) =>
$"您已参与执裁:\n{appliedEvent.EventName}\n{FormatMatchPeriod(appliedEvent)}\n\n" +
$"当前赛事执裁时间:\n{FormatMatchPeriod(targetEvent)}\n\n" +
"执裁时间存在重叠,暂无法报名。";
private static string FormatMatchPeriod(EventInfo evt)
{
var start = evt.MatchStartTime;
var end = evt.MatchEndTime;
return start.Date == end.Date
? $"{start:M月d日 HH:mm}-{end:HH:mm}"
: $"{start:M月d日 HH:mm}-{end:M月d日 HH:mm}";
}
private static EventStatus GetEffectiveEventStatus(EventInfo evt) =>
EventStatusUpdateService.CalculateEventStatus(evt, DateTime.Now);
private async Task VerifyCodeAsync(string mobile, string? code)
{
if (string.IsNullOrWhiteSpace(code)) throw Oops.Bah("请输入验证码");
@ -321,7 +407,8 @@ public class RefereeController : IDynamicApiController
private async Task<long> EnsureRefereeAsync()
{
var userId = _authManager.UserId;
var user = await _userRepository.GetFirstAsync(x => x.Id == userId);
var user = await _userRepository.GetFirstAsync(x =>
x.Id == userId && x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
if (user == null) throw Oops.Bah("用户不存在");
if (user.MiniProgramRole != MiniProgramRole.Referee) throw Oops.Bah("当前账号不是裁判身份");
return userId;

View File

@ -1,5 +1,6 @@
using YD_Event.Application.Auth;
using YD_Event.Application.Common;
using YD_Event.Application.Event;
using YD_Event.Application.Scoring.Dtos;
using System.Text.RegularExpressions;
@ -101,27 +102,64 @@ public class JudgeScoringController : IDynamicApiController
{
var referee = await EnsureRefereeAsync();
var assignments = await _assignmentRepository.AsQueryable()
.Where(x => x.RefereeId == referee.Id && x.AssignmentStatus == 1)
.Where(x => x.RefereeId == referee.Id && x.AssignmentStatus == 1 &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.WhereIF(eventId.HasValue, x => x.EventId == eventId!.Value)
.ToListAsync();
if (assignments.Count == 0) return new List<JudgeSessionDto>();
var sessionIds = assignments.Select(x => x.SessionId).Distinct().ToList();
var sessions = await _sessionRepository.AsQueryable()
.Where(x => sessionIds.Contains(x.Id) && x.SessionStatus != 90).ToListAsync();
.Where(x => sessionIds.Contains(x.Id) && x.SessionStatus != 90 &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.ToListAsync();
if (sessions.Count == 0) return new List<JudgeSessionDto>();
var eventIds = sessions.Select(x => x.EventId).Distinct().ToList();
var events = (await _eventRepository.AsQueryable().Where(x => eventIds.Contains(x.Id)).ToListAsync())
var events = (await _eventRepository.AsQueryable()
.Where(x => eventIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.ToListAsync())
.ToDictionary(x => x.Id);
var now = DateTime.Now;
EventInfo targetEvent;
if (eventId.HasValue)
{
targetEvent = events.GetValueOrDefault(eventId.Value);
}
else
{
targetEvent = events.Values
.Where(x => GetEffectiveEventStatus(x, now) == EventStatus.Ongoing)
.OrderBy(x => x.MatchStartTime)
.ThenBy(x => x.Id)
.FirstOrDefault()
?? events.Values
.Where(x => GetEffectiveEventStatus(x, now) != EventStatus.Ended &&
x.MatchStartTime > now)
.OrderBy(x => x.MatchStartTime)
.ThenBy(x => x.Id)
.FirstOrDefault();
}
if (targetEvent == null) return new List<JudgeSessionDto>();
sessions = sessions.Where(x => x.EventId == targetEvent.Id).ToList();
assignments = assignments.Where(x => x.EventId == targetEvent.Id).ToList();
sessionIds = sessions.Select(x => x.Id).ToList();
var venues = (await _venueRepository.AsQueryable()
.Where(x => sessions.Select(s => s.VenueId).Contains(x.Id)).ToListAsync()).ToDictionary(x => x.Id);
.Where(x => sessions.Select(s => s.VenueId).Contains(x.Id) &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.ToListAsync()).ToDictionary(x => x.Id);
var eventProjects = (await _eventProjectRepository.AsQueryable()
.Where(x => sessions.Select(s => s.EventProjectId).Contains(x.Id)).ToListAsync()).ToDictionary(x => x.Id);
.Where(x => sessions.Select(s => s.EventProjectId).Contains(x.Id) && !x.DeleteMark)
.ToListAsync()).ToDictionary(x => x.Id);
var projects = (await _projectRepository.AsQueryable()
.Where(x => eventProjects.Values.Select(e => e.ProjectId).Contains(x.Id)).ToListAsync()).ToDictionary(x => x.Id);
.Where(x => eventProjects.Values.Select(e => e.ProjectId).Contains(x.Id) && !x.DeleteMark)
.ToListAsync()).ToDictionary(x => x.Id);
var rules = (await _ruleRepository.AsQueryable()
.Where(x => sessions.Select(s => s.ScoreRuleId).Contains(x.Id)).ToListAsync()).ToDictionary(x => x.Id);
.Where(x => sessions.Select(s => s.ScoreRuleId).Contains(x.Id) && !x.DeleteMark)
.ToListAsync()).ToDictionary(x => x.Id);
sessions = sessions
.OrderBy(x => eventProjects.TryGetValue(x.EventProjectId, out var eventProject)
@ -131,15 +169,20 @@ public class JudgeScoringController : IDynamicApiController
.ThenBy(x => eventProjects.TryGetValue(x.EventProjectId, out var eventProject)
? eventProject.ProjectId
: long.MaxValue)
.ThenBy(x => x.StartTime ?? DateTime.MaxValue)
.ThenBy(x => GetArrangementSequence(x.SessionNo))
.ThenBy(x => venues.TryGetValue(x.VenueId, out var venue) ? venue.Sort : int.MaxValue)
.ThenBy(x => x.SessionNo)
.ThenBy(x => x.Id)
.ToList();
var entries = await _entryRepository.AsQueryable()
.Where(x => sessionIds.Contains(x.SessionId) && !x.IsVoided).ToListAsync();
.Where(x => sessionIds.Contains(x.SessionId) && !x.IsVoided &&
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.ToListAsync();
var mySheets = await _sheetRepository.AsQueryable()
.Where(x => sessionIds.Contains(x.SessionId) && x.RefereeId == referee.Id
&& x.IsCurrent && x.SheetStatus >= EventScoreCalculationService.SheetStatusSubmitted).ToListAsync();
&& x.IsCurrent && !x.DeleteMark
&& x.SheetStatus >= EventScoreCalculationService.SheetStatusSubmitted).ToListAsync();
var rows = new List<JudgeSessionDto>();
foreach (var session in sessions)
@ -286,7 +329,9 @@ public class JudgeScoringController : IDynamicApiController
: await _detailRepository.GetListAsync(x => x.ScoreSheetId == source.Id);
var inputs = details.Select(x => new ScoreItemInputValue
{
ItemCode = x.ItemCode, InputValue = x.InputValue, CountValue = x.CountValue
ItemCode = x.ItemCode,
InputValue = x.InputValue,
CountValue = x.CountValue
}).ToList();
// 草稿允许留空,因此按非必填口径试算,只汇总已录入的部分。
var preview = ScoreSheetCalculationService.Calculate(definitions, inputs, ctx.Config, false);
@ -538,7 +583,9 @@ public class JudgeScoringController : IDynamicApiController
IsDisqualified = outcome.IsDisqualified,
Symbols = (outcome.Aggregate?.Symbols ?? new List<EntrySymbolScore>()).Select(symbol => new JudgeSymbolScoreDto
{
Symbol = symbol.Symbol, Label = symbol.Label, Sign = symbol.Sign,
Symbol = symbol.Symbol,
Label = symbol.Label,
Sign = symbol.Sign,
Value = ctx.Config.Format(symbol.Value),
Judges = symbol.Judges.Select(judge => new JudgeSymbolJudgeDto
{
@ -683,12 +730,18 @@ public class JudgeScoringController : IDynamicApiController
: (await _correctionRepository.GetListAsync(x => correctionIds.Contains(x.Id))).ToDictionary(x => x.Id);
return messages.Select(x => new JudgeMessageDto
{
Id = x.Id, EventId = x.EventId, MessageType = x.MessageType, Title = x.Title, Content = x.Content,
RelationType = x.RelationType, RelationId = x.RelationId,
Id = x.Id,
EventId = x.EventId,
MessageType = x.MessageType,
Title = x.Title,
Content = x.Content,
RelationType = x.RelationType,
RelationId = x.RelationId,
SessionId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.SessionId : null,
SessionEntryId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.SessionEntryId : null,
ScoreSheetId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.OriginalScoreSheetId : null,
IsRead = x.IsRead, CreatedTime = x.CreatedTime
IsRead = x.IsRead,
CreatedTime = x.CreatedTime
}).ToList();
}
@ -770,12 +823,22 @@ public class JudgeScoringController : IDynamicApiController
if (current.Count > 0) await _attachmentRepository.UpdateRangeAsync(current);
var attachment = new EventJudgeScoreAttachment
{
Id = _idGenerator.NextId(), EventId = sheet.EventId, SessionId = sheet.SessionId,
SessionEntryId = sheet.SessionEntryId, ScoreSheetId = sheet.Id,
JudgeAssignmentId = sheet.JudgeAssignmentId, RefereeId = referee.Id,
AttachmentType = AnnotationAttachmentType, FileName = Path.GetFileName(file.FileName),
FileUrl = url, FileSize = file.Length, ContentType = "image/png", IdempotencyKey = key,
IsCurrent = true, CreatedUserId = _authManager.UserId, CreatedTime = now
Id = _idGenerator.NextId(),
EventId = sheet.EventId,
SessionId = sheet.SessionId,
SessionEntryId = sheet.SessionEntryId,
ScoreSheetId = sheet.Id,
JudgeAssignmentId = sheet.JudgeAssignmentId,
RefereeId = referee.Id,
AttachmentType = AnnotationAttachmentType,
FileName = Path.GetFileName(file.FileName),
FileUrl = url,
FileSize = file.Length,
ContentType = "image/png",
IdempotencyKey = key,
IsCurrent = true,
CreatedUserId = _authManager.UserId,
CreatedTime = now
};
await _attachmentRepository.InsertAsync(attachment);
return ToAttachmentDto(attachment);
@ -1077,7 +1140,9 @@ public class JudgeScoringController : IDynamicApiController
private static List<ScoreItemInputValue> ToInputValues(List<JudgeScoreItemInput> items) =>
items.Select(x => new ScoreItemInputValue
{
ItemCode = x.ItemCode, InputValue = x.InputValue, CountValue = x.CountValue
ItemCode = x.ItemCode,
InputValue = x.InputValue,
CountValue = x.CountValue
}).ToList();
/// <summary>
@ -1098,7 +1163,10 @@ public class JudgeScoringController : IDynamicApiController
RequiredMissingCount = calculation.RequiredMissingCount,
Steps = calculation.Steps.Select(x => new JudgeScoreStepDto
{
Code = x.Code, Label = x.Label, Formula = x.Formula, Value = x.Value
Code = x.Code,
Label = x.Label,
Formula = x.Formula,
Value = x.Value
}).ToList()
};
@ -1107,8 +1175,11 @@ public class JudgeScoringController : IDynamicApiController
/// </summary>
private static JudgeRoleProgressDto ToProgressDto(EntryRoleProgress progress) => new()
{
RoleType = progress.RoleType, RoleName = progress.RoleName, Symbol = progress.Symbol,
ExpectedCount = progress.ExpectedCount, SubmittedCount = progress.SubmittedCount,
RoleType = progress.RoleType,
RoleName = progress.RoleName,
Symbol = progress.Symbol,
ExpectedCount = progress.ExpectedCount,
SubmittedCount = progress.SubmittedCount,
PendingJudges = progress.PendingJudges
};
@ -1186,6 +1257,38 @@ public class JudgeScoringController : IDynamicApiController
/// </summary>
private const int EntryStatusWithdrawn = 80;
/// <summary>
/// 获取场次在赛事编排中的顺序。自动编排和秩序册导入生成的场次号末段均保存场次顺序;
/// 速度项目只有项目和场地段,同一项目内按场地顺序排列。
/// </summary>
private static int GetArrangementSequence(string? sessionNo)
{
if (string.IsNullOrWhiteSpace(sessionNo)) return int.MaxValue;
var match = Regex.Match(sessionNo, @"^EP\d+-(?:V)?\d+(?:-(?<sequence>\d+))?$",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!match.Success) return int.MaxValue;
return match.Groups["sequence"].Success &&
int.TryParse(match.Groups["sequence"].Value, out var sequence)
? sequence
: 0;
}
/// <summary>
/// 获取用于裁判场次选择的赛事实时状态,避免后台状态同步延迟导致比赛中场次不可见。
/// </summary>
private static EventStatus GetEffectiveEventStatus(EventInfo eventInfo, DateTime now)
{
var calculatedStatus = EventStatusUpdateService.CalculateEventStatus(eventInfo, now);
if (calculatedStatus == EventStatus.Ended || eventInfo.EventStatus == EventStatus.Ended)
return EventStatus.Ended;
if (calculatedStatus == EventStatus.Ongoing || eventInfo.EventStatus == EventStatus.Ongoing)
return EventStatus.Ongoing;
return calculatedStatus;
}
/// <summary>
/// 场次状态名称
/// </summary>