using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using YD_Event.Application.Event.Dtos; using YD_Event.Core.Entities; using YD_Event.Core.Enum; using SqlSugar; using Mapster; using YD_Event.Application.Auth; using Furion.DynamicApiController; using Microsoft.AspNetCore.Mvc; using YD_Event.Application.File; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authorization; using System.ComponentModel; using Furion.FriendlyException; using Furion.DatabaseAccessor; using Furion.DependencyInjection; namespace YD_Event.Application.Event; /// /// 用户端报名管理服务 /// public class UserEventRegistrationService : IDynamicApiController, ITransient { private readonly ISqlSugarRepository _eventRepository; private readonly ISqlSugarRepository _unitRepository; private readonly ISqlSugarRepository _eventProjectRepository; private readonly ISqlSugarRepository _projectRepository; private readonly ISqlSugarRepository _registrationRepository; private readonly ISqlSugarRepository _registrationItemRepository; private readonly ISqlSugarRepository _registrationPersonRepository; private readonly ISqlSugarRepository _userRepository; private readonly ISqlSugarRepository _feeConfigRepository; private readonly ISqlSugarRepository _limitRepository; private readonly ISqlSugarRepository _pointsRuleRepository; private readonly AuthManager _authManager; private readonly IIdGenerator _idGenerator; public UserEventRegistrationService( ISqlSugarRepository eventRepository, ISqlSugarRepository unitRepository, ISqlSugarRepository eventProjectRepository, ISqlSugarRepository projectRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository registrationItemRepository, ISqlSugarRepository registrationPersonRepository, ISqlSugarRepository userRepository, ISqlSugarRepository feeConfigRepository, ISqlSugarRepository limitRepository, ISqlSugarRepository pointsRuleRepository, AuthManager authManager, IIdGenerator idGenerator) { _eventRepository = eventRepository; _unitRepository = unitRepository; _eventProjectRepository = eventProjectRepository; _projectRepository = projectRepository; _registrationRepository = registrationRepository; _registrationItemRepository = registrationItemRepository; _registrationPersonRepository = registrationPersonRepository; _userRepository = userRepository; _feeConfigRepository = feeConfigRepository; _limitRepository = limitRepository; _pointsRuleRepository = pointsRuleRepository; _authManager = authManager; _idGenerator = idGenerator; } /// /// 1. 赛事列表展示接口 /// [DisplayName("赛事列表展示")] [HttpGet("list")] public async Task> Page([FromQuery] UserEventQueryInput input) { var query = _eventRepository.AsQueryable() .Where(x => x.Status == AvailabilityStatus.Enable) .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), x => x.EventName.Contains(input.Keyword)) .WhereIF(input.Status.HasValue && input.Status != 0, x => x.EventStatus == input.Status) .WhereIF(input.Type.HasValue, x => x.EventType == input.Type) .WhereIF(input.StartTime.HasValue, x => x.MatchStartTime >= input.StartTime) .WhereIF(input.EndTime.HasValue, x => x.MatchEndTime <= input.EndTime) .OrderByDescending(x => x.CreatedTime); return await query.Select(x => new UserEventListOutput { Id = x.Id, Name = x.EventName, CoverImage = x.CoverImage, Status = x.EventStatus.ToString(), Type = x.EventType.ToString(), RegistrationEndTime = x.RegistrationEndTime, MatchStartTime = x.MatchStartTime, MatchEndTime = x.MatchEndTime }).ToPagedListAsync(input); } /// /// 2. 赛事详情查看接口 /// [DisplayName("赛事详情查看")] [HttpGet("detail/{id}")] public async Task Detail([FromRoute] long id) { var eventInfo = await _eventRepository.GetByIdAsync(id); if (eventInfo == null) throw Oops.Bah("赛事不存在"); var output = eventInfo.Adapt(); output.Name = eventInfo.EventName; output.Description = eventInfo.EventIntroduction; output.Address = eventInfo.DetailAddress; output.EventVideo = eventInfo.EventVideo; output.RegistrationType = eventInfo.RegistrationType.ToString(); output.IsNeedMaterial = eventInfo.IsNeedMaterial; output.MaterialRemark = eventInfo.MaterialRemark; output.MaterialUploadEndTime = eventInfo.MaterialUploadEndTime; if (eventInfo.PointsRuleId.HasValue) { var rule = await _pointsRuleRepository.GetByIdAsync(eventInfo.PointsRuleId.Value); output.PointsRuleName = rule?.RuleName ?? ""; } var units = await _unitRepository.GetListAsync(x => x.EventId == id); output.Units = units.OrderBy(x => x.Sort).Select(x => x.Adapt()).ToList(); var limit = await _limitRepository.GetFirstAsync(x => x.EventId == id); if (limit != null) { output.LimitConfig = new UserEventLimitOutput { IndividualProjectLimit = limit.IndividualProjectLimit, TeamProjectLimit = limit.TeamProjectLimit }; } var fee = await _feeConfigRepository.GetFirstAsync(x => x.EventId == id); if (fee != null) { output.FeeConfig = new UserEventFeeOutput { ChargeMode = fee.ChargeMode.ToString(), Price = fee.Price }; } var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable); output.Projects = eventProjects.Select(ep => { var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); return new UserEventProjectListOutput { Id = ep.Id, Name = p?.Name ?? "", Fee = ep.Price / 100m, CompletionCount = p?.CompletionCount ?? 0 }; }).Where(x => !string.IsNullOrEmpty(x.Name)).ToList(); var userId = _authManager.UserId; var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == id && x.CreatedUserId == userId && !x.DeleteMark); if (registration != null) { output.RegistrationId = registration.Id; output.UserRegistrationStatus = GetRegistrationStatusDesc(registration.RegistrationStatus); } else { output.UserRegistrationStatus = "未报名"; } return output; } /// /// 3.1 选择报名项目接口 /// [DisplayName("选择报名项目")] [HttpGet("projects/{eventId}")] public async Task> GetProjects([FromRoute] long eventId) { var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable); return eventProjects.Select(ep => { var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); return new UserEventProjectListOutput { Id = ep.Id, Name = p?.Name ?? "", Fee = ep.Price / 100m, TeamSize = 1, // 默认为1 CompletionCount = p?.CompletionCount ?? 0 }; }).Where(x => !string.IsNullOrEmpty(x.Name)).ToList(); } /// /// 3.2 分配队员接口 - 搜索队员 /// [DisplayName("搜索队员")] [HttpGet("members/search")] public async Task> SearchMembers([FromQuery] string keyword) { if (string.IsNullOrWhiteSpace(keyword)) return new List(); var users = await _userRepository.AsQueryable() .Where(x => (x.Mobile.Contains(keyword) || x.Id.ToString() == keyword) && !x.DeleteMark) .Take(10) .ToListAsync(); return users.Select(u => new UserTeamMemberOutput { UserId = u.Id, Name = u.Name, Mobile = u.Mobile, IdCard = MaskIdCard(u.IdCardNumber), Gender = u.Gender.ToString(), IsEligible = true, IneligibilityReason = "" }).ToList(); } private string MaskIdCard(string idCard) { if (string.IsNullOrEmpty(idCard) || idCard.Length < 10) return idCard; return idCard.Substring(0, 6) + "********" + idCard.Substring(idCard.Length - 4); } /// /// 3.2 分配队员接口 - 提交报名(含保存草稿) /// [DisplayName("提交报名")] [HttpPost("submit")] [UnitOfWork] public async Task SubmitRegistration([FromBody] UserSubmitRegistrationInput input) { var userId = _authManager.UserId; var user = await _userRepository.GetByIdAsync(userId); if (user == null) throw Oops.Bah("用户不存在"); var eventInfo = await _eventRepository.GetByIdAsync(input.EventId); if (eventInfo == null) throw Oops.Bah("赛事不存在"); var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == input.EventId && x.CreatedUserId == userId && !x.DeleteMark); if (registration == null) { registration = new EventRegistration { Id = _idGenerator.NewLong(), EventId = input.EventId, CreatedUserId = userId, CreatedTime = DateTime.Now, Status = AvailabilityStatus.Enable, RegistrationStatus = input.IsDraft ? 0 : 1, // 0-待提交(草稿), 1-待审核 ContactName = user.Name, ContactPhone = user.Mobile, OrderNo = "", RegistrationMode = 1 }; await _registrationRepository.InsertAsync(registration); } else { registration.RegistrationStatus = input.IsDraft ? 0 : 1; registration.ContactName = user.Name; registration.ContactPhone = user.Mobile; registration.UpdatedTime = DateTime.Now; registration.UpdatedUserId = userId; await _registrationRepository.UpdateAsync(registration); } await _registrationItemRepository.DeleteAsync(x => x.RegistrationId == registration.Id); await _registrationPersonRepository.DeleteAsync(x => x.RegistrationId == registration.Id); var eventProjects = await _eventProjectRepository.GetListAsync(x => input.ProjectIds.Contains(x.Id)); var baseProjectIds = eventProjects.Select(x => x.ProjectId).ToList(); var baseProjects = await _projectRepository.GetListAsync(x => baseProjectIds.Contains(x.Id)); var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id)); decimal totalAmount = 0; int totalPersonCount = 0; foreach (var eventProject in eventProjects) { var baseProject = baseProjects.FirstOrDefault(x => x.Id == eventProject.ProjectId); var item = new EventRegistrationItem { Id = _idGenerator.NewLong(), RegistrationId = registration.Id, ProjectId = eventProject.Id, ProjectName = baseProject?.Name ?? "", UnitPrice = eventProject.Price / 100m, PersonCount = members.Count(), TotalPrice = (eventProject.Price / 100m) * members.Count(), CreatedUserId = userId, CreatedTime = DateTime.Now }; await _registrationItemRepository.InsertAsync(item); totalAmount += item.TotalPrice; totalPersonCount += item.PersonCount; foreach (var member in members) { var person = new EventRegistrationPerson { Id = _idGenerator.NewLong(), RegistrationId = registration.Id, RegistrationItemId = item.Id, ProjectId = eventProject.Id, Name = member.Name, Gender = member.Gender, IdCard = member.IdCardNumber, Phone = member.Mobile, Age = member.Age, BirthDate = member.BirthDate ?? DateTime.MinValue, PersonType = PersonType.Athlete, CreatedUserId = userId, CreatedTime = DateTime.Now }; await _registrationPersonRepository.InsertAsync(person); } } registration.TotalAmount = totalAmount; registration.TotalPersonCount = totalPersonCount; await _registrationRepository.UpdateAsync(registration); return registration.Id; } /// /// 3.3 预览支付接口 /// [DisplayName("预览支付")] [HttpGet("preview/{registrationId}")] public async Task PreviewOrder([FromRoute] long registrationId) { var registration = await _registrationRepository.GetByIdAsync(registrationId); if (registration == null) 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 projectOutputs = items.Select(i => new UserEventProjectListOutput { Id = i.ProjectId, Name = i.ProjectName, Fee = i.UnitPrice, RegisteredCount = i.PersonCount }).ToList(); var memberOutputs = persons.DistinctBy(p => p.IdCard).Select(p => new UserTeamMemberOutput { Name = p.Name, Mobile = p.Phone, IdCard = MaskIdCard(p.IdCard), Gender = p.Gender.ToString() }).ToList(); return new UserOrderPreviewOutput { EventId = eventInfo.Id, EventName = eventInfo.EventName, Projects = projectOutputs, Members = memberOutputs, TotalAmount = registration.TotalAmount, FeeDetails = new Dictionary { { "报名费", registration.TotalAmount } } }; } /// /// 创建支付订单 /// [DisplayName("创建支付订单")] [HttpPost("order/create")] [UnitOfWork] public async Task CreateOrder([FromBody] UserCreateOrderInput input) { var registration = await _registrationRepository.GetByIdAsync(input.RegistrationId); if (registration == null) throw Oops.Bah("报名信息不存在"); var orderNo = _idGenerator.NewLong().ToString(); registration.OrderNo = orderNo; registration.PaymentMethod = input.PaymentMethod; registration.PaymentStatus = 0; // 未支付 await _registrationRepository.UpdateAsync(registration); return new UserPaymentResultOutput { OrderNo = orderNo, PayUrl = "http://mock-pay-url.com/" + orderNo, Status = 0 }; } private string GetRegistrationStatusDesc(int status) { return status switch { 0 => "待提交", 1 => "审核中", 2 => "已报名", 3 => "审核拒绝", 4 => "已取消", _ => "未知" }; } }