using YD_Event.Application.Auth; using YD_Event.Application.Client.Dtos; namespace YD_Event.Application.Client; /// 小程序检录员按秩序册列进行顺序检录。 [ApiDescriptionSettings("微信小程序接口")] [Authorize] public class CheckinController : IDynamicApiController { private readonly ISqlSugarRepository _userRepository; private readonly ISqlSugarRepository _taskRepository; private readonly ISqlSugarRepository _recordRepository; private readonly CheckinScheduleService _scheduleService; private readonly AuthManager _authManager; public CheckinController( ISqlSugarRepository userRepository, ISqlSugarRepository taskRepository, ISqlSugarRepository recordRepository, CheckinScheduleService scheduleService, AuthManager authManager) { _userRepository = userRepository; _taskRepository = taskRepository; _recordRepository = recordRepository; _scheduleService = scheduleService; _authManager = authManager; } [HttpGet("current")] [DisplayName("获取当前赛事检录任务")] public async Task GetCurrent( [FromQuery] long? eventProjectId = null, [FromQuery] long? groupId = null) { var userId = await EnsureCheckerAsync(); var task = await GetCurrentTaskAsync(userId); return task == null ? null : await BuildContextAsync(task, eventProjectId, groupId); } [HttpPost("status")] [UnitOfWork] [DisplayName("更新秩序册列当前检次状态")] public async Task UpdateStatus([FromBody] UpdateCheckinStatusInput input) { var checkerUserId = await EnsureCheckerAsync(); var task = await GetCurrentTaskAsync(checkerUserId); if (task == null) throw Oops.Bah("当前账号尚未由后台绑定赛事检录任务"); var options = await _scheduleService.GetOptionsAsync(task.EventId); var unit = CheckinScheduleService.ResolveUnit(options, input.UnitKey); if (task.CheckinNo > 1) { var previousTask = await GetEventTaskAsync(task.EventId, task.CheckinNo - 1); var previousPassed = previousTask != null && await _recordRepository.IsAnyAsync(x => x.TaskId == previousTask.Id && x.SessionKey == unit.UnitKey && x.Checked && !x.DeleteMark); if (!previousPassed) throw Oops.Bah($"该秩序册列尚未通过{ToCheckinName(task.CheckinNo - 1)},不能进行{ToCheckinName(task.CheckinNo)}"); } if (!input.Checked) { var laterTasks = await _taskRepository.AsQueryable() .Where(x => x.EventId == task.EventId && x.CheckinNo > task.CheckinNo && x.TaskStatus == 1 && x.Status == AvailabilityStatus.Enable && !x.DeleteMark) .ToListAsync(); var laterTaskIds = laterTasks.Select(x => x.Id).ToList(); if (laterTaskIds.Count > 0 && await _recordRepository.IsAnyAsync(x => laterTaskIds.Contains(x.TaskId) && x.SessionKey == unit.UnitKey && x.Checked && !x.DeleteMark)) throw Oops.Bah("该秩序册列已通过后续检录,不能取消当前检录"); } var record = await _recordRepository.GetFirstAsync(x => x.TaskId == task.Id && x.SessionKey == unit.UnitKey && !x.DeleteMark); if (record == null) { record = new CheckinRecord { TaskId = task.Id, Round = task.CheckinNo, SessionKey = unit.UnitKey, SessionOrder = unit.Order, GroupId = unit.GroupId, RegistrationPersonId = unit.RegistrationPersonIds.FirstOrDefault(), CheckerUserId = checkerUserId, Checked = input.Checked, CheckedTime = input.Checked ? DateTime.Now : null, CreatedUserId = checkerUserId, CreatedTime = DateTime.Now }; await _recordRepository.InsertAsync(record); } else { record.Round = task.CheckinNo; record.SessionOrder = unit.Order; record.GroupId = unit.GroupId; record.CheckerUserId = checkerUserId; record.Checked = input.Checked; record.CheckedTime = input.Checked ? DateTime.Now : null; record.UpdatedUserId = checkerUserId; record.UpdatedTime = DateTime.Now; await _recordRepository.UpdateAsync(record); } // 保持页面当前项目/组别筛选;UnitKey 已独立校验检录项归属。 return await BuildContextAsync(task, input.EventProjectId, input.GroupId); } private async Task BuildContextAsync(CheckinTask task, long? eventProjectId, long? groupId) { var options = await _scheduleService.GetOptionsAsync(task.EventId); var selectedProject = options.Projects.FirstOrDefault(x => x.EventProjectId == eventProjectId) ?? options.Projects.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) .OrderBy(x => x.Order).ToList(); var allUnitKeys = allUnits.Select(x => x.UnitKey).ToHashSet(); var records = await _recordRepository.AsQueryable() .Where(x => x.TaskId == task.Id && x.SessionKey != null && !x.DeleteMark) .ToListAsync(); var checkedKeys = records.Where(x => x.Checked && allUnitKeys.Contains(x.SessionKey!)) .Select(x => x.SessionKey!).ToHashSet(); HashSet previousPassedKeys; if (task.CheckinNo == 1) { previousPassedKeys = allUnitKeys; } else { var previousTask = await GetEventTaskAsync(task.EventId, task.CheckinNo - 1); var previousRecords = previousTask == null ? new List() : await _recordRepository.AsQueryable() .Where(x => x.TaskId == previousTask.Id && x.SessionKey != null && x.Checked && !x.DeleteMark) .ToListAsync(); previousPassedKeys = previousRecords.Where(x => allUnitKeys.Contains(x.SessionKey!)) .Select(x => x.SessionKey!).ToHashSet(); } var allStatusUnits = allUnits.Select(unit => { var previousPassed = previousPassedKeys.Contains(unit.UnitKey); return new CheckinOrderBookUnitStatusDto { UnitKey = unit.UnitKey, Order = unit.Order, SessionIndex = unit.SessionIndex, VenueIndex = unit.VenueIndex, VenueName = unit.VenueName, EventProjectId = unit.EventProjectId, ProjectId = unit.ProjectId, ProjectName = unit.ProjectName, GroupId = unit.GroupId, GroupName = unit.GroupName, RegistrationItemId = unit.RegistrationItemId, TeamName = unit.TeamName, UnitName = unit.UnitName, RegistrationPersonIds = unit.RegistrationPersonIds, Athletes = unit.Athletes, Checked = checkedKeys.Contains(unit.UnitKey), PreviousPassed = previousPassed, CanCheck = previousPassed, LockReason = previousPassed ? string.Empty : $"待通过{ToCheckinName(task.CheckinNo - 1)}" }; }).ToList(); var statusUnits = allStatusUnits .Where(x => x.EventProjectId == selectedProject.EventProjectId && x.GroupId == selectedGroup.Id) .ToList(); // 为项目、组别选择区计算当前检次概况,全部完成时由小程序显示绿色状态。 foreach (var project in options.Projects) { var projectUnits = allStatusUnits.Where(x => x.EventProjectId == project.EventProjectId).ToList(); ApplyProgress(projectUnits, out var projectTotal, out var projectChecked, out var projectLocked, out var projectPercent, out var projectCompleted); project.TotalUnits = projectTotal; project.CheckedUnits = projectChecked; project.LockedUnits = projectLocked; project.ProgressPercent = projectPercent; project.IsCompleted = projectCompleted; foreach (var group in project.Groups) { var groupUnits = projectUnits.Where(x => x.GroupId == group.Id).ToList(); ApplyProgress(groupUnits, out var groupTotal, out var groupChecked, out var groupLocked, out var groupPercent, out var groupCompleted); group.TotalUnits = groupTotal; group.CheckedUnits = groupChecked; group.LockedUnits = groupLocked; group.ProgressPercent = groupPercent; group.IsCompleted = groupCompleted; } } var activeTasks = await GetActiveEventTasksAsync(task.EventId); return new CheckinContextDto { TaskId = task.Id, EventId = task.EventId, EventName = options.EventName, TotalCheckins = activeTasks.Count, CurrentCheckinNo = task.CheckinNo, CurrentCheckinName = ToCheckinName(task.CheckinNo), TotalRounds = activeTasks.Count, CurrentRound = task.CheckinNo, RoundName = ToCheckinName(task.CheckinNo), SelectedEventProjectId = selectedProject.EventProjectId, SelectedGroupId = selectedGroup.Id, CurrentGroupId = selectedGroup.Id, CurrentGroupName = selectedGroup.GroupName, Projects = options.Projects, 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(), Rounds = activeTasks.Select(x => new CheckinRoundOptionDto { Round = x.CheckinNo, RoundName = ToCheckinName(x.CheckinNo) }).ToList() }; } private async Task GetCurrentTaskAsync(long userId) => await _taskRepository.AsQueryable() .Where(x => x.CheckerUserId == userId && x.TaskStatus == 1 && x.Status == AvailabilityStatus.Enable && !x.DeleteMark) .OrderByDescending(x => x.UpdatedTime).OrderByDescending(x => x.CreatedTime).FirstAsync(); private async Task GetEventTaskAsync(long eventId, int checkinNo) => await _taskRepository.GetFirstAsync(x => x.EventId == eventId && x.CheckinNo == checkinNo && x.TaskStatus == 1 && x.Status == AvailabilityStatus.Enable && !x.DeleteMark); private async Task> GetActiveEventTasksAsync(long eventId) => await _taskRepository.AsQueryable() .Where(x => x.EventId == eventId && x.TaskStatus == 1 && x.Status == AvailabilityStatus.Enable && !x.DeleteMark) .OrderBy(x => x.CheckinNo).ToListAsync(); private async Task EnsureCheckerAsync() { var userId = _authManager.UserId; var user = await _userRepository.GetFirstAsync(x => x.Id == userId && !x.DeleteMark); if (user == null) throw Oops.Bah("用户不存在"); if (user.MiniProgramRole != MiniProgramRole.Checker) throw Oops.Bah("当前账号不是检录员身份"); return userId; } private static string ToCheckinName(int checkinNo) => checkinNo switch { 1 => "一检", 2 => "二检", 3 => "三检", _ => $"第{checkinNo}检" }; private static void ApplyProgress( List units, out int total, out int checkedCount, out int lockedCount, out int percent, out bool completed) { total = units.Count; checkedCount = units.Count(x => x.Checked); lockedCount = units.Count(x => !x.CanCheck); percent = total == 0 ? 0 : (int)Math.Round(checkedCount * 100d / total); completed = total > 0 && checkedCount == total; } }