Compare commits

...

2 Commits

Author SHA1 Message Date
ltx
f4e5db3f52 Merge branch 'main' of http://8.153.108.90:3000/YD/YD_Event.Admin 2026-10-30 10:42:45 +08:00
ltx
e54112e08d fix: 修复赛事编排并完善成绩与检录大屏数据"
-m "修复赛事编排确认时 EventSessionEntry 写入无效 RegistrationItemId 导致的外键冲突。"
  -m "完善成绩展示数据,补充参赛单位名称,支持成绩管理矩阵及大屏展示。"
  -m "调整检录大屏进度规则:赛事检录顺序、当前检录项目及队伍状态统一跟随一检进度。"
  -m "当前比赛默认根据三检通过记录更新;赛事仅配置两检时,允许二检作为上场标记。"
  -m "一检全部完成后自动切换下一项目,全部完成后不再错误展示全部参赛队伍。
2026-10-30 10:42:40 +08:00
5 changed files with 381 additions and 4 deletions

View File

@ -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>

View File

@ -39,6 +39,12 @@ public class ResultExportQueryInput
/// 是否包含被取消比赛资格(DQ)的记录,默认不包含
/// </summary>
public bool IncludeDq { get; set; }
/// <summary>
/// 导出出场全名单:无成绩、DQ、退赛和作废记录均保留;忽略成绩状态与关键词筛选。
/// 默认关闭,以兼容原有成绩导出调用方。
/// </summary>
public bool IncludeUnscored { get; set; }
}
/// <summary>

View File

@ -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();
}

View File

@ -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("出场记录不存在");
}
}

View File

@ -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>
/// 获取场次在秩序册/自动编排中的全局顺序。
/// 秩序册导入及花样赛自动编排生成的场次号末段均保存该顺序;