YD_Event.Admin/YD_Event.Application/Event/UserEventRecordService.cs

735 lines
31 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using YD_Event.Application.Event.Dtos;
using YD_Event.Application.Common;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
using Easy.Core;
using SqlSugar;
using Mapster;
using YD_Event.Application.Auth;
using Furion.DynamicApiController;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Authorization;
using System.ComponentModel;
using Furion.FriendlyException;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
using YD_Event.Core.Infrastructure.WeChat;
using YD_Event.Core.Domain;
using Microsoft.Extensions.Logging;
namespace YD_Event.Application.Event;
/// <summary>
/// 用户端参赛记录管理
/// </summary>
public class UserEventRecordService : IDynamicApiController, ITransient
{
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly ISqlSugarRepository<EventRegistrationPerson> _registrationPersonRepository;
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _userRepository;
private readonly ISqlSugarRepository<EventCertificate> _certificateRepository;
private readonly ISqlSugarRepository<WeChatPayConfig> _weChatPayConfigRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
private readonly ISqlSugarRepository<EventInfo> _eventInfoRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
private readonly ISqlSugarClient _db;
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
private readonly FileService _fileService;
private readonly WeChatPayService _weChatPayService;
private readonly OrderNoGeneratorService _orderNoGeneratorService;
private readonly ILogger<UserEventRecordService> _logger;
public UserEventRecordService(
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<EventRegistrationPerson> registrationPersonRepository,
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository,
ISqlSugarRepository<EventCertificate> certificateRepository,
ISqlSugarRepository<WeChatPayConfig> weChatPayConfigRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
ISqlSugarRepository<EventInfo> eventInfoRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
ISqlSugarClient db,
AuthManager authManager,
IIdGenerator idGenerator,
FileService fileService,
WeChatPayService weChatPayService,
OrderNoGeneratorService orderNoGeneratorService,
ILogger<UserEventRecordService> logger)
{
_registrationRepository = registrationRepository;
_registrationPersonRepository = registrationPersonRepository;
_registrationItemRepository = registrationItemRepository;
_projectRepository = projectRepository;
_userRepository = userRepository;
_certificateRepository = certificateRepository;
_weChatPayConfigRepository = weChatPayConfigRepository;
_merchantRepository = merchantRepository;
_db = db;
_authManager = authManager;
_idGenerator = idGenerator;
_fileService = fileService;
_weChatPayService = weChatPayService;
_eventInfoRepository = eventInfoRepository;
_orderRepository = orderRepository;
_orderNoGeneratorService = orderNoGeneratorService;
_logger = logger;
}
/// <summary>
/// 1. 列表查询
/// </summary>
[DisplayName("参赛记录列表查询")]
[HttpGet("list")]
public async Task<PageResult<RegistrationItemDTO>> GetRegistrationList([FromQuery] RegistrationListQueryInput input)
{
var userId = _authManager.UserId;
var list = await _registrationRepository.AsQueryable()
.InnerJoin<EventInfo>((r, e) => r.EventId == e.Id)
.LeftJoin<EventFeeConfig>((r, e, fc) => e.Id == fc.EventId)
.Where((r, e, fc) => r.CreatedUserId == userId && !r.DeleteMark)
.WhereIF(input.EventId.HasValue, (r, e, fc) => r.EventId == input.EventId)
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (r, e, fc) => r.TeamName.Contains(input.Keyword) || e.EventName.Contains(input.Keyword))
.OrderByDescending((r, e, fc) => r.CreatedTime)
.Select((r, e, fc) => new
{
r.Id, r.EventId, e.EventName, r.TeamName, r.PaymentStatus, r.MaterialUrl, r.PaymentProofUrl,
r.CreatedTime, r.UpdatedTime, r.TotalPersonCount, e.MaterialUploadEndTime,
e.CoverImage, e.EventType, e.EventStatus, e.RegistrationStartTime, e.RegistrationEndTime,
e.MatchStartTime, e.MatchEndTime,
fc.ChargeMode
})
.ToPagedListAsync(input);
var resultItems = list.Rows.Select(x => {
// 计算综合状态
int statusValue = 0;
if (x.PaymentStatus == 4)
statusValue = 2; // 已取消
else if (x.PaymentStatus == 0)
statusValue = 0; // 待付款
else if (x.PaymentStatus == 1 && string.IsNullOrEmpty(x.MaterialUrl))
statusValue = 1; // 已支付
else if (x.PaymentStatus == 1 && !string.IsNullOrEmpty(x.MaterialUrl))
statusValue = 4; // 已完成/已报名
else if (x.PaymentStatus == 2)
statusValue = 1; // 审核拒绝
else if (x.PaymentStatus == 3)
statusValue = 3; // 已退款
return new RegistrationItemDTO
{
RegistrationId = x.Id,
EventName = x.EventName,
CoverImage = x.CoverImage,
EventType = (int?)x.EventType,
ChargeMode = (int?)x.ChargeMode,
EventStatus = (int?)x.EventStatus,
RegistrationStartTime = x.RegistrationStartTime,
RegistrationEndTime = x.RegistrationEndTime,
MaterialDeadline = x.MaterialUploadEndTime,
MatchStartTime = x.MatchStartTime,
MatchEndTime = x.MatchEndTime,
TeamName = x.TeamName,
Status = statusValue,
CreateTime = x.CreatedTime,
UpdateTime = x.UpdatedTime,
PayStatus = x.PaymentStatus,
CertStatus = 0, // 未颁发
HasMaterial = !string.IsNullOrEmpty(x.MaterialUrl),
HasVoucher = !string.IsNullOrEmpty(x.PaymentProofUrl),
MemberCount = x.TotalPersonCount,
MaxMember = 10,
ExpireTime = x.MaterialUploadEndTime
};
}).ToList();
return new PageResult<RegistrationItemDTO>
{
PageNo = list.PageNo,
PageSize = list.PageSize,
Total = list.Total,
Pages = list.Pages,
Rows = resultItems
};
}
/// <summary>
/// 2. 取消报名
/// </summary>
[DisplayName("取消报名")]
[HttpPost("cancel/{registrationId}")]
[UnitOfWork]
public async Task<ResponseDTO<bool>> CancelRegistration([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
if (registration.PaymentStatus == 4) throw Oops.Bah("订单已取消");
// 校验状态:只有未支付状态可直接取消,已支付需走退款流程
if (registration.PaymentStatus == 1) throw Oops.Bah("已支付订单请联系客服退款");
_logger.LogInformation("开始取消报名:报名ID={RegistrationId}, 操作人={UserId}, 当前状态={PaymentStatus}",
registrationId, userId, registration.PaymentStatus);
// 更新报名状态
registration.PaymentStatus = 4; // 已取消
registration.UpdatedUserId = userId;
registration.UpdatedTime = DateTime.Now;
await _registrationRepository.UpdateAsync(registration);
// 同步更新订单状态
if (!string.IsNullOrEmpty(registration.OrderNo))
{
var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == registration.OrderNo);
if (order != null)
{
order.OrderStatus = OrderStatus.Cancelled;
order.UpdatedTime = DateTime.Now;
order.UpdatedUserId = userId;
await _orderRepository.UpdateAsync(order);
_logger.LogInformation("订单状态已同步更新:订单号={OrderNo}, 状态={OrderStatus}",
registration.OrderNo, OrderStatus.Cancelled);
}
else
{
_logger.LogWarning("未找到对应的订单记录:报名ID={RegistrationId}, 订单号={OrderNo}",
registrationId, registration.OrderNo);
}
}
_logger.LogInformation("取消报名成功:报名ID={RegistrationId}", registrationId);
return new ResponseDTO<bool> { Code = 200, Success = true, Data = true, Message = "取消成功" };
}
/// <summary>
/// 3. 上传素材
/// </summary>
[DisplayName("上传素材")]
[HttpPost("material/{registrationId}")]
[UnitOfWork]
public async Task<ResponseDTO<List<MaterialDTO>>> UploadMaterial([FromRoute] long registrationId, [FromForm] UploadMaterialInput input)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 仅 status = 待上传素材 或 已上传素材 允许再次上传
// 即 PayStatus == 1 (已支付)
if (registration.PaymentStatus != 1) throw Oops.Bah("未支付或状态不正确,无法上传素材");
// 总素材大小累计不超过 100 MB (简化校验,假设单次)
long totalSize = input.Files.Sum(f => f.Length);
if (totalSize > 100 * 1024 * 1024) throw Oops.Bah("素材总大小超过100MB");
var result = new List<MaterialDTO>();
foreach (var file in input.Files)
{
var uploadResult = await _fileService.Upload(file);
var item = uploadResult.FirstOrDefault();
if (item != null)
{
result.Add(new MaterialDTO
{
MaterialId = _idGenerator.NewLong().ToString(),
OssUrl = item.Url,
FileName = item.Name,
FileSize = file.Length,
UploadTime = DateTime.Now
});
}
}
if (result.Any())
{
registration.MaterialUrl = result.First().OssUrl; // 更新主表
await _registrationRepository.UpdateAsync(registration);
}
return new ResponseDTO<List<MaterialDTO>> { Code = 200, Success = true, Data = result, Message = "上传成功" };
}
/// <summary>
/// 4. 上传付款凭证
/// </summary>
[DisplayName("上传付款凭证")]
[HttpPost("voucher/{registrationId}")]
[UnitOfWork]
public async Task<ResponseDTO<VoucherDTO>> UploadVoucher([FromRoute] long registrationId, [FromForm] UploadVoucherInput input)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 仅 status = 待付款 允许上传
if (registration.PaymentStatus != 0) throw Oops.Bah("当前状态不允许上传凭证");
var uploadResult = await _fileService.Upload(input.File);
var fileInfo = uploadResult.First();
registration.PaymentProofUrl = fileInfo.Url;
registration.PaymentStatus = 0;
registration.UpdatedTime = DateTime.Now;
await _registrationRepository.UpdateAsync(registration);
return new ResponseDTO<VoucherDTO>
{
Code = 200, Success = true, Message = "上传成功",
Data = new VoucherDTO
{
VoucherId = _idGenerator.NewLong().ToString(),
OssUrl = fileInfo.Url,
AuditStatus = "待审核"
}
};
}
/// <summary>
/// 5.1 查看队员列表
/// </summary>
[DisplayName("查看队员列表")]
[HttpGet("members/{registrationId}")]
public async Task<List<TeamMemberDTO>> GetTeamMembers([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
// 需要关联 AppUser 获取更多信息 (Email 等)
// 这里简化处理
return persons.Select(p => new TeamMemberDTO
{
UserId = 0, // 需关联 AppUser
RealName = p.Name,
Phone = p.Phone,
Email = "", // 暂无
Role = p.PersonType == PersonType.Leader ? "队长" : "队员", // 假设
InviteStatus = "已加入",
JoinTime = p.CreatedTime
}).ToList();
}
/// <summary>
/// 5.2 更换队员
/// </summary>
[DisplayName("更换队员")]
[HttpPost("members/replace/{registrationId}/{userId}")]
[UnitOfWork]
public async Task<ResponseDTO<bool>> ReplaceMember([FromRoute] long registrationId, [FromRoute] long userId, [FromBody] ReplaceMemberInput input)
{
var currentUserId = _authManager.UserId;
// 校验队长权限: 仅创建者(队长)可操作
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == currentUserId);
if (registration == null) throw Oops.Bah("无权操作或记录不存在");
var person = await _registrationPersonRepository.GetFirstAsync(x => x.RegistrationId == registrationId && x.Id == userId);
if (person == null) throw Oops.Bah("队员不存在");
// 被替换人 role ≠ 队长
if (person.PersonType == PersonType.Leader) throw Oops.Bah("不能替换队长");
// 检查新队员
var newUser = await _userRepository.GetByIdAsync(input.NewUserId);
if (newUser == null) throw Oops.Bah("新队员不存在");
long oldUserId = person.Id;
// 更新信息
person.Name = newUser.Name;
person.IdCard = newUser.IdCardNumber;
person.Phone = newUser.Mobile;
person.UpdatedTime = DateTime.Now;
await _registrationPersonRepository.UpdateAsync(person);
return new ResponseDTO<bool> { Code = 200, Success = true, Data = true, Message = "更换成功" };
}
/// <summary>
/// 6. 查看证书
/// </summary>
[DisplayName("查看证书")]
[HttpGet("certificate/{registrationId}")]
public async Task<ResponseDTO<CertificateDTO>> GetCertificate([FromRoute] long registrationId)
{
// 2-已发放
var cert = await _certificateRepository.GetFirstAsync(x => x.RegistrationId == registrationId && x.CertificateStatus == 2);
if (cert == null) return new ResponseDTO<CertificateDTO> { Code = 404, Message = "证书未颁发", Success = false };
return new ResponseDTO<CertificateDTO>
{
Code = 200, Success = true,
Data = new CertificateDTO
{
CertUrl = cert.CertificateUrl,
CertNumber = cert.CertificateNo,
IssueTime = cert.IssuedTime ?? DateTime.MinValue,
ExpireTime = null
}
};
}
/// <summary>
/// 7. 取消报名预览
/// </summary>
[DisplayName("取消报名预览")]
[HttpGet("cancel/preview/{registrationId}")]
public async Task<CancelPreviewOutput> CancelPreview([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 获取赛事信息
var eventInfo = await _db.Queryable<EventInfo>().InSingleAsync(registration.EventId);
// 获取项目列表
var items = await _db.Queryable<EventRegistrationItem>()
.Where(x => x.RegistrationId == registrationId)
.ToListAsync();
// 获取队员列表
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
// 计算可退款金额(简化逻辑,实际需根据退款规则计算)
var refundableAmount = registration.TotalAmount;
var output = new CancelPreviewOutput
{
RegistrationId = registration.Id,
EventName = eventInfo?.EventName ?? "",
TeamName = registration.TeamName ?? "",
ChargeMode = registration.TotalAmount > 0 ? 1 : 0,
ActualPaidAmount = registration.TotalAmount,
RefundableAmount = refundableAmount
};
// 添加项目
foreach (var item in items)
{
output.Projects.Add(new CancelPreviewProjectDto
{
ProjectId = item.ProjectId,
ProjectName = item.ProjectName ?? "",
ProjectType = item.PersonCount > 1 ? "团队" : "个人",
PersonCount = item.PersonCount,
UnitPrice = item.UnitPrice
});
}
// 添加队员
foreach (var person in persons)
{
output.Members.Add(new CancelPreviewMemberDto
{
MemberId = person.Id,
Name = person.Name ?? "",
IsLeader = person.PersonType == PersonType.Leader
});
}
return output;
}
/// <summary>
/// 8. 执行取消报名
/// </summary>
[DisplayName("执行取消报名")]
[HttpPost("cancel/execute")]
[UnitOfWork]
public async Task<CancelExecuteOutput> CancelExecute([FromBody] CancelExecuteInput input)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == input.RegistrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 检查状态
if (registration.PaymentStatus == 4) throw Oops.Bah("订单已取消");
if (registration.PaymentStatus != 1) throw Oops.Bah("未支付的订单无法退款");
var now = DateTime.Now;
var output = new CancelExecuteOutput
{
Success = true,
Message = "取消报名成功",
CancelTime = now,
RefundAmount = registration.TotalAmount
};
// 如果有支付,先执行退款,成功后再更新状态
if (registration.TotalAmount > 0 && !string.IsNullOrEmpty(registration.OrderNo))
{
var eventInfo = await _eventInfoRepository.GetFirstAsync(e => e.Id == registration.EventId);
// 查找商户配置
var merchant = await _merchantRepository.GetFirstAsync(x => x.Id == eventInfo.MerchantId);
if (merchant != null)
{
var wechatConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.MerchantId == merchant.Id);
if (wechatConfig != null)
{
// 生成退款单号(使用统一的订单号生成器)
var outRefundNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync();
output.RefundNo = outRefundNo;
// 调用微信退款服务
var refundRequest = new Core.Infrastructure.WeChat.Dto.CreateRefundRequest
{
OutTradeNo = registration.OrderNo,
OutRefundNo = outRefundNo,
RefundAmount = AmountHelper.YuanToFen(registration.TotalAmount), // 转分
TotalAmount = AmountHelper.YuanToFen(registration.TotalAmount),
Reason = input.CancelReason
};
_logger.LogInformation("开始执行退款:报名ID={RegistrationId}, 订单号={OrderNo}, 退款单号={RefundNo}, 退款金额={Amount}",
registration.Id, registration.OrderNo, outRefundNo, registration.TotalAmount);
var refundResult = await _weChatPayService.CreateRefundAsync(wechatConfig, refundRequest);
if (!refundResult.Success)
{
_logger.LogError("退款失败:报名ID={RegistrationId}, 错误信息={ErrorMessage}",
registration.Id, refundResult.Message);
output.Success = false;
output.Message = refundResult.Message;
output.RefundAmount = 0;
output.RefundNo = null;
return output;
}
_logger.LogInformation("退款成功:报名ID={RegistrationId}, 退款单号={RefundNo}",
registration.Id, outRefundNo);
}
}
}
// 退款成功后才更新报名状态
registration.PaymentStatus = 3; // 已退款
registration.UpdatedUserId = userId;
registration.UpdatedTime = now;
registration.RegistrationRemark = input.CancelReason;
await _registrationRepository.UpdateAsync(registration);
_logger.LogInformation("取消报名成功:报名ID={RegistrationId}, 操作人={UserId}",
registration.Id, userId);
return output;
}
/// <summary>
/// 9. 获取参赛记录详情(包含项目和队员)
/// </summary>
[DisplayName("获取参赛记录详情(包含项目和队员)")]
[HttpGet("detail-with-items/{registrationId}")]
public async Task<RegistrationDetailWithItemsDto> GetRegistrationDetailWithItems([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在");
// 获取赛事信息
var eventInfo = await _eventInfoRepository.GetByIdAsync(registration.EventId);
// 获取报名项目
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registrationId);
// 获取所有队员
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
// 获取组别信息(年龄和性别限制)
var groupIds = items.Where(x => x.EventProjectGroupId.HasValue).Select(x => x.EventProjectGroupId.Value).Distinct().ToList();
var groups = new Dictionary<long, EventProjectGroup>();
if (groupIds.Any())
{
var groupList = await _db.Queryable<EventProjectGroup>()
.Where(x => groupIds.Contains(x.Id))
.ToListAsync();
groups = groupList.ToDictionary(x => x.Id);
}
// 获取项目信息(从Project表查询项目名称和完成人数)
var projectIds = items.Select(x => x.ProjectId).Distinct().ToList();
var projects = new Dictionary<long, YD_Event.Core.Entities.Project>();
if (projectIds.Any())
{
var projectList = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
projects = projectList.ToDictionary(x => x.Id);
}
var result = new RegistrationDetailWithItemsDto
{
RegistrationId = registration.Id,
EventName = eventInfo?.EventName ?? "",
TeamName = registration.TeamName ?? ""
};
foreach (var item in items)
{
var itemMembers = persons.Where(p => p.RegistrationItemId == item.Id).ToList();
// 获取组别信息
EventProjectGroup? group = null;
if (item.EventProjectGroupId.HasValue && groups.TryGetValue(item.EventProjectGroupId.Value, out var foundGroup))
{
group = foundGroup;
}
// 获取项目信息
YD_Event.Core.Entities.Project? project = null;
if (projects.TryGetValue(item.ProjectId, out var foundProject))
{
project = foundProject;
}
result.Items.Add(new RegistrationItemWithMembersDto
{
RegistrationItemId = item.Id,
ProjectId = item.ProjectId,
// 优先从Project表获取项目名称,如果没有则使用报名时保存的名称
ProjectName = project?.Name ?? item.ProjectName ?? "",
GroupName = item.GroupName ?? "",
ProjectTypeName = item.ProjectTypeName ?? "",
PersonCount = item.PersonCount,
// 从Project表获取完成人数
ProjectCompletionCount = project?.CompletionCount ?? 0,
BirthDateStart = group?.BirthDateStart,
BirthDateEnd = group?.BirthDateEnd,
GenderRequirement = group?.Gender,
Members = itemMembers.Select(p => new RegistrationPersonDto
{
Id = p.Id,
Name = p.Name,
IdCardNumber = p.IdCard,
Gender = p.Gender,
Mobile = p.Phone,
BirthDate = p.BirthDate
}).ToList()
});
}
return result;
}
/// <summary>
/// 10. 更新参赛队员
/// </summary>
[DisplayName("更新参赛队员")]
[HttpPost("update-members")]
[UnitOfWork]
public async Task<ResponseDTO<bool>> UpdateRegistrationMembers([FromBody] UpdateRegistrationMembersInput input)
{
var userId = _authManager.UserId;
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == input.RegistrationId && x.CreatedUserId == userId);
if (registration == null) throw Oops.Bah("报名记录不存在或无权操作");
_logger.LogInformation("开始更新参赛队员:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId);
// 获取所有需要查询的用户ID
var userIds = input.Items.SelectMany(x => x.Members).Where(x => x.UserId.HasValue).Select(x => x.UserId.Value).Distinct().ToList();
var users = new Dictionary<long, YD_Event.Core.Entities.AppUser>();
if (userIds.Any())
{
var userList = await _userRepository.GetListAsync(x => userIds.Contains(x.Id));
users = userList.ToDictionary(x => x.Id);
}
foreach (var item in input.Items)
{
// 验证项目是否属于该报名
var regItem = await _registrationItemRepository.GetFirstAsync(x => x.Id == item.RegistrationItemId && x.RegistrationId == input.RegistrationId);
if (regItem == null) throw Oops.Bah($"项目记录不存在:{item.RegistrationItemId}");
// 删除旧的队员
await _registrationPersonRepository.DeleteAsync(x => x.RegistrationItemId == item.RegistrationItemId);
// 添加新的队员
var newPersons = new List<EventRegistrationPerson>();
foreach (var member in item.Members)
{
// 如果有UserId,从用户表获取完整信息
YD_Event.Core.Entities.AppUser? user = null;
if (member.UserId.HasValue && users.TryGetValue(member.UserId.Value, out var foundUser))
{
user = foundUser;
}
var name = member.Name ?? user?.Name;
var gender = member.Gender ?? user?.Gender ?? Gender.Male;
var idCardNumber = member.IdCardNumber ?? user?.IdCardNumber;
var birthDate = member.BirthDate ?? user?.BirthDate ?? DateTime.Now.AddYears(-20);
var mobile = member.Mobile ?? user?.Mobile;
// 验证必填字段
if (string.IsNullOrWhiteSpace(name)) throw Oops.Bah("队员姓名不能为空");
if (string.IsNullOrWhiteSpace(idCardNumber)) throw Oops.Bah("队员身份证号不能为空");
if (string.IsNullOrWhiteSpace(mobile)) throw Oops.Bah("队员手机号不能为空");
newPersons.Add(new EventRegistrationPerson
{
Id = _idGenerator.NewLong(),
RegistrationId = input.RegistrationId,
RegistrationItemId = item.RegistrationItemId,
ProjectId = regItem.ProjectId,
EventProjectGroupId = regItem.EventProjectGroupId,
GroupName = regItem.GroupName,
ProjectName = regItem.ProjectName,
Name = name,
Gender = gender,
IdCard = idCardNumber,
BirthDate = birthDate,
Phone = mobile,
Age = CalculateAge(birthDate),
PersonType = PersonType.Athlete,
UserId = member.UserId ?? 0,
CreatedUserId = userId,
CreatedTime = DateTime.Now
});
}
await _registrationPersonRepository.InsertRangeAsync(newPersons);
// 更新项目的人数
regItem.PersonCount = item.Members.Count;
regItem.UpdatedTime = DateTime.Now;
regItem.UpdatedUserId = userId;
await _registrationItemRepository.UpdateAsync(regItem);
}
// 更新报名总人数
var allPersonsCount = await _registrationPersonRepository.CountAsync(x => x.RegistrationId == input.RegistrationId);
registration.TotalPersonCount = allPersonsCount;
registration.UpdatedTime = DateTime.Now;
registration.UpdatedUserId = userId;
await _registrationRepository.UpdateAsync(registration);
_logger.LogInformation("更新参赛队员成功:报名ID={RegistrationId}", input.RegistrationId);
return new ResponseDTO<bool> { Code = 200, Success = true, Data = true, Message = "更新成功" };
}
/// <summary>
/// 计算年龄
/// </summary>
private int CalculateAge(DateTime birthDate)
{
var today = DateTime.Today;
var age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age)) age--;
return age;
}
}