update:赛事检录顺序&检录大屏

This commit is contained in:
ltx 2026-09-18 17:39:01 +08:00
parent 93567c00b8
commit 320c7afc6d
4 changed files with 93 additions and 41 deletions

View File

@ -52,28 +52,30 @@ public partial class CheckinController
// 未指定项目组别时投屏当前检录中的项目组别。
var targetProjectId = eventProjectId ?? context.CurrentCheckin?.EventProjectId;
var targetGroupId = groupId ?? context.CurrentCheckin?.GroupId;
var units = options.Units
.Where(x => (!targetProjectId.HasValue || x.EventProjectId == targetProjectId.Value) &&
(!targetGroupId.HasValue || x.GroupId == targetGroupId.Value))
.OrderBy(x => x.Order)
.Select(unit =>
{
var status = ResolveUnitStatus(unit, context, activeTasks);
return new CheckinScreenCastUnitDto
var units = !targetProjectId.HasValue || !targetGroupId.HasValue
? new List<CheckinScreenCastUnitDto>()
: options.Units
.Where(x => x.EventProjectId == targetProjectId.Value && x.GroupId == targetGroupId.Value)
.OrderBy(x => x.Order)
.Select(unit =>
{
UnitKey = unit.UnitKey,
Order = unit.Order,
SessionIndex = unit.SessionIndex,
VenueName = unit.VenueName,
ProjectName = unit.ProjectName,
GroupName = unit.GroupName,
TeamName = ResolveTeamName(unit),
UnitName = unit.UnitName,
AthleteNames = unit.Athletes.Select(x => x.Name).ToList(),
CheckinStatus = status,
CheckinStatusText = ToCheckinStatusText(status)
};
}).ToList();
// 投屏的“当前检录项目”卡片只表达一检状态,避免二检/三检进度覆盖为“已上场”。
var status = ResolveFirstCheckinUnitStatus(unit, context);
return new CheckinScreenCastUnitDto
{
UnitKey = unit.UnitKey,
Order = unit.Order,
SessionIndex = unit.SessionIndex,
VenueName = unit.VenueName,
ProjectName = unit.ProjectName,
GroupName = unit.GroupName,
TeamName = ResolveTeamName(unit),
UnitName = unit.UnitName,
AthleteNames = unit.Athletes.Select(x => x.Name).ToList(),
CheckinStatus = status,
CheckinStatusText = ToCheckinStatusText(status)
};
}).ToList();
var now = DateTime.Now;
return new CheckinScreenCastOutput
@ -136,17 +138,17 @@ public partial class CheckinController
}
var firstCheckinNo = activeTasks.Count == 0 ? 1 : activeTasks.Min(x => x.CheckinNo);
var lastCheckinNo = activeTasks.Count == 0 ? 1 : activeTasks.Max(x => x.CheckinNo);
var onStageCheckinNo = ResolveOnStageCheckinNo(activeTasks);
var context = new CheckinProgressContext
{
PassedByKey = passedByKey,
CheckerNameByKey = checkerNameByKey,
FirstCheckinNo = firstCheckinNo,
LastCheckinNo = lastCheckinNo,
OnStageCheckinNo = onStageCheckinNo,
ActiveCheckinNumbers = activeTasks.Select(x => x.CheckinNo).OrderBy(x => x).ToList()
};
// 按秩序册顺序汇总项目组别阶段;一检开始即为检录中,三检全部通过即为已完成。
// 赛事检录顺序完全跟随一检进度:一检开始即为检录中,一检全部通过即切换下一项目。
var stages = options.Units
.GroupBy(x => new { x.EventProjectId, x.GroupId, x.ProjectName, x.GroupName })
.Select(group =>
@ -155,8 +157,6 @@ public partial class CheckinController
var total = groupUnits.Count;
var checkedCount = groupUnits.Count(x =>
context.IsPassed(x.UnitKey, firstCheckinNo));
var finishedCount = groupUnits.Count(x =>
context.IsPassed(x.UnitKey, lastCheckinNo));
return new CheckinProgressStageDto
{
EventProjectId = group.Key.EventProjectId,
@ -168,7 +168,7 @@ public partial class CheckinController
TotalUnits = total,
CheckedUnits = checkedCount,
ProgressPercent = total == 0 ? 0 : (int)Math.Round(checkedCount * 100d / total),
StageStatus = ResolveStageStatus(total, checkedCount, finishedCount)
StageStatus = ResolveStageStatus(total, checkedCount)
};
})
.OrderBy(x => x.Order)
@ -180,11 +180,12 @@ public partial class CheckinController
foreach (var stage in stages) stage.StageStatusText = ToStageStatusText(stage.StageStatus);
context.Stages = stages;
context.CurrentCheckin = stages.FirstOrDefault(x => x.StageStatus == 1);
// 当前检录项目同样只由一检推进;尚未扫入第一位选手时也展示下一待检项目。
context.CurrentCheckin = stages.FirstOrDefault(x => x.StageStatus is 1 or 2);
// 当前正在比赛:末检次已通过且顺序最靠后的一列。
// 当前正在比赛:三检通过视为上场;赛事仅配置两检时,二检通过即视为上场。
var competingUnit = options.Units
.Where(x => context.IsPassed(x.UnitKey, lastCheckinNo))
.Where(x => context.IsPassed(x.UnitKey, onStageCheckinNo))
.OrderByDescending(x => x.Order)
.FirstOrDefault();
if (competingUnit != null)
@ -213,7 +214,7 @@ public partial class CheckinController
.Where(x => x.EventProjectId == context.CurrentCheckin.EventProjectId &&
x.GroupId == context.CurrentCheckin.GroupId)
.OrderBy(x => x.Order)
.FirstOrDefault(x => !context.IsPassed(x.UnitKey, lastCheckinNo));
.FirstOrDefault(x => !context.IsPassed(x.UnitKey, firstCheckinNo));
if (checkingUnit != null)
{
context.CurrentCheckin.TeamName = ResolveTeamName(checkingUnit);
@ -289,7 +290,7 @@ public partial class CheckinController
private static int ResolveUnitStatus(
CheckinOrderBookUnitDto unit, CheckinProgressContext context, List<CheckinTask> activeTasks)
{
if (context.IsPassed(unit.UnitKey, context.LastCheckinNo)) return 3;
if (context.IsPassed(unit.UnitKey, context.OnStageCheckinNo)) return 3;
if (context.IsPassed(unit.UnitKey, context.FirstCheckinNo))
return activeTasks.Count > 1 ? 2 : 3;
@ -299,9 +300,21 @@ public partial class CheckinController
: 0;
}
private static int ResolveStageStatus(int total, int checkedCount, int finishedCount)
/// <summary>检录大屏当前项目中的队伍状态仅跟随一检。</summary>
private static int ResolveFirstCheckinUnitStatus(
CheckinOrderBookUnitDto unit, CheckinProgressContext context)
{
if (total > 0 && finishedCount == total) return 4;
if (context.IsPassed(unit.UnitKey, context.FirstCheckinNo)) return 2;
var stage = context.CurrentCheckin;
return stage != null && stage.EventProjectId == unit.EventProjectId && stage.GroupId == unit.GroupId
? 1
: 0;
}
private static int ResolveStageStatus(int total, int checkedCount)
{
if (total > 0 && checkedCount == total) return 4;
if (checkedCount > 0) return 1;
return 3;
}
@ -340,7 +353,7 @@ public partial class CheckinController
public Dictionary<string, Dictionary<int, DateTime?>> PassedByKey { get; init; } = new();
public Dictionary<string, Dictionary<int, string>> CheckerNameByKey { get; init; } = new();
public int FirstCheckinNo { get; init; } = 1;
public int LastCheckinNo { get; init; } = 1;
public int OnStageCheckinNo { get; init; } = 1;
public List<int> ActiveCheckinNumbers { get; init; } = new();
public List<CheckinProgressStageDto> Stages { get; set; } = new();
public CheckinProgressStageDto? CurrentCheckin { get; set; }

View File

@ -388,7 +388,7 @@ public partial class CheckinController : IDynamicApiController
.Select(x => x.SessionKey!).ToHashSet();
var activeTasks = await GetActiveEventTasksAsync(task.EventId);
// 末检次(默认三检)通过即视为已上场。
// 三检通过视为已上场;仅配置两检时,二检通过即视为已上场。
var onStageKeys = await GetOnStageKeysAsync(activeTasks, allUnitKeys);
HashSet<string> previousPassedKeys;
@ -506,19 +506,28 @@ public partial class CheckinController : IDynamicApiController
x.Status == AvailabilityStatus.Enable && !x.DeleteMark)
.OrderBy(x => x.CheckinNo).ToListAsync();
/// <summary>末检次通过的秩序册列即为已上场,供检录端与运动员端统一判定。</summary>
/// <summary>三检通过即为已上场;仅配置两检时由二检标记上场。</summary>
private async Task<HashSet<string>> GetOnStageKeysAsync(
List<CheckinTask> activeTasks, HashSet<string> validUnitKeys)
{
if (activeTasks.Count == 0) return new HashSet<string>();
var lastTask = activeTasks.OrderByDescending(x => x.CheckinNo).First();
var onStageCheckinNo = ResolveOnStageCheckinNo(activeTasks);
var onStageTask = activeTasks.First(x => x.CheckinNo == onStageCheckinNo);
var records = await _recordRepository.AsQueryable()
.Where(x => x.TaskId == lastTask.Id && x.SessionKey != null && x.Checked && !x.DeleteMark)
.Where(x => x.TaskId == onStageTask.Id && x.SessionKey != null && x.Checked && !x.DeleteMark)
.ToListAsync();
return records.Where(x => validUnitKeys.Contains(x.SessionKey!))
.Select(x => x.SessionKey!).ToHashSet();
}
/// <summary>上场检次固定为三检;未配置三检但配置了二检时使用二检。</summary>
private static int ResolveOnStageCheckinNo(List<CheckinTask> activeTasks)
{
if (activeTasks.Any(x => x.CheckinNo == 3)) return 3;
if (activeTasks.Any(x => x.CheckinNo == 2)) return 2;
return activeTasks.Count == 0 ? 1 : activeTasks.Min(x => x.CheckinNo);
}
private async Task<long> EnsureCheckerAsync()
{
var userId = _authManager.UserId;

View File

@ -603,11 +603,20 @@ public class WorkbenchController : IDynamicApiController
if (expectedJudgeCount <= 0 || submittedJudgeCount < expectedJudgeCount)
throw Oops.Bah("相关裁判评分单尚未全部提交,不能投屏");
var unitName = string.Empty;
if (result.MerchantCertificationId.HasValue)
{
var merchant = await _merchantCertificationRepository.GetFirstAsync(m =>
m.Id == result.MerchantCertificationId.Value && !m.DeleteMark);
unitName = merchant?.MerchantName ?? string.Empty;
}
var payload = new
{
EntryNo = entry.EntryNo,
ParticipantName = entry.ParticipantName,
TeamName = entry.TeamName,
UnitName = unitName,
GroupName = entry.GroupName,
DifficultyScore = result.DifficultyScore,
PerformanceScore = result.PerformanceScore,
@ -722,13 +731,25 @@ public class WorkbenchController : IDynamicApiController
r.DeductionScore,
r.TotalScore,
r.Score,
r.IsDisqualified
r.IsDisqualified,
r.MerchantCertificationId
})
.ToListAsync();
if (!results.Any())
throw Oops.Bah("当前项目组别没有可投屏的已确认成绩");
var rankingMerchantIds = results
.Where(r => r.MerchantCertificationId.HasValue)
.Select(r => r.MerchantCertificationId!.Value)
.Distinct()
.ToList();
var rankingMerchantNames = (await _merchantCertificationRepository.AsQueryable()
.Where(m => rankingMerchantIds.Contains(m.Id) && !m.DeleteMark)
.Select(m => new { m.Id, m.MerchantName })
.ToListAsync())
.ToDictionary(m => m.Id, m => m.MerchantName);
// 落库名次可能是成绩刚生成时连同“待复核”成绩一起计算的。
// 投屏时必须以上面已经过状态筛选的有效成绩重新排名,避免待复核成绩隐形占位。
var scoreRuleId = results.Select(r => r.ScoreRuleId).First();
@ -765,6 +786,9 @@ public class WorkbenchController : IDynamicApiController
r.EntryNo,
r.ParticipantName,
r.TeamName,
UnitName = r.MerchantCertificationId.HasValue
? rankingMerchantNames.GetValueOrDefault(r.MerchantCertificationId.Value)
: null,
r.GroupName,
r.TotalScore,
r.Score,

