From c9eb6391237fdff8f725b0d0a8dc9dbdca3b5b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Fri, 17 Apr 2026 09:19:21 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B5=9B=E4=BA=8B=E6=8A=A5=E5=90=8D=E9=99=90?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YD_Event.Application/Event/Dtos/ScoreDtos.cs | 2 +- .../Event/EventScoreService.cs | 54 ++++---- .../Event/UnifiedRegistrationService.cs | 115 +++++++++++++++++- YD_Event.Application/YD_Event.Application.xml | 46 ++++++- YD_Event.Core/Enum/CertificatePageType.cs | 33 ----- YD_Event.Core/YD_Event.Core.xml | 25 ---- 6 files changed, 184 insertions(+), 91 deletions(-) delete mode 100644 YD_Event.Core/Enum/CertificatePageType.cs diff --git a/YD_Event.Application/Event/Dtos/ScoreDtos.cs b/YD_Event.Application/Event/Dtos/ScoreDtos.cs index 5c42a7e..3b74be2 100644 --- a/YD_Event.Application/Event/Dtos/ScoreDtos.cs +++ b/YD_Event.Application/Event/Dtos/ScoreDtos.cs @@ -198,7 +198,7 @@ public class IssueCertificateInput /// 页面类型:1-运动员页面,2-教练页面,3-领队页面 /// 用于明确区分不同角色页面的证书生成逻辑 /// - public CertificatePageType PageType { get; set; } + public int PageType { get; set; } } /// diff --git a/YD_Event.Application/Event/EventScoreService.cs b/YD_Event.Application/Event/EventScoreService.cs index b796a0f..56221f2 100644 --- a/YD_Event.Application/Event/EventScoreService.cs +++ b/YD_Event.Application/Event/EventScoreService.cs @@ -423,25 +423,14 @@ public class EventScoreService : BaseService, ITransient _logger.LogInformation("发放证书 - 页面类型: {PageType}, 允许的人员类型: {AllowedTypes}", input.PageType, string.Join(", ", allowedPersonTypes)); - // 批量预加载所有需要的数据 - // 先尝试直接从 EventRegistrationPerson 表获取所有人员 - var directPersons = await _personRepository.GetListAsync(x => input.PersonIds.Contains(x.Id)); - var directPersonDict = directPersons.ToDictionary(p => p.Id); - - // 找出在 directPersonDict 中找不到的人员ID,这些可能是教练/领队的 AppUser ID - var missingPersonIds = input.PersonIds.Where(id => !directPersonDict.ContainsKey(id)).ToList(); - // 构建最终的人员字典 var personDict = new Dictionary(); - foreach (var kvp in directPersonDict) - { - personDict[kvp.Key] = kvp.Value; - } - // 对于找不到的人员ID,尝试通过团队关系查找对应的 EventRegistrationPerson 记录 - if (missingPersonIds.Any()) + // 根据页面类型区分人员信息获取逻辑 + if (input.PageType == 2 || input.PageType == 3) { - _logger.LogInformation("查找 {Count} 个缺失的人员ID,尝试通过团队关系获取", missingPersonIds.Count); + // 页面类型为教练(2)或领队(3)时,从团队关系数据中获取对应人员ID + _logger.LogInformation("页面类型为教练/领队,从团队关系数据中获取人员信息"); // 获取该赛事的所有 EventRegistrationPerson 记录 var allEventPersons = await _personRepository.AsQueryable() @@ -456,20 +445,33 @@ public class EventScoreService : BaseService, ITransient .GroupBy(p => p.UserId) .ToDictionary(g => g.Key, g => g.First()); - // 尝试通过 UserId 匹配 - foreach (var missingId in missingPersonIds) + // 通过 UserId 查找对应的 EventRegistrationPerson + foreach (var userId in input.PersonIds) { - if (userToPersonMap.TryGetValue(missingId, out var person)) + if (userToPersonMap.TryGetValue(userId, out var person)) { - personDict[missingId] = person; - _logger.LogInformation("通过 UserId {UserId} 找到对应的 EventRegistrationPerson: {PersonId}", missingId, person.Id); + personDict[userId] = person; + _logger.LogInformation("通过 UserId {UserId} 找到对应的 EventRegistrationPerson: {PersonId}", userId, person.Id); } else { - _logger.LogWarning("无法找到人员ID: {PersonId}", missingId); + _logger.LogWarning("无法通过 UserId {UserId} 找到对应的 EventRegistrationPerson", userId); } } } + else + { + // 页面类型为运动员(1)或其他,直接获取人员ID + _logger.LogInformation("页面类型为运动员,直接获取人员信息"); + + // 直接从 EventRegistrationPerson 表获取所有人员 + var directPersons = await _personRepository.GetListAsync(x => input.PersonIds.Contains(x.Id)); + foreach (var person in directPersons) + { + personDict[person.Id] = person; + _logger.LogInformation("直接获取 EventRegistrationPerson: {PersonId}", person.Id); + } + } // 使用最终找到的人员ID列表 var foundPersonIds = personDict.Values.Select(p => p.Id).ToList(); @@ -677,15 +679,15 @@ public class EventScoreService : BaseService, ITransient /// /// 根据页面类型获取允许的人员类型列表 /// - /// 页面类型 + /// 页面类型:1-运动员页面,2-教练页面,3-领队页面 /// 允许的人员类型列表 - private List GetAllowedPersonTypes(CertificatePageType pageType) + private List GetAllowedPersonTypes(int pageType) { return pageType switch { - CertificatePageType.Athlete => new List { PersonType.Athlete, PersonType.User }, - CertificatePageType.Coach => new List { PersonType.Coach }, - CertificatePageType.Leader => new List { PersonType.Leader }, + 1 => new List { PersonType.Athlete, PersonType.User }, + 2 => new List { PersonType.Coach }, + 3 => new List { PersonType.Leader }, _ => new List { PersonType.Athlete, PersonType.User, PersonType.Coach, PersonType.Leader } }; } diff --git a/YD_Event.Application/Event/UnifiedRegistrationService.cs b/YD_Event.Application/Event/UnifiedRegistrationService.cs index 2fc9ecd..04f5435 100644 --- a/YD_Event.Application/Event/UnifiedRegistrationService.cs +++ b/YD_Event.Application/Event/UnifiedRegistrationService.cs @@ -1160,7 +1160,8 @@ public class UnifiedRegistrationService : ITransient // 2. 再检查成员项目限报 - 检查队员是否超过个人/团队项目的限报数量 // 3. 然后检查人员项目组别重复 - 检查同一人员是否在同一项目的多个组别中重复报名 // 4. 再检查人员是否已报名 - 检查人员是否已在其他报名记录中报过同一项目组别 - // 5. 最后检查项目完成人数 - 确保人数与项目的完成人数完全一致 + // 5. 再检查团队成员冲突 - 检查同一人员是否在同一项目的多个团队中 + // 6. 最后检查项目完成人数 - 确保人数与项目的完成人数完全一致 // 检查1:团队组别容量限制 // 验证是否有组别超过了限报的团队数量(例如:某组别限报2队,但本次报了3队) @@ -1178,7 +1179,11 @@ public class UnifiedRegistrationService : ITransient // 验证人员是否已在其他已支付的报名记录中报过同一项目组别(避免重复占用名额) CheckPersonAlreadyRegisteredAsync(input, result, eventProjects, projects, eventProjectGroups, registrationPersons, allRegistrationItems, eventRegistrationIds); - // 检查5:项目完成人数验证 + // 检查5:团队成员冲突检测 + // 验证同一人员是否在同一项目的多个团队中(例如:同一队员既在1队又在2队) + CheckTeamMemberConflictAsync(input, result, eventProjects, projects, eventProjectGroups); + + // 检查6:项目完成人数验证 // 验证用户选择的参与人数必须与项目的"完成人数"完全一致(例如:项目要求完成3人,但只选了2人或选了4人) CheckProjectCompletionAsync(input, result, eventProjects, projects, eventProjectGroups); @@ -1595,6 +1600,112 @@ public class UnifiedRegistrationService : ITransient } } + /// + /// 检查团队成员冲突 + /// 验证同一人员是否在同一项目的多个团队中(例如:同一队员既在1队又在2队) + /// + private void CheckTeamMemberConflictAsync( + UnifiedRegistrationInput input, + RegistrationLimitCheckOutput result, + List eventProjects, + List projects, + List eventProjectGroups) + { + foreach (var item in input.Items) + { + if (!item.GroupId.HasValue) continue; + + // 获取组别信息 + var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == item.GroupId.Value); + if (eventProjectGroup == null) continue; + + // 获取赛事项目信息 + var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId); + if (eventProject == null) continue; + + // 获取基础项目信息 + var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId); + if (project == null) continue; + + // 判断是否为团队项目 + var isTeamProject = project.IsTeamProject(); + if (!isTeamProject) continue; + + // 获取所有团队 + var teams = GetTeamsFromItem(item); + if (teams.Count <= 1) continue; + + // 建立用户到团队的映射,用于检测冲突 + var userTeamMap = new Dictionary>(); + + foreach (var team in teams) + { + if (team.Persons == null || !team.Persons.Any()) continue; + + foreach (var person in team.Persons) + { + if (!person.UserId.HasValue) continue; + + var userId = person.UserId.Value; + var memberName = person.Name ?? "未知人员"; + + if (!userTeamMap.TryGetValue(userId, out var teamsList)) + { + teamsList = new List<(int, string)>(); + userTeamMap[userId] = teamsList; + } + + teamsList.Add((team.TeamIndex, memberName)); + } + } + + // 检测冲突:同一用户在多个团队中 + foreach (var kvp in userTeamMap) + { + if (kvp.Value.Count > 1) + { + var memberName = kvp.Value.First().MemberName; + var firstTeamIndex = kvp.Value.First().TeamIndex; + var projectName = project.Name ?? "未知项目"; + + // 提示信息格式:"XXX已在xx项目x队" + var errorMessage = $"{memberName}已在{projectName}{GetTeamNumber(firstTeamIndex)}"; + + _logger.LogWarning("团队成员冲突检测:人员{MemberName}已在项目{ProjectName}的多个团队中", + memberName, projectName); + + result.HasLimit = true; + result.LimitDetails.Add(CreateLimitDetail( + kvp.Key, + memberName, + "TeamMemberConflict", + errorMessage)); + } + } + } + } + + /// + /// 获取团队序号的中文表示 + /// + private static string GetTeamNumber(int teamIndex) + { + return teamIndex switch + { + 1 => "1队", + 2 => "2队", + 3 => "3队", + 4 => "4队", + 5 => "5队", + 6 => "6队", + 7 => "7队", + 8 => "8队", + 9 => "9队", + 10 => "10队", + _ => $"{teamIndex}队" + }; + } + /// /// 检查项目完成人数 /// diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml index bb5cf3a..85282d1 100644 --- a/YD_Event.Application/YD_Event.Application.xml +++ b/YD_Event.Application/YD_Event.Application.xml @@ -5897,7 +5897,7 @@ - 页面类型:1-运动员页面,2-教练页面,3-领队页面 + 页面类型:0-未指定,1-运动员页面,2-教练页面,3-领队页面 用于明确区分不同角色页面的证书生成逻辑 @@ -6758,7 +6758,27 @@ - 成员ID列表 + 成员ID列表(单个团队报名时使用,向后兼容) + + + + + 团队列表(支持多个团队报名同一项目组别) + + + + + 用户团队输入DTO + + + + + 团队序号(用于显示,如"团队一"、"团队二") + + + + + 该团队的成员ID列表 @@ -7755,11 +7775,18 @@ 发放证书(支持模板选择和批量发放) - + + + 验证 PageType 参数是否有效 + + 页面类型值 + 是否有效 + + 根据页面类型获取允许的人员类型列表 - 页面类型 + 页面类型:1-运动员页面,2-教练页面,3-领队页面 允许的人员类型列表 @@ -8143,6 +8170,17 @@ 检查人员是否已在其他报名中报过同一项目组别 + + + 检查团队成员冲突 + 验证同一人员是否在同一项目的多个团队中(例如:同一队员既在1队又在2队) + + + + + 获取团队序号的中文表示 + + 检查项目完成人数 diff --git a/YD_Event.Core/Enum/CertificatePageType.cs b/YD_Event.Core/Enum/CertificatePageType.cs deleted file mode 100644 index 4242b3f..0000000 --- a/YD_Event.Core/Enum/CertificatePageType.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.ComponentModel; - -namespace YD_Event.Core.Enum; - -/// -/// 证书页面类型 -/// -public enum CertificatePageType -{ - /// - /// 未指定(向后兼容) - /// - [Description("未指定")] - Unspecified = 0, - - /// - /// 运动员页面 - /// - [Description("运动员页面")] - Athlete = 1, - - /// - /// 教练页面 - /// - [Description("教练页面")] - Coach = 2, - - /// - /// 领队页面 - /// - [Description("领队页面")] - Leader = 3 -} diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml index 494f3b0..15bde8d 100644 --- a/YD_Event.Core/YD_Event.Core.xml +++ b/YD_Event.Core/YD_Event.Core.xml @@ -4295,31 +4295,6 @@ 禁用 - - - 证书页面类型 - - - - - 未指定(向后兼容) - - - - - 运动员页面 - - - - - 教练页面 - - - - - 领队页面 - - 收费模式枚举