468 lines
20 KiB
C#
468 lines
20 KiB
C#
using YD_Event.Application.Event.Dtos;
|
||
using YD_Event.Application.Auth;
|
||
using YD_Event.Core.Entities;
|
||
using MiniExcelLibs;
|
||
|
||
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 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,
|
||
AuthManager authManager) : base(repository)
|
||
{
|
||
_repository = repository;
|
||
_itemRepository = itemRepository;
|
||
_personRepository = personRepository;
|
||
_paymentRepository = paymentRepository;
|
||
_orderRepository = orderRepository;
|
||
_teamMemberRepository = teamMemberRepository;
|
||
_appUserRepository = appUserRepository;
|
||
_authManager = authManager;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分页查询报名
|
||
/// </summary>
|
||
[DisplayName("分页查询报名")]
|
||
public async Task<PageResult<RegistrationListOutput>> Page(RegistrationQueryInput input)
|
||
{
|
||
var query = _repository.AsQueryable()
|
||
.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,
|
||
UnitName = x.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);
|
||
|
||
// 填充下拉详情
|
||
if (result.Rows.Any())
|
||
{
|
||
var regIds = result.Rows.Select(x => x.Id).ToList();
|
||
var items = await _itemRepository.GetListAsync(x => regIds.Contains(x.RegistrationId));
|
||
// 使用关联查询获取报名人员及其关联的用户信息
|
||
var persons = await _personRepository.AsQueryable()
|
||
.Includes(p => p.User) // 关联查询用户信息
|
||
.Where(x => regIds.Contains(x.RegistrationId))
|
||
.ToListAsync();
|
||
|
||
// 获取所有报名人员的 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 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();
|
||
|
||
// 构造参与人信息字符串,可以使用关联的 User 信息
|
||
var personInfo = string.Join(";", myPersons.Select(p =>
|
||
{
|
||
// 将性别转换为中文显示
|
||
var genderText = p.Gender switch
|
||
{
|
||
YD_Event.Core.Enum.Gender.Male => "男",
|
||
YD_Event.Core.Enum.Gender.Female => "女",
|
||
_ => "未知"
|
||
};
|
||
|
||
// 如果有关联的用户信息,可以使用更完整的信息
|
||
if (p.User != null)
|
||
{
|
||
return $"{p.Name}({p.User.Mobile})/{genderText}/{p.Age}";
|
||
}
|
||
return $"{p.Name}/{genderText}/{p.Age}/{p.IdCard}";
|
||
}));
|
||
if (personInfo.Length > 100) personInfo = personInfo.Substring(0, 97) + "..."; // 截断
|
||
|
||
var detail = new RegistrationItemDetailDto
|
||
{
|
||
ProjectName = item.ProjectName,
|
||
GroupName = item.GroupName,
|
||
ParticipantInfo = personInfo
|
||
};
|
||
dto.Items.Add(detail);
|
||
}
|
||
|
||
// 设置领队信息和教练信息 - 从团队中获取
|
||
var regPersons = persons.Where(p => p.RegistrationId == dto.Id).ToList();
|
||
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>
|
||
[UnitOfWork]
|
||
[DisplayName("批量审核")]
|
||
public async Task BatchAudit(BatchAuditInput input)
|
||
{
|
||
if (input.Ids == null || !input.Ids.Any()) return;
|
||
|
||
// 更新报名记录的支付状态
|
||
await _repository.UpdateAsync(x => new EventRegistration
|
||
{
|
||
PaymentStatus = input.PaymentStatus,
|
||
ReviewRemark = input.ReviewRemark,
|
||
ReviewTime = DateTime.Now
|
||
}, x => input.Ids.Contains(x.Id));
|
||
|
||
// 如果审核通过(PaymentStatus = 1),同步更新相关订单的状态为已支付
|
||
if (input.PaymentStatus == 1)
|
||
{
|
||
// 获取需要更新的报名记录
|
||
var registrations = await _repository.GetListAsync(x => input.Ids.Contains(x.Id));
|
||
|
||
// 提取所有订单号
|
||
var orderNos = registrations
|
||
.Where(r => !string.IsNullOrEmpty(r.OrderNo))
|
||
.Select(r => r.OrderNo)
|
||
.ToList();
|
||
|
||
if (orderNos.Any())
|
||
{
|
||
var now = DateTime.Now;
|
||
// 更新相关订单的状态为已支付
|
||
await _orderRepository.UpdateAsync(x => new YD_Event.Core.Entities.Order
|
||
{
|
||
OrderStatus = YD_Event.Core.Enum.OrderStatus.Paid,
|
||
PaymentTime = now,
|
||
UpdatedTime = now,
|
||
UpdatedUserId = _authManager.UserId
|
||
}, x => orderNos.Contains(x.OrderNo));
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出报名
|
||
/// </summary>
|
||
[DisplayName("导出报名")]
|
||
public async Task<byte[]> Export(RegistrationQueryInput input)
|
||
{
|
||
var list = await _repository.AsQueryable()
|
||
.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.PaymentStatus.HasValue, x => x.PaymentStatus == input.PaymentStatus)
|
||
.Select(x => new
|
||
{
|
||
x.RegistrationMode,
|
||
x.TeamName,
|
||
x.ContactName,
|
||
x.ContactPhone,
|
||
x.TotalPersonCount,
|
||
x.PaymentMethod,
|
||
x.TotalAmount,
|
||
x.PaymentStatus,
|
||
x.RegistrationTime
|
||
})
|
||
.ToListAsync();
|
||
|
||
using var stream = new MemoryStream();
|
||
stream.SaveAs(list);
|
||
return stream.ToArray();
|
||
}
|
||
|
||
/// <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 _repository.UpdateAsync(registration);
|
||
|
||
// 如果审核通过(PaymentStatus = 1),同步更新相关订单的状态为已支付
|
||
if (input.PaymentStatus == 1 && !string.IsNullOrEmpty(registration.OrderNo))
|
||
{
|
||
// 更新相关订单的状态为已支付
|
||
await _orderRepository.UpdateAsync(x => new YD_Event.Core.Entities.Order
|
||
{
|
||
OrderStatus = YD_Event.Core.Enum.OrderStatus.Paid,
|
||
PaymentTime = now,
|
||
UpdatedTime = now,
|
||
UpdatedUserId = _authManager.UserId
|
||
}, x => x.OrderNo == registration.OrderNo);
|
||
}
|
||
}
|
||
|
||
/// <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;
|
||
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));
|
||
|
||
// 如果审核通过(PaymentStatus = 1),同步更新相关订单的状态为已支付
|
||
if (input.PaymentStatus == 1)
|
||
{
|
||
// 提取所有订单号
|
||
var orderNos = registrations
|
||
.Where(r => !string.IsNullOrEmpty(r.OrderNo))
|
||
.Select(r => r.OrderNo)
|
||
.ToList();
|
||
|
||
if (orderNos.Any())
|
||
{
|
||
// 更新相关订单的状态为已支付
|
||
await _orderRepository.UpdateAsync(x => new YD_Event.Core.Entities.Order
|
||
{
|
||
OrderStatus = YD_Event.Core.Enum.OrderStatus.Paid,
|
||
PaymentTime = now,
|
||
UpdatedTime = now,
|
||
UpdatedUserId = _authManager.UserId
|
||
}, x => orderNos.Contains(x.OrderNo));
|
||
}
|
||
}
|
||
}
|
||
}
|