YD_Event.Admin/YD_Event.Application/Event/EventRegistrationService.cs

1107 lines
50 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

using YD_Event.Application.Event.Dtos;
using YD_Event.Application.Auth;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using YD_Event.Core.Domain;
using Microsoft.AspNetCore.Mvc;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using System.Drawing;
namespace YD_Event.Application.Event;
/// <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;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
private readonly ISqlSugarRepository<TeamMember> _teamMemberRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _appUserRepository;
private readonly ISqlSugarRepository<MerchantCertification> _merchantCertificationRepository;
private readonly ISqlSugarRepository<EventInfo> _eventInfoRepository;
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
private readonly IIdGenerator _idGenerator;
private readonly OrderNoGeneratorService _orderNoGeneratorService;
private readonly AuthManager _authManager;
public EventRegistrationService(
ISqlSugarRepository<EventRegistration> repository,
ISqlSugarRepository<EventRegistrationItem> itemRepository,
ISqlSugarRepository<EventRegistrationPerson> personRepository,
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarRepository<TeamMember> teamMemberRepository,
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> appUserRepository,
ISqlSugarRepository<MerchantCertification> merchantCertificationRepository,
ISqlSugarRepository<EventInfo> eventInfoRepository,
ISqlSugarRepository<EventProject> eventProjectRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
IIdGenerator idGenerator,
OrderNoGeneratorService orderNoGeneratorService,
AuthManager authManager) : base(repository)
{
_repository = repository;
_itemRepository = itemRepository;
_personRepository = personRepository;
_paymentRepository = paymentRepository;
_orderRepository = orderRepository;
_teamMemberRepository = teamMemberRepository;
_appUserRepository = appUserRepository;
_merchantCertificationRepository = merchantCertificationRepository;
_eventInfoRepository = eventInfoRepository;
_eventProjectRepository = eventProjectRepository;
_projectRepository = projectRepository;
_idGenerator = idGenerator;
_orderNoGeneratorService = orderNoGeneratorService;
_authManager = authManager;
}
/// <summary>
/// 分页查询报名
/// </summary>
[DisplayName("分页查询报名")]
public async Task<PageResult<RegistrationListOutput>> Page(RegistrationQueryInput input)
{
var query = _repository.AsQueryable()
// 排除软删除的报名记录
.Where(x => !x.DeleteMark)
// 线上支付只显示已支付(1),线下支付显示待审核(0)和已支付(1)
.Where(x => (x.PaymentMethod == 2 && x.PaymentStatus == 1) || (x.PaymentMethod == 1 && (x.PaymentStatus == 0 || x.PaymentStatus == 1)))
.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,
CreatedUserId = x.CreatedUserId, // 保存创建用户ID以便后续查询单位名称
MerchantCertificationId = x.MerchantCertificationId, // 保存单位认证ID以便后续查询单位名称
UnitName = null, // 不初始化为 TeamName,只从单位认证信息中获取
ContactName = x.ContactName,
ContactPhone = x.ContactPhone,
TotalPersonCount = x.TotalPersonCount,
IsUploadMaterial = !string.IsNullOrEmpty(x.MaterialUrl),
PaymentMethod = x.PaymentMethod,
PaymentMethodType = x.PaymentMethodType,
OrderNo = x.OrderNo,
TotalAmount = x.TotalAmount,
PaymentStatus = x.PaymentStatus,
PaymentProofUrl = x.PaymentProofUrl,
RegistrationTime = x.RegistrationTime
});
var result = await query.ToPagedListAsync(input);
// 获取所有报名记录的 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);
}
// 填充下拉详情
if (result.Rows.Any())
{
var items = await _itemRepository.GetListAsync(x => regIds.Contains(x.RegistrationId) && !x.DeleteMark);
// 使用关联查询获取报名人员及其关联的用户信息
var persons = await _personRepository.AsQueryable()
.Includes(p => p.User) // 关联查询用户信息
.Where(x => regIds.Contains(x.RegistrationId) && !x.DeleteMark)
.ToListAsync();
// 获取所有赛事项目和项目信息,用于判断项目类型
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);
// 获取所有报名人员的 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;
}
}
}
foreach (var dto in result.Rows)
{
// 获取当前报名的所有人员
var regPersons = persons.Where(p => p.RegistrationId == dto.Id).ToList();
// 计算去重后的人数(按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);
// 根据报名记录的 MerchantCertificationId 获取单位名称
if (dto.MerchantCertificationId.HasValue &&
merchantNameDict.TryGetValue(dto.MerchantCertificationId.Value, out var unitName))
{
dto.UnitName = unitName;
}
else
{
dto.UnitName = null;
}
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();
// 判断项目类型
bool isIndividualProject = false;
if (eventProjectToProjectDict.TryGetValue(item.ProjectId, out var projectId) &&
projectDict.TryGetValue(projectId, out var project))
{
isIndividualProject = YD_Event.Core.Extensions.ProjectTypeExtensions.IsIndividualProject(project);
}
if (isIndividualProject)
{
// 个人项目:为每个运动员创建一个条目,使用 EventRegistrationPerson.Id
foreach (var person in myPersons.Where(p => p.PersonType == PersonType.Athlete))
{
// 构造参与人信息字符串
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}";
var detail = new RegistrationItemDetailDto
{
Id = person.Id, // 使用 EventRegistrationPerson.Id
ProjectName = item.ProjectName,
GroupName = item.GroupName,
ParticipantInfo = personInfo
};
dto.Items.Add(detail);
}
}
else
{
// 团队项目:保持原有逻辑,使用 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);
}
}
// 设置领队信息和教练信息 - 从团队中获取
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()
? string.Join(",", allLeaders.Select(l => $"{l.Name}/{getGenderText(l.Gender)}/{l.Mobile}"))
: null;
dto.CoachInfo = allCoaches.Any()
? string.Join(",", allCoaches.Select(c => $"{c.Name}/{getGenderText(c.Gender)}/{c.Mobile}"))
: null;
}
}
return result;
}
/// <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);
}
}
}
/// <summary>
/// 批量审核
/// </summary>
[UnitOfWork]
[DisplayName("批量审核")]
public async Task BatchAudit(BatchAuditInput input)
{
if (input.Ids == null || !input.Ids.Any()) return;
var now = DateTime.Now;
// 如果审核通过(PaymentStatus = 1),先处理订单创建和更新
if (input.PaymentStatus == 1)
{
// 获取需要更新的报名记录
var registrations = await _repository.GetListAsync(x => input.Ids.Contains(x.Id));
foreach (var registration in registrations)
{
// 确保订单存在
await CreateOrderIfNotExistsAsync(registration, now);
// 更新报名记录的支付状态
registration.PaymentStatus = input.PaymentStatus;
registration.ReviewRemark = input.ReviewRemark;
registration.ReviewTime = now;
registration.Reviewer = _authManager.Account;
registration.PaymentTime = now;
registration.UpdatedTime = now;
registration.UpdatedUserId = _authManager.UserId;
await _repository.UpdateAsync(registration);
}
}
else
{
// 审核不通过,只更新状态
await _repository.UpdateAsync(x => new EventRegistration
{
PaymentStatus = input.PaymentStatus,
ReviewRemark = input.ReviewRemark,
ReviewTime = now,
Reviewer = _authManager.Account,
UpdatedTime = now,
UpdatedUserId = _authManager.UserId
}, x => input.Ids.Contains(x.Id));
}
}
/// <summary>
/// 导出报名信息
/// </summary>
[DisplayName("导出报名")]
public async Task<IActionResult> Export(RegistrationQueryInput input)
{
var registrations = await _repository.AsQueryable()
.Where(x => !x.DeleteMark)
.Where(x => (x.PaymentMethod == 2 && x.PaymentStatus == 1) || (x.PaymentMethod == 1 && (x.PaymentStatus == 0 || x.PaymentStatus == 1)))
.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.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)
.OrderBy(x => x.RegistrationMode, OrderByType.Asc)
.OrderBy(x => x.RegistrationTime, OrderByType.Desc)
.ToListAsync();
if (!registrations.Any())
{
throw Oops.Bah("暂无数据可导出");
}
var regIds = registrations.Select(r => r.Id).ToList();
var merchantCertIds = registrations
.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);
}
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())
{
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
{
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;
}
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++;
}
}
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)
{
bool isPersonal = reg.RegistrationMode == 1;
string type = isPersonal ? "个人" : "单位";
string unitName = "/";
if (!isPersonal && reg.MerchantCertificationId.HasValue && merchantNameDict.TryGetValue(reg.MerchantCertificationId.Value, out var un))
{
unitName = un;
}
string teamName = reg.TeamName ?? "/";
if (!personGroups.TryGetValue(reg.Id, out var athletes)) continue;
var personProjectGroups = new Dictionary<string, List<EventRegistrationPerson>>();
foreach (var athlete in athletes)
{
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++;
}
}
}
}
/// <summary>
/// 获取素材下载链接
/// </summary>
[DisplayName("获取素材下载链接")]
public async Task<List<string>> MaterialUrls(List<long> ids)
{
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();
}
/// <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("该记录已完成支付审核,无需重复操作");
}
var now = DateTime.Now;
registration.PaymentStatus = input.PaymentStatus;
registration.ReviewRemark = input.ReviewRemark;
registration.ReviewTime = now;
registration.Reviewer = _authManager.Account;
registration.UpdatedTime = now;
registration.UpdatedUserId = _authManager.UserId;
if (input.PaymentStatus == 1)
{
registration.PaymentTime = now;
// 确保订单存在
await CreateOrderIfNotExistsAsync(registration, now);
}
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
.Where(r => r.PaymentStatus != 0)
.ToList();
if (invalidRegistrations.Any())
{
throw Oops.Bah("存在非待审核状态的记录,无法进行支付审核");
}
var now = DateTime.Now;
// 如果审核通过(PaymentStatus = 1),先处理订单创建
if (input.PaymentStatus == 1)
{
foreach (var registration in registrations)
{
// 确保订单存在
await CreateOrderIfNotExistsAsync(registration, now);
}
}
// 更新报名记录的状态
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));
}
/// <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)
.InnerJoin<EventRegistrationItem>((p, r, item) => p.RegistrationItemId == item.Id)
.Where((p, r, item) => r.EventId == eventId
&& !r.DeleteMark // 排除软删除的报名记录
// 线上支付只显示已支付(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, item) => new
{
PersonId = p.Id,
EventProjectId = p.ProjectId,
GroupName = p.GroupName,
TeamName = item.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;
}
}