修复bug,完善业务逻辑
This commit is contained in:
parent
00155c596e
commit
8efb6a3707
@ -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 => "一检",
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user