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 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, 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; _idGenerator = idGenerator; _orderNoGeneratorService = orderNoGeneratorService; _authManager = authManager; } /// /// 分页查询报名 /// [DisplayName("分页查询报名")] public async Task> Page(RegistrationQueryInput input) { var query = _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.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, UnitName = x.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); // 填充下拉详情 if (result.Rows.Any()) { var regIds = result.Rows.Select(x => x.Id).ToList(); var items = await _itemRepository.GetListAsync(x => regIds.Contains(x.RegistrationId)); // 使用关联查询获取报名人员及其关联的用户信息 var persons = await _personRepository.AsQueryable() .Includes(p => p.User) // 关联查询用户信息 .Where(x => regIds.Contains(x.RegistrationId)) .ToListAsync(); // 获取所有相关用户的 MerchantCertificationId var merchantCertIds = persons .Where(p => p.User != null && p.User.MerchantCertificationId.HasValue) .Select(p => p.User.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); } // 获取所有报名人员的 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(); // 从关联的单位认证信息中提取单位名称 string? unitName = null; foreach (var person in regPersons) { if (person.User != null && person.User.MerchantCertificationId.HasValue) { if (merchantNameDict.TryGetValue(person.User.MerchantCertificationId.Value, out var name)) { unitName = name; break; // 找到第一个就可以了 } } } // 如果没有找到单位认证信息,回退使用原来的 TeamName dto.UnitName = unitName ?? dto.UnitName; 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(); // 构造参与人信息字符串,统一格式为:姓名/性别/年龄/手机号 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 { 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(); // 获取相关的报名人员和用户信息 var regIds = registrations.Select(x => x.Id).ToList(); var persons = await _personRepository.AsQueryable() .Includes(p => p.User) .Where(x => regIds.Contains(x.RegistrationId)) .ToListAsync(); // 获取所有相关用户的 MerchantCertificationId var merchantCertIds = persons .Where(p => p.User != null && p.User.MerchantCertificationId.HasValue) .Select(p => p.User.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); } // 构建导出列表 var list = registrations.Select(x => { // 获取当前报名的人员 var regPersons = persons.Where(p => p.RegistrationId == x.Id).ToList(); // 获取单位名称 string? unitName = x.TeamName; foreach (var person in regPersons) { if (person.User != null && person.User.MerchantCertificationId.HasValue) { if (merchantNameDict.TryGetValue(person.User.MerchantCertificationId.Value, out var name)) { unitName = name; break; } } } return new { x.RegistrationMode, TeamName = unitName, 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)); } }