教练/领队证书
This commit is contained in:
parent
9beafeb1b0
commit
6c152e5cd6
@ -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
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -275,20 +275,25 @@ public class EventScoreService : BaseService<EventResult>, ITransient
|
||||
/// 教练/领队列表
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 通过运动员所在的团队关联获取教练和领队人员信息
|
||||
/// 通过运动员报名记录 -> TeamMember团队关联 -> 找到同团队的教练/领队
|
||||
/// </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)查找证书
|
||||
|
||||
@ -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>
|
||||
通过运动员所在的团队关联获取教练和领队人员信息
|
||||
通过运动员报名记录 -> TeamMember团队关联 -> 找到同团队的教练/领队
|
||||
</remarks>
|
||||
</member>
|
||||
<!-- Badly formed XML comment ignored for member "M:YD_Event.Application.Event.EventScoreService.ImportScore(System.Int64,Microsoft.AspNetCore.Http.IFormFile)" -->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user