Compare commits
2 Commits
6d0babb4d2
...
f4e5db3f52
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4e5db3f52 | ||
|
|
e54112e08d |
@ -189,6 +189,9 @@ public class WorkbenchSessionDto
|
||||
/// </summary>
|
||||
public class WorkbenchEntryQueryInput
|
||||
{
|
||||
/// <summary>仅获取指定出场记录(供详情按需加载)。</summary>
|
||||
public long? SessionEntryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 场次ID
|
||||
/// </summary>
|
||||
@ -215,6 +218,9 @@ public class WorkbenchEntryQueryInput
|
||||
/// </summary>
|
||||
public class WorkbenchEntryDetailDto
|
||||
{
|
||||
/// <summary>所属场次ID。</summary>
|
||||
public long SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出场记录ID
|
||||
/// </summary>
|
||||
@ -452,6 +458,11 @@ public class WorkbenchJudgeScoreDto
|
||||
/// </summary>
|
||||
public bool HasPendingCorrection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 修正任务已批准或裁判修改中,原评分仅供追溯,本次提交仍待裁判重新完成。
|
||||
/// </summary>
|
||||
public bool RequiresResubmission { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前评分单提交的记录画板 PNG 地址
|
||||
/// </summary>
|
||||
|
||||
@ -39,6 +39,12 @@ public class ResultExportQueryInput
|
||||
/// 是否包含被取消比赛资格(DQ)的记录,默认不包含
|
||||
/// </summary>
|
||||
public bool IncludeDq { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 导出出场全名单:无成绩、DQ、退赛和作废记录均保留;忽略成绩状态与关键词筛选。
|
||||
/// 默认关闭,以兼容原有成绩导出调用方。
|
||||
/// </summary>
|
||||
public bool IncludeUnscored { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
namespace YD_Event.Application.Workbench.Dtos;
|
||||
|
||||
/// <summary>按项目读取成绩概要,分页后才加载裁判提交状态和成绩。</summary>
|
||||
public class WorkbenchProjectEntriesInput
|
||||
{
|
||||
public long EventId { get; set; }
|
||||
public int Page { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 20;
|
||||
public string? Keyword { get; set; }
|
||||
public int? ResultStatus { get; set; }
|
||||
}
|
||||
|
||||
public class WorkbenchPageRangeDto
|
||||
{
|
||||
public int Page { get; set; }
|
||||
public int FirstSessionOrder { get; set; }
|
||||
public int LastSessionOrder { get; set; }
|
||||
}
|
||||
|
||||
public class WorkbenchPagedEntriesOutput
|
||||
{
|
||||
public int Total { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public List<WorkbenchPageRangeDto> PageRanges { get; set; } = new();
|
||||
public List<WorkbenchEntryDetailDto> Items { get; set; } = new();
|
||||
}
|
||||
@ -0,0 +1,210 @@
|
||||
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("出场记录不存在");
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,7 @@ namespace YD_Event.Application.Workbench;
|
||||
[ApiDescriptionSettings("打分系统")]
|
||||
[Route("api/workbench")]
|
||||
[Authorize]
|
||||
public class WorkbenchController : IDynamicApiController
|
||||
public partial class WorkbenchController : IDynamicApiController
|
||||
{
|
||||
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
|
||||
private readonly ISqlSugarRepository<EventVenue> _venueRepository;
|
||||
@ -300,6 +300,9 @@ public class WorkbenchController : IDynamicApiController
|
||||
var query = _entryRepository.AsQueryable()
|
||||
.Where(e => !e.DeleteMark && e.SessionId == sessionId);
|
||||
|
||||
if (input.SessionEntryId.HasValue)
|
||||
query = query.Where(e => e.Id == input.SessionEntryId.Value);
|
||||
|
||||
if (input.EntryStatus.HasValue)
|
||||
query = query.Where(e => e.EntryStatus == input.EntryStatus.Value);
|
||||
|
||||
@ -421,11 +424,14 @@ public class WorkbenchController : IDynamicApiController
|
||||
.ToDictionary(r => r.Id, r => r.Name);
|
||||
|
||||
var pendingCorrections = await _correctionRepository.AsQueryable()
|
||||
.Where(c => !c.DeleteMark && entryIds.Contains(c.SessionEntryId) && c.CorrectionStatus < 20)
|
||||
.Select(c => c.OriginalScoreSheetId)
|
||||
.Where(c => !c.DeleteMark && entryIds.Contains(c.SessionEntryId) &&
|
||||
(c.CorrectionStatus == 0 || c.CorrectionStatus == 10 || c.CorrectionStatus == 30))
|
||||
.Select(c => new { c.OriginalScoreSheetId, c.CorrectionStatus })
|
||||
.ToListAsync();
|
||||
|
||||
var pendingCorrectionSet = pendingCorrections.ToHashSet();
|
||||
var pendingCorrectionSet = pendingCorrections.Select(c => c.OriginalScoreSheetId).ToHashSet();
|
||||
var resubmissionSet = pendingCorrections.Where(c => c.CorrectionStatus is 10 or 30)
|
||||
.Select(c => c.OriginalScoreSheetId).ToHashSet();
|
||||
// 集体项目为每名队员留存同一出场成绩,记分台按出场展示一行。
|
||||
// 稳定选取一个代表记录,避免同队多名队员导致重复字典键。
|
||||
var resultDict = results.GroupBy(r => r.SessionEntryId!.Value)
|
||||
@ -449,6 +455,7 @@ public class WorkbenchController : IDynamicApiController
|
||||
IsDisqualified = sheet.IsDisqualified,
|
||||
SubmittedTime = sheet.SubmittedTime,
|
||||
HasPendingCorrection = pendingCorrectionSet.Contains(sheet.Id),
|
||||
RequiresResubmission = resubmissionSet.Contains(sheet.Id),
|
||||
AnnotationUrl = annotationUrls.GetValueOrDefault(sheet.Id),
|
||||
ScoreItems = scoreItems.Where(d => d.ScoreSheetId == sheet.Id).Select(d => new WorkbenchScoreItemDto
|
||||
{
|
||||
@ -494,6 +501,7 @@ public class WorkbenchController : IDynamicApiController
|
||||
return new WorkbenchEntryDetailDto
|
||||
{
|
||||
SessionEntryId = e.Id,
|
||||
SessionId = sessionId,
|
||||
EntryNo = e.EntryNo,
|
||||
PlayOrder = e.PlayOrder,
|
||||
SessionOrder = e.SourceType == 1
|
||||
@ -669,6 +677,9 @@ public class WorkbenchController : IDynamicApiController
|
||||
if (input.ResultVersion.HasValue && input.ResultVersion.Value != result.ResultVersion)
|
||||
throw Oops.Bah("成绩已产生新版本,请刷新后重新投屏");
|
||||
|
||||
if (await HasPendingResubmissionAsync(entry.Id))
|
||||
throw Oops.Bah("裁判评分待重新提交,不能投屏旧成绩");
|
||||
|
||||
// 该出场应到裁判的评分单必须全部提交,否则成绩未定稿,不允许投屏
|
||||
var expectedJudgeCount = await _assignmentRepository.AsQueryable()
|
||||
.Where(a => !a.DeleteMark && a.SessionId == session.Id && a.Status == Core.Enum.AvailabilityStatus.Enable)
|
||||
@ -819,6 +830,16 @@ public class WorkbenchController : IDynamicApiController
|
||||
if (!results.Any())
|
||||
throw Oops.Bah("当前项目组别没有可投屏的已确认成绩");
|
||||
|
||||
// 修正任务未完成时,原成绩保留供追溯,但不能再次进入排名投屏。
|
||||
var rankingEntryIds = results.Select(r => r.SessionEntryId).Distinct().ToList();
|
||||
var awaitingEntries = (await _correctionRepository.AsQueryable()
|
||||
.Where(c => !c.DeleteMark && rankingEntryIds.Contains(c.SessionEntryId) &&
|
||||
(c.CorrectionStatus == 10 || c.CorrectionStatus == 30))
|
||||
.Select(c => c.SessionEntryId).ToListAsync()).ToHashSet();
|
||||
results = results.Where(r => !awaitingEntries.Contains(r.SessionEntryId)).ToList();
|
||||
if (!results.Any())
|
||||
throw Oops.Bah("当前项目组别没有已完成重交的有效成绩可投屏");
|
||||
|
||||
var rankingMerchantIds = results
|
||||
.Where(r => r.MerchantCertificationId.HasValue)
|
||||
.Select(r => r.MerchantCertificationId!.Value)
|
||||
@ -1508,6 +1529,9 @@ public class WorkbenchController : IDynamicApiController
|
||||
if (result.ResultStatus != 10)
|
||||
throw Oops.Bah("只能确认待复核状态的成绩");
|
||||
|
||||
if (result.SessionEntryId.HasValue && await HasPendingResubmissionAsync(result.SessionEntryId.Value))
|
||||
throw Oops.Bah("裁判评分待重新提交,不能确认旧成绩");
|
||||
|
||||
// 当前版本由成绩修正产生时确认为“已更正(40)”,常规成绩确认为“已确认(20)”
|
||||
var currentVersion = await _resultVersionRepository.GetFirstAsync(v =>
|
||||
!v.DeleteMark && v.EventResultId == result.Id && v.ResultVersion == result.ResultVersion);
|
||||
@ -1550,6 +1574,13 @@ public class WorkbenchController : IDynamicApiController
|
||||
if (results.Count != requestedIds.Count)
|
||||
throw Oops.Bah("选中成绩中包含非当前场次或非待复核数据,请刷新后重试");
|
||||
|
||||
var batchEntryIds = results.Where(r => r.SessionEntryId.HasValue)
|
||||
.Select(r => r.SessionEntryId!.Value).ToList();
|
||||
if (await _correctionRepository.AsQueryable().AnyAsync(c => !c.DeleteMark &&
|
||||
batchEntryIds.Contains(c.SessionEntryId) &&
|
||||
(c.CorrectionStatus == 10 || c.CorrectionStatus == 30)))
|
||||
throw Oops.Bah("选中成绩中有裁判评分待重新提交,不能确认旧成绩");
|
||||
|
||||
// 批量取出当前版本,逐条同步确认状态(含修正产生的已更正40状态)
|
||||
var resultIds = results.Select(r => r.Id).ToList();
|
||||
var resultVersions = await _resultVersionRepository.AsQueryable()
|
||||
@ -1722,6 +1753,9 @@ public class WorkbenchController : IDynamicApiController
|
||||
[HttpGet("results/export")]
|
||||
public async Task<IActionResult> ExportResults([FromQuery] ResultExportQueryInput input)
|
||||
{
|
||||
if (input.IncludeUnscored)
|
||||
return await ExportFullRosterAsync(input);
|
||||
|
||||
var query = _resultRepository.AsQueryable()
|
||||
.InnerJoin<EventSessionEntry>((r, e) => r.SessionEntryId == e.Id)
|
||||
.LeftJoin<EventSession>((r, e, s) => e.SessionId == s.Id)
|
||||
@ -1832,6 +1866,90 @@ public class WorkbenchController : IDynamicApiController
|
||||
return ExcelFile(bytes, $"成绩册_{input.EventId}_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
|
||||
}
|
||||
|
||||
/// <summary>从出场名单而不是成绩表生成全员成绩册,未评分者也占一行。</summary>
|
||||
private async Task<IActionResult> ExportFullRosterAsync(ResultExportQueryInput input)
|
||||
{
|
||||
var sessions = await _sessionRepository.AsQueryable()
|
||||
.Where(s => !s.DeleteMark && s.EventId == input.EventId)
|
||||
.Select(s => new { s.Id, s.SessionNo, s.SessionName, s.EventProjectId, s.VenueId })
|
||||
.ToListAsync();
|
||||
if (input.EventProjectId.HasValue)
|
||||
sessions = sessions.Where(s => s.EventProjectId == input.EventProjectId.Value).ToList();
|
||||
if (input.SessionId.HasValue)
|
||||
sessions = sessions.Where(s => s.Id == input.SessionId.Value).ToList();
|
||||
if (sessions.Count == 0) throw Oops.Bah("当前范围没有可导出的出场名单");
|
||||
|
||||
var sessionIds = sessions.Select(s => s.Id).ToList();
|
||||
var entries = await _entryRepository.AsQueryable()
|
||||
.Where(e => !e.DeleteMark && sessionIds.Contains(e.SessionId))
|
||||
.ToListAsync();
|
||||
if (entries.Count == 0) throw Oops.Bah("当前范围没有可导出的出场名单");
|
||||
|
||||
var entryIds = entries.Select(e => e.Id).ToList();
|
||||
var results = await _resultRepository.AsQueryable()
|
||||
.Where(r => !r.DeleteMark && r.EventId == input.EventId && r.SessionEntryId.HasValue && entryIds.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 venueIds = sessions.Select(s => s.VenueId).Distinct().ToList();
|
||||
var venueOrders = (await _venueRepository.AsQueryable()
|
||||
.Where(v => venueIds.Contains(v.Id))
|
||||
.Select(v => new { v.Id, v.Sort }).ToListAsync()).ToDictionary(v => v.Id, v => v.Sort);
|
||||
var eventProjectIds = sessions.Select(s => s.EventProjectId).Distinct().ToList();
|
||||
var eventProjects = await _eventProjectRepository.AsQueryable()
|
||||
.Where(p => eventProjectIds.Contains(p.Id))
|
||||
.Select(p => new { p.Id, p.ProjectId }).ToListAsync();
|
||||
var projectIds = eventProjects.Select(p => p.ProjectId).Distinct().ToList();
|
||||
var projectNames = (await _projectRepository.AsQueryable()
|
||||
.Where(p => projectIds.Contains(p.Id))
|
||||
.Select(p => new { p.Id, p.Name }).ToListAsync()).ToDictionary(p => p.Id, p => p.Name);
|
||||
var projectIdByEventProject = eventProjects.ToDictionary(p => p.Id, p => p.ProjectId);
|
||||
var sessionById = sessions.ToDictionary(s => s.Id);
|
||||
|
||||
long ScheduleOrder(EventSessionEntry entry)
|
||||
{
|
||||
if (entry.SourceType == 1) return entry.Id;
|
||||
var session = sessionById[entry.SessionId];
|
||||
return BuildEntryScheduleOrder(GetArrangementSequence(session.SessionNo),
|
||||
venueOrders.GetValueOrDefault(session.VenueId, 1), entry.PlayOrder);
|
||||
}
|
||||
|
||||
var orderedEntries = entries.OrderBy(ScheduleOrder).ThenBy(e => e.PlayOrder).ThenBy(e => e.Id).ToList();
|
||||
var rows = orderedEntries.Select(entry =>
|
||||
{
|
||||
var session = sessionById[entry.SessionId];
|
||||
resultByEntry.TryGetValue(entry.Id, out var result);
|
||||
var projectId = projectIdByEventProject.GetValueOrDefault(session.EventProjectId);
|
||||
var projectName = projectNames.GetValueOrDefault(projectId, string.Empty);
|
||||
var status = entry.IsVoided || result?.ResultStatus == 90 ? "已作废"
|
||||
: entry.EntryStatus is 80 or 90 ? GetEntryStatusName(entry.EntryStatus)
|
||||
: result?.IsDisqualified == true ? "取消资格"
|
||||
: result == null ? "未评分" : GetResultStatusName(result.ResultStatus);
|
||||
return new object?[]
|
||||
{
|
||||
result?.IsDisqualified == true ? "DQ" : result?.Rank > 0 ? result.Rank.ToString() : string.Empty,
|
||||
entry.EntryNo,
|
||||
string.IsNullOrWhiteSpace(entry.TeamName) ? entry.ParticipantName : entry.TeamName,
|
||||
projectName,
|
||||
entry.GroupName,
|
||||
result?.DifficultyScore.ToString("F2", System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
result?.PerformanceScore.ToString("F2", System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
result?.RequiredScore.ToString("F2", System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
result?.DeductionScore.ToString("F3", System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
result == null ? string.Empty : ScoreDisplayService.DeductionFactor(result.CalculationJson)?.ToString("F3", System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
result?.TotalScore.HasValue == true ? result.TotalScore.Value.ToString("F2", System.Globalization.CultureInfo.InvariantCulture) : result?.Score ?? string.Empty,
|
||||
status,
|
||||
result?.ResultVersion.ToString() ?? string.Empty,
|
||||
result == null ? string.Empty : FmtTime(result.ReviewTime)
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var headers = new[] { "名次", "出场编号", "姓名/队名", "项目", "组别", "难度分", "表现分", "规定分", "扣分合计", "得分系数M", "总分", "状态", "成绩版本", "确认时间" };
|
||||
var bytes = await new WorkbenchListExcelExporter("成绩册", $"成绩册全名单(导出时间:{DateTime.Now:yyyy-MM-dd HH:mm})", headers, rows,
|
||||
new double[] { 8, 16, 16, 16, 12, 10, 10, 10, 10, 12, 10, 10, 10, 20 }).ExportAsync();
|
||||
return ExcelFile(bytes, $"成绩册全名单_{input.EventId}_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出评分单明细(按场次/项目,逐出场逐裁判列出评分项与提交信息)
|
||||
/// </summary>
|
||||
@ -2052,6 +2170,11 @@ public class WorkbenchController : IDynamicApiController
|
||||
|
||||
#region 辅助方法
|
||||
|
||||
private Task<bool> HasPendingResubmissionAsync(long sessionEntryId) =>
|
||||
_correctionRepository.AsQueryable().AnyAsync(c => !c.DeleteMark &&
|
||||
c.SessionEntryId == sessionEntryId &&
|
||||
(c.CorrectionStatus == 10 || c.CorrectionStatus == 30));
|
||||
|
||||
/// <summary>
|
||||
/// 获取场次在秩序册/自动编排中的全局顺序。
|
||||
/// 秩序册导入及花样赛自动编排生成的场次号末段均保存该顺序;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user