using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using YD_Event.Application.Event.Dtos; using YD_Event.Application.Common; using YD_Event.Core.Entities; using YD_Event.Core.Enum; using YD_Event.Core.Extensions; using Easy.Core; using SqlSugar; using Mapster; using YD_Event.Application.Auth; using Furion.DynamicApiController; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authorization; using System.ComponentModel; using Furion.FriendlyException; using Furion.DatabaseAccessor; using Furion.DependencyInjection; using YD_Event.Core.Infrastructure.WeChat; using YD_Event.Core.Domain; using Microsoft.Extensions.Logging; namespace YD_Event.Application.Event; /// /// 用户端参赛记录管理 /// public class UserEventRecordService : IDynamicApiController, ITransient { private readonly ISqlSugarRepository _registrationRepository; private readonly ISqlSugarRepository _registrationPersonRepository; private readonly ISqlSugarRepository _registrationItemRepository; private readonly ISqlSugarRepository _projectRepository; private readonly ISqlSugarRepository _userRepository; private readonly ISqlSugarRepository _certificateRepository; private readonly ISqlSugarRepository _eventResultRepository; private readonly ISqlSugarRepository _orderRepository; private readonly ISqlSugarRepository _eventRepository; private readonly ISqlSugarRepository _sysUserRepository; private readonly ISqlSugarRepository _eventProjectRepository; private readonly ISqlSugarRepository _eventProjectGroupRepository; private readonly ISqlSugarRepository _merchantCertificationRepository; private readonly ISqlSugarClient _db; private readonly AuthManager _authManager; private readonly IIdGenerator _idGenerator; private readonly FileService _fileService; private readonly ILogger _logger; private readonly UnifiedRegistrationService _unifiedRegistrationService; public UserEventRecordService( ISqlSugarRepository registrationRepository, ISqlSugarRepository registrationPersonRepository, ISqlSugarRepository registrationItemRepository, ISqlSugarRepository projectRepository, ISqlSugarRepository userRepository, ISqlSugarRepository certificateRepository, ISqlSugarRepository eventResultRepository, ISqlSugarRepository orderRepository, ISqlSugarRepository eventRepository, ISqlSugarRepository sysUserRepository, ISqlSugarRepository eventProjectRepository, ISqlSugarRepository eventProjectGroupRepository, ISqlSugarRepository merchantCertificationRepository, ISqlSugarClient db, AuthManager authManager, IIdGenerator idGenerator, FileService fileService, ILogger logger, UnifiedRegistrationService unifiedRegistrationService) { _registrationRepository = registrationRepository; _registrationPersonRepository = registrationPersonRepository; _registrationItemRepository = registrationItemRepository; _projectRepository = projectRepository; _userRepository = userRepository; _certificateRepository = certificateRepository; _eventResultRepository = eventResultRepository; _orderRepository = orderRepository; _eventRepository = eventRepository; _sysUserRepository = sysUserRepository; _eventProjectRepository = eventProjectRepository; _eventProjectGroupRepository = eventProjectGroupRepository; _merchantCertificationRepository = merchantCertificationRepository; _db = db; _authManager = authManager; _idGenerator = idGenerator; _fileService = fileService; _logger = logger; _unifiedRegistrationService = unifiedRegistrationService; } /// /// 1. 列表查询 /// [DisplayName("参赛记录列表查询")] [HttpGet("list")] public async Task> GetRegistrationList([FromQuery] RegistrationListQueryInput input) { var userId = _authManager.UserId; var list = await _registrationRepository.AsQueryable() .InnerJoin((r, e) => r.EventId == e.Id) .LeftJoin((r, e, fc) => e.Id == fc.EventId) .Where((r, e, fc) => r.CreatedUserId == userId && !r.DeleteMark) // 支付状态筛选: // - 免费赛事:直接显示 // - 线下支付时:显示未支付(0)和已支付(1)状态的数据 // - 线上支付时:仅显示已支付(1)状态的数据 // - 排除预报名记录(5),仅在支付成功后才可见 .Where((r, e, fc) => r.PaymentStatus != 3 && // 排除已退款的报名 r.PaymentStatus != 4 && // 排除已取消的报名 r.PaymentStatus != 5 && // 排除预报名记录 ((e.ChargeMode == ChargeMode.Free) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)) || (r.PaymentMethod == 2 && r.PaymentStatus == 1))) .WhereIF(input.EventId.HasValue, (r, e, fc) => r.EventId == input.EventId) .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (r, e, fc) => r.TeamName.Contains(input.Keyword) || e.EventName.Contains(input.Keyword)) .OrderByDescending((r, e, fc) => r.CreatedTime) .Select((r, e, fc) => new { r.Id, r.EventId, e.EventName, r.TeamName, r.PaymentStatus, r.MaterialUrl, r.PaymentProofUrl, r.CreatedTime, r.UpdatedTime, r.TotalPersonCount, e.MaterialUploadEndTime, e.CoverImage, e.EventType, e.EventStatus, e.RegistrationStartTime, e.RegistrationEndTime, e.MatchStartTime, e.MatchEndTime, fc.ChargeMode, r.PaymentMethod }) .ToPagedListAsync(input); var resultItems = list.Rows.Select(x => { return new RegistrationItemDTO { RegistrationId = x.Id, EventName = x.EventName, CoverImage = x.CoverImage, EventType = (int?)x.EventType, ChargeMode = (int?)x.ChargeMode, EventStatus = (int?)x.EventStatus, RegistrationStartTime = x.RegistrationStartTime, RegistrationEndTime = x.RegistrationEndTime, MaterialDeadline = x.MaterialUploadEndTime, MatchStartTime = x.MatchStartTime, MatchEndTime = x.MatchEndTime, TeamName = x.TeamName, CreateTime = x.CreatedTime, UpdateTime = x.UpdatedTime, PayStatus = x.PaymentStatus, PaymentMethod = x.PaymentMethod, CertStatus = 0, // 未颁发 HasMaterial = !string.IsNullOrEmpty(x.MaterialUrl), HasVoucher = !string.IsNullOrEmpty(x.PaymentProofUrl), MemberCount = x.TotalPersonCount, MaxMember = 10, ExpireTime = x.MaterialUploadEndTime }; }).ToList(); return new PageResult { PageNo = list.PageNo, PageSize = list.PageSize, Total = list.Total, Pages = list.Pages, Rows = resultItems }; } /// /// 2. 取消报名 /// [DisplayName("取消报名")] [HttpPost("cancel/{registrationId}")] [UnitOfWork] public async Task> CancelRegistration([FromRoute] long registrationId) { var userId = _authManager.UserId; var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId); if (registration == null) throw Oops.Bah("报名记录不存在"); if (registration.PaymentStatus == 4) throw Oops.Bah("订单已取消"); // 校验状态:线下支付(1)仅当状态为未支付(0)或已支付(1)时允许取消,线上支付只有未支付状态可直接取消,已支付需走退款流程 if (registration.PaymentMethod == 1 && registration.PaymentStatus != 0 && registration.PaymentStatus != 1) { throw Oops.Bah("线下支付订单仅当状态为未支付或已支付时允许取消,当前状态不允许取消"); } if (registration.PaymentMethod != 1 && registration.PaymentStatus == 1) { throw Oops.Bah("已支付订单请联系客服退款"); } _logger.LogInformation("开始取消报名:报名ID={RegistrationId}, 操作人={UserId}, 当前状态={PaymentStatus}, 支付方式={PaymentMethod}", registrationId, userId, registration.PaymentStatus, registration.PaymentMethod); // 更新报名状态 registration.PaymentStatus = 4; // 已取消 registration.UpdatedUserId = userId; registration.UpdatedTime = DateTime.Now; await _registrationRepository.UpdateAsync(registration); // 同步更新订单状态 if (!string.IsNullOrEmpty(registration.OrderNo)) { var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == registration.OrderNo); if (order != null) { order.OrderStatus = OrderStatus.Cancelled; order.UpdatedTime = DateTime.Now; order.UpdatedUserId = userId; await _orderRepository.UpdateAsync(order); _logger.LogInformation("订单状态已同步更新:订单号={OrderNo}, 状态={OrderStatus}", registration.OrderNo, OrderStatus.Cancelled); } else { _logger.LogWarning("未找到对应的订单记录:报名ID={RegistrationId}, 订单号={OrderNo}", registrationId, registration.OrderNo); } } _logger.LogInformation("取消报名成功:报名ID={RegistrationId}", registrationId); return new ResponseDTO { Code = 200, Success = true, Data = true, Message = "取消成功" }; } /// /// 3. 上传素材 /// [DisplayName("上传素材")] [HttpPost("material/{registrationId}")] [UnitOfWork] public async Task>> UploadMaterial([FromRoute] long registrationId, [FromForm] UploadMaterialInput input) { var userId = _authManager.UserId; var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId); if (registration == null) throw Oops.Bah("报名记录不存在"); // 仅 status = 待上传素材 或 已上传素材 允许再次上传 // 即 PayStatus == 1 (已支付) if (registration.PaymentStatus != 1) throw Oops.Bah("未支付或状态不正确,无法上传素材"); // 总素材大小累计不超过 100 MB (简化校验,假设单次) long totalSize = input.Files.Sum(f => f.Length); if (totalSize > 100 * 1024 * 1024) throw Oops.Bah("素材总大小超过100MB"); var result = new List(); foreach (var file in input.Files) { var uploadResult = await _fileService.Upload(file); var item = uploadResult.FirstOrDefault(); if (item != null) { result.Add(new MaterialDTO { MaterialId = _idGenerator.NewLong().ToString(), OssUrl = item.Url, FileName = item.Name, FileSize = file.Length, UploadTime = DateTime.Now }); } } if (result.Any()) { registration.MaterialUrl = result.First().OssUrl; // 更新主表 await _registrationRepository.UpdateAsync(registration); } return new ResponseDTO> { Code = 200, Success = true, Data = result, Message = "上传成功" }; } /// /// 4. 上传付款凭证 /// [DisplayName("上传付款凭证")] [HttpPost("voucher/{registrationId}")] [UnitOfWork] public async Task> UploadVoucher([FromRoute] long registrationId, [FromForm] UploadVoucherInput input) { var userId = _authManager.UserId; var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId); if (registration == null) throw Oops.Bah("报名记录不存在"); // 允许的文件类型 var allowedFileTypes = new[] { "image/jpeg", "image/jpg", "image/png", "application/pdf" }; // 允许的文件扩展名 var allowedFileExtensions = new[] { ".jpg", ".jpeg", ".png", ".pdf" }; // 最大文件大小 5MB const long maxFileSize = 5 * 1024 * 1024; // 验证文件类型 var fileName = input.File.FileName.ToLower(); var hasValidExtension = allowedFileExtensions.Any(ext => fileName.EndsWith(ext)); var hasValidType = allowedFileTypes.Contains(input.File.ContentType); if (!hasValidExtension && !hasValidType) { throw Oops.Bah("仅支持jpg、jpeg、png、pdf格式的文件"); } // 验证文件大小 if (input.File.Length > maxFileSize) { throw Oops.Bah("文件大小不能超过5MB"); } // 验证支付金额 if (input.PayAmount <= 0) { throw Oops.Bah("支付金额必须大于0"); } var uploadResult = await _fileService.Upload(input.File); var fileInfo = uploadResult.First(); registration.PaymentProofUrl = fileInfo.Url; registration.UpdatedTime = DateTime.Now; await _registrationRepository.UpdateAsync(registration); return new ResponseDTO { Code = 200, Success = true, Message = "上传成功", Data = new VoucherDTO { VoucherId = _idGenerator.NewLong().ToString(), OssUrl = fileInfo.Url, AuditStatus = "待审核" } }; } /// /// 5.1 查看队员列表 /// [DisplayName("查看队员列表")] [HttpGet("members/{registrationId}")] public async Task> GetTeamMembers([FromRoute] long registrationId) { var userId = _authManager.UserId; var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId); if (registration == null) throw Oops.Bah("报名记录不存在"); var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId); // 需要关联 AppUser 获取更多信息 (Email 等) // 这里简化处理 return persons.Select(p => new TeamMemberDTO { UserId = 0, // 需关联 AppUser RealName = p.Name, Phone = p.Phone, Email = "", // 暂无 Role = p.PersonType == PersonType.Leader ? "队长" : "队员", // 假设 InviteStatus = "已加入", JoinTime = p.CreatedTime }).ToList(); } /// /// 5.2 更换队员 /// [DisplayName("更换队员")] [HttpPost("members/replace/{registrationId}/{userId}")] [UnitOfWork] public async Task> ReplaceMember([FromRoute] long registrationId, [FromRoute] long userId, [FromBody] ReplaceMemberInput input) { var currentUserId = _authManager.UserId; // 校验队长权限: 仅创建者(队长)可操作 var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == currentUserId); if (registration == null) throw Oops.Bah("无权操作或记录不存在"); var person = await _registrationPersonRepository.GetFirstAsync(x => x.RegistrationId == registrationId && x.Id == userId); if (person == null) throw Oops.Bah("队员不存在"); // 被替换人 role ≠ 队长 if (person.PersonType == PersonType.Leader) throw Oops.Bah("不能替换队长"); // 检查新队员 var newUser = await _userRepository.GetByIdAsync(input.NewUserId); if (newUser == null) throw Oops.Bah("新队员不存在"); long oldUserId = person.Id; // 更新信息 person.Name = newUser.Name; person.IdCard = newUser.IdCardNumber; person.Phone = newUser.Mobile; person.UpdatedTime = DateTime.Now; await _registrationPersonRepository.UpdateAsync(person); return new ResponseDTO { Code = 200, Success = true, Data = true, Message = "更换成功" }; } /// /// 6. 查看证书 /// [DisplayName("查看证书")] [HttpGet("certificate/{registrationId}")] public async Task> GetCertificate([FromRoute] long registrationId) { var userId = _authManager.UserId; // 2-已发放 var cert = await _certificateRepository.GetFirstAsync(x => x.RegistrationId == registrationId && x.CertificateStatus == 2); if (cert == null) return new ResponseDTO { Code = 404, Message = "证书未颁发", Success = false }; // 权限验证 bool hasPermission = await CheckCertificatePermissionAsync(userId, cert); if (!hasPermission) { return new ResponseDTO { Code = 403, Message = "无权查看该证书", Success = false }; } return new ResponseDTO { Code = 200, Success = true, Data = new CertificateDTO { CertUrl = cert.CertificateUrl, CertNumber = cert.CertificateNo, IssueTime = cert.IssuedTime ?? DateTime.MinValue, ExpireTime = null } }; } /// /// 检查证书查看权限 /// private async Task CheckCertificatePermissionAsync(long currentUserId, EventCertificate cert) { // 如果是自己的证书,直接允许 if (cert.UserId == currentUserId) { return true; } // 先尝试获取 appuser var appUser = await _userRepository.GetByIdAsync(currentUserId); // 如果 appuser 为空,尝试获取 sysuser var sysUser = appUser == null ? await _sysUserRepository.GetByIdAsync(currentUserId) : null; // 两个用户都不存在,返回 false if (appUser == null && sysUser == null) { return false; } long? merchantCertificationId = null; // 优先使用 appuser 的认证信息 if (appUser != null && appUser.MerchantCertificationId.HasValue) { merchantCertificationId = appUser.MerchantCertificationId.Value; } // 如果 appuser 没有认证,尝试通过 sysuser 获取认证信息 else if (sysUser != null) { // 查找该 sysuser 关联的单位认证 var merchantCert = await _merchantCertificationRepository.GetFirstAsync(m => m.UserId == currentUserId && !m.DeleteMark && m.AuditStatus == AuditStatus.Pass && m.Status == AvailabilityStatus.Enable); if (merchantCert != null) { merchantCertificationId = merchantCert.Id; } } // 如果没有单位认证,返回 false if (!merchantCertificationId.HasValue) { return false; } // 查询该单位认证下的团队成员 // 首先查询该单位认证创建的所有团队 var teamMemberUserIds = await _db.Queryable() .InnerJoin((t, tm) => t.Id == tm.TeamId) .Where((t, tm) => t.MerchantCertificationId == merchantCertificationId && !t.DeleteMark && !tm.DeleteMark) .Select((t, tm) => tm.UserId) .ToListAsync(); // 检查证书用户是否在团队成员列表中 if (teamMemberUserIds.Contains(cert.UserId)) { return true; } return false; } /// /// 6.1 获取所有证书列表 /// [DisplayName("获取所有证书列表")] [HttpGet("certificates/{registrationId}")] public async Task>> GetCertificates([FromRoute] long registrationId) { var userId = _authManager.UserId; // 2-已发放 var certs = await _certificateRepository.GetListAsync(x => x.RegistrationId == registrationId && x.CertificateStatus == 2); if (certs == null || certs.Count == 0) return new ResponseDTO> { Code = 200, Success = true, Data = new List() }; // 权限验证 - 筛选有权限查看的证书 var allowedCerts = new List(); foreach (var cert in certs) { bool hasPermission = await CheckCertificatePermissionAsync(userId, cert); if (hasPermission) { allowedCerts.Add(cert); } } // 获取报名项目信息 var registrationItems = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registrationId); // 获取所有相关项目信息 var projectIds = registrationItems.Select(x => x.ProjectId).Distinct().ToList(); var certProjectIds = allowedCerts.Where(x => x.ProjectId > 0).Select(x => x.ProjectId).Distinct().ToList(); var allProjectIds = projectIds.Concat(certProjectIds).Distinct().ToList(); var projects = await _projectRepository.GetListAsync(x => allProjectIds.Contains(x.Id)); var projectDict = projects.ToDictionary(x => x.Id, x => x); // 获取报名人员信息 var personIds = allowedCerts.Where(x => x.ParticipationRecordId > 0).Select(x => x.ParticipationRecordId).Distinct().ToList(); var persons = await _registrationPersonRepository.GetListAsync(x => personIds.Contains(x.Id)); var personDict = persons.ToDictionary(x => x.Id, x => x); // 获取成绩信息 var resultIds = allowedCerts.Where(x => x.ResultId > 0).Select(x => x.ResultId).Distinct().ToList(); var results = await _eventResultRepository.GetListAsync(x => resultIds.Contains(x.Id)); var resultDict = results.ToDictionary(x => x.Id, x => x); var result = allowedCerts.Select(cert => { // 查找对应的报名项目 - 只通过项目ID查找 var registrationItem = registrationItems.FirstOrDefault(x => cert.ProjectId > 0 && x.ProjectId == cert.ProjectId); var registrationItemId = registrationItem?.Id ?? 0; // 获取项目名称 string projectName = "未知项目"; if (registrationItem != null && projectDict.TryGetValue(registrationItem.ProjectId, out var p)) { projectName = p.Name; } var groupName = cert.GroupName ?? "未知组别"; // 获取人员角色 var person = personDict.TryGetValue(cert.ParticipationRecordId, out var rp) ? rp : null; var role = "运动员"; if (person != null) { if (person.PersonType == PersonType.Coach) { role = "教练"; } else if (person.PersonType == PersonType.Leader) { role = "领队"; } } // 获取成绩和名次 var resultEntity = resultDict.TryGetValue(cert.ResultId, out var r) ? r : null; return new CertificateListItemDTO { CertificateId = cert.Id, CertificateUrl = cert.CertificateUrl?.Trim() ?? string.Empty, CertificateNo = cert.CertificateNo, ParticipantName = cert.ParticipantName, ProjectName = projectName, GroupName = groupName, RegistrationItemId = registrationItemId, Role = role, CertificateType = cert.CertificateType, IssuedTime = cert.IssuedTime ?? DateTime.MinValue, Score = resultEntity?.Score, Rank = resultEntity?.Rank }; }).ToList(); return new ResponseDTO> { Code = 200, Success = true, Data = result }; } /// /// 7. 取消报名预览 /// [DisplayName("取消报名预览")] [HttpGet("cancel/preview/{registrationId}")] public async Task CancelPreview([FromRoute] long registrationId) { var userId = _authManager.UserId; _logger.LogInformation("调用统一取消报名预览:报名ID={RegistrationId}, 操作人={UserId}", registrationId, userId); return await _unifiedRegistrationService.GetCancelPreviewAsync(registrationId, userId); } /// /// 8. 执行取消报名 /// [DisplayName("执行取消报名")] [HttpPost("cancel/execute")] [UnitOfWork] public async Task CancelExecute([FromBody] CancelExecuteInput input) { var userId = _authManager.UserId; _logger.LogInformation("调用统一执行取消报名:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId); return await _unifiedRegistrationService.ExecuteCancelRegistrationAsync(input, userId); } /// /// 9. 计算退款金额 /// [DisplayName("计算退款金额")] [HttpPost("cancel/calculate-refund")] public async Task CalculateRefund([FromBody] CalculateRefundAmountInput input) { var userId = _authManager.UserId; _logger.LogInformation("调用计算退款金额:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId); return await _unifiedRegistrationService.CalculateRefundAmountAsync(input, userId); } /// /// 9. 获取参赛记录详情(包含项目和队员) /// [DisplayName("获取参赛记录详情(包含项目和队员)")] [HttpGet("detail-with-items/{registrationId}")] public async Task GetRegistrationDetailWithItems([FromRoute] long registrationId) { var userId = _authManager.UserId; var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId); if (registration == null) throw Oops.Bah("报名记录不存在"); // 预报名记录不可见 if (registration.PaymentStatus == 5) throw Oops.Bah("报名记录不存在"); // 获取赛事信息 var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId); // 获取报名项目 var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registrationId); // 获取所有队员 var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId); // 获取组别信息(年龄和性别限制) var groupIds = items.Where(x => x.EventProjectGroupId.HasValue).Select(x => x.EventProjectGroupId.Value).Distinct().ToList(); var groups = new Dictionary(); if (groupIds.Any()) { var groupList = await _db.Queryable() .Where(x => groupIds.Contains(x.Id)) .ToListAsync(); groups = groupList.ToDictionary(x => x.Id); } // 获取项目信息(从Project表查询项目名称和完成人数) var projectIds = items.Select(x => x.ProjectId).Distinct().ToList(); var projects = new Dictionary(); if (projectIds.Any()) { var projectList = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); projects = projectList.ToDictionary(x => x.Id); } var result = new RegistrationDetailWithItemsDto { RegistrationId = registration.Id, EventId = registration.EventId, EventName = eventInfo?.EventName ?? "", TeamName = registration.TeamName ?? "", RegistrationEndTime = eventInfo.RegistrationEndTime, EventStatus = (int)eventInfo.EventStatus }; foreach (var item in items) { var itemMembers = persons.Where(p => p.RegistrationItemId == item.Id).ToList(); // 获取组别信息 EventProjectGroup? group = null; if (item.EventProjectGroupId.HasValue && groups.TryGetValue(item.EventProjectGroupId.Value, out var foundGroup)) { group = foundGroup; } // 获取项目信息 YD_Event.Core.Entities.Project? project = null; if (projects.TryGetValue(item.ProjectId, out var foundProject)) { project = foundProject; } result.Items.Add(new RegistrationItemWithMembersDto { RegistrationItemId = item.Id, ProjectId = item.ProjectId, EventProjectGroupId = item.EventProjectGroupId ?? 0, // 优先从Project表获取项目名称,如果没有则使用报名时保存的名称 ProjectName = project?.Name ?? item.ProjectName ?? "", GroupName = item.GroupName ?? "", ProjectTypeName = item.ProjectTypeName ?? "", PersonCount = item.PersonCount, // 从Project表获取完成人数 ProjectCompletionCount = project?.CompletionCount ?? 0, BirthDateStart = group?.BirthDateStart, BirthDateEnd = group?.BirthDateEnd, GenderRequirement = group?.Gender, Members = itemMembers.Select(p => new RegistrationPersonDto { Id = p.Id, UserId = p.UserId, // 这里是关键!设置正确的用户ID Name = p.Name, IdCardNumber = p.IdCard, Gender = p.Gender, Mobile = p.Phone, BirthDate = p.BirthDate }).ToList() }); } return result; } /// /// 10. 更新参赛队员 /// [DisplayName("更新参赛队员")] [HttpPost("update-members")] [UnitOfWork] public async Task> UpdateRegistrationMembers([FromBody] UpdateRegistrationMembersInput input) { var userId = _authManager.UserId; var registration = await _registrationRepository.GetFirstAsync(x => x.Id == input.RegistrationId && x.CreatedUserId == userId); if (registration == null) throw Oops.Bah("报名记录不存在或无权操作"); _logger.LogInformation("开始更新参赛队员:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId); // 获取赛事信息 var eventInfo = await _eventRepository.GetFirstAsync(x => x.Id == registration.EventId); if (eventInfo == null) throw Oops.Bah("赛事信息不存在"); // 检查报名时间是否已结束 var now = DateTime.Now; if (now > eventInfo.RegistrationEndTime) { throw Oops.Bah("赛事报名时间已结束,无法更换参赛队员"); } // 检查赛事状态,只有报名中状态才允许更换队员 if (eventInfo.EventStatus != EventStatus.Enrolling) { throw Oops.Bah("当前赛事状态不允许更换参赛队员"); } // 获取报名项目信息 var registrationItems = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == input.RegistrationId); var eventProjectIds = registrationItems.Select(x => x.ProjectId).Distinct().ToList(); var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id)); var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => eventProjectIds.Contains(x.EventProjectId)); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); // 获取所有需要查询的用户ID var userIds = input.Items.SelectMany(x => x.Members).Where(x => x.UserId.HasValue).Select(x => x.UserId.Value).Distinct().ToList(); var users = new Dictionary(); if (userIds.Any()) { var userList = await _userRepository.GetListAsync(x => userIds.Contains(x.Id)); users = userList.ToDictionary(x => x.Id); } foreach (var item in input.Items) { // 验证项目是否属于该报名 var regItem = await _registrationItemRepository.GetFirstAsync(x => x.Id == item.RegistrationItemId && x.RegistrationId == input.RegistrationId); if (regItem == null) throw Oops.Bah($"项目记录不存在:{item.RegistrationItemId}"); // 删除旧的队员 await _registrationPersonRepository.DeleteAsync(x => x.RegistrationItemId == item.RegistrationItemId); // 添加新的队员 var newPersons = new List(); foreach (var member in item.Members) { // 如果有UserId,从用户表获取完整信息 YD_Event.Core.Entities.AppUser? user = null; if (member.UserId.HasValue && users.TryGetValue(member.UserId.Value, out var foundUser)) { user = foundUser; } var name = member.Name ?? user?.Name; var gender = member.Gender ?? user?.Gender ?? Gender.Male; var idCardNumber = member.IdCardNumber ?? user?.IdCardNumber; var birthDate = member.BirthDate ?? user?.BirthDate ?? DateTime.Now.AddYears(-20); var mobile = member.Mobile ?? user?.Mobile; // 验证必填字段 if (string.IsNullOrWhiteSpace(name)) throw Oops.Bah("队员姓名不能为空"); if (string.IsNullOrWhiteSpace(idCardNumber)) throw Oops.Bah("队员身份证号不能为空"); if (string.IsNullOrWhiteSpace(mobile)) throw Oops.Bah("队员手机号不能为空"); newPersons.Add(new EventRegistrationPerson { Id = _idGenerator.NewLong(), RegistrationId = input.RegistrationId, RegistrationItemId = item.RegistrationItemId, ProjectId = regItem.ProjectId, EventProjectGroupId = regItem.EventProjectGroupId, GroupName = regItem.GroupName, ProjectName = regItem.ProjectName, Name = name, Gender = gender, IdCard = idCardNumber, BirthDate = birthDate, Phone = mobile, Age = CalculateAge(birthDate), PersonType = PersonType.Athlete, UserId = member.UserId ?? 0, CreatedUserId = userId, CreatedTime = DateTime.Now }); } await _registrationPersonRepository.InsertRangeAsync(newPersons); // 更新项目的人数 regItem.PersonCount = item.Members.Count; regItem.UpdatedTime = DateTime.Now; regItem.UpdatedUserId = userId; await _registrationItemRepository.UpdateAsync(regItem); } // 更新报名总人数 var allPersonsCount = await _registrationPersonRepository.CountAsync(x => x.RegistrationId == input.RegistrationId); registration.TotalPersonCount = allPersonsCount; registration.UpdatedTime = DateTime.Now; registration.UpdatedUserId = userId; await _registrationRepository.UpdateAsync(registration); _logger.LogInformation("更新参赛队员成功:报名ID={RegistrationId}", input.RegistrationId); return new ResponseDTO { Code = 200, Success = true, Data = true, Message = "更新成功" }; } /// /// 计算年龄 /// private int CalculateAge(DateTime birthDate) { var today = DateTime.Today; var age = today.Year - birthDate.Year; if (birthDate.Date > today.AddYears(-age)) age--; return age; } /// /// 验证队员是否符合报名条件 /// [DisplayName("验证队员")] [HttpPost("member/validate")] public async Task ValidateMember([FromBody] MemberValidateInput input) { var result = new MemberValidateOutput { IsValid = true, AgeValid = true, GenderValid = true, RegistrationLimitValid = true, Message = "验证通过" }; // 添加调试:查看接收的ID var memberId = input.MemberId; var member = await _userRepository.GetByIdAsync(memberId); // 添加调试:如果找不到,尝试不删除标记查询 if (member == null) { var memberWithoutDelete = await _db.Queryable() .FirstAsync(m => m.Id == memberId); if (memberWithoutDelete != null) { // 队员存在但被标记删除 result.IsValid = false; result.Message = "队员不存在(已删除)"; return result; } result.IsValid = false; result.Message = "队员不存在"; return result; } if (!member.BirthDate.HasValue) { result.IsValid = false; result.AgeValid = false; result.Message = "队员缺少出生日期信息"; return result; } var eventProjectGroup = await _eventProjectGroupRepository.GetByIdAsync(input.GroupId); if (eventProjectGroup != null) { // 获取项目类型信息 var eventProject = await _eventProjectRepository.GetByIdAsync(eventProjectGroup.EventProjectId); var baseProject = eventProject != null ? await _projectRepository.GetByIdAsync(eventProject.ProjectId) : null; var isTeamProject = baseProject?.IsTeamProject() ?? false; // 创建单个队员的验证输入 var persons = new List { new UnifiedPersonInput { UserId = member.Id, Name = member.Name ?? "", IdCardNumber = member.IdCardNumber ?? "", Gender = member.Gender, Mobile = member.Mobile, BirthDate = member.BirthDate.Value } }; // 使用统一的验证方法进行验证 var validationResult = _unifiedRegistrationService.ValidatePersonQualifications(persons, eventProjectGroup, isTeamProject); if (!validationResult.IsValid) { result.IsValid = false; result.Message = validationResult.ErrorMessage ?? "验证失败"; // 根据验证详情设置具体的验证状态 var ageDetail = validationResult.Details?.FirstOrDefault(d => d.ItemName == "年龄验证" || d.ItemName == "年龄跨组验证"); var genderDetail = validationResult.Details?.FirstOrDefault(d => d.ItemName == "性别验证"); if (ageDetail != null && !ageDetail.IsPassed) { result.AgeValid = false; } if (genderDetail != null && !genderDetail.IsPassed) { result.GenderValid = false; } } } return result; } /// /// 检查报名限制 /// [DisplayName("检查报名限制")] [HttpPost("registration-limit/check")] public async Task CheckRegistrationLimit([FromBody] RegistrationLimitCheckInput input) { var userId = _authManager.UserId; // 获取当前用户信息 var sysUser = await _sysUserRepository.GetByIdAsync(userId); if (sysUser == null) throw Oops.Bah("用户不存在"); string userName = sysUser.Name; string userMobile = sysUser.Mobile ?? ""; // 获取团队信息 - 优先使用新结构的 TeamId string teamName = "临时团队"; if (input.TeamId.HasValue) { var team = await _db.Queryable().FirstAsync(t => t.Id == input.TeamId.Value && !t.DeleteMark); if (team != null) { teamName = team.Name; } } // 获取所有成员信息 var allMemberIds = new List(); if (input.GroupMembers != null) { foreach (var gm in input.GroupMembers) { // 检查是否有多团队数据 if (gm.Teams != null && gm.Teams.Count > 0) { foreach (var team in gm.Teams) { allMemberIds.AddRange(team.MemberIds ?? new List()); } } else { // 向后兼容单团队模式 allMemberIds.AddRange(gm.MemberIds ?? new List()); } } } else if (input.MemberIds != null) { // 向后兼容旧结构 allMemberIds.AddRange(input.MemberIds); } allMemberIds = allMemberIds.Distinct().ToList(); // 获取成员信息 var members = await _userRepository.GetListAsync(x => allMemberIds.Contains(x.Id)); // 获取组别信息 var groupIds = input.GroupMembers?.Select(g => g.GroupId).ToList() ?? input.GroupIds ?? new List(); var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id)); // 获取项目信息 var eventProjectIds = eventProjectGroups.Select(epg => epg.EventProjectId).Distinct().ToList(); var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id)); // 构建统一报名输入 var unifiedInput = new UnifiedRegistrationInput { EventId = input.EventId, RegistrationMode = 2, // PC端团队报名 TeamName = teamName, ContactName = userName, ContactPhone = userMobile, CreatedUserId = userId, IsDraft = false, Items = new List() }; // 为每个组别添加项目项 foreach (var groupMember in input.GroupMembers ?? new List()) { var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == groupMember.GroupId); if (eventProjectGroup == null) continue; var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId); if (eventProject == null) continue; var item = new UnifiedRegistrationItemInput { ProjectId = eventProject.Id, GroupId = groupMember.GroupId, GroupName = eventProjectGroup.GroupName, Teams = new List(), Persons = new List() }; // 检查是多团队模式还是单团队模式 var hasMultipleTeams = groupMember.Teams != null && groupMember.Teams.Count > 0; if (hasMultipleTeams) { // ========== 多团队模式 ========== foreach (var team in groupMember.Teams) { var teamMembers = members.Where(m => team.MemberIds != null && team.MemberIds.Contains(m.Id)).ToList(); item.Teams.Add(new UnifiedTeamInput { TeamIndex = team.TeamIndex, Persons = teamMembers.Select(m => new UnifiedPersonInput { UserId = m.Id, Name = m.Name ?? "", IdCardNumber = m.IdCardNumber ?? "", Gender = m.Gender, Mobile = m.Mobile, BirthDate = m.BirthDate ?? DateTime.MinValue }).ToList() }); } } else { // ========== 单团队模式(向后兼容) ========== var groupMembersList = members.Where(m => groupMember.MemberIds != null && groupMember.MemberIds.Contains(m.Id)).ToList(); item.Persons = groupMembersList.Select(m => new UnifiedPersonInput { UserId = m.Id, Name = m.Name ?? "", IdCardNumber = m.IdCardNumber ?? "", Gender = m.Gender, Mobile = m.Mobile, BirthDate = m.BirthDate ?? DateTime.MinValue }).ToList(); } unifiedInput.Items.Add(item); } // 调用统一的限制检查方法 return await _unifiedRegistrationService.CheckRegistrationLimitAsync(unifiedInput, input.ExcludeRegistrationId); } }