View File

@ -1868,7 +1868,10 @@
<summary>对报名了所选项目组别的人员发送检录通知;单人失败不影响整体流程。</summary>
</member>
<member name="M:YD_Event.Application.Client.CheckinController.GetOnStageKeysAsync(System.Collections.Generic.List{YD_Event.Core.Entities.CheckinTask},System.Collections.Generic.HashSet{System.String})">
<summary>末检次通过的秩序册列即为已上场,供检录端与运动员端统一判定。</summary>
<summary>三检通过即为已上场;仅配置两检时由二检标记上场。</summary>
</member>
<member name="M:YD_Event.Application.Client.CheckinController.ResolveOnStageCheckinNo(System.Collections.Generic.List{YD_Event.Core.Entities.CheckinTask})">
<summary>上场检次固定为三检;未配置三检但配置了二检时使用二检。</summary>
</member>
<member name="M:YD_Event.Application.Client.CheckinController.BuildProgressContextAsync(YD_Event.Application.Client.Dtos.CheckinEventOptionsDto,System.Collections.Generic.List{YD_Event.Core.Entities.CheckinTask})">
<summary>汇总各检次的检录记录,生成项目组别三态与当前检录、当前比赛信息。</summary>
@ -1879,6 +1882,9 @@
<member name="M:YD_Event.Application.Client.CheckinController.ResolveUnitStatus(YD_Event.Application.Client.Dtos.CheckinOrderBookUnitDto,YD_Event.Application.Client.CheckinController.CheckinProgressContext,System.Collections.Generic.List{YD_Event.Core.Entities.CheckinTask})">
<summary>秩序册列状态:0-等待中,1-检录中,2-已检录,3-已上场。</summary>
</member>
<member name="M:YD_Event.Application.Client.CheckinController.ResolveFirstCheckinUnitStatus(YD_Event.Application.Client.Dtos.CheckinOrderBookUnitDto,YD_Event.Application.Client.CheckinController.CheckinProgressContext)">
<summary>检录大屏当前项目中的队伍状态仅跟随一检。</summary>
</member>
<member name="T:YD_Event.Application.Client.CheckinController.CheckinProgressContext">
<summary>检录进度计算的中间上下文,避免重复查询检录记录。</summary>
</member>