using YD_Event.Application.Event.Dtos; using YD_Event.Core.Entities; using YD_Event.Core.Enum; using AppUserEntity = YD_Event.Core.Entities.AppUser; using SysUserEntity = YD_Event.Core.Entities.SysUser; using MerchantEntity = YD_Event.Core.Entities.Merchant; namespace YD_Event.Application.Event; /// /// 参赛记录管理 /// public class EventRecordService : BaseService, ITransient { private static readonly string[] ChineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" }; /// /// 将数字名次转换为汉字形式(与成绩与证书页面保持一致) /// private static string ConvertRankToChinese(int rank) { if (rank <= 0) return string.Empty; if (rank <= 10) return $"第{ChineseNumbers[rank]}名"; if (rank < 20) return $"第十{ChineseNumbers[rank % 10]}名"; if (rank % 10 == 0) return $"第{ChineseNumbers[rank / 10]}十名"; return $"第{ChineseNumbers[rank / 10]}十{ChineseNumbers[rank % 10]}名"; } private readonly ISqlSugarRepository _registrationRep; private readonly ISqlSugarRepository _eventRep; private readonly ISqlSugarRepository _certificateRep; private readonly ISqlSugarRepository _appUserRep; private readonly ISqlSugarRepository _sysUserRep; private readonly ISqlSugarRepository _merchantRep; private readonly ISqlSugarRepository _registrationItemRep; private readonly ISqlSugarRepository _resultRep; private readonly ISqlSugarRepository _registrationPersonRep; private readonly ISqlSugarRepository _eventProjectRep; private readonly ISqlSugarRepository _projectRep; public EventRecordService(ISqlSugarRepository registrationRep, ISqlSugarRepository eventRep, ISqlSugarRepository certificateRep, ISqlSugarRepository appUserRep, ISqlSugarRepository sysUserRep, ISqlSugarRepository merchantRep, ISqlSugarRepository registrationItemRep, ISqlSugarRepository resultRep, ISqlSugarRepository registrationPersonRep, ISqlSugarRepository eventProjectRep, ISqlSugarRepository projectRep) : base(registrationRep) { _registrationRep = registrationRep; _eventRep = eventRep; _certificateRep = certificateRep; _appUserRep = appUserRep; _sysUserRep = sysUserRep; _merchantRep = merchantRep; _registrationItemRep = registrationItemRep; _resultRep = resultRep; _registrationPersonRep = registrationPersonRep; _eventProjectRep = eventProjectRep; _projectRep = projectRep; } /// /// 分页查询参赛记录 /// [HttpGet("page")] public async Task> Page([FromQuery] EventRecordPageInput input) { // 参数验证 ValidateInput(input); // 第一步:查询用户报名过的所有赛事(去重,按赛事分组) var eventsQuery = _registrationRep.AsQueryable() .LeftJoin((r, e) => r.EventId == e.Id) .LeftJoin((r, e, u) => r.CreatedUserId == u.Id) .LeftJoin((r, e, u, s) => r.CreatedUserId == s.Id) .LeftJoin((r, e, u, s, m) => r.CreatedUserId == m.CreatedUserId) .WhereIF(!string.IsNullOrWhiteSpace(input.EventName), (r, e, u, s, m) => e.EventName.Contains(input.EventName)) .WhereIF(input.StartTime.HasValue, (r, e, u, s, m) => e.MatchStartTime >= input.StartTime) .WhereIF(input.EndTime.HasValue, (r, e, u, s, m) => e.MatchStartTime <= input.EndTime) .WhereIF(input.PaymentStatus.HasValue, (r, e, u, s, m) => r.PaymentStatus == input.PaymentStatus) // Filter by User ID (AppUser, SysUser, or Merchant) .WhereIF(input.AppUserId.HasValue, (r, e, u, s, m) => u.Id == input.AppUserId) .WhereIF(input.SysUserId.HasValue, (r, e, u, s, m) => s.Id == input.SysUserId) .WhereIF(input.MerchantId.HasValue, (r, e, u, s, m) => m.CreatedUserId == input.MerchantId) .OrderByDescending((r, e, u, s, m) => e.CreatedTime) .Select((r, e, u, s, m) => new { RegistrationId = r.Id, EventId = e.Id, EventName = e.EventName, EventType = e.EventType.ToString(), StartTime = e.MatchStartTime, EndTime = e.MatchEndTime, PaymentStatus = r.PaymentStatus, ContactName = r.ContactName, ContactPhone = r.ContactPhone, TeamName = r.TeamName, CreatedTime = r.CreatedTime, EventCreatedTime = e.CreatedTime }); var allEventRecords = await eventsQuery.ToListAsync(); // 第二步:按赛事分组并聚合 var groupedEvents = allEventRecords .GroupBy(x => x.EventId) .Select(g => { // 获取该赛事最早的报名记录作为主记录 var firstRecord = g.OrderBy(x => x.CreatedTime).First(); // 收集该赛事的所有报名记录ID var registrationIds = g.Select(x => x.RegistrationId).ToList(); return new EventRecordOutput { // 用赛事ID作为主要ID(避免分页问题) Id = firstRecord.EventId, EventName = firstRecord.EventName, EventType = firstRecord.EventType, StartTime = firstRecord.StartTime, EndTime = firstRecord.EndTime, PaymentStatus = firstRecord.PaymentStatus, EventId = firstRecord.EventId, ContactName = firstRecord.ContactName, ContactPhone = firstRecord.ContactPhone, TeamName = firstRecord.TeamName, RegistrationIds = registrationIds, // 保存所有报名ID用于查询证书 // 保存赛事创建时间用于排序 EventCreatedTime = g.First().EventCreatedTime }; }) .OrderByDescending(x => x.EventCreatedTime) .ToList(); // 第三步:手动分页 var pageIndex = input.PageNo; var pageSize = input.PageSize; var total = groupedEvents.Count; var pages = (int)Math.Ceiling((double)total / pageSize); var pagedEvents = groupedEvents .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); // 第四步:获取所有需要的报名记录ID(包括所有页面的,避免数据不完整) var allRegistrationIds = groupedEvents.SelectMany(x => x.RegistrationIds).Distinct().ToList(); // 第五步:获取所有报名项目 var items = await _registrationItemRep.AsQueryable() .Where(i => allRegistrationIds.Contains(i.RegistrationId)) .ToListAsync(); // 第六步:获取所有成绩 var results = await _resultRep.AsQueryable() .Where(r => allRegistrationIds.Contains(r.RegistrationId)) .ToListAsync(); // 第七步:获取所有报名人员 var registrationPersons = await _registrationPersonRep.AsQueryable() .Where(p => allRegistrationIds.Contains(p.RegistrationId)) .ToListAsync(); // 第八步:获取所有 EventProject 和 Project 信息,用于判断项目类型 var allEventProjectIds = items.Select(x => x.ProjectId).Distinct().ToList(); var eventProjects = await _eventProjectRep.GetListAsync(x => allEventProjectIds.Contains(x.Id)); var allProjectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var projects = await _projectRep.GetListAsync(x => allProjectIds.Contains(x.Id)); var eventProjectDict = eventProjects.ToDictionary(x => x.Id, x => x.ProjectId); var projectDict = projects.ToDictionary(x => x.Id, x => x); // 第九步:将项目、成绩和队员姓名数据填充到结果中 foreach (var row in pagedEvents) { // 获取该赛事所有报名记录的项目 var registrationItems = items.Where(i => row.RegistrationIds.Contains(i.RegistrationId)).ToList(); foreach (var item in registrationItems) { // 判断项目类型 bool isIndividualProject = false; int projectType = 0; if (eventProjectDict.TryGetValue(item.ProjectId, out var projectId) && projectDict.TryGetValue(projectId, out var project)) { isIndividualProject = project.ProjectType == ProjectTypeEnum.PersonalSpeed || project.ProjectType == ProjectTypeEnum.PersonalFreestyle; projectType = (int)project.ProjectType; } if (isIndividualProject) { // 个人项目:每个运动员单独一行 // 查找该项目的所有队员(按 RegistrationItemId 筛选) var members = registrationPersons .Where(p => p.RegistrationItemId == item.Id) .ToList(); foreach (var member in members) { // 查找该成员对应的成绩 var memberResult = results.FirstOrDefault(r => r.RegistrationId == item.RegistrationId && r.ProjectId == item.ProjectId && r.GroupName == item.GroupName && r.RegistrationPersonId == member.Id); row.Items.Add(new EventRegistrationItemOutput { Id = member.Id, // 使用队员ID作为条目ID ProjectName = item.ProjectName, ProjectTypeName = item.ProjectTypeName, GroupName = item.GroupName, PersonCount = item.PersonCount, Score = memberResult?.Score, Rank = memberResult?.Rank > 0 ? ConvertRankToChinese(memberResult.Rank) : null, MemberNames = member.Name, // 单个姓名 MemberIds = new List { member.Id }, // 单个队员ID Members = new List { new MemberInfo { Id = member.Id, Name = member.Name, PersonType = (int)member.PersonType } }, IsIndividualProject = isIndividualProject, ProjectType = projectType }); } } else { // 团队项目:每个 RegistrationItemId 单独一行,只显示该项目的队员和成绩 // 查找该项目的所有队员(按 RegistrationItemId 筛选) var members = registrationPersons .Where(p => p.RegistrationItemId == item.Id) .ToList(); // 查找团队成绩:找到该团队中任意一个队员的成绩作为团队成绩 var teamMemberIds = members.Select(m => m.Id).ToList(); var result = results.FirstOrDefault(r => r.RegistrationId == item.RegistrationId && r.ProjectId == item.ProjectId && r.GroupName == item.GroupName && teamMemberIds.Contains(r.RegistrationPersonId)); string? memberNames = members.Count > 0 ? string.Join(", ", members.Select(m => m.Name)) : null; List? memberIds = members.Count > 0 ? members.Select(m => m.Id).ToList() : null; var memberInfoList = members.Select(m => new MemberInfo { Id = m.Id, Name = m.Name, PersonType = (int)m.PersonType }).ToList(); row.Items.Add(new EventRegistrationItemOutput { Id = item.Id, ProjectName = item.ProjectName, ProjectTypeName = item.ProjectTypeName, GroupName = item.GroupName, PersonCount = item.PersonCount, Score = result?.Score, Rank = result?.Rank > 0 ? ConvertRankToChinese(result.Rank) : null, MemberNames = memberNames, MemberIds = memberIds, Members = memberInfoList, IsIndividualProject = isIndividualProject, ProjectType = projectType }); } } } // 第八步:构造分页结果 var pageResult = new PageResult { Total = total, PageNo = pageIndex, PageSize = pageSize, Pages = pages, Rows = pagedEvents }; return pageResult; } /// /// 验证输入参数 /// private void ValidateInput(EventRecordPageInput input) { // 检查是否同时提供了多个用户ID类型 var userTypeCount = 0; if (input.AppUserId.HasValue) userTypeCount++; if (input.SysUserId.HasValue) userTypeCount++; if (input.MerchantId.HasValue) userTypeCount++; if (userTypeCount > 1) { throw Oops.Bah("只能提供一种用户类型ID参数(AppUserId、SysUserId或MerchantId)"); } // 验证用户ID有效性 if (input.AppUserId.HasValue && input.AppUserId.Value <= 0) { throw Oops.Bah("普通用户ID无效"); } if (input.SysUserId.HasValue && input.SysUserId.Value <= 0) { throw Oops.Bah("系统用户ID无效"); } if (input.MerchantId.HasValue && input.MerchantId.Value <= 0) { throw Oops.Bah("商户ID无效"); } } /// /// 获取证书列表 /// [HttpGet("certificates")] public async Task> GetCertificates([FromQuery] List registrationIds) { var certs = await _certificateRep.AsQueryable() .Where(c => registrationIds.Contains(c.RegistrationId)) .ToListAsync(); return certs.Select(c => new CertificateOutput { Id = c.Id, Name = c.ParticipantName, AwardLevel = GetAwardLevelName(c.AwardLevel), CertificateType = GetCertificateTypeName(c.CertificateType), ImageUrl = c.CertificateUrl }).ToList(); } private string GetAwardLevelName(int level) { return level switch { 1 => "特等奖", 2 => "一等奖", 3 => "二等奖", 4 => "三等奖", 5 => "优秀奖", _ => "未知" }; } private string GetCertificateTypeName(int type) { return type switch { 1 => "冠军证书", 2 => "亚军证书", 3 => "季军证书", 4 => "优胜证书", 5 => "参与证书", _ => "未知" }; } }