759 lines
31 KiB
C#
759 lines
31 KiB
C#
using YD_Event.Application.Event.Dtos;
|
||
using YD_Event.Core.Entities;
|
||
using YD_Event.Core.Enum;
|
||
using SqlSugar;
|
||
using Mapster;
|
||
using YD_Event.Application.Auth;
|
||
using Furion.DynamicApiController;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using YD_Event.Application.File;
|
||
using Microsoft.AspNetCore.Http;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using System.ComponentModel;
|
||
|
||
namespace YD_Event.Application.Event;
|
||
|
||
/// <summary>
|
||
/// 用户端赛事报名服务
|
||
/// </summary>
|
||
public class UserCompetitionService : IDynamicApiController, ITransient
|
||
{
|
||
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
|
||
private readonly ISqlSugarRepository<EventUnit> _unitRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
|
||
private readonly ISqlSugarRepository<EventRegistration> _registrationRepository;
|
||
private readonly ISqlSugarRepository<EventRegistrationItem> _registrationItemRepository;
|
||
private readonly ISqlSugarRepository<EventRegistrationPerson> _registrationPersonRepository;
|
||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _userRepository;
|
||
private readonly ISqlSugarRepository<EventFeeConfig> _feeConfigRepository;
|
||
private readonly ISqlSugarRepository<EventRegistrationLimit> _limitRepository;
|
||
private readonly ISqlSugarRepository<PointsRule> _pointsRuleRepository;
|
||
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
||
private readonly ISqlSugarRepository<EventCertificate> _certificateRepository;
|
||
private readonly AuthManager _authManager;
|
||
private readonly IIdGenerator _idGenerator;
|
||
private readonly FileService _fileService;
|
||
|
||
public UserCompetitionService(
|
||
ISqlSugarRepository<EventInfo> eventRepository,
|
||
ISqlSugarRepository<EventUnit> unitRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
|
||
ISqlSugarRepository<EventRegistration> registrationRepository,
|
||
ISqlSugarRepository<EventRegistrationItem> registrationItemRepository,
|
||
ISqlSugarRepository<EventRegistrationPerson> registrationPersonRepository,
|
||
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository,
|
||
ISqlSugarRepository<EventFeeConfig> feeConfigRepository,
|
||
ISqlSugarRepository<EventRegistrationLimit> limitRepository,
|
||
ISqlSugarRepository<PointsRule> pointsRuleRepository,
|
||
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
||
ISqlSugarRepository<EventCertificate> certificateRepository,
|
||
AuthManager authManager,
|
||
IIdGenerator idGenerator,
|
||
FileService fileService)
|
||
{
|
||
_eventRepository = eventRepository;
|
||
_unitRepository = unitRepository;
|
||
_projectRepository = projectRepository;
|
||
_registrationRepository = registrationRepository;
|
||
_registrationItemRepository = registrationItemRepository;
|
||
_registrationPersonRepository = registrationPersonRepository;
|
||
_userRepository = userRepository;
|
||
_feeConfigRepository = feeConfigRepository;
|
||
_limitRepository = limitRepository;
|
||
_pointsRuleRepository = pointsRuleRepository;
|
||
_paymentRepository = paymentRepository;
|
||
_certificateRepository = certificateRepository;
|
||
_authManager = authManager;
|
||
_idGenerator = idGenerator;
|
||
_fileService = fileService;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 1. 赛事列表展示接口
|
||
/// </summary>
|
||
[HttpGet("list")]
|
||
public async Task<PageResult<UserEventListOutput>> Page([FromQuery] UserEventQueryInput input)
|
||
{
|
||
var query = _eventRepository.AsQueryable()
|
||
.Where(x => x.Status == AvailabilityStatus.Enable)
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), x => x.EventName.Contains(input.Keyword))
|
||
.WhereIF(input.Status.HasValue && input.Status != 0, x => x.EventStatus == input.Status)
|
||
.WhereIF(input.Type.HasValue, x => x.EventType == input.Type)
|
||
.WhereIF(input.StartTime.HasValue, x => x.MatchStartTime >= input.StartTime)
|
||
.WhereIF(input.EndTime.HasValue, x => x.MatchEndTime <= input.EndTime)
|
||
.OrderByDescending(x => x.CreatedTime);
|
||
|
||
return await query.Select(x => new UserEventListOutput
|
||
{
|
||
Id = x.Id,
|
||
Name = x.EventName,
|
||
CoverImage = x.CoverImage,
|
||
Status = x.EventStatus.ToString(),
|
||
Type = x.EventType.ToString(),
|
||
RegistrationEndTime = x.RegistrationEndTime,
|
||
MatchStartTime = x.MatchStartTime,
|
||
MatchEndTime = x.MatchEndTime
|
||
}).ToPagedListAsync(input);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 2. 赛事详情查看接口
|
||
/// </summary>
|
||
[HttpGet("detail/{id}")]
|
||
public async Task<UserEventDetailOutput> Detail([FromRoute] long id)
|
||
{
|
||
var eventInfo = await _eventRepository.GetByIdAsync(id);
|
||
if (eventInfo == null) throw Oops.Bah("赛事不存在");
|
||
|
||
var output = eventInfo.Adapt<UserEventDetailOutput>();
|
||
output.Name = eventInfo.EventName;
|
||
output.Description = eventInfo.EventIntroduction;
|
||
output.Address = eventInfo.DetailAddress;
|
||
output.EventVideo = eventInfo.EventVideo;
|
||
output.RegistrationType = eventInfo.RegistrationType.ToString();
|
||
output.IsNeedMaterial = eventInfo.IsNeedMaterial;
|
||
output.MaterialRemark = eventInfo.MaterialRemark;
|
||
output.MaterialUploadEndTime = eventInfo.MaterialUploadEndTime;
|
||
|
||
if (eventInfo.PointsRuleId.HasValue)
|
||
{
|
||
var rule = await _pointsRuleRepository.GetByIdAsync(eventInfo.PointsRuleId.Value);
|
||
output.PointsRuleName = rule?.RuleName ?? "";
|
||
}
|
||
|
||
var units = await _unitRepository.GetListAsync(x => x.EventId == id);
|
||
output.Units = units.OrderBy(x => x.Sort).Select(x => x.Adapt<EventUnitDto>()).ToList();
|
||
|
||
var limit = await _limitRepository.GetFirstAsync(x => x.EventId == id);
|
||
if (limit != null)
|
||
{
|
||
output.LimitConfig = new UserEventLimitOutput
|
||
{
|
||
IndividualProjectLimit = limit.IndividualProjectLimit,
|
||
TeamProjectLimit = limit.TeamProjectLimit
|
||
};
|
||
}
|
||
|
||
var fee = await _feeConfigRepository.GetFirstAsync(x => x.EventId == id);
|
||
if (fee != null)
|
||
{
|
||
output.FeeConfig = new UserEventFeeOutput
|
||
{
|
||
ChargeMode = fee.ChargeMode.ToString(),
|
||
Price = fee.Price
|
||
};
|
||
}
|
||
|
||
var projects = await _projectRepository.GetListAsync(x => x.EventId == id && x.Status == AvailabilityStatus.Enable);
|
||
output.Projects = projects.Select(p => new UserEventProjectListOutput
|
||
{
|
||
Id = p.Id,
|
||
Name = p.Name,
|
||
Fee = p.Fee,
|
||
CompletionCount = p.CompletionCount
|
||
}).ToList();
|
||
|
||
var userId = _authManager.UserId;
|
||
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == id && x.CreatedUserId == userId && !x.DeleteMark);
|
||
|
||
if (registration != null)
|
||
{
|
||
output.RegistrationId = registration.Id;
|
||
output.UserRegistrationStatus = GetRegistrationStatusDesc(registration.RegistrationStatus);
|
||
}
|
||
else
|
||
{
|
||
output.UserRegistrationStatus = "未报名";
|
||
}
|
||
|
||
return output;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 3.1 选择报名项目接口
|
||
/// </summary>
|
||
[HttpGet("projects/{eventId}")]
|
||
public async Task<List<UserEventProjectListOutput>> GetProjects([FromRoute] long eventId)
|
||
{
|
||
var projects = await _projectRepository.GetListAsync(x => x.EventId == eventId && x.Status == AvailabilityStatus.Enable);
|
||
|
||
return projects.Select(p => new UserEventProjectListOutput
|
||
{
|
||
Id = p.Id,
|
||
Name = p.Name,
|
||
Fee = p.Fee,
|
||
TeamSize = 1, // 默认为1
|
||
CompletionCount = p.CompletionCount
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 3.2 分配队员接口 - 搜索队员
|
||
/// </summary>
|
||
[HttpGet("members/search")]
|
||
public async Task<List<UserTeamMemberOutput>> SearchMembers([FromQuery] string keyword)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(keyword)) return new List<UserTeamMemberOutput>();
|
||
|
||
var users = await _userRepository.AsQueryable()
|
||
.Where(x => (x.Mobile.Contains(keyword) || x.Id.ToString() == keyword) && !x.DeleteMark)
|
||
.Take(10)
|
||
.ToListAsync();
|
||
|
||
return users.Select(u => new UserTeamMemberOutput
|
||
{
|
||
UserId = u.Id,
|
||
Name = u.Name,
|
||
Mobile = u.Mobile,
|
||
IdCard = MaskIdCard(u.IdCardNumber),
|
||
Gender = u.Gender.ToString(),
|
||
IsEligible = true,
|
||
IneligibilityReason = ""
|
||
}).ToList();
|
||
}
|
||
|
||
private string MaskIdCard(string idCard)
|
||
{
|
||
if (string.IsNullOrEmpty(idCard) || idCard.Length < 10) return idCard;
|
||
return idCard.Substring(0, 6) + "********" + idCard.Substring(idCard.Length - 4);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 3.2 分配队员接口 - 提交报名(含保存草稿)
|
||
/// </summary>
|
||
[HttpPost("submit")]
|
||
[UnitOfWork]
|
||
public async Task<long> SubmitRegistration([FromBody] UserSubmitRegistrationInput input)
|
||
{
|
||
var userId = _authManager.UserId;
|
||
var user = await _userRepository.GetByIdAsync(userId);
|
||
if (user == null) throw Oops.Bah("用户不存在");
|
||
|
||
var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
|
||
if (eventInfo == null) throw Oops.Bah("赛事不存在");
|
||
|
||
var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == input.EventId && x.CreatedUserId == userId && !x.DeleteMark);
|
||
|
||
if (registration == null)
|
||
{
|
||
registration = new EventRegistration
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
EventId = input.EventId,
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now,
|
||
Status = AvailabilityStatus.Enable,
|
||
RegistrationStatus = input.IsDraft ? 0 : 1, // 0-待提交(草稿), 1-待审核
|
||
ContactName = user.Name,
|
||
ContactPhone = user.Mobile,
|
||
OrderNo = "",
|
||
RegistrationMode = 1
|
||
};
|
||
await _registrationRepository.InsertAsync(registration);
|
||
}
|
||
else
|
||
{
|
||
registration.RegistrationStatus = input.IsDraft ? 0 : 1;
|
||
registration.ContactName = user.Name;
|
||
registration.ContactPhone = user.Mobile;
|
||
registration.UpdatedTime = DateTime.Now;
|
||
registration.UpdatedUserId = userId;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
}
|
||
|
||
await _registrationItemRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
|
||
await _registrationPersonRepository.DeleteAsync(x => x.RegistrationId == registration.Id);
|
||
|
||
var projects = await _projectRepository.GetListAsync(x => input.ProjectIds.Contains(x.Id));
|
||
var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id));
|
||
|
||
decimal totalAmount = 0;
|
||
int totalPersonCount = 0;
|
||
|
||
foreach (var project in projects)
|
||
{
|
||
var item = new EventRegistrationItem
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
RegistrationId = registration.Id,
|
||
ProjectId = project.Id,
|
||
ProjectName = project.Name,
|
||
UnitPrice = project.Fee,
|
||
PersonCount = members.Count(),
|
||
TotalPrice = project.Fee * members.Count(),
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
await _registrationItemRepository.InsertAsync(item);
|
||
|
||
totalAmount += item.TotalPrice;
|
||
totalPersonCount += item.PersonCount;
|
||
|
||
foreach (var member in members)
|
||
{
|
||
var person = new EventRegistrationPerson
|
||
{
|
||
Id = _idGenerator.NewLong(),
|
||
RegistrationId = registration.Id,
|
||
RegistrationItemId = item.Id,
|
||
ProjectId = project.Id,
|
||
Name = member.Name,
|
||
Gender = member.Gender,
|
||
IdCard = member.IdCardNumber,
|
||
Phone = member.Mobile,
|
||
Age = member.Age,
|
||
BirthDate = member.BirthDate ?? DateTime.MinValue,
|
||
PersonType = 1,
|
||
CreatedUserId = userId,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
await _registrationPersonRepository.InsertAsync(person);
|
||
}
|
||
}
|
||
|
||
registration.TotalAmount = totalAmount;
|
||
registration.TotalPersonCount = totalPersonCount;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
|
||
return registration.Id;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 3.3 预览支付接口
|
||
/// </summary>
|
||
[HttpGet("preview/{registrationId}")]
|
||
public async Task<UserOrderPreviewOutput> PreviewOrder([FromRoute] long registrationId)
|
||
{
|
||
var registration = await _registrationRepository.GetByIdAsync(registrationId);
|
||
if (registration == null) throw Oops.Bah("报名信息不存在");
|
||
|
||
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 projectOutputs = items.Select(i => new UserEventProjectListOutput
|
||
{
|
||
Id = i.ProjectId,
|
||
Name = i.ProjectName,
|
||
Fee = i.UnitPrice,
|
||
RegisteredCount = i.PersonCount
|
||
}).ToList();
|
||
|
||
var memberOutputs = persons.DistinctBy(p => p.IdCard).Select(p => new UserTeamMemberOutput
|
||
{
|
||
Name = p.Name,
|
||
Mobile = p.Phone,
|
||
IdCard = MaskIdCard(p.IdCard),
|
||
Gender = p.Gender.ToString()
|
||
}).ToList();
|
||
|
||
return new UserOrderPreviewOutput
|
||
{
|
||
EventId = eventInfo.Id,
|
||
EventName = eventInfo.EventName,
|
||
Projects = projectOutputs,
|
||
Members = memberOutputs,
|
||
TotalAmount = registration.TotalAmount,
|
||
FeeDetails = new Dictionary<string, decimal> { { "报名费", registration.TotalAmount } }
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建支付订单
|
||
/// </summary>
|
||
[HttpPost("order/create")]
|
||
[UnitOfWork]
|
||
public async Task<UserPaymentResultOutput> CreateOrder([FromBody] UserCreateOrderInput input)
|
||
{
|
||
var registration = await _registrationRepository.GetByIdAsync(input.RegistrationId);
|
||
if (registration == null) throw Oops.Bah("报名信息不存在");
|
||
|
||
var orderNo = _idGenerator.NewLong().ToString();
|
||
registration.OrderNo = orderNo;
|
||
registration.PaymentMethod = input.PaymentMethod;
|
||
registration.PaymentStatus = 0; // 未支付
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
|
||
return new UserPaymentResultOutput
|
||
{
|
||
OrderNo = orderNo,
|
||
PayUrl = "http://mock-pay-url.com/" + orderNo,
|
||
Status = 0
|
||
};
|
||
}
|
||
|
||
#region 参赛记录相关
|
||
|
||
/// <summary>
|
||
/// 1. 列表查询
|
||
/// </summary>
|
||
[HttpGet("/event/pc/registration/list")]
|
||
[Authorize(Roles = "PC_USER")]
|
||
public async Task<PageResult<RegistrationItemDTO>> GetRegistrationList([FromQuery] RegistrationListQueryInput input)
|
||
{
|
||
var userId = _authManager.UserId;
|
||
var query = _registrationRepository.AsQueryable()
|
||
.InnerJoin<EventInfo>((r, e) => r.EventId == e.Id)
|
||
.Where((r, e) => r.CreatedUserId == userId && !r.DeleteMark)
|
||
.WhereIF(input.EventId.HasValue, (r, e) => r.EventId == input.EventId)
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (r, e) => r.TeamName.Contains(input.Keyword) || e.EventName.Contains(input.Keyword))
|
||
.OrderByDescending((r, e) => r.CreatedTime)
|
||
.Select((r, e) => new RegistrationItemDTO
|
||
{
|
||
RegistrationId = r.Id,
|
||
EventName = e.EventName,
|
||
TeamName = r.TeamName,
|
||
CreateTime = r.CreatedTime,
|
||
UpdateTime = r.UpdatedTime,
|
||
PayStatus = r.PaymentStatus == 1 ? "已支付" : "待付款", // 简化映射
|
||
CertStatus = "未颁发", // 暂无证书逻辑
|
||
HasMaterial = SqlFunc.HasValue(r.MaterialUrl),
|
||
HasVoucher = SqlFunc.HasValue(r.PaymentProofUrl),
|
||
MemberCount = r.TotalPersonCount,
|
||
MaxMember = 10, // 假设
|
||
ExpireTime = e.MaterialUploadEndTime
|
||
});
|
||
|
||
// 这里需要先把数据查出来再做复杂的状态转换,因为 SqlSugar Select 中不能写太复杂的 switch
|
||
var pagedResult = await query.ToPagedListAsync(input);
|
||
|
||
// 二次处理状态描述
|
||
foreach (var item in pagedResult.Rows)
|
||
{
|
||
// 状态映射逻辑
|
||
// status ∈ (待付款, 待上传素材, 已取消, 已报名, 待审核)
|
||
// 假设:RegStatus=4 -> 已取消
|
||
// RegStatus=1 -> 待审核
|
||
// PayStatus=0 -> 待付款
|
||
// PayStatus=1 && !HasMaterial -> 待上传素材
|
||
// PayStatus=1 && HasMaterial -> 已报名
|
||
|
||
// 注意:这里需要再次查询原始状态值才能准确判断,上面的Select只映射了部分。
|
||
// 优化:Select时把原始状态值带出来
|
||
// 重新查询一遍原始数据比较准确
|
||
}
|
||
|
||
// 重新构建查询以支持复杂逻辑
|
||
var list = await _registrationRepository.AsQueryable()
|
||
.InnerJoin<EventInfo>((r, e) => r.EventId == e.Id)
|
||
.Where((r, e) => r.CreatedUserId == userId && !r.DeleteMark)
|
||
.WhereIF(input.EventId.HasValue, (r, e) => r.EventId == input.EventId)
|
||
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), (r, e) => r.TeamName.Contains(input.Keyword) || e.EventName.Contains(input.Keyword))
|
||
.OrderByDescending((r, e) => r.CreatedTime)
|
||
.Select((r, e) => new
|
||
{
|
||
r.Id, r.EventId, e.EventName, r.TeamName, r.RegistrationStatus, r.PaymentStatus, r.MaterialUrl, r.PaymentProofUrl,
|
||
r.CreatedTime, r.UpdatedTime, r.TotalPersonCount, e.MaterialUploadEndTime
|
||
})
|
||
.ToPagedListAsync(input);
|
||
|
||
var resultItems = list.Rows.Select(x => {
|
||
string statusDesc = "未知";
|
||
if (x.RegistrationStatus == 4) statusDesc = "已取消";
|
||
else if (x.RegistrationStatus == 1) statusDesc = "待审核"; // 假设1是待审核
|
||
else if (x.PaymentStatus == 0) statusDesc = "待付款";
|
||
else if (x.PaymentStatus == 1 && string.IsNullOrEmpty(x.MaterialUrl)) statusDesc = "待上传素材";
|
||
else if (x.PaymentStatus == 1 && !string.IsNullOrEmpty(x.MaterialUrl)) statusDesc = "已报名"; // 或已完成
|
||
|
||
return new RegistrationItemDTO
|
||
{
|
||
RegistrationId = x.Id,
|
||
EventName = x.EventName,
|
||
TeamName = x.TeamName,
|
||
Status = statusDesc,
|
||
CreateTime = x.CreatedTime,
|
||
UpdateTime = x.UpdatedTime,
|
||
PayStatus = x.PaymentStatus == 1 ? "已支付" : (x.PaymentStatus == 4 ? "待审核" : "待付款"),
|
||
CertStatus = "未颁发",
|
||
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>
|
||
[HttpPut("/event/pc/registration/{registrationId}/cancel")]
|
||
[Authorize(Roles = "PC_USER")]
|
||
[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("报名记录不存在");
|
||
|
||
// 仅允许 status ∈ (待付款, 待上传素材) 可取消
|
||
// 待付款: PayStatus == 0
|
||
// 待上传素材: PayStatus == 1 && !HasMaterial
|
||
// 已取消: RegStatus == 4
|
||
if (registration.RegistrationStatus == 4) throw Oops.Bah("订单已取消");
|
||
|
||
bool canCancel = false;
|
||
if (registration.PaymentStatus == 0) canCancel = true;
|
||
else if (registration.PaymentStatus == 1 && string.IsNullOrEmpty(registration.MaterialUrl)) canCancel = true;
|
||
|
||
if (!canCancel)
|
||
{
|
||
// 已付款且已上传素材的报名需走退赛流程
|
||
throw Oops.Bah("当前状态无法直接取消,请联系管理员退赛");
|
||
}
|
||
|
||
registration.RegistrationStatus = 4; // 已取消
|
||
registration.UpdatedUserId = userId;
|
||
registration.UpdatedTime = DateTime.Now;
|
||
// registration.CancelReason = "用户主动取消"; // 需扩展字段
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
|
||
// 记录日志 (暂不需要)
|
||
/*
|
||
await _eventLogRepository.InsertAsync(new EventLog
|
||
{
|
||
RegistrationId = registration.Id,
|
||
LogType = EventLogType.Cancel,
|
||
Description = "用户取消报名",
|
||
OperatorId = userId,
|
||
CreatedTime = DateTime.Now
|
||
});
|
||
*/
|
||
|
||
return new ResponseDTO<bool> { Code = 200, Success = true, Data = true, Message = "取消成功" };
|
||
}
|
||
|
||
/// <summary>
|
||
/// 3. 上传素材
|
||
/// </summary>
|
||
[HttpPost("/event/pc/registration/{registrationId}/material")]
|
||
[Authorize(Roles = "PC_USER")]
|
||
[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); // 假设 FileService 支持 IFormFile
|
||
var item = uploadResult.FirstOrDefault(); // 假设返回 List
|
||
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);
|
||
|
||
// 记录日志 (暂不需要)
|
||
/*
|
||
await _eventLogRepository.InsertAsync(new EventLog
|
||
{
|
||
RegistrationId = registration.Id,
|
||
LogType = EventLogType.UploadMaterial,
|
||
Description = $"用户上传素材: {result.Count}个文件",
|
||
OperatorId = userId,
|
||
CreatedTime = DateTime.Now
|
||
});
|
||
*/
|
||
}
|
||
|
||
return new ResponseDTO<List<MaterialDTO>> { Code = 200, Success = true, Data = result, Message = "上传成功" };
|
||
}
|
||
|
||
/// <summary>
|
||
/// 4. 上传付款凭证
|
||
/// </summary>
|
||
[HttpPost("/event/pc/registration/{registrationId}/voucher")]
|
||
[Authorize(Roles = "PC_USER")]
|
||
[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 = 4; // 假设 4 = 待审核
|
||
registration.UpdatedTime = DateTime.Now;
|
||
// registration.VoucherUploadTime = DateTime.Now;
|
||
await _registrationRepository.UpdateAsync(registration);
|
||
|
||
// 记录日志 (暂不需要)
|
||
/*
|
||
await _eventLogRepository.InsertAsync(new EventLog
|
||
{
|
||
RegistrationId = registration.Id,
|
||
LogType = EventLogType.UploadVoucher,
|
||
Description = "用户上传付款凭证",
|
||
OperatorId = userId,
|
||
CreatedTime = DateTime.Now
|
||
});
|
||
*/
|
||
|
||
return new ResponseDTO<VoucherDTO>
|
||
{
|
||
Code = 200, Success = true, Message = "上传成功",
|
||
Data = new VoucherDTO
|
||
{
|
||
VoucherId = _idGenerator.NewLong().ToString(),
|
||
OssUrl = fileInfo.Url,
|
||
AuditStatus = "待审核"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 5.1 查看队员列表
|
||
/// </summary>
|
||
[HttpGet("/event/pc/registration/{registrationId}/member")]
|
||
[Authorize(Roles = "PC_USER")]
|
||
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 == 3 ? "队长" : "队员", // 假设
|
||
InviteStatus = "已加入",
|
||
JoinTime = p.CreatedTime
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 5.2 更换队员
|
||
/// </summary>
|
||
[HttpPut("/event/pc/registration/{registrationId}/member/{userId}/replace")]
|
||
[Authorize(Roles = "PC_USER")]
|
||
[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 ≠ 队长 (假设 PersonType=3 是队长)
|
||
if (person.PersonType == 3) throw Oops.Bah("不能替换队长");
|
||
|
||
// 检查新队员
|
||
var newUser = await _userRepository.GetByIdAsync(input.NewUserId);
|
||
if (newUser == null) throw Oops.Bah("新队员不存在");
|
||
|
||
// 检查新队员是否已报名该赛事 (简化检查: 是否在 Person 表中存在该 EventId 的记录)
|
||
// var exists = await _registrationPersonRepository.AnyAsync(x => x.EventId == registration.EventId && x.IdCard == newUser.IdCardNumber);
|
||
// if (exists) throw Oops.Bah("新队员已报名该赛事");
|
||
|
||
long oldUserId = person.Id; // 这里应该是 AppUserId,但 Person 表没存 AppUserId,只存了 Name/Phone
|
||
|
||
// 更新信息
|
||
person.Name = newUser.Name;
|
||
person.IdCard = newUser.IdCardNumber;
|
||
person.Phone = newUser.Mobile;
|
||
person.UpdatedTime = DateTime.Now;
|
||
await _registrationPersonRepository.UpdateAsync(person);
|
||
|
||
// 记录日志 (暂不需要)
|
||
/*
|
||
await _eventLogRepository.InsertAsync(new EventLog
|
||
{
|
||
RegistrationId = registration.Id,
|
||
LogType = EventLogType.ReplaceMember,
|
||
Description = $"更换队员: {oldUserId} -> {input.NewUserId}",
|
||
OperatorId = currentUserId,
|
||
CreatedTime = DateTime.Now
|
||
});
|
||
*/
|
||
|
||
return new ResponseDTO<bool> { Code = 200, Success = true, Data = true, Message = "更换成功" };
|
||
}
|
||
|
||
/// <summary>
|
||
/// 6. 查看证书
|
||
/// </summary>
|
||
[HttpGet("/event/pc/registration/{registrationId}/certificate")]
|
||
[Authorize(Roles = "PC_USER")]
|
||
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 + "?sas_token=mock_10min_token", // 模拟带签名 URL
|
||
CertNumber = cert.CertificateNo,
|
||
IssueTime = cert.IssuedTime ?? DateTime.MinValue,
|
||
ExpireTime = null
|
||
}
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
|
||
private string GetRegistrationStatusDesc(int status)
|
||
{
|
||
return status switch
|
||
{
|
||
0 => "待提交",
|
||
1 => "审核中",
|
||
2 => "已报名",
|
||
3 => "审核拒绝",
|
||
4 => "已取消",
|
||
_ => "未知"
|
||
};
|
||
}
|
||
}
|