2026-03-08 15:46:19 +08:00
|
|
|
|
using YD_Event.Application.Event.Dtos;
|
2026-04-09 09:09:50 +08:00
|
|
|
|
using YD_Event.Application.Auth;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
using YD_Event.Core.Entities;
|
2026-04-21 09:40:58 +08:00
|
|
|
|
using YD_Event.Core.Enum;
|
|
|
|
|
|
using YD_Event.Core.Domain;
|
2026-06-28 20:32:27 +08:00
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
using OfficeOpenXml;
|
|
|
|
|
|
using OfficeOpenXml.Style;
|
|
|
|
|
|
using System.Drawing;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-03-08 15:46:19 +08:00
|
|
|
|
namespace YD_Event.Application.Event;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 报名管理服务
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class EventRegistrationService : BaseService<EventRegistration>, ITransient
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistration> _repository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationItem> _itemRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationPerson> _personRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
2026-04-13 06:58:21 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
|
2026-04-14 17:30:20 +08:00
|
|
|
|
private readonly ISqlSugarRepository<TeamMember> _teamMemberRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _appUserRepository;
|
2026-04-20 13:01:39 +08:00
|
|
|
|
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
|
2026-04-21 09:40:58 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventInfo> _eventInfoRepository;
|
2026-04-28 00:02:01 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
|
|
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
|
2026-04-21 09:40:58 +08:00
|
|
|
|
private readonly IIdGenerator _idGenerator;
|
|
|
|
|
|
private readonly OrderNoGeneratorService _orderNoGeneratorService;
|
2026-04-09 09:09:50 +08:00
|
|
|
|
private readonly AuthManager _authManager;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
public EventRegistrationService(
|
|
|
|
|
|
ISqlSugarRepository<EventRegistration> repository,
|
|
|
|
|
|
ISqlSugarRepository<EventRegistrationItem> itemRepository,
|
|
|
|
|
|
ISqlSugarRepository<EventRegistrationPerson> personRepository,
|
2026-04-09 09:09:50 +08:00
|
|
|
|
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
2026-04-13 06:58:21 +08:00
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
|
2026-04-14 17:30:20 +08:00
|
|
|
|
ISqlSugarRepository<TeamMember> teamMemberRepository,
|
|
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> appUserRepository,
|
2026-04-20 13:01:39 +08:00
|
|
|
|
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
|
2026-04-21 09:40:58 +08:00
|
|
|
|
ISqlSugarRepository<EventInfo> eventInfoRepository,
|
2026-04-28 00:02:01 +08:00
|
|
|
|
ISqlSugarRepository<EventProject> eventProjectRepository,
|
|
|
|
|
|
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
|
2026-04-21 09:40:58 +08:00
|
|
|
|
IIdGenerator idGenerator,
|
|
|
|
|
|
OrderNoGeneratorService orderNoGeneratorService,
|
2026-04-09 09:09:50 +08:00
|
|
|
|
AuthManager authManager) : base(repository)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
_repository = repository;
|
|
|
|
|
|
_itemRepository = itemRepository;
|
|
|
|
|
|
_personRepository = personRepository;
|
|
|
|
|
|
_paymentRepository = paymentRepository;
|
2026-04-13 06:58:21 +08:00
|
|
|
|
_orderRepository = orderRepository;
|
2026-04-14 17:30:20 +08:00
|
|
|
|
_teamMemberRepository = teamMemberRepository;
|
|
|
|
|
|
_appUserRepository = appUserRepository;
|
2026-04-20 13:01:39 +08:00
|
|
|
|
_merchantCertificationRepository = merchantCertificationRepository;
|
2026-04-21 09:40:58 +08:00
|
|
|
|
_eventInfoRepository = eventInfoRepository;
|
2026-04-28 00:02:01 +08:00
|
|
|
|
_eventProjectRepository = eventProjectRepository;
|
|
|
|
|
|
_projectRepository = projectRepository;
|
2026-04-21 09:40:58 +08:00
|
|
|
|
_idGenerator = idGenerator;
|
|
|
|
|
|
_orderNoGeneratorService = orderNoGeneratorService;
|
2026-04-09 09:09:50 +08:00
|
|
|
|
_authManager = authManager;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 分页查询报名
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("分页查询报名")]
|
|
|
|
|
|
public async Task<PageResult<RegistrationListOutput>> Page(RegistrationQueryInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var query = _repository.AsQueryable()
|
2026-06-25 20:51:40 +08:00
|
|
|
|
// 排除软删除的报名记录
|
|
|
|
|
|
.Where(x => !x.DeleteMark)
|
2026-05-12 10:51:12 +08:00
|
|
|
|
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
|
|
|
|
|
|
.Where(x => (x.PaymentMethod == 2 && x.PaymentStatus == 1) || (x.PaymentMethod == 1 && (x.PaymentStatus == 0 || x.PaymentStatus == 1)))
|
2026-02-08 18:09:05 +08:00
|
|
|
|
.WhereIF(input.EventId.HasValue, x => x.EventId == input.EventId)
|
|
|
|
|
|
.WhereIF(input.RegistrationMode.HasValue, x => x.RegistrationMode == input.RegistrationMode)
|
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.UnitName), x => x.TeamName.Contains(input.UnitName))
|
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.UserName), x => x.ContactName.Contains(input.UserName))
|
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), x => x.ContactPhone.Contains(input.ContactPhone))
|
|
|
|
|
|
.WhereIF(input.IsUploadMaterial.HasValue, x => (input.IsUploadMaterial.Value ? !string.IsNullOrEmpty(x.MaterialUrl) : string.IsNullOrEmpty(x.MaterialUrl)))
|
|
|
|
|
|
.WhereIF(input.PaymentMethod.HasValue, x => x.PaymentMethod == input.PaymentMethod)
|
|
|
|
|
|
.WhereIF(input.PaymentStatus.HasValue, x => x.PaymentStatus == input.PaymentStatus)
|
|
|
|
|
|
.WhereIF(input.RegistrationTimeStart.HasValue, x => x.RegistrationTime >= input.RegistrationTimeStart)
|
|
|
|
|
|
.WhereIF(input.RegistrationTimeEnd.HasValue, x => x.RegistrationTime <= input.RegistrationTimeEnd)
|
|
|
|
|
|
.OrderByDescending(x => x.RegistrationTime)
|
|
|
|
|
|
.Select(x => new RegistrationListOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = x.Id,
|
|
|
|
|
|
RegistrationMode = x.RegistrationMode,
|
2026-04-21 20:39:24 +08:00
|
|
|
|
CreatedUserId = x.CreatedUserId, // 保存创建用户ID以便后续查询单位名称
|
2026-04-28 00:25:09 +08:00
|
|
|
|
MerchantCertificationId = x.MerchantCertificationId, // 保存单位认证ID以便后续查询单位名称
|
2026-04-21 20:39:24 +08:00
|
|
|
|
UnitName = null, // 不初始化为 TeamName,只从单位认证信息中获取
|
2026-02-08 18:09:05 +08:00
|
|
|
|
ContactName = x.ContactName,
|
|
|
|
|
|
ContactPhone = x.ContactPhone,
|
|
|
|
|
|
TotalPersonCount = x.TotalPersonCount,
|
|
|
|
|
|
IsUploadMaterial = !string.IsNullOrEmpty(x.MaterialUrl),
|
|
|
|
|
|
PaymentMethod = x.PaymentMethod,
|
2026-04-13 13:23:00 +08:00
|
|
|
|
PaymentMethodType = x.PaymentMethodType,
|
2026-02-08 18:09:05 +08:00
|
|
|
|
OrderNo = x.OrderNo,
|
|
|
|
|
|
TotalAmount = x.TotalAmount,
|
|
|
|
|
|
PaymentStatus = x.PaymentStatus,
|
|
|
|
|
|
PaymentProofUrl = x.PaymentProofUrl,
|
2026-04-09 23:12:51 +08:00
|
|
|
|
RegistrationTime = x.RegistrationTime
|
2026-02-08 18:09:05 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
var result = await query.ToPagedListAsync(input);
|
2026-04-28 00:25:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取所有报名记录的 MerchantCertificationId
|
|
|
|
|
|
var regIds = result.Rows.Select(x => x.Id).ToList();
|
|
|
|
|
|
var merchantCertIds = result.Rows
|
|
|
|
|
|
.Where(r => r.MerchantCertificationId.HasValue)
|
|
|
|
|
|
.Select(r => r.MerchantCertificationId.Value)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取单位认证信息
|
|
|
|
|
|
Dictionary<long, string> merchantNameDict = new Dictionary<long, string>();
|
|
|
|
|
|
if (merchantCertIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var merchantCerts = await _merchantCertificationRepository.AsQueryable()
|
|
|
|
|
|
.Where(mc => merchantCertIds.Contains(mc.Id) && !mc.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
merchantNameDict = merchantCerts.ToDictionary(mc => mc.Id, mc => mc.MerchantName);
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 填充下拉详情
|
|
|
|
|
|
if (result.Rows.Any())
|
|
|
|
|
|
{
|
2026-05-07 08:33:53 +08:00
|
|
|
|
var items = await _itemRepository.GetListAsync(x => regIds.Contains(x.RegistrationId) && !x.DeleteMark);
|
2026-04-13 05:20:09 +08:00
|
|
|
|
// 使用关联查询获取报名人员及其关联的用户信息
|
|
|
|
|
|
var persons = await _personRepository.AsQueryable()
|
|
|
|
|
|
.Includes(p => p.User) // 关联查询用户信息
|
2026-05-07 08:33:53 +08:00
|
|
|
|
.Where(x => regIds.Contains(x.RegistrationId) && !x.DeleteMark)
|
2026-04-13 05:20:09 +08:00
|
|
|
|
.ToListAsync();
|
2026-04-28 00:02:01 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取所有赛事项目和项目信息,用于判断项目类型
|
|
|
|
|
|
var eventProjectIds = items.Select(x => x.ProjectId).Distinct().ToList();
|
|
|
|
|
|
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
|
|
|
|
|
|
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
|
|
|
|
|
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
|
|
|
|
|
|
|
|
|
|
|
|
// 创建映射字典
|
|
|
|
|
|
var eventProjectToProjectDict = eventProjects.ToDictionary(x => x.Id, x => x.ProjectId);
|
|
|
|
|
|
Dictionary<long, YD_Event.Core.Entities.Project> projectDict = projects.ToDictionary(x => x.Id, x => x);
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-04-14 17:30:20 +08:00
|
|
|
|
// 获取所有报名人员的 UserId
|
|
|
|
|
|
var allAthleteUserIds = persons
|
|
|
|
|
|
.Where(p => p.PersonType == YD_Event.Core.Enum.PersonType.Athlete && p.UserId > 0)
|
|
|
|
|
|
.Select(p => p.UserId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤1:通过运动员的 UserId 找到对应的 TeamId
|
|
|
|
|
|
var teamIds = new List<long>();
|
|
|
|
|
|
if (allAthleteUserIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
teamIds = await _teamMemberRepository.AsQueryable()
|
|
|
|
|
|
.Where(tm => allAthleteUserIds.Contains(tm.UserId) && !tm.DeleteMark)
|
|
|
|
|
|
.Select(tm => tm.TeamId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤2:通过 TeamId 获取同团队的教练/领队的 UserId,并获取用户详情
|
|
|
|
|
|
Dictionary<long, List<YD_Event.Core.Entities.AppUser>> teamLeadersDict = new();
|
|
|
|
|
|
Dictionary<long, List<YD_Event.Core.Entities.AppUser>> teamCoachesDict = new();
|
|
|
|
|
|
|
|
|
|
|
|
if (teamIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var teamMembers = await _teamMemberRepository.AsQueryable()
|
|
|
|
|
|
.Where(tm => teamIds.Contains(tm.TeamId) && !tm.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var coachLeaderUserIds = teamMembers
|
|
|
|
|
|
.Where(tm => tm.PersonType == YD_Event.Core.Enum.PersonType.Leader ||
|
|
|
|
|
|
tm.PersonType == YD_Event.Core.Enum.PersonType.Coach)
|
|
|
|
|
|
.Select(tm => tm.UserId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (coachLeaderUserIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var coachLeaderUsers = await _appUserRepository.AsQueryable()
|
|
|
|
|
|
.Where(u => coachLeaderUserIds.Contains(u.Id) && !u.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var userDict = coachLeaderUsers.ToDictionary(u => u.Id);
|
|
|
|
|
|
|
|
|
|
|
|
// 按团队分组
|
|
|
|
|
|
foreach (var teamId in teamIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
var teamLeaders = teamMembers
|
|
|
|
|
|
.Where(tm => tm.TeamId == teamId && tm.PersonType == YD_Event.Core.Enum.PersonType.Leader)
|
|
|
|
|
|
.Select(tm => userDict.TryGetValue(tm.UserId, out var user) ? user : null)
|
|
|
|
|
|
.Where(u => u != null)
|
|
|
|
|
|
.Cast<YD_Event.Core.Entities.AppUser>()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var teamCoaches = teamMembers
|
|
|
|
|
|
.Where(tm => tm.TeamId == teamId && tm.PersonType == YD_Event.Core.Enum.PersonType.Coach)
|
|
|
|
|
|
.Select(tm => userDict.TryGetValue(tm.UserId, out var user) ? user : null)
|
|
|
|
|
|
.Where(u => u != null)
|
|
|
|
|
|
.Cast<YD_Event.Core.Entities.AppUser>()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (teamLeaders.Any())
|
|
|
|
|
|
teamLeadersDict[teamId] = teamLeaders;
|
|
|
|
|
|
|
|
|
|
|
|
if (teamCoaches.Any())
|
|
|
|
|
|
teamCoachesDict[teamId] = teamCoaches;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
foreach (var dto in result.Rows)
|
|
|
|
|
|
{
|
2026-04-20 13:01:39 +08:00
|
|
|
|
// 获取当前报名的所有人员
|
|
|
|
|
|
var regPersons = persons.Where(p => p.RegistrationId == dto.Id).ToList();
|
2026-05-12 09:26:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算去重后的人数(按UserId去重)
|
|
|
|
|
|
var uniqueUserIds = regPersons
|
|
|
|
|
|
.Where(p => p.PersonType == PersonType.Athlete && p.UserId > 0)
|
|
|
|
|
|
.Select(p => p.UserId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新TotalPersonCount为去重后的人数
|
|
|
|
|
|
dto.TotalPersonCount = uniqueUserIds.Count > 0 ? uniqueUserIds.Count : regPersons.Count(p => p.PersonType == PersonType.Athlete);
|
2026-04-21 20:39:24 +08:00
|
|
|
|
|
2026-04-28 00:25:09 +08:00
|
|
|
|
// 根据报名记录的 MerchantCertificationId 获取单位名称
|
|
|
|
|
|
if (dto.MerchantCertificationId.HasValue &&
|
|
|
|
|
|
merchantNameDict.TryGetValue(dto.MerchantCertificationId.Value, out var unitName))
|
2026-04-20 13:01:39 +08:00
|
|
|
|
{
|
2026-04-28 00:25:09 +08:00
|
|
|
|
dto.UnitName = unitName;
|
2026-04-21 20:39:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
dto.UnitName = null;
|
2026-04-20 13:01:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
var myItems = items.Where(x => x.RegistrationId == dto.Id).ToList();
|
|
|
|
|
|
dto.Items = new List<RegistrationItemDetailDto>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var item in myItems)
|
|
|
|
|
|
{
|
|
|
|
|
|
var myPersons = persons.Where(p => p.RegistrationItemId == item.Id).ToList();
|
|
|
|
|
|
|
2026-04-28 00:02:01 +08:00
|
|
|
|
// 判断项目类型
|
|
|
|
|
|
bool isIndividualProject = false;
|
|
|
|
|
|
if (eventProjectToProjectDict.TryGetValue(item.ProjectId, out var projectId) &&
|
|
|
|
|
|
projectDict.TryGetValue(projectId, out var project))
|
2026-04-13 05:20:09 +08:00
|
|
|
|
{
|
2026-04-28 00:02:01 +08:00
|
|
|
|
isIndividualProject = YD_Event.Core.Extensions.ProjectTypeExtensions.IsIndividualProject(project);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isIndividualProject)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 个人项目:为每个运动员创建一个条目,使用 EventRegistrationPerson.Id
|
|
|
|
|
|
foreach (var person in myPersons.Where(p => p.PersonType == PersonType.Athlete))
|
2026-04-13 13:23:00 +08:00
|
|
|
|
{
|
2026-04-28 00:02:01 +08:00
|
|
|
|
// 构造参与人信息字符串
|
|
|
|
|
|
var genderText = person.Gender switch
|
|
|
|
|
|
{
|
|
|
|
|
|
YD_Event.Core.Enum.Gender.Male => "男",
|
|
|
|
|
|
YD_Event.Core.Enum.Gender.Female => "女",
|
|
|
|
|
|
_ => "未知"
|
|
|
|
|
|
};
|
|
|
|
|
|
var mobile = person.User?.Mobile ?? person.Phone ?? "";
|
|
|
|
|
|
var personInfo = $"{person.Name}/{genderText}/{person.Age}/{mobile}";
|
2026-02-08 18:09:05 +08:00
|
|
|
|
|
2026-04-28 00:02:01 +08:00
|
|
|
|
var detail = new RegistrationItemDetailDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = person.Id, // 使用 EventRegistrationPerson.Id
|
|
|
|
|
|
ProjectName = item.ProjectName,
|
|
|
|
|
|
GroupName = item.GroupName,
|
|
|
|
|
|
ParticipantInfo = personInfo
|
|
|
|
|
|
};
|
|
|
|
|
|
dto.Items.Add(detail);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-04-28 00:02:01 +08:00
|
|
|
|
// 团队项目:保持原有逻辑,使用 EventRegistrationItem.Id
|
|
|
|
|
|
// 构造参与人信息字符串,统一格式为:姓名/性别/年龄/手机号
|
|
|
|
|
|
var personInfo = string.Join(";", myPersons.Select(p =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 将性别转换为中文显示
|
|
|
|
|
|
var genderText = p.Gender switch
|
|
|
|
|
|
{
|
|
|
|
|
|
YD_Event.Core.Enum.Gender.Male => "男",
|
|
|
|
|
|
YD_Event.Core.Enum.Gender.Female => "女",
|
|
|
|
|
|
_ => "未知"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取手机号,优先使用关联用户信息,否则使用自身的 Phone 字段
|
|
|
|
|
|
var mobile = p.User?.Mobile ?? p.Phone ?? "";
|
|
|
|
|
|
|
|
|
|
|
|
// 统一格式:姓名/性别/年龄/手机号
|
|
|
|
|
|
return $"{p.Name}/{genderText}/{p.Age}/{mobile}";
|
|
|
|
|
|
}));
|
|
|
|
|
|
if (personInfo.Length > 100) personInfo = personInfo.Substring(0, 97) + "..."; // 截断
|
|
|
|
|
|
|
|
|
|
|
|
var detail = new RegistrationItemDetailDto
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = item.Id, // 使用 EventRegistrationItem.Id
|
|
|
|
|
|
ProjectName = item.ProjectName,
|
|
|
|
|
|
GroupName = item.GroupName,
|
|
|
|
|
|
ParticipantInfo = personInfo
|
|
|
|
|
|
};
|
|
|
|
|
|
dto.Items.Add(detail);
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
2026-04-13 13:23:00 +08:00
|
|
|
|
|
2026-04-14 17:30:20 +08:00
|
|
|
|
// 设置领队信息和教练信息 - 从团队中获取
|
|
|
|
|
|
var regAthleteUserIds = regPersons
|
|
|
|
|
|
.Where(p => p.PersonType == YD_Event.Core.Enum.PersonType.Athlete && p.UserId > 0)
|
|
|
|
|
|
.Select(p => p.UserId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取这些运动员所属的团队
|
|
|
|
|
|
var regTeamIds = new List<long>();
|
|
|
|
|
|
if (regAthleteUserIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
regTeamIds = await _teamMemberRepository.AsQueryable()
|
|
|
|
|
|
.Where(tm => regAthleteUserIds.Contains(tm.UserId) && !tm.DeleteMark)
|
|
|
|
|
|
.Select(tm => tm.TeamId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收集所有相关团队的领队和教练
|
|
|
|
|
|
var allLeaders = new List<YD_Event.Core.Entities.AppUser>();
|
|
|
|
|
|
var allCoaches = new List<YD_Event.Core.Entities.AppUser>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var teamId in regTeamIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (teamLeadersDict.TryGetValue(teamId, out var leaders))
|
|
|
|
|
|
allLeaders.AddRange(leaders);
|
|
|
|
|
|
if (teamCoachesDict.TryGetValue(teamId, out var coaches))
|
|
|
|
|
|
allCoaches.AddRange(coaches);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 去重
|
|
|
|
|
|
allLeaders = allLeaders.GroupBy(l => l.Id).Select(g => g.First()).ToList();
|
|
|
|
|
|
allCoaches = allCoaches.GroupBy(c => c.Id).Select(g => g.First()).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果从团队中没有找到,再回退到从报名人员中查找
|
|
|
|
|
|
if (!allLeaders.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var regLeaders = regPersons.Where(p => p.PersonType == YD_Event.Core.Enum.PersonType.Leader).ToList();
|
|
|
|
|
|
allLeaders = regLeaders.Select(l => new YD_Event.Core.Entities.AppUser
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = l.Name,
|
|
|
|
|
|
Mobile = l.Phone,
|
|
|
|
|
|
Gender = l.Gender
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!allCoaches.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var regCoaches = regPersons.Where(p => p.PersonType == YD_Event.Core.Enum.PersonType.Coach).ToList();
|
|
|
|
|
|
allCoaches = regCoaches.Select(c => new YD_Event.Core.Entities.AppUser
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = c.Name,
|
|
|
|
|
|
Mobile = c.Phone,
|
|
|
|
|
|
Gender = c.Gender
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 辅助函数:获取性别文本
|
|
|
|
|
|
Func<YD_Event.Core.Enum.Gender, string> getGenderText = (gender) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
return gender switch
|
|
|
|
|
|
{
|
|
|
|
|
|
YD_Event.Core.Enum.Gender.Male => "男",
|
|
|
|
|
|
YD_Event.Core.Enum.Gender.Female => "女",
|
|
|
|
|
|
_ => "未知"
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
dto.TeamLeaderInfo = allLeaders.Any()
|
2026-04-14 23:34:16 +08:00
|
|
|
|
? string.Join(",", allLeaders.Select(l => $"{l.Name}/{getGenderText(l.Gender)}/{l.Mobile}"))
|
2026-04-13 13:23:00 +08:00
|
|
|
|
: null;
|
|
|
|
|
|
|
2026-04-14 17:30:20 +08:00
|
|
|
|
dto.CoachInfo = allCoaches.Any()
|
2026-04-14 23:34:16 +08:00
|
|
|
|
? string.Join(",", allCoaches.Select(c => $"{c.Name}/{getGenderText(c.Gender)}/{c.Mobile}"))
|
2026-04-13 13:23:00 +08:00
|
|
|
|
: null;
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-21 09:40:58 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 为报名记录创建订单(如果不存在)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task CreateOrderIfNotExistsAsync(EventRegistration registration, DateTime now)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查是否已存在订单
|
|
|
|
|
|
var existingOrder = await _orderRepository.GetFirstAsync(o =>
|
|
|
|
|
|
o.RegistrationId == registration.Id && !o.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingOrder == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 不存在订单,先检查是否有订单号
|
|
|
|
|
|
if (string.IsNullOrEmpty(registration.OrderNo))
|
|
|
|
|
|
{
|
|
|
|
|
|
registration.OrderNo = await _orderNoGeneratorService.GenerateOrderNoAsync();
|
|
|
|
|
|
await _repository.UpdateAsync(registration);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取赛事信息
|
|
|
|
|
|
var eventInfo = await _eventInfoRepository.GetByIdAsync(registration.EventId);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户单位认证信息
|
|
|
|
|
|
var certification = await _merchantCertificationRepository.GetFirstAsync(c =>
|
|
|
|
|
|
c.UserId == registration.CreatedUserId && c.AuditStatus == AuditStatus.Pass && !c.DeleteMark);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建订单
|
|
|
|
|
|
var order = new YD_Event.Core.Entities.Order
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NewLong(),
|
|
|
|
|
|
OrderNo = registration.OrderNo,
|
|
|
|
|
|
OrderType = 1,
|
|
|
|
|
|
MerchantId = eventInfo?.MerchantId,
|
|
|
|
|
|
MerchantCertificationId = certification?.Id,
|
|
|
|
|
|
EventId = registration.EventId,
|
|
|
|
|
|
RegistrationId = registration.Id,
|
|
|
|
|
|
OriginalAmount = registration.TotalAmount,
|
|
|
|
|
|
ActualAmount = registration.TotalAmount,
|
|
|
|
|
|
DiscountAmount = 0,
|
|
|
|
|
|
CouponId = null,
|
|
|
|
|
|
OrderStatus = OrderStatus.Paid,
|
|
|
|
|
|
PaymentTime = now,
|
|
|
|
|
|
CreatedTime = now,
|
|
|
|
|
|
CreatedUserId = _authManager.UserId,
|
|
|
|
|
|
UpdatedTime = now,
|
|
|
|
|
|
UpdatedUserId = _authManager.UserId,
|
|
|
|
|
|
Status = AvailabilityStatus.Enable
|
|
|
|
|
|
};
|
|
|
|
|
|
await _orderRepository.InsertAsync(order);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 订单已存在,只更新状态为已支付
|
|
|
|
|
|
if (existingOrder.OrderStatus != OrderStatus.Paid)
|
|
|
|
|
|
{
|
|
|
|
|
|
existingOrder.OrderStatus = OrderStatus.Paid;
|
|
|
|
|
|
existingOrder.PaymentTime = now;
|
|
|
|
|
|
existingOrder.UpdatedTime = now;
|
|
|
|
|
|
existingOrder.UpdatedUserId = _authManager.UserId;
|
|
|
|
|
|
await _orderRepository.UpdateAsync(existingOrder);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 18:09:05 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 批量审核
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
|
[DisplayName("批量审核")]
|
|
|
|
|
|
public async Task BatchAudit(BatchAuditInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (input.Ids == null || !input.Ids.Any()) return;
|
|
|
|
|
|
|
2026-04-21 09:40:58 +08:00
|
|
|
|
var now = DateTime.Now;
|
2026-04-13 06:58:21 +08:00
|
|
|
|
|
2026-04-21 09:40:58 +08:00
|
|
|
|
// 如果审核通过(PaymentStatus = 1),先处理订单创建和更新
|
2026-04-13 06:58:21 +08:00
|
|
|
|
if (input.PaymentStatus == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取需要更新的报名记录
|
|
|
|
|
|
var registrations = await _repository.GetListAsync(x => input.Ids.Contains(x.Id));
|
|
|
|
|
|
|
2026-04-21 09:40:58 +08:00
|
|
|
|
foreach (var registration in registrations)
|
2026-04-13 06:58:21 +08:00
|
|
|
|
{
|
2026-04-21 09:40:58 +08:00
|
|
|
|
// 确保订单存在
|
|
|
|
|
|
await CreateOrderIfNotExistsAsync(registration, now);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新报名记录的支付状态
|
|
|
|
|
|
registration.PaymentStatus = input.PaymentStatus;
|
|
|
|
|
|
registration.ReviewRemark = input.ReviewRemark;
|
|
|
|
|
|
registration.ReviewTime = now;
|
2026-08-30 21:46:05 +08:00
|
|
|
|
registration.Reviewer = _authManager.Account;
|
2026-04-21 09:40:58 +08:00
|
|
|
|
registration.PaymentTime = now;
|
|
|
|
|
|
registration.UpdatedTime = now;
|
|
|
|
|
|
registration.UpdatedUserId = _authManager.UserId;
|
|
|
|
|
|
await _repository.UpdateAsync(registration);
|
2026-04-13 06:58:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-21 09:40:58 +08:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 审核不通过,只更新状态
|
|
|
|
|
|
await _repository.UpdateAsync(x => new EventRegistration
|
|
|
|
|
|
{
|
|
|
|
|
|
PaymentStatus = input.PaymentStatus,
|
|
|
|
|
|
ReviewRemark = input.ReviewRemark,
|
2026-08-30 21:46:05 +08:00
|
|
|
|
ReviewTime = now,
|
|
|
|
|
|
Reviewer = _authManager.Account,
|
|
|
|
|
|
UpdatedTime = now,
|
|
|
|
|
|
UpdatedUserId = _authManager.UserId
|
2026-04-21 09:40:58 +08:00
|
|
|
|
}, x => input.Ids.Contains(x.Id));
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-06-28 20:32:27 +08:00
|
|
|
|
/// 导出报名信息
|
2026-02-08 18:09:05 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("导出报名")]
|
2026-06-28 20:32:27 +08:00
|
|
|
|
public async Task<IActionResult> Export(RegistrationQueryInput input)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-04-20 13:01:39 +08:00
|
|
|
|
var registrations = await _repository.AsQueryable()
|
2026-06-25 20:51:40 +08:00
|
|
|
|
.Where(x => !x.DeleteMark)
|
2026-05-12 10:51:12 +08:00
|
|
|
|
.Where(x => (x.PaymentMethod == 2 && x.PaymentStatus == 1) || (x.PaymentMethod == 1 && (x.PaymentStatus == 0 || x.PaymentStatus == 1)))
|
2026-02-08 18:09:05 +08:00
|
|
|
|
.WhereIF(input.EventId.HasValue, x => x.EventId == input.EventId)
|
|
|
|
|
|
.WhereIF(input.RegistrationMode.HasValue, x => x.RegistrationMode == input.RegistrationMode)
|
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.UnitName), x => x.TeamName.Contains(input.UnitName))
|
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.UserName), x => x.ContactName.Contains(input.UserName))
|
|
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.ContactPhone), x => x.ContactPhone.Contains(input.ContactPhone))
|
2026-06-28 20:32:27 +08:00
|
|
|
|
.WhereIF(input.PaymentMethod.HasValue, x => x.PaymentMethod == input.PaymentMethod)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
.WhereIF(input.PaymentStatus.HasValue, x => x.PaymentStatus == input.PaymentStatus)
|
2026-06-28 20:32:27 +08:00
|
|
|
|
.WhereIF(input.RegistrationTimeStart.HasValue, x => x.RegistrationTime >= input.RegistrationTimeStart)
|
|
|
|
|
|
.WhereIF(input.RegistrationTimeEnd.HasValue, x => x.RegistrationTime <= input.RegistrationTimeEnd)
|
|
|
|
|
|
.OrderBy(x => x.RegistrationMode, OrderByType.Asc)
|
|
|
|
|
|
.OrderBy(x => x.RegistrationTime, OrderByType.Desc)
|
2026-04-20 13:01:39 +08:00
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
2026-06-28 20:32:27 +08:00
|
|
|
|
if (!registrations.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("暂无数据可导出");
|
|
|
|
|
|
}
|
2026-04-21 20:39:24 +08:00
|
|
|
|
|
2026-06-28 20:32:27 +08:00
|
|
|
|
var regIds = registrations.Select(r => r.Id).ToList();
|
2026-04-20 13:01:39 +08:00
|
|
|
|
|
2026-06-28 20:32:27 +08:00
|
|
|
|
var merchantCertIds = registrations
|
|
|
|
|
|
.Where(r => r.MerchantCertificationId.HasValue)
|
|
|
|
|
|
.Select(r => r.MerchantCertificationId.Value)
|
2026-04-20 13:01:39 +08:00
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
Dictionary<long, string> merchantNameDict = new Dictionary<long, string>();
|
|
|
|
|
|
if (merchantCertIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var merchantCerts = await _merchantCertificationRepository.AsQueryable()
|
|
|
|
|
|
.Where(mc => merchantCertIds.Contains(mc.Id) && !mc.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
merchantNameDict = merchantCerts.ToDictionary(mc => mc.Id, mc => mc.MerchantName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-28 20:32:27 +08:00
|
|
|
|
var allPersons = await _personRepository.AsQueryable()
|
|
|
|
|
|
.Includes(p => p.User)
|
|
|
|
|
|
.Where(p => regIds.Contains(p.RegistrationId) && !p.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var athleteUserIds = allPersons
|
|
|
|
|
|
.Where(p => p.PersonType == PersonType.Athlete && p.UserId > 0)
|
|
|
|
|
|
.Select(p => p.UserId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var allTeamMembers = new List<TeamMember>();
|
|
|
|
|
|
Dictionary<long, List<YD_Event.Core.Entities.AppUser>> teamLeadersDict = new();
|
|
|
|
|
|
Dictionary<long, List<YD_Event.Core.Entities.AppUser>> teamCoachesDict = new();
|
|
|
|
|
|
|
|
|
|
|
|
if (athleteUserIds.Any())
|
2026-04-21 20:39:24 +08:00
|
|
|
|
{
|
2026-06-28 20:32:27 +08:00
|
|
|
|
allTeamMembers = await _teamMemberRepository.AsQueryable()
|
|
|
|
|
|
.Where(tm => athleteUserIds.Contains(tm.UserId) && !tm.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var allTeamIds = allTeamMembers.Select(tm => tm.TeamId).Distinct().ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (allTeamIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var fullTeamMembers = await _teamMemberRepository.AsQueryable()
|
|
|
|
|
|
.Where(tm => allTeamIds.Contains(tm.TeamId) && !tm.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var coachLeaderUserIds = fullTeamMembers
|
|
|
|
|
|
.Where(tm => tm.PersonType == PersonType.Leader || tm.PersonType == PersonType.Coach)
|
|
|
|
|
|
.Select(tm => tm.UserId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (coachLeaderUserIds.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
var coachLeaderUsers = await _appUserRepository.AsQueryable()
|
|
|
|
|
|
.Where(u => coachLeaderUserIds.Contains(u.Id) && !u.DeleteMark)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
var userDict = coachLeaderUsers.ToDictionary(u => u.Id);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var teamId in allTeamIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
var teamLeaders = fullTeamMembers
|
|
|
|
|
|
.Where(tm => tm.TeamId == teamId && tm.PersonType == PersonType.Leader)
|
|
|
|
|
|
.Select(tm => userDict.TryGetValue(tm.UserId, out var user) ? user : null)
|
|
|
|
|
|
.Where(u => u != null)
|
|
|
|
|
|
.Cast<YD_Event.Core.Entities.AppUser>()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
var teamCoaches = fullTeamMembers
|
|
|
|
|
|
.Where(tm => tm.TeamId == teamId && tm.PersonType == PersonType.Coach)
|
|
|
|
|
|
.Select(tm => userDict.TryGetValue(tm.UserId, out var user) ? user : null)
|
|
|
|
|
|
.Where(u => u != null)
|
|
|
|
|
|
.Cast<YD_Event.Core.Entities.AppUser>()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
if (teamLeaders.Any()) teamLeadersDict[teamId] = teamLeaders;
|
|
|
|
|
|
if (teamCoaches.Any()) teamCoachesDict[teamId] = teamCoaches;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var userToTeamIdsDict = allTeamMembers
|
|
|
|
|
|
.GroupBy(tm => tm.UserId)
|
|
|
|
|
|
.ToDictionary(g => g.Key, g => g.Select(tm => tm.TeamId).Distinct().ToList());
|
|
|
|
|
|
|
|
|
|
|
|
ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
|
|
|
|
|
|
using var package = new ExcelPackage();
|
|
|
|
|
|
|
|
|
|
|
|
BuildTeamSheet(package, registrations, allPersons, merchantNameDict, teamLeadersDict, teamCoachesDict, userToTeamIdsDict);
|
|
|
|
|
|
BuildPersonSheet(package, registrations, allPersons, merchantNameDict);
|
|
|
|
|
|
|
|
|
|
|
|
var bytes = await package.GetAsByteArrayAsync();
|
|
|
|
|
|
return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
|
|
|
|
|
{
|
|
|
|
|
|
FileDownloadName = $"报名信息_{DateTime.Now:yyyyMMddHHmmss}.xlsx"
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BuildTeamSheet(
|
|
|
|
|
|
ExcelPackage package,
|
|
|
|
|
|
List<EventRegistration> registrations,
|
|
|
|
|
|
List<EventRegistrationPerson> allPersons,
|
|
|
|
|
|
Dictionary<long, string> merchantNameDict,
|
|
|
|
|
|
Dictionary<long, List<YD_Event.Core.Entities.AppUser>> teamLeadersDict,
|
|
|
|
|
|
Dictionary<long, List<YD_Event.Core.Entities.AppUser>> teamCoachesDict,
|
|
|
|
|
|
Dictionary<long, List<long>> userToTeamIdsDict)
|
|
|
|
|
|
{
|
|
|
|
|
|
var ws = package.Workbook.Worksheets.Add("队伍信息");
|
|
|
|
|
|
|
|
|
|
|
|
string[] headers = { "类型", "参赛单位", "队伍名称/运动员名称", "领队", "联系方式", "教练", "联系方式", "人数", "缴费情况" };
|
|
|
|
|
|
for (int i = 0; i < headers.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ws.Cells[1, i + 1].Value = headers[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var headerRange = ws.Cells[1, 1, 1, headers.Length];
|
|
|
|
|
|
headerRange.Style.Font.Bold = true;
|
|
|
|
|
|
headerRange.Style.Font.Size = 14;
|
|
|
|
|
|
headerRange.Style.Font.Name = "宋体";
|
|
|
|
|
|
headerRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
|
|
|
|
|
headerRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
|
|
|
|
|
headerRange.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
|
|
|
|
|
headerRange.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(217, 226, 243));
|
|
|
|
|
|
headerRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
headerRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
headerRange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
headerRange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
ws.Row(1).Height = 30;
|
|
|
|
|
|
|
|
|
|
|
|
double[] colWidths = { 13.4, 44.6, 38.5, 23.1, 30.4, 13, 24.9, 15.3, 22.7 };
|
|
|
|
|
|
for (int i = 0; i < colWidths.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ws.Column(i + 1).Width = colWidths[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int row = 2;
|
|
|
|
|
|
var personRegGroups = allPersons.GroupBy(p => p.RegistrationId).ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var reg in registrations)
|
|
|
|
|
|
{
|
|
|
|
|
|
var regPersons = personRegGroups.TryGetValue(reg.Id, out var rp) ? rp : new List<EventRegistrationPerson>();
|
|
|
|
|
|
var athletes = regPersons.Where(p => p.PersonType == PersonType.Athlete).ToList();
|
|
|
|
|
|
bool isPersonal = reg.RegistrationMode == 1;
|
|
|
|
|
|
string type = isPersonal ? "个人" : "单位";
|
|
|
|
|
|
|
|
|
|
|
|
string unitName = "/";
|
|
|
|
|
|
string teamName = reg.TeamName ?? "";
|
|
|
|
|
|
string leaderName = reg.ContactName ?? "";
|
|
|
|
|
|
string leaderPhone = reg.ContactPhone ?? "";
|
|
|
|
|
|
string coachNames = "/";
|
|
|
|
|
|
string coachPhones = "/";
|
|
|
|
|
|
int personCount = 1;
|
|
|
|
|
|
string paymentStatus = reg.PaymentStatus == 1 ? "已支付" : "未支付";
|
|
|
|
|
|
|
|
|
|
|
|
if (isPersonal)
|
|
|
|
|
|
{
|
|
|
|
|
|
var athlete = athletes.FirstOrDefault();
|
|
|
|
|
|
if (athlete != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
teamName = athlete.Name ?? reg.TeamName ?? "";
|
|
|
|
|
|
leaderName = athlete.Name ?? reg.ContactName ?? "";
|
|
|
|
|
|
leaderPhone = athlete.User?.Mobile ?? athlete.Phone ?? reg.ContactPhone ?? "";
|
|
|
|
|
|
}
|
|
|
|
|
|
personCount = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2026-04-21 20:39:24 +08:00
|
|
|
|
{
|
2026-06-28 20:32:27 +08:00
|
|
|
|
if (reg.MerchantCertificationId.HasValue && merchantNameDict.TryGetValue(reg.MerchantCertificationId.Value, out var un))
|
|
|
|
|
|
{
|
|
|
|
|
|
unitName = un;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var regAthleteUserIds = athletes.Where(p => p.UserId > 0).Select(p => p.UserId).Distinct().ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var regTeamIds = new HashSet<long>();
|
|
|
|
|
|
foreach (var uid in regAthleteUserIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (userToTeamIdsDict.TryGetValue(uid, out var tids))
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var tid in tids) regTeamIds.Add(tid);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var allLeaders = new List<YD_Event.Core.Entities.AppUser>();
|
|
|
|
|
|
var allCoaches = new List<YD_Event.Core.Entities.AppUser>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var tid in regTeamIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (teamLeadersDict.TryGetValue(tid, out var leaders)) allLeaders.AddRange(leaders);
|
|
|
|
|
|
if (teamCoachesDict.TryGetValue(tid, out var coaches)) allCoaches.AddRange(coaches);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
allLeaders = allLeaders.GroupBy(l => l.Id).Select(g => g.First()).ToList();
|
|
|
|
|
|
allCoaches = allCoaches.GroupBy(c => c.Id).Select(g => g.First()).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
var regLeaders = regPersons.Where(p => p.PersonType == PersonType.Leader).ToList();
|
|
|
|
|
|
if (!allLeaders.Any() && regLeaders.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
allLeaders = regLeaders.Select(l => new YD_Event.Core.Entities.AppUser { Name = l.Name, Mobile = l.Phone }).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var regCoaches = regPersons.Where(p => p.PersonType == PersonType.Coach).ToList();
|
|
|
|
|
|
if (!allCoaches.Any() && regCoaches.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
allCoaches = regCoaches.Select(c => new YD_Event.Core.Entities.AppUser { Name = c.Name, Mobile = c.Phone }).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (allLeaders.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
leaderName = string.Join("\n", allLeaders.Select(l => l.Name ?? "").Where(n => !string.IsNullOrEmpty(n)));
|
|
|
|
|
|
leaderPhone = string.Join("\n", allLeaders.Select(l => l.Mobile ?? "").Where(m => !string.IsNullOrEmpty(m)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (allCoaches.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
coachNames = string.Join("\n", allCoaches.Select(c => c.Name ?? "").Where(n => !string.IsNullOrEmpty(n)));
|
|
|
|
|
|
coachPhones = string.Join("\n", allCoaches.Select(c => c.Mobile ?? "").Where(m => !string.IsNullOrEmpty(m)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var uniqueIds = athletes.Where(p => p.UserId > 0).Select(p => p.UserId).Distinct().Count();
|
|
|
|
|
|
personCount = uniqueIds > 0 ? uniqueIds : athletes.Count;
|
|
|
|
|
|
if (personCount == 0) personCount = reg.TotalPersonCount;
|
2026-04-21 20:39:24 +08:00
|
|
|
|
}
|
2026-06-28 20:32:27 +08:00
|
|
|
|
|
|
|
|
|
|
ws.Cells[row, 1].Value = type;
|
|
|
|
|
|
ws.Cells[row, 2].Value = unitName;
|
|
|
|
|
|
ws.Cells[row, 3].Value = teamName;
|
|
|
|
|
|
ws.Cells[row, 4].Value = leaderName;
|
|
|
|
|
|
ws.Cells[row, 5].Value = leaderPhone;
|
|
|
|
|
|
ws.Cells[row, 6].Value = coachNames;
|
|
|
|
|
|
ws.Cells[row, 7].Value = coachPhones;
|
|
|
|
|
|
ws.Cells[row, 8].Value = personCount;
|
|
|
|
|
|
ws.Cells[row, 9].Value = paymentStatus;
|
|
|
|
|
|
|
|
|
|
|
|
var dataRange = ws.Cells[row, 1, row, headers.Length];
|
|
|
|
|
|
dataRange.Style.Font.Size = 14;
|
|
|
|
|
|
dataRange.Style.Font.Name = "宋体";
|
|
|
|
|
|
dataRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
|
|
|
|
|
dataRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
|
|
|
|
|
dataRange.Style.WrapText = true;
|
|
|
|
|
|
dataRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
dataRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
dataRange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
dataRange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
|
|
|
|
|
|
ws.Row(row).Height = 30;
|
|
|
|
|
|
row++;
|
2026-04-21 20:39:24 +08:00
|
|
|
|
}
|
2026-06-28 20:32:27 +08:00
|
|
|
|
}
|
2026-04-21 20:39:24 +08:00
|
|
|
|
|
2026-06-28 20:32:27 +08:00
|
|
|
|
private void BuildPersonSheet(
|
|
|
|
|
|
ExcelPackage package,
|
|
|
|
|
|
List<EventRegistration> registrations,
|
|
|
|
|
|
List<EventRegistrationPerson> allPersons,
|
|
|
|
|
|
Dictionary<long, string> merchantNameDict)
|
|
|
|
|
|
{
|
|
|
|
|
|
var ws = package.Workbook.Worksheets.Add("人员信息");
|
|
|
|
|
|
|
|
|
|
|
|
string[] headers = { "类型", "参赛单位", "参赛队伍", "参赛队员", "身份证号", "出生年月日", "性别", "项目", "组别" };
|
|
|
|
|
|
for (int i = 0; i < headers.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ws.Cells[1, i + 1].Value = headers[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var headerRange = ws.Cells[1, 1, 1, headers.Length];
|
|
|
|
|
|
headerRange.Style.Font.Bold = true;
|
|
|
|
|
|
headerRange.Style.Font.Size = 14;
|
|
|
|
|
|
headerRange.Style.Font.Name = "宋体";
|
|
|
|
|
|
headerRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
|
|
|
|
|
headerRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
|
|
|
|
|
headerRange.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
|
|
|
|
|
headerRange.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(217, 226, 243));
|
|
|
|
|
|
headerRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
headerRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
headerRange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
headerRange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
ws.Row(1).Height = 30;
|
|
|
|
|
|
|
|
|
|
|
|
double[] colWidths = { 13.4, 30.4, 29.7, 26.4, 32.1, 23.1, 13, 56.8, 23.1 };
|
|
|
|
|
|
for (int i = 0; i < colWidths.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ws.Column(i + 1).Width = colWidths[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ws.Column(5).Style.Numberformat.Format = "@";
|
|
|
|
|
|
|
|
|
|
|
|
int row = 2;
|
|
|
|
|
|
var personGroups = allPersons
|
|
|
|
|
|
.Where(p => p.PersonType == PersonType.Athlete)
|
|
|
|
|
|
.GroupBy(p => p.RegistrationId)
|
|
|
|
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var reg in registrations)
|
2026-04-20 13:01:39 +08:00
|
|
|
|
{
|
2026-06-28 20:32:27 +08:00
|
|
|
|
bool isPersonal = reg.RegistrationMode == 1;
|
|
|
|
|
|
string type = isPersonal ? "个人" : "单位";
|
|
|
|
|
|
|
|
|
|
|
|
string unitName = "/";
|
|
|
|
|
|
if (!isPersonal && reg.MerchantCertificationId.HasValue && merchantNameDict.TryGetValue(reg.MerchantCertificationId.Value, out var un))
|
2026-04-20 13:01:39 +08:00
|
|
|
|
{
|
2026-06-28 20:32:27 +08:00
|
|
|
|
unitName = un;
|
2026-04-20 13:01:39 +08:00
|
|
|
|
}
|
2026-06-29 14:34:57 +08:00
|
|
|
|
string teamName = reg.TeamName ?? "/";
|
2026-06-28 20:32:27 +08:00
|
|
|
|
|
|
|
|
|
|
if (!personGroups.TryGetValue(reg.Id, out var athletes)) continue;
|
2026-04-20 13:01:39 +08:00
|
|
|
|
|
2026-06-28 20:32:27 +08:00
|
|
|
|
var personProjectGroups = new Dictionary<string, List<EventRegistrationPerson>>();
|
|
|
|
|
|
foreach (var athlete in athletes)
|
2026-02-08 18:09:05 +08:00
|
|
|
|
{
|
2026-06-28 20:32:27 +08:00
|
|
|
|
string key = athlete.UserId > 0
|
|
|
|
|
|
? $"u_{athlete.UserId}"
|
|
|
|
|
|
: $"c_{(athlete.IdCard ?? athlete.Name ?? "")}_{athlete.Name ?? ""}";
|
|
|
|
|
|
if (!personProjectGroups.ContainsKey(key))
|
|
|
|
|
|
personProjectGroups[key] = new List<EventRegistrationPerson>();
|
|
|
|
|
|
personProjectGroups[key].Add(athlete);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var kvp in personProjectGroups)
|
|
|
|
|
|
{
|
|
|
|
|
|
var projects = kvp.Value;
|
|
|
|
|
|
var firstPerson = projects.OrderBy(p => p.Id).First();
|
|
|
|
|
|
|
|
|
|
|
|
string certNumber = firstPerson.IdCard ?? "";
|
|
|
|
|
|
string personName = firstPerson.Name ?? "";
|
|
|
|
|
|
bool isIdCard = !string.IsNullOrEmpty(certNumber) && certNumber.Length == 18;
|
|
|
|
|
|
|
|
|
|
|
|
for (int projIdx = 0; projIdx < projects.Count; projIdx++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var projPerson = projects[projIdx];
|
|
|
|
|
|
bool isFirstRow = projIdx == 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (isFirstRow)
|
|
|
|
|
|
{
|
|
|
|
|
|
ws.Cells[row, 1].Value = type;
|
|
|
|
|
|
ws.Cells[row, 2].Value = unitName;
|
|
|
|
|
|
ws.Cells[row, 3].Value = teamName;
|
|
|
|
|
|
ws.Cells[row, 4].Value = personName;
|
|
|
|
|
|
ws.Cells[row, 5].Value = certNumber;
|
|
|
|
|
|
|
|
|
|
|
|
if (isIdCard)
|
|
|
|
|
|
{
|
|
|
|
|
|
ws.Cells[row, 6].Formula = $"=DATE(MID(E{row},7,4),MID(E{row},11,2),MID(E{row},13,2))";
|
|
|
|
|
|
ws.Cells[row, 6].Style.Numberformat.Format = "yyyy-MM-dd";
|
|
|
|
|
|
ws.Cells[row, 7].Formula = $"=IF(MOD(MID($E{row},17,1),2),\"男\",\"女\")";
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
string genderText = firstPerson.Gender switch
|
|
|
|
|
|
{
|
|
|
|
|
|
Gender.Male => "男",
|
|
|
|
|
|
Gender.Female => "女",
|
|
|
|
|
|
_ => ""
|
|
|
|
|
|
};
|
|
|
|
|
|
ws.Cells[row, 6].Value = firstPerson.BirthDate.ToString("yyyy-MM-dd");
|
|
|
|
|
|
ws.Cells[row, 7].Value = genderText;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ws.Cells[row, 8].Value = projPerson.ProjectName ?? "";
|
|
|
|
|
|
ws.Cells[row, 9].Value = projPerson.GroupName ?? "";
|
|
|
|
|
|
|
|
|
|
|
|
var dataRange = ws.Cells[row, 1, row, headers.Length];
|
|
|
|
|
|
dataRange.Style.Font.Size = 14;
|
|
|
|
|
|
dataRange.Style.Font.Name = "宋体";
|
|
|
|
|
|
dataRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
|
|
|
|
|
dataRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
|
|
|
|
|
dataRange.Style.WrapText = true;
|
|
|
|
|
|
dataRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
dataRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
dataRange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
dataRange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
|
|
|
|
|
|
|
|
|
|
|
|
ws.Row(row).Height = 35;
|
|
|
|
|
|
row++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|
2026-03-08 15:46:19 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取素材下载链接
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("获取素材下载链接")]
|
2026-03-19 23:36:06 +08:00
|
|
|
|
public async Task<List<string>> MaterialUrls(List<long> ids)
|
2026-03-08 15:46:19 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (ids == null || !ids.Any()) return new List<string>();
|
|
|
|
|
|
|
|
|
|
|
|
return await _repository.AsQueryable()
|
|
|
|
|
|
.Where(x => ids.Contains(x.Id) && !string.IsNullOrEmpty(x.MaterialUrl))
|
|
|
|
|
|
.Select(x => x.MaterialUrl)
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
}
|
2026-04-09 09:09:50 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 单条支付审核
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
|
[DisplayName("单条支付审核")]
|
|
|
|
|
|
public async Task PaymentAudit(PaymentAuditInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var registration = await _repository.GetByIdAsync(input.RegistrationId);
|
|
|
|
|
|
if (registration == null) throw Oops.Bah("报名记录不存在");
|
|
|
|
|
|
|
|
|
|
|
|
if (registration.PaymentStatus != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw Oops.Bah("该记录已完成支付审核,无需重复操作");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 06:58:21 +08:00
|
|
|
|
var now = DateTime.Now;
|
2026-04-09 09:09:50 +08:00
|
|
|
|
registration.PaymentStatus = input.PaymentStatus;
|
|
|
|
|
|
registration.ReviewRemark = input.ReviewRemark;
|
2026-04-13 06:58:21 +08:00
|
|
|
|
registration.ReviewTime = now;
|
2026-04-09 09:09:50 +08:00
|
|
|
|
registration.Reviewer = _authManager.Account;
|
2026-04-13 06:58:21 +08:00
|
|
|
|
registration.UpdatedTime = now;
|
2026-04-09 09:09:50 +08:00
|
|
|
|
registration.UpdatedUserId = _authManager.UserId;
|
|
|
|
|
|
|
|
|
|
|
|
if (input.PaymentStatus == 1)
|
|
|
|
|
|
{
|
2026-04-13 06:58:21 +08:00
|
|
|
|
registration.PaymentTime = now;
|
2026-04-21 09:40:58 +08:00
|
|
|
|
// 确保订单存在
|
|
|
|
|
|
await CreateOrderIfNotExistsAsync(registration, now);
|
2026-04-09 09:09:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await _repository.UpdateAsync(registration);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 批量支付审核
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[UnitOfWork]
|
|
|
|
|
|
[DisplayName("批量支付审核")]
|
|
|
|
|
|
public async Task BatchPaymentAudit(BatchPaymentAuditInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (input.Ids == null || !input.Ids.Any()) return;
|
|
|
|
|
|
|
|
|
|
|
|
var registrations = await _repository.GetListAsync(x => input.Ids.Contains(x.Id));
|
|
|
|
|
|
if (!registrations.Any()) throw Oops.Bah("未找到有效的报名记录");
|
|
|
|
|
|
|
|
|
|
|
|
var invalidRegistrations = registrations
|
2026-04-09 23:12:51 +08:00
|
|
|
|
.Where(r => r.PaymentStatus != 0)
|
2026-04-09 09:09:50 +08:00
|
|
|
|
.ToList();
|
|
|
|
|
|
if (invalidRegistrations.Any())
|
|
|
|
|
|
{
|
2026-04-09 23:12:51 +08:00
|
|
|
|
throw Oops.Bah("存在非待审核状态的记录,无法进行支付审核");
|
2026-04-09 09:09:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var now = DateTime.Now;
|
2026-04-21 09:40:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果审核通过(PaymentStatus = 1),先处理订单创建
|
|
|
|
|
|
if (input.PaymentStatus == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var registration in registrations)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 确保订单存在
|
|
|
|
|
|
await CreateOrderIfNotExistsAsync(registration, now);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新报名记录的状态
|
2026-04-09 09:09:50 +08:00
|
|
|
|
await _repository.UpdateAsync(x => new EventRegistration
|
|
|
|
|
|
{
|
|
|
|
|
|
PaymentStatus = input.PaymentStatus,
|
|
|
|
|
|
ReviewRemark = input.ReviewRemark,
|
|
|
|
|
|
ReviewTime = now,
|
|
|
|
|
|
Reviewer = _authManager.Account,
|
|
|
|
|
|
UpdatedTime = now,
|
|
|
|
|
|
UpdatedUserId = _authManager.UserId,
|
|
|
|
|
|
PaymentTime = input.PaymentStatus == 1 ? now : x.PaymentTime
|
|
|
|
|
|
}, x => input.Ids.Contains(x.Id));
|
|
|
|
|
|
}
|
2026-05-15 13:46:58 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取报名统计数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("获取报名统计数据")]
|
|
|
|
|
|
[HttpGet("stats/{eventId}")]
|
|
|
|
|
|
public async Task<RegistrationStatsOutput> GetStats(long eventId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var stats = new RegistrationStatsOutput();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取赛事项目信息
|
|
|
|
|
|
var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId);
|
|
|
|
|
|
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
|
|
|
|
|
|
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
|
|
|
|
|
|
var eventProjectToProjectDict = eventProjects.ToDictionary(x => x.Id, x => x.ProjectId);
|
|
|
|
|
|
var projectDict = projects.ToDictionary(x => x.Id, x => x);
|
|
|
|
|
|
|
|
|
|
|
|
// 统计符合条件的所有运动员(与赛事编排页面保持一致的筛选规则)
|
|
|
|
|
|
var athletes = await _personRepository.AsQueryable()
|
|
|
|
|
|
.InnerJoin<EventRegistration>((p, r) => p.RegistrationId == r.Id)
|
|
|
|
|
|
.Where((p, r) => r.EventId == eventId
|
2026-06-25 20:51:40 +08:00
|
|
|
|
&& !r.DeleteMark // 排除软删除的报名记录
|
2026-05-15 13:46:58 +08:00
|
|
|
|
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
|
|
|
|
|
|
&& ((r.PaymentMethod == 2 && r.PaymentStatus == 1) || (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)))
|
|
|
|
|
|
&& !p.DeleteMark // 排除已删除的人员
|
|
|
|
|
|
&& p.PersonType == PersonType.Athlete) // 只统计运动员
|
|
|
|
|
|
.Select((p, r) => new
|
|
|
|
|
|
{
|
|
|
|
|
|
PersonId = p.Id,
|
|
|
|
|
|
EventProjectId = p.ProjectId,
|
|
|
|
|
|
GroupName = p.GroupName,
|
|
|
|
|
|
TeamName = r.TeamName,
|
|
|
|
|
|
RegistrationItemId = p.RegistrationItemId,
|
|
|
|
|
|
UserId = p.UserId,
|
|
|
|
|
|
RegistrationId = r.Id,
|
|
|
|
|
|
PaymentMethod = r.PaymentMethod,
|
|
|
|
|
|
PaymentStatus = r.PaymentStatus
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
if (!athletes.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
// 待审核数量:线下支付(1)且支付状态为0(待审核)
|
|
|
|
|
|
var pendingRegistrations = await _repository.GetListAsync(x =>
|
|
|
|
|
|
x.EventId == eventId && !x.DeleteMark && x.PaymentMethod == 1 && x.PaymentStatus == 0);
|
|
|
|
|
|
stats.PendingReview = pendingRegistrations.Count;
|
|
|
|
|
|
return stats;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算总人数(所有符合条件的运动员)
|
|
|
|
|
|
stats.TotalParticipants = athletes.Select(x => x.UserId > 0 ? x.UserId : x.PersonId).Distinct().Count();
|
|
|
|
|
|
|
|
|
|
|
|
// 分别计算个人项目和团队项目的统计数据
|
|
|
|
|
|
var individualSet = new HashSet<long>();
|
|
|
|
|
|
var teamSet = new HashSet<(long EventProjectId, string GroupName, string TeamName, long RegistrationItemId)>();
|
|
|
|
|
|
var teamPersonSet = new HashSet<long>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var athlete in athletes)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 判断项目类型
|
|
|
|
|
|
bool isIndividualProject = false;
|
|
|
|
|
|
if (eventProjectToProjectDict.TryGetValue(athlete.EventProjectId, out var projectId) &&
|
|
|
|
|
|
projectDict.TryGetValue(projectId, out var project))
|
|
|
|
|
|
{
|
|
|
|
|
|
isIndividualProject = YD_Event.Core.Extensions.ProjectTypeExtensions.IsIndividualProject(project);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var personKey = athlete.UserId > 0 ? athlete.UserId : athlete.PersonId;
|
|
|
|
|
|
|
|
|
|
|
|
if (isIndividualProject)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 个人项目:统计人数
|
|
|
|
|
|
individualSet.Add(personKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 团队项目:统计团队数(与赛事编排一致,按 EventProjectId、GroupName、TeamName、RegistrationItemId 分组)
|
|
|
|
|
|
if (!string.IsNullOrEmpty(athlete.TeamName))
|
|
|
|
|
|
{
|
|
|
|
|
|
teamSet.Add((
|
|
|
|
|
|
athlete.EventProjectId,
|
|
|
|
|
|
athlete.GroupName ?? "",
|
|
|
|
|
|
athlete.TeamName,
|
|
|
|
|
|
athlete.RegistrationItemId
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 团队项目人数
|
|
|
|
|
|
teamPersonSet.Add(personKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stats.IndividualParticipants = individualSet.Count;
|
|
|
|
|
|
stats.TeamCount = teamSet.Count;
|
|
|
|
|
|
stats.TeamParticipants = teamPersonSet.Count;
|
|
|
|
|
|
|
|
|
|
|
|
// 待审核数量:线下支付(1)且支付状态为0(待审核)
|
|
|
|
|
|
var pendingRegs = await _repository.GetListAsync(x =>
|
|
|
|
|
|
x.EventId == eventId && !x.DeleteMark && x.PaymentMethod == 1 && x.PaymentStatus == 0);
|
|
|
|
|
|
stats.PendingReview = pendingRegs.Count;
|
|
|
|
|
|
|
|
|
|
|
|
return stats;
|
|
|
|
|
|
}
|
2026-02-08 18:09:05 +08:00
|
|
|
|
}
|