1450 lines
59 KiB
C#
1450 lines
59 KiB
C#
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 SqlSugar;
|
||
using Mapster;
|
||
using YD_Event.Application.Auth;
|
||
using Furion.DynamicApiController;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using YD_Event.Application.File;
|
||
using Microsoft.AspNetCore.Http;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using System.ComponentModel;
|
||
using Furion.FriendlyException;
|
||
using Furion.DatabaseAccessor;
|
||
using Furion.DependencyInjection;
|
||
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<ProjectType> _projectTypeRepository;
|
||
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 ILogger<UserEventRegistrationService> _logger;
|
||
|
||
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<ProjectType> projectTypeRepository,
|
||
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,
|
||
ILogger<UserEventRegistrationService> logger)
|
||
{
|
||
_eventRepository = eventRepository;
|
||
_unitRepository = unitRepository;
|
||
_eventProjectRepository = eventProjectRepository;
|
||
_eventProjectGroupRepository = eventProjectGroupRepository;
|
||
_projectGroupRepository = projectGroupRepository;
|
||
_projectLimitRepository = projectLimitRepository;
|
||
_projectRepository = projectRepository;
|
||
_projectTypeRepository = projectTypeRepository;
|
||
_registrationRepository = registrationRepository;
|
||
_registrationItemRepository = registrationItemRepository;
|
||
_registrationPersonRepository = registrationPersonRepository;
|
||
_userRepository = userRepository;
|
||
_sysUserRepository = sysUserRepository;
|
||
_feeConfigRepository = feeConfigRepository;
|
||
_limitRepository = limitRepository;
|
||
_pointsRuleRepository = pointsRuleRepository;
|
||
_orderRepository = orderRepository;
|
||
_weChatPayConfigRepository = weChatPayConfigRepository;
|
||
_merchantRepository = merchantRepository;
|
||
_db = db;
|
||
_authManager = authManager;
|
||
_idGenerator = idGenerator;
|
||
_weChatPayService = weChatPayService;
|
||
_wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor;
|
||
_httpContextAccessor = httpContextAccessor;
|
||
_projectService = projectService;
|
||
_couponRepository = couponRepository;
|
||
_userCouponRepository = userCouponRepository;
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <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)
|
||
{
|
||
output.FeeConfig = new UserEventFeeOutput
|
||
{
|
||
ChargeMode = fee.ChargeMode.ToString(),
|
||
Price = fee.Price
|
||
};
|
||
}
|
||
|
||
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id);
|
||
var eventProjectIds = eventProjects.Select(x => x.Id).ToList();
|
||
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => eventProjectIds.Contains(x.EventProjectId));
|
||
var projectLimits = await _projectLimitRepository.GetListAsync(x => eventProjectIds.Contains(x.ProjectId));
|
||
|
||
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
||
var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable);
|
||
var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
|
||
|
||
// 获取所有项目类型
|
||
var typeIds = baseProjects.Select(x => x.ProjectTypeId).Distinct().ToList();
|
||
var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id));
|
||
|
||
var allProjectTypes = await _projectTypeRepository.AsQueryable()
|
||
.Where(t => !t.DeleteMark && t.Status == AvailabilityStatus.Enable)
|
||
.ToListAsync();
|
||
|
||
var completionStats = await _projectService.GetProjectStatisticsBatchAsync(
|
||
projectIds,
|
||
ProjectService.ProjectStatisticType.CompletionCount);
|
||
|
||
output.Projects = eventProjects.Select(ep =>
|
||
{
|
||
var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
|
||
var completionCount = 0;
|
||
if (p != null && completionStats.TryGetValue(p.Id, out var count))
|
||
{
|
||
completionCount = count;
|
||
}
|
||
|
||
var dto = new UserEventProjectListOutput
|
||
{
|
||
Id = ep.Id,
|
||
Name = p?.Name ?? "",
|
||
Fee = ep.Price / 100m,
|
||
CompletionCount = completionCount
|
||
};
|
||
|
||
if (p != null)
|
||
{
|
||
var type = types.FirstOrDefault(x => x.Id == p.ProjectTypeId);
|
||
if (type != null)
|
||
{
|
||
dto.ProjectTypeName = type.Name;
|
||
|
||
// 为每个项目单独设置上级项目类型名称
|
||
if (type.ParentId > 0)
|
||
{
|
||
var parentProjectType = allProjectTypes.FirstOrDefault(t => t.Id == type.ParentId);
|
||
dto.ParentProjectTypeName = parentProjectType?.Name;
|
||
}
|
||
}
|
||
}
|
||
|
||
var epGroups = eventProjectGroups.Where(g => g.EventProjectId == ep.Id).ToList();
|
||
dto.Groups = epGroups.Select(eg =>
|
||
{
|
||
var baseGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eg.ProjectGroupId);
|
||
return new UserEventProjectGroupOutput
|
||
{
|
||
Id = eg.Id,
|
||
GroupName = eg.GroupName ?? baseGroup?.GroupName,
|
||
BirthDateStart = eg.BirthDateStart != DateTime.MinValue ? eg.BirthDateStart : baseGroup?.BirthDateStart,
|
||
BirthDateEnd = eg.BirthDateEnd != DateTime.MinValue ? eg.BirthDateEnd : baseGroup?.BirthDateEnd,
|
||
Gender = (int)eg.Gender
|
||
};
|
||
}).ToList();
|
||
|
||
var projLimit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.Id);
|
||
if (projLimit != null) dto.LimitCount = projLimit.LimitCount;
|
||
|
||
return dto;
|
||
}).Where(x => !string.IsNullOrEmpty(x.Name)).ToList();
|
||
|
||
var userId = _authManager.UserId;
|
||
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == id && x.CreatedUserId == userId && !x.DeleteMark);
|
||
|
||
if (registration != null)
|
||
{
|
||
output.RegistrationId = registration.Id;
|
||
output.UserRegistrationStatus = GetRegistrationStatusDesc(registration.RegistrationStatus);
|
||
}
|
||
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));
|
||
|
||
var typeIds = baseProjects.Select(x => x.ProjectTypeId).Distinct().ToList();
|
||
var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id));
|
||
|
||
var allProjectTypes = await _projectTypeRepository.AsQueryable()
|
||
.Where(t => !t.DeleteMark && t.Status == AvailabilityStatus.Enable)
|
||
.ToListAsync();
|
||
|
||
var completionStats = await _projectService.GetProjectStatisticsBatchAsync(
|
||
projectIds,
|
||
ProjectService.ProjectStatisticType.CompletionCount);
|
||
|
||
return eventProjects.Select(ep =>
|
||
{
|
||
var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId);
|
||
var completionCount = 0;
|
||
if (p != null && completionStats.TryGetValue(p.Id, out var count))
|
||
{
|
||
completionCount = count;
|
||
}
|
||
|
||
var dto = new UserEventProjectListOutput
|
||
{
|
||
Id = ep.Id,
|
||
Name = p?.Name ?? "",
|
||
Fee = ep.Price / 100m,
|
||
TeamSize = 1,
|
||
CompletionCount = completionCount
|
||
};
|
||
|
||
if (p != null)
|
||
{
|
||
var type = types.FirstOrDefault(x => x.Id == p.ProjectTypeId);
|
||
if (type != null)
|
||
{
|
||
dto.ProjectTypeName = type.Name;
|
||
|
||
// 为每个项目单独设置上级项目类型名称
|
||
if (type.ParentId > 0)
|
||
{
|
||
var parentProjectType = allProjectTypes.FirstOrDefault(t => t.Id == type.ParentId);
|
||
dto.ParentProjectTypeName = parentProjectType?.Name;
|
||
}
|
||
}
|
||
}
|
||
|
||
var epGroups = eventProjectGroups.Where(g => g.EventProjectId == ep.Id).ToList();
|
||
dto.Groups = epGroups.Select(eg =>
|
||
{
|
||
var baseGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eg.ProjectGroupId);
|
||
return new UserEventProjectGroupOutput
|
||
{
|
||
Id = eg.Id,
|
||
GroupName = eg.GroupName ?? baseGroup?.GroupName,
|
||
BirthDateStart = eg.BirthDateStart != DateTime.MinValue ? eg.BirthDateStart : baseGroup?.BirthDateStart,
|
||
BirthDateEnd = eg.BirthDateEnd != DateTime.MinValue ? eg.BirthDateEnd : baseGroup?.BirthDateEnd,
|
||
Gender = (int)eg.Gender
|
||
};
|
||
}).ToList();
|
||
|
||
var projLimit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.Id);
|
||
if (projLimit != null) dto.LimitCount = projLimit.LimitCount;
|
||
|
||
return dto;
|
||
}).Where(x => !string.IsNullOrEmpty(x.Name)).ToList();
|
||
}
|
||
|
||
/// <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("赛事不存在");
|
||
|
||
// 验证1:一次报名只能选择一个项目
|
||
if (input.ProjectIds != null && input.ProjectIds.Count > 1)
|
||
{
|
||
throw Oops.Bah("一次报名只能选择一个项目");
|
||
}
|
||
|
||
// 验证2:一次报名只能选择单个团队内的运动员
|
||
if (input.MemberIds != null && input.MemberIds.Count > 0)
|
||
{
|
||
// 获取所有队员的团队关系
|
||
var teamMembers = await _db.Queryable<TeamMember>()
|
||
.Where(tm => input.MemberIds.Contains(tm.UserId))
|
||
.Where(tm => !tm.DeleteMark)
|
||
.ToListAsync();
|
||
|
||
if (teamMembers == null || teamMembers.Count == 0)
|
||
{
|
||
throw Oops.Bah("未找到队员信息");
|
||
}
|
||
|
||
// 按用户ID分组,获取每个用户所属的所有团队
|
||
var userTeamMap = teamMembers
|
||
.GroupBy(tm => tm.UserId)
|
||
.ToDictionary(g => g.Key, g => g.Select(tm => tm.TeamId).ToList());
|
||
|
||
// 检查是否所有用户都有团队信息
|
||
var missingUsers = input.MemberIds.Except(userTeamMap.Keys).ToList();
|
||
if (missingUsers.Any())
|
||
{
|
||
throw Oops.Bah("部分队员未找到团队信息");
|
||
}
|
||
|
||
// 验证所有队员是否都属于指定的 TeamId
|
||
var allBelongToTeam = userTeamMap.All(ut => ut.Value.Contains(input.TeamId));
|
||
if (!allBelongToTeam)
|
||
{
|
||
throw Oops.Bah("所有选择的运动员必须隶属于同一个团队");
|
||
}
|
||
}
|
||
|
||
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == input.EventId && x.CreatedUserId == userId && !x.DeleteMark);
|
||
|
||
if (registration == null)
|
||
{
|
||
registration = new EventRegistration
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
EventId = input.EventId,
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now,
|
||
Status = AvailabilityStatus.Enable,
|
||
RegistrationStatus = input.IsDraft ? 0 : 1, // 0-待提交(草稿), 1-待审核
|
||
ContactName = userName,
|
||
ContactPhone = userMobile,
|
||
OrderNo = "",
|
||
RegistrationMode = 1
|
||
};
|
||
await _registrationRepository.InsertAsync(registration);
|
||
}
|
||
else
|
||
{
|
||
registration.RegistrationStatus = input.IsDraft ? 0 : 1;
|
||
registration.ContactName = userName;
|
||
registration.ContactPhone = userMobile;
|
||
registration.UpdatedTime = DateTime.Now;
|
||
registration.UpdatedUserId = userId;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
}
|
||
|
||
await _registrationItemRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
|
||
await _registrationPersonRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
|
||
|
||
var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id));
|
||
|
||
var groupIds = input.GroupMembers?.Select(g => g.GroupId).Distinct().ToList() ?? new List<long>();
|
||
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
|
||
|
||
// 通过EventProjectGroup获取对应的EventProject
|
||
var eventProjectIds = eventProjectGroups.Select(x => x.EventProjectId).Distinct().ToList();
|
||
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
|
||
|
||
var baseProjectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
||
var baseProjects = await _projectRepository.GetListAsync(x => baseProjectIds.Contains(x.Id));
|
||
|
||
// 获取项目类型信息
|
||
var projectTypeIds = baseProjects.Select(x => x.ProjectTypeId).Distinct().ToList();
|
||
var projectTypes = await _projectTypeRepository.GetListAsync(x => projectTypeIds.Contains(x.Id));
|
||
|
||
var baseProjectGroupIds = eventProjectGroups.Select(x => x.ProjectGroupId).Distinct().ToList();
|
||
var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => baseProjectGroupIds.Contains(x.Id));
|
||
|
||
int totalPersonCount = 0;
|
||
FeeCalculationResult? feeCalculationResult = null;
|
||
|
||
if (input.GroupMembers != null && input.GroupMembers.Any())
|
||
{
|
||
foreach (var groupMember in input.GroupMembers)
|
||
{
|
||
var eventProjectGroup = eventProjectGroups.FirstOrDefault(g => g.Id == groupMember.GroupId);
|
||
if (eventProjectGroup == null) continue;
|
||
|
||
var eventProject = eventProjects.FirstOrDefault(p => p.Id == eventProjectGroup.EventProjectId);
|
||
if (eventProject == null) continue;
|
||
|
||
var baseProject = baseProjects.FirstOrDefault(x => x.Id == eventProject.ProjectId);
|
||
var baseProjectGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eventProjectGroup.ProjectGroupId);
|
||
var projectType = projectTypes.FirstOrDefault(pt => pt.Id == baseProject?.ProjectTypeId);
|
||
|
||
var groupMembers = members.Where(m => groupMember.MemberIds.Contains(m.Id)).ToList();
|
||
if (!groupMembers.Any()) continue;
|
||
|
||
var item = new EventRegistrationItem
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
RegistrationId = registration.Id,
|
||
ProjectId = eventProject.Id,
|
||
EventProjectGroupId = eventProjectGroup.Id,
|
||
ProjectName = baseProject?.Name ?? "",
|
||
GroupName = baseProjectGroup?.GroupName ?? "",
|
||
ProjectTypeName = projectType?.Name ?? "",
|
||
UnitPrice = eventProject.Price / 100m,
|
||
PersonCount = groupMembers.Count(),
|
||
TotalPrice = (eventProject.Price / 100m) * groupMembers.Count(),
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
await _registrationItemRepository.InsertAsync(item);
|
||
|
||
totalPersonCount += item.PersonCount;
|
||
|
||
foreach (var member in groupMembers)
|
||
{
|
||
var person = new EventRegistrationPerson
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
RegistrationId = registration.Id,
|
||
RegistrationItemId = item.Id,
|
||
ProjectId = eventProject.Id,
|
||
EventProjectGroupId = eventProjectGroup.Id,
|
||
GroupName = baseProjectGroup?.GroupName ?? "",
|
||
ProjectName = baseProject?.Name ?? "",
|
||
Name = member.Name,
|
||
Gender = member.Gender,
|
||
IdCard = member.IdCardNumber,
|
||
Phone = member.Mobile,
|
||
Age = member.Age ?? 0,
|
||
BirthDate = member.BirthDate ?? DateTime.MinValue,
|
||
PersonType = PersonType.Athlete,
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
await _registrationPersonRepository.InsertAsync(person);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
foreach (var eventProject in eventProjects)
|
||
{
|
||
var baseProject = baseProjects.FirstOrDefault(x => x.Id == eventProject.ProjectId);
|
||
var projectType = projectTypes.FirstOrDefault(pt => pt.Id == baseProject?.ProjectTypeId);
|
||
var item = new EventRegistrationItem
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
RegistrationId = registration.Id,
|
||
ProjectId = eventProject.Id,
|
||
ProjectName = baseProject?.Name ?? "",
|
||
ProjectTypeName = projectType?.Name ?? "",
|
||
UnitPrice = eventProject.Price / 100m,
|
||
PersonCount = members.Count(),
|
||
TotalPrice = (eventProject.Price / 100m) * members.Count(),
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
await _registrationItemRepository.InsertAsync(item);
|
||
|
||
totalPersonCount += item.PersonCount;
|
||
|
||
foreach (var member in members)
|
||
{
|
||
var person = new EventRegistrationPerson
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
RegistrationId = registration.Id,
|
||
RegistrationItemId = item.Id,
|
||
ProjectId = eventProject.Id,
|
||
GroupName = baseProject?.Name ?? "",
|
||
ProjectName = baseProject?.Name ?? "",
|
||
Name = member.Name,
|
||
Gender = member.Gender,
|
||
IdCard = member.IdCardNumber,
|
||
Phone = member.Mobile,
|
||
Age = member.Age ?? 0,
|
||
BirthDate = member.BirthDate ?? DateTime.MinValue,
|
||
PersonType = PersonType.Athlete,
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
await _registrationPersonRepository.InsertAsync(person);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (totalPersonCount > 0)
|
||
{
|
||
var feeInput = new FeeCalculationInput
|
||
{
|
||
EventId = input.EventId,
|
||
EventProjectIds = eventProjectIds,
|
||
ParticipantCount = totalPersonCount
|
||
};
|
||
feeCalculationResult = await CalculateFeeAsync(feeInput);
|
||
registration.TotalAmount = feeCalculationResult.TotalAmount;
|
||
}
|
||
|
||
registration.TotalPersonCount = totalPersonCount;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
|
||
return registration.Id;
|
||
}
|
||
|
||
/// <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 registration = await _registrationRepository.GetByIdAsync(input.RegistrationId);
|
||
if (registration == null) throw Oops.Bah("报名信息不存在");
|
||
|
||
var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId);
|
||
if (eventInfo == null) throw Oops.Bah("赛事信息不存在");
|
||
|
||
var result = new UserPaymentResultOutput();
|
||
|
||
// 计算最终支付金额
|
||
decimal originalAmount = registration.TotalAmount;
|
||
decimal finalAmount = originalAmount;
|
||
decimal discountAmount = 0;
|
||
YD_Event.Core.Entities.Coupon? coupon = null;
|
||
YD_Event.Core.Entities.UserCoupon? userCoupon = null;
|
||
|
||
// 处理优惠券
|
||
if (input.CouponId.HasValue)
|
||
{
|
||
var userId = _authManager.UserId;
|
||
coupon = await _couponRepository.GetFirstAsync(c =>
|
||
c.Id == input.CouponId.Value &&
|
||
c.Status == AvailabilityStatus.Enable &&
|
||
!c.DeleteMark);
|
||
|
||
if (coupon != null)
|
||
{
|
||
userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
|
||
uc.CouponId == coupon.Id &&
|
||
uc.UserId == userId &&
|
||
uc.Status == 0 && // 0-未使用
|
||
!uc.DeleteMark);
|
||
|
||
if (userCoupon != null)
|
||
{
|
||
// 计算折扣金额
|
||
if (coupon.Type == 0) // 折扣
|
||
{
|
||
discountAmount = originalAmount * (1 - coupon.Value / 100);
|
||
}
|
||
else if (coupon.Type == 1) // 定额
|
||
{
|
||
discountAmount = Math.Min(coupon.Value, originalAmount);
|
||
}
|
||
|
||
finalAmount = originalAmount - discountAmount;
|
||
finalAmount = Math.Max(0, finalAmount);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果最终金额为0,直接完成报名,无需支付
|
||
if (finalAmount <= 0)
|
||
{
|
||
// 如果使用了优惠券,标记为已使用
|
||
if (userCoupon != null)
|
||
{
|
||
userCoupon.Status = 1; // 1-已使用
|
||
userCoupon.UseTime = DateTime.Now;
|
||
await _userCouponRepository.UpdateAsync(userCoupon);
|
||
}
|
||
|
||
registration.PaymentStatus = 1; // 已支付
|
||
registration.RegistrationStatus = 1; // 待审核
|
||
registration.PaymentTime = DateTime.Now;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
|
||
result.Status = 1;
|
||
result.IsFree = true;
|
||
return result;
|
||
}
|
||
|
||
// 获取当前用户的单位认证ID
|
||
var certification = await _db.Queryable<MerchantCertification>()
|
||
.FirstAsync(c => c.UserId == registration.CreatedUserId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
|
||
|
||
var orderNo = _idGenerator.NewLong().ToString();
|
||
registration.OrderNo = orderNo;
|
||
registration.PaymentMethod = input.PaymentMethod;
|
||
registration.PaymentStatus = 0; // 未支付
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
|
||
// 创建内部订单记录
|
||
var order = new YD_Event.Core.Entities.Order
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
OrderNo = orderNo,
|
||
OrderType = 1, // Income
|
||
PaymentMethod = input.PaymentMethod,
|
||
RegistrationMode = registration.RegistrationMode,
|
||
MerchantId = eventInfo.MerchantId,
|
||
MerchantCertificationId = certification?.Id, // 设置单位认证ID
|
||
EventId = registration.EventId,
|
||
RegistrationId = registration.Id,
|
||
OriginalAmount = originalAmount,
|
||
ActualAmount = finalAmount,
|
||
DiscountAmount = discountAmount,
|
||
CouponId = input.CouponId,
|
||
OrderStatus = OrderStatus.Pending,
|
||
CreatedTime = DateTime.Now,
|
||
CreatedUserId = registration.CreatedUserId
|
||
};
|
||
await _orderRepository.InsertAsync(order);
|
||
|
||
result.OrderNo = orderNo;
|
||
result.Status = 0;
|
||
result.IsFree = false;
|
||
|
||
// 如果是微信支付 (假设 1 是微信支付)
|
||
if (input.PaymentMethod == 1)
|
||
{
|
||
WeChatPayConfig? payConfig = null;
|
||
if (eventInfo.MerchantId.HasValue)
|
||
{
|
||
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
||
x.MerchantId == eventInfo.MerchantId.Value &&
|
||
x.Status == AvailabilityStatus.Enable &&
|
||
!x.DeleteMark);
|
||
}
|
||
|
||
if (payConfig == null)
|
||
{
|
||
throw Oops.Bah("商户微信支付配置不存在");
|
||
}
|
||
|
||
var configOptions = _wechatTenpayClientOptionsMonitor.CurrentValue;
|
||
var nativeRequest = new CreateNativePayRequest
|
||
{
|
||
OutTradeNo = orderNo,
|
||
AppId = configOptions.AppId,
|
||
Description = $"{eventInfo.EventName} - 报名费",
|
||
NotifyUrl = configOptions.NotifyUrl,
|
||
TotalAmount = (int)(finalAmount * 100), // 单位为分,使用优惠后的金额
|
||
ExpireTime = DateTimeOffset.Now.AddMinutes(5)
|
||
};
|
||
|
||
var payResult = await _weChatPayService.CreateNativePayAsync(payConfig, nativeRequest);
|
||
if (payResult.Success)
|
||
{
|
||
result.PayUrl = payResult.QrcodeUrl ?? string.Empty;
|
||
result.QrcodeUrl = payResult.QrcodeUrl;
|
||
result.TradeType = payResult.TradeType;
|
||
}
|
||
else
|
||
{
|
||
throw Oops.Bah($"微信统一下单失败:{payResult.Message}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
result.PayUrl = "http://mock-pay-url.com/" + orderNo;
|
||
result.TradeType = "MOCK";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 微信支付回调
|
||
/// </summary>
|
||
[DisplayName("微信支付回调")]
|
||
[HttpPost("wechat/notify")]
|
||
[AllowAnonymous]
|
||
[NonUnify]
|
||
public async Task<IActionResult> WechatPayNotify()
|
||
{
|
||
var request = _httpContextAccessor.HttpContext.Request;
|
||
using var reader = new StreamReader(request.Body);
|
||
var body = await reader.ReadToEndAsync();
|
||
|
||
var signature = request.Headers["Wechatpay-Signature"].ToString();
|
||
var timestamp = request.Headers["Wechatpay-Timestamp"].ToString();
|
||
var nonce = request.Headers["Wechatpay-Nonce"].ToString();
|
||
var serialNumber = request.Headers["Wechatpay-Serial"].ToString();
|
||
|
||
var notifyRequest = new WeChatPayNotifyRequest
|
||
{
|
||
Body = body,
|
||
Signature = signature,
|
||
Timestamp = timestamp,
|
||
Nonce = nonce,
|
||
SerialNumber = serialNumber
|
||
};
|
||
|
||
var payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
||
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
|
||
|
||
if (payConfig == null)
|
||
{
|
||
return new BadRequestObjectResult(new { code = "FAIL", message = "支付配置不存在" });
|
||
}
|
||
|
||
bool valid = await _weChatPayService.VerifyPayNotifyAsync(payConfig, notifyRequest);
|
||
if (!valid)
|
||
{
|
||
return new BadRequestResult();
|
||
}
|
||
|
||
var notifyData = await _weChatPayService.DecryptPayNotifyDataAsync(payConfig, notifyRequest);
|
||
if (notifyData != null && notifyData.EventType == "TRANSACTION.SUCCESS")
|
||
{
|
||
var orderNo = notifyData.OutTradeNo;
|
||
var transactionId = notifyData.TransactionId;
|
||
|
||
var order = await _orderRepository.GetFirstAsync(x => x.OrderNo == orderNo);
|
||
if (order != null && order.OrderStatus == OrderStatus.Pending)
|
||
{
|
||
order.OrderStatus = OrderStatus.Paid;
|
||
order.PaymentTime = DateTime.Now;
|
||
if (notifyData.PayerAmount.HasValue)
|
||
{
|
||
order.ActualAmount = notifyData.PayerAmount.Value / 100m;
|
||
}
|
||
order.Remark = $"微信支付单号: {transactionId}";
|
||
await _orderRepository.UpdateAsync(order);
|
||
|
||
var registration = await _registrationRepository.GetByIdAsync(order.RegistrationId);
|
||
if (registration != null && registration.PaymentStatus != 1)
|
||
{
|
||
registration.PaymentStatus = 1;
|
||
registration.RegistrationStatus = 1;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
}
|
||
}
|
||
}
|
||
|
||
return new OkObjectResult(new { code = "SUCCESS", message = "成功" });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询订单支付状态
|
||
/// </summary>
|
||
[DisplayName("查询订单支付状态")]
|
||
[HttpGet("order/status/{orderNo}")]
|
||
public async Task<int> QueryOrderStatus([FromRoute] string orderNo)
|
||
{
|
||
var order = await _orderRepository.GetFirstAsync(x => x.OrderNo == orderNo);
|
||
if (order == null) throw Oops.Bah("订单不存在");
|
||
|
||
if (order.OrderStatus == OrderStatus.Paid)
|
||
return 1; // 已支付
|
||
|
||
if (order.PaymentMethod == 1)
|
||
{
|
||
WeChatPayConfig? payConfig = null;
|
||
if (order.MerchantId.HasValue)
|
||
{
|
||
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
||
x.MerchantId == order.MerchantId.Value &&
|
||
x.Status == AvailabilityStatus.Enable &&
|
||
!x.DeleteMark);
|
||
}
|
||
|
||
if (payConfig == null)
|
||
{
|
||
payConfig = await _weChatPayConfigRepository.GetFirstAsync(x =>
|
||
x.Status == AvailabilityStatus.Enable && !x.DeleteMark);
|
||
}
|
||
|
||
if (payConfig != null)
|
||
{
|
||
var queryResult = await _weChatPayService.QueryOrderAsync(payConfig, orderNo);
|
||
if (queryResult.Success && queryResult.TradeState == "SUCCESS")
|
||
{
|
||
order.OrderStatus = OrderStatus.Paid;
|
||
order.PaymentTime = queryResult.SuccessTime?.DateTime;
|
||
if (queryResult.PayerAmount.HasValue)
|
||
{
|
||
order.ActualAmount = queryResult.PayerAmount.Value / 100m;
|
||
}
|
||
await _orderRepository.UpdateAsync(order);
|
||
|
||
var registration = await _registrationRepository.GetByIdAsync(order.RegistrationId);
|
||
if (registration != null && registration.PaymentStatus != 1)
|
||
{
|
||
registration.PaymentStatus = 1;
|
||
registration.RegistrationStatus = 1;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
}
|
||
return 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0; // 未支付
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证优惠券
|
||
/// </summary>
|
||
[DisplayName("验证优惠券")]
|
||
[HttpPost("coupon/validate")]
|
||
public async Task<ValidateCouponOutput> ValidateCoupon([FromBody] ValidateCouponInput input)
|
||
{
|
||
var result = new ValidateCouponOutput
|
||
{
|
||
IsValid = false,
|
||
InvalidReason = ""
|
||
};
|
||
|
||
// 查找优惠券(通过兑换码)
|
||
var coupon = await _couponRepository.GetFirstAsync(c =>
|
||
c.RedemptionCode == input.CouponCode &&
|
||
c.Status == AvailabilityStatus.Enable &&
|
||
!c.DeleteMark);
|
||
|
||
if (coupon == null)
|
||
{
|
||
result.InvalidReason = "优惠券不存在或已失效";
|
||
return result;
|
||
}
|
||
|
||
// 检查优惠券是否已过期
|
||
if (coupon.EndTime.HasValue && coupon.EndTime.Value < DateTime.Now)
|
||
{
|
||
result.InvalidReason = "优惠券已过期";
|
||
return result;
|
||
}
|
||
|
||
// 检查优惠券是否已开始
|
||
if (coupon.StartTime.HasValue && coupon.StartTime.Value > DateTime.Now)
|
||
{
|
||
result.InvalidReason = "优惠券尚未生效";
|
||
return result;
|
||
}
|
||
|
||
// 检查库存
|
||
if (coupon.TotalQuantity > 0 && coupon.ClaimedQuantity >= coupon.TotalQuantity)
|
||
{
|
||
result.InvalidReason = "优惠券已被领完";
|
||
return result;
|
||
}
|
||
|
||
// 检查优惠券是否适用于该赛事
|
||
if (!string.IsNullOrEmpty(coupon.EventIds))
|
||
{
|
||
var eventIds = coupon.EventIds.Split(',').Select(long.Parse).ToList();
|
||
if (!eventIds.Contains(input.EventId))
|
||
{
|
||
result.InvalidReason = "该优惠券不适用于当前赛事";
|
||
return result;
|
||
}
|
||
}
|
||
|
||
// 检查当前用户是否拥有该优惠券且未使用,或者可以直接领取
|
||
var userId = _authManager.UserId;
|
||
var userCoupon = await _userCouponRepository.GetFirstAsync(uc =>
|
||
uc.CouponId == coupon.Id &&
|
||
uc.UserId == userId &&
|
||
uc.Status == 0 && // 0-未使用
|
||
!uc.DeleteMark);
|
||
|
||
// 如果用户没有该优惠券,检查是否可以直接领取(假设可以直接通过兑换码领取)
|
||
if (userCoupon == null)
|
||
{
|
||
// 创建用户优惠券记录
|
||
userCoupon = new YD_Event.Core.Entities.UserCoupon
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
UserId = userId,
|
||
CouponId = coupon.Id,
|
||
ClaimTime = DateTime.Now,
|
||
Status = 0, // 未使用
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now,
|
||
DeleteMark = false
|
||
};
|
||
await _userCouponRepository.InsertAsync(userCoupon);
|
||
|
||
// 更新优惠券已领取数量
|
||
coupon.ClaimedQuantity += 1;
|
||
await _couponRepository.UpdateAsync(coupon);
|
||
}
|
||
|
||
// 计算优惠金额
|
||
decimal discountAmount = 0;
|
||
decimal finalAmount = input.OriginalAmount;
|
||
|
||
// Type: 0-折扣, 1-定额
|
||
if (coupon.Type == 0) // 折扣
|
||
{
|
||
discountAmount = input.OriginalAmount * (1 - coupon.Value / 100);
|
||
}
|
||
else if (coupon.Type == 1) // 定额
|
||
{
|
||
discountAmount = Math.Min(coupon.Value, input.OriginalAmount);
|
||
}
|
||
|
||
finalAmount = input.OriginalAmount - discountAmount;
|
||
finalAmount = Math.Max(0, finalAmount); // 确保不小于0
|
||
|
||
result.IsValid = true;
|
||
result.CouponId = coupon.Id;
|
||
result.CouponName = coupon.Name ?? "优惠券";
|
||
result.DiscountType = coupon.Type;
|
||
result.DiscountValue = coupon.Value;
|
||
result.DiscountAmount = discountAmount;
|
||
result.FinalAmount = finalAmount;
|
||
|
||
return result;
|
||
}
|
||
|
||
private string GetRegistrationStatusDesc(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 projectGroup = await _projectGroupRepository.GetByIdAsync(eventProjectGroup.ProjectGroupId);
|
||
|
||
// 优先使用EventProjectGroup中保存的信息,如果没有则使用原始ProjectGroup的信息
|
||
var birthDateStart = eventProjectGroup.BirthDateStart != DateTime.MinValue ? eventProjectGroup.BirthDateStart : (projectGroup?.BirthDateStart ?? DateTime.MinValue);
|
||
var birthDateEnd = eventProjectGroup.BirthDateEnd != DateTime.MinValue ? eventProjectGroup.BirthDateEnd : (projectGroup?.BirthDateEnd ?? DateTime.MaxValue);
|
||
var gender = eventProjectGroup.Gender;
|
||
|
||
var birthDate = member.BirthDate.Value;
|
||
if (birthDate < birthDateStart || birthDate > birthDateEnd)
|
||
{
|
||
result.AgeValid = false;
|
||
result.IsValid = false;
|
||
result.Message = "队员年龄不符合组别要求";
|
||
}
|
||
|
||
if (gender != member.Gender && gender != Gender.Unknown && gender != Gender.All)
|
||
{
|
||
result.GenderValid = false;
|
||
result.IsValid = false;
|
||
result.Message = "队员性别不符合组别要求";
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查报名限制
|
||
/// </summary>
|
||
[DisplayName("检查报名限制")]
|
||
[HttpPost("registration-limit/check")]
|
||
public async Task<RegistrationLimitCheckOutput> CheckRegistrationLimit([FromBody] RegistrationLimitCheckInput input)
|
||
{
|
||
var result = new RegistrationLimitCheckOutput
|
||
{
|
||
HasLimit = false,
|
||
LimitDetails = new List<RegistrationLimitDetail>()
|
||
};
|
||
|
||
var limit = await _limitRepository.GetFirstAsync(x => x.EventId == input.EventId);
|
||
if (limit != null)
|
||
{
|
||
var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id));
|
||
foreach (var member in members)
|
||
{
|
||
var registrationPersons = await _registrationPersonRepository.GetListAsync(x =>
|
||
!x.DeleteMark);
|
||
|
||
var matchedPersons = registrationPersons.Where(x => x.IdCard == member.IdCardNumber && !string.IsNullOrEmpty(x.IdCard)).ToList();
|
||
|
||
var registrationIds = matchedPersons.Select(x => x.RegistrationId).Distinct().ToList();
|
||
var registrations = await _registrationRepository.GetListAsync(x =>
|
||
registrationIds.Contains(x.Id) &&
|
||
x.EventId == input.EventId &&
|
||
x.RegistrationStatus != 4 &&
|
||
!x.DeleteMark);
|
||
|
||
if (registrations.Count() >= limit.IndividualProjectLimit)
|
||
{
|
||
result.HasLimit = true;
|
||
result.LimitDetails.Add(new RegistrationLimitDetail
|
||
{
|
||
MemberId = member.Id,
|
||
MemberName = member.Name ?? member.NickName ?? "未知",
|
||
LimitType = "IndividualLimit",
|
||
LimitMessage = $"该队员已报名{registrations.Count()}个项目,达到个人报名限制{limit.IndividualProjectLimit}个"
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <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);
|
||
|
||
foreach (var eventProjectGroup in eventProjectGroups)
|
||
{
|
||
var currentTeams = await _registrationItemRepository.AsQueryable()
|
||
.Where(x => x.EventProjectGroupId == eventProjectGroup.Id && !x.DeleteMark)
|
||
.Select(x => x.RegistrationId)
|
||
.Distinct()
|
||
.CountAsync();
|
||
|
||
result.Add(new GroupTeamLimitOutput
|
||
{
|
||
GroupId = eventProjectGroup.Id,
|
||
MaxTeams = 2,
|
||
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 totalAssignedMembers = input.GroupMembers
|
||
.Where(g => assignedProjectGroups.Select(pg => pg.Id).Contains(g.GroupId))
|
||
.SelectMany(g => g.MemberIds)
|
||
.Distinct()
|
||
.Count();
|
||
|
||
if (project.CompletionCount > 0)
|
||
{
|
||
if (totalAssignedMembers < project.CompletionCount)
|
||
{
|
||
result.HasError = true;
|
||
result.Errors.Add(new ProjectCompletionError
|
||
{
|
||
ProjectId = eventProject.Id,
|
||
ProjectName = project.Name ?? "未知项目",
|
||
ErrorType = "Insufficient",
|
||
ErrorMessage = $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前只分配了{totalAssignedMembers}人"
|
||
});
|
||
}
|
||
else if (totalAssignedMembers > project.CompletionCount)
|
||
{
|
||
result.HasError = true;
|
||
result.Errors.Add(new ProjectCompletionError
|
||
{
|
||
ProjectId = eventProject.Id,
|
||
ProjectName = project.Name ?? "未知项目",
|
||
ErrorType = "Excessive",
|
||
ErrorMessage = $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前分配了{totalAssignedMembers}人,人数超额"
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <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 EventPricingService.PricingInput
|
||
{
|
||
ChargeMode = feeConfig.ChargeMode,
|
||
ParticipantCount = input.ParticipantCount
|
||
};
|
||
|
||
// 添加项目信息
|
||
foreach (var eventProject in eventProjects)
|
||
{
|
||
var baseProject = baseProjects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
|
||
pricingInput.Projects.Add(new EventPricingService.ProjectPricingInfo
|
||
{
|
||
ProjectId = eventProject.ProjectId,
|
||
ProjectName = baseProject?.Name ?? "未知项目",
|
||
UnitPrice = eventProject.Price / 100m
|
||
});
|
||
}
|
||
|
||
// 使用统一计算服务计算费用
|
||
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 => "单价(项目)",
|
||
_ => "未知模式"
|
||
};
|
||
}
|
||
|
||
}
|