-m "修复赛事编排确认时 EventSessionEntry 写入无效 RegistrationItemId 导致的外键冲突。" -m "完善成绩展示数据,补充参赛单位名称,支持成绩管理矩阵及大屏展示。" -m "调整检录大屏进度规则:赛事检录顺序、当前检录项目及队伍状态统一跟随一检进度。" -m "当前比赛默认根据三检通过记录更新;赛事仅配置两检时,允许二检作为上场标记。" -m "一检全部完成后自动切换下一项目,全部完成后不再错误展示全部参赛队伍。
211 lines
11 KiB
C#
211 lines
11 KiB
C#
using YD_Event.Application.Scoring;
|
|
using YD_Event.Application.Workbench.Dtos;
|
|
using YD_Event.Core.Entities;
|
|
|
|
namespace YD_Event.Application.Workbench;
|
|
|
|
public partial class WorkbenchController
|
|
{
|
|
/// <summary>
|
|
/// 按项目分页获取成绩概要。先按秩序册顺序确定页边界,再仅为当前页查询成绩与裁判提交状态;
|
|
/// 评分项和计分板不随五秒轮询返回。
|
|
/// </summary>
|
|
[HttpGet("projects/{eventProjectId}/entries")]
|
|
public async Task<WorkbenchPagedEntriesOutput> GetProjectEntries(
|
|
long eventProjectId, [FromQuery] WorkbenchProjectEntriesInput input)
|
|
{
|
|
if (input.EventId <= 0 || eventProjectId <= 0) throw Oops.Bah("请选择赛事和项目");
|
|
var pageSize = input.PageSize is 20 or 50 or 100 ? input.PageSize : 20;
|
|
var sessions = await _sessionRepository.AsQueryable()
|
|
.Where(s => !s.DeleteMark && s.EventId == input.EventId && s.EventProjectId == eventProjectId)
|
|
.ToListAsync();
|
|
var output = new WorkbenchPagedEntriesOutput { PageSize = pageSize };
|
|
if (sessions.Count == 0) return output;
|
|
|
|
var sessionIds = sessions.Select(s => s.Id).ToList();
|
|
var query = _entryRepository.AsQueryable()
|
|
.Where(e => !e.DeleteMark && sessionIds.Contains(e.SessionId));
|
|
if (!string.IsNullOrWhiteSpace(input.Keyword))
|
|
{
|
|
var keyword = input.Keyword.Trim();
|
|
query = query.Where(e => e.EntryNo.Contains(keyword) || e.ParticipantName.Contains(keyword) ||
|
|
(e.TeamName != null && e.TeamName.Contains(keyword)));
|
|
}
|
|
var entries = await query.Select(e => new EventSessionEntry
|
|
{
|
|
Id = e.Id,
|
|
SessionId = e.SessionId,
|
|
EntryNo = e.EntryNo,
|
|
PlayOrder = e.PlayOrder,
|
|
SourceType = e.SourceType,
|
|
ParticipantName = e.ParticipantName,
|
|
TeamName = e.TeamName,
|
|
GroupName = e.GroupName,
|
|
EntryStatus = e.EntryStatus,
|
|
IsRematch = e.IsRematch
|
|
}).ToListAsync();
|
|
if (input.ResultStatus.HasValue && entries.Count > 0)
|
|
{
|
|
var allEntryIds = entries.Select(e => e.Id).ToList();
|
|
var statuses = await _resultRepository.AsQueryable()
|
|
.Where(r => !r.DeleteMark && r.EventId == input.EventId && r.SessionEntryId.HasValue && allEntryIds.Contains(r.SessionEntryId.Value))
|
|
.Select(r => new { r.SessionEntryId, r.ResultStatus, r.ResultVersion, r.Id })
|
|
.ToListAsync();
|
|
var currentStatuses = statuses.GroupBy(r => r.SessionEntryId!.Value)
|
|
.ToDictionary(g => g.Key, g => g.OrderByDescending(r => r.ResultVersion)
|
|
.ThenByDescending(r => r.Id).First().ResultStatus);
|
|
entries = entries.Where(e => currentStatuses.GetValueOrDefault(e.Id, -1) == input.ResultStatus.Value).ToList();
|
|
}
|
|
|
|
var sessionById = sessions.ToDictionary(s => s.Id);
|
|
var arrangementOrder = sessions.ToDictionary(s => s.Id, s => GetArrangementSequence(s.SessionNo));
|
|
var venueIds = sessions.Select(s => s.VenueId).Distinct().ToList();
|
|
var venueOrder = (await _venueRepository.AsQueryable()
|
|
.Where(v => venueIds.Contains(v.Id))
|
|
.Select(v => new { v.Id, v.Sort }).ToListAsync()).ToDictionary(v => v.Id, v => v.Sort);
|
|
int SessionOrder(EventSessionEntry entry)
|
|
{
|
|
var session = sessionById[entry.SessionId];
|
|
var order = arrangementOrder[session.Id];
|
|
return entry.SourceType == 1
|
|
? GetImportedSessionOrder(session.SessionName, order)
|
|
: BuildEntrySessionOrder(order, entry.PlayOrder);
|
|
}
|
|
long ScheduleOrder(EventSessionEntry entry)
|
|
{
|
|
if (entry.SourceType == 1) return entry.Id;
|
|
var session = sessionById[entry.SessionId];
|
|
return BuildEntryScheduleOrder(arrangementOrder[session.Id],
|
|
venueOrder.GetValueOrDefault(session.VenueId, 1), entry.PlayOrder);
|
|
}
|
|
var ordered = entries.OrderBy(ScheduleOrder).ThenBy(e => e.PlayOrder).ThenBy(e => e.Id).ToList();
|
|
output.Total = ordered.Count;
|
|
var pageCount = Math.Max(1, (ordered.Count + pageSize - 1) / pageSize);
|
|
output.Page = Math.Clamp(input.Page, 1, pageCount);
|
|
for (var index = 0; index < ordered.Count; index += pageSize)
|
|
{
|
|
var lastIndex = Math.Min(index + pageSize, ordered.Count) - 1;
|
|
output.PageRanges.Add(new WorkbenchPageRangeDto
|
|
{
|
|
Page = index / pageSize + 1,
|
|
FirstSessionOrder = SessionOrder(ordered[index]),
|
|
LastSessionOrder = SessionOrder(ordered[lastIndex])
|
|
});
|
|
}
|
|
var pageEntries = ordered.Skip((output.Page - 1) * pageSize).Take(pageSize).ToList();
|
|
if (pageEntries.Count == 0) return output;
|
|
|
|
var pageEntryIds = pageEntries.Select(e => e.Id).ToList();
|
|
var pageSessionIds = pageEntries.Select(e => e.SessionId).Distinct().ToList();
|
|
var results = await _resultRepository.AsQueryable()
|
|
.Where(r => !r.DeleteMark && r.EventId == input.EventId && r.SessionEntryId.HasValue && pageEntryIds.Contains(r.SessionEntryId.Value))
|
|
.ToListAsync();
|
|
var resultByEntry = results.GroupBy(r => r.SessionEntryId!.Value)
|
|
.ToDictionary(g => g.Key, g => g.OrderByDescending(r => r.ResultVersion).ThenByDescending(r => r.Id).First());
|
|
var sheets = await _sheetRepository.AsQueryable()
|
|
.Where(s => !s.DeleteMark && s.IsCurrent && pageEntryIds.Contains(s.SessionEntryId))
|
|
.ToListAsync();
|
|
var sheetsByEntry = sheets.GroupBy(s => s.SessionEntryId).ToDictionary(g => g.Key, g => g.ToList());
|
|
var assignments = await _assignmentRepository.AsQueryable()
|
|
.Where(a => !a.DeleteMark && pageSessionIds.Contains(a.SessionId) && a.AssignmentStatus < 9 &&
|
|
a.Status == Core.Enum.AvailabilityStatus.Enable)
|
|
.OrderBy(a => a.JudgeRole).ToListAsync();
|
|
var assignmentsBySession = assignments.GroupBy(a => a.SessionId).ToDictionary(g => g.Key, g => g.ToList());
|
|
var corrections = await _correctionRepository.AsQueryable()
|
|
.Where(c => !c.DeleteMark && pageEntryIds.Contains(c.SessionEntryId) &&
|
|
(c.CorrectionStatus == 0 || c.CorrectionStatus == 10 || c.CorrectionStatus == 30))
|
|
.Select(c => new { c.OriginalScoreSheetId, c.CorrectionStatus }).ToListAsync();
|
|
var pendingSheetIds = corrections.Select(c => c.OriginalScoreSheetId).ToHashSet();
|
|
var resubmissionSheetIds = corrections.Where(c => c.CorrectionStatus is 10 or 30)
|
|
.Select(c => c.OriginalScoreSheetId).ToHashSet();
|
|
var merchantIds = results.Where(r => r.MerchantCertificationId.HasValue)
|
|
.Select(r => r.MerchantCertificationId!.Value).Distinct().ToList();
|
|
var merchantNames = (await _merchantCertificationRepository.AsQueryable()
|
|
.Where(m => merchantIds.Contains(m.Id) && !m.DeleteMark)
|
|
.Select(m => new { m.Id, m.MerchantName }).ToListAsync())
|
|
.ToDictionary(m => m.Id, m => m.MerchantName);
|
|
|
|
WorkbenchJudgeScoreDto SummarySheet(EventJudgeScoreSheet sheet) => new()
|
|
{
|
|
ScoreSheetId = sheet.Id,
|
|
JudgeRole = sheet.JudgeRole,
|
|
JudgeRoleType = sheet.JudgeRoleType,
|
|
SheetStatus = sheet.SheetStatus,
|
|
SheetStatusName = GetSheetStatusName(sheet.SheetStatus),
|
|
HasPendingCorrection = pendingSheetIds.Contains(sheet.Id),
|
|
RequiresResubmission = resubmissionSheetIds.Contains(sheet.Id)
|
|
};
|
|
|
|
output.Items = pageEntries.Select(entry =>
|
|
{
|
|
resultByEntry.TryGetValue(entry.Id, out var result);
|
|
var entrySheets = sheetsByEntry.GetValueOrDefault(entry.Id) ?? new List<EventJudgeScoreSheet>();
|
|
var assigned = assignmentsBySession.GetValueOrDefault(entry.SessionId) ?? new List<EventJudgeAssignment>();
|
|
var judgeScores = assigned.Select(assignment =>
|
|
{
|
|
var sheet = entrySheets.FirstOrDefault(s => s.RefereeId == assignment.RefereeId &&
|
|
s.JudgeRole == assignment.JudgeRole);
|
|
return sheet != null ? SummarySheet(sheet) : new WorkbenchJudgeScoreDto
|
|
{
|
|
JudgeRole = assignment.JudgeRole,
|
|
JudgeRoleType = assignment.JudgeRoleType ?? string.Empty,
|
|
SheetStatus = 0,
|
|
SheetStatusName = "待提交"
|
|
};
|
|
}).ToList();
|
|
var assignmentKeys = assigned.Select(a => $"{a.RefereeId}:{a.JudgeRole}").ToHashSet();
|
|
judgeScores.AddRange(entrySheets.Where(s => !assignmentKeys.Contains($"{s.RefereeId}:{s.JudgeRole}"))
|
|
.Select(SummarySheet));
|
|
return new WorkbenchEntryDetailDto
|
|
{
|
|
SessionId = entry.SessionId,
|
|
SessionEntryId = entry.Id,
|
|
EntryNo = entry.EntryNo,
|
|
PlayOrder = entry.PlayOrder,
|
|
SessionOrder = SessionOrder(entry),
|
|
ScheduleOrder = ScheduleOrder(entry),
|
|
ParticipantName = entry.ParticipantName,
|
|
TeamName = entry.TeamName,
|
|
UnitName = result?.MerchantCertificationId is long merchantId
|
|
? merchantNames.GetValueOrDefault(merchantId) : null,
|
|
GroupName = entry.GroupName,
|
|
EntryStatus = entry.EntryStatus,
|
|
EntryStatusName = GetEntryStatusName(entry.EntryStatus),
|
|
IsRematch = entry.IsRematch,
|
|
JudgeScores = judgeScores,
|
|
Result = result == null ? null : new WorkbenchEntryResultDto
|
|
{
|
|
ResultId = result.Id,
|
|
DifficultyScore = result.DifficultyScore,
|
|
PerformanceScore = result.PerformanceScore,
|
|
RequiredScore = result.RequiredScore,
|
|
DeductionScore = result.DeductionScore,
|
|
DeductionFactor = ScoreDisplayService.DeductionFactor(result.CalculationJson),
|
|
TotalScore = result.TotalScore,
|
|
ScoreText = result.Score,
|
|
IsDisqualified = result.IsDisqualified,
|
|
DisqualifyReason = result.DisqualifyReasonCode,
|
|
ResultStatus = result.ResultStatus,
|
|
ResultStatusName = GetResultStatusName(result.ResultStatus),
|
|
ResultVersion = result.ResultVersion,
|
|
ReviewUserId = result.ReviewUserId,
|
|
ReviewTime = result.ReviewTime,
|
|
Rank = result.Rank
|
|
}
|
|
};
|
|
}).ToList();
|
|
return output;
|
|
}
|
|
|
|
/// <summary>点击详情时才加载该出场的裁判评分项和计分板地址。</summary>
|
|
[HttpGet("entries/{sessionEntryId}/detail")]
|
|
public async Task<WorkbenchEntryDetailDto> GetEntryDetail(long sessionEntryId)
|
|
{
|
|
var entry = await _entryRepository.GetFirstAsync(e => e.Id == sessionEntryId && !e.DeleteMark)
|
|
?? throw Oops.Bah("出场记录不存在");
|
|
var rows = await GetSessionEntries(entry.SessionId,
|
|
new WorkbenchEntryQueryInput { SessionEntryId = sessionEntryId });
|
|
return rows.FirstOrDefault() ?? throw Oops.Bah("出场记录不存在");
|
|
}
|
|
}
|