feat: 支持按项目聚合检录流程

This commit is contained in:
ltx 2026-09-20 17:40:52 +08:00
parent 00155c596e
commit bb493c350d
3 changed files with 44 additions and 47 deletions

View File

@ -49,13 +49,12 @@ public partial class CheckinController
var activeTasks = await GetActiveEventTasksAsync(eventId);
var context = await BuildProgressContextAsync(options, activeTasks);
// 未指定项目组别时投屏当前检录中的项目组别。
// 未指定项目时投屏当前检录项目,并覆盖该项目下全部组别。
var targetProjectId = eventProjectId ?? context.CurrentCheckin?.EventProjectId;
var targetGroupId = groupId ?? context.CurrentCheckin?.GroupId;
var units = !targetProjectId.HasValue || !targetGroupId.HasValue
var units = !targetProjectId.HasValue
? new List<CheckinScreenCastUnitDto>()
: options.Units
.Where(x => x.EventProjectId == targetProjectId.Value && x.GroupId == targetGroupId.Value)
.Where(x => x.EventProjectId == targetProjectId.Value)
.OrderBy(x => x.Order)
.Select(unit =>
{
@ -150,7 +149,7 @@ public partial class CheckinController
// 赛事检录顺序完全跟随一检进度:一检开始即为检录中,一检全部通过即切换下一项目。
var stages = options.Units
.GroupBy(x => new { x.EventProjectId, x.GroupId, x.ProjectName, x.GroupName })
.GroupBy(x => new { x.EventProjectId, x.ProjectName })
.Select(group =>
{
var groupUnits = group.OrderBy(x => x.Order).ToList();
@ -160,10 +159,10 @@ public partial class CheckinController
return new CheckinProgressStageDto
{
EventProjectId = group.Key.EventProjectId,
GroupId = group.Key.GroupId,
GroupId = null,
ProjectName = group.Key.ProjectName,
GroupName = group.Key.GroupName,
DisplayName = $"{group.Key.ProjectName} - {group.Key.GroupName}",
GroupName = string.Empty,
DisplayName = group.Key.ProjectName,
Order = groupUnits[0].Order,
TotalUnits = total,
CheckedUnits = checkedCount,
@ -211,8 +210,7 @@ public partial class CheckinController
if (context.CurrentCheckin != null)
{
var checkingUnit = options.Units
.Where(x => x.EventProjectId == context.CurrentCheckin.EventProjectId &&
x.GroupId == context.CurrentCheckin.GroupId)
.Where(x => x.EventProjectId == context.CurrentCheckin.EventProjectId)
.OrderBy(x => x.Order)
.FirstOrDefault(x => !context.IsPassed(x.UnitKey, firstCheckinNo));
if (checkingUnit != null)
@ -295,7 +293,7 @@ public partial class CheckinController
return activeTasks.Count > 1 ? 2 : 3;
var stage = context.CurrentCheckin;
return stage != null && stage.EventProjectId == unit.EventProjectId && stage.GroupId == unit.GroupId
return stage != null && stage.EventProjectId == unit.EventProjectId
? 1
: 0;
}
@ -307,7 +305,7 @@ public partial class CheckinController
if (context.IsPassed(unit.UnitKey, context.FirstCheckinNo)) return 2;
var stage = context.CurrentCheckin;
return stage != null && stage.EventProjectId == unit.EventProjectId && stage.GroupId == unit.GroupId
return stage != null && stage.EventProjectId == unit.EventProjectId
? 1
: 0;
}

View File

@ -130,8 +130,8 @@ public partial class CheckinController : IDynamicApiController
await _recordRepository.UpdateAsync(record);
}
// 保持页面当前项目/组别筛选;UnitKey 已独立校验检录项归属。
return await BuildContextAsync(task, input.EventProjectId, input.GroupId);
// 保持页面当前项目筛选;UnitKey 已独立校验检录项归属。
return await BuildContextAsync(task, input.EventProjectId, null);
}
[HttpPost("start")]
@ -147,24 +147,21 @@ public partial class CheckinController : IDynamicApiController
var options = await _scheduleService.GetOptionsAsync(task.EventId);
var project = options.Projects.FirstOrDefault(x => x.EventProjectId == input.EventProjectId)
?? throw Oops.Bah("所选参赛项目不属于当前赛事秩序册");
var group = project.Groups.FirstOrDefault(x => x.Id == input.GroupId)
?? throw Oops.Bah("所选参赛组别不属于当前参赛项目");
var units = options.Units
.Where(x => x.EventProjectId == input.EventProjectId && x.GroupId == input.GroupId)
.Where(x => x.EventProjectId == input.EventProjectId)
.OrderBy(x => x.Order).ToList();
if (units.Count == 0) throw Oops.Bah("所选项目组别没有可检录的参赛队伍");
if (units.Count == 0) throw Oops.Bah("所选项目没有可检录的参赛队伍");
// 记录当前检录进度,供运动员端与投屏读取"当前检录项目"。
task.CurrentEventProjectId = input.EventProjectId;
task.CurrentGroupId = input.GroupId;
task.CurrentGroupName = group.GroupName;
task.GroupOrder = group.Order;
task.CurrentGroupId = null;
task.CurrentGroupName = "全部组别";
task.GroupOrder = project.Groups.Count == 0 ? 0 : project.Groups.Min(x => x.Order);
task.TotalGroups = project.Groups.Count;
task.NextGroupName = project.Groups.FirstOrDefault(x => x.Order > group.Order)?.GroupName;
task.NextGroupName = null;
task.CurrentSessionKey = units[0].UnitKey;
task.CurrentSessionOrder = units[0].Order;
task.CurrentSessionName = $"{project.ProjectName}-{group.GroupName}";
task.CurrentSessionName = project.ProjectName;
task.CurrentSessionVenue = units[0].VenueName;
task.UpdatedUserId = checkerUserId;
task.UpdatedTime = DateTime.Now;
@ -179,19 +176,19 @@ public partial class CheckinController : IDynamicApiController
x.Id == project.ProjectId && !x.DeleteMark && x.Status == AvailabilityStatus.Enable);
if (projectEntity == null) throw Oops.Bah("参赛项目不存在或已停用");
var noticeName = $"{project.ProjectName}-{group.GroupName}";
var noticeName = project.ProjectName;
(total, success, failed) = await NotifyUnitsAsync(
units, projectEntity.ProjectType, options.EventName, noticeName);
}
var context = await BuildContextAsync(task, input.EventProjectId, input.GroupId);
var context = await BuildContextAsync(task, input.EventProjectId, null);
return new StartCheckinOutput
{
TaskId = task.Id,
EventProjectId = project.EventProjectId,
ProjectName = project.ProjectName,
GroupId = group.Id,
GroupName = group.GroupName,
GroupId = 0,
GroupName = "全部组别",
UnitCount = units.Count,
NoticeTotal = total,
SmsSuccessCount = success,
@ -226,9 +223,7 @@ public partial class CheckinController : IDynamicApiController
await EnsurePreviousPassedAsync(task, unit.UnitKey);
await UpsertRecordAsync(task, unit, checkerUserId, true);
var context = await BuildContextAsync(task,
input.EventProjectId ?? unit.EventProjectId,
input.GroupId ?? unit.GroupId ?? 0);
var context = await BuildContextAsync(task, input.EventProjectId ?? unit.EventProjectId, null);
return new ScanCheckinOutput
{
Success = true,
@ -275,7 +270,7 @@ public partial class CheckinController : IDynamicApiController
task.UpdatedTime = DateTime.Now;
await _taskRepository.UpdateAsync(task);
return await BuildContextAsync(task, input.EventProjectId, input.GroupId);
return await BuildContextAsync(task, input.EventProjectId, null);
}
/// <summary>按扫码内容定位秩序册列;支持号码布、报名人员ID和姓名。</summary>
@ -588,16 +583,20 @@ public partial class CheckinController : IDynamicApiController
: EventStatus.NotStarted;
var options = await _scheduleService.GetOptionsAsync(task.EventId);
var selectedProject = options.Projects.FirstOrDefault(x => x.EventProjectId == eventProjectId)
?? options.Projects.FirstOrDefault();
var orderedProjects = options.Projects
.OrderBy(project => options.Units
.Where(unit => unit.EventProjectId == project.EventProjectId)
.Select(unit => unit.Order)
.DefaultIfEmpty(int.MaxValue)
.Min())
.ToList();
var selectedProject = orderedProjects.FirstOrDefault(x => x.EventProjectId == eventProjectId)
?? orderedProjects.FirstOrDefault();
if (selectedProject == null) throw Oops.Bah("当前赛事秩序册没有可检录项目");
var selectedGroup = selectedProject.Groups.FirstOrDefault(x => x.Id == groupId)
?? selectedProject.Groups.FirstOrDefault();
if (selectedGroup == null) throw Oops.Bah("所选参赛项目没有可检录组别");
var allUnits = options.Units.OrderBy(x => x.Order).ToList();
var units = allUnits
.Where(x => x.EventProjectId == selectedProject.EventProjectId && x.GroupId == selectedGroup.Id)
.Where(x => x.EventProjectId == selectedProject.EventProjectId)
.OrderBy(x => x.Order).ToList();
var allUnitKeys = allUnits.Select(x => x.UnitKey).ToHashSet();
var records = await _recordRepository.AsQueryable()
@ -657,11 +656,11 @@ public partial class CheckinController : IDynamicApiController
};
}).ToList();
var statusUnits = allStatusUnits
.Where(x => x.EventProjectId == selectedProject.EventProjectId && x.GroupId == selectedGroup.Id)
.Where(x => x.EventProjectId == selectedProject.EventProjectId)
.ToList();
// 为项目、组别选择区计算当前检次概况,全部完成时由小程序显示绿色状态。
foreach (var project in options.Projects)
foreach (var project in orderedProjects)
{
var projectUnits = allStatusUnits.Where(x => x.EventProjectId == project.EventProjectId).ToList();
ApplyProgress(projectUnits, out var projectTotal, out var projectChecked,
@ -697,13 +696,13 @@ public partial class CheckinController : IDynamicApiController
CurrentRound = task.CheckinNo,
RoundName = ToCheckinName(task.CheckinNo),
SelectedEventProjectId = selectedProject.EventProjectId,
SelectedGroupId = selectedGroup.Id,
CurrentGroupId = selectedGroup.Id,
CurrentGroupName = selectedGroup.GroupName,
Projects = options.Projects,
SelectedGroupId = 0,
CurrentGroupId = null,
CurrentGroupName = "全部组别",
Projects = orderedProjects,
Units = statusUnits,
PendingUnits = statusUnits.Where(x => x.CanCheck && !x.Checked).ToList(),
// 已检录跟随当前选中的项目和组别,便于现场聚焦操作。
// 已检录跟随当前选中的项目,覆盖该项目全部组别。
CheckedUnits = statusUnits.Where(x => x.Checked).ToList(),
LockedUnits = statusUnits.Where(x => !x.CanCheck).ToList(),
CanCheck = eventCanCheckin,

View File

@ -411,8 +411,8 @@ public class StartCheckinInput
[Required(ErrorMessage = "赛事项目ID不能为空")]
public long EventProjectId { get; set; }
[Required(ErrorMessage = "参赛组别ID不能为空")]
public long GroupId { get; set; }
/// <summary>兼容旧客户端;项目级检录时无需传组别。</summary>
public long? GroupId { get; set; }
}
/// <summary>