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 { /// /// 按项目分页获取成绩概要。先按秩序册顺序确定页边界,再仅为当前页查询成绩与裁判提交状态; /// 评分项和计分板不随五秒轮询返回。 /// [HttpGet("projects/{eventProjectId}/entries")] public async Task 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(); var assigned = assignmentsBySession.GetValueOrDefault(entry.SessionId) ?? new List(); 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; } /// 点击详情时才加载该出场的裁判评分项和计分板地址。 [HttpGet("entries/{sessionEntryId}/detail")] public async Task 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("出场记录不存在"); } }