修复bug,完善业务逻辑
This commit is contained in:
parent
00155c596e
commit
8efb6a3707
@ -1,6 +1,7 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using YD_Event.Application.Auth;
|
using YD_Event.Application.Auth;
|
||||||
using YD_Event.Application.Client.Dtos;
|
using YD_Event.Application.Client.Dtos;
|
||||||
|
using YD_Event.Application.Event;
|
||||||
|
|
||||||
namespace YD_Event.Application.Client;
|
namespace YD_Event.Application.Client;
|
||||||
|
|
||||||
@ -192,17 +193,25 @@ public class CheckinAdminController : IDynamicApiController
|
|||||||
await _userRepository.UpdateAsync(checker);
|
await _userRepository.UpdateAsync(checker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (task == null)
|
||||||
|
{
|
||||||
|
// 兼容历史解绑数据:旧逻辑只禁用了任务,但唯一索引仍将其视为占用。
|
||||||
|
// 同一赛事、同一检次存在已停用任务时直接复用该任务槽位,避免再次插入触发唯一索引冲突。
|
||||||
|
task = await _taskRepository.GetFirstAsync(x =>
|
||||||
|
x.EventId == input.EventId && x.CheckinNo == input.CheckinNo && !x.DeleteMark);
|
||||||
if (task == null)
|
if (task == null)
|
||||||
{
|
{
|
||||||
task = new CheckinTask
|
task = new CheckinTask
|
||||||
{
|
{
|
||||||
EventId = input.EventId,
|
EventId = input.EventId,
|
||||||
CheckerUserId = checker.Id,
|
|
||||||
CreatedUserId = managerUserId,
|
CreatedUserId = managerUserId,
|
||||||
CreatedTime = DateTime.Now
|
CreatedTime = DateTime.Now
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task.CheckerUserId = checker.Id;
|
||||||
|
}
|
||||||
|
|
||||||
ApplyCheckinAssignment(task, input.CheckinNo, assignedNumbers.Count, input.Venue);
|
ApplyCheckinAssignment(task, input.CheckinNo, assignedNumbers.Count, input.Venue);
|
||||||
task.TaskStatus = 1;
|
task.TaskStatus = 1;
|
||||||
task.Status = AvailabilityStatus.Enable;
|
task.Status = AvailabilityStatus.Enable;
|
||||||
@ -230,6 +239,11 @@ public class CheckinAdminController : IDynamicApiController
|
|||||||
EnsureManager();
|
EnsureManager();
|
||||||
var task = await _taskRepository.GetFirstAsync(x => x.Id == input.TaskId && !x.DeleteMark);
|
var task = await _taskRepository.GetFirstAsync(x => x.Id == input.TaskId && !x.DeleteMark);
|
||||||
if (task == null) throw Oops.Bah("检录任务不存在");
|
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 =>
|
var laterExists = await _taskRepository.IsAnyAsync(x =>
|
||||||
x.EventId == task.EventId && x.CheckinNo > task.CheckinNo && x.TaskStatus == 1 &&
|
x.EventId == task.EventId && x.CheckinNo > task.CheckinNo && x.TaskStatus == 1 &&
|
||||||
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
|
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
|
||||||
@ -237,6 +251,7 @@ public class CheckinAdminController : IDynamicApiController
|
|||||||
|
|
||||||
task.TaskStatus = 0;
|
task.TaskStatus = 0;
|
||||||
task.Status = AvailabilityStatus.Disable;
|
task.Status = AvailabilityStatus.Disable;
|
||||||
|
task.DeleteMark = true;
|
||||||
task.UpdatedUserId = _authManager.UserId;
|
task.UpdatedUserId = _authManager.UserId;
|
||||||
task.UpdatedTime = DateTime.Now;
|
task.UpdatedTime = DateTime.Now;
|
||||||
await _taskRepository.UpdateAsync(task);
|
await _taskRepository.UpdateAsync(task);
|
||||||
@ -320,10 +335,13 @@ public class CheckinAdminController : IDynamicApiController
|
|||||||
.ToList();
|
.ToList();
|
||||||
if (occupiedEventIds.Count > 0)
|
if (occupiedEventIds.Count > 0)
|
||||||
{
|
{
|
||||||
var occupiedEvent = await _eventRepository.AsQueryable()
|
var occupiedEvents = await _eventRepository.AsQueryable()
|
||||||
.Where(x => occupiedEventIds.Contains(x.Id) && !x.DeleteMark &&
|
.Where(x => occupiedEventIds.Contains(x.Id) &&
|
||||||
x.EventStatus != EventStatus.Ended)
|
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
|
||||||
.FirstAsync();
|
.ToListAsync();
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var occupiedEvent = occupiedEvents.FirstOrDefault(x =>
|
||||||
|
GetEffectiveEventStatus(x, now) != EventStatus.Ended);
|
||||||
if (occupiedEvent != null)
|
if (occupiedEvent != null)
|
||||||
{
|
{
|
||||||
return new CheckerAccountAvailabilityDto
|
return new CheckerAccountAvailabilityDto
|
||||||
@ -378,6 +396,21 @@ public class CheckinAdminController : IDynamicApiController
|
|||||||
task.Venue = venue?.Trim();
|
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
|
private static string ToCheckinName(int checkinNo) => checkinNo switch
|
||||||
{
|
{
|
||||||
1 => "一检",
|
1 => "一检",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using EasyCaching.Core;
|
using EasyCaching.Core;
|
||||||
using YD_Event.Application.Auth;
|
using YD_Event.Application.Auth;
|
||||||
using YD_Event.Application.Client.Dtos;
|
using YD_Event.Application.Client.Dtos;
|
||||||
|
using YD_Event.Application.Event;
|
||||||
|
|
||||||
namespace YD_Event.Application.Client;
|
namespace YD_Event.Application.Client;
|
||||||
|
|
||||||
@ -190,7 +191,7 @@ public class RefereeController : IDynamicApiController
|
|||||||
{
|
{
|
||||||
var userId = await EnsureRefereeAsync();
|
var userId = await EnsureRefereeAsync();
|
||||||
var applications = await _applicationRepository.AsQueryable()
|
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)
|
.OrderByDescending(x => x.CreatedTime)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
@ -229,20 +230,29 @@ public class RefereeController : IDynamicApiController
|
|||||||
public async Task<object> Apply([FromBody] RefereeApplyInput input)
|
public async Task<object> Apply([FromBody] RefereeApplyInput input)
|
||||||
{
|
{
|
||||||
var userId = await EnsureRefereeAsync();
|
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 == 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.AsQueryable()
|
||||||
|
.With("UPDLOCK, HOLDLOCK")
|
||||||
var referee = await _refereeRepository.GetFirstAsync(x => x.AppUserId == userId && !x.DeleteMark);
|
.Where(x => x.AppUserId == userId && !x.DeleteMark)
|
||||||
|
.FirstAsync();
|
||||||
if (referee == null) throw Oops.Bah("裁判档案不存在,请先完善裁判资料");
|
if (referee == null) throw Oops.Bah("裁判档案不存在,请先完善裁判资料");
|
||||||
if (referee.AuditStatus != AuditStatus.Pass) throw Oops.Bah("裁判认证尚未通过审核,暂不能报名执裁");
|
if (referee.AuditStatus != AuditStatus.Pass) throw Oops.Bah("裁判认证尚未通过审核,暂不能报名执裁");
|
||||||
|
|
||||||
var quota = await BuildQuotaAsync(evt, userId);
|
var exists = await _applicationRepository.IsAnyAsync(x =>
|
||||||
if (quota.IsFull) throw Oops.Bah("当前赛事裁判报名名额已满");
|
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
|
var application = new RefereeApplication
|
||||||
{
|
{
|
||||||
@ -257,7 +267,8 @@ public class RefereeController : IDynamicApiController
|
|||||||
};
|
};
|
||||||
await _applicationRepository.InsertAsync(application);
|
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
|
await _eventRefereeRepository.InsertAsync(new EventReferee
|
||||||
{
|
{
|
||||||
@ -278,23 +289,40 @@ public class RefereeController : IDynamicApiController
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 报名名额按赛事报名限制中的裁判限报数量控制,为 0 时不限名额。
|
/// 报名名额按赛事报名限制中的裁判限报数量控制,为 0 时不限名额。
|
||||||
/// </summary>
|
/// </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 limit = await _limitRepository.GetFirstAsync(x => x.EventId == evt.Id && !x.DeleteMark);
|
||||||
var max = limit?.RefereeLimit ?? 0;
|
var max = limit?.RefereeLimit ?? 0;
|
||||||
var applied = await _applicationRepository.AsQueryable()
|
var applicationsQuery = _applicationRepository.AsQueryable();
|
||||||
.Where(x => x.EventId == evt.Id && x.ApplicationStatus != 4 && x.Status == AvailabilityStatus.Enable)
|
if (lockForUpdate)
|
||||||
.CountAsync();
|
{
|
||||||
var appliedByMe = await _applicationRepository.IsAnyAsync(x =>
|
// Apply 由 UnitOfWork 包裹。更新锁和范围锁会把同一赛事的“统计名额并新增报名”串行化,
|
||||||
x.EventId == evt.Id && x.RefereeUserId == userId && x.ApplicationStatus != 4);
|
// 防止最后一个名额被并发请求同时占用。
|
||||||
|
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 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;
|
string message;
|
||||||
if (appliedByMe) message = "已报名执裁当前赛事";
|
if (appliedByMe) message = "已报名执裁当前赛事";
|
||||||
else if (isFull) 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}个" : "裁判名额未满";
|
else message = max > 0 ? $"裁判名额未满,剩余{max - applied}个" : "裁判名额未满";
|
||||||
|
|
||||||
return new RefereeQuotaDto
|
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)
|
private async Task VerifyCodeAsync(string mobile, string? code)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(code)) throw Oops.Bah("请输入验证码");
|
if (string.IsNullOrWhiteSpace(code)) throw Oops.Bah("请输入验证码");
|
||||||
@ -321,7 +407,8 @@ public class RefereeController : IDynamicApiController
|
|||||||
private async Task<long> EnsureRefereeAsync()
|
private async Task<long> EnsureRefereeAsync()
|
||||||
{
|
{
|
||||||
var userId = _authManager.UserId;
|
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 == null) throw Oops.Bah("用户不存在");
|
||||||
if (user.MiniProgramRole != MiniProgramRole.Referee) throw Oops.Bah("当前账号不是裁判身份");
|
if (user.MiniProgramRole != MiniProgramRole.Referee) throw Oops.Bah("当前账号不是裁判身份");
|
||||||
return userId;
|
return userId;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using YD_Event.Application.Auth;
|
using YD_Event.Application.Auth;
|
||||||
using YD_Event.Application.Common;
|
using YD_Event.Application.Common;
|
||||||
|
using YD_Event.Application.Event;
|
||||||
using YD_Event.Application.Scoring.Dtos;
|
using YD_Event.Application.Scoring.Dtos;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
@ -101,27 +102,64 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
{
|
{
|
||||||
var referee = await EnsureRefereeAsync();
|
var referee = await EnsureRefereeAsync();
|
||||||
var assignments = await _assignmentRepository.AsQueryable()
|
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)
|
.WhereIF(eventId.HasValue, x => x.EventId == eventId!.Value)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
if (assignments.Count == 0) return new List<JudgeSessionDto>();
|
if (assignments.Count == 0) return new List<JudgeSessionDto>();
|
||||||
|
|
||||||
var sessionIds = assignments.Select(x => x.SessionId).Distinct().ToList();
|
var sessionIds = assignments.Select(x => x.SessionId).Distinct().ToList();
|
||||||
var sessions = await _sessionRepository.AsQueryable()
|
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>();
|
if (sessions.Count == 0) return new List<JudgeSessionDto>();
|
||||||
|
|
||||||
var eventIds = sessions.Select(x => x.EventId).Distinct().ToList();
|
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);
|
.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()
|
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()
|
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()
|
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()
|
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
|
sessions = sessions
|
||||||
.OrderBy(x => eventProjects.TryGetValue(x.EventProjectId, out var eventProject)
|
.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)
|
.ThenBy(x => eventProjects.TryGetValue(x.EventProjectId, out var eventProject)
|
||||||
? eventProject.ProjectId
|
? eventProject.ProjectId
|
||||||
: long.MaxValue)
|
: 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.SessionNo)
|
||||||
|
.ThenBy(x => x.Id)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var entries = await _entryRepository.AsQueryable()
|
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()
|
var mySheets = await _sheetRepository.AsQueryable()
|
||||||
.Where(x => sessionIds.Contains(x.SessionId) && x.RefereeId == referee.Id
|
.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>();
|
var rows = new List<JudgeSessionDto>();
|
||||||
foreach (var session in sessions)
|
foreach (var session in sessions)
|
||||||
@ -286,7 +329,9 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
: await _detailRepository.GetListAsync(x => x.ScoreSheetId == source.Id);
|
: await _detailRepository.GetListAsync(x => x.ScoreSheetId == source.Id);
|
||||||
var inputs = details.Select(x => new ScoreItemInputValue
|
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();
|
}).ToList();
|
||||||
// 草稿允许留空,因此按非必填口径试算,只汇总已录入的部分。
|
// 草稿允许留空,因此按非必填口径试算,只汇总已录入的部分。
|
||||||
var preview = ScoreSheetCalculationService.Calculate(definitions, inputs, ctx.Config, false);
|
var preview = ScoreSheetCalculationService.Calculate(definitions, inputs, ctx.Config, false);
|
||||||
@ -538,7 +583,9 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
IsDisqualified = outcome.IsDisqualified,
|
IsDisqualified = outcome.IsDisqualified,
|
||||||
Symbols = (outcome.Aggregate?.Symbols ?? new List<EntrySymbolScore>()).Select(symbol => new JudgeSymbolScoreDto
|
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),
|
Value = ctx.Config.Format(symbol.Value),
|
||||||
Judges = symbol.Judges.Select(judge => new JudgeSymbolJudgeDto
|
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);
|
: (await _correctionRepository.GetListAsync(x => correctionIds.Contains(x.Id))).ToDictionary(x => x.Id);
|
||||||
return messages.Select(x => new JudgeMessageDto
|
return messages.Select(x => new JudgeMessageDto
|
||||||
{
|
{
|
||||||
Id = x.Id, EventId = x.EventId, MessageType = x.MessageType, Title = x.Title, Content = x.Content,
|
Id = x.Id,
|
||||||
RelationType = x.RelationType, RelationId = x.RelationId,
|
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,
|
SessionId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.SessionId : null,
|
||||||
SessionEntryId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.SessionEntryId : null,
|
SessionEntryId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.SessionEntryId : null,
|
||||||
ScoreSheetId = x.RelationId.HasValue ? corrections.GetValueOrDefault(x.RelationId.Value)?.OriginalScoreSheetId : 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();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -770,12 +823,22 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
if (current.Count > 0) await _attachmentRepository.UpdateRangeAsync(current);
|
if (current.Count > 0) await _attachmentRepository.UpdateRangeAsync(current);
|
||||||
var attachment = new EventJudgeScoreAttachment
|
var attachment = new EventJudgeScoreAttachment
|
||||||
{
|
{
|
||||||
Id = _idGenerator.NextId(), EventId = sheet.EventId, SessionId = sheet.SessionId,
|
Id = _idGenerator.NextId(),
|
||||||
SessionEntryId = sheet.SessionEntryId, ScoreSheetId = sheet.Id,
|
EventId = sheet.EventId,
|
||||||
JudgeAssignmentId = sheet.JudgeAssignmentId, RefereeId = referee.Id,
|
SessionId = sheet.SessionId,
|
||||||
AttachmentType = AnnotationAttachmentType, FileName = Path.GetFileName(file.FileName),
|
SessionEntryId = sheet.SessionEntryId,
|
||||||
FileUrl = url, FileSize = file.Length, ContentType = "image/png", IdempotencyKey = key,
|
ScoreSheetId = sheet.Id,
|
||||||
IsCurrent = true, CreatedUserId = _authManager.UserId, CreatedTime = now
|
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);
|
await _attachmentRepository.InsertAsync(attachment);
|
||||||
return ToAttachmentDto(attachment);
|
return ToAttachmentDto(attachment);
|
||||||
@ -1077,7 +1140,9 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
private static List<ScoreItemInputValue> ToInputValues(List<JudgeScoreItemInput> items) =>
|
private static List<ScoreItemInputValue> ToInputValues(List<JudgeScoreItemInput> items) =>
|
||||||
items.Select(x => new ScoreItemInputValue
|
items.Select(x => new ScoreItemInputValue
|
||||||
{
|
{
|
||||||
ItemCode = x.ItemCode, InputValue = x.InputValue, CountValue = x.CountValue
|
ItemCode = x.ItemCode,
|
||||||
|
InputValue = x.InputValue,
|
||||||
|
CountValue = x.CountValue
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -1098,7 +1163,10 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
RequiredMissingCount = calculation.RequiredMissingCount,
|
RequiredMissingCount = calculation.RequiredMissingCount,
|
||||||
Steps = calculation.Steps.Select(x => new JudgeScoreStepDto
|
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()
|
}).ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1107,8 +1175,11 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static JudgeRoleProgressDto ToProgressDto(EntryRoleProgress progress) => new()
|
private static JudgeRoleProgressDto ToProgressDto(EntryRoleProgress progress) => new()
|
||||||
{
|
{
|
||||||
RoleType = progress.RoleType, RoleName = progress.RoleName, Symbol = progress.Symbol,
|
RoleType = progress.RoleType,
|
||||||
ExpectedCount = progress.ExpectedCount, SubmittedCount = progress.SubmittedCount,
|
RoleName = progress.RoleName,
|
||||||
|
Symbol = progress.Symbol,
|
||||||
|
ExpectedCount = progress.ExpectedCount,
|
||||||
|
SubmittedCount = progress.SubmittedCount,
|
||||||
PendingJudges = progress.PendingJudges
|
PendingJudges = progress.PendingJudges
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1186,6 +1257,38 @@ public class JudgeScoringController : IDynamicApiController
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const int EntryStatusWithdrawn = 80;
|
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>
|
||||||
/// 场次状态名称
|
/// 场次状态名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user