赛事报名限制
This commit is contained in:
parent
fc2cc2f2bb
commit
c9eb639123
@ -198,7 +198,7 @@ public class IssueCertificateInput
|
||||
/// 页面类型:1-运动员页面,2-教练页面,3-领队页面
|
||||
/// 用于明确区分不同角色页面的证书生成逻辑
|
||||
/// </summary>
|
||||
public CertificatePageType PageType { get; set; }
|
||||
public int PageType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -423,25 +423,14 @@ public class EventScoreService : BaseService<EventResult>, 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<long, EventRegistrationPerson>();
|
||||
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<EventResult>, 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<EventResult>, ITransient
|
||||
/// <summary>
|
||||
/// 根据页面类型获取允许的人员类型列表
|
||||
/// </summary>
|
||||
/// <param name="pageType">页面类型</param>
|
||||
/// <param name="pageType">页面类型:1-运动员页面,2-教练页面,3-领队页面</param>
|
||||
/// <returns>允许的人员类型列表</returns>
|
||||
private List<PersonType> GetAllowedPersonTypes(CertificatePageType pageType)
|
||||
private List<PersonType> GetAllowedPersonTypes(int pageType)
|
||||
{
|
||||
return pageType switch
|
||||
{
|
||||
CertificatePageType.Athlete => new List<PersonType> { PersonType.Athlete, PersonType.User },
|
||||
CertificatePageType.Coach => new List<PersonType> { PersonType.Coach },
|
||||
CertificatePageType.Leader => new List<PersonType> { PersonType.Leader },
|
||||
1 => new List<PersonType> { PersonType.Athlete, PersonType.User },
|
||||
2 => new List<PersonType> { PersonType.Coach },
|
||||
3 => new List<PersonType> { PersonType.Leader },
|
||||
_ => new List<PersonType> { PersonType.Athlete, PersonType.User, PersonType.Coach, PersonType.Leader }
|
||||
};
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查团队成员冲突
|
||||
/// 验证同一人员是否在同一项目的多个团队中(例如:同一队员既在1队又在2队)
|
||||
/// </summary>
|
||||
private void CheckTeamMemberConflictAsync(
|
||||
UnifiedRegistrationInput input,
|
||||
RegistrationLimitCheckOutput result,
|
||||
List<EventProject> eventProjects,
|
||||
List<YD_Event.Core.Entities.Project> projects,
|
||||
List<EventProjectGroup> 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<long, List<(int TeamIndex, string MemberName)>>();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取团队序号的中文表示
|
||||
/// </summary>
|
||||
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}队"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查项目完成人数
|
||||
/// </summary>
|
||||
|
||||
@ -5897,7 +5897,7 @@
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.IssueCertificateInput.PageType">
|
||||
<summary>
|
||||
页面类型:1-运动员页面,2-教练页面,3-领队页面
|
||||
页面类型:0-未指定,1-运动员页面,2-教练页面,3-领队页面
|
||||
用于明确区分不同角色页面的证书生成逻辑
|
||||
</summary>
|
||||
</member>
|
||||
@ -6758,7 +6758,27 @@
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UserGroupMemberInput.MemberIds">
|
||||
<summary>
|
||||
成员ID列表
|
||||
成员ID列表(单个团队报名时使用,向后兼容)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UserGroupMemberInput.Teams">
|
||||
<summary>
|
||||
团队列表(支持多个团队报名同一项目组别)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UserTeamInput">
|
||||
<summary>
|
||||
用户团队输入DTO
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UserTeamInput.TeamIndex">
|
||||
<summary>
|
||||
团队序号(用于显示,如"团队一"、"团队二")
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Application.Event.Dtos.UserTeamInput.MemberIds">
|
||||
<summary>
|
||||
该团队的成员ID列表
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Application.Event.Dtos.UserOrderPreviewOutput">
|
||||
@ -7755,11 +7775,18 @@
|
||||
发放证书(支持模板选择和批量发放)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.EventScoreService.GetAllowedPersonTypes(YD_Event.Core.Enum.CertificatePageType)">
|
||||
<member name="M:YD_Event.Application.Event.EventScoreService.IsValidPageType(System.Int32)">
|
||||
<summary>
|
||||
验证 PageType 参数是否有效
|
||||
</summary>
|
||||
<param name="pageType">页面类型值</param>
|
||||
<returns>是否有效</returns>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.EventScoreService.GetAllowedPersonTypes(System.Int32)">
|
||||
<summary>
|
||||
根据页面类型获取允许的人员类型列表
|
||||
</summary>
|
||||
<param name="pageType">页面类型</param>
|
||||
<param name="pageType">页面类型:1-运动员页面,2-教练页面,3-领队页面</param>
|
||||
<returns>允许的人员类型列表</returns>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.EventScoreService.BuildPlaceholderData(YD_Event.Core.Entities.EventRegistrationPerson,YD_Event.Core.Entities.EventResult)">
|
||||
@ -8143,6 +8170,17 @@
|
||||
检查人员是否已在其他报名中报过同一项目组别
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CheckTeamMemberConflictAsync(YD_Event.Application.Event.Dtos.UnifiedRegistrationInput,YD_Event.Application.Event.Dtos.RegistrationLimitCheckOutput,System.Collections.Generic.List{YD_Event.Core.Entities.EventProject},System.Collections.Generic.List{YD_Event.Core.Entities.Project},System.Collections.Generic.List{YD_Event.Core.Entities.EventProjectGroup})">
|
||||
<summary>
|
||||
检查团队成员冲突
|
||||
验证同一人员是否在同一项目的多个团队中(例如:同一队员既在1队又在2队)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.GetTeamNumber(System.Int32)">
|
||||
<summary>
|
||||
获取团队序号的中文表示
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:YD_Event.Application.Event.UnifiedRegistrationService.CheckProjectCompletionAsync(YD_Event.Application.Event.Dtos.UnifiedRegistrationInput,YD_Event.Application.Event.Dtos.RegistrationLimitCheckOutput,System.Collections.Generic.List{YD_Event.Core.Entities.EventProject},System.Collections.Generic.List{YD_Event.Core.Entities.Project},System.Collections.Generic.List{YD_Event.Core.Entities.EventProjectGroup})">
|
||||
<summary>
|
||||
检查项目完成人数
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace YD_Event.Core.Enum;
|
||||
|
||||
/// <summary>
|
||||
/// 证书页面类型
|
||||
/// </summary>
|
||||
public enum CertificatePageType
|
||||
{
|
||||
/// <summary>
|
||||
/// 未指定(向后兼容)
|
||||
/// </summary>
|
||||
[Description("未指定")]
|
||||
Unspecified = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 运动员页面
|
||||
/// </summary>
|
||||
[Description("运动员页面")]
|
||||
Athlete = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 教练页面
|
||||
/// </summary>
|
||||
[Description("教练页面")]
|
||||
Coach = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 领队页面
|
||||
/// </summary>
|
||||
[Description("领队页面")]
|
||||
Leader = 3
|
||||
}
|
||||
@ -4295,31 +4295,6 @@
|
||||
禁用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Core.Enum.CertificatePageType">
|
||||
<summary>
|
||||
证书页面类型
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Core.Enum.CertificatePageType.Unspecified">
|
||||
<summary>
|
||||
未指定(向后兼容)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Core.Enum.CertificatePageType.Athlete">
|
||||
<summary>
|
||||
运动员页面
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Core.Enum.CertificatePageType.Coach">
|
||||
<summary>
|
||||
教练页面
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:YD_Event.Core.Enum.CertificatePageType.Leader">
|
||||
<summary>
|
||||
领队页面
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:YD_Event.Core.Enum.ChargeMode">
|
||||
<summary>
|
||||
收费模式枚举
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user