教练/领队证书

This commit is contained in:
马梓彭 2026-04-27 13:22:59 +08:00
parent 9beafeb1b0
commit 6c152e5cd6
6 changed files with 623 additions and 111 deletions

View File

@ -151,38 +151,45 @@ public class AppUserService : BaseService<AppUserEntity>, ITransient
/// <returns></returns>
[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<string>(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<AppUserEntity>, ITransient
{
throw Oops.Bah($"该手机号在本单位已存在相同类型的成员");
}
entity.UpdatedUserId = _authManager.UserId;
entity.UpdatedTime = DateTime.Now;
}
@ -215,8 +222,7 @@ public class AppUserService : BaseService<AppUserEntity>, ITransient
throw Oops.Bah($"该手机号在本单位已存在相同类型的成员");
}
// 新增记录(即使手机号在其他单位或其他类型中已存在,也允许新增)
isNewRecord = true;
// 新增记录
entity = new AppUserEntity
{
Id = YitIdHelper.NextId(),
@ -227,6 +233,29 @@ public class AppUserService : BaseService<AppUserEntity>, 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<string>(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<AppUserEntity>, 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<AppUserEntity>, ITransient
entity.CertificationStatus = input.CertificationStatus;
}
// 保存用户信息
await _appUserRep.InsertOrUpdateAsync(entity);
// 如果是单位更换,处理团队关系
if (isUnitTransfer && oldMerchantId.HasValue)
{
await HandleTeamTransfer(entity.Id, oldMerchantId.Value, merchantCertificationId.Value);
}
}
/// <summary>
/// 处理单位更换时的团队成员关系
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="oldMerchantId">原单位ID</param>
/// <param name="newMerchantId">新单位ID</param>
/// <returns></returns>
private async Task HandleTeamTransfer(long userId, long oldMerchantId, long newMerchantId)
{
// 从原单位的所有团队中移除该成员
var oldTeamIds = await _db.Queryable<YD_Event.Core.Entities.Team>()
.Where(t => t.MerchantCertificationId == oldMerchantId && !t.DeleteMark)
.Select(t => t.Id)
.ToListAsync();
if (oldTeamIds.Any())
{
await _db.Updateable<TeamMember>()
.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<AppUserEntity>, ITransient
}
#endregion
}
}

View File

@ -95,6 +95,57 @@ public class EventRecordOutput
/// 团队名称
/// </summary>
public string TeamName { get; set; }
/// <summary>
/// 报名项目列表
/// </summary>
public List<EventRegistrationItemOutput> Items { get; set; } = new();
/// <summary>
/// 该赛事的所有报名记录ID列表
/// </summary>
public List<long> RegistrationIds { get; set; } = new();
}
/// <summary>
/// 报名项目输出
/// </summary>
public class EventRegistrationItemOutput
{
/// <summary>
/// 报名项目ID
/// </summary>
public long Id { get; set; }
/// <summary>
/// 项目名称
/// </summary>
public string ProjectName { get; set; }
/// <summary>
/// 项目类型名称
/// </summary>
public string ProjectTypeName { get; set; }
/// <summary>
/// 组别名称
/// </summary>
public string GroupName { get; set; }
/// <summary>
/// 报名人数
/// </summary>
public int PersonCount { get; set; }
/// <summary>
/// 成绩
/// </summary>
public string? Score { get; set; }
/// <summary>
/// 名次
/// </summary>
public string? Rank { get; set; }
}
/// <summary>

View File

@ -133,10 +133,15 @@ public class CoachLeaderQueryInput : Pagination
public class CoachLeaderListOutput
{
/// <summary>
/// 报名人员ID
/// 报名人员ID (0 表示没有 EventRegistrationPerson 记录,只有 TeamMember 记录)
/// </summary>
public long Id { get; set; }
/// <summary>
/// 用户ID (对应 AppUser.Id)
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 姓名
/// </summary>

View File

@ -18,13 +18,17 @@ public class EventRecordService : BaseService<EventRegistration>, ITransient
private readonly ISqlSugarRepository<AppUserEntity> _appUserRep;
private readonly ISqlSugarRepository<SysUserEntity> _sysUserRep;
private readonly ISqlSugarRepository<MerchantEntity> _merchantRep;
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRep;
private readonly ISqlSugarRepository<EventResult> _resultRep;
public EventRecordService(ISqlSugarRepository<EventRegistration> registrationRep,
ISqlSugarRepository<EventInfo> eventRep,
ISqlSugarRepository<EventCertificate> certificateRep,
ISqlSugarRepository<AppUserEntity> appUserRep,
ISqlSugarRepository<SysUserEntity> sysUserRep,
ISqlSugarRepository<MerchantEntity> merchantRep) : base(registrationRep)
ISqlSugarRepository<MerchantEntity> merchantRep,
ISqlSugarRepository<EventRegistrationItem> registrationItemRep,
ISqlSugarRepository<EventResult> resultRep) : base(registrationRep)
{
_registrationRep = registrationRep;
_eventRep = eventRep;
@ -32,6 +36,8 @@ public class EventRecordService : BaseService<EventRegistration>, ITransient
_appUserRep = appUserRep;
_sysUserRep = sysUserRep;
_merchantRep = merchantRep;
_registrationItemRep = registrationItemRep;
_resultRep = resultRep;
}
/// <summary>
@ -43,8 +49,8 @@ public class EventRecordService : BaseService<EventRegistration>, ITransient
// 参数验证
ValidateInput(input);
// Join EventRegistration and EventInfo
var query = _registrationRep.AsQueryable()
// 第一步:查询用户报名过的所有赛事(去重,按赛事分组)
var eventsQuery = _registrationRep.AsQueryable()
.LeftJoin<EventInfo>((r, e) => r.EventId == e.Id)
.LeftJoin<AppUserEntity>((r, e, u) => r.CreatedUserId == u.Id)
.LeftJoin<SysUserEntity>((r, e, u, s) => r.CreatedUserId == s.Id)
@ -58,21 +64,122 @@ public class EventRecordService : BaseService<EventRegistration>, 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<EventRecordOutput>
{
Total = total,
PageNo = pageIndex,
PageSize = pageSize,
Pages = pages,
Rows = pagedEvents
};
return pageResult;
}
private string GetRankName(int rank)
{
return rank switch
{
1 => "冠军",
2 => "亚军",
3 => "季军",
_ => $"{rank}名"
};
}
/// <summary>
@ -111,11 +218,11 @@ public class EventRecordService : BaseService<EventRegistration>, ITransient
/// <summary>
/// 获取证书列表
/// </summary>
[HttpGet("{registrationId}/certificates")]
public async Task<List<CertificateOutput>> GetCertificates([FromRoute] long registrationId)
[HttpGet("certificates")]
public async Task<List<CertificateOutput>> GetCertificates([FromQuery] List<long> registrationIds)
{
var certs = await _certificateRep.AsQueryable()
.Where(c => c.RegistrationId == registrationId)
.Where(c => registrationIds.Contains(c.RegistrationId))
.ToListAsync();
return certs.Select(c => new CertificateOutput

View File

@ -275,20 +275,25 @@ public class EventScoreService : BaseService<EventResult>, ITransient
/// 教练/领队列表
/// </summary>
/// <remarks>
/// 通过运动员所在的团队关联获取教练和领队人员信息
/// 通过运动员报名记录 -&gt; TeamMember团队关联 -&gt; 找到同团队的教练/领队
/// </remarks>
[DisplayName("教练领队列表")]
public async Task<PageResult<CoachLeaderListOutput>> PageCoachLeader(CoachLeaderQueryInput input)
{
_logger.LogInformation("开始查询教练/领队列表,赛事ID={EventId}, 人员类型={PersonType}",
input.EventId, input.PersonType);
// 先从 EventRegistrationPerson 查找,找不到则通过 TeamMember 关联查找
// 步骤1:获取该赛事的所有运动员的 UserId
var athleteUserIds = await _personRepository.AsQueryable()
.InnerJoin<EventRegistration>((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<CoachLeaderListOutput>();
}
@ -301,6 +306,7 @@ public class EventScoreService : BaseService<EventResult>, ITransient
if (!teamIds.Any())
{
_logger.LogWarning("未找到关联的团队信息");
return new PageResult<CoachLeaderListOutput>();
}
@ -313,42 +319,128 @@ public class EventScoreService : BaseService<EventResult>, ITransient
if (!coachLeaderUserIds.Any())
{
_logger.LogWarning("该团队中没有找到指定类型的人员");
return new PageResult<CoachLeaderListOutput>();
}
// 步骤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<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.Where((p, r) => r.EventId == input.EventId && coachLeaderUserIds.Contains(p.UserId) && p.PersonType == input.PersonType && !p.DeleteMark)
.LeftJoin<YD_Event.Core.Entities.AppUser>((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<CoachLeaderListOutput>
{
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<EventResult>, ITransient
})
.ToListAsync();
// 构建最终的人员字典
var personDict = new Dictionary<long, dynamic>();
// 处理 PersonIds:将负的 Id 转换为 UserId,然后尝试找到或创建对应的 EventRegistrationPerson
var processedPersonIds = new List<long>();
var userIdToNewPersonId = new Dictionary<long, long>();
// 根据页面类型区分人员信息获取逻辑
if (input.PageType == 2 || input.PageType == 3)
// 创建一个简单的人员信息类用于内部存储
var personInfoDict = new Dictionary<long, (long Id, PersonType PersonType, long UserId, string Name, string? ProjectName, string? GroupName, long RegistrationId, long ProjectId, long RegistrationItemId, long? EventProjectGroupId, bool DeleteMark, string? TeamName, long? MerchantCertificationId, EventRegistrationPerson Person)>();
// 从查询结果构建字典
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<EventRegistration>((p, r) => p.RegistrationId == r.Id)
.InnerJoin<TeamMember>((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<EventResult>, ITransient
var certsToInsert = new List<EventCertificate>();
var certsToUpdate = new List<EventCertificate>();
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<EventResult>, 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<EventResult>, 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<EventResult>, ITransient
}
}
// 获取实际的 EventRegistrationPerson ID(如果通过 UserId 映射过来的,需要使用实际的 Id)
// 获取实际的 EventRegistrationPerson ID
var actualPersonId = person.Id;
var merchantCertId = person.MerchantCertificationId;
@ -814,7 +1039,7 @@ public class EventScoreService : BaseService<EventResult>, 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<EventResult>, ITransient
{
if (input.Id <= 0)
{
throw Oops.Bah("无效的 ID");
throw Oops.Bah("未生成证书");
}
// 首先尝试直接通过 ParticipationRecordId(EventRegistrationPerson.Id)查找证书

View File

@ -86,6 +86,15 @@
<param name="input"></param>
<returns></returns>
</member>
<member name="M:YD_Event.Application.AppUser.AppUserService.HandleTeamTransfer(System.Int64,System.Int64,System.Int64)">
<summary>
处理单位更换时的团队成员关系
</summary>
<param name="userId">用户ID</param>
<param name="oldMerchantId">原单位ID</param>
<param name="newMerchantId">新单位ID</param>
<returns></returns>
</member>
<member name="M:YD_Event.Application.AppUser.AppUserService.DeleteMember(System.Int64)">
<summary>
删除成员
@ -4895,6 +4904,56 @@
团队名称
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRecordOutput.Items">
<summary>
报名项目列表
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRecordOutput.RegistrationIds">
<summary>
该赛事的所有报名记录ID列表
</summary>
</member>
<member name="T:YD_Event.Application.Event.Dtos.EventRegistrationItemOutput">
<summary>
报名项目输出
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRegistrationItemOutput.Id">
<summary>
报名项目ID
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRegistrationItemOutput.ProjectName">
<summary>
项目名称
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRegistrationItemOutput.ProjectTypeName">
<summary>
项目类型名称
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRegistrationItemOutput.GroupName">
<summary>
组别名称
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRegistrationItemOutput.PersonCount">
<summary>
报名人数
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRegistrationItemOutput.Score">
<summary>
成绩
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.EventRegistrationItemOutput.Rank">
<summary>
名次
</summary>
</member>
<member name="T:YD_Event.Application.Event.Dtos.CertificateOutput">
<summary>
证书信息输出
@ -5977,7 +6036,12 @@
</member>
<member name="P:YD_Event.Application.Event.Dtos.CoachLeaderListOutput.Id">
<summary>
报名人员ID
报名人员ID (0 表示没有 EventRegistrationPerson 记录,只有 TeamMember 记录)
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.CoachLeaderListOutput.UserId">
<summary>
用户ID (对应 AppUser.Id)
</summary>
</member>
<member name="P:YD_Event.Application.Event.Dtos.CoachLeaderListOutput.Name">
@ -7911,7 +7975,7 @@
验证输入参数
</summary>
</member>
<member name="M:YD_Event.Application.Event.EventRecordService.GetCertificates(System.Int64)">
<member name="M:YD_Event.Application.Event.EventRecordService.GetCertificates(System.Collections.Generic.List{System.Int64})">
<summary>
获取证书列表
</summary>
@ -7976,7 +8040,7 @@
教练/领队列表
</summary>
<remarks>
通过运动员所在的团队关联获取教练和领队人员信息
通过运动员报名记录 -&gt; TeamMember团队关联 -&gt; 找到同团队的教练/领队
</remarks>
</member>
<!-- Badly formed XML comment ignored for member "M:YD_Event.Application.Event.EventScoreService.ImportScore(System.Int64,Microsoft.AspNetCore.Http.IFormFile)" -->