YD_Event.Admin/YD_Event.Application/Event/UserEventRegistrationService.cs
2026-04-20 13:01:39 +08:00

1663 lines
69 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 YD_Event.Core.Domain;
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 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;
/// <summary>
/// 用户端报名管理服务
/// </summary>
public class UserEventRegistrationService : IDynamicApiController, ITransient
{
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
private readonly ISqlSugarRepository<EventUnit> _unitRepository;
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
private readonly ISqlSugarRepository<EventProjectGroup> _eventProjectGroupRepository;
private readonly ISqlSugarRepository<ProjectGroup> _projectGroupRepository;
private readonly ISqlSugarRepository<EventProjectRegistrationLimit> _projectLimitRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRepository;
private readonly ISqlSugarRepository<EventRegistrationPerson> _registrationPersonRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _userRepository;
private readonly ISqlSugarRepository<SysUser> _sysUserRepository;
private readonly ISqlSugarRepository<EventFeeConfig> _feeConfigRepository;
private readonly ISqlSugarRepository<EventRegistrationLimit> _limitRepository;
private readonly ISqlSugarRepository<PointsRule> _pointsRuleRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
private readonly ISqlSugarRepository<WeChatPayConfig> _weChatPayConfigRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
private readonly ISqlSugarClient _db;
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
private readonly WeChatPayService _weChatPayService;
private readonly IOptionsMonitor<YD_Event.Core.Options.WechatTenpayClientOptions> _wechatTenpayClientOptionsMonitor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ProjectService _projectService;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Coupon> _couponRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> _userCouponRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> _redemptionCodeRepository;
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
private readonly ILogger<UserEventRegistrationService> _logger;
private readonly OrderNoGeneratorService _orderNoGeneratorService;
private readonly UnifiedRegistrationService _unifiedRegistrationService;
public UserEventRegistrationService(
ISqlSugarRepository<EventInfo> eventRepository,
ISqlSugarRepository<EventUnit> unitRepository,
ISqlSugarRepository<EventProject> eventProjectRepository,
ISqlSugarRepository<EventProjectGroup> eventProjectGroupRepository,
ISqlSugarRepository<ProjectGroup> projectGroupRepository,
ISqlSugarRepository<EventProjectRegistrationLimit> projectLimitRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
ISqlSugarRepository<EventRegistrationPerson> registrationPersonRepository,
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository,
ISqlSugarRepository<SysUser> sysUserRepository,
ISqlSugarRepository<EventFeeConfig> feeConfigRepository,
ISqlSugarRepository<EventRegistrationLimit> limitRepository,
ISqlSugarRepository<PointsRule> pointsRuleRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarRepository<WeChatPayConfig> weChatPayConfigRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
ISqlSugarClient db,
AuthManager authManager,
IIdGenerator idGenerator,
WeChatPayService weChatPayService,
IOptionsMonitor<YD_Event.Core.Options.WechatTenpayClientOptions> wechatTenpayClientOptionsMonitor,
IHttpContextAccessor httpContextAccessor,
ProjectService projectService,
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> couponRepository,
ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> userCouponRepository,
ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> redemptionCodeRepository,
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
ILogger<UserEventRegistrationService> logger,
OrderNoGeneratorService orderNoGeneratorService,
UnifiedRegistrationService unifiedRegistrationService)
{
_eventRepository = eventRepository;
_unitRepository = unitRepository;
_eventProjectRepository = eventProjectRepository;
_eventProjectGroupRepository = eventProjectGroupRepository;
_projectGroupRepository = projectGroupRepository;
_projectLimitRepository = projectLimitRepository;
_projectRepository = projectRepository;
_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;
_redemptionCodeRepository = redemptionCodeRepository;
_paymentRepository = paymentRepository;
_logger = logger;
_orderNoGeneratorService = orderNoGeneratorService;
_unifiedRegistrationService = unifiedRegistrationService;
}
/// <summary>
/// 根据出生日期计算年龄
/// </summary>
/// <param name="birthDate">出生日期</param>
/// <returns>年龄</returns>
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;
}
/// <summary>
/// 1. 赛事列表展示接口
/// </summary>
[DisplayName("赛事列表展示")]
[HttpGet("list")]
public async Task<PageResult<UserEventListOutput>> 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<EventFeeConfig>((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<MerchantCertification>()
.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;
}
/// <summary>
/// 2. 赛事详情查看接口
/// </summary>
[DisplayName("赛事详情查看")]
[HttpGet("detail/{id}")]
public async Task<UserEventDetailOutput> Detail([FromRoute] long id)
{
var eventInfo = await _eventRepository.GetByIdAsync(id);
if (eventInfo == null) throw Oops.Bah("赛事不存在");
var output = eventInfo.Adapt<UserEventDetailOutput>();
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<EventUnitDto>()).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)
{
var feeConfig = new UserEventFeeOutput
{
ChargeMode = fee.ChargeMode.ToString(),
Price = fee.Price
};
// 如果赛事有关联的商户ID,查询并填充商户信息
if (eventInfo.MerchantId.HasValue)
{
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
if (merchant != null && merchant.Status == AvailabilityStatus.Enable)
{
feeConfig.MerchantName = merchant.MerchantName;
feeConfig.CreditCode = merchant.CreditCode;
feeConfig.BankName = merchant.BankName;
feeConfig.AccountNumber = merchant.AccountNumber;
}
}
output.FeeConfig = feeConfig;
}
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));
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)
{
dto.ProjectTypeName = p.ProjectType.Description();
}
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;
}
/// <summary>
/// 3.1 选择报名项目接口
/// </summary>
[DisplayName("选择报名项目")]
[HttpGet("projects/{eventId}")]
public async Task<List<UserEventProjectListOutput>> 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));
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)
{
dto.ProjectTypeName = p.ProjectType.Description();
}
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();
}
/// <summary>
/// 3.2 分配队员接口 - 搜索队员
/// </summary>
[DisplayName("搜索队员")]
[HttpGet("members/search")]
public async Task<List<UserTeamMemberOutput>> SearchMembers([FromQuery] string keyword)
{
if (string.IsNullOrWhiteSpace(keyword)) return new List<UserTeamMemberOutput>();
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);
}
/// <summary>
/// 3.2 分配队员接口 - 提交报名(含保存草稿)
/// </summary>
[DisplayName("提交报名")]
[HttpPost("submit")]
[UnitOfWork]
public async Task<long> 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("赛事不存在");
// 获取团队信息
var team = await _db.Queryable<YD_Event.Core.Entities.Team>().FirstAsync(t => t.Id == input.TeamId && !t.DeleteMark);
if (team == null) throw Oops.Bah("团队不存在");
// 收集所有成员ID
var allMemberIds = new List<long>();
if (input.GroupMembers != null)
{
foreach (var gm in input.GroupMembers)
{
// 检查是否有多团队数据
if (gm.Teams != null && gm.Teams.Count > 0)
{
foreach (var t in gm.Teams)
{
allMemberIds.AddRange(t.MemberIds ?? new List<long>());
}
}
else
{
// 向后兼容单团队模式
allMemberIds.AddRange(gm.MemberIds ?? new List<long>());
}
}
}
else if (input.MemberIds != null)
{
// 向后兼容旧结构
allMemberIds.AddRange(input.MemberIds);
}
allMemberIds = allMemberIds.Distinct().ToList();
// 验证:一次报名只能选择单个团队内的运动员
if (allMemberIds.Count > 0)
{
var teamMembers = await _db.Queryable<YD_Event.Core.Entities.TeamMember>()
.Where(tm => allMemberIds.Contains(tm.UserId))
.Where(tm => !tm.DeleteMark)
.ToListAsync();
if (teamMembers == null || teamMembers.Count == 0)
{
throw Oops.Bah("未找到队员信息");
}
var userTeamMap = teamMembers
.GroupBy(tm => tm.UserId)
.ToDictionary(g => g.Key, g => g.Select(tm => tm.TeamId).ToList());
var missingUsers = allMemberIds.Except(userTeamMap.Keys).ToList();
if (missingUsers.Any())
{
throw Oops.Bah("部分队员未找到团队信息");
}
var allBelongToTeam = userTeamMap.All(ut => ut.Value.Contains(input.TeamId));
if (!allBelongToTeam)
{
throw Oops.Bah("所有选择的运动员必须隶属于同一个团队");
}
}
// 获取成员信息
var members = await _userRepository.GetListAsync(x => allMemberIds.Contains(x.Id));
// 获取所有组别信息
var groupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List<long>();
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
// 构建统一报名输入
var unifiedInput = new UnifiedRegistrationInput
{
EventId = input.EventId,
RegistrationMode = 2, // PC端只做团队报名
TeamName = team.Name,
ContactName = userName,
ContactPhone = userMobile,
Items = new List<UnifiedRegistrationItemInput>(),
CreatedUserId = userId,
IsDraft = input.IsDraft
};
// 填充项目和人员信息
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 item = new UnifiedRegistrationItemInput
{
ProjectId = eventProjectGroup.EventProjectId,
GroupId = groupMember.GroupId,
GroupName = eventProjectGroup.GroupName,
Teams = new List<UnifiedTeamInput>(),
Persons = new List<UnifiedPersonInput>()
};
// 检查是多团队模式还是单团队模式
var hasMultipleTeams = groupMember.Teams != null && groupMember.Teams.Count > 0;
if (hasMultipleTeams)
{
// ========== 多团队模式 ==========
foreach (var t in groupMember.Teams)
{
var teamMembersList = members.Where(m => t.MemberIds != null && t.MemberIds.Contains(m.Id)).ToList();
item.Teams.Add(new UnifiedTeamInput
{
TeamIndex = t.TeamIndex,
Persons = teamMembersList.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();
if (!groupMembersList.Any()) continue;
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);
}
}
// 调用统一报名服务
var result = await _unifiedRegistrationService.ProcessRegistrationAsync(unifiedInput);
return result.RegistrationId;
}
/// <summary>
/// 3.3 预览支付接口
/// </summary>
[DisplayName("预览支付")]
[HttpGet("preview/{registrationId}")]
public async Task<UserOrderPreviewOutput> 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<string, decimal> { { "报名费", registration.TotalAmount } }
};
}
/// <summary>
/// 创建支付订单
/// </summary>
[DisplayName("创建支付订单")]
[HttpPost("order/create")]
[UnitOfWork]
public async Task<UserPaymentResultOutput> CreateOrder([FromBody] UserCreateOrderInput input)
{
var userId = _authManager.UserId;
// 获取报名信息和赛事信息
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("赛事不存在");
// 确定支付方式类型(优先使用新的 PaymentMethodType,如果没有则向后兼容旧的 PaymentMethod)
PaymentMethodType paymentMethodType;
if (input.PaymentMethodType != PaymentMethodType.None)
{
paymentMethodType = input.PaymentMethodType;
}
else if (input.PaymentMethod > 0)
{
// 向后兼容:将旧的 PaymentMethod 转换为 PaymentMethodType
paymentMethodType = (PaymentMethodType)input.PaymentMethod;
}
else
{
throw Oops.Bah("请选择支付方式");
}
// 确定支付方式(线上/线下)
int paymentMethod;
if (paymentMethodType == PaymentMethodType.WeChatPay || paymentMethodType == PaymentMethodType.AliPay)
{
paymentMethod = 2; // 线上支付
}
else if (paymentMethodType == PaymentMethodType.BankTransfer)
{
paymentMethod = 1; // 线下支付
}
else
{
throw Oops.Bah("不支持的支付方式");
}
// 构建统一支付输入
var unifiedInput = new UnifiedPaymentInput
{
RegistrationId = input.RegistrationId,
PaymentMethod = paymentMethod,
PaymentMethodType = paymentMethodType,
CouponId = input.CouponId,
OpenId = input.OpenId,
OperatorUserId = userId
};
// 调用统一支付处理服务
var unifiedResult = await _unifiedRegistrationService.ProcessPaymentAsync(unifiedInput);
// 转换结果
var result = new UserPaymentResultOutput
{
OrderNo = unifiedResult.OrderNo,
Status = unifiedResult.PaymentStatus,
IsFree = unifiedResult.IsFree,
PayUrl = unifiedResult.QrcodeUrl ?? string.Empty,
QrcodeUrl = unifiedResult.QrcodeUrl,
WechatPayParams = unifiedResult.WechatPayParams,
TradeType = unifiedResult.TradeType
};
// 如果是线下支付,获取收款商户信息
if (paymentMethodType == PaymentMethodType.BankTransfer && eventInfo.MerchantId.HasValue)
{
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
if (merchant != null)
{
result.PayeeMerchantName = merchant.MerchantName;
result.PayeeAccountInfo = !string.IsNullOrEmpty(merchant.BankName) && !string.IsNullOrEmpty(merchant.AccountNumber)
? $"{merchant.BankName} {merchant.AccountNumber}"
: null;
}
}
return result;
}
/// <summary>
/// 微信支付回调
/// </summary>
[DisplayName("微信支付回调")]
[HttpPost("wechat/notify")]
[AllowAnonymous]
[UnitOfWork]
[NonUnify]
public async Task<IActionResult> WechatPayNotify()
{
try
{
_logger.LogInformation("========== 收到微信支付回调请求 ==========");
using var reader = new StreamReader(_httpContextAccessor.HttpContext.Request.Body);
var body = await reader.ReadToEndAsync();
_logger.LogInformation("微信支付回调 - 请求Body: {Body}", body);
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();
_logger.LogInformation("微信支付回调 - 头部参数: Timestamp={Timestamp}, Nonce={Nonce}, SerialNumber={SerialNumber}",
timestamp, nonce, serialNumber);
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 _unifiedRegistrationService.GetPlatformWeChatPayConfigAsync();
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
};
_logger.LogInformation("微信支付回调 - 开始验证签名");
var isValid = await _weChatPayService.VerifyPayNotifyAsync(platformPayConfig, notifyRequest);
if (!isValid)
{
_logger.LogWarning("微信支付回调签名验证失败");
return new JsonResult(new { code = "FAIL", message = "签名验证失败" });
}
_logger.LogInformation("微信支付回调 - 签名验证成功");
_logger.LogInformation("微信支付回调 - 开始解密数据");
var notifyData = await _weChatPayService.DecryptPayNotifyDataAsync(platformPayConfig, notifyRequest);
if (notifyData == null)
{
_logger.LogWarning("微信支付回调数据解密失败或为空");
return new JsonResult(new { code = "SUCCESS", message = "成功" });
}
_logger.LogInformation("微信支付回调 - 解密成功: OutTradeNo={OutTradeNo}, TradeState={TradeState}, TransactionId={TransactionId}",
notifyData.OutTradeNo, notifyData.TradeState, notifyData.TransactionId);
if (notifyData.TradeState != "SUCCESS")
{
_logger.LogWarning("微信支付回调交易未成功,TradeState={TradeState}", notifyData.TradeState);
return new JsonResult(new { code = "SUCCESS", message = "成功" });
}
_logger.LogInformation("微信支付回调 - 开始处理支付成功逻辑,订单号:{OutTradeNo}", notifyData.OutTradeNo);
// 调用统一支付成功处理方法
await _unifiedRegistrationService.HandlePaymentSuccessAsync(
notifyData.OutTradeNo,
notifyData.TransactionId,
notifyData.SuccessTime?.DateTime,
0);
_logger.LogInformation("微信支付回调 - 支付成功处理完成,订单号:{OutTradeNo}", notifyData.OutTradeNo);
_logger.LogInformation("========== 微信支付回调处理完成 ==========");
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 = "处理失败" });
}
}
/// <summary>
/// 查询订单支付状态
/// </summary>
[DisplayName("查询订单支付状态")]
[HttpGet("order/status/{orderNo}")]
public async Task<int> QueryOrderStatus([FromRoute] string orderNo)
{
var query = _orderRepository.AsQueryable()
.LeftJoin<EventRegistration>((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)
{
_logger.LogInformation("查询订单支付状态 - 订单已支付,订单号:{OrderNo}", orderNo);
return 1; // 已支付
}
// 检查是否是微信/支付宝支付(线上支付)
bool isOnlinePayment = orderInfo.Registration?.PaymentMethod == 2 ||
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.WeChatPay ||
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.AliPay;
_logger.LogInformation("查询订单支付状态 - 订单号:{OrderNo},PaymentMethod={PaymentMethod},PaymentMethodType={PaymentMethodType},IsOnlinePayment={IsOnlinePayment}",
orderNo, orderInfo.Registration?.PaymentMethod, orderInfo.Registration?.PaymentMethodType, isOnlinePayment);
if (isOnlinePayment)
{
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)
{
_logger.LogInformation("查询订单支付状态 - 调用微信支付查询接口,订单号:{OrderNo}", orderNo);
var queryResult = await _weChatPayService.QueryOrderAsync(payConfig, orderNo);
_logger.LogInformation("查询订单支付状态 - 微信支付查询结果,订单号:{OrderNo},Success={Success},TradeState={TradeState}",
orderNo, queryResult.Success, queryResult.TradeState);
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);
}
_logger.LogInformation("查询订单支付状态 - 订单已更新为已支付,订单号:{OrderNo}", orderNo);
return 1;
}
}
else
{
_logger.LogWarning("查询订单支付状态 - 未找到微信支付配置,订单号:{OrderNo}", orderNo);
}
}
return 0; // 未支付
}
/// <summary>
/// 验证优惠券
/// </summary>
[DisplayName("验证优惠券")]
[HttpPost("coupon/validate")]
public async Task<ValidateCouponOutput> ValidateCoupon([FromBody] ValidateCouponInput input)
{
var userId = _authManager.UserId;
return await _unifiedRegistrationService.ValidateAndUseCouponAsync(input, userId);
}
private string GetPaymentStatusDesc(int status)
{
return status switch
{
0 => "待审核",
1 => "已支付",
2 => "支付失败",
3 => "已退款",
4 => "已取消",
_ => "未知"
};
}
/// <summary>
/// 验证队员是否符合报名条件
/// </summary>
[DisplayName("验证队员")]
[HttpPost("member/validate")]
public async Task<MemberValidateOutput> 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 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<UnifiedPersonInput>
{
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;
}
/// <summary>
/// 检查报名限制
/// </summary>
[DisplayName("检查报名限制")]
[HttpPost("registration-limit/check")]
public async Task<RegistrationLimitCheckOutput> 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<YD_Event.Core.Entities.Team>().FirstAsync(t => t.Id == input.TeamId.Value && !t.DeleteMark);
if (team != null)
{
teamName = team.Name;
}
}
// 获取所有成员信息
var allMemberIds = new List<long>();
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<long>());
}
}
else
{
// 向后兼容单团队模式
allMemberIds.AddRange(gm.MemberIds ?? new List<long>());
}
}
}
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<long>();
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<UnifiedRegistrationItemInput>()
};
// 为每个组别添加项目项
foreach (var groupMember in input.GroupMembers ?? new List<GroupMemberAssignment>())
{
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<UnifiedTeamInput>(),
Persons = new List<UnifiedPersonInput>()
};
// 检查是多团队模式还是单团队模式
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);
}
// 处理旧结构(向后兼容)
if ((input.GroupMembers == null || !input.GroupMembers.Any()) && input.GroupIds != null && input.MemberIds != null)
{
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 groupMembersList = members.Where(m => input.MemberIds.Contains(m.Id)).ToList();
unifiedInput.Items.Add(new UnifiedRegistrationItemInput
{
ProjectId = eventProject.Id,
GroupId = groupId,
GroupName = eventProjectGroup.GroupName,
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()
});
}
}
// 调用统一报名服务检查限制
return await _unifiedRegistrationService.CheckRegistrationLimitAsync(unifiedInput);
}
/// <summary>
/// 获取组别可报队伍数量配置
/// </summary>
[DisplayName("获取组别队伍限制")]
[HttpGet("group-team-limit/{eventId}")]
public async Task<List<GroupTeamLimitOutput>> GetGroupTeamLimit([FromRoute] long eventId)
{
var result = new List<GroupTeamLimitOutput>();
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?.Id);
var maxTeams = projectLimit?.LimitCount ?? 2; // 默认2队
result.Add(new GroupTeamLimitOutput
{
GroupId = eventProjectGroup.Id,
MaxTeams = maxTeams,
CurrentTeams = currentTeams
});
}
return result;
}
/// <summary>
/// 检查项目完成人数
/// </summary>
[DisplayName("检查项目完成人数")]
[HttpPost("project-completion/check")]
public async Task<ProjectCompletionCheckOutput> CheckProjectCompletion([FromBody] ProjectCompletionCheckInput input)
{
var result = new ProjectCompletionCheckOutput
{
HasError = false,
Errors = new List<ProjectCompletionError>()
};
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 isTeamProject = project.IsTeamProject();
// 对每个分配的组别进行独立验证
foreach (var assignedGroup in assignedProjectGroups)
{
// 找到该组别的队员分配
var groupMemberAssignment = input.GroupMembers.FirstOrDefault(gm => gm.GroupId == assignedGroup.Id);
if (groupMemberAssignment == null) continue;
// ========== 关键修改:支持多团队模式 ==========
// 检查是多团队模式还是单团队模式
var hasMultipleTeams = groupMemberAssignment.Teams != null && groupMemberAssignment.Teams.Count > 0;
if (hasMultipleTeams && isTeamProject)
{
// ========== 多团队模式验证 ==========
// 验证每个团队的人数是否等于 CompletionCount
foreach (var team in groupMemberAssignment.Teams)
{
var teamMemberCount = team.MemberIds?.Distinct().Count() ?? 0;
// 如果该团队没有选择任何人员,跳过验证
if (teamMemberCount == 0) continue;
// 验证该团队的人数是否等于 CompletionCount
if (project.CompletionCount > 0 && teamMemberCount != project.CompletionCount)
{
string errorMessage;
if (teamMemberCount < project.CompletionCount)
{
errorMessage = $"团队{team.TeamIndex}参赛人数错误(少{project.CompletionCount - teamMemberCount}人)";
}
else
{
errorMessage = $"团队{team.TeamIndex}参赛人数错误(多{teamMemberCount - project.CompletionCount}人)";
}
result.HasError = true;
result.Errors.Add(new ProjectCompletionError
{
ProjectId = eventProject.Id,
ProjectName = project.Name ?? "未知项目",
ErrorType = teamMemberCount < project.CompletionCount ? "Insufficient" : "Excessive",
ErrorMessage = errorMessage
});
}
}
}
else
{
// ========== 单团队模式验证(向后兼容) ==========
// 计算该组别的分配人数
var assignedCount = groupMemberAssignment.MemberIds?.Distinct().Count() ?? 0;
// 如果该组别没有选择任何人员,跳过验证
if (assignedCount == 0) continue;
// 只对团队项目进行完成人数验证
if (isTeamProject && project.CompletionCount > 0 && assignedCount != project.CompletionCount)
{
string errorMessage;
if (assignedCount < project.CompletionCount)
{
errorMessage = $"{project.Name}参赛人数错误(少{project.CompletionCount - assignedCount}人)";
}
else
{
errorMessage = $"{project.Name}参赛人数错误(多{assignedCount - project.CompletionCount}人)";
}
result.HasError = true;
result.Errors.Add(new ProjectCompletionError
{
ProjectId = eventProject.Id,
ProjectName = project.Name ?? "未知项目",
ErrorType = assignedCount < project.CompletionCount ? "Insufficient" : "Excessive",
ErrorMessage = errorMessage
});
}
}
}
}
return result;
}
/// <summary>
/// 计算赛事报名费用
/// </summary>
/// <param name="input">费用计算输入</param>
/// <returns>费用计算结果</returns>
private async Task<FeeCalculationResult> 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;
}
/// <summary>
/// 获取收费模式描述
/// </summary>
private string GetChargeModeDescription(ChargeMode mode)
{
return mode switch
{
ChargeMode.Free => "免费",
ChargeMode.FixedPrice => "定价(赛事)",
ChargeMode.UnitPriceEvent => "单价(赛事)",
ChargeMode.UnitPriceProject => "单价(项目)",
_ => "未知模式"
};
}
/// <summary>
/// 检查组别选择限制
/// </summary>
[DisplayName("检查组别选择限制")]
[HttpPost("check-group-selection-limit")]
public async Task<CheckGroupSelectionLimitOutput> CheckGroupSelectionLimit([FromBody] CheckGroupSelectionLimitInput input)
{
var result = new CheckGroupSelectionLimitOutput
{
CanSelect = true
};
// 获取组别信息
var group = await _eventProjectGroupRepository.GetByIdAsync(input.GroupId);
if (group == null)
{
result.CanSelect = false;
result.LimitMessage = "组别不存在";
return result;
}
// 获取项目信息
var eventProject = await _eventProjectRepository.GetByIdAsync(group.EventProjectId);
if (eventProject == null)
{
result.CanSelect = false;
result.LimitMessage = "项目不存在";
return result;
}
// 获取基础项目信息
var baseProject = await _projectRepository.GetByIdAsync(eventProject.ProjectId);
// 获取赛事信息获取报名限制
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
var registrationLimit = eventInfo != null
? await _limitRepository.GetFirstAsync(x => x.EventId == input.EventId && !x.DeleteMark)
: null;
// 1. 检查组别自身限制(这里暂不实现,因为 EventProjectGroup 实体没有 GroupLimitCount 字段)
// 后续可以根据实际需求添加相关的限制配置表
// 2. 检查项目限制
var selectedGroupIds = input.SelectedGroupIds ?? new List<long>();
var otherSelectedGroups = await _eventProjectGroupRepository.GetListAsync(x => selectedGroupIds.Contains(x.Id));
var projectGroups = otherSelectedGroups.Where(g => g.EventProjectId == eventProject.Id).ToList();
// 计算该项目已选的组别数量(排除当前组别)
var selectedCountForProject = projectGroups.Count;
// 获取该项目的限报配置
var projectLimit = await _projectLimitRepository.GetFirstAsync(x =>
x.EventId == input.EventId && x.ProjectId == eventProject.ProjectId && !x.DeleteMark);
if (projectLimit != null)
{
result.ProjectLimit = new ProjectLimitDetail
{
ProjectLimitCount = projectLimit.LimitCount,
CurrentSelectedCount = selectedCountForProject,
IsLimitReached = selectedCountForProject >= projectLimit.LimitCount
};
if (result.ProjectLimit.IsLimitReached && !selectedGroupIds.Contains(input.GroupId))
{
result.CanSelect = false;
result.LimitMessage = $"该项目最多可报名{projectLimit.LimitCount}个组别";
return result;
}
}
// 3. 检查团队/个人项目限制
if (registrationLimit != null && baseProject != null)
{
var isTeamProject = baseProject.IsTeamProject();
var isIndividualProject = baseProject.IsIndividualProject();
// 计算已选的团队项目和个人项目数量
var allSelectedEventProjectIds = otherSelectedGroups
.Where(g => g.Id != input.GroupId)
.Select(g => g.EventProjectId)
.Distinct()
.ToList();
var allSelectedEventProjects = await _eventProjectRepository.GetListAsync(x => allSelectedEventProjectIds.Contains(x.Id));
var allSelectedProjectIds = allSelectedEventProjects.Select(ep => ep.ProjectId).Distinct().ToList();
var allSelectedProjects = await _projectRepository.GetListAsync(x => allSelectedProjectIds.Contains(x.Id));
var selectedTeamCount = 0;
var selectedIndividualCount = 0;
foreach (var selectedProject in allSelectedProjects)
{
if (selectedProject.IsTeamProject()) selectedTeamCount++;
if (selectedProject.IsIndividualProject()) selectedIndividualCount++;
}
// 团队项目限制
if (isTeamProject)
{
result.TeamProjectLimit = new EventLimitDetail
{
LimitCount = registrationLimit.TeamProjectLimit,
CurrentSelectedCount = selectedTeamCount,
IsLimitReached = selectedTeamCount >= registrationLimit.TeamProjectLimit
};
if (result.TeamProjectLimit.IsLimitReached && !selectedGroupIds.Contains(input.GroupId))
{
result.CanSelect = false;
result.LimitMessage = $"团队项目最多可报名{registrationLimit.TeamProjectLimit}个";
return result;
}
}
// 个人项目限制
if (isIndividualProject)
{
result.IndividualProjectLimit = new EventLimitDetail
{
LimitCount = registrationLimit.IndividualProjectLimit,
CurrentSelectedCount = selectedIndividualCount,
IsLimitReached = selectedIndividualCount >= registrationLimit.IndividualProjectLimit
};
if (result.IndividualProjectLimit.IsLimitReached && !selectedGroupIds.Contains(input.GroupId))
{
result.CanSelect = false;
result.LimitMessage = $"个人项目最多可报名{registrationLimit.IndividualProjectLimit}个";
return result;
}
}
}
return result;
}
/// <summary>
/// 验证用户是否已报名该赛事
/// </summary>
[DisplayName("验证用户报名状态")]
[HttpPost("registration-status/check")]
public async Task<CheckRegistrationStatusOutput> CheckRegistrationStatus([FromBody] CheckRegistrationStatusInput input)
{
var userId = _authManager.UserId;
var result = new CheckRegistrationStatusOutput();
// 查询用户是否已报名该赛事
var registration = await _registrationRepository.GetFirstAsync(x =>
x.EventId == input.EventId &&
x.CreatedUserId == userId &&
!x.DeleteMark &&
(
(x.PaymentMethod == 1 && (x.PaymentStatus == 0 || x.PaymentStatus == 1)) ||
(x.PaymentMethod == 2 && x.PaymentStatus == 1)
));
if (registration != null)
{
result.IsRegistered = true;
result.RegistrationId = registration.Id;
result.RegistrationStatus = GetPaymentStatusDesc(registration.PaymentStatus);
result.Message = "您已报名该赛事,请先取消报名后再重新报名";
}
else
{
result.IsRegistered = false;
result.Message = "可以报名";
}
return result;
}
/// <summary>
/// 验证用户报名状态输入
/// </summary>
public class CheckRegistrationStatusInput
{
/// <summary>
/// 赛事ID
/// </summary>
[Required]
public long EventId { get; set; }
}
/// <summary>
/// 验证用户报名状态输出
/// </summary>
public class CheckRegistrationStatusOutput
{
/// <summary>
/// 是否已报名
/// </summary>
public bool IsRegistered { get; set; }
/// <summary>
/// 报名ID (如果已报名)
/// </summary>
public long? RegistrationId { get; set; }
/// <summary>
/// 报名状态描述
/// </summary>
public string? RegistrationStatus { get; set; }
/// <summary>
/// 提示消息
/// </summary>
public string? Message { get; set; }
}
}