diff --git a/YD_Event.Application/AppUser/AppUserService.cs b/YD_Event.Application/AppUser/AppUserService.cs index dd797ee..b6b6ea0 100644 --- a/YD_Event.Application/AppUser/AppUserService.cs +++ b/YD_Event.Application/AppUser/AppUserService.cs @@ -151,38 +151,45 @@ public class AppUserService : BaseService, ITransient /// [HttpPost("member/save")] [DisplayName("统一新增/编辑成员")] + [UnitOfWork] public async Task SaveUnifiedMember(CreateOrUpdateMemberInput input) { - // 仅在新增运动员时验证验证码 - if (!input.Id.HasValue && input.PersonType == PersonType.Athlete) - { - if (string.IsNullOrWhiteSpace(input.VerificationCode)) - { - throw Oops.Bah("验证码不能为空"); - } - - var cacheKey = $"sms:code:{input.Mobile}"; - var cachedCodeResult = _cachingProvider.Get(cacheKey); - string? cachedCode = cachedCodeResult.Value; - if (!cachedCodeResult.HasValue || cachedCode != input.VerificationCode) - { - throw Oops.Bah("验证码错误或已过期"); - } - } - var (userId, merchantCertificationId) = await GetCurrentUserAndMerchantCertificationId(); + // 检查是否有跨单位操作权限 + if (!merchantCertificationId.HasValue) + { + throw Oops.Bah("当前用户未关联单位,无法进行操作"); + } + + // 先检查是否存在相同手机号且相同类型的运动员在其他单位 + var existingInOtherUnit = await _appUserRep.AsQueryable() + .Where(x => x.Mobile == input.Mobile + && x.PersonType == input.PersonType + && x.MerchantCertificationId != merchantCertificationId + && x.MerchantCertificationId != null + && !x.DeleteMark) + .FirstAsync(); + AppUserEntity entity; - bool isNewRecord = false; + bool isUnitTransfer = false; + long? originalMerchantId = null; + + if (existingInOtherUnit != null) + { + // 单位更换流程 + isUnitTransfer = true; + entity = existingInOtherUnit; + originalMerchantId = entity.MerchantCertificationId; + } if (input.Id.HasValue) { // 编辑模式 entity = await _appUserRep.GetByIdAsync(input.Id.Value); if (entity == null || entity.DeleteMark) throw Oops.Bah("成员不存在"); - await CheckPermission(entity); - // 编辑模式下:检查手机号是否被本单位内同类型的其他记录占用 + // 检查手机号是否被本单位内同类型的其他记录占用 var duplicateInSameUnit = await _appUserRep.AsQueryable() .Where(x => x.Mobile == input.Mobile && x.PersonType == input.PersonType @@ -195,7 +202,7 @@ public class AppUserService : BaseService, ITransient { throw Oops.Bah($"该手机号在本单位已存在相同类型的成员"); } - + entity.UpdatedUserId = _authManager.UserId; entity.UpdatedTime = DateTime.Now; } @@ -215,8 +222,7 @@ public class AppUserService : BaseService, ITransient throw Oops.Bah($"该手机号在本单位已存在相同类型的成员"); } - // 新增记录(即使手机号在其他单位或其他类型中已存在,也允许新增) - isNewRecord = true; + // 新增记录 entity = new AppUserEntity { Id = YitIdHelper.NextId(), @@ -227,6 +233,29 @@ public class AppUserService : BaseService, ITransient Status = AvailabilityStatus.Enable, AuditStatus = AuditStatus.PendingSubmission }; + + // 仅在真正新增运动员时验证验证码 + if (input.PersonType == PersonType.Athlete) + { + if (string.IsNullOrWhiteSpace(input.VerificationCode)) + { + throw Oops.Bah("验证码不能为空"); + } + + var cacheKey = $"sms:code:{input.Mobile}"; + var cachedCodeResult = _cachingProvider.Get(cacheKey); + string? cachedCode = cachedCodeResult.Value; + if (!cachedCodeResult.HasValue || cachedCode != input.VerificationCode) + { + throw Oops.Bah("验证码错误或已过期"); + } + } + + if (isUnitTransfer) + { + existingInOtherUnit.DeleteMark = true; + await _appUserRep.UpdateAsync(existingInOtherUnit); + } } // 更新用户信息字段 @@ -237,24 +266,17 @@ public class AppUserService : BaseService, ITransient entity.PassportPhotoUrl = input.PassportPhotoUrl; entity.Mobile = input.Mobile; entity.Remark = input.Remark; - entity.AuditRemark = ""; - - // 保存出生日期 + entity.AuditRemark = isUnitTransfer ? "单位更换" : ""; entity.BirthDate = input.BirthDate; - - // 保存年龄字段:如果用户填写了年龄,直接保存;如果没有填写但有出生日期,可以在查询时动态计算 entity.Age = input.Age; - // 更新运动员类型(如果是新记录或需要更新) - if (isNewRecord || entity.PersonType != input.PersonType) - { - entity.PersonType = input.PersonType; - } - // 更新认证单位信息 - if (isNewRecord || entity.MerchantCertificationId != merchantCertificationId) + var oldMerchantId = entity.MerchantCertificationId; + if (entity.MerchantCertificationId != merchantCertificationId) { entity.MerchantCertificationId = merchantCertificationId; + entity.UpdatedUserId = _authManager.UserId; + entity.UpdatedTime = DateTime.Now; } if (input.PersonType == PersonType.Coach) @@ -263,7 +285,45 @@ public class AppUserService : BaseService, ITransient entity.CertificationStatus = input.CertificationStatus; } + // 保存用户信息 await _appUserRep.InsertOrUpdateAsync(entity); + + // 如果是单位更换,处理团队关系 + if (isUnitTransfer && oldMerchantId.HasValue) + { + await HandleTeamTransfer(entity.Id, oldMerchantId.Value, merchantCertificationId.Value); + } + } + + /// + /// 处理单位更换时的团队成员关系 + /// + /// 用户ID + /// 原单位ID + /// 新单位ID + /// + private async Task HandleTeamTransfer(long userId, long oldMerchantId, long newMerchantId) + { + // 从原单位的所有团队中移除该成员 + var oldTeamIds = await _db.Queryable() + .Where(t => t.MerchantCertificationId == oldMerchantId && !t.DeleteMark) + .Select(t => t.Id) + .ToListAsync(); + + if (oldTeamIds.Any()) + { + await _db.Updateable() + .SetColumns(tm => new TeamMember + { + DeleteMark = true, + UpdatedTime = DateTime.Now, + UpdatedUserId = _authManager.UserId + }) + .Where(tm => tm.UserId == userId + && oldTeamIds.Contains(tm.TeamId) + && !tm.DeleteMark) + .ExecuteCommandAsync(); + } } #endregion @@ -354,4 +414,4 @@ public class AppUserService : BaseService, ITransient } #endregion -} +} \ No newline at end of file diff --git a/YD_Event.Application/Event/Dtos/EventRecordDtos.cs b/YD_Event.Application/Event/Dtos/EventRecordDtos.cs index 4348342..48b2d40 100644 --- a/YD_Event.Application/Event/Dtos/EventRecordDtos.cs +++ b/YD_Event.Application/Event/Dtos/EventRecordDtos.cs @@ -95,6 +95,57 @@ public class EventRecordOutput /// 团队名称 /// public string TeamName { get; set; } + + /// + /// 报名项目列表 + /// + public List Items { get; set; } = new(); + + /// + /// 该赛事的所有报名记录ID列表 + /// + public List RegistrationIds { get; set; } = new(); +} + +/// +/// 报名项目输出 +/// +public class EventRegistrationItemOutput +{ + /// + /// 报名项目ID + /// + public long Id { get; set; } + + /// + /// 项目名称 + /// + public string ProjectName { get; set; } + + /// + /// 项目类型名称 + /// + public string ProjectTypeName { get; set; } + + /// + /// 组别名称 + /// + public string GroupName { get; set; } + + /// + /// 报名人数 + /// + public int PersonCount { get; set; } + + /// + /// 成绩 + /// + public string? Score { get; set; } + + /// + /// 名次 + /// + public string? Rank { get; set; } } /// diff --git a/YD_Event.Application/Event/Dtos/ScoreDtos.cs b/YD_Event.Application/Event/Dtos/ScoreDtos.cs index 3b366e9..6eb8065 100644 --- a/YD_Event.Application/Event/Dtos/ScoreDtos.cs +++ b/YD_Event.Application/Event/Dtos/ScoreDtos.cs @@ -133,10 +133,15 @@ public class CoachLeaderQueryInput : Pagination public class CoachLeaderListOutput { /// - /// 报名人员ID + /// 报名人员ID (0 表示没有 EventRegistrationPerson 记录,只有 TeamMember 记录) /// public long Id { get; set; } + /// + /// 用户ID (对应 AppUser.Id) + /// + public long UserId { get; set; } + /// /// 姓名 /// diff --git a/YD_Event.Application/Event/EventRecordService.cs b/YD_Event.Application/Event/EventRecordService.cs index 7c286c0..3d664e2 100644 --- a/YD_Event.Application/Event/EventRecordService.cs +++ b/YD_Event.Application/Event/EventRecordService.cs @@ -18,13 +18,17 @@ public class EventRecordService : BaseService, ITransient private readonly ISqlSugarRepository _appUserRep; private readonly ISqlSugarRepository _sysUserRep; private readonly ISqlSugarRepository _merchantRep; + private readonly ISqlSugarRepository _registrationItemRep; + private readonly ISqlSugarRepository _resultRep; public EventRecordService(ISqlSugarRepository registrationRep, ISqlSugarRepository eventRep, ISqlSugarRepository certificateRep, ISqlSugarRepository appUserRep, ISqlSugarRepository sysUserRep, - ISqlSugarRepository merchantRep) : base(registrationRep) + ISqlSugarRepository merchantRep, + ISqlSugarRepository registrationItemRep, + ISqlSugarRepository resultRep) : base(registrationRep) { _registrationRep = registrationRep; _eventRep = eventRep; @@ -32,6 +36,8 @@ public class EventRecordService : BaseService, ITransient _appUserRep = appUserRep; _sysUserRep = sysUserRep; _merchantRep = merchantRep; + _registrationItemRep = registrationItemRep; + _resultRep = resultRep; } /// @@ -43,8 +49,8 @@ public class EventRecordService : BaseService, ITransient // 参数验证 ValidateInput(input); - // Join EventRegistration and EventInfo - var query = _registrationRep.AsQueryable() + // 第一步:查询用户报名过的所有赛事(去重,按赛事分组) + 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) @@ -58,21 +64,122 @@ public class EventRecordService : BaseService, ITransient .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) => r.CreatedTime) - .Select((r, e, u, s, m) => new EventRecordOutput + .Select((r, e, u, s, m) => new { - Id = r.Id, + RegistrationId = r.Id, + EventId = e.Id, EventName = e.EventName, EventType = e.EventType.ToString(), StartTime = e.MatchStartTime, EndTime = e.MatchEndTime, PaymentStatus = r.PaymentStatus, - EventId = e.Id, ContactName = r.ContactName, ContactPhone = r.ContactPhone, - TeamName = r.TeamName + TeamName = r.TeamName, + CreatedTime = r.CreatedTime }); - return await query.ToPagedListAsync(input); + 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用于查询证书 + }; + }) + .OrderByDescending(x => x.StartTime) + .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(); + + // 第七步:将项目和成绩数据填充到结果中 + foreach (var row in pagedEvents) + { + // 获取该赛事所有报名记录的项目 + var registrationItems = items.Where(i => row.RegistrationIds.Contains(i.RegistrationId)).ToList(); + foreach (var item in registrationItems) + { + // 查找对应的成绩 + var result = results.FirstOrDefault(r => + r.RegistrationId == item.RegistrationId && + r.ProjectId == item.ProjectId && + r.GroupName == item.GroupName); + + 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 ? GetRankName(result.Rank) : null + }); + } + } + + // 第八步:构造分页结果 + var pageResult = new PageResult + { + Total = total, + PageNo = pageIndex, + PageSize = pageSize, + Pages = pages, + Rows = pagedEvents + }; + + return pageResult; + } + + private string GetRankName(int rank) + { + return rank switch + { + 1 => "冠军", + 2 => "亚军", + 3 => "季军", + _ => $"{rank}名" + }; } /// @@ -111,11 +218,11 @@ public class EventRecordService : BaseService, ITransient /// /// 获取证书列表 /// - [HttpGet("{registrationId}/certificates")] - public async Task> GetCertificates([FromRoute] long registrationId) + [HttpGet("certificates")] + public async Task> GetCertificates([FromQuery] List registrationIds) { var certs = await _certificateRep.AsQueryable() - .Where(c => c.RegistrationId == registrationId) + .Where(c => registrationIds.Contains(c.RegistrationId)) .ToListAsync(); return certs.Select(c => new CertificateOutput diff --git a/YD_Event.Application/Event/EventScoreService.cs b/YD_Event.Application/Event/EventScoreService.cs index 59acf49..e5f95f7 100644 --- a/YD_Event.Application/Event/EventScoreService.cs +++ b/YD_Event.Application/Event/EventScoreService.cs @@ -275,20 +275,25 @@ public class EventScoreService : BaseService, ITransient /// 教练/领队列表 /// /// - /// 通过运动员所在的团队关联获取教练和领队人员信息 + /// 通过运动员报名记录 -> TeamMember团队关联 -> 找到同团队的教练/领队 /// [DisplayName("教练领队列表")] public async Task> PageCoachLeader(CoachLeaderQueryInput input) { + _logger.LogInformation("开始查询教练/领队列表,赛事ID={EventId}, 人员类型={PersonType}", + input.EventId, input.PersonType); + + // 先从 EventRegistrationPerson 查找,找不到则通过 TeamMember 关联查找 // 步骤1:获取该赛事的所有运动员的 UserId var athleteUserIds = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) - .Where((p, r) => r.EventId == input.EventId && p.PersonType == PersonType.Athlete) + .Where((p, r) => r.EventId == input.EventId && p.PersonType == PersonType.Athlete && !p.DeleteMark) .Select(p => p.UserId) .ToListAsync(); if (!athleteUserIds.Any()) { + _logger.LogWarning("该赛事没有运动员报名记录"); return new PageResult(); } @@ -301,6 +306,7 @@ public class EventScoreService : BaseService, ITransient if (!teamIds.Any()) { + _logger.LogWarning("未找到关联的团队信息"); return new PageResult(); } @@ -313,42 +319,128 @@ public class EventScoreService : BaseService, ITransient if (!coachLeaderUserIds.Any()) { + _logger.LogWarning("该团队中没有找到指定类型的人员"); return new PageResult(); } - // 步骤4:从 AppUser 表获取教练/领队的详细信息(需要关联查询) - var userQuery = _appUserRepository.AsQueryable() - .Where(p => coachLeaderUserIds.Contains(p.Id)) - .Where(p=> p.PersonType == input.PersonType); - - var pageResult = await userQuery - .Select(p => new CoachLeaderListOutput + // 步骤4:首先尝试从该赛事的 EventRegistrationPerson 中找到这些教练/领队 + // 这样可以获取到 EventRegistrationPerson.Id 用于证书发放 + var existingPersons = await _personRepository.AsQueryable() + .InnerJoin((p, r) => p.RegistrationId == r.Id) + .Where((p, r) => r.EventId == input.EventId && coachLeaderUserIds.Contains(p.UserId) && p.PersonType == input.PersonType && !p.DeleteMark) + .LeftJoin((p, r, u) => p.UserId == u.Id) + .Select((p, r, u) => new { - Id = p.Id, + PersonId = p.Id, + UserId = p.UserId, Name = p.Name, Age = p.Age, Gender = (int)p.Gender, BirthDate = p.BirthDate, - IdCardNumber = p.IdCardNumber, - IdCardImageUrl = p.IdCardImageUrl, - IdCardBackImageUrl = p.IdCardBackImageUrl, - Mobile = p.Mobile, - CertificateId = 0 // 需要单独查询,此处先设为0 + IdCardNumber = p.IdCard, + IdCardImageUrl = u.IdCardImageUrl, + IdCardBackImageUrl = u.IdCardBackImageUrl, + Mobile = p.Phone, + HasPersonRecord = true }) - .ToPagedListAsync(input); + .ToListAsync(); - foreach (var item in pageResult.Rows) + // 步骤5:找出在 EventRegistrationPerson 中没有记录的教练/领队 + var foundUserIds = existingPersons.Select(x => x.UserId).ToList(); + var missingUserIds = coachLeaderUserIds.Except(foundUserIds).ToList(); + + var allResults = existingPersons.Select(x => new CoachLeaderListOutput { - if (item.Age.HasValue) + Id = x.PersonId, + UserId = x.UserId, + Name = x.Name, + Age = x.Age, + Gender = x.Gender, + BirthDate = x.BirthDate, + IdCardNumber = x.IdCardNumber, + IdCardImageUrl = x.IdCardImageUrl, + IdCardBackImageUrl = x.IdCardBackImageUrl, + Mobile = x.Mobile, + CertificateId = 0 + }).ToList(); + + // 步骤6:如果有教练/领队没有 EventRegistrationPerson 记录,则从 AppUser 获取基本信息 + // 注意:这种情况下 Id = -UserId(负数),表示没有 EventRegistrationPerson 记录,需要后续处理 + if (missingUserIds.Any()) + { + var appUsers = await _appUserRepository.AsQueryable() + .Where(u => missingUserIds.Contains(u.Id) && !u.DeleteMark) + .Select(u => new CoachLeaderListOutput + { + Id = -u.Id, // 使用 -UserId 表示没有 EventRegistrationPerson 记录 + UserId = u.Id, + Name = u.Name ?? string.Empty, + Age = u.Age, + Gender = (int)u.Gender, + BirthDate = u.BirthDate, + IdCardNumber = u.IdCardNumber, + IdCardImageUrl = u.IdCardImageUrl, + IdCardBackImageUrl = u.IdCardBackImageUrl, + Mobile = u.Mobile, + CertificateId = 0 + }) + .ToListAsync(); + + allResults.AddRange(appUsers); + _logger.LogInformation("找到 {Count} 名有报名记录的人员,{MissingCount} 名只有团队关联的人员", + existingPersons.Count, appUsers.Count); + } + + // 应用筛选条件(在内存中筛选) + if (!string.IsNullOrWhiteSpace(input.Name)) + { + allResults = allResults.Where(x => x.Name.Contains(input.Name)).ToList(); + } + + if (!string.IsNullOrWhiteSpace(input.Mobile)) + { + allResults = allResults.Where(x => x.Mobile.Contains(input.Mobile)).ToList(); + } + + if (input.Gender.HasValue) + { + allResults = allResults.Where(x => x.Gender == (int)input.Gender.Value).ToList(); + } + + // 手动分页 + var total = allResults.Count; + var pageSize = input.PageSize > 0 ? input.PageSize : 10; + var pageNo = input.PageNo > 0 ? input.PageNo - 1 : 0; + var pagedData = allResults.Skip(pageNo * pageSize).Take(pageSize).ToList(); + + // 获取所有有 EventRegistrationPerson 记录的证书信息 + var personIds = pagedData.Where(x => x.Id > 0).Select(x => x.Id).ToList(); + if (personIds.Any()) + { + var certs = await _certificateRepository.GetListAsync(x => + personIds.Contains(x.ParticipationRecordId) && !x.DeleteMark); + + var certDict = certs.ToDictionary(c => c.ParticipationRecordId); + + foreach (var item in pagedData) { - continue; - } - if (item.BirthDate.HasValue) - { - item.Age = MemberOutput.CalculateAgeFromBirthDate(item.BirthDate); + if (item.Id > 0 && certDict.TryGetValue(item.Id, out var cert)) + { + item.CertificateId = cert.Id; + } } } + var pageResult = new PageResult + { + PageNo = input.PageNo, + PageSize = pageSize, + Total = total, + Pages = (int)Math.Ceiling((double)total / pageSize), + Rows = pagedData + }; + + _logger.LogInformation("查询教练/领队列表完成,共 {Count} 条记录", total); return pageResult; } @@ -678,47 +770,181 @@ public class EventScoreService : BaseService, ITransient }) .ToListAsync(); - // 构建最终的人员字典 - var personDict = new Dictionary(); + // 处理 PersonIds:将负的 Id 转换为 UserId,然后尝试找到或创建对应的 EventRegistrationPerson + var processedPersonIds = new List(); + var userIdToNewPersonId = new Dictionary(); - // 根据页面类型区分人员信息获取逻辑 - if (input.PageType == 2 || input.PageType == 3) + // 创建一个简单的人员信息类用于内部存储 + var personInfoDict = new Dictionary(); + + // 从查询结果构建字典 + foreach (var p in allPersonsWithTeam) { - // 页面类型为教练(2)或领队(3)时,从团队关系数据中获取对应人员ID - _logger.LogInformation("页面类型为教练/领队,从团队关系数据中获取人员信息"); - - // 建立 UserId 到人员的映射 - var userToPersonMap = allPersonsWithTeam - .Where(p => p.UserId > 0) - .GroupBy(p => p.UserId) - .ToDictionary(g => g.Key, g => g.First()); - - // 通过 UserId 查找对应的人员 - foreach (var userId in input.PersonIds) + personInfoDict[p.Id] = (p.Id, p.PersonType, p.UserId, p.Name, p.ProjectName, p.GroupName, p.RegistrationId, p.ProjectId, p.RegistrationItemId, p.EventProjectGroupId, p.DeleteMark, p.TeamName, p.MerchantCertificationId, p.Person); + } + + foreach (var personId in input.PersonIds) + { + if (personId > 0) { - if (userToPersonMap.TryGetValue(userId, out var person)) + // 正常的 EventRegistrationPerson.Id + processedPersonIds.Add(personId); + } + else if (personId < 0) + { + // 负的 Id 表示是 UserId 的负数,需要找到或创建 EventRegistrationPerson + var userId = -personId; + + // 先尝试从现有数据中找到该 UserId 在该赛事中的 EventRegistrationPerson + var existingPersonEntry = personInfoDict.Values.FirstOrDefault(p => p.UserId == userId); + if (existingPersonEntry.Id != 0) { - personDict[userId] = person; - _logger.LogInformation("通过 UserId {UserId} 找到对应的人员: {PersonId}", userId, person.Id); + processedPersonIds.Add(existingPersonEntry.Id); + continue; + } + + // 如果找不到,我们需要为这个用户创建 EventRegistration 和 EventRegistrationPerson 记录 + _logger.LogInformation($"为 UserId {userId} 创建 EventRegistrationPerson 记录"); + + // 先找到该用户所在 Team 关联的任意一个运动员的 Registration 信息(或者创建一个新的) + var teamIds = await _teamMemberRepository.AsQueryable() + .Where(tm => tm.UserId == userId && !tm.DeleteMark) + .Select(tm => tm.TeamId) + .Distinct() + .ToListAsync(); + + if (!teamIds.Any()) + { + result.FailCount++; + result.FailDetails.Add($"无法为 UserId {userId} 找到关联的团队信息"); + continue; + } + + // 找到同一 Team 和同一 Event 中的任意一个运动员的 Registration + var relatedAthleteRegistrations = await _personRepository.AsQueryable() + .InnerJoin((p, r) => p.RegistrationId == r.Id) + .InnerJoin((p, r, tm) => p.UserId == tm.UserId) + .Where((p, r, tm) => r.EventId == input.EventId && teamIds.Contains(tm.TeamId) && !p.DeleteMark) + .Select((p, r) => r) + .ToListAsync(); + + var relatedAthleteRegistration = relatedAthleteRegistrations.FirstOrDefault(); + + long registrationId; + long? merchantCertId = null; + + if (relatedAthleteRegistration != null) + { + // 复用已有的 Registration + registrationId = relatedAthleteRegistration.Id; + merchantCertId = relatedAthleteRegistration.MerchantCertificationId; } else { - _logger.LogWarning("无法通过 UserId {UserId} 找到对应的人员", userId); + // 创建一个新的 Registration 记录(临时) + var appUser = await _appUserRepository.GetFirstAsync(u => u.Id == userId && !u.DeleteMark); + if (appUser == null) + { + result.FailCount++; + result.FailDetails.Add($"无法找到 UserId {userId} 对应的用户信息"); + continue; + } + + var newRegistration = new EventRegistration + { + Id = _idGenerator.NewLong(), + EventId = input.EventId, + RegistrationMode = 1, // 个人模式 + TeamName = null, + ContactName = appUser.Name ?? "未知", + ContactPhone = appUser.Mobile, + ContactEmail = null, + TotalPersonCount = 1, + TotalAmount = 0, + PaymentStatus = 1, + PaymentMethod = 1, + PaymentMethodType = PaymentMethodType.None, + OrderNo = $"TEMP_{DateTime.Now:yyyyMMddHHmmss}", + PaymentTime = null, + Reviewer = null, + ReviewTime = null, + ReviewRemark = null, + RegistrationRemark = "教练/领队证书发放临时报名记录", + Status = AvailabilityStatus.Enable, + DeleteMark = false, + CreatedUserId = 0, + CreatedTime = DateTime.Now, + UpdatedUserId = null, + UpdatedTime = null + }; + await _registrationRepository.InsertAsync(newRegistration); + registrationId = newRegistration.Id; } + + // 获取 AppUser 信息 + var appUserInfo = await _appUserRepository.GetFirstAsync(u => u.Id == userId && !u.DeleteMark); + if (appUserInfo == null) + { + result.FailCount++; + result.FailDetails.Add($"无法找到 UserId {userId} 对应的用户信息"); + continue; + } + + // 创建 EventRegistrationPerson 记录 + var newPerson = new EventRegistrationPerson + { + Id = _idGenerator.NewLong(), + RegistrationId = registrationId, + UserId = appUserInfo.Id, + PersonType = input.PageType == 2 ? PersonType.Coach : PersonType.Leader, + Name = appUserInfo.Name ?? "未知", + Gender = appUserInfo.Gender, + BirthDate = appUserInfo.BirthDate ?? DateTime.Now, + Age = appUserInfo.Age ?? 0, + IdCard = appUserInfo.IdCardNumber ?? "", + Phone = appUserInfo.Mobile, + ProjectName = null, + GroupName = "", + ProjectId = 0, + EventProjectGroupId = null, + RegistrationItemId = 0, + DeleteMark = false, + CreatedUserId = 0, + CreatedTime = DateTime.Now, + UpdatedUserId = null, + UpdatedTime = null + }; + + await _personRepository.InsertAsync(newPerson); + + // 添加到映射字典和处理列表 + userIdToNewPersonId[userId] = newPerson.Id; + processedPersonIds.Add(newPerson.Id); + + // 添加到 allPersonsWithTeam 中,以便后续查找 + allPersonsWithTeam.Add(new { + Id = newPerson.Id, + PersonType = newPerson.PersonType, + UserId = newPerson.UserId, + Name = newPerson.Name, + ProjectName = newPerson.ProjectName, + GroupName = newPerson.GroupName, + RegistrationId = newPerson.RegistrationId, + ProjectId = newPerson.ProjectId, + RegistrationItemId = newPerson.RegistrationItemId, + EventProjectGroupId = newPerson.EventProjectGroupId, + DeleteMark = false, + TeamName = (string?)null, + MerchantCertificationId = merchantCertId, + Person = newPerson + }); } } - else - { - // 页面类型为运动员(1)或其他,直接获取人员ID - _logger.LogInformation("页面类型为运动员,直接获取人员信息"); - - // 直接从人员列表中查找 - foreach (var person in allPersonsWithTeam.Where(p => input.PersonIds.Contains(p.Id))) - { - personDict[person.Id] = person; - _logger.LogInformation("直接获取人员: {PersonId}", person.Id); - } - } + + // 由于现在 PageCoachLeader 返回的就是 EventRegistrationPerson.Id(或者我们刚刚创建的),我们可以直接使用 + var personDict = allPersonsWithTeam + .Where(p => processedPersonIds.Contains(p.Id)) + .ToDictionary(p => p.Id); // 使用最终找到的人员ID列表 var foundPersonIds = personDict.Values.Select(p => (long)p.Id).ToList(); @@ -732,7 +958,7 @@ public class EventScoreService : BaseService, ITransient var certsToInsert = new List(); var certsToUpdate = new List(); - foreach (var personId in input.PersonIds) + foreach (var personId in processedPersonIds) { if (!personDict.TryGetValue(personId, out var person)) { @@ -749,7 +975,7 @@ public class EventScoreService : BaseService, ITransient continue; } - // 获取项目信息,判断是否为团队项目 + // 获取项目信息,判断是否为团队项目(仅对运动员有效) bool isTeamProject = false; string combinedNames = person.Name; if (person.PersonType == PersonType.Athlete || person.PersonType == PersonType.User) @@ -766,7 +992,6 @@ public class EventScoreService : BaseService, ITransient // 如果是团队项目,找到该团队的所有成员并合并姓名 if (isTeamProject) { - // 回到原来的方法,但确保不会出错 var teamMembers = allPersonsWithTeam .Where(p => p.RegistrationItemId == person.RegistrationItemId) .OrderBy(p => (string)p.Name) @@ -781,7 +1006,7 @@ public class EventScoreService : BaseService, ITransient } } - // 获取实际的 EventRegistrationPerson ID(如果通过 UserId 映射过来的,需要使用实际的 Id) + // 获取实际的 EventRegistrationPerson ID var actualPersonId = person.Id; var merchantCertId = person.MerchantCertificationId; @@ -814,7 +1039,7 @@ public class EventScoreService : BaseService, ITransient ParticipationRecordId = actualPersonId, ProjectId = person.ProjectId, UserId = person.UserId, - ParticipantName = combinedNames, // 团队项目显示所有成员姓名 + ParticipantName = combinedNames, GroupName = person.GroupName, MerchantCertificationId = merchantCertId, CertificateStatus = 1, @@ -1083,7 +1308,7 @@ public class EventScoreService : BaseService, ITransient { if (input.Id <= 0) { - throw Oops.Bah("无效的 ID"); + throw Oops.Bah("未生成证书"); } // 首先尝试直接通过 ParticipationRecordId(EventRegistrationPerson.Id)查找证书 diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml index 7e6ee8a..e8a720f 100644 --- a/YD_Event.Application/YD_Event.Application.xml +++ b/YD_Event.Application/YD_Event.Application.xml @@ -86,6 +86,15 @@ + + + 处理单位更换时的团队成员关系 + + 用户ID + 原单位ID + 新单位ID + + 删除成员 @@ -4895,6 +4904,56 @@ 团队名称 + + + 报名项目列表 + + + + + 该赛事的所有报名记录ID列表 + + + + + 报名项目输出 + + + + + 报名项目ID + + + + + 项目名称 + + + + + 项目类型名称 + + + + + 组别名称 + + + + + 报名人数 + + + + + 成绩 + + + + + 名次 + + 证书信息输出 @@ -5977,7 +6036,12 @@ - 报名人员ID + 报名人员ID (0 表示没有 EventRegistrationPerson 记录,只有 TeamMember 记录) + + + + + 用户ID (对应 AppUser.Id) @@ -7911,7 +7975,7 @@ 验证输入参数 - + 获取证书列表 @@ -7976,7 +8040,7 @@ 教练/领队列表 - 通过运动员所在的团队关联获取教练和领队人员信息 + 通过运动员报名记录 -> TeamMember团队关联 -> 找到同团队的教练/领队