新增成绩查询、裁判认证及检录员账号管理接口,并统一小程序角色登录与个人中心数据。 检录任务绑定赛事秩序册场次,按项目、组别、场地和场次顺序执行;支持最多三名检录员、分配检录轮次及前序检录校验。 补充检录任务、检录记录、裁判申请实体及四份数据库迁移,包含表字段备注、秩序册绑定和检录序号。
277 lines
13 KiB
C#
277 lines
13 KiB
C#
using YD_Event.Application.Auth;
|
||
using YD_Event.Application.Client.Dtos;
|
||
|
||
namespace YD_Event.Application.Client;
|
||
|
||
/// <summary>小程序检录员按秩序册列进行顺序检录。</summary>
|
||
[ApiDescriptionSettings("微信小程序接口")]
|
||
[Authorize]
|
||
public class CheckinController : IDynamicApiController
|
||
{
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _userRepository;
|
||
private readonly ISqlSugarRepository<CheckinTask> _taskRepository;
|
||
private readonly ISqlSugarRepository<CheckinRecord> _recordRepository;
|
||
private readonly CheckinScheduleService _scheduleService;
|
||
private readonly AuthManager _authManager;
|
||
|
||
public CheckinController(
|
||
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository,
|
||
ISqlSugarRepository<CheckinTask> taskRepository,
|
||
ISqlSugarRepository<CheckinRecord> recordRepository,
|
||
CheckinScheduleService scheduleService,
|
||
AuthManager authManager)
|
||
{
|
||
_userRepository = userRepository;
|
||
_taskRepository = taskRepository;
|
||
_recordRepository = recordRepository;
|
||
_scheduleService = scheduleService;
|
||
_authManager = authManager;
|
||
}
|
||
|
||
[HttpGet("current")]
|
||
[DisplayName("获取当前赛事检录任务")]
|
||
public async Task<CheckinContextDto?> 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<CheckinContextDto> 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<CheckinContextDto> 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<string> previousPassedKeys;
|
||
if (task.CheckinNo == 1)
|
||
{
|
||
previousPassedKeys = allUnitKeys;
|
||
}
|
||
else
|
||
{
|
||
var previousTask = await GetEventTaskAsync(task.EventId, task.CheckinNo - 1);
|
||
var previousRecords = previousTask == null
|
||
? new List<CheckinRecord>()
|
||
: 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<CheckinTask?> 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<CheckinTask?> 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<List<CheckinTask>> 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<long> 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<CheckinOrderBookUnitStatusDto> 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;
|
||
}
|
||
}
|