YD_Event.Admin/YD_Event.Application/Client/CheckinController.Progress.cs

331 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using YD_Event.Application.Client.Dtos;
namespace YD_Event.Application.Client;
/// <summary>面向运动员、教练的检录进度查询与检录投屏。</summary>
public partial class CheckinController
{
[HttpGet("progress")]
[DisplayName("查看赛事检录进度")]
public async Task<CheckinProgressOutput> GetProgress([FromQuery] long eventId)
{
var userId = _authManager.UserId;
var options = await _scheduleService.GetOptionsAsync(eventId);
var activeTasks = await GetActiveEventTasksAsync(eventId);
var context = await BuildProgressContextAsync(options, activeTasks);
// 报名人员与当前登录用户按 UserId 关联;教练可看到同一报名单下的运动员。
var myPersonIds = await ResolveMyPersonIdsAsync(eventId, userId);
var mine = options.Units
.Where(x => x.RegistrationPersonIds.Any(myPersonIds.Contains))
.OrderBy(x => x.Order)
.Select(unit => BuildMineDto(unit, context, activeTasks, myPersonIds))
.ToList();
var now = DateTime.Now;
return new CheckinProgressOutput
{
EventId = options.EventId,
EventName = options.EventName,
ServerTime = now,
ServerTimeText = now.ToString("yyyy-MM-dd HH:mm:ss"),
WeekDayText = ToWeekDayText(now),
CurrentCheckin = context.CurrentCheckin,
CurrentCompeting = context.CurrentCompeting,
Stages = context.Stages,
Mine = mine
};
}
[HttpGet("screen-cast")]
[AllowAnonymous]
[DisplayName("检录投屏")]
public async Task<CheckinScreenCastOutput> GetScreenCast(
[FromQuery] long eventId,
[FromQuery] long? eventProjectId = null,
[FromQuery] long? groupId = null)
{
var options = await _scheduleService.GetOptionsAsync(eventId);
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 = 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
{
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
{
EventId = options.EventId,
EventName = options.EventName,
ServerTime = now,
ServerTimeText = now.ToString("yyyy-MM-dd HH:mm:ss"),
WeekDayText = ToWeekDayText(now),
CurrentCheckin = context.CurrentCheckin,
CurrentCompeting = context.CurrentCompeting,
Stages = context.Stages,
Units = units
};
}
/// <summary>汇总各检次的检录记录,生成项目组别三态与当前检录、当前比赛信息。</summary>
private async Task<CheckinProgressContext> BuildProgressContextAsync(
CheckinEventOptionsDto options, List<CheckinTask> activeTasks)
{
var validUnitKeys = options.Units.Select(x => x.UnitKey).ToHashSet();
var taskIds = activeTasks.Select(x => x.Id).ToList();
var records = taskIds.Count == 0
? new List<CheckinRecord>()
: await _recordRepository.AsQueryable()
.Where(x => taskIds.Contains(x.TaskId) && x.SessionKey != null && x.Checked && !x.DeleteMark)
.ToListAsync();
var taskCheckinNoMap = activeTasks.ToDictionary(x => x.Id, x => x.CheckinNo);
var passedByKey = new Dictionary<string, Dictionary<int, DateTime?>>();
foreach (var record in records.Where(x => validUnitKeys.Contains(x.SessionKey!)))
{
if (!taskCheckinNoMap.TryGetValue(record.TaskId, out var checkinNo)) continue;
if (!passedByKey.TryGetValue(record.SessionKey!, out var rounds))
{
rounds = new Dictionary<int, DateTime?>();
passedByKey[record.SessionKey!] = rounds;
}
rounds[checkinNo] = record.CheckedTime;
}
var firstCheckinNo = activeTasks.Count == 0 ? 1 : activeTasks.Min(x => x.CheckinNo);
var lastCheckinNo = activeTasks.Count == 0 ? 1 : activeTasks.Max(x => x.CheckinNo);
var context = new CheckinProgressContext
{
PassedByKey = passedByKey,
FirstCheckinNo = firstCheckinNo,
LastCheckinNo = lastCheckinNo,
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 =>
{
var groupUnits = group.OrderBy(x => x.Order).ToList();
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,
GroupId = group.Key.GroupId,
ProjectName = group.Key.ProjectName,
GroupName = group.Key.GroupName,
DisplayName = $"{group.Key.ProjectName} - {group.Key.GroupName}",
Order = groupUnits[0].Order,
TotalUnits = total,
CheckedUnits = checkedCount,
ProgressPercent = total == 0 ? 0 : (int)Math.Round(checkedCount * 100d / total),
StageStatus = ResolveStageStatus(total, checkedCount, finishedCount)
};
})
.OrderBy(x => x.Order)
.ToList();
// 未开始的阶段中最靠前的一个显示"即将开始",其余保持"等待中"。
var firstWaiting = stages.FirstOrDefault(x => x.StageStatus == 3);
if (firstWaiting != null) firstWaiting.StageStatus = 2;
foreach (var stage in stages) stage.StageStatusText = ToStageStatusText(stage.StageStatus);
context.Stages = stages;
context.CurrentCheckin = stages.FirstOrDefault(x => x.StageStatus == 1);
// 当前正在比赛:末检次已通过且顺序最靠后的一列。
var competingUnit = options.Units
.Where(x => context.IsPassed(x.UnitKey, lastCheckinNo))
.OrderByDescending(x => x.Order)
.FirstOrDefault();
if (competingUnit != null)
{
context.CurrentCompeting = new CheckinProgressStageDto
{
EventProjectId = competingUnit.EventProjectId,
GroupId = competingUnit.GroupId,
ProjectName = competingUnit.ProjectName,
GroupName = competingUnit.GroupName,
DisplayName = $"{competingUnit.ProjectName} - {competingUnit.GroupName}",
Order = competingUnit.Order,
StageStatus = 1,
StageStatusText = "比赛中",
TeamName = ResolveTeamName(competingUnit),
AthleteNames = competingUnit.Athletes.Select(x => x.Name).ToList()
};
}
if (context.CurrentCheckin != null)
{
var checkingUnit = options.Units
.Where(x => x.EventProjectId == context.CurrentCheckin.EventProjectId &&
x.GroupId == context.CurrentCheckin.GroupId)
.OrderBy(x => x.Order)
.FirstOrDefault(x => !context.IsPassed(x.UnitKey, lastCheckinNo));
if (checkingUnit != null)
{
context.CurrentCheckin.TeamName = ResolveTeamName(checkingUnit);
context.CurrentCheckin.AthleteNames = checkingUnit.Athletes.Select(x => x.Name).ToList();
}
}
return context;
}
private CheckinProgressMineDto BuildMineDto(
CheckinOrderBookUnitDto unit,
CheckinProgressContext context,
List<CheckinTask> activeTasks,
HashSet<long> myPersonIds)
{
var status = ResolveUnitStatus(unit, context, activeTasks);
var athleteNames = unit.Athletes.Where(x => myPersonIds.Contains(x.Id)).Select(x => x.Name).ToList();
if (athleteNames.Count == 0) athleteNames = unit.Athletes.Select(x => x.Name).ToList();
return new CheckinProgressMineDto
{
UnitKey = unit.UnitKey,
EventProjectId = unit.EventProjectId,
GroupId = unit.GroupId,
ProjectName = unit.ProjectName,
GroupName = unit.GroupName,
DisplayName = $"{unit.ProjectName} - {unit.GroupName}",
AthleteNames = athleteNames,
TeamName = ResolveTeamName(unit),
UnitName = string.IsNullOrWhiteSpace(unit.UnitName) ? "/" : unit.UnitName,
SessionIndex = unit.SessionIndex,
VenueName = unit.VenueName,
CheckinStatus = status,
CheckinStatusText = ToCheckinStatusText(status),
Rounds = context.ActiveCheckinNumbers.Select(checkinNo => new CheckinProgressRoundDto
{
CheckinNo = checkinNo,
CheckinName = ToCheckinName(checkinNo),
Passed = context.IsPassed(unit.UnitKey, checkinNo),
CheckedTime = context.GetCheckedTime(unit.UnitKey, checkinNo)
}).ToList(),
Notice = status switch
{
1 => "请前往检录口",
2 => "已完成检录,请等待上场",
3 => "已上场",
_ => "请留意检录通知"
}
};
}
/// <summary>我的报名人员:本人报名记录 + 我作为联系人提交的报名单下全部人员。</summary>
private async Task<HashSet<long>> ResolveMyPersonIdsAsync(long eventId, long userId)
{
var mine = await _personRepository.AsQueryable()
.Where(x => x.UserId == userId && !x.DeleteMark)
.ToListAsync();
var registrationIds = await _registrationRepository.AsQueryable()
.Where(x => x.EventId == eventId && x.CreatedUserId == userId && !x.DeleteMark)
.Select(x => x.Id).ToListAsync();
var teamPersons = registrationIds.Count == 0
? new List<EventRegistrationPerson>()
: await _personRepository.AsQueryable()
.Where(x => registrationIds.Contains(x.RegistrationId) && !x.DeleteMark)
.ToListAsync();
return mine.Select(x => x.Id).Concat(teamPersons.Select(x => x.Id)).ToHashSet();
}
/// <summary>秩序册列状态:0-等待中,1-检录中,2-已检录,3-已上场。</summary>
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.FirstCheckinNo))
return activeTasks.Count > 1 ? 2 : 3;
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, int finishedCount)
{
if (total > 0 && finishedCount == total) return 4;
if (checkedCount > 0) return 1;
return 3;
}
private static string ResolveTeamName(CheckinOrderBookUnitDto unit)
{
if (!string.IsNullOrWhiteSpace(unit.TeamName)) return unit.TeamName;
return string.IsNullOrWhiteSpace(unit.UnitName) ? "/" : unit.UnitName;
}
private static string ToStageStatusText(int stageStatus) => stageStatus switch
{
1 => "检录中", 2 => "即将开始", 4 => "已完成", _ => "等待中"
};
private static string ToCheckinStatusText(int status) => status switch
{
1 => "检录中", 2 => "已检录", 3 => "已上场", _ => "等待中"
};
private static string ToWeekDayText(DateTime time) => time.DayOfWeek switch
{
DayOfWeek.Monday => "星期一",
DayOfWeek.Tuesday => "星期二",
DayOfWeek.Wednesday => "星期三",
DayOfWeek.Thursday => "星期四",
DayOfWeek.Friday => "星期五",
DayOfWeek.Saturday => "星期六",
_ => "星期日"
};
/// <summary>检录进度计算的中间上下文,避免重复查询检录记录。</summary>
private sealed class CheckinProgressContext
{
public Dictionary<string, Dictionary<int, DateTime?>> PassedByKey { get; init; } = new();
public int FirstCheckinNo { get; init; } = 1;
public int LastCheckinNo { get; init; } = 1;
public List<int> ActiveCheckinNumbers { get; init; } = new();
public List<CheckinProgressStageDto> Stages { get; set; } = new();
public CheckinProgressStageDto? CurrentCheckin { get; set; }
public CheckinProgressStageDto? CurrentCompeting { get; set; }
public bool IsPassed(string unitKey, int checkinNo) =>
PassedByKey.TryGetValue(unitKey, out var rounds) && rounds.ContainsKey(checkinNo);
public DateTime? GetCheckedTime(string unitKey, int checkinNo) =>
PassedByKey.TryGetValue(unitKey, out var rounds) && rounds.TryGetValue(checkinNo, out var time)
? time
: null;
}
}