using YD_Event.Application.Event.Dtos; using YD_Event.Application.Auth; using YD_Event.Core.Entities; using YD_Event.Core.Enum; using MiniExcelLibs; using YD_Event.Core.Domain; namespace YD_Event.Application.Event; /// /// 报名管理服务 /// public class EventRegistrationService : BaseService, ITransient { private readonly ISqlSugarRepository _repository; private readonly ISqlSugarRepository _itemRepository; private readonly ISqlSugarRepository _personRepository; private readonly ISqlSugarRepository _paymentRepository; private readonly ISqlSugarRepository _orderRepository; private readonly ISqlSugarRepository _teamMemberRepository; private readonly ISqlSugarRepository _appUserRepository; private readonly ISqlSugarRepository _merchantCertificationRepository; private readonly ISqlSugarRepository _eventInfoRepository; private readonly ISqlSugarRepository _eventProjectRepository; private readonly ISqlSugarRepository _projectRepository; private readonly IIdGenerator _idGenerator; private readonly OrderNoGeneratorService _orderNoGeneratorService; private readonly AuthManager _authManager; public EventRegistrationService( ISqlSugarRepository repository, ISqlSugarRepository itemRepository, ISqlSugarRepository personRepository, ISqlSugarRepository paymentRepository, ISqlSugarRepository orderRepository, ISqlSugarRepository teamMemberRepository, ISqlSugarRepository appUserRepository, ISqlSugarRepository merchantCertificationRepository, ISqlSugarRepository eventInfoRepository, ISqlSugarRepository eventProjectRepository, ISqlSugarRepository projectRepository, IIdGenerator idGenerator, OrderNoGeneratorService orderNoGeneratorService, AuthManager authManager) : base(repository) { _repository = repository; _itemRepository = itemRepository; _personRepository = personRepository; _paymentRepository = paymentRepository; _orderRepository = orderRepository; _teamMemberRepository = teamMemberRepository; _appUserRepository = appUserRepository; _merchantCertificationRepository = merchantCertificationRepository; _eventInfoRepository = eventInfoRepository; _eventProjectRepository = eventProjectRepository; _projectRepository = projectRepository; _idGenerator = idGenerator; _orderNoGeneratorService = orderNoGeneratorService; _authManager = authManager; } /// /// 分页查询报名 /// [DisplayName("分页查询报名")] public async Task> Page(RegistrationQueryInput input) { var query = _repository.AsQueryable() .Where(x => x.PaymentStatus != 4) // 过滤掉已取消的报名记录 .WhereIF(input.EventId.HasValue, x => x.EventId == input.EventId) .WhereIF(input.RegistrationMode.HasValue, x => x.RegistrationMode == input.RegistrationMode) .WhereIF(!string.IsNullOrWhiteSpace(input.UnitName), x => x.TeamName.Contains(input.UnitName)) .WhereIF(!string.IsNullOrWhiteSpace(input.UserName), x => x.ContactName.Contains(input.UserName)) .WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), x => x.ContactPhone.Contains(input.ContactPhone)) .WhereIF(input.IsUploadMaterial.HasValue, x => (input.IsUploadMaterial.Value ? !string.IsNullOrEmpty(x.MaterialUrl) : string.IsNullOrEmpty(x.MaterialUrl))) .WhereIF(input.PaymentMethod.HasValue, x => x.PaymentMethod == input.PaymentMethod) .WhereIF(input.PaymentStatus.HasValue, x => x.PaymentStatus == input.PaymentStatus) .WhereIF(input.RegistrationTimeStart.HasValue, x => x.RegistrationTime >= input.RegistrationTimeStart) .WhereIF(input.RegistrationTimeEnd.HasValue, x => x.RegistrationTime <= input.RegistrationTimeEnd) .OrderByDescending(x => x.RegistrationTime) .Select(x => new RegistrationListOutput { Id = x.Id, RegistrationMode = x.RegistrationMode, CreatedUserId = x.CreatedUserId, // 保存创建用户ID以便后续查询单位名称 MerchantCertificationId = x.MerchantCertificationId, // 保存单位认证ID以便后续查询单位名称 UnitName = null, // 不初始化为 TeamName,只从单位认证信息中获取 ContactName = x.ContactName, ContactPhone = x.ContactPhone, TotalPersonCount = x.TotalPersonCount, IsUploadMaterial = !string.IsNullOrEmpty(x.MaterialUrl), PaymentMethod = x.PaymentMethod, PaymentMethodType = x.PaymentMethodType, OrderNo = x.OrderNo, TotalAmount = x.TotalAmount, PaymentStatus = x.PaymentStatus, PaymentProofUrl = x.PaymentProofUrl, RegistrationTime = x.RegistrationTime }); var result = await query.ToPagedListAsync(input); // 获取所有报名记录的 MerchantCertificationId var regIds = result.Rows.Select(x => x.Id).ToList(); var merchantCertIds = result.Rows .Where(r => r.MerchantCertificationId.HasValue) .Select(r => r.MerchantCertificationId.Value) .Distinct() .ToList(); // 获取单位认证信息 Dictionary merchantNameDict = new Dictionary(); if (merchantCertIds.Any()) { var merchantCerts = await _merchantCertificationRepository.AsQueryable() .Where(mc => merchantCertIds.Contains(mc.Id) && !mc.DeleteMark) .ToListAsync(); merchantNameDict = merchantCerts.ToDictionary(mc => mc.Id, mc => mc.MerchantName); } // 填充下拉详情 if (result.Rows.Any()) { var items = await _itemRepository.GetListAsync(x => regIds.Contains(x.RegistrationId) && !x.DeleteMark); // 使用关联查询获取报名人员及其关联的用户信息 var persons = await _personRepository.AsQueryable() .Includes(p => p.User) // 关联查询用户信息 .Where(x => regIds.Contains(x.RegistrationId) && !x.DeleteMark) .ToListAsync(); // 获取所有赛事项目和项目信息,用于判断项目类型 var eventProjectIds = items.Select(x => x.ProjectId).Distinct().ToList(); var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id)); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); // 创建映射字典 var eventProjectToProjectDict = eventProjects.ToDictionary(x => x.Id, x => x.ProjectId); Dictionary projectDict = projects.ToDictionary(x => x.Id, x => x); // 获取所有报名人员的 UserId var allAthleteUserIds = persons .Where(p => p.PersonType == YD_Event.Core.Enum.PersonType.Athlete && p.UserId > 0) .Select(p => p.UserId) .Distinct() .ToList(); // 步骤1:通过运动员的 UserId 找到对应的 TeamId var teamIds = new List(); if (allAthleteUserIds.Any()) { teamIds = await _teamMemberRepository.AsQueryable() .Where(tm => allAthleteUserIds.Contains(tm.UserId) && !tm.DeleteMark) .Select(tm => tm.TeamId) .Distinct() .ToListAsync(); } // 步骤2:通过 TeamId 获取同团队的教练/领队的 UserId,并获取用户详情 Dictionary> teamLeadersDict = new(); Dictionary> teamCoachesDict = new(); if (teamIds.Any()) { var teamMembers = await _teamMemberRepository.AsQueryable() .Where(tm => teamIds.Contains(tm.TeamId) && !tm.DeleteMark) .ToListAsync(); var coachLeaderUserIds = teamMembers .Where(tm => tm.PersonType == YD_Event.Core.Enum.PersonType.Leader || tm.PersonType == YD_Event.Core.Enum.PersonType.Coach) .Select(tm => tm.UserId) .Distinct() .ToList(); if (coachLeaderUserIds.Any()) { var coachLeaderUsers = await _appUserRepository.AsQueryable() .Where(u => coachLeaderUserIds.Contains(u.Id) && !u.DeleteMark) .ToListAsync(); var userDict = coachLeaderUsers.ToDictionary(u => u.Id); // 按团队分组 foreach (var teamId in teamIds) { var teamLeaders = teamMembers .Where(tm => tm.TeamId == teamId && tm.PersonType == YD_Event.Core.Enum.PersonType.Leader) .Select(tm => userDict.TryGetValue(tm.UserId, out var user) ? user : null) .Where(u => u != null) .Cast() .ToList(); var teamCoaches = teamMembers .Where(tm => tm.TeamId == teamId && tm.PersonType == YD_Event.Core.Enum.PersonType.Coach) .Select(tm => userDict.TryGetValue(tm.UserId, out var user) ? user : null) .Where(u => u != null) .Cast() .ToList(); if (teamLeaders.Any()) teamLeadersDict[teamId] = teamLeaders; if (teamCoaches.Any()) teamCoachesDict[teamId] = teamCoaches; } } } foreach (var dto in result.Rows) { // 获取当前报名的所有人员 var regPersons = persons.Where(p => p.RegistrationId == dto.Id).ToList(); // 根据报名记录的 MerchantCertificationId 获取单位名称 if (dto.MerchantCertificationId.HasValue && merchantNameDict.TryGetValue(dto.MerchantCertificationId.Value, out var unitName)) { dto.UnitName = unitName; } else { dto.UnitName = null; } var myItems = items.Where(x => x.RegistrationId == dto.Id).ToList(); dto.Items = new List(); foreach (var item in myItems) { var myPersons = persons.Where(p => p.RegistrationItemId == item.Id).ToList(); // 判断项目类型 bool isIndividualProject = false; if (eventProjectToProjectDict.TryGetValue(item.ProjectId, out var projectId) && projectDict.TryGetValue(projectId, out var project)) { isIndividualProject = YD_Event.Core.Extensions.ProjectTypeExtensions.IsIndividualProject(project); } if (isIndividualProject) { // 个人项目:为每个运动员创建一个条目,使用 EventRegistrationPerson.Id foreach (var person in myPersons.Where(p => p.PersonType == PersonType.Athlete)) { // 构造参与人信息字符串 var genderText = person.Gender switch { YD_Event.Core.Enum.Gender.Male => "男", YD_Event.Core.Enum.Gender.Female => "女", _ => "未知" }; var mobile = person.User?.Mobile ?? person.Phone ?? ""; var personInfo = $"{person.Name}/{genderText}/{person.Age}/{mobile}"; var detail = new RegistrationItemDetailDto { Id = person.Id, // 使用 EventRegistrationPerson.Id ProjectName = item.ProjectName, GroupName = item.GroupName, ParticipantInfo = personInfo }; dto.Items.Add(detail); } } else { // 团队项目:保持原有逻辑,使用 EventRegistrationItem.Id // 构造参与人信息字符串,统一格式为:姓名/性别/年龄/手机号 var personInfo = string.Join(";", myPersons.Select(p => { // 将性别转换为中文显示 var genderText = p.Gender switch { YD_Event.Core.Enum.Gender.Male => "男", YD_Event.Core.Enum.Gender.Female => "女", _ => "未知" }; // 获取手机号,优先使用关联用户信息,否则使用自身的 Phone 字段 var mobile = p.User?.Mobile ?? p.Phone ?? ""; // 统一格式:姓名/性别/年龄/手机号 return $"{p.Name}/{genderText}/{p.Age}/{mobile}"; })); if (personInfo.Length > 100) personInfo = personInfo.Substring(0, 97) + "..."; // 截断 var detail = new RegistrationItemDetailDto { Id = item.Id, // 使用 EventRegistrationItem.Id ProjectName = item.ProjectName, GroupName = item.GroupName, ParticipantInfo = personInfo }; dto.Items.Add(detail); } } // 设置领队信息和教练信息 - 从团队中获取 var regAthleteUserIds = regPersons .Where(p => p.PersonType == YD_Event.Core.Enum.PersonType.Athlete && p.UserId > 0) .Select(p => p.UserId) .Distinct() .ToList(); // 获取这些运动员所属的团队 var regTeamIds = new List(); if (regAthleteUserIds.Any()) { regTeamIds = await _teamMemberRepository.AsQueryable() .Where(tm => regAthleteUserIds.Contains(tm.UserId) && !tm.DeleteMark) .Select(tm => tm.TeamId) .Distinct() .ToListAsync(); } // 收集所有相关团队的领队和教练 var allLeaders = new List(); var allCoaches = new List(); foreach (var teamId in regTeamIds) { if (teamLeadersDict.TryGetValue(teamId, out var leaders)) allLeaders.AddRange(leaders); if (teamCoachesDict.TryGetValue(teamId, out var coaches)) allCoaches.AddRange(coaches); } // 去重 allLeaders = allLeaders.GroupBy(l => l.Id).Select(g => g.First()).ToList(); allCoaches = allCoaches.GroupBy(c => c.Id).Select(g => g.First()).ToList(); // 如果从团队中没有找到,再回退到从报名人员中查找 if (!allLeaders.Any()) { var regLeaders = regPersons.Where(p => p.PersonType == YD_Event.Core.Enum.PersonType.Leader).ToList(); allLeaders = regLeaders.Select(l => new YD_Event.Core.Entities.AppUser { Name = l.Name, Mobile = l.Phone, Gender = l.Gender }).ToList(); } if (!allCoaches.Any()) { var regCoaches = regPersons.Where(p => p.PersonType == YD_Event.Core.Enum.PersonType.Coach).ToList(); allCoaches = regCoaches.Select(c => new YD_Event.Core.Entities.AppUser { Name = c.Name, Mobile = c.Phone, Gender = c.Gender }).ToList(); } // 辅助函数:获取性别文本 Func getGenderText = (gender) => { return gender switch { YD_Event.Core.Enum.Gender.Male => "男", YD_Event.Core.Enum.Gender.Female => "女", _ => "未知" }; }; dto.TeamLeaderInfo = allLeaders.Any() ? string.Join(",", allLeaders.Select(l => $"{l.Name}/{getGenderText(l.Gender)}/{l.Mobile}")) : null; dto.CoachInfo = allCoaches.Any() ? string.Join(",", allCoaches.Select(c => $"{c.Name}/{getGenderText(c.Gender)}/{c.Mobile}")) : null; } } return result; } /// /// 为报名记录创建订单(如果不存在) /// private async Task CreateOrderIfNotExistsAsync(EventRegistration registration, DateTime now) { // 检查是否已存在订单 var existingOrder = await _orderRepository.GetFirstAsync(o => o.RegistrationId == registration.Id && !o.DeleteMark); if (existingOrder == null) { // 不存在订单,先检查是否有订单号 if (string.IsNullOrEmpty(registration.OrderNo)) { registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync(); await _repository.UpdateAsync(registration); } // 获取赛事信息 var eventInfo = await _eventInfoRepository.GetByIdAsync(registration.EventId); // 获取用户单位认证信息 var certification = await _merchantCertificationRepository.GetFirstAsync(c => c.UserId == registration.CreatedUserId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark); // 创建订单 var order = new YD_Event.Core.Entities.Order { Id = _idGenerator.NewLong(), OrderNo = registration.OrderNo, OrderType = 1, MerchantId = eventInfo?.MerchantId, MerchantCertificationId = certification?.Id, EventId = registration.EventId, RegistrationId = registration.Id, OriginalAmount = registration.TotalAmount, ActualAmount = registration.TotalAmount, DiscountAmount = 0, CouponId = null, OrderStatus = OrderStatus.Paid, PaymentTime = now, CreatedTime = now, CreatedUserId = _authManager.UserId, UpdatedTime = now, UpdatedUserId = _authManager.UserId, Status = AvailabilityStatus.Enable }; await _orderRepository.InsertAsync(order); } else { // 订单已存在,只更新状态为已支付 if (existingOrder.OrderStatus != OrderStatus.Paid) { existingOrder.OrderStatus = OrderStatus.Paid; existingOrder.PaymentTime = now; existingOrder.UpdatedTime = now; existingOrder.UpdatedUserId = _authManager.UserId; await _orderRepository.UpdateAsync(existingOrder); } } } /// /// 批量审核 /// [UnitOfWork] [DisplayName("批量审核")] public async Task BatchAudit(BatchAuditInput input) { if (input.Ids == null || !input.Ids.Any()) return; var now = DateTime.Now; // 如果审核通过(PaymentStatus = 1),先处理订单创建和更新 if (input.PaymentStatus == 1) { // 获取需要更新的报名记录 var registrations = await _repository.GetListAsync(x => input.Ids.Contains(x.Id)); foreach (var registration in registrations) { // 确保订单存在 await CreateOrderIfNotExistsAsync(registration, now); // 更新报名记录的支付状态 registration.PaymentStatus = input.PaymentStatus; registration.ReviewRemark = input.ReviewRemark; registration.ReviewTime = now; registration.PaymentTime = now; registration.UpdatedTime = now; registration.UpdatedUserId = _authManager.UserId; await _repository.UpdateAsync(registration); } } else { // 审核不通过,只更新状态 await _repository.UpdateAsync(x => new EventRegistration { PaymentStatus = input.PaymentStatus, ReviewRemark = input.ReviewRemark, ReviewTime = now }, x => input.Ids.Contains(x.Id)); } } /// /// 导出报名 /// [DisplayName("导出报名")] public async Task Export(RegistrationQueryInput input) { var registrations = await _repository.AsQueryable() .WhereIF(input.EventId.HasValue, x => x.EventId == input.EventId) .WhereIF(input.RegistrationMode.HasValue, x => x.RegistrationMode == input.RegistrationMode) .WhereIF(!string.IsNullOrWhiteSpace(input.UnitName), x => x.TeamName.Contains(input.UnitName)) .WhereIF(!string.IsNullOrWhiteSpace(input.UserName), x => x.ContactName.Contains(input.UserName)) .WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), x => x.ContactPhone.Contains(input.ContactPhone)) .WhereIF(input.PaymentStatus.HasValue, x => x.PaymentStatus == input.PaymentStatus) .ToListAsync(); // 获取所有报名记录的 CreatedUserId var createdUserIds = registrations.Select(x => x.CreatedUserId).Distinct().ToList(); // 获取这些创建用户的信息 var createdUsers = await _appUserRepository.AsQueryable() .Where(u => createdUserIds.Contains(u.Id) && !u.DeleteMark) .ToListAsync(); // 获取这些用户的 MerchantCertificationId var merchantCertIds = createdUsers .Where(u => u.MerchantCertificationId.HasValue) .Select(u => u.MerchantCertificationId.Value) .Distinct() .ToList(); // 获取单位认证信息 Dictionary merchantNameDict = new Dictionary(); if (merchantCertIds.Any()) { var merchantCerts = await _merchantCertificationRepository.AsQueryable() .Where(mc => merchantCertIds.Contains(mc.Id) && !mc.DeleteMark) .ToListAsync(); merchantNameDict = merchantCerts.ToDictionary(mc => mc.Id, mc => mc.MerchantName); } // 创建 CreatedUserId 到单位名称的映射 Dictionary userIdToUnitNameDict = new Dictionary(); foreach (var user in createdUsers) { if (user.MerchantCertificationId.HasValue && merchantNameDict.TryGetValue(user.MerchantCertificationId.Value, out var unitName)) { userIdToUnitNameDict[user.Id] = unitName; } } // 构建导出列表 var list = registrations.Select(x => { // 根据报名记录的 CreatedUserId 获取单位名称 string? unitName = null; if (userIdToUnitNameDict.TryGetValue(x.CreatedUserId, out var name)) { unitName = name; } return new { x.RegistrationMode, UnitName = unitName, TeamName = x.TeamName, x.ContactName, x.ContactPhone, x.TotalPersonCount, x.PaymentMethod, x.TotalAmount, x.PaymentStatus, x.RegistrationTime }; }).ToList(); using var stream = new MemoryStream(); stream.SaveAs(list); return stream.ToArray(); } /// /// 获取素材下载链接 /// [DisplayName("获取素材下载链接")] public async Task> MaterialUrls(List ids) { if (ids == null || !ids.Any()) return new List(); return await _repository.AsQueryable() .Where(x => ids.Contains(x.Id) && !string.IsNullOrEmpty(x.MaterialUrl)) .Select(x => x.MaterialUrl) .ToListAsync(); } /// /// 单条支付审核 /// [UnitOfWork] [DisplayName("单条支付审核")] public async Task PaymentAudit(PaymentAuditInput input) { var registration = await _repository.GetByIdAsync(input.RegistrationId); if (registration == null) throw Oops.Bah("报名记录不存在"); if (registration.PaymentStatus != 0) { throw Oops.Bah("该记录已完成支付审核,无需重复操作"); } var now = DateTime.Now; registration.PaymentStatus = input.PaymentStatus; registration.ReviewRemark = input.ReviewRemark; registration.ReviewTime = now; registration.Reviewer = _authManager.Account; registration.UpdatedTime = now; registration.UpdatedUserId = _authManager.UserId; if (input.PaymentStatus == 1) { registration.PaymentTime = now; // 确保订单存在 await CreateOrderIfNotExistsAsync(registration, now); } await _repository.UpdateAsync(registration); } /// /// 批量支付审核 /// [UnitOfWork] [DisplayName("批量支付审核")] public async Task BatchPaymentAudit(BatchPaymentAuditInput input) { if (input.Ids == null || !input.Ids.Any()) return; var registrations = await _repository.GetListAsync(x => input.Ids.Contains(x.Id)); if (!registrations.Any()) throw Oops.Bah("未找到有效的报名记录"); var invalidRegistrations = registrations .Where(r => r.PaymentStatus != 0) .ToList(); if (invalidRegistrations.Any()) { throw Oops.Bah("存在非待审核状态的记录,无法进行支付审核"); } var now = DateTime.Now; // 如果审核通过(PaymentStatus = 1),先处理订单创建 if (input.PaymentStatus == 1) { foreach (var registration in registrations) { // 确保订单存在 await CreateOrderIfNotExistsAsync(registration, now); } } // 更新报名记录的状态 await _repository.UpdateAsync(x => new EventRegistration { PaymentStatus = input.PaymentStatus, ReviewRemark = input.ReviewRemark, ReviewTime = now, Reviewer = _authManager.Account, UpdatedTime = now, UpdatedUserId = _authManager.UserId, PaymentTime = input.PaymentStatus == 1 ? now : x.PaymentTime }, x => input.Ids.Contains(x.Id)); } }