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

911 lines
38 KiB
C#
Raw Normal View History

2026-03-20 03:15:31 +08:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
2026-03-14 05:19:40 +08:00
using YD_Event.Application.Event.Dtos;
2026-04-12 13:14:20 +08:00
using YD_Event.Application.Common;
2026-03-14 05:19:40 +08:00
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
2026-04-20 14:01:30 +08:00
using YD_Event.Core.Extensions;
2026-04-10 10:54:47 +08:00
using Easy.Core;
2026-03-14 05:19:40 +08:00
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;
2026-03-20 03:15:31 +08:00
using Furion.FriendlyException;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
using YD_Event.Core.Infrastructure.WeChat;
2026-04-10 22:39:32 +08:00
using YD_Event.Core.Domain;
using Microsoft.Extensions.Logging;
2026-03-14 05:19:40 +08:00
namespace YD_Event.Application.Event;
/// <summary>
2026-03-20 03:15:31 +08:00
/// 用户端参赛记录管理
2026-03-14 05:19:40 +08:00
/// </summary>
2026-03-20 03:15:31 +08:00
public class UserEventRecordService : IDynamicApiController, ITransient
2026-03-14 05:19:40 +08:00
{
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
private readonly ISqlSugarRepository<EventRegistrationPerson> _registrationPersonRepository;
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRepository;
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
2026-03-14 05:19:40 +08:00
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _userRepository;
private readonly ISqlSugarRepository<EventCertificate> _certificateRepository;
2026-04-10 22:39:32 +08:00
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
2026-04-15 09:58:05 +08:00
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
2026-04-20 13:01:39 +08:00
private readonly ISqlSugarRepository<SysUser> _sysUserRepository;
private readonly ISqlSugarRepository<EventProject> _eventProjectRepository;
private readonly ISqlSugarRepository<EventProjectGroup> _eventProjectGroupRepository;
private readonly ISqlSugarClient _db;
2026-03-14 05:19:40 +08:00
private readonly AuthManager _authManager;
private readonly IIdGenerator _idGenerator;
private readonly FileService _fileService;
2026-04-10 22:39:32 +08:00
private readonly ILogger<UserEventRecordService> _logger;
2026-04-15 09:58:05 +08:00
private readonly UnifiedRegistrationService _unifiedRegistrationService;
2026-03-14 05:19:40 +08:00
2026-03-20 03:15:31 +08:00
public UserEventRecordService(
2026-03-14 05:19:40 +08:00
ISqlSugarRepository<EventRegistration> registrationRepository,
ISqlSugarRepository<EventRegistrationPerson> registrationPersonRepository,
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
2026-03-14 05:19:40 +08:00
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository,
ISqlSugarRepository<EventCertificate> certificateRepository,
2026-04-10 22:39:32 +08:00
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
2026-04-15 09:58:05 +08:00
ISqlSugarRepository<EventInfo> eventRepository,
2026-04-20 13:01:39 +08:00
ISqlSugarRepository<SysUser> sysUserRepository,
ISqlSugarRepository<EventProject> eventProjectRepository,
ISqlSugarRepository<EventProjectGroup> eventProjectGroupRepository,
ISqlSugarClient db,
2026-03-14 05:19:40 +08:00
AuthManager authManager,
IIdGenerator idGenerator,
FileService fileService,
2026-04-15 09:58:05 +08:00
ILogger<UserEventRecordService> logger,
UnifiedRegistrationService unifiedRegistrationService)
2026-03-14 05:19:40 +08:00
{
_registrationRepository = registrationRepository;
_registrationPersonRepository = registrationPersonRepository;
_registrationItemRepository = registrationItemRepository;
_projectRepository = projectRepository;
2026-03-14 05:19:40 +08:00
_userRepository = userRepository;
_certificateRepository = certificateRepository;
2026-04-15 09:58:05 +08:00
_orderRepository = orderRepository;
_eventRepository = eventRepository;
2026-04-20 13:01:39 +08:00
_sysUserRepository = sysUserRepository;
_eventProjectRepository = eventProjectRepository;
_eventProjectGroupRepository = eventProjectGroupRepository;
_db = db;
2026-03-14 05:19:40 +08:00
_authManager = authManager;
_idGenerator = idGenerator;
_fileService = fileService;
2026-04-10 22:39:32 +08:00
_logger = logger;
2026-04-15 09:58:05 +08:00
_unifiedRegistrationService = unifiedRegistrationService;
2026-03-14 05:19:40 +08:00
}
/// <summary>
/// 1. 列表查询
/// </summary>
2026-03-17 15:32:01 +08:00
[DisplayName("参赛记录列表查询")]
2026-03-20 03:15:31 +08:00
[HttpGet("list")]
2026-03-14 05:19:40 +08:00
public async Task<PageResult<RegistrationItemDTO>> GetRegistrationList([FromQuery] RegistrationListQueryInput input)
{
var userId = _authManager.UserId;
2026-04-09 23:12:51 +08:00
2026-03-14 05:19:40 +08:00
var list = await _registrationRepository.AsQueryable()
.InnerJoin<EventInfo>((r, e) => r.EventId == e.Id)
2026-04-06 09:21:47 +08:00
.LeftJoin<EventFeeConfig>((r, e, fc) => e.Id == fc.EventId)
.Where((r, e, fc) => r.CreatedUserId == userId && !r.DeleteMark)
2026-04-15 09:58:05 +08:00
// 支付状态筛选:
// - 线下支付时:显示未支付(0)和已支付(1)状态的数据
// - 线上支付时:仅显示已支付(1)状态的数据
.Where((r, e, fc) =>
(r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)) ||
(r.PaymentMethod == 2 && r.PaymentStatus == 1))
2026-04-06 09:21:47 +08:00
.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
2026-03-14 05:19:40 +08:00
{
2026-04-09 23:12:51 +08:00
r.Id, r.EventId, e.EventName, r.TeamName, r.PaymentStatus, r.MaterialUrl, r.PaymentProofUrl,
2026-04-06 09:21:47 +08:00
r.CreatedTime, r.UpdatedTime, r.TotalPersonCount, e.MaterialUploadEndTime,
e.CoverImage, e.EventType, e.EventStatus, e.RegistrationStartTime, e.RegistrationEndTime,
e.MatchStartTime, e.MatchEndTime,
2026-04-15 09:58:05 +08:00
fc.ChargeMode,
r.PaymentMethod
2026-03-14 05:19:40 +08:00
})
.ToPagedListAsync(input);
var resultItems = list.Rows.Select(x => {
return new RegistrationItemDTO
{
RegistrationId = x.Id,
EventName = x.EventName,
2026-04-06 09:21:47 +08:00
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,
2026-03-14 05:19:40 +08:00
TeamName = x.TeamName,
CreateTime = x.CreatedTime,
UpdateTime = x.UpdatedTime,
2026-04-05 20:58:17 +08:00
PayStatus = x.PaymentStatus,
2026-04-20 13:01:39 +08:00
PaymentMethod = x.PaymentMethod,
2026-04-05 20:58:17 +08:00
CertStatus = 0, // 未颁发
2026-03-14 05:19:40 +08:00
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>
2026-03-17 15:32:01 +08:00
[DisplayName("取消报名")]
2026-03-20 03:15:31 +08:00
[HttpPost("cancel/{registrationId}")]
2026-03-14 05:19:40 +08:00
[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("报名记录不存在");
2026-04-09 23:12:51 +08:00
if (registration.PaymentStatus == 4) throw Oops.Bah("订单已取消");
2026-03-14 05:19:40 +08:00
2026-04-20 13:01:39 +08:00
// 校验状态:线下支付(1)仅当状态为未支付(0)或已支付(1)时允许取消,线上支付只有未支付状态可直接取消,已支付需走退款流程
if (registration.PaymentMethod == 1 && registration.PaymentStatus != 0 && registration.PaymentStatus != 1)
{
throw Oops.Bah("线下支付订单仅当状态为未支付或已支付时允许取消,当前状态不允许取消");
}
if (registration.PaymentMethod != 1 && registration.PaymentStatus == 1)
{
throw Oops.Bah("已支付订单请联系客服退款");
}
2026-03-14 05:19:40 +08:00
2026-04-20 13:01:39 +08:00
_logger.LogInformation("开始取消报名:报名ID={RegistrationId}, 操作人={UserId}, 当前状态={PaymentStatus}, 支付方式={PaymentMethod}",
registrationId, userId, registration.PaymentStatus, registration.PaymentMethod);
2026-04-10 22:39:32 +08:00
// 更新报名状态
2026-04-09 23:12:51 +08:00
registration.PaymentStatus = 4; // 已取消
2026-03-14 05:19:40 +08:00
registration.UpdatedUserId = userId;
registration.UpdatedTime = DateTime.Now;
await _registrationRepository.UpdateAsync(registration);
2026-04-10 22:39:32 +08:00
// 同步更新订单状态
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);
2026-03-14 05:19:40 +08:00
return new ResponseDTO<bool> { Code = 200, Success = true, Data = true, Message = "取消成功" };
}
/// <summary>
/// 3. 上传素材
/// </summary>
2026-03-17 15:32:01 +08:00
[DisplayName("上传素材")]
2026-03-20 03:15:31 +08:00
[HttpPost("material/{registrationId}")]
2026-03-14 05:19:40 +08:00
[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)
{
2026-04-09 23:12:51 +08:00
var uploadResult = await _fileService.Upload(file);
var item = uploadResult.FirstOrDefault();
2026-03-14 05:19:40 +08:00
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>
2026-03-17 15:32:01 +08:00
[DisplayName("上传付款凭证")]
2026-03-20 03:15:31 +08:00
[HttpPost("voucher/{registrationId}")]
2026-03-14 05:19:40 +08:00
[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("报名记录不存在");
2026-04-21 08:38:45 +08:00
2026-03-14 05:19:40 +08:00
var uploadResult = await _fileService.Upload(input.File);
var fileInfo = uploadResult.First();
registration.PaymentProofUrl = fileInfo.Url;
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>
2026-03-17 15:32:01 +08:00
[DisplayName("查看队员列表")]
2026-03-20 03:15:31 +08:00
[HttpGet("members/{registrationId}")]
2026-03-14 05:19:40 +08:00
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 = "", // 暂无
2026-03-20 02:00:34 +08:00
Role = p.PersonType == PersonType.Leader ? "队长" : "队员", // 假设
2026-03-14 05:19:40 +08:00
InviteStatus = "已加入",
JoinTime = p.CreatedTime
}).ToList();
}
/// <summary>
/// 5.2 更换队员
/// </summary>
2026-03-17 15:32:01 +08:00
[DisplayName("更换队员")]
2026-03-20 03:15:31 +08:00
[HttpPost("members/replace/{registrationId}/{userId}")]
2026-03-14 05:19:40 +08:00
[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("队员不存在");
2026-04-09 23:12:51 +08:00
// 被替换人 role ≠ 队长
2026-03-20 02:00:34 +08:00
if (person.PersonType == PersonType.Leader) throw Oops.Bah("不能替换队长");
2026-03-14 05:19:40 +08:00
// 检查新队员
var newUser = await _userRepository.GetByIdAsync(input.NewUserId);
if (newUser == null) throw Oops.Bah("新队员不存在");
2026-04-09 23:12:51 +08:00
long oldUserId = person.Id;
2026-03-14 05:19:40 +08:00
// 更新信息
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>
2026-03-17 15:32:01 +08:00
[DisplayName("查看证书")]
2026-03-20 03:15:31 +08:00
[HttpGet("certificate/{registrationId}")]
2026-03-14 05:19:40 +08:00
public async Task<ResponseDTO<CertificateDTO>> GetCertificate([FromRoute] long registrationId)
{
2026-04-16 15:42:29 +08:00
var userId = _authManager.UserId;
2026-03-14 05:19:40 +08:00
// 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 };
2026-04-16 15:42:29 +08:00
// 权限验证
bool hasPermission = await CheckCertificatePermissionAsync(userId, cert);
if (!hasPermission)
{
return new ResponseDTO<CertificateDTO> { Code = 403, Message = "无权查看该证书", Success = false };
}
2026-03-14 05:19:40 +08:00
return new ResponseDTO<CertificateDTO>
{
Code = 200, Success = true,
Data = new CertificateDTO
{
2026-04-13 00:40:44 +08:00
CertUrl = cert.CertificateUrl,
2026-03-14 05:19:40 +08:00
CertNumber = cert.CertificateNo,
IssueTime = cert.IssuedTime ?? DateTime.MinValue,
ExpireTime = null
}
};
}
2026-04-16 15:42:29 +08:00
/// <summary>
/// 检查证书查看权限
/// </summary>
private async Task<bool> CheckCertificatePermissionAsync(long currentUserId, EventCertificate cert)
{
// 如果是自己的证书,直接允许
if (cert.UserId == currentUserId)
{
return true;
}
// 检查当前用户是否是单位用户,且该证书属于其管理的团队成员
var currentUser = await _userRepository.GetByIdAsync(currentUserId);
if (currentUser == null)
{
return false;
}
// 如果当前用户有单位认证
if (currentUser.MerchantCertificationId.HasValue)
{
// 查询当前用户管理的团队成员
var teamMemberUserIds = await _db.Queryable<YD_Event.Core.Entities.Team>()
.InnerJoin<TeamMember>((t, tm) => t.Id == tm.TeamId)
.Where((t, tm) => t.CreatedUserId == currentUserId && !t.DeleteMark && !tm.DeleteMark)
.Select((t, tm) => tm.UserId)
.ToListAsync();
// 检查证书用户是否在团队成员列表中
if (teamMemberUserIds.Contains(cert.UserId))
{
return true;
}
}
return false;
}
/// <summary>
/// 7. 取消报名预览
/// </summary>
[DisplayName("取消报名预览")]
[HttpGet("cancel/preview/{registrationId}")]
public async Task<CancelPreviewOutput> CancelPreview([FromRoute] long registrationId)
{
var userId = _authManager.UserId;
2026-04-15 09:58:05 +08:00
_logger.LogInformation("调用统一取消报名预览:报名ID={RegistrationId}, 操作人={UserId}", registrationId, userId);
return await _unifiedRegistrationService.GetCancelPreviewAsync(registrationId, userId);
}
/// <summary>
/// 8. 执行取消报名
/// </summary>
[DisplayName("执行取消报名")]
[HttpPost("cancel/execute")]
[UnitOfWork]
public async Task<CancelExecuteOutput> CancelExecute([FromBody] CancelExecuteInput input)
{
var userId = _authManager.UserId;
2026-04-15 09:58:05 +08:00
_logger.LogInformation("调用统一执行取消报名:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId);
return await _unifiedRegistrationService.ExecuteCancelRegistrationAsync(input, userId);
}
/// <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("报名记录不存在");
// 获取赛事信息
2026-04-15 09:58:05 +08:00
var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId);
// 获取报名项目
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registrationId);
// 获取所有队员
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
// 获取组别信息(年龄和性别限制)
var 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,
2026-04-20 13:01:39 +08:00
EventId = registration.EventId,
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,
2026-04-20 13:01:39 +08:00
EventProjectGroupId = item.EventProjectGroupId ?? 0,
// 优先从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,
2026-04-20 14:01:30 +08:00
UserId = p.UserId, // 这里是关键!设置正确的用户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);
2026-04-20 13:01:39 +08:00
// 获取赛事信息
var eventInfo = await _eventRepository.GetFirstAsync(x => x.Id == registration.EventId);
if (eventInfo == null) throw Oops.Bah("赛事信息不存在");
// 获取报名项目信息
var registrationItems = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == input.RegistrationId);
var eventProjectIds = registrationItems.Select(x => x.ProjectId).Distinct().ToList();
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => eventProjectIds.Contains(x.EventProjectId));
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
// 获取所有需要查询的用户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;
}
2026-04-20 14:01:30 +08:00
/// <summary>
/// 验证队员是否符合报名条件
/// </summary>
[DisplayName("验证队员")]
[HttpPost("member/validate")]
public async Task<MemberValidateOutput> ValidateMember([FromBody] MemberValidateInput input)
{
var result = new MemberValidateOutput
{
IsValid = true,
AgeValid = true,
GenderValid = true,
RegistrationLimitValid = true,
Message = "验证通过"
};
// 添加调试:查看接收的ID
var memberId = input.MemberId;
var member = await _userRepository.GetByIdAsync(memberId);
// 添加调试:如果找不到,尝试不删除标记查询
if (member == null)
{
var memberWithoutDelete = await _db.Queryable<YD_Event.Core.Entities.AppUser>()
.FirstAsync(m => m.Id == memberId);
if (memberWithoutDelete != null)
{
// 队员存在但被标记删除
result.IsValid = false;
result.Message = "队员不存在(已删除)";
return result;
}
result.IsValid = false;
result.Message = "队员不存在";
return result;
}
if (!member.BirthDate.HasValue)
{
result.IsValid = false;
result.AgeValid = false;
result.Message = "队员缺少出生日期信息";
return result;
}
var eventProjectGroup = await _eventProjectGroupRepository.GetByIdAsync(input.GroupId);
if (eventProjectGroup != null)
{
// 获取项目类型信息
var eventProject = await _eventProjectRepository.GetByIdAsync(eventProjectGroup.EventProjectId);
var baseProject = eventProject != null ? await _projectRepository.GetByIdAsync(eventProject.ProjectId) : null;
var isTeamProject = baseProject?.IsTeamProject() ?? false;
// 创建单个队员的验证输入
var persons = new List<UnifiedPersonInput>
{
new UnifiedPersonInput
{
UserId = member.Id,
Name = member.Name ?? "",
IdCardNumber = member.IdCardNumber ?? "",
Gender = member.Gender,
Mobile = member.Mobile,
BirthDate = member.BirthDate.Value
}
};
// 使用统一的验证方法进行验证
var validationResult = _unifiedRegistrationService.ValidatePersonQualifications(persons, eventProjectGroup, isTeamProject);
if (!validationResult.IsValid)
{
result.IsValid = false;
result.Message = validationResult.ErrorMessage ?? "验证失败";
// 根据验证详情设置具体的验证状态
var ageDetail = validationResult.Details?.FirstOrDefault(d => d.ItemName == "年龄验证" || d.ItemName == "年龄跨组验证");
var genderDetail = validationResult.Details?.FirstOrDefault(d => d.ItemName == "性别验证");
if (ageDetail != null && !ageDetail.IsPassed)
{
result.AgeValid = false;
}
if (genderDetail != null && !genderDetail.IsPassed)
{
result.GenderValid = false;
}
}
}
return result;
}
/// <summary>
/// 检查报名限制
/// </summary>
[DisplayName("检查报名限制")]
[HttpPost("registration-limit/check")]
public async Task<RegistrationLimitCheckOutput> CheckRegistrationLimit([FromBody] RegistrationLimitCheckInput input)
{
var userId = _authManager.UserId;
// 获取当前用户信息
var sysUser = await _sysUserRepository.GetByIdAsync(userId);
if (sysUser == null) throw Oops.Bah("用户不存在");
string userName = sysUser.Name;
string userMobile = sysUser.Mobile ?? "";
// 获取团队信息 - 优先使用新结构的 TeamId
string teamName = "临时团队";
if (input.TeamId.HasValue)
{
var team = await _db.Queryable<YD_Event.Core.Entities.Team>().FirstAsync(t => t.Id == input.TeamId.Value && !t.DeleteMark);
if (team != null)
{
teamName = team.Name;
}
}
// 获取所有成员信息
var allMemberIds = new List<long>();
if (input.GroupMembers != null)
{
foreach (var gm in input.GroupMembers)
{
// 检查是否有多团队数据
if (gm.Teams != null && gm.Teams.Count > 0)
{
foreach (var team in gm.Teams)
{
allMemberIds.AddRange(team.MemberIds ?? new List<long>());
}
}
else
{
// 向后兼容单团队模式
allMemberIds.AddRange(gm.MemberIds ?? new List<long>());
}
}
}
else if (input.MemberIds != null)
{
// 向后兼容旧结构
allMemberIds.AddRange(input.MemberIds);
}
allMemberIds = allMemberIds.Distinct().ToList();
// 获取成员信息
var members = await _userRepository.GetListAsync(x => allMemberIds.Contains(x.Id));
// 获取组别信息
var groupIds = input.GroupMembers?.Select(g => g.GroupId).ToList() ?? input.GroupIds ?? new List<long>();
var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
// 获取项目信息
var eventProjectIds = eventProjectGroups.Select(epg => epg.EventProjectId).Distinct().ToList();
var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id));
// 构建统一报名输入
var unifiedInput = new UnifiedRegistrationInput
{
EventId = input.EventId,
RegistrationMode = 2, // PC端团队报名
TeamName = teamName,
ContactName = userName,
ContactPhone = userMobile,
CreatedUserId = userId,
IsDraft = false,
Items = new List<UnifiedRegistrationItemInput>()
};
// 为每个组别添加项目项
foreach (var groupMember in input.GroupMembers ?? new List<GroupMemberAssignment>())
{
var eventProjectGroup = eventProjectGroups.FirstOrDefault(epg => epg.Id == groupMember.GroupId);
if (eventProjectGroup == null) continue;
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == eventProjectGroup.EventProjectId);
if (eventProject == null) continue;
var item = new UnifiedRegistrationItemInput
{
ProjectId = eventProject.Id,
GroupId = groupMember.GroupId,
GroupName = eventProjectGroup.GroupName,
Teams = new List<UnifiedTeamInput>(),
Persons = new List<UnifiedPersonInput>()
};
// 检查是多团队模式还是单团队模式
var hasMultipleTeams = groupMember.Teams != null && groupMember.Teams.Count > 0;
if (hasMultipleTeams)
{
// ========== 多团队模式 ==========
foreach (var team in groupMember.Teams)
{
var teamMembers = members.Where(m => team.MemberIds != null && team.MemberIds.Contains(m.Id)).ToList();
item.Teams.Add(new UnifiedTeamInput
{
TeamIndex = team.TeamIndex,
Persons = teamMembers.Select(m => new UnifiedPersonInput
{
UserId = m.Id,
Name = m.Name ?? "",
IdCardNumber = m.IdCardNumber ?? "",
Gender = m.Gender,
Mobile = m.Mobile,
BirthDate = m.BirthDate ?? DateTime.MinValue
}).ToList()
});
}
}
else
{
// ========== 单团队模式(向后兼容) ==========
var groupMembersList = members.Where(m => groupMember.MemberIds != null && groupMember.MemberIds.Contains(m.Id)).ToList();
item.Persons = groupMembersList.Select(m => new UnifiedPersonInput
{
UserId = m.Id,
Name = m.Name ?? "",
IdCardNumber = m.IdCardNumber ?? "",
Gender = m.Gender,
Mobile = m.Mobile,
BirthDate = m.BirthDate ?? DateTime.MinValue
}).ToList();
}
unifiedInput.Items.Add(item);
}
// 调用统一的限制检查方法
2026-04-20 14:34:50 +08:00
return await _unifiedRegistrationService.CheckRegistrationLimitAsync(unifiedInput, input.ExcludeRegistrationId);
2026-04-20 14:01:30 +08:00
}
2026-03-14 05:19:40 +08:00
}