2026-03-20 03:15:31 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using YD_Event.Application.Event.Dtos;
|
2026-03-31 17:38:36 +08:00
|
|
|
|
using YD_Event.Application.Project;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
using YD_Event.Core.Entities;
|
|
|
|
|
|
using YD_Event.Core.Enum;
|
2026-04-06 10:50:57 +08:00
|
|
|
|
using YD_Event.Core.Domain.Pricing;
|
2026-04-10 10:54:47 +08:00
|
|
|
|
using YD_Event.Core.Domain.Pricing.Models;
|
2026-04-10 22:39:32 +08:00
|
|
|
|
using YD_Event.Core.Domain;
|
2026-04-13 13:23:00 +08:00
|
|
|
|
using YD_Event.Core.Extensions;
|
2026-04-10 10:54:47 +08:00
|
|
|
|
using Easy.Core;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
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;
|
2026-03-25 11:24:52 +08:00
|
|
|
|
using Microsoft.Extensions.Options;
|
2026-03-25 23:58:30 +08:00
|
|
|
|
using YD_Event.Core.Infrastructure.WeChat;
|
|
|
|
|
|
using YD_Event.Core.Infrastructure.WeChat.Dto;
|
2026-03-25 11:24:52 +08:00
|
|
|
|
using System.IO;
|
2026-04-06 09:21:47 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
2026-04-01 10:12:06 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventProjectGroup> _eventProjectGroupRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<ProjectGroup> _projectGroupRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventProjectRegistrationLimit> _projectLimitRepository;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
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;
|
2026-04-01 10:12:06 +08:00
|
|
|
|
private readonly ISqlSugarRepository<SysUser> _sysUserRepository;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventFeeConfig> _feeConfigRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationLimit> _limitRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<PointsRule> _pointsRuleRepository;
|
2026-05-13 17:23:27 +08:00
|
|
|
|
private readonly ISqlSugarRepository<PointsRuleDetail> _pointsRuleDetailRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<RefundConfig> _refundConfigRepository;
|
2026-03-25 11:24:52 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
|
2026-03-25 23:58:30 +08:00
|
|
|
|
private readonly ISqlSugarRepository<WeChatPayConfig> _weChatPayConfigRepository;
|
2026-03-26 23:08:22 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
|
2026-03-30 17:06:12 +08:00
|
|
|
|
private readonly ISqlSugarClient _db;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
private readonly AuthManager _authManager;
|
|
|
|
|
|
private readonly IIdGenerator _idGenerator;
|
2026-03-25 23:58:30 +08:00
|
|
|
|
private readonly WeChatPayService _weChatPayService;
|
2026-03-25 11:24:52 +08:00
|
|
|
|
private readonly IOptionsMonitor<YD_Event.Core.Options.WechatTenpayClientOptions> _wechatTenpayClientOptionsMonitor;
|
|
|
|
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
2026-03-31 17:38:36 +08:00
|
|
|
|
private readonly ProjectService _projectService;
|
2026-04-01 13:04:41 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Coupon> _couponRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> _userCouponRepository;
|
2026-04-12 13:14:20 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> _redemptionCodeRepository;
|
2026-04-09 23:12:51 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
2026-04-06 09:21:47 +08:00
|
|
|
|
private readonly ILogger<UserEventRegistrationService> _logger;
|
2026-04-10 22:39:32 +08:00
|
|
|
|
private readonly OrderNoGeneratorService _orderNoGeneratorService;
|
2026-04-13 00:40:44 +08:00
|
|
|
|
private readonly UnifiedRegistrationService _unifiedRegistrationService;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
|
|
|
|
|
|
public UserEventRegistrationService(
|
|
|
|
|
|
ISqlSugarRepository<EventInfo> eventRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventUnit> unitRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventProject> eventProjectRepository,
|
2026-04-01 10:12:06 +08:00
|
|
|
|
ISqlSugarRepository<EventProjectGroup> eventProjectGroupRepository,
|
|
|
|
|
|
ISqlSugarRepository<ProjectGroup> projectGroupRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventProjectRegistrationLimit> projectLimitRepository,
|
2026-03-20 03:15:31 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventRegistration> registrationRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventRegistrationPerson> registrationPersonRepository,
|
|
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository,
|
2026-04-01 10:12:06 +08:00
|
|
|
|
ISqlSugarRepository<SysUser> sysUserRepository,
|
2026-03-20 03:15:31 +08:00
|
|
|
|
ISqlSugarRepository<EventFeeConfig> feeConfigRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventRegistrationLimit> limitRepository,
|
|
|
|
|
|
ISqlSugarRepository<PointsRule> pointsRuleRepository,
|
2026-05-13 17:23:27 +08:00
|
|
|
|
ISqlSugarRepository<PointsRuleDetail> pointsRuleDetailRepository,
|
|
|
|
|
|
ISqlSugarRepository<RefundConfig> refundConfigRepository,
|
2026-03-25 11:24:52 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
|
2026-03-25 23:58:30 +08:00
|
|
|
|
ISqlSugarRepository<WeChatPayConfig> weChatPayConfigRepository,
|
2026-03-26 23:08:22 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
|
2026-03-30 17:06:12 +08:00
|
|
|
|
ISqlSugarClient db,
|
2026-03-20 03:15:31 +08:00
|
|
|
|
AuthManager authManager,
|
2026-03-25 11:24:52 +08:00
|
|
|
|
IIdGenerator idGenerator,
|
2026-03-25 23:58:30 +08:00
|
|
|
|
WeChatPayService weChatPayService,
|
2026-03-25 11:24:52 +08:00
|
|
|
|
IOptionsMonitor<YD_Event.Core.Options.WechatTenpayClientOptions> wechatTenpayClientOptionsMonitor,
|
2026-03-31 17:38:36 +08:00
|
|
|
|
IHttpContextAccessor httpContextAccessor,
|
2026-04-01 13:04:41 +08:00
|
|
|
|
ProjectService projectService,
|
|
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Coupon> couponRepository,
|
2026-04-06 09:21:47 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.UserCoupon> userCouponRepository,
|
2026-04-12 13:14:20 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.CouponRedemptionCode> redemptionCodeRepository,
|
2026-04-09 23:12:51 +08:00
|
|
|
|
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
2026-04-10 22:39:32 +08:00
|
|
|
|
ILogger<UserEventRegistrationService> logger,
|
2026-04-13 00:40:44 +08:00
|
|
|
|
OrderNoGeneratorService orderNoGeneratorService,
|
|
|
|
|
|
UnifiedRegistrationService unifiedRegistrationService)
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
_eventRepository = eventRepository;
|
|
|
|
|
|
_unitRepository = unitRepository;
|
|
|
|
|
|
_eventProjectRepository = eventProjectRepository;
|
2026-04-01 10:12:06 +08:00
|
|
|
|
_eventProjectGroupRepository = eventProjectGroupRepository;
|
|
|
|
|
|
_projectGroupRepository = projectGroupRepository;
|
|
|
|
|
|
_projectLimitRepository = projectLimitRepository;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
_projectRepository = projectRepository;
|
|
|
|
|
|
_registrationRepository = registrationRepository;
|
|
|
|
|
|
_registrationItemRepository = registrationItemRepository;
|
|
|
|
|
|
_registrationPersonRepository = registrationPersonRepository;
|
|
|
|
|
|
_userRepository = userRepository;
|
2026-04-01 10:12:06 +08:00
|
|
|
|
_sysUserRepository = sysUserRepository;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
_feeConfigRepository = feeConfigRepository;
|
|
|
|
|
|
_limitRepository = limitRepository;
|
|
|
|
|
|
_pointsRuleRepository = pointsRuleRepository;
|
2026-05-13 17:23:27 +08:00
|
|
|
|
_pointsRuleDetailRepository = pointsRuleDetailRepository;
|
|
|
|
|
|
_refundConfigRepository = refundConfigRepository;
|
2026-03-25 11:24:52 +08:00
|
|
|
|
_orderRepository = orderRepository;
|
2026-03-25 23:58:30 +08:00
|
|
|
|
_weChatPayConfigRepository = weChatPayConfigRepository;
|
2026-03-26 23:08:22 +08:00
|
|
|
|
_merchantRepository = merchantRepository;
|
2026-03-30 17:06:12 +08:00
|
|
|
|
_db = db;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
_authManager = authManager;
|
|
|
|
|
|
_idGenerator = idGenerator;
|
2026-03-25 23:58:30 +08:00
|
|
|
|
_weChatPayService = weChatPayService;
|
2026-03-25 11:24:52 +08:00
|
|
|
|
_wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor;
|
|
|
|
|
|
_httpContextAccessor = httpContextAccessor;
|
2026-03-31 17:38:36 +08:00
|
|
|
|
_projectService = projectService;
|
2026-04-01 13:04:41 +08:00
|
|
|
|
_couponRepository = couponRepository;
|
|
|
|
|
|
_userCouponRepository = userCouponRepository;
|
2026-04-12 13:14:20 +08:00
|
|
|
|
_redemptionCodeRepository = redemptionCodeRepository;
|
2026-04-09 23:12:51 +08:00
|
|
|
|
_paymentRepository = paymentRepository;
|
2026-04-06 09:21:47 +08:00
|
|
|
|
_logger = logger;
|
2026-04-10 22:39:32 +08:00
|
|
|
|
_orderNoGeneratorService = orderNoGeneratorService;
|
2026-04-13 00:40:44 +08:00
|
|
|
|
_unifiedRegistrationService = unifiedRegistrationService;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-10 14:29:41 +08:00
|
|
|
|
/// <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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 03:15:31 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 1. 赛事列表展示接口
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("赛事列表展示")]
|
|
|
|
|
|
[HttpGet("list")]
|
|
|
|
|
|
public async Task<PageResult<UserEventListOutput>> Page([FromQuery] UserEventQueryInput input)
|
|
|
|
|
|
{
|
2026-04-06 09:21:47 +08:00
|
|
|
|
var pageResult = await _eventRepository.AsQueryable()
|
2026-03-20 03:15:31 +08:00
|
|
|
|
.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)
|
2026-04-06 09:21:47 +08:00
|
|
|
|
.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);
|
2026-03-26 23:08:22 +08:00
|
|
|
|
|
|
|
|
|
|
var userId = _authManager.UserId;
|
2026-03-30 17:06:12 +08:00
|
|
|
|
var certification = await _db.Queryable<MerchantCertification>()
|
|
|
|
|
|
.FirstAsync(c => c.UserId == userId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
|
|
|
|
|
|
var canRegister = certification != null;
|
2026-03-26 23:08:22 +08:00
|
|
|
|
|
|
|
|
|
|
foreach (var item in pageResult.Rows)
|
|
|
|
|
|
{
|
|
|
|
|
|
item.CanRegister = canRegister;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return pageResult;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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;
|
2026-04-27 11:23:47 +08:00
|
|
|
|
|
|
|
|
|
|
// 直接赋值 int 类型
|
|
|
|
|
|
output.Type = (int)eventInfo.EventType;
|
|
|
|
|
|
output.RegistrationType = (int)eventInfo.RegistrationType;
|
|
|
|
|
|
|
2026-05-13 17:53:41 +08:00
|
|
|
|
// 设置赛事状态
|
|
|
|
|
|
output.EventStatus = eventInfo.EventStatus;
|
|
|
|
|
|
|
2026-03-20 03:15:31 +08:00
|
|
|
|
output.IsNeedMaterial = eventInfo.IsNeedMaterial;
|
|
|
|
|
|
output.MaterialRemark = eventInfo.MaterialRemark;
|
|
|
|
|
|
output.MaterialUploadEndTime = eventInfo.MaterialUploadEndTime;
|
|
|
|
|
|
|
2026-04-27 11:23:47 +08:00
|
|
|
|
// 查询省市区名称
|
|
|
|
|
|
if (eventInfo.ProvinceId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var province = await _db.Queryable<SysProvince>().FirstAsync(x => x.Id == eventInfo.ProvinceId.Value && !x.DeleteMark);
|
|
|
|
|
|
output.ProvinceName = province?.ProvinceName ?? "";
|
|
|
|
|
|
}
|
|
|
|
|
|
if (eventInfo.CityId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var city = await _db.Queryable<SysCity>().FirstAsync(x => x.Id == eventInfo.CityId.Value && !x.DeleteMark);
|
|
|
|
|
|
output.CityName = city?.CityName ?? "";
|
|
|
|
|
|
}
|
|
|
|
|
|
if (eventInfo.DistrictId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var district = await _db.Queryable<SysDistrict>().FirstAsync(x => x.Id == eventInfo.DistrictId.Value && !x.DeleteMark);
|
|
|
|
|
|
output.DistrictName = district?.DistrictName ?? "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 03:15:31 +08:00
|
|
|
|
if (eventInfo.PointsRuleId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rule = await _pointsRuleRepository.GetByIdAsync(eventInfo.PointsRuleId.Value);
|
|
|
|
|
|
output.PointsRuleName = rule?.RuleName ?? "";
|
2026-05-13 17:23:27 +08:00
|
|
|
|
|
|
|
|
|
|
if (rule != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var details = await _pointsRuleDetailRepository.AsQueryable()
|
|
|
|
|
|
.Where(d => d.PointsRuleId == rule.Id && !d.DeleteMark)
|
|
|
|
|
|
.OrderBy(d => d.StartRank)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
output.PointsRuleDetail = new YD_Event.Application.Client.Dtos.PointsRuleDto
|
|
|
|
|
|
{
|
|
|
|
|
|
RuleName = rule.RuleName,
|
|
|
|
|
|
ParticipationPoints = rule.ParticipationPoints,
|
|
|
|
|
|
Remark = rule.Remark,
|
|
|
|
|
|
Details = details.Select(d => new YD_Event.Application.Client.Dtos.PointsRuleDetailDto
|
|
|
|
|
|
{
|
|
|
|
|
|
StartRank = d.StartRank,
|
|
|
|
|
|
EndRank = d.EndRank,
|
|
|
|
|
|
Points = d.Points
|
|
|
|
|
|
}).ToList()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|
2026-05-13 17:23:27 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取退款规则
|
|
|
|
|
|
var refundConfigs = await _refundConfigRepository.AsQueryable()
|
|
|
|
|
|
.Where(c => c.Status == AvailabilityStatus.Enable && !c.DeleteMark)
|
|
|
|
|
|
.OrderBy(c => c.DayStart)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
output.RefundRules = refundConfigs.Select(c => new YD_Event.Application.Event.Dtos.RefundRuleDto
|
|
|
|
|
|
{
|
|
|
|
|
|
DayStart = c.DayStart,
|
|
|
|
|
|
DayEnd = c.DayEnd,
|
|
|
|
|
|
DeductPercentage = c.DeductPercentage,
|
|
|
|
|
|
Remark = c.Remark
|
|
|
|
|
|
}).ToList();
|
2026-03-20 03:15:31 +08:00
|
|
|
|
|
|
|
|
|
|
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);
|
2026-04-20 09:38:02 +08:00
|
|
|
|
if (fee != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var feeConfig = new UserEventFeeOutput
|
|
|
|
|
|
{
|
2026-04-27 11:23:47 +08:00
|
|
|
|
ChargeMode = (int)fee.ChargeMode,
|
2026-04-20 09:38:02 +08:00
|
|
|
|
Price = fee.Price
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 如果赛事有关联的商户ID,查询并填充商户信息
|
|
|
|
|
|
if (eventInfo.MerchantId.HasValue)
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
2026-04-20 09:38:02 +08:00
|
|
|
|
var merchant = await _merchantRepository.GetByIdAsync(eventInfo.MerchantId.Value);
|
|
|
|
|
|
if (merchant != null && merchant.Status == AvailabilityStatus.Enable)
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
2026-04-20 09:38:02 +08:00
|
|
|
|
feeConfig.MerchantName = merchant.MerchantName;
|
|
|
|
|
|
feeConfig.CreditCode = merchant.CreditCode;
|
|
|
|
|
|
feeConfig.BankName = merchant.BankName;
|
|
|
|
|
|
feeConfig.AccountNumber = merchant.AccountNumber;
|
|
|
|
|
|
}
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:38:02 +08:00
|
|
|
|
output.FeeConfig = feeConfig;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 03:15:31 +08:00
|
|
|
|
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id);
|
2026-04-01 10:12:06 +08:00
|
|
|
|
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));
|
|
|
|
|
|
|
2026-03-20 03:15:31 +08:00
|
|
|
|
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
2026-04-01 10:12:06 +08:00
|
|
|
|
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable);
|
|
|
|
|
|
var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
|
2026-03-20 03:15:31 +08:00
|
|
|
|
|
|
|
|
|
|
output.Projects = eventProjects.Select(ep =>
|
|
|
|
|
|
{
|
2026-04-01 10:12:06 +08:00
|
|
|
|
var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
|
|
|
|
|
|
|
|
|
|
|
|
var dto = new UserEventProjectListOutput
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = ep.Id,
|
|
|
|
|
|
Name = p?.Name ?? "",
|
2026-04-10 14:29:41 +08:00
|
|
|
|
Fee = ep.Price,
|
2026-04-09 20:49:36 +08:00
|
|
|
|
CompletionCount = p?.CompletionCount ?? 0
|
2026-03-20 03:15:31 +08:00
|
|
|
|
};
|
2026-04-01 10:12:06 +08:00
|
|
|
|
|
|
|
|
|
|
if (p != null)
|
|
|
|
|
|
{
|
2026-04-12 16:11:20 +08:00
|
|
|
|
dto.ProjectTypeName = p.ProjectType.Description();
|
2026-04-01 10:12:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
2026-04-05 20:58:17 +08:00
|
|
|
|
GroupName = eg.GroupName ?? baseGroup?.GroupName,
|
|
|
|
|
|
BirthDateStart = eg.BirthDateStart != DateTime.MinValue ? eg.BirthDateStart : baseGroup?.BirthDateStart,
|
|
|
|
|
|
BirthDateEnd = eg.BirthDateEnd != DateTime.MinValue ? eg.BirthDateEnd : baseGroup?.BirthDateEnd,
|
2026-04-06 14:13:43 +08:00
|
|
|
|
Gender = (int)eg.Gender
|
2026-04-01 10:12:06 +08:00
|
|
|
|
};
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var projLimit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.Id);
|
|
|
|
|
|
if (projLimit != null) dto.LimitCount = projLimit.LimitCount;
|
|
|
|
|
|
|
|
|
|
|
|
return dto;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}).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;
|
2026-04-09 23:12:51 +08:00
|
|
|
|
output.UserRegistrationStatus = GetPaymentStatusDesc(registration.PaymentStatus);
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
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);
|
2026-04-01 10:12:06 +08:00
|
|
|
|
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));
|
|
|
|
|
|
|
2026-03-20 03:15:31 +08:00
|
|
|
|
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
|
|
|
|
|
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable);
|
2026-04-01 10:12:06 +08:00
|
|
|
|
var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
|
|
|
|
|
|
|
2026-03-20 03:15:31 +08:00
|
|
|
|
return eventProjects.Select(ep =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
|
2026-04-01 10:12:06 +08:00
|
|
|
|
|
|
|
|
|
|
var dto = new UserEventProjectListOutput
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
Id = ep.Id,
|
|
|
|
|
|
Name = p?.Name ?? "",
|
2026-04-10 14:29:41 +08:00
|
|
|
|
Fee = ep.Price,
|
2026-03-31 17:38:36 +08:00
|
|
|
|
TeamSize = 1,
|
2026-04-09 20:49:36 +08:00
|
|
|
|
CompletionCount = p?.CompletionCount ?? 0
|
2026-03-20 03:15:31 +08:00
|
|
|
|
};
|
2026-04-01 10:12:06 +08:00
|
|
|
|
|
|
|
|
|
|
if (p != null)
|
|
|
|
|
|
{
|
2026-04-12 16:11:20 +08:00
|
|
|
|
dto.ProjectTypeName = p.ProjectType.Description();
|
2026-04-01 10:12:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
2026-04-05 20:58:17 +08:00
|
|
|
|
GroupName = eg.GroupName ?? baseGroup?.GroupName,
|
|
|
|
|
|
BirthDateStart = eg.BirthDateStart != DateTime.MinValue ? eg.BirthDateStart : baseGroup?.BirthDateStart,
|
|
|
|
|
|
BirthDateEnd = eg.BirthDateEnd != DateTime.MinValue ? eg.BirthDateEnd : baseGroup?.BirthDateEnd,
|
2026-04-06 14:13:43 +08:00
|
|
|
|
Gender = (int)eg.Gender
|
2026-04-01 10:12:06 +08:00
|
|
|
|
};
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var projLimit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.Id);
|
|
|
|
|
|
if (projLimit != null) dto.LimitCount = projLimit.LimitCount;
|
|
|
|
|
|
|
|
|
|
|
|
return dto;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}).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,
|
2026-03-25 09:09:45 +08:00
|
|
|
|
IdCardNumber = MaskIdCard(u.IdCardNumber),
|
2026-03-20 03:15:31 +08:00
|
|
|
|
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;
|
2026-04-01 10:12:06 +08:00
|
|
|
|
var sysUser = await _sysUserRepository.GetByIdAsync(userId);
|
|
|
|
|
|
if (sysUser == null) throw Oops.Bah("用户不存在");
|
|
|
|
|
|
string userName = sysUser.Name;
|
|
|
|
|
|
string userMobile = sysUser.Mobile ?? "";
|
2026-03-20 03:15:31 +08:00
|
|
|
|
|
|
|
|
|
|
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
|
|
|
|
|
|
if (eventInfo == null) throw Oops.Bah("赛事不存在");
|
2026-04-05 20:58:17 +08:00
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 获取团队信息
|
|
|
|
|
|
var team = await _db.Queryable<YD_Event.Core.Entities.Team>().FirstAsync(t => t.Id == input.TeamId && !t.DeleteMark);
|
|
|
|
|
|
if (team == null) throw Oops.Bah("团队不存在");
|
|
|
|
|
|
|
2026-04-20 13:01:39 +08:00
|
|
|
|
// 收集所有成员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();
|
|
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 验证:一次报名只能选择单个团队内的运动员
|
2026-04-20 13:01:39 +08:00
|
|
|
|
if (allMemberIds.Count > 0)
|
2026-04-05 20:58:17 +08:00
|
|
|
|
{
|
2026-04-13 00:40:44 +08:00
|
|
|
|
var teamMembers = await _db.Queryable<YD_Event.Core.Entities.TeamMember>()
|
2026-04-20 13:01:39 +08:00
|
|
|
|
.Where(tm => allMemberIds.Contains(tm.UserId))
|
2026-04-05 20:58:17 +08:00
|
|
|
|
.Where(tm => !tm.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (teamMembers == null || teamMembers.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("未找到队员信息");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 10:50:57 +08:00
|
|
|
|
var userTeamMap = teamMembers
|
|
|
|
|
|
.GroupBy(tm => tm.UserId)
|
|
|
|
|
|
.ToDictionary(g => g.Key, g => g.Select(tm => tm.TeamId).ToList());
|
|
|
|
|
|
|
2026-04-20 13:01:39 +08:00
|
|
|
|
var missingUsers = allMemberIds.Except(userTeamMap.Keys).ToList();
|
2026-04-06 10:50:57 +08:00
|
|
|
|
if (missingUsers.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("部分队员未找到团队信息");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var allBelongToTeam = userTeamMap.All(ut => ut.Value.Contains(input.TeamId));
|
|
|
|
|
|
if (!allBelongToTeam)
|
2026-04-05 20:58:17 +08:00
|
|
|
|
{
|
2026-04-06 10:50:57 +08:00
|
|
|
|
throw Oops.Bah("所有选择的运动员必须隶属于同一个团队");
|
2026-04-05 20:58:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-20 03:15:31 +08:00
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 获取成员信息
|
2026-04-20 13:01:39 +08:00
|
|
|
|
var members = await _userRepository.GetListAsync(x => allMemberIds.Contains(x.Id));
|
2026-04-01 10:12:06 +08:00
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 获取所有组别信息
|
2026-04-01 10:12:06 +08:00
|
|
|
|
var groupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List<long>();
|
|
|
|
|
|
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
|
2026-04-01 13:04:41 +08:00
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 构建统一报名输入
|
|
|
|
|
|
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
|
|
|
|
|
|
};
|
2026-03-20 03:15:31 +08:00
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 填充项目和人员信息
|
2026-04-01 10:12:06 +08:00
|
|
|
|
if (input.GroupMembers != null && input.GroupMembers.Any())
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
2026-04-01 10:12:06 +08:00
|
|
|
|
foreach (var groupMember in input.GroupMembers)
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
2026-04-01 10:12:06 +08:00
|
|
|
|
var eventProjectGroup = eventProjectGroups.FirstOrDefault(g => g.Id == groupMember.GroupId);
|
|
|
|
|
|
if (eventProjectGroup == null) continue;
|
2026-04-20 13:01:39 +08:00
|
|
|
|
|
|
|
|
|
|
var item = new UnifiedRegistrationItemInput
|
2026-04-01 10:12:06 +08:00
|
|
|
|
{
|
2026-04-13 00:40:44 +08:00
|
|
|
|
ProjectId = eventProjectGroup.EventProjectId,
|
|
|
|
|
|
GroupId = groupMember.GroupId,
|
|
|
|
|
|
GroupName = eventProjectGroup.GroupName,
|
2026-04-20 13:01:39 +08:00
|
|
|
|
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
|
2026-04-01 10:12:06 +08:00
|
|
|
|
{
|
2026-04-13 00:40:44 +08:00
|
|
|
|
UserId = m.Id,
|
|
|
|
|
|
Name = m.Name ?? "",
|
|
|
|
|
|
IdCardNumber = m.IdCardNumber ?? "",
|
|
|
|
|
|
Gender = m.Gender,
|
|
|
|
|
|
Mobile = m.Mobile,
|
|
|
|
|
|
BirthDate = m.BirthDate ?? DateTime.MinValue
|
2026-04-20 13:01:39 +08:00
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unifiedInput.Items.Add(item);
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 调用统一报名服务
|
|
|
|
|
|
var result = await _unifiedRegistrationService.ProcessRegistrationAsync(unifiedInput);
|
|
|
|
|
|
return result.RegistrationId;
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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);
|
|
|
|
|
|
|
2026-04-01 13:04:41 +08:00
|
|
|
|
var projectOutputs = items.Select(i =>
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
2026-04-01 13:04:41 +08:00
|
|
|
|
// 获取该项目的队员
|
|
|
|
|
|
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
|
|
|
|
|
|
};
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var memberOutputs = persons.DistinctBy(p => p.IdCard).Select(p => new UserTeamMemberOutput
|
|
|
|
|
|
{
|
2026-04-01 13:04:41 +08:00
|
|
|
|
UserId = p.Id,
|
2026-03-20 03:15:31 +08:00
|
|
|
|
Name = p.Name,
|
|
|
|
|
|
Mobile = p.Phone,
|
2026-03-25 09:09:45 +08:00
|
|
|
|
IdCardNumber = MaskIdCard(p.IdCard),
|
2026-04-01 13:04:41 +08:00
|
|
|
|
Gender = p.Gender.ToString(),
|
|
|
|
|
|
IsEligible = true,
|
|
|
|
|
|
IneligibilityReason = ""
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}).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)
|
|
|
|
|
|
{
|
2026-04-13 00:40:44 +08:00
|
|
|
|
var userId = _authManager.UserId;
|
2026-04-01 13:04:41 +08:00
|
|
|
|
|
2026-04-13 13:23:00 +08:00
|
|
|
|
// 获取报名信息和赛事信息
|
|
|
|
|
|
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("不支持的支付方式");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 构建统一支付输入
|
|
|
|
|
|
var unifiedInput = new UnifiedPaymentInput
|
2026-03-25 11:24:52 +08:00
|
|
|
|
{
|
2026-04-13 00:40:44 +08:00
|
|
|
|
RegistrationId = input.RegistrationId,
|
2026-04-13 13:23:00 +08:00
|
|
|
|
PaymentMethod = paymentMethod,
|
|
|
|
|
|
PaymentMethodType = paymentMethodType,
|
2026-04-01 13:04:41 +08:00
|
|
|
|
CouponId = input.CouponId,
|
2026-04-13 13:23:00 +08:00
|
|
|
|
OpenId = input.OpenId,
|
2026-04-13 00:40:44 +08:00
|
|
|
|
OperatorUserId = userId
|
2026-03-25 11:24:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 调用统一支付处理服务
|
|
|
|
|
|
var unifiedResult = await _unifiedRegistrationService.ProcessPaymentAsync(unifiedInput);
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 转换结果
|
|
|
|
|
|
var result = new UserPaymentResultOutput
|
2026-03-25 11:24:52 +08:00
|
|
|
|
{
|
2026-04-13 00:40:44 +08:00
|
|
|
|
OrderNo = unifiedResult.OrderNo,
|
|
|
|
|
|
Status = unifiedResult.PaymentStatus,
|
|
|
|
|
|
IsFree = unifiedResult.IsFree,
|
|
|
|
|
|
PayUrl = unifiedResult.QrcodeUrl ?? string.Empty,
|
|
|
|
|
|
QrcodeUrl = unifiedResult.QrcodeUrl,
|
2026-04-13 13:23:00 +08:00
|
|
|
|
WechatPayParams = unifiedResult.WechatPayParams,
|
2026-04-13 00:40:44 +08:00
|
|
|
|
TradeType = unifiedResult.TradeType
|
|
|
|
|
|
};
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-04-13 13:23:00 +08:00
|
|
|
|
// 如果是线下支付,获取收款商户信息
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 11:24:52 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 16:17:39 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 确认银行转账
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("确认银行转账")]
|
|
|
|
|
|
[HttpPost("bank-transfer/confirm")]
|
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
|
public async Task<ConfirmBankTransferOutput> ConfirmBankTransfer([FromBody] ConfirmBankTransferInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = _authManager.UserId;
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("用户确认银行转账,报名ID:{RegistrationId},用户ID:{UserId}", input.RegistrationId, userId);
|
|
|
|
|
|
|
|
|
|
|
|
// 调用统一报名服务处理确认转账
|
|
|
|
|
|
var result = await _unifiedRegistrationService.ConfirmBankTransferAsync(input, userId);
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 11:24:52 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 微信支付回调
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("微信支付回调")]
|
|
|
|
|
|
[HttpPost("wechat/notify")]
|
|
|
|
|
|
[AllowAnonymous]
|
2026-04-09 23:12:51 +08:00
|
|
|
|
[UnitOfWork]
|
2026-03-25 11:24:52 +08:00
|
|
|
|
[NonUnify]
|
|
|
|
|
|
public async Task<IActionResult> WechatPayNotify()
|
|
|
|
|
|
{
|
2026-04-09 23:12:51 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("========== 收到微信支付回调请求 ==========");
|
|
|
|
|
|
|
2026-04-09 23:12:51 +08:00
|
|
|
|
using var reader = new StreamReader(_httpContextAccessor.HttpContext.Request.Body);
|
|
|
|
|
|
var body = await reader.ReadToEndAsync();
|
|
|
|
|
|
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("微信支付回调 - 请求Body: {Body}", body);
|
|
|
|
|
|
|
2026-04-09 23:12:51 +08:00
|
|
|
|
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();
|
|
|
|
|
|
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("微信支付回调 - 头部参数: Timestamp={Timestamp}, Nonce={Nonce}, SerialNumber={SerialNumber}",
|
|
|
|
|
|
timestamp, nonce, serialNumber);
|
|
|
|
|
|
|
2026-04-09 23:12:51 +08:00
|
|
|
|
if (string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) ||
|
|
|
|
|
|
string.IsNullOrEmpty(signature) || string.IsNullOrEmpty(serialNumber))
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("微信支付回调通知参数不完整");
|
|
|
|
|
|
return new JsonResult(new { code = "FAIL", message = "参数不完整" });
|
|
|
|
|
|
}
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
var platformPayConfig = await _unifiedRegistrationService.GetPlatformWeChatPayConfigAsync();
|
2026-04-09 23:12:51 +08:00
|
|
|
|
if (platformPayConfig == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError("未找到平台级别的微信支付配置");
|
|
|
|
|
|
return new JsonResult(new { code = "FAIL", message = "支付配置未找到" });
|
|
|
|
|
|
}
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-04-09 23:12:51 +08:00
|
|
|
|
var notifyRequest = new WeChatPayNotifyRequest
|
|
|
|
|
|
{
|
|
|
|
|
|
Timestamp = timestamp,
|
|
|
|
|
|
Nonce = nonce,
|
|
|
|
|
|
Signature = signature,
|
|
|
|
|
|
SerialNumber = serialNumber,
|
|
|
|
|
|
Body = body
|
|
|
|
|
|
};
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("微信支付回调 - 开始验证签名");
|
2026-04-09 23:12:51 +08:00
|
|
|
|
var isValid = await _weChatPayService.VerifyPayNotifyAsync(platformPayConfig, notifyRequest);
|
|
|
|
|
|
if (!isValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("微信支付回调签名验证失败");
|
|
|
|
|
|
return new JsonResult(new { code = "FAIL", message = "签名验证失败" });
|
|
|
|
|
|
}
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("微信支付回调 - 签名验证成功");
|
2026-03-25 23:58:30 +08:00
|
|
|
|
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("微信支付回调 - 开始解密数据");
|
2026-04-09 23:12:51 +08:00
|
|
|
|
var notifyData = await _weChatPayService.DecryptPayNotifyDataAsync(platformPayConfig, notifyRequest);
|
2026-04-13 18:14:32 +08:00
|
|
|
|
|
|
|
|
|
|
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")
|
2026-04-09 23:12:51 +08:00
|
|
|
|
{
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogWarning("微信支付回调交易未成功,TradeState={TradeState}", notifyData.TradeState);
|
2026-04-09 23:12:51 +08:00
|
|
|
|
return new JsonResult(new { code = "SUCCESS", message = "成功" });
|
|
|
|
|
|
}
|
2026-03-25 23:58:30 +08:00
|
|
|
|
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("微信支付回调 - 开始处理支付成功逻辑,订单号:{OutTradeNo}", notifyData.OutTradeNo);
|
|
|
|
|
|
|
2026-04-13 00:40:44 +08:00
|
|
|
|
// 调用统一支付成功处理方法
|
|
|
|
|
|
await _unifiedRegistrationService.HandlePaymentSuccessAsync(
|
|
|
|
|
|
notifyData.OutTradeNo,
|
|
|
|
|
|
notifyData.TransactionId,
|
|
|
|
|
|
notifyData.SuccessTime?.DateTime,
|
|
|
|
|
|
0);
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("微信支付回调 - 支付成功处理完成,订单号:{OutTradeNo}", notifyData.OutTradeNo);
|
|
|
|
|
|
_logger.LogInformation("========== 微信支付回调处理完成 ==========");
|
|
|
|
|
|
|
2026-04-09 23:12:51 +08:00
|
|
|
|
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 = "处理失败" });
|
|
|
|
|
|
}
|
2026-03-25 11:24:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查询订单支付状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("查询订单支付状态")]
|
|
|
|
|
|
[HttpGet("order/status/{orderNo}")]
|
|
|
|
|
|
public async Task<int> QueryOrderStatus([FromRoute] string orderNo)
|
|
|
|
|
|
{
|
2026-04-10 14:29:41 +08:00
|
|
|
|
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("订单不存在");
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-04-10 14:29:41 +08:00
|
|
|
|
if (orderInfo.Order.OrderStatus == OrderStatus.Paid)
|
2026-04-13 18:14:32 +08:00
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation("查询订单支付状态 - 订单已支付,订单号:{OrderNo}", orderNo);
|
2026-03-25 11:24:52 +08:00
|
|
|
|
return 1; // 已支付
|
2026-04-13 18:14:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是微信/支付宝支付(线上支付)
|
|
|
|
|
|
bool isOnlinePayment = orderInfo.Registration?.PaymentMethod == 2 ||
|
|
|
|
|
|
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.WeChatPay ||
|
|
|
|
|
|
orderInfo.Registration?.PaymentMethodType == PaymentMethodType.AliPay;
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("查询订单支付状态 - 订单号:{OrderNo},PaymentMethod={PaymentMethod},PaymentMethodType={PaymentMethodType},IsOnlinePayment={IsOnlinePayment}",
|
|
|
|
|
|
orderNo, orderInfo.Registration?.PaymentMethod, orderInfo.Registration?.PaymentMethodType, isOnlinePayment);
|
|
|
|
|
|
|
|
|
|
|
|
if (isOnlinePayment)
|
2026-03-25 11:24:52 +08:00
|
|
|
|
{
|
2026-03-25 23:58:30 +08:00
|
|
|
|
WeChatPayConfig? payConfig = null;
|
2026-04-10 14:29:41 +08:00
|
|
|
|
if (orderInfo.Order.MerchantId.HasValue)
|
2026-03-25 11:24:52 +08:00
|
|
|
|
{
|
2026-03-25 23:58:30 +08:00
|
|
|
|
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
2026-04-10 14:29:41 +08:00
|
|
|
|
x.MerchantId == orderInfo.Order.MerchantId.Value &&
|
2026-03-25 23:58:30 +08:00
|
|
|
|
x.Status == AvailabilityStatus.Enable &&
|
|
|
|
|
|
!x.DeleteMark);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (payConfig == null)
|
2026-03-25 11:24:52 +08:00
|
|
|
|
{
|
2026-03-25 23:58:30 +08:00
|
|
|
|
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
|
|
|
|
|
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
|
|
|
|
|
|
}
|
2026-03-25 11:24:52 +08:00
|
|
|
|
|
2026-03-25 23:58:30 +08:00
|
|
|
|
if (payConfig != null)
|
|
|
|
|
|
{
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("查询订单支付状态 - 调用微信支付查询接口,订单号:{OrderNo}", orderNo);
|
2026-03-25 23:58:30 +08:00
|
|
|
|
var queryResult = await _weChatPayService.QueryOrderAsync(payConfig, orderNo);
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("查询订单支付状态 - 微信支付查询结果,订单号:{OrderNo},Success={Success},TradeState={TradeState}",
|
|
|
|
|
|
orderNo, queryResult.Success, queryResult.TradeState);
|
|
|
|
|
|
|
2026-03-25 23:58:30 +08:00
|
|
|
|
if (queryResult.Success && queryResult.TradeState == "SUCCESS")
|
2026-03-25 11:24:52 +08:00
|
|
|
|
{
|
2026-04-10 14:29:41 +08:00
|
|
|
|
orderInfo.Order.OrderStatus = OrderStatus.Paid;
|
|
|
|
|
|
orderInfo.Order.PaymentTime = queryResult.SuccessTime?.DateTime;
|
2026-03-25 23:58:30 +08:00
|
|
|
|
if (queryResult.PayerAmount.HasValue)
|
|
|
|
|
|
{
|
2026-04-10 14:29:41 +08:00
|
|
|
|
orderInfo.Order.ActualAmount = queryResult.PayerAmount.Value / 100m;
|
2026-03-25 23:58:30 +08:00
|
|
|
|
}
|
2026-04-10 14:29:41 +08:00
|
|
|
|
await _orderRepository.UpdateAsync(orderInfo.Order);
|
2026-03-25 23:58:30 +08:00
|
|
|
|
|
2026-04-10 14:29:41 +08:00
|
|
|
|
var registration = await _registrationRepository.GetByIdAsync(orderInfo.Order.RegistrationId);
|
2026-03-25 23:58:30 +08:00
|
|
|
|
if (registration != null && registration.PaymentStatus != 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
registration.PaymentStatus = 1;
|
|
|
|
|
|
await _registrationRepository.UpdateAsync(registration);
|
|
|
|
|
|
}
|
2026-04-13 18:14:32 +08:00
|
|
|
|
_logger.LogInformation("查询订单支付状态 - 订单已更新为已支付,订单号:{OrderNo}", orderNo);
|
2026-03-25 23:58:30 +08:00
|
|
|
|
return 1;
|
2026-03-25 11:24:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-13 18:14:32 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("查询订单支付状态 - 未找到微信支付配置,订单号:{OrderNo}", orderNo);
|
|
|
|
|
|
}
|
2026-03-25 11:24:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 0; // 未支付
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-01 13:04:41 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 验证优惠券
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("验证优惠券")]
|
|
|
|
|
|
[HttpPost("coupon/validate")]
|
|
|
|
|
|
public async Task<ValidateCouponOutput> ValidateCoupon([FromBody] ValidateCouponInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userId = _authManager.UserId;
|
|
|
|
|
|
|
2026-04-13 15:28:25 +08:00
|
|
|
|
return await _unifiedRegistrationService.ValidateAndUseCouponAsync(input, userId);
|
2026-04-01 13:04:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 23:12:51 +08:00
|
|
|
|
private string GetPaymentStatusDesc(int status)
|
2026-03-20 03:15:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
return status switch
|
|
|
|
|
|
{
|
2026-04-09 23:12:51 +08:00
|
|
|
|
0 => "待审核",
|
|
|
|
|
|
1 => "已支付",
|
|
|
|
|
|
2 => "支付失败",
|
|
|
|
|
|
3 => "已退款",
|
2026-03-20 03:15:31 +08:00
|
|
|
|
4 => "已取消",
|
|
|
|
|
|
_ => "未知"
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-04-02 08:40:47 +08:00
|
|
|
|
|
|
|
|
|
|
/// <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)
|
|
|
|
|
|
{
|
2026-04-13 13:23:00 +08:00
|
|
|
|
// 获取项目类型信息
|
|
|
|
|
|
var eventProject = await _eventProjectRepository.GetByIdAsync(eventProjectGroup.EventProjectId);
|
|
|
|
|
|
var baseProject = eventProject != null ? await _projectRepository.GetByIdAsync(eventProject.ProjectId) : null;
|
|
|
|
|
|
var isTeamProject = baseProject?.IsTeamProject() ?? false;
|
|
|
|
|
|
|
2026-04-13 05:20:09 +08:00
|
|
|
|
// 创建单个队员的验证输入
|
|
|
|
|
|
var persons = new List<UnifiedPersonInput>
|
2026-04-02 08:40:47 +08:00
|
|
|
|
{
|
2026-04-13 05:20:09 +08:00
|
|
|
|
new UnifiedPersonInput
|
|
|
|
|
|
{
|
|
|
|
|
|
UserId = member.Id,
|
|
|
|
|
|
Name = member.Name ?? "",
|
|
|
|
|
|
IdCardNumber = member.IdCardNumber ?? "",
|
|
|
|
|
|
Gender = member.Gender,
|
|
|
|
|
|
Mobile = member.Mobile,
|
|
|
|
|
|
BirthDate = member.BirthDate.Value
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-04-02 08:40:47 +08:00
|
|
|
|
|
2026-04-13 05:20:09 +08:00
|
|
|
|
// 使用统一的验证方法进行验证
|
2026-04-13 13:23:00 +08:00
|
|
|
|
var validationResult = _unifiedRegistrationService.ValidatePersonQualifications(persons, eventProjectGroup, isTeamProject);
|
2026-04-13 05:20:09 +08:00
|
|
|
|
|
|
|
|
|
|
if (!validationResult.IsValid)
|
2026-04-05 20:58:17 +08:00
|
|
|
|
{
|
|
|
|
|
|
result.IsValid = false;
|
2026-04-13 05:20:09 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-04-02 08:40:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查报名限制
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("检查报名限制")]
|
|
|
|
|
|
[HttpPost("registration-limit/check")]
|
|
|
|
|
|
public async Task<RegistrationLimitCheckOutput> CheckRegistrationLimit([FromBody] RegistrationLimitCheckInput input)
|
|
|
|
|
|
{
|
2026-04-13 13:23:00 +08:00
|
|
|
|
var userId = _authManager.UserId;
|
2026-04-07 09:04:38 +08:00
|
|
|
|
|
2026-04-13 13:23:00 +08:00
|
|
|
|
// 获取当前用户信息
|
|
|
|
|
|
var sysUser = await _sysUserRepository.GetByIdAsync(userId);
|
|
|
|
|
|
if (sysUser == null) throw Oops.Bah("用户不存在");
|
|
|
|
|
|
string userName = sysUser.Name;
|
|
|
|
|
|
string userMobile = sysUser.Mobile ?? "";
|
2026-04-07 09:04:38 +08:00
|
|
|
|
|
2026-04-20 13:01:39 +08:00
|
|
|
|
// 获取团队信息 - 优先使用新结构的 TeamId
|
2026-04-13 13:23:00 +08:00
|
|
|
|
string teamName = "临时团队";
|
|
|
|
|
|
if (input.TeamId.HasValue)
|
2026-04-07 09:04:38 +08:00
|
|
|
|
{
|
2026-04-13 13:23:00 +08:00
|
|
|
|
var team = await _db.Queryable<YD_Event.Core.Entities.Team>().FirstAsync(t => t.Id == input.TeamId.Value && !t.DeleteMark);
|
|
|
|
|
|
if (team != null)
|
2026-04-07 09:04:38 +08:00
|
|
|
|
{
|
2026-04-13 13:23:00 +08:00
|
|
|
|
teamName = team.Name;
|
2026-04-07 09:04:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-13 13:23:00 +08:00
|
|
|
|
|
2026-04-20 13:01:39 +08:00
|
|
|
|
// 获取所有成员信息
|
|
|
|
|
|
var allMemberIds = new List<long>();
|
|
|
|
|
|
if (input.GroupMembers != null)
|
2026-04-02 08:40:47 +08:00
|
|
|
|
{
|
2026-04-20 13:01:39 +08:00
|
|
|
|
foreach (var gm in input.GroupMembers)
|
2026-04-02 08:40:47 +08:00
|
|
|
|
{
|
2026-04-20 13:01:39 +08:00
|
|
|
|
// 检查是否有多团队数据
|
|
|
|
|
|
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>());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-02 08:40:47 +08:00
|
|
|
|
}
|
2026-04-20 13:01:39 +08:00
|
|
|
|
else if (input.MemberIds != null)
|
2026-04-13 13:23:00 +08:00
|
|
|
|
{
|
2026-04-20 13:01:39 +08:00
|
|
|
|
// 向后兼容旧结构
|
|
|
|
|
|
allMemberIds.AddRange(input.MemberIds);
|
2026-04-13 13:23:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 13:01:39 +08:00
|
|
|
|
allMemberIds = allMemberIds.Distinct().ToList();
|
2026-04-13 13:23:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取成员信息
|
|
|
|
|
|
var members = await _userRepository.GetListAsync(x => allMemberIds.Contains(x.Id));
|
|
|
|
|
|
|
|
|
|
|
|
// 获取组别信息
|
2026-04-20 13:01:39 +08:00
|
|
|
|
var groupIds = input.GroupMembers?.Select(g => g.GroupId).ToList() ?? input.GroupIds ?? new List<long>();
|
2026-04-13 13:23:00 +08:00
|
|
|
|
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>()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个组别添加项目项
|
2026-04-20 13:01:39 +08:00
|
|
|
|
foreach (var groupMember in input.GroupMembers ?? new List<GroupMemberAssignment>())
|
2026-04-13 13:23:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-04-20 13:01:39 +08:00
|
|
|
|
var item = new UnifiedRegistrationItemInput
|
2026-04-13 13:23:00 +08:00
|
|
|
|
{
|
|
|
|
|
|
ProjectId = eventProject.Id,
|
|
|
|
|
|
GroupId = groupMember.GroupId,
|
|
|
|
|
|
GroupName = eventProjectGroup.GroupName,
|
2026-04-20 13:01:39 +08:00
|
|
|
|
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()
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-04-13 13:23:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用统一报名服务检查限制
|
|
|
|
|
|
return await _unifiedRegistrationService.CheckRegistrationLimitAsync(unifiedInput);
|
2026-04-02 08:40:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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);
|
2026-04-07 09:04:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取该赛事的项目级限报配置
|
|
|
|
|
|
var projectLimits = await _projectLimitRepository.GetListAsync(x => x.EventId == eventId && !x.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有赛事项目,用于关联 ProjectId
|
|
|
|
|
|
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId);
|
2026-04-02 08:40:47 +08:00
|
|
|
|
|
|
|
|
|
|
foreach (var eventProjectGroup in eventProjectGroups)
|
|
|
|
|
|
{
|
|
|
|
|
|
var currentTeams = await _registrationItemRepository.AsQueryable()
|
|
|
|
|
|
.Where(x => x.EventProjectGroupId == eventProjectGroup.Id && !x.DeleteMark)
|
|
|
|
|
|
.Select(x => x.RegistrationId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.CountAsync();
|
|
|
|
|
|
|
2026-04-07 09:04:38 +08:00
|
|
|
|
// 查找该组别的项目限报配置
|
|
|
|
|
|
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
|
2026-04-13 05:20:09 +08:00
|
|
|
|
var projectLimit = projectLimits.FirstOrDefault(pl => pl.ProjectId == eventProject?.Id);
|
2026-04-07 09:04:38 +08:00
|
|
|
|
var maxTeams = projectLimit?.LimitCount ?? 2; // 默认2队
|
|
|
|
|
|
|
2026-04-02 08:40:47 +08:00
|
|
|
|
result.Add(new GroupTeamLimitOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
GroupId = eventProjectGroup.Id,
|
2026-04-07 09:04:38 +08:00
|
|
|
|
MaxTeams = maxTeams,
|
2026-04-02 08:40:47 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-15 09:58:05 +08:00
|
|
|
|
// 判断是否为团队项目
|
|
|
|
|
|
var isTeamProject = project.IsTeamProject();
|
|
|
|
|
|
|
2026-04-16 11:08:23 +08:00
|
|
|
|
// 对每个分配的组别进行独立验证
|
|
|
|
|
|
foreach (var assignedGroup in assignedProjectGroups)
|
2026-04-02 08:40:47 +08:00
|
|
|
|
{
|
2026-04-16 11:08:23 +08:00
|
|
|
|
// 找到该组别的队员分配
|
|
|
|
|
|
var groupMemberAssignment = input.GroupMembers.FirstOrDefault(gm => gm.GroupId == assignedGroup.Id);
|
|
|
|
|
|
if (groupMemberAssignment == null) continue;
|
|
|
|
|
|
|
2026-04-20 13:01:39 +08:00
|
|
|
|
// ========== 关键修改:支持多团队模式 ==========
|
|
|
|
|
|
// 检查是多团队模式还是单团队模式
|
|
|
|
|
|
var hasMultipleTeams = groupMemberAssignment.Teams != null && groupMemberAssignment.Teams.Count > 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (hasMultipleTeams && isTeamProject)
|
2026-04-16 15:42:29 +08:00
|
|
|
|
{
|
2026-04-20 13:01:39 +08:00
|
|
|
|
// ========== 多团队模式验证 ==========
|
|
|
|
|
|
// 验证每个团队的人数是否等于 CompletionCount
|
|
|
|
|
|
foreach (var team in groupMemberAssignment.Teams)
|
2026-04-02 08:40:47 +08:00
|
|
|
|
{
|
2026-04-20 13:01:39 +08:00
|
|
|
|
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
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-04-16 11:08:23 +08:00
|
|
|
|
}
|
2026-04-20 13:01:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// ========== 单团队模式验证(向后兼容) ==========
|
|
|
|
|
|
// 计算该组别的分配人数
|
|
|
|
|
|
var assignedCount = groupMemberAssignment.MemberIds?.Distinct().Count() ?? 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果该组别没有选择任何人员,跳过验证
|
|
|
|
|
|
if (assignedCount == 0) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 只对团队项目进行完成人数验证
|
|
|
|
|
|
if (isTeamProject && project.CompletionCount > 0 && assignedCount != project.CompletionCount)
|
2026-04-02 08:40:47 +08:00
|
|
|
|
{
|
2026-04-20 13:01:39 +08:00
|
|
|
|
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
|
|
|
|
|
|
});
|
2026-04-16 11:08:23 +08:00
|
|
|
|
}
|
2026-04-02 08:40:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2026-04-06 09:21:47 +08:00
|
|
|
|
|
|
|
|
|
|
/// <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);
|
2026-04-06 10:50:57 +08:00
|
|
|
|
var freeResult = new FeeCalculationResult
|
|
|
|
|
|
{
|
|
|
|
|
|
ChargeMode = "免费",
|
|
|
|
|
|
TotalAmount = 0,
|
|
|
|
|
|
TotalPersonCount = input.ParticipantCount
|
|
|
|
|
|
};
|
|
|
|
|
|
freeResult.CalculationLogs.Add($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 未找到费用配置,默认免费");
|
|
|
|
|
|
return freeResult;
|
2026-04-06 09:21:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 10:50:57 +08:00
|
|
|
|
// 获取项目信息(用于单价项目模式)
|
2026-04-06 09:21:47 +08:00
|
|
|
|
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));
|
|
|
|
|
|
|
2026-04-06 10:50:57 +08:00
|
|
|
|
// 构建统一计算服务的输入参数
|
2026-04-10 10:54:47 +08:00
|
|
|
|
var pricingInput = new PricingInput
|
2026-04-06 09:21:47 +08:00
|
|
|
|
{
|
2026-04-06 10:50:57 +08:00
|
|
|
|
ChargeMode = feeConfig.ChargeMode,
|
2026-04-09 15:16:43 +08:00
|
|
|
|
ParticipantCount = input.ParticipantCount,
|
|
|
|
|
|
EventPrice = feeConfig.Price
|
2026-04-06 10:50:57 +08:00
|
|
|
|
};
|
2026-04-06 09:21:47 +08:00
|
|
|
|
|
2026-04-06 10:50:57 +08:00
|
|
|
|
// 添加项目信息
|
|
|
|
|
|
foreach (var eventProject in eventProjects)
|
2026-04-06 09:21:47 +08:00
|
|
|
|
{
|
2026-04-06 10:50:57 +08:00
|
|
|
|
var baseProject = baseProjects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
|
2026-04-10 10:54:47 +08:00
|
|
|
|
pricingInput.Projects.Add(new ProjectPricingInfo
|
2026-04-06 10:50:57 +08:00
|
|
|
|
{
|
|
|
|
|
|
ProjectId = eventProject.ProjectId,
|
|
|
|
|
|
ProjectName = baseProject?.Name ?? "未知项目",
|
2026-04-10 14:29:41 +08:00
|
|
|
|
UnitPrice = eventProject.Price
|
2026-04-06 10:50:57 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-04-06 09:21:47 +08:00
|
|
|
|
|
2026-04-06 10:50:57 +08:00
|
|
|
|
// 使用统一计算服务计算费用
|
|
|
|
|
|
var pricingResult = EventPricingService.Calculate(pricingInput);
|
2026-04-06 09:21:47 +08:00
|
|
|
|
|
2026-04-06 10:50:57 +08:00
|
|
|
|
// 转换为现有格式的结果
|
|
|
|
|
|
var result = new FeeCalculationResult
|
2026-04-06 09:21:47 +08:00
|
|
|
|
{
|
2026-04-06 10:50:57 +08:00
|
|
|
|
ChargeMode = GetChargeModeDescription(feeConfig.ChargeMode),
|
|
|
|
|
|
TotalAmount = pricingResult.TotalAmount,
|
|
|
|
|
|
TotalPersonCount = pricingResult.TotalPersonCount,
|
|
|
|
|
|
CalculationLogs = pricingResult.CalculationLogs
|
|
|
|
|
|
};
|
2026-04-06 09:21:47 +08:00
|
|
|
|
|
2026-04-06 10:50:57 +08:00
|
|
|
|
// 转换费用明细
|
|
|
|
|
|
foreach (var detail in pricingResult.Details)
|
2026-04-06 09:21:47 +08:00
|
|
|
|
{
|
|
|
|
|
|
result.FeeDetails.Add(new FeeDetailItem
|
|
|
|
|
|
{
|
2026-04-06 10:50:57 +08:00
|
|
|
|
ItemName = detail.ItemName,
|
|
|
|
|
|
UnitPrice = detail.UnitPrice,
|
|
|
|
|
|
Quantity = detail.Quantity,
|
|
|
|
|
|
Subtotal = detail.Subtotal,
|
|
|
|
|
|
Remark = detail.Remark
|
2026-04-06 09:21:47 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 10:50:57 +08:00
|
|
|
|
_logger.LogInformation("费用计算完成,总金额: {TotalAmount:F2} 元", result.TotalAmount);
|
2026-04-06 09:21:47 +08:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取收费模式描述
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private string GetChargeModeDescription(ChargeMode mode)
|
|
|
|
|
|
{
|
|
|
|
|
|
return mode switch
|
|
|
|
|
|
{
|
|
|
|
|
|
ChargeMode.Free => "免费",
|
|
|
|
|
|
ChargeMode.FixedPrice => "定价(赛事)",
|
|
|
|
|
|
ChargeMode.UnitPriceEvent => "单价(赛事)",
|
|
|
|
|
|
ChargeMode.UnitPriceProject => "单价(项目)",
|
|
|
|
|
|
_ => "未知模式"
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-15 09:58:05 +08:00
|
|
|
|
/// <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;
|
|
|
|
|
|
}
|
2026-04-17 13:51:27 +08:00
|
|
|
|
|
|
|
|
|
|
/// <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; }
|
|
|
|
|
|
}
|
2026-03-20 03:15:31 +08:00
|
|
|
|
}
|