using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using YD_Event.Application.Event.Dtos; using YD_Event.Application.Project; using YD_Event.Core.Entities; using YD_Event.Core.Enum; using YD_Event.Core.Domain.Pricing; using YD_Event.Core.Domain.Pricing.Models; using Easy.Core; using SqlSugar; using Mapster; using YD_Event.Application.Auth; using Furion.DynamicApiController; using Microsoft.AspNetCore.Mvc; using YD_Event.Application.Common; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authorization; using System.ComponentModel; using Furion.FriendlyException; using Furion.DatabaseAccessor; using Furion.DependencyInjection; using Microsoft.Extensions.Options; using YD_Event.Core.Infrastructure.WeChat; using YD_Event.Core.Infrastructure.WeChat.Dto; using System.IO; using Microsoft.Extensions.Logging; 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 _eventProjectGroupRepository; private readonly ISqlSugarRepository _projectGroupRepository; private readonly ISqlSugarRepository _projectLimitRepository; private readonly ISqlSugarRepository _projectRepository; private readonly ISqlSugarRepository _projectTypeRepository; private readonly ISqlSugarRepository _registrationRepository; private readonly ISqlSugarRepository _registrationItemRepository; private readonly ISqlSugarRepository _registrationPersonRepository; private readonly ISqlSugarRepository _userRepository; private readonly ISqlSugarRepository _sysUserRepository; private readonly ISqlSugarRepository _feeConfigRepository; private readonly ISqlSugarRepository _limitRepository; private readonly ISqlSugarRepository _pointsRuleRepository; private readonly ISqlSugarRepository _orderRepository; private readonly ISqlSugarRepository _weChatPayConfigRepository; private readonly ISqlSugarRepository _merchantRepository; private readonly ISqlSugarClient _db; private readonly AuthManager _authManager; private readonly IIdGenerator _idGenerator; private readonly WeChatPayService _weChatPayService; private readonly IOptionsMonitor _wechatTenpayClientOptionsMonitor; private readonly IHttpContextAccessor _httpContextAccessor; private readonly ProjectService _projectService; private readonly ISqlSugarRepository _couponRepository; private readonly ISqlSugarRepository _userCouponRepository; private readonly ISqlSugarRepository _paymentRepository; private readonly ILogger _logger; public UserEventRegistrationService( ISqlSugarRepository eventRepository, ISqlSugarRepository unitRepository, ISqlSugarRepository eventProjectRepository, ISqlSugarRepository eventProjectGroupRepository, ISqlSugarRepository projectGroupRepository, ISqlSugarRepository projectLimitRepository, ISqlSugarRepository projectRepository, ISqlSugarRepository projectTypeRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository registrationItemRepository, ISqlSugarRepository registrationPersonRepository, ISqlSugarRepository userRepository, ISqlSugarRepository sysUserRepository, ISqlSugarRepository feeConfigRepository, ISqlSugarRepository limitRepository, ISqlSugarRepository pointsRuleRepository, ISqlSugarRepository orderRepository, ISqlSugarRepository weChatPayConfigRepository, ISqlSugarRepository merchantRepository, ISqlSugarClient db, AuthManager authManager, IIdGenerator idGenerator, WeChatPayService weChatPayService, IOptionsMonitor wechatTenpayClientOptionsMonitor, IHttpContextAccessor httpContextAccessor, ProjectService projectService, ISqlSugarRepository couponRepository, ISqlSugarRepository userCouponRepository, ISqlSugarRepository paymentRepository, ILogger logger) { _eventRepository = eventRepository; _unitRepository = unitRepository; _eventProjectRepository = eventProjectRepository; _eventProjectGroupRepository = eventProjectGroupRepository; _projectGroupRepository = projectGroupRepository; _projectLimitRepository = projectLimitRepository; _projectRepository = projectRepository; _projectTypeRepository = projectTypeRepository; _registrationRepository = registrationRepository; _registrationItemRepository = registrationItemRepository; _registrationPersonRepository = registrationPersonRepository; _userRepository = userRepository; _sysUserRepository = sysUserRepository; _feeConfigRepository = feeConfigRepository; _limitRepository = limitRepository; _pointsRuleRepository = pointsRuleRepository; _orderRepository = orderRepository; _weChatPayConfigRepository = weChatPayConfigRepository; _merchantRepository = merchantRepository; _db = db; _authManager = authManager; _idGenerator = idGenerator; _weChatPayService = weChatPayService; _wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor; _httpContextAccessor = httpContextAccessor; _projectService = projectService; _couponRepository = couponRepository; _userCouponRepository = userCouponRepository; _paymentRepository = paymentRepository; _logger = logger; } /// /// 根据出生日期计算年龄 /// /// 出生日期 /// 年龄 private static int CalculateAge(DateTime? birthDate) { if (!birthDate.HasValue) return 0; var today = DateTime.Today; var age = today.Year - birthDate.Value.Year; if (birthDate.Value.Date > today.AddYears(-age)) age--; return age; } /// /// 1. 赛事列表展示接口 /// [DisplayName("赛事列表展示")] [HttpGet("list")] public async Task> Page([FromQuery] UserEventQueryInput input) { var pageResult = await _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) .LeftJoin((e, fc) => e.Id == fc.EventId) .OrderByDescending((e, fc) => e.CreatedTime) .Select((e, fc) => new UserEventListOutput { Id = e.Id, Name = e.EventName, CoverImage = e.CoverImage, Status = e.EventStatus.ToString(), Type = e.EventType.ToString(), RegistrationType = e.RegistrationType.ToString(), ChargeMode = fc.ChargeMode.ToString(), RegistrationStartTime = e.RegistrationStartTime, RegistrationEndTime = e.RegistrationEndTime, MatchStartTime = e.MatchStartTime, MatchEndTime = e.MatchEndTime }).ToPagedListAsync(input); var userId = _authManager.UserId; var certification = await _db.Queryable() .FirstAsync(c => c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark); var canRegister = certification != null; foreach (var item in pageResult.Rows) { item.CanRegister = canRegister; } return pageResult; } /// /// 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 eventProjectIds = eventProjects.Select(x => x.Id).ToList(); var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => eventProjectIds.Contains(x.EventProjectId)); var projectLimits = await _projectLimitRepository.GetListAsync(x => eventProjectIds.Contains(x.ProjectId)); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable); var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); // 获取所有项目类型 var typeIds = baseProjects.Select(x => x.ProjectTypeId).Distinct().ToList(); var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id)); var allProjectTypes = await _projectTypeRepository.AsQueryable() .Where(t => !t.DeleteMark && t.Status == AvailabilityStatus.Enable) .ToListAsync(); output.Projects = eventProjects.Select(ep => { var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); var dto = new UserEventProjectListOutput { Id = ep.Id, Name = p?.Name ?? "", Fee = ep.Price, CompletionCount = p?.CompletionCount ?? 0 }; if (p != null) { var type = types.FirstOrDefault(x => x.Id == p.ProjectTypeId); if (type != null) { dto.ProjectTypeName = type.Name; // 为每个项目单独设置上级项目类型名称 if (type.ParentId > 0) { var parentProjectType = allProjectTypes.FirstOrDefault(t => t.Id == type.ParentId); dto.ParentProjectTypeName = parentProjectType?.Name; } } } var epGroups = eventProjectGroups.Where(g => g.EventProjectId == ep.Id).ToList(); dto.Groups = epGroups.Select(eg => { var baseGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eg.ProjectGroupId); return new UserEventProjectGroupOutput { Id = eg.Id, GroupName = eg.GroupName ?? baseGroup?.GroupName, BirthDateStart = eg.BirthDateStart != DateTime.MinValue ? eg.BirthDateStart : baseGroup?.BirthDateStart, BirthDateEnd = eg.BirthDateEnd != DateTime.MinValue ? eg.BirthDateEnd : baseGroup?.BirthDateEnd, Gender = (int)eg.Gender }; }).ToList(); var projLimit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.Id); if (projLimit != null) dto.LimitCount = projLimit.LimitCount; return dto; }).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 = GetPaymentStatusDesc(registration.PaymentStatus); } 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 eventProjectIds = eventProjects.Select(x => x.Id).ToList(); var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => eventProjectIds.Contains(x.EventProjectId)); var projectLimits = await _projectLimitRepository.GetListAsync(x => eventProjectIds.Contains(x.ProjectId)); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable); var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); var typeIds = baseProjects.Select(x => x.ProjectTypeId).Distinct().ToList(); var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id)); var allProjectTypes = await _projectTypeRepository.AsQueryable() .Where(t => !t.DeleteMark && t.Status == AvailabilityStatus.Enable) .ToListAsync(); return eventProjects.Select(ep => { var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); var dto = new UserEventProjectListOutput { Id = ep.Id, Name = p?.Name ?? "", Fee = ep.Price, TeamSize = 1, CompletionCount = p?.CompletionCount ?? 0 }; if (p != null) { var type = types.FirstOrDefault(x => x.Id == p.ProjectTypeId); if (type != null) { dto.ProjectTypeName = type.Name; // 为每个项目单独设置上级项目类型名称 if (type.ParentId > 0) { var parentProjectType = allProjectTypes.FirstOrDefault(t => t.Id == type.ParentId); dto.ParentProjectTypeName = parentProjectType?.Name; } } } var epGroups = eventProjectGroups.Where(g => g.EventProjectId == ep.Id).ToList(); dto.Groups = epGroups.Select(eg => { var baseGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eg.ProjectGroupId); return new UserEventProjectGroupOutput { Id = eg.Id, GroupName = eg.GroupName ?? baseGroup?.GroupName, BirthDateStart = eg.BirthDateStart != DateTime.MinValue ? eg.BirthDateStart : baseGroup?.BirthDateStart, BirthDateEnd = eg.BirthDateEnd != DateTime.MinValue ? eg.BirthDateEnd : baseGroup?.BirthDateEnd, Gender = (int)eg.Gender }; }).ToList(); var projLimit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.Id); if (projLimit != null) dto.LimitCount = projLimit.LimitCount; return dto; }).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, IdCardNumber = 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 sysUser = await _sysUserRepository.GetByIdAsync(userId); if (sysUser == null) throw Oops.Bah("用户不存在"); string userName = sysUser.Name; string userMobile = sysUser.Mobile ?? ""; var eventInfo = await _eventRepository.GetByIdAsync(input.EventId); if (eventInfo == null) throw Oops.Bah("赛事不存在"); // 验证2:一次报名只能选择单个团队内的运动员 if (input.MemberIds != null && input.MemberIds.Count > 0) { // 获取所有队员的团队关系 var teamMembers = await _db.Queryable() .Where(tm => input.MemberIds.Contains(tm.UserId)) .Where(tm => !tm.DeleteMark) .ToListAsync(); if (teamMembers == null || teamMembers.Count == 0) { throw Oops.Bah("未找到队员信息"); } // 按用户ID分组,获取每个用户所属的所有团队 var userTeamMap = teamMembers .GroupBy(tm => tm.UserId) .ToDictionary(g => g.Key, g => g.Select(tm => tm.TeamId).ToList()); // 检查是否所有用户都有团队信息 var missingUsers = input.MemberIds.Except(userTeamMap.Keys).ToList(); if (missingUsers.Any()) { throw Oops.Bah("部分队员未找到团队信息"); } // 验证所有队员是否都属于指定的 TeamId var allBelongToTeam = userTeamMap.All(ut => ut.Value.Contains(input.TeamId)); if (!allBelongToTeam) { throw Oops.Bah("所有选择的运动员必须隶属于同一个团队"); } } // 验证3:调用检查报名限制API,确保所有限制都满足 var checkInput = new RegistrationLimitCheckInput { EventId = input.EventId, MemberIds = input.MemberIds ?? new List(), GroupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List() }; var checkResult = await CheckRegistrationLimit(checkInput); if (checkResult.HasLimit) { var firstLimit = checkResult.LimitDetails.FirstOrDefault(); throw Oops.Bah(firstLimit?.LimitMessage ?? "报名限制检查失败"); } 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, PaymentStatus = input.IsDraft ? 0 : 0, // 0-未支付/待审核 ContactName = userName, ContactPhone = userMobile, OrderNo = "", RegistrationMode = 2 // PC端只做团队报名 }; await _registrationRepository.InsertAsync(registration); } else { registration.PaymentStatus = input.IsDraft ? 0 : 0; registration.ContactName = userName; registration.ContactPhone = userMobile; 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 members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id)); var groupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List(); var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id)); // 通过EventProjectGroup获取对应的EventProject var eventProjectIds = eventProjectGroups.Select(x => x.EventProjectId).Distinct().ToList(); var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id)); var baseProjectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var baseProjects = await _projectRepository.GetListAsync(x => baseProjectIds.Contains(x.Id)); // 获取项目类型信息 var projectTypeIds = baseProjects.Select(x => x.ProjectTypeId).Distinct().ToList(); var projectTypes = await _projectTypeRepository.GetListAsync(x => projectTypeIds.Contains(x.Id)); var baseProjectGroupIds = eventProjectGroups.Select(x => x.ProjectGroupId).Distinct().ToList(); var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => baseProjectGroupIds.Contains(x.Id)); // 获取赛事费用配置 var feeConfig = await _feeConfigRepository.GetFirstAsync(x => x.EventId == input.EventId && !x.DeleteMark); int totalPersonCount = 0; FeeCalculationResult? feeCalculationResult = null; if (input.GroupMembers != null && input.GroupMembers.Any()) { foreach (var groupMember in input.GroupMembers) { var eventProjectGroup = eventProjectGroups.FirstOrDefault(g => g.Id == groupMember.GroupId); if (eventProjectGroup == null) continue; var eventProject = eventProjects.FirstOrDefault(p => p.Id == eventProjectGroup.EventProjectId); if (eventProject == null) continue; var baseProject = baseProjects.FirstOrDefault(x => x.Id == eventProject.ProjectId); var baseProjectGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eventProjectGroup.ProjectGroupId); var projectType = projectTypes.FirstOrDefault(pt => pt.Id == baseProject?.ProjectTypeId); var groupMembers = members.Where(m => groupMember.MemberIds.Contains(m.Id)).ToList(); if (!groupMembers.Any()) continue; // 根据收费模式设置单价和总价 decimal unitPrice = 0; decimal totalPrice = 0; switch (feeConfig.ChargeMode) { case ChargeMode.Free: unitPrice = 0; totalPrice = 0; break; case ChargeMode.FixedPrice: // 定价(赛事)模式:使用赛事费用配置的单价 unitPrice = feeConfig.Price; totalPrice = unitPrice * groupMembers.Count(); break; case ChargeMode.UnitPriceEvent: // 单价(赛事)模式:使用赛事单价 unitPrice = feeConfig.Price; totalPrice = unitPrice * groupMembers.Count(); break; case ChargeMode.UnitPriceProject: // 单价(项目)模式:使用项目单价 unitPrice = eventProject.Price; totalPrice = unitPrice * groupMembers.Count(); break; } var item = new EventRegistrationItem { Id = _idGenerator.NewLong(), RegistrationId = registration.Id, ProjectId = eventProject.Id, EventProjectGroupId = eventProjectGroup.Id, ProjectName = baseProject?.Name ?? "", GroupName = baseProjectGroup?.GroupName ?? "", ProjectTypeName = projectType?.Name ?? "", UnitPrice = unitPrice, PersonCount = groupMembers.Count(), TotalPrice = totalPrice, CreatedUserId = userId, CreatedTime = DateTime.Now }; await _registrationItemRepository.InsertAsync(item); totalPersonCount += item.PersonCount; foreach (var member in groupMembers) { var person = new EventRegistrationPerson { Id = _idGenerator.NewLong(), RegistrationId = registration.Id, RegistrationItemId = item.Id, ProjectId = eventProject.Id, EventProjectGroupId = eventProjectGroup.Id, GroupName = baseProjectGroup?.GroupName ?? "", ProjectName = baseProject?.Name ?? "", Name = member.Name, Gender = member.Gender, IdCard = member.IdCardNumber, Phone = member.Mobile, Age = CalculateAge(member.BirthDate), BirthDate = member.BirthDate ?? DateTime.MinValue, PersonType = PersonType.Athlete, CreatedUserId = userId, CreatedTime = DateTime.Now }; await _registrationPersonRepository.InsertAsync(person); } } } else { foreach (var eventProject in eventProjects) { var baseProject = baseProjects.FirstOrDefault(x => x.Id == eventProject.ProjectId); var projectType = projectTypes.FirstOrDefault(pt => pt.Id == baseProject?.ProjectTypeId); // 根据收费模式设置单价和总价 decimal unitPrice = 0; decimal totalPrice = 0; if (feeConfig != null) { switch (feeConfig.ChargeMode) { case ChargeMode.Free: unitPrice = 0; totalPrice = 0; break; case ChargeMode.FixedPrice: // 定价(赛事)模式:使用赛事费用配置的单价 unitPrice = feeConfig.Price; totalPrice = unitPrice * members.Count(); break; case ChargeMode.UnitPriceEvent: // 单价(赛事)模式:使用赛事单价 unitPrice = feeConfig.Price; totalPrice = unitPrice * members.Count(); break; case ChargeMode.UnitPriceProject: // 单价(项目)模式:使用项目单价 unitPrice = eventProject.Price; totalPrice = unitPrice * members.Count(); break; } } var item = new EventRegistrationItem { Id = _idGenerator.NewLong(), RegistrationId = registration.Id, ProjectId = eventProject.Id, ProjectName = baseProject?.Name ?? "", ProjectTypeName = projectType?.Name ?? "", UnitPrice = unitPrice, PersonCount = members.Count(), TotalPrice = totalPrice, CreatedUserId = userId, CreatedTime = DateTime.Now }; await _registrationItemRepository.InsertAsync(item); totalPersonCount += item.PersonCount; foreach (var member in members) { var person = new EventRegistrationPerson { Id = _idGenerator.NewLong(), RegistrationId = registration.Id, RegistrationItemId = item.Id, ProjectId = eventProject.Id, GroupName = baseProject?.Name ?? "", ProjectName = baseProject?.Name ?? "", Name = member.Name, Gender = member.Gender, IdCard = member.IdCardNumber, Phone = member.Mobile, Age = CalculateAge(member.BirthDate), BirthDate = member.BirthDate ?? DateTime.MinValue, PersonType = PersonType.Athlete, CreatedUserId = userId, CreatedTime = DateTime.Now }; await _registrationPersonRepository.InsertAsync(person); } } } if (totalPersonCount > 0) { var feeInput = new FeeCalculationInput { EventId = input.EventId, EventProjectIds = eventProjectIds, ParticipantCount = totalPersonCount }; feeCalculationResult = await CalculateFeeAsync(feeInput); registration.TotalAmount = feeCalculationResult.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 => { // 获取该项目的队员 var itemMembers = persons .Where(p => p.RegistrationItemId == i.Id) .Select(p => new UserTeamMemberOutput { UserId = p.Id, Name = p.Name, Mobile = p.Phone, IdCardNumber = MaskIdCard(p.IdCard), Gender = p.Gender.ToString(), IsEligible = true, IneligibilityReason = "" }) .ToList(); return new UserEventProjectListOutput { Id = i.ProjectId, Name = i.ProjectName, GroupName = i.GroupName, ProjectTypeName = i.ProjectTypeName, Fee = i.UnitPrice, RegisteredCount = i.PersonCount, CompletionCount = i.PersonCount, Members = itemMembers }; }).ToList(); var memberOutputs = persons.DistinctBy(p => p.IdCard).Select(p => new UserTeamMemberOutput { UserId = p.Id, Name = p.Name, Mobile = p.Phone, IdCardNumber = MaskIdCard(p.IdCard), Gender = p.Gender.ToString(), IsEligible = true, IneligibilityReason = "" }).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 eventInfo = await _eventRepository.GetByIdAsync(registration.EventId); if (eventInfo == null) throw Oops.Bah("赛事信息不存在"); var result = new UserPaymentResultOutput(); // 计算最终支付金额 decimal originalAmount = registration.TotalAmount; decimal finalAmount = originalAmount; decimal discountAmount = 0; YD_Event.Core.Entities.Coupon? coupon = null; YD_Event.Core.Entities.UserCoupon? userCoupon = null; // 处理优惠券 if (input.CouponId.HasValue) { var userId = _authManager.UserId; coupon = await _couponRepository.GetFirstAsync(c => c.Id == input.CouponId.Value && c.Status == AvailabilityStatus.Enable && !c.DeleteMark); if (coupon != null) { userCoupon = await _userCouponRepository.GetFirstAsync(uc => uc.CouponId == coupon.Id && uc.UserId == userId && uc.Status == 0 && // 0-未使用 !uc.DeleteMark); if (userCoupon != null) { // 计算折扣金额 if (coupon.Type == 0) // 折扣 { discountAmount = originalAmount * (1 - coupon.Value / 100); } else if (coupon.Type == 1) // 定额 { discountAmount = Math.Min(coupon.Value, originalAmount); } finalAmount = originalAmount - discountAmount; finalAmount = Math.Max(0, finalAmount); } } } // 如果最终金额为0,直接完成报名,无需支付 if (finalAmount <= 0) { // 如果使用了优惠券,标记为已使用 if (userCoupon != null) { userCoupon.Status = 1; // 1-已使用 userCoupon.UseTime = DateTime.Now; await _userCouponRepository.UpdateAsync(userCoupon); } registration.PaymentStatus = 1; // 已支付 registration.PaymentTime = DateTime.Now; await _registrationRepository.UpdateAsync(registration); result.Status = 1; result.IsFree = true; return result; } // 获取当前用户的单位认证ID var certification = await _db.Queryable() .FirstAsync(c => c.UserId == registration.CreatedUserId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark); var orderNo = _idGenerator.NewLong().ToString(); registration.OrderNo = orderNo; registration.PaymentMethod = input.PaymentMethod; registration.PaymentStatus = 0; // 未支付 await _registrationRepository.UpdateAsync(registration); // 创建内部订单记录 var order = new YD_Event.Core.Entities.Order { Id = _idGenerator.NewLong(), OrderNo = orderNo, OrderType = 1, // Income MerchantId = eventInfo.MerchantId, MerchantCertificationId = certification?.Id, // 设置单位认证ID EventId = registration.EventId, RegistrationId = registration.Id, OriginalAmount = originalAmount, ActualAmount = finalAmount, DiscountAmount = discountAmount, CouponId = input.CouponId, OrderStatus = OrderStatus.Pending, CreatedTime = DateTime.Now, CreatedUserId = registration.CreatedUserId }; await _orderRepository.InsertAsync(order); result.OrderNo = orderNo; result.Status = 0; result.IsFree = false; // 如果是微信支付 (假设 1 是微信支付) if (input.PaymentMethod == 1) { WeChatPayConfig? payConfig = null; if (eventInfo.MerchantId.HasValue) { payConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.MerchantId == eventInfo.MerchantId.Value && x.Status == AvailabilityStatus.Enable && !x.DeleteMark); } if (payConfig == null) { throw Oops.Bah("商户微信支付配置不存在"); } var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue; var nativeRequest = new CreateNativePayRequest { OutTradeNo = orderNo, AppId = configOptions.AppId, Description = $"{eventInfo.EventName} - 报名费", NotifyUrl = configOptions.NotifyUrl, TotalAmount = AmountHelper.YuanToFen(finalAmount), // 单位为分,使用优惠后的金额 ExpireTime = DateTimeOffset.Now.AddMinutes(5) }; var payResult = await _weChatPayService.CreateNativePayAsync(payConfig, nativeRequest); if (payResult.Success) { result.PayUrl = payResult.QrcodeUrl ?? string.Empty; result.QrcodeUrl = payResult.QrcodeUrl; result.TradeType = payResult.TradeType; } else { throw Oops.Bah($"微信统一下单失败:{payResult.Message}"); } } else { result.PayUrl = "http://mock-pay-url.com/" + orderNo; result.TradeType = "MOCK"; } return result; } /// /// 微信支付回调 /// [DisplayName("微信支付回调")] [HttpPost("wechat/notify")] [AllowAnonymous] [UnitOfWork] [NonUnify] public async Task WechatPayNotify() { try { using var reader = new StreamReader(_httpContextAccessor.HttpContext.Request.Body); var body = await reader.ReadToEndAsync(); var headers = _httpContextAccessor.HttpContext.Request.Headers; var timestamp = headers["Wechatpay-Timestamp"].FirstOrDefault(); var nonce = headers["Wechatpay-Nonce"].FirstOrDefault(); var signature = headers["Wechatpay-Signature"].FirstOrDefault(); var serialNumber = headers["Wechatpay-Serial"].FirstOrDefault(); if (string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(serialNumber)) { _logger.LogWarning("微信支付回调通知参数不完整"); return new JsonResult(new { code = "FAIL", message = "参数不完整" }); } var platformPayConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.Status == AvailabilityStatus.Enable && !x.DeleteMark); if (platformPayConfig == null) { _logger.LogError("未找到平台级别的微信支付配置"); return new JsonResult(new { code = "FAIL", message = "支付配置未找到" }); } var notifyRequest = new WeChatPayNotifyRequest { Timestamp = timestamp, Nonce = nonce, Signature = signature, SerialNumber = serialNumber, Body = body }; var isValid = await _weChatPayService.VerifyPayNotifyAsync(platformPayConfig, notifyRequest); if (!isValid) { _logger.LogWarning("微信支付回调签名验证失败"); return new JsonResult(new { code = "FAIL", message = "签名验证失败" }); } var notifyData = await _weChatPayService.DecryptPayNotifyDataAsync(platformPayConfig, notifyRequest); if (notifyData == null || notifyData.TradeState != "SUCCESS") { _logger.LogWarning("微信支付回调数据无效或交易未成功"); return new JsonResult(new { code = "SUCCESS", message = "成功" }); } var reg = await _registrationRepository.GetFirstAsync(r => r.OrderNo == notifyData.OutTradeNo); if (reg == null) { _logger.LogWarning($"未找到订单:{notifyData.OutTradeNo}"); return new JsonResult(new { code = "SUCCESS", message = "成功" }); } if (reg.PaymentStatus == 1) { _logger.LogInformation($"订单 {reg.OrderNo} 已处理过支付回调"); return new JsonResult(new { code = "SUCCESS", message = "成功" }); } var evt = await _eventRepository.GetFirstAsync(e => e.Id == reg.EventId); if (evt == null) { _logger.LogError($"未找到赛事信息,订单号:{reg.OrderNo},赛事ID:{reg.EventId}"); return new JsonResult(new { code = "FAIL", message = "赛事信息不存在" }); } _logger.LogInformation("订单支付成功,订单号={OrderNo},赛事ID={EventId},商户ID={MerchantId}", reg.OrderNo, evt.Id, evt.MerchantId); reg.PaymentStatus = 1; reg.PaymentTime = notifyData.SuccessTime?.DateTime; reg.RegistrationRemark = notifyData.TransactionId; reg.UpdatedTime = DateTime.Now; await _registrationRepository.UpdateAsync(reg); _logger.LogInformation($"订单 {reg.OrderNo} 支付成功,微信交易号:{notifyData.TransactionId}"); var payment = new EventRegistrationPayment { Id = _idGenerator.NewLong(), RegistrationId = reg.Id, TransactionNo = notifyData.TransactionId ?? "", PaymentChannel = 1, PaymentAmount = reg.TotalAmount, PaymentTime = notifyData.SuccessTime?.DateTime ?? DateTime.Now, PaymentStatus = 1, RefundStatus = 0, PayeeAccount = "", CreatedUserId = reg.CreatedUserId, CreatedTime = DateTime.Now }; await _paymentRepository.InsertAsync(payment); _logger.LogInformation($"支付记录创建成功,报名ID={reg.Id},交易号={notifyData.TransactionId}"); var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == reg.OrderNo); if (order != null) { order.OrderStatus = OrderStatus.Paid; order.PaymentTime = notifyData.SuccessTime?.DateTime; order.UpdatedTime = DateTime.Now; await _orderRepository.UpdateAsync(order); _logger.LogInformation($"Order 记录 {order.OrderNo} 状态已更新为已支付"); } return new JsonResult(new { code = "SUCCESS", message = "成功" }); } catch (ArgumentException ex) { _logger.LogError(ex, "处理微信支付回调通知时参数验证失败"); return new JsonResult(new { code = "FAIL", message = "业务验证失败" }); } catch (Exception ex) { _logger.LogError(ex, "处理微信支付回调通知异常"); return new JsonResult(new { code = "FAIL", message = "处理失败" }); } } /// /// 查询订单支付状态 /// [DisplayName("查询订单支付状态")] [HttpGet("order/status/{orderNo}")] public async Task QueryOrderStatus([FromRoute] string orderNo) { var query = _orderRepository.AsQueryable() .LeftJoin((o, r) => o.RegistrationId == r.Id) .Where((o, r) => o.OrderNo == orderNo) .Select((o, r) => new { Order = o, Registration = r }); var orderInfo = await query.FirstAsync(); if (orderInfo == null || orderInfo.Order == null) throw Oops.Bah("订单不存在"); if (orderInfo.Order.OrderStatus == OrderStatus.Paid) return 1; // 已支付 if (orderInfo.Registration?.PaymentMethod == 1) { WeChatPayConfig? payConfig = null; if (orderInfo.Order.MerchantId.HasValue) { payConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.MerchantId == orderInfo.Order.MerchantId.Value && x.Status == AvailabilityStatus.Enable && !x.DeleteMark); } if (payConfig == null) { payConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.Status == AvailabilityStatus.Enable && !x.DeleteMark); } if (payConfig != null) { var queryResult = await _weChatPayService.QueryOrderAsync(payConfig, orderNo); if (queryResult.Success && queryResult.TradeState == "SUCCESS") { orderInfo.Order.OrderStatus = OrderStatus.Paid; orderInfo.Order.PaymentTime = queryResult.SuccessTime?.DateTime; if (queryResult.PayerAmount.HasValue) { orderInfo.Order.ActualAmount = queryResult.PayerAmount.Value / 100m; } await _orderRepository.UpdateAsync(orderInfo.Order); var registration = await _registrationRepository.GetByIdAsync(orderInfo.Order.RegistrationId); if (registration != null && registration.PaymentStatus != 1) { registration.PaymentStatus = 1; await _registrationRepository.UpdateAsync(registration); } return 1; } } } return 0; // 未支付 } /// /// 验证优惠券 /// [DisplayName("验证优惠券")] [HttpPost("coupon/validate")] public async Task ValidateCoupon([FromBody] ValidateCouponInput input) { var result = new ValidateCouponOutput { IsValid = false, InvalidReason = "" }; // 查找优惠券(通过兑换码) var coupon = await _couponRepository.GetFirstAsync(c => c.RedemptionCode == input.CouponCode && c.Status == AvailabilityStatus.Enable && !c.DeleteMark); if (coupon == null) { result.InvalidReason = "优惠券不存在或已失效"; return result; } // 检查优惠券是否已过期 if (coupon.EndTime.HasValue && coupon.EndTime.Value < DateTime.Now) { result.InvalidReason = "优惠券已过期"; return result; } // 检查优惠券是否已开始 if (coupon.StartTime.HasValue && coupon.StartTime.Value > DateTime.Now) { result.InvalidReason = "优惠券尚未生效"; return result; } // 检查库存 if (coupon.TotalQuantity > 0 && coupon.ClaimedQuantity >= coupon.TotalQuantity) { result.InvalidReason = "优惠券已被领完"; return result; } // 检查优惠券是否适用于该赛事 if (!string.IsNullOrEmpty(coupon.EventIds)) { var eventIds = coupon.EventIds.Split(',').Select(long.Parse).ToList(); if (!eventIds.Contains(input.EventId)) { result.InvalidReason = "该优惠券不适用于当前赛事"; return result; } } // 检查当前用户是否拥有该优惠券且未使用,或者可以直接领取 var userId = _authManager.UserId; var userCoupon = await _userCouponRepository.GetFirstAsync(uc => uc.CouponId == coupon.Id && uc.UserId == userId && uc.Status == 0 && // 0-未使用 !uc.DeleteMark); // 如果用户没有该优惠券,检查是否可以直接领取(假设可以直接通过兑换码领取) if (userCoupon == null) { // 创建用户优惠券记录 userCoupon = new YD_Event.Core.Entities.UserCoupon { Id = _idGenerator.NewLong(), UserId = userId, CouponId = coupon.Id, ClaimTime = DateTime.Now, Status = 0, // 未使用 CreatedUserId = userId, CreatedTime = DateTime.Now, DeleteMark = false }; await _userCouponRepository.InsertAsync(userCoupon); // 更新优惠券已领取数量 coupon.ClaimedQuantity += 1; await _couponRepository.UpdateAsync(coupon); } // 计算优惠金额 decimal discountAmount = 0; decimal finalAmount = input.OriginalAmount; // Type: 0-折扣, 1-定额 if (coupon.Type == 0) // 折扣 { discountAmount = input.OriginalAmount * (1 - coupon.Value / 100); } else if (coupon.Type == 1) // 定额 { discountAmount = Math.Min(coupon.Value, input.OriginalAmount); } finalAmount = input.OriginalAmount - discountAmount; finalAmount = Math.Max(0, finalAmount); // 确保不小于0 result.IsValid = true; result.CouponId = coupon.Id; result.CouponName = coupon.Name ?? "优惠券"; result.DiscountType = coupon.Type; result.DiscountValue = coupon.Value; result.DiscountAmount = discountAmount; result.FinalAmount = finalAmount; return result; } private string GetPaymentStatusDesc(int status) { return status switch { 0 => "待审核", 1 => "已支付", 2 => "支付失败", 3 => "已退款", 4 => "已取消", _ => "未知" }; } /// /// 验证队员是否符合报名条件 /// [DisplayName("验证队员")] [HttpPost("member/validate")] public async Task ValidateMember([FromBody] MemberValidateInput input) { var result = new MemberValidateOutput { IsValid = true, AgeValid = true, GenderValid = true, RegistrationLimitValid = true, Message = "验证通过" }; var member = await _userRepository.GetByIdAsync(input.MemberId); if (member == null) { 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 projectGroup = await _projectGroupRepository.GetByIdAsync(eventProjectGroup.ProjectGroupId); // 优先使用EventProjectGroup中保存的信息,如果没有则使用原始ProjectGroup的信息 var birthDateStart = eventProjectGroup.BirthDateStart != DateTime.MinValue ? eventProjectGroup.BirthDateStart : (projectGroup?.BirthDateStart ?? DateTime.MinValue); var birthDateEnd = eventProjectGroup.BirthDateEnd != DateTime.MinValue ? eventProjectGroup.BirthDateEnd : (projectGroup?.BirthDateEnd ?? DateTime.MaxValue); var gender = eventProjectGroup.Gender; var birthDate = member.BirthDate.Value; if (birthDate < birthDateStart || birthDate > birthDateEnd) { result.AgeValid = false; result.IsValid = false; result.Message = "队员年龄不符合组别要求"; } if (gender != member.Gender && gender != Gender.Unknown && gender != Gender.All) { result.GenderValid = false; result.IsValid = false; result.Message = "队员性别不符合组别要求"; } } return result; } /// /// 检查报名限制 /// [DisplayName("检查报名限制")] [HttpPost("registration-limit/check")] public async Task CheckRegistrationLimit([FromBody] RegistrationLimitCheckInput input) { var result = new RegistrationLimitCheckOutput { HasLimit = false, LimitDetails = new List() }; var limit = await _limitRepository.GetFirstAsync(x => x.EventId == input.EventId); // 获取该赛事的项目级限报配置 var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark); // 获取所有赛事项目,用于关联 var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId); // 获取所有基础项目信息(用于获取项目名称) var projectIds = eventProjects.Select(ep => ep.ProjectId).Distinct().ToList(); var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && !x.DeleteMark); // 获取所有赛事项目组 var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark); // 检查项目级限报(每个项目下的报名队伍数量) if (input.GroupIds != null && input.GroupIds.Any()) { foreach (var groupId in input.GroupIds) { var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == groupId); if (eventProjectGroup == null) continue; var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId); if (eventProject == null) continue; var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == eventProject.ProjectId); if (projectLimit == null) continue; // 统计该项目下已报名的队伍数量 var currentTeams = await _registrationItemRepository.AsQueryable() .Where(x => x.EventProjectGroupId == eventProjectGroup.Id && !x.DeleteMark) .Select(x => x.RegistrationId) .Distinct() .CountAsync(); if (currentTeams >= projectLimit.LimitCount) { var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId); var projectName = project?.Name ?? "未知项目"; result.HasLimit = true; result.LimitDetails.Add(new RegistrationLimitDetail { MemberId = 0, MemberName = eventProjectGroup.GroupName ?? "未知组别", LimitType = "ProjectLimit", LimitMessage = $"项目\"{projectName}\"的组别\"{eventProjectGroup.GroupName}\"已报名{currentTeams}队,达到限报{projectLimit.LimitCount}队" }); } } } // 检查个人项目限报和团队项目限报 if (limit != null && input.MemberIds != null && input.MemberIds.Any()) { var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id)); // 先查询该赛事的所有报名记录 var eventRegistrations = await _registrationRepository.GetListAsync(x => x.EventId == input.EventId && x.PaymentStatus != 4 && !x.DeleteMark); var eventRegistrationIds = eventRegistrations.Select(r => r.Id).ToList(); // 只查询该赛事的报名人员 var registrationPersons = await _registrationPersonRepository.GetListAsync(x => eventRegistrationIds.Contains(x.RegistrationId) && !x.DeleteMark); foreach (var member in members) { var matchedPersons = registrationPersons.Where(x => x.IdCard == member.IdCardNumber && !string.IsNullOrEmpty(x.IdCard)).ToList(); var registrationIds = matchedPersons.Select(x => x.RegistrationId).Distinct().ToList(); var registrations = await _registrationRepository.GetListAsync(x => registrationIds.Contains(x.Id) && x.EventId == input.EventId && x.PaymentStatus != 4 && !x.DeleteMark); // 统计该队员已报名的项目数量(不区分个人/团队) var registeredProjectCount = registrations.Count(); // 检查个人项目限报 if (registeredProjectCount >= limit.IndividualProjectLimit) { result.HasLimit = true; result.LimitDetails.Add(new RegistrationLimitDetail { MemberId = member.Id, MemberName = member.Name ?? member.NickName ?? "未知", LimitType = "IndividualLimit", LimitMessage = $"该队员已报名{registeredProjectCount}个项目,达到个人报名限制{limit.IndividualProjectLimit}个" }); } // 检查团队项目限报(PC端只做团队报名) if (registeredProjectCount >= limit.TeamProjectLimit) { result.HasLimit = true; result.LimitDetails.Add(new RegistrationLimitDetail { MemberId = member.Id, MemberName = member.Name ?? member.NickName ?? "未知", LimitType = "TeamLimit", LimitMessage = $"该队员已报名{registeredProjectCount}个团队项目,达到团队报名限制{limit.TeamProjectLimit}个" }); } } } return result; } /// /// 获取组别可报队伍数量配置 /// [DisplayName("获取组别队伍限制")] [HttpGet("group-team-limit/{eventId}")] public async Task> GetGroupTeamLimit([FromRoute] long eventId) { var result = new List(); var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark); // 获取该赛事的项目级限报配置 var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark); // 获取所有赛事项目,用于关联 ProjectId var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); foreach (var eventProjectGroup in eventProjectGroups) { var currentTeams = await _registrationItemRepository.AsQueryable() .Where(x => x.EventProjectGroupId == eventProjectGroup.Id && !x.DeleteMark) .Select(x => x.RegistrationId) .Distinct() .CountAsync(); // 查找该组别的项目限报配置 var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId); var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == eventProject?.ProjectId); var maxTeams = projectLimit?.LimitCount ?? 2; // 默认2队 result.Add(new GroupTeamLimitOutput { GroupId = eventProjectGroup.Id, MaxTeams = maxTeams, CurrentTeams = currentTeams }); } return result; } /// /// 检查项目完成人数 /// [DisplayName("检查项目完成人数")] [HttpPost("project-completion/check")] public async Task CheckProjectCompletion([FromBody] ProjectCompletionCheckInput input) { var result = new ProjectCompletionCheckOutput { HasError = false, Errors = new List() }; var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId); var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => x.EventId == input.EventId && !x.DeleteMark); var projects = await _projectRepository.GetListAsync(x => eventProjects.Select(ep => ep.ProjectId).Contains(x.Id) && !x.DeleteMark); foreach (var eventProject in eventProjects) { var project = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId); if (project == null) continue; var projectGroups = eventProjectGroups.Where(g => g.EventProjectId == eventProject.Id).ToList(); var assignedGroupIds = input.GroupMembers.Select(g => g.GroupId).ToList(); var assignedProjectGroups = projectGroups.Where(g => assignedGroupIds.Contains(g.Id)).ToList(); if (assignedProjectGroups.Count == 0) { continue; } var totalAssignedMembers = input.GroupMembers .Where(g => assignedProjectGroups.Select(pg => pg.Id).Contains(g.GroupId)) .SelectMany(g => g.MemberIds) .Distinct() .Count(); if (project.CompletionCount > 0) { if (totalAssignedMembers < project.CompletionCount) { result.HasError = true; result.Errors.Add(new ProjectCompletionError { ProjectId = eventProject.Id, ProjectName = project.Name ?? "未知项目", ErrorType = "Insufficient", ErrorMessage = $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前只分配了{totalAssignedMembers}人" }); } else if (totalAssignedMembers > project.CompletionCount) { result.HasError = true; result.Errors.Add(new ProjectCompletionError { ProjectId = eventProject.Id, ProjectName = project.Name ?? "未知项目", ErrorType = "Excessive", ErrorMessage = $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前分配了{totalAssignedMembers}人,人数超额" }); } } } return result; } /// /// 计算赛事报名费用 /// /// 费用计算输入 /// 费用计算结果 private async Task CalculateFeeAsync(FeeCalculationInput input) { _logger.LogInformation("开始计算费用,赛事ID: {EventId}, 项目数: {ProjectCount}, 人数: {ParticipantCount}", input.EventId, input.EventProjectIds.Count, input.ParticipantCount); // 获取赛事费用配置 var feeConfig = await _feeConfigRepository.GetFirstAsync(x => x.EventId == input.EventId); if (feeConfig == null) { _logger.LogWarning("未找到赛事费用配置,赛事ID: {EventId}", input.EventId); var freeResult = new FeeCalculationResult { ChargeMode = "免费", TotalAmount = 0, TotalPersonCount = input.ParticipantCount }; freeResult.CalculationLogs.Add($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 未找到费用配置,默认免费"); return freeResult; } // 获取项目信息(用于单价项目模式) var eventProjects = await _eventProjectRepository.GetListAsync(x => input.EventProjectIds.Contains(x.Id)); var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); // 构建统一计算服务的输入参数 var pricingInput = new PricingInput { ChargeMode = feeConfig.ChargeMode, ParticipantCount = input.ParticipantCount, EventPrice = feeConfig.Price }; // 添加项目信息 foreach (var eventProject in eventProjects) { var baseProject = baseProjects.FirstOrDefault(p => p.Id == eventProject.ProjectId); pricingInput.Projects.Add(new ProjectPricingInfo { ProjectId = eventProject.ProjectId, ProjectName = baseProject?.Name ?? "未知项目", UnitPrice = eventProject.Price }); } // 使用统一计算服务计算费用 var pricingResult = EventPricingService.Calculate(pricingInput); // 转换为现有格式的结果 var result = new FeeCalculationResult { ChargeMode = GetChargeModeDescription(feeConfig.ChargeMode), TotalAmount = pricingResult.TotalAmount, TotalPersonCount = pricingResult.TotalPersonCount, CalculationLogs = pricingResult.CalculationLogs }; // 转换费用明细 foreach (var detail in pricingResult.Details) { result.FeeDetails.Add(new FeeDetailItem { ItemName = detail.ItemName, UnitPrice = detail.UnitPrice, Quantity = detail.Quantity, Subtotal = detail.Subtotal, Remark = detail.Remark }); } _logger.LogInformation("费用计算完成,总金额: {TotalAmount:F2} 元", result.TotalAmount); return result; } /// /// 获取收费模式描述 /// private string GetChargeModeDescription(ChargeMode mode) { return mode switch { ChargeMode.Free => "免费", ChargeMode.FixedPrice => "定价(赛事)", ChargeMode.UnitPriceEvent => "单价(赛事)", ChargeMode.UnitPriceProject => "单价(项目)", _ => "未知模式" }; } }