diff --git a/YD_Event.Application/Client/OAuthController.cs b/YD_Event.Application/Client/OAuthController.cs
index 292aa2a..573d39d 100644
--- a/YD_Event.Application/Client/OAuthController.cs
+++ b/YD_Event.Application/Client/OAuthController.cs
@@ -52,17 +52,20 @@ public class OAuthController : IDynamicApiController
}
var appUser = await _appUserRepository.GetFirstAsync(x => x.Mobile == input.Mobile);
- if (appUser == null)
- {
- throw Oops.Bah("用户不存在,请先注册");
- }
-
- if (isPasswordLogin)
+
+ // 统一的未注册账户判断逻辑
+ bool isRegistered = appUser != null && !string.IsNullOrEmpty(appUser.Password);
+
+ if (!isRegistered)
{
if (string.IsNullOrEmpty(appUser.Password))
{
throw Oops.Bah("未注册过账户");
}
+ }
+
+ if (isPasswordLogin)
+ {
var encryptedPassword = MD5Encryption.Encrypt($"{_idGenerator.Encode(appUser.Id)}{input.Password}");
if (appUser.Password != encryptedPassword)
{
diff --git a/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs b/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
index e0c9c3a..d203f21 100644
--- a/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
+++ b/YD_Event.Application/Event/Dtos/UserCompetitionDtos.cs
@@ -752,11 +752,6 @@ public class RegistrationItemDTO
///
public string TeamName { get; set; }
- ///
- /// 支付状态:0-未支付/待审核,1-已支付/审核通过,2-支付失败/审核拒绝,3-已退款,4-已取消
- ///
- public int Status { get; set; }
-
///
/// 创建时间
///
@@ -1177,6 +1172,128 @@ public class GroupTeamLimitOutput
public int CurrentTeams { get; set; }
}
+///
+/// 检查组别选择限制输入
+///
+public class CheckGroupSelectionLimitInput
+{
+ ///
+ /// 赛事ID
+ ///
+ [Required]
+ public long EventId { get; set; }
+
+ ///
+ /// 要检查的组别ID
+ ///
+ [Required]
+ public long GroupId { get; set; }
+
+ ///
+ /// 当前已选择的组别ID列表
+ ///
+ public List SelectedGroupIds { get; set; }
+}
+
+///
+/// 检查组别选择限制输出
+///
+public class CheckGroupSelectionLimitOutput
+{
+ ///
+ /// 是否可以选择该组别
+ ///
+ public bool CanSelect { get; set; }
+
+ ///
+ /// 限制消息(如果不能选择)
+ ///
+ public string? LimitMessage { get; set; }
+
+ ///
+ /// 组别限制详情
+ ///
+ public GroupLimitDetail? GroupLimit { get; set; }
+
+ ///
+ /// 项目限制详情
+ ///
+ public ProjectLimitDetail? ProjectLimit { get; set; }
+
+ ///
+ /// 团队项目限制详情
+ ///
+ public EventLimitDetail? TeamProjectLimit { get; set; }
+
+ ///
+ /// 个人项目限制详情
+ ///
+ public EventLimitDetail? IndividualProjectLimit { get; set; }
+}
+
+///
+/// 组别限制详情
+///
+public class GroupLimitDetail
+{
+ ///
+ /// 组别限报数
+ ///
+ public int? GroupLimitCount { get; set; }
+
+ ///
+ /// 当前报名数
+ ///
+ public int CurrentRegistrations { get; set; }
+
+ ///
+ /// 是否达到限制
+ ///
+ public bool IsLimitReached { get; set; }
+}
+
+///
+/// 项目限制详情
+///
+public class ProjectLimitDetail
+{
+ ///
+ /// 项目限报数
+ ///
+ public int? ProjectLimitCount { get; set; }
+
+ ///
+ /// 当前已选择数
+ ///
+ public int CurrentSelectedCount { get; set; }
+
+ ///
+ /// 是否达到限制
+ ///
+ public bool IsLimitReached { get; set; }
+}
+
+///
+/// 赛事项目限制详情
+///
+public class EventLimitDetail
+{
+ ///
+ /// 限报数
+ ///
+ public int? LimitCount { get; set; }
+
+ ///
+ /// 当前已选择数
+ ///
+ public int CurrentSelectedCount { get; set; }
+
+ ///
+ /// 是否达到限制
+ ///
+ public bool IsLimitReached { get; set; }
+}
+
///
/// 组别队员分配
///
diff --git a/YD_Event.Application/Event/EventArrangementService.cs b/YD_Event.Application/Event/EventArrangementService.cs
index 1d37435..ce86e51 100644
--- a/YD_Event.Application/Event/EventArrangementService.cs
+++ b/YD_Event.Application/Event/EventArrangementService.cs
@@ -88,7 +88,9 @@ public class EventArrangementService : BaseService x.EventId == eventId);
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
- projects = projects.OrderBy(x => x.SortIndex).ToList();
+
+ // 按照 SortIndex 对 projects 排序
+ var sortedProjects = projects.OrderBy(x => x.SortIndex).ToList();
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
@@ -123,10 +125,11 @@ public class EventArrangementService : BaseService();
- foreach (var eventProj in eventProjects)
+ // 按照排序后的项目顺序处理
+ foreach (var proj in sortedProjects)
{
- var proj = projects.FirstOrDefault(x => x.Id == eventProj.ProjectId);
- if (proj == null) continue;
+ var eventProj = eventProjects.FirstOrDefault(x => x.ProjectId == proj.Id);
+ if (eventProj == null) continue;
var dto = new ArrangementProjectListOutput
{
@@ -241,7 +244,9 @@ public class EventArrangementService : BaseService x.EventId == eventId);
var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList();
var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id));
- projects = projects.OrderBy(x => x.SortIndex).ToList();
+
+ // 按照 SortIndex 对 projects 排序
+ var sortedProjects = projects.OrderBy(x => x.SortIndex).ToList();
var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId));
// 统计各组别报名人数
@@ -274,11 +279,11 @@ public class EventArrangementService : BaseService();
- // 2. 生成排期
- foreach (var eventProj in eventProjects)
+ // 2. 生成排期 - 按照排序后的项目顺序
+ foreach (var proj in sortedProjects)
{
- var proj = projects.FirstOrDefault(x => x.Id == eventProj.ProjectId);
- if (proj == null) continue;
+ var eventProj = eventProjects.FirstOrDefault(x => x.ProjectId == proj.Id);
+ if (eventProj == null) continue;
int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1;
diff --git a/YD_Event.Application/Event/EventScoreService.cs b/YD_Event.Application/Event/EventScoreService.cs
index 225e274..17c2012 100644
--- a/YD_Event.Application/Event/EventScoreService.cs
+++ b/YD_Event.Application/Event/EventScoreService.cs
@@ -755,21 +755,11 @@ public class EventScoreService : BaseService, ITransient
throw Oops.Bah("没有可发放积分的成绩记录");
}
- var existingRecords = await _pointsRecordRepository.AsQueryable()
- .Where(x => x.EventId == input.EventId)
- .Select(x => x.Remark)
+ var processedResultIdsList = await _pointsRecordRepository.AsQueryable()
+ .Where(x => x.EventId == input.EventId && x.ResultId.HasValue)
+ .Select(x => x.ResultId.Value)
.ToListAsync();
-
- var processedResultIds = existingRecords
- .Where(x => !string.IsNullOrEmpty(x) && x.StartsWith("ResultId:"))
- .Select(x =>
- {
- var parts = x.Split('|');
- var resultIdPart = parts.FirstOrDefault(p => p.StartsWith("ResultId:"));
- return resultIdPart != null && long.TryParse(resultIdPart.Replace("ResultId:", ""), out var id) ? id : 0;
- })
- .Where(x => x > 0)
- .ToHashSet();
+ var processedResultIds = new HashSet(processedResultIdsList);
int successCount = 0;
int skipCount = 0;
@@ -805,8 +795,9 @@ public class EventScoreService : BaseService, ITransient
Type = PointsOperationType.Acquire,
EventId = input.EventId,
ProjectId = result.ProjectIdForRecord,
+ ResultId = result.ResultId,
Points = pointsToAward,
- Remark = $"ResultId:{result.ResultId}|Rank:{result.Rank}",
+ Remark = eventInfo.EventName,
Status = AvailabilityStatus.Enable,
CreatedTime = DateTime.Now
};
diff --git a/YD_Event.Application/Event/UnifiedRegistrationService.cs b/YD_Event.Application/Event/UnifiedRegistrationService.cs
index 6a56f0b..1ebff60 100644
--- a/YD_Event.Application/Event/UnifiedRegistrationService.cs
+++ b/YD_Event.Application/Event/UnifiedRegistrationService.cs
@@ -1,6 +1,8 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using YD_Event.Application.Event.Dtos;
+using YD_Event.Application.Order.Dtos;
+using YD_Event.Application.Refund;
using YD_Event.Core.Domain;
using YD_Event.Core.Domain.Pricing;
using YD_Event.Core.Domain.Pricing.Models;
@@ -46,6 +48,8 @@ public class UnifiedRegistrationService : ITransient
private readonly IOptionsMonitor _wechatTenpayClientOptionsMonitor;
private readonly ILogger _logger;
private readonly OrderNoGeneratorService _orderNoGeneratorService;
+ private readonly RefundConfigService _refundConfigService;
+ private readonly ISqlSugarRepository _refundRecordRepository;
public UnifiedRegistrationService(
ISqlSugarRepository eventRepository,
@@ -75,7 +79,9 @@ public class UnifiedRegistrationService : ITransient
WeChatPayService weChatPayService,
IOptionsMonitor wechatTenpayClientOptionsMonitor,
ILogger logger,
- OrderNoGeneratorService orderNoGeneratorService)
+ OrderNoGeneratorService orderNoGeneratorService,
+ RefundConfigService refundConfigService,
+ ISqlSugarRepository refundRecordRepository)
{
_eventRepository = eventRepository;
_unitRepository = unitRepository;
@@ -105,6 +111,8 @@ public class UnifiedRegistrationService : ITransient
_wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor;
_logger = logger;
_orderNoGeneratorService = orderNoGeneratorService;
+ _refundConfigService = refundConfigService;
+ _refundRecordRepository = refundRecordRepository;
}
///
@@ -1285,7 +1293,7 @@ public class UnifiedRegistrationService : ITransient
_logger.LogDebug("检查成员报名限制,成员ID: {MemberId}, 姓名: {MemberName}", member.Id, member.Name);
var (individualProjectCount, teamProjectCount) = CalculateMemberProjectCounts(
- member.Id, eventProjects, projects, registrationPersons, allRegistrationItems);
+ member.Id, eventProjects, projects, registrationPersons, allRegistrationItems, input);
_logger.LogDebug("成员 {MemberName} 个人项目报名数: {IndividualCount}, 团队项目报名数: {TeamCount}",
member.Name, individualProjectCount, teamProjectCount);
@@ -1296,14 +1304,15 @@ public class UnifiedRegistrationService : ITransient
}
///
- /// 计算成员项目报名数量
+ /// 计算成员项目报名数量(包含本次提交的项目)
///
private (int individualCount, int teamCount) CalculateMemberProjectCounts(
long memberId,
List eventProjects,
List projects,
List registrationPersons,
- List allRegistrationItems)
+ List allRegistrationItems,
+ UnifiedRegistrationInput input)
{
var matchedPersons = registrationPersons.Where(x => x.UserId == memberId).ToList();
var personRegistrationIds = matchedPersons.Select(x => x.RegistrationId).Distinct().ToList();
@@ -1312,6 +1321,7 @@ public class UnifiedRegistrationService : ITransient
int individualProjectCount = 0;
int teamProjectCount = 0;
+ // 统计已存在的报名项目
foreach (var item in personRegistrationItems)
{
var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == item.ProjectId);
@@ -1330,6 +1340,43 @@ public class UnifiedRegistrationService : ITransient
}
}
+ // 统计本次提交的项目,确保不重复统计
+ var existingProjectIds = new HashSet(personRegistrationItems.Select(x => x.ProjectId));
+
+ foreach (var item in input.Items)
+ {
+ // 检查该成员是否在本次提交的项目中
+ var allPersonsInItem = GetAllPersonsFromItem(item);
+ if (!allPersonsInItem.Any(p => p.UserId.HasValue && p.UserId.Value == memberId))
+ {
+ continue;
+ }
+
+ // 检查是否已经统计过该项目
+ if (existingProjectIds.Contains(item.ProjectId))
+ {
+ continue;
+ }
+
+ var eventProject = eventProjects.FirstOrDefault(ep => ep.Id == item.ProjectId);
+ if (eventProject == null) continue;
+
+ var baseProject = projects.FirstOrDefault(p => p.Id == eventProject.ProjectId);
+ if (baseProject == null) continue;
+
+ if (baseProject.IsIndividualProject())
+ {
+ individualProjectCount++;
+ }
+ else if (baseProject.IsTeamProject())
+ {
+ teamProjectCount++;
+ }
+
+ // 标记该项目已统计
+ existingProjectIds.Add(item.ProjectId);
+ }
+
return (individualProjectCount, teamProjectCount);
}
@@ -1342,7 +1389,7 @@ public class UnifiedRegistrationService : ITransient
YD_Event.Core.Entities.AppUser member,
int individualProjectCount)
{
- if (individualProjectCount < limit.IndividualProjectLimit)
+ if (individualProjectCount <= limit.IndividualProjectLimit)
{
return;
}
@@ -1367,7 +1414,7 @@ public class UnifiedRegistrationService : ITransient
YD_Event.Core.Entities.AppUser member,
int teamProjectCount)
{
- if (teamProjectCount < limit.TeamProjectLimit)
+ if (teamProjectCount <= limit.TeamProjectLimit)
{
return;
}
@@ -2108,4 +2155,434 @@ public class UnifiedRegistrationService : ITransient
result.Details.Add(CreateValidationDetail("单位认证验证", true, "用户单位认证验证通过"));
return result;
}
+
+ #region 统一退款相关方法
+
+ ///
+ /// 计算退款金额(统一方法)
+ ///
+ /// 总金额
+ /// 赛事开始时间
+ /// 应退金额
+ [NonAction]
+ public async Task CalculateRefundAmountAsync(decimal totalAmount, DateTime eventStartTime)
+ {
+ var refundAmount = await _refundConfigService.CalculateRefundAmount(totalAmount, eventStartTime);
+ _logger.LogInformation("统一退款金额计算:总金额={TotalAmount}, 赛事开始时间={EventStartTime}, 计算后退款金额={RefundAmount}",
+ totalAmount, eventStartTime, refundAmount);
+ return refundAmount;
+ }
+
+ ///
+ /// 统一取消报名预览方法
+ ///
+ /// 报名ID
+ /// 操作人用户ID
+ /// 取消预览输出
+ [NonAction]
+ public async Task GetCancelPreviewAsync(long registrationId, long operatorUserId)
+ {
+ var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == operatorUserId);
+ if (registration == null) throw Oops.Bah("报名记录不存在");
+
+ var eventInfo = await _db.Queryable().InSingleAsync(registration.EventId);
+ var items = await _db.Queryable().Where(x => x.RegistrationId == registrationId).ToListAsync();
+ var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
+
+ var refundableAmount = await CalculateRefundAmountAsync(registration.TotalAmount, eventInfo.MatchStartTime);
+
+ _logger.LogInformation("统一取消报名预览:报名ID={RegistrationId}, 赛事名称={EventName}, 实际支付={ActualAmount}, 可退款={RefundAmount}",
+ registrationId, eventInfo?.EventName, registration.TotalAmount, refundableAmount);
+
+ 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.ProjectTypeName.Contains("团队")? "团队" : (item.ProjectTypeName.Contains("个人") ? "个人" : string.Empty),
+ 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;
+ }
+
+ ///
+ /// 统一取消报名执行方法
+ ///
+ /// 取消执行输入
+ /// 操作人用户ID
+ /// 取消执行输出
+ [UnitOfWork]
+ [NonAction]
+ public async Task ExecuteCancelRegistrationAsync(CancelExecuteInput input, long operatorUserId)
+ {
+ var registration = await _registrationRepository.GetFirstAsync(x => x.Id == input.RegistrationId && x.CreatedUserId == operatorUserId);
+ 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 eventInfo = await _eventRepository.GetFirstAsync(e => e.Id == registration.EventId);
+
+ var baseRefundAmount = await CalculateRefundAmountAsync(registration.TotalAmount, eventInfo.MatchStartTime);
+ _logger.LogInformation("统一退款金额计算:报名ID={RegistrationId}, 总金额={TotalAmount}, 按规则计算后金额={BaseRefundAmount}",
+ registration.Id, registration.TotalAmount, baseRefundAmount);
+
+ decimal actualRefundAmount = baseRefundAmount;
+ bool isPartialCancel = input.CancelType == 1 && input.PersonIds != null && input.PersonIds.Any();
+
+ if (isPartialCancel)
+ {
+ var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registration.Id);
+ var totalPersons = persons.Count;
+ if (totalPersons == 0)
+ {
+ throw Oops.Bah("报名记录中没有队员信息");
+ }
+
+ var selectedPersons = persons.Where(p => input.PersonIds.Contains(p.Id)).ToList();
+ if (selectedPersons.Count != input.PersonIds.Count)
+ {
+ var invalidIds = input.PersonIds.Except(selectedPersons.Select(p => p.Id)).ToList();
+ throw Oops.Bah($"选择的人员ID无效:{string.Join(", ", invalidIds)}");
+ }
+
+ var perPersonRefund = baseRefundAmount / totalPersons;
+ actualRefundAmount = perPersonRefund * selectedPersons.Count;
+
+ _logger.LogInformation("统一部分退款:报名ID={RegistrationId}, 总人数={TotalPersons}, 选择人数={SelectedCount}, 单人退款金额={PerPersonRefund}, 实际退款金额={ActualRefundAmount}",
+ registration.Id, totalPersons, selectedPersons.Count, perPersonRefund, actualRefundAmount);
+
+ await _registrationPersonRepository.DeleteAsync(x => input.PersonIds.Contains(x.Id));
+ _logger.LogInformation("已删除队员:报名ID={RegistrationId}, 队员ID列表={PersonIds}",
+ registration.Id, string.Join(", ", input.PersonIds));
+
+ var remainingPersonsCount = await _registrationPersonRepository.CountAsync(x => x.RegistrationId == registration.Id);
+ registration.TotalPersonCount = remainingPersonsCount;
+
+ if (remainingPersonsCount > 0)
+ {
+ registration.UpdatedUserId = operatorUserId;
+ registration.UpdatedTime = now;
+ registration.RegistrationRemark = input.CancelReason;
+ await _registrationRepository.UpdateAsync(registration);
+ _logger.LogInformation("统一部分退款完成,仍有剩余人员:报名ID={RegistrationId}, 剩余人数={RemainingCount}",
+ registration.Id, remainingPersonsCount);
+ }
+ else
+ {
+ registration.PaymentStatus = 3;
+ registration.UpdatedUserId = operatorUserId;
+ registration.UpdatedTime = now;
+ registration.RegistrationRemark = input.CancelReason;
+ await _registrationRepository.UpdateAsync(registration);
+ _logger.LogInformation("统一所有人员已退款,更新报名状态为已退款:报名ID={RegistrationId}", registration.Id);
+ }
+ }
+ else
+ {
+ _logger.LogInformation("统一全额退款:报名ID={RegistrationId}, 退款金额={ActualRefundAmount}",
+ registration.Id, actualRefundAmount);
+
+ registration.PaymentStatus = 3;
+ registration.UpdatedUserId = operatorUserId;
+ registration.UpdatedTime = now;
+ registration.RegistrationRemark = input.CancelReason;
+ await _registrationRepository.UpdateAsync(registration);
+ }
+
+ var output = new CancelExecuteOutput
+ {
+ Success = true,
+ Message = isPartialCancel ? "部分取消报名成功" : "取消报名成功",
+ CancelTime = now,
+ RefundAmount = actualRefundAmount
+ };
+
+ if (registration.TotalAmount > 0 && !string.IsNullOrEmpty(registration.OrderNo) && actualRefundAmount > 0)
+ {
+ 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(actualRefundAmount),
+ TotalAmount = AmountHelper.YuanToFen(registration.TotalAmount),
+ Reason = input.CancelReason
+ };
+
+ _logger.LogInformation("开始执行统一退款:报名ID={RegistrationId}, 订单号={OrderNo}, 退款单号={RefundNo}, 退款金额={Amount}, 退款类型={CancelType}",
+ registration.Id, registration.OrderNo, outRefundNo, actualRefundAmount, isPartialCancel ? "部分退款" : "全额退款");
+
+ 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;
+ }
+
+ await CreateRefundRecordAsync(registration, eventInfo, wechatConfig, outRefundNo,
+ refundResult.RefundId, actualRefundAmount, registration.TotalAmount, input.CancelReason, operatorUserId);
+
+ _logger.LogInformation("统一退款成功:报名ID={RegistrationId}, 退款单号={RefundNo}, 退款金额={Amount}",
+ registration.Id, outRefundNo, actualRefundAmount);
+ }
+ }
+ }
+
+ if (!isPartialCancel || registration.PaymentStatus == 3)
+ {
+ await UpdateOrderStatusAsync(registration.OrderNo, OrderStatus.Refunded, operatorUserId);
+ }
+
+ _logger.LogInformation("统一取消报名完成:报名ID={RegistrationId}, 操作人={UserId}, 退款金额={RefundAmount}, 成功={Success}",
+ registration.Id, operatorUserId, actualRefundAmount, output.Success);
+
+ return output;
+ }
+
+ ///
+ /// 创建退款记录
+ ///
+ private async Task CreateRefundRecordAsync(EventRegistration registration, EventInfo eventInfo,
+ WeChatPayConfig wechatConfig, string outRefundNo, string? refundId,
+ decimal refundAmount, decimal totalAmount, string? reason, long operatorUserId)
+ {
+ var refundRecord = new RefundRecord
+ {
+ Id = _idGenerator.NewLong(),
+ OutRefundNo = outRefundNo,
+ RefundId = refundId,
+ RegistrationId = registration.Id,
+ OutTradeNo = registration.OrderNo,
+ TransactionId = registration.PaymentProofUrl,
+ RefundAmount = refundAmount,
+ TotalAmount = totalAmount,
+ RefundReason = reason,
+ RefundStatus = 1,
+ ApplyTime = DateTime.Now,
+ SuccessTime = DateTime.Now,
+ MerchantId = eventInfo.MerchantId,
+ WeChatPayConfigId = wechatConfig.Id,
+ Status = AvailabilityStatus.Enable,
+ CreatedUserId = operatorUserId,
+ CreatedTime = DateTime.Now
+ };
+
+ await _refundRecordRepository.InsertAsync(refundRecord);
+ _logger.LogInformation("退款记录创建成功:退款记录ID={RefundRecordId}, 退款单号={OutRefundNo}",
+ refundRecord.Id, outRefundNo);
+ }
+
+ ///
+ /// 更新订单状态
+ ///
+ private async Task UpdateOrderStatusAsync(string orderNo, OrderStatus orderStatus, long operatorUserId)
+ {
+ if (!string.IsNullOrEmpty(orderNo))
+ {
+ var order = await _orderRepository.GetFirstAsync(o => o.OrderNo == orderNo);
+ if (order != null)
+ {
+ order.OrderStatus = orderStatus;
+ order.UpdatedTime = DateTime.Now;
+ order.UpdatedUserId = operatorUserId;
+ await _orderRepository.UpdateAsync(order);
+ _logger.LogInformation("订单状态已同步更新:订单号={OrderNo}, 状态={OrderStatus}",
+ orderNo, orderStatus);
+ }
+ else
+ {
+ _logger.LogWarning("未找到对应的订单记录:订单号={OrderNo}", orderNo);
+ }
+ }
+ }
+
+ ///
+ /// 统一后台管理退款方法
+ ///
+ /// 退款订单输入
+ /// 操作人用户ID
+ [UnitOfWork]
+ [NonAction]
+ public async Task ExecuteAdminRefundAsync(RefundOrderInput input, long operatorUserId)
+ {
+ var order = await _orderRepository.GetByIdAsync(input.Id);
+ if (order == null) throw Oops.Bah("订单不存在");
+
+ if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款");
+
+ if (!AmountHelper.IsValidPositiveAmount(input.RefundAmount))
+ {
+ throw Oops.Bah("退款金额必须大于0且最多保留两位小数");
+ }
+
+ if (input.RefundAmount > order.ActualAmount)
+ {
+ _logger.LogWarning("统一后台超额退款尝试:订单ID={OrderId}, 实付金额={ActualAmount}, 申请退款金额={RefundAmount}",
+ order.Id, order.ActualAmount, input.RefundAmount);
+ throw Oops.Bah($"退款金额({input.RefundAmount})不能超过实付金额({order.ActualAmount})");
+ }
+
+ var currentUserId = operatorUserId;
+ var now = DateTime.Now;
+ bool isPartialCancel = input.CancelType == 1 && input.PersonIds != null && input.PersonIds.Any();
+
+ if (isPartialCancel)
+ {
+ var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == order.RegistrationId);
+ var totalPersons = persons.Count;
+ if (totalPersons == 0)
+ {
+ throw Oops.Bah("报名记录中没有队员信息");
+ }
+
+ var selectedPersons = persons.Where(p => input.PersonIds.Contains(p.Id)).ToList();
+ if (selectedPersons.Count != input.PersonIds.Count)
+ {
+ var invalidIds = input.PersonIds.Except(selectedPersons.Select(p => p.Id)).ToList();
+ throw Oops.Bah($"选择的人员ID无效:{string.Join(", ", invalidIds)}");
+ }
+
+ _logger.LogInformation("统一后台部分退款:订单ID={OrderId}, 总人数={TotalPersons}, 选择人数={SelectedCount}, 退款金额={RefundAmount}",
+ order.Id, totalPersons, selectedPersons.Count, input.RefundAmount);
+
+ await _registrationPersonRepository.DeleteAsync(x => input.PersonIds.Contains(x.Id));
+ _logger.LogInformation("已删除队员:订单ID={OrderId}, 队员ID列表={PersonIds}",
+ order.Id, string.Join(", ", input.PersonIds));
+
+ var remainingPersonsCount = await _registrationPersonRepository.CountAsync(x => x.RegistrationId == order.RegistrationId);
+ var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
+ if (reg != null)
+ {
+ reg.TotalPersonCount = remainingPersonsCount;
+ reg.UpdatedTime = now;
+ reg.UpdatedUserId = currentUserId;
+ await _registrationRepository.UpdateAsync(reg);
+ }
+
+ if (remainingPersonsCount > 0)
+ {
+ var refundOrder = new YD_Event.Core.Entities.Order
+ {
+ Id = _idGenerator.NewLong(),
+ OrderNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync(),
+ OrderType = 2,
+ MerchantId = order.MerchantId,
+ MerchantCertificationId = order.MerchantCertificationId,
+ EventId = order.EventId,
+ RegistrationId = order.RegistrationId,
+ RelatedOrderId = order.Id,
+ OriginalAmount = order.ActualAmount,
+ ActualAmount = input.RefundAmount,
+ OrderStatus = OrderStatus.Refunded,
+ PaymentTime = now,
+ Remark = input.Reason,
+ CreatedTime = now,
+ CreatedUserId = currentUserId
+ };
+ await _orderRepository.InsertAsync(refundOrder);
+
+ _logger.LogInformation("统一后台部分退款成功:订单ID={OrderId}, 退款订单号={RefundOrderNo}, 退款金额={RefundAmount}, 剩余人数={RemainingCount}, 操作人={UserId}",
+ order.Id, refundOrder.OrderNo, input.RefundAmount, remainingPersonsCount, currentUserId);
+ return;
+ }
+ }
+
+ if (!isPartialCancel)
+ {
+ var existingRefund = await _orderRepository.IsAnyAsync(o => o.RelatedOrderId == order.Id && o.OrderType == 2);
+ if (existingRefund)
+ {
+ throw Oops.Bah("该订单已存在退款记录");
+ }
+ }
+
+ var fullRefundOrder = new YD_Event.Core.Entities.Order
+ {
+ Id = _idGenerator.NewLong(),
+ OrderNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync(),
+ OrderType = 2,
+ MerchantId = order.MerchantId,
+ MerchantCertificationId = order.MerchantCertificationId,
+ EventId = order.EventId,
+ RegistrationId = order.RegistrationId,
+ RelatedOrderId = order.Id,
+ OriginalAmount = order.ActualAmount,
+ ActualAmount = input.RefundAmount,
+ OrderStatus = OrderStatus.Refunded,
+ PaymentTime = now,
+ Remark = input.Reason,
+ CreatedTime = now,
+ CreatedUserId = currentUserId
+ };
+ await _orderRepository.InsertAsync(fullRefundOrder);
+
+ _logger.LogInformation("统一后台退款成功:订单ID={OrderId}, 退款订单号={RefundOrderNo}, 退款金额={RefundAmount}, 退款类型={RefundType}, 操作人={UserId}",
+ order.Id, fullRefundOrder.OrderNo, input.RefundAmount, isPartialCancel ? "部分退款" : "全额退款", currentUserId);
+
+ order.OrderStatus = OrderStatus.Refunded;
+ order.UpdatedTime = now;
+ order.UpdatedUserId = currentUserId;
+ await _orderRepository.UpdateAsync(order);
+
+ var registration = await _registrationRepository.GetByIdAsync(order.RegistrationId);
+ if (registration != null)
+ {
+ registration.PaymentStatus = 3;
+ registration.UpdatedTime = now;
+ registration.UpdatedUserId = currentUserId;
+ await _registrationRepository.UpdateAsync(registration);
+ }
+
+ var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == order.RegistrationId);
+ if (payment != null)
+ {
+ payment.RefundStatus = 1;
+ payment.UpdatedTime = now;
+ payment.UpdatedUserId = currentUserId;
+ await _paymentRepository.UpdateAsync(payment);
+ }
+ }
+
+ #endregion
}
diff --git a/YD_Event.Application/Event/UserEventRecordService.cs b/YD_Event.Application/Event/UserEventRecordService.cs
index 4e62ec9..f13cd89 100644
--- a/YD_Event.Application/Event/UserEventRecordService.cs
+++ b/YD_Event.Application/Event/UserEventRecordService.cs
@@ -35,17 +35,14 @@ public class UserEventRecordService : IDynamicApiController, ITransient
private readonly ISqlSugarRepository _projectRepository;
private readonly ISqlSugarRepository _userRepository;
private readonly ISqlSugarRepository _certificateRepository;
- private readonly ISqlSugarRepository _weChatPayConfigRepository;
- private readonly ISqlSugarRepository _merchantRepository;
- private readonly ISqlSugarRepository _eventInfoRepository;
private readonly ISqlSugarRepository _orderRepository;
+ private readonly ISqlSugarRepository _eventRepository;
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 _logger;
+ private readonly UnifiedRegistrationService _unifiedRegistrationService;
public UserEventRecordService(
ISqlSugarRepository registrationRepository,
@@ -54,17 +51,14 @@ public class UserEventRecordService : IDynamicApiController, ITransient
ISqlSugarRepository projectRepository,
ISqlSugarRepository userRepository,
ISqlSugarRepository certificateRepository,
- ISqlSugarRepository weChatPayConfigRepository,
- ISqlSugarRepository merchantRepository,
- ISqlSugarRepository eventInfoRepository,
ISqlSugarRepository orderRepository,
+ ISqlSugarRepository eventRepository,
ISqlSugarClient db,
AuthManager authManager,
IIdGenerator idGenerator,
FileService fileService,
- WeChatPayService weChatPayService,
- OrderNoGeneratorService orderNoGeneratorService,
- ILogger logger)
+ ILogger logger,
+ UnifiedRegistrationService unifiedRegistrationService)
{
_registrationRepository = registrationRepository;
_registrationPersonRepository = registrationPersonRepository;
@@ -72,17 +66,14 @@ public class UserEventRecordService : IDynamicApiController, ITransient
_projectRepository = projectRepository;
_userRepository = userRepository;
_certificateRepository = certificateRepository;
- _weChatPayConfigRepository = weChatPayConfigRepository;
- _merchantRepository = merchantRepository;
+ _orderRepository = orderRepository;
+ _eventRepository = eventRepository;
_db = db;
_authManager = authManager;
_idGenerator = idGenerator;
_fileService = fileService;
- _weChatPayService = weChatPayService;
- _eventInfoRepository = eventInfoRepository;
- _orderRepository = orderRepository;
- _orderNoGeneratorService = orderNoGeneratorService;
_logger = logger;
+ _unifiedRegistrationService = unifiedRegistrationService;
}
///
@@ -98,6 +89,12 @@ public class UserEventRecordService : IDynamicApiController, ITransient
.InnerJoin((r, e) => r.EventId == e.Id)
.LeftJoin((r, e, fc) => e.Id == fc.EventId)
.Where((r, e, fc) => r.CreatedUserId == userId && !r.DeleteMark)
+ // 支付状态筛选:
+ // - 线下支付时:显示未支付(0)和已支付(1)状态的数据
+ // - 线上支付时:仅显示已支付(1)状态的数据
+ .Where((r, e, fc) =>
+ (r.PaymentMethod == 1 && (r.PaymentStatus == 0 || r.PaymentStatus == 1)) ||
+ (r.PaymentMethod == 2 && r.PaymentStatus == 1))
.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)
@@ -107,7 +104,8 @@ public class UserEventRecordService : IDynamicApiController, ITransient
r.CreatedTime, r.UpdatedTime, r.TotalPersonCount, e.MaterialUploadEndTime,
e.CoverImage, e.EventType, e.EventStatus, e.RegistrationStartTime, e.RegistrationEndTime,
e.MatchStartTime, e.MatchEndTime,
- fc.ChargeMode
+ fc.ChargeMode,
+ r.PaymentMethod
})
.ToPagedListAsync(input);
@@ -126,7 +124,6 @@ public class UserEventRecordService : IDynamicApiController, ITransient
MatchStartTime = x.MatchStartTime,
MatchEndTime = x.MatchEndTime,
TeamName = x.TeamName,
- Status = x.PaymentStatus,
CreateTime = x.CreatedTime,
UpdateTime = x.UpdatedTime,
PayStatus = x.PaymentStatus,
@@ -375,58 +372,8 @@ public class UserEventRecordService : IDynamicApiController, ITransient
public async Task 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().InSingleAsync(registration.EventId);
-
- // 获取项目列表
- var items = await _db.Queryable()
- .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;
+ _logger.LogInformation("调用统一取消报名预览:报名ID={RegistrationId}, 操作人={UserId}", registrationId, userId);
+ return await _unifiedRegistrationService.GetCancelPreviewAsync(registrationId, userId);
}
///
@@ -438,81 +385,8 @@ public class UserEventRecordService : IDynamicApiController, ITransient
public async Task 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;
+ _logger.LogInformation("调用统一执行取消报名:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId);
+ return await _unifiedRegistrationService.ExecuteCancelRegistrationAsync(input, userId);
}
///
@@ -527,7 +401,7 @@ public class UserEventRecordService : IDynamicApiController, ITransient
if (registration == null) throw Oops.Bah("报名记录不存在");
// 获取赛事信息
- var eventInfo = await _eventInfoRepository.GetByIdAsync(registration.EventId);
+ var eventInfo = await _eventRepository.GetByIdAsync(registration.EventId);
// 获取报名项目
var items = await _registrationItemRepository.GetListAsync(x => x.RegistrationId == registrationId);
diff --git a/YD_Event.Application/Event/UserEventRegistrationService.cs b/YD_Event.Application/Event/UserEventRegistrationService.cs
index ba226c6..856447b 100644
--- a/YD_Event.Application/Event/UserEventRegistrationService.cs
+++ b/YD_Event.Application/Event/UserEventRegistrationService.cs
@@ -1128,6 +1128,10 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
continue;
}
+ // 判断是否为团队项目
+ var isTeamProject = project.IsTeamProject();
+
+ // 计算分配的人数(去重)
var totalAssignedMembers = input.GroupMembers
.Where(g => assignedProjectGroups.Select(pg => pg.Id).Contains(g.GroupId))
.SelectMany(g => g.MemberIds)
@@ -1144,18 +1148,23 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
ProjectId = eventProject.Id,
ProjectName = project.Name ?? "未知项目",
ErrorType = "Insufficient",
- ErrorMessage = $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前只分配了{totalAssignedMembers}人"
+ ErrorMessage = isTeamProject
+ ? $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前只分配了{totalAssignedMembers}人"
+ : $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前只分配了{totalAssignedMembers}人"
});
}
- else if (totalAssignedMembers > project.CompletionCount)
+ else if (totalAssignedMembers >= project.CompletionCount)
{
+ // 只要达到或超过完成人数就提示超限(特别是个人项目)
result.HasError = true;
result.Errors.Add(new ProjectCompletionError
{
ProjectId = eventProject.Id,
ProjectName = project.Name ?? "未知项目",
ErrorType = "Excessive",
- ErrorMessage = $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前分配了{totalAssignedMembers}人,人数超额"
+ ErrorMessage = isTeamProject
+ ? $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前分配了{totalAssignedMembers}人,人数超额"
+ : $"项目\"{project.Name}\"要求完成人数为{project.CompletionCount}人,当前分配了{totalAssignedMembers}人,人数超额"
});
}
}
@@ -1258,4 +1267,140 @@ public class UserEventRegistrationService : IDynamicApiController, ITransient
};
}
+ ///
+ /// 检查组别选择限制
+ ///
+ [DisplayName("检查组别选择限制")]
+ [HttpPost("check-group-selection-limit")]
+ public async Task CheckGroupSelectionLimit([FromBody] CheckGroupSelectionLimitInput input)
+ {
+ var result = new CheckGroupSelectionLimitOutput
+ {
+ CanSelect = true
+ };
+
+ // 获取组别信息
+ var group = await _eventProjectGroupRepository.GetByIdAsync(input.GroupId);
+ if (group == null)
+ {
+ result.CanSelect = false;
+ result.LimitMessage = "组别不存在";
+ return result;
+ }
+
+ // 获取项目信息
+ var eventProject = await _eventProjectRepository.GetByIdAsync(group.EventProjectId);
+ if (eventProject == null)
+ {
+ result.CanSelect = false;
+ result.LimitMessage = "项目不存在";
+ return result;
+ }
+
+ // 获取基础项目信息
+ var baseProject = await _projectRepository.GetByIdAsync(eventProject.ProjectId);
+
+ // 获取赛事信息获取报名限制
+ var eventInfo = await _eventRepository.GetByIdAsync(input.EventId);
+ var registrationLimit = eventInfo != null
+ ? await _limitRepository.GetFirstAsync(x => x.EventId == input.EventId && !x.DeleteMark)
+ : null;
+
+ // 1. 检查组别自身限制(这里暂不实现,因为 EventProjectGroup 实体没有 GroupLimitCount 字段)
+ // 后续可以根据实际需求添加相关的限制配置表
+
+ // 2. 检查项目限制
+ var selectedGroupIds = input.SelectedGroupIds ?? new List();
+ var otherSelectedGroups = await _eventProjectGroupRepository.GetListAsync(x => selectedGroupIds.Contains(x.Id));
+ var projectGroups = otherSelectedGroups.Where(g => g.EventProjectId == eventProject.Id).ToList();
+
+ // 计算该项目已选的组别数量(排除当前组别)
+ var selectedCountForProject = projectGroups.Count;
+
+ // 获取该项目的限报配置
+ var projectLimit = await _projectLimitRepository.GetFirstAsync(x =>
+ x.EventId == input.EventId && x.ProjectId == eventProject.ProjectId && !x.DeleteMark);
+
+ if (projectLimit != null)
+ {
+ result.ProjectLimit = new ProjectLimitDetail
+ {
+ ProjectLimitCount = projectLimit.LimitCount,
+ CurrentSelectedCount = selectedCountForProject,
+ IsLimitReached = selectedCountForProject >= projectLimit.LimitCount
+ };
+
+ if (result.ProjectLimit.IsLimitReached && !selectedGroupIds.Contains(input.GroupId))
+ {
+ result.CanSelect = false;
+ result.LimitMessage = $"该项目最多可报名{projectLimit.LimitCount}个组别";
+ return result;
+ }
+ }
+
+ // 3. 检查团队/个人项目限制
+ if (registrationLimit != null && baseProject != null)
+ {
+ var isTeamProject = baseProject.IsTeamProject();
+ var isIndividualProject = baseProject.IsIndividualProject();
+
+ // 计算已选的团队项目和个人项目数量
+ var allSelectedEventProjectIds = otherSelectedGroups
+ .Where(g => g.Id != input.GroupId)
+ .Select(g => g.EventProjectId)
+ .Distinct()
+ .ToList();
+
+ var allSelectedEventProjects = await _eventProjectRepository.GetListAsync(x => allSelectedEventProjectIds.Contains(x.Id));
+ var allSelectedProjectIds = allSelectedEventProjects.Select(ep => ep.ProjectId).Distinct().ToList();
+ var allSelectedProjects = await _projectRepository.GetListAsync(x => allSelectedProjectIds.Contains(x.Id));
+
+ var selectedTeamCount = 0;
+ var selectedIndividualCount = 0;
+
+ foreach (var selectedProject in allSelectedProjects)
+ {
+ if (selectedProject.IsTeamProject()) selectedTeamCount++;
+ if (selectedProject.IsIndividualProject()) selectedIndividualCount++;
+ }
+
+ // 团队项目限制
+ if (isTeamProject)
+ {
+ result.TeamProjectLimit = new EventLimitDetail
+ {
+ LimitCount = registrationLimit.TeamProjectLimit,
+ CurrentSelectedCount = selectedTeamCount,
+ IsLimitReached = selectedTeamCount >= registrationLimit.TeamProjectLimit
+ };
+
+ if (result.TeamProjectLimit.IsLimitReached && !selectedGroupIds.Contains(input.GroupId))
+ {
+ result.CanSelect = false;
+ result.LimitMessage = $"团队项目最多可报名{registrationLimit.TeamProjectLimit}个";
+ return result;
+ }
+ }
+
+ // 个人项目限制
+ if (isIndividualProject)
+ {
+ result.IndividualProjectLimit = new EventLimitDetail
+ {
+ LimitCount = registrationLimit.IndividualProjectLimit,
+ CurrentSelectedCount = selectedIndividualCount,
+ IsLimitReached = selectedIndividualCount >= registrationLimit.IndividualProjectLimit
+ };
+
+ if (result.IndividualProjectLimit.IsLimitReached && !selectedGroupIds.Contains(input.GroupId))
+ {
+ result.CanSelect = false;
+ result.LimitMessage = $"个人项目最多可报名{registrationLimit.IndividualProjectLimit}个";
+ return result;
+ }
+ }
+ }
+
+ return result;
+ }
}
diff --git a/YD_Event.Application/Order/Dtos/OrderListOutput.cs b/YD_Event.Application/Order/Dtos/OrderListOutput.cs
index bd3cac8..3c41889 100644
--- a/YD_Event.Application/Order/Dtos/OrderListOutput.cs
+++ b/YD_Event.Application/Order/Dtos/OrderListOutput.cs
@@ -311,4 +311,14 @@ public class RefundOrderInput
/// 退款原因
///
public string Reason { get; set; }
+
+ ///
+ /// 取消类型(0-全部取消,1-部分取消)
+ ///
+ public int CancelType { get; set; }
+
+ ///
+ /// 要取消的队员ID列表(部分取消时使用)
+ ///
+ public List? PersonIds { get; set; }
}
diff --git a/YD_Event.Application/Order/OrderService.cs b/YD_Event.Application/Order/OrderService.cs
index 570eb1b..5108507 100644
--- a/YD_Event.Application/Order/OrderService.cs
+++ b/YD_Event.Application/Order/OrderService.cs
@@ -3,10 +3,10 @@ using OfficeOpenXml;
using OfficeOpenXml.Style;
using System.Drawing;
using YD_Event.Application.Auth;
+using YD_Event.Application.Event;
using YD_Event.Application.Order.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Enum;
-using YD_Event.Application.Refund;
using Microsoft.Extensions.Logging;
using YD_Event.Core.Domain;
@@ -24,12 +24,12 @@ public class OrderService : BaseService, ITransien
private readonly ISqlSugarRepository _paymentRepository;
private readonly ISqlSugarRepository _eventRepository;
private readonly ISqlSugarRepository _merchantRepository;
- private readonly RefundConfigService _refundConfigService;
private readonly IIdGenerator _idGenerator;
private readonly AuthManager _authManager;
private readonly ISqlSugarClient _db;
private readonly OrderNoGeneratorService _orderNoGeneratorService;
private readonly ILogger _logger;
+ private readonly UnifiedRegistrationService _unifiedRegistrationService;
public OrderService(
ISqlSugarRepository orderRepository,
@@ -39,12 +39,12 @@ public class OrderService : BaseService, ITransien
ISqlSugarRepository paymentRepository,
ISqlSugarRepository eventRepository,
ISqlSugarRepository merchantRepository,
- RefundConfigService refundConfigService,
IIdGenerator idGenerator,
AuthManager authManager,
ISqlSugarClient db,
OrderNoGeneratorService orderNoGeneratorService,
- ILogger logger) : base(orderRepository)
+ ILogger logger,
+ UnifiedRegistrationService unifiedRegistrationService) : base(orderRepository)
{
_orderRepository = orderRepository;
_registrationRepository = registrationRepository;
@@ -53,12 +53,12 @@ public class OrderService : BaseService, ITransien
_paymentRepository = paymentRepository;
_eventRepository = eventRepository;
_merchantRepository = merchantRepository;
- _refundConfigService = refundConfigService;
_idGenerator = idGenerator;
_authManager = authManager;
_db = db;
_orderNoGeneratorService = orderNoGeneratorService;
_logger = logger;
+ _unifiedRegistrationService = unifiedRegistrationService;
}
///
@@ -432,7 +432,9 @@ public class OrderService : BaseService, ITransien
var eventInfo = await _eventRepository.GetByIdAsync(order.EventId);
if (eventInfo == null) return order.ActualAmount;
- return await _refundConfigService.CalculateRefundAmount(order.ActualAmount, eventInfo.MatchStartTime);
+ _logger.LogInformation("调用统一退款金额计算:订单ID={OrderId}, 总金额={TotalAmount}, 赛事开始时间={EventStartTime}",
+ orderId, order.ActualAmount, eventInfo.MatchStartTime);
+ return await _unifiedRegistrationService.CalculateRefundAmountAsync(order.ActualAmount, eventInfo.MatchStartTime);
}
///
@@ -443,83 +445,9 @@ public class OrderService : BaseService, ITransien
[UnitOfWork]
public async Task Refund(RefundOrderInput input)
{
- var order = await _orderRepository.GetByIdAsync(input.Id);
- if (order == null) throw Oops.Bah("订单不存在");
-
- if (order.OrderStatus != OrderStatus.Paid) throw Oops.Bah("订单未支付,无法退款");
-
- // 验证退款金额
- if (!AmountHelper.IsValidPositiveAmount(input.RefundAmount))
- {
- throw Oops.Bah("退款金额必须大于0且最多保留两位小数");
- }
-
- if (input.RefundAmount > order.ActualAmount)
- {
- _logger.LogWarning("超额退款尝试:订单ID={OrderId}, 实付金额={ActualAmount}, 申请退款金额={RefundAmount}",
- order.Id, order.ActualAmount, input.RefundAmount);
- throw Oops.Bah($"退款金额({input.RefundAmount})不能超过实付金额({order.ActualAmount})");
- }
-
- // 检查该订单是否已有退款记录
- var existingRefund = await _orderRepository.IsAnyAsync(o => o.RelatedOrderId == order.Id && o.OrderType == 2);
- if (existingRefund)
- {
- throw Oops.Bah("该订单已存在退款记录");
- }
-
var currentUserId = _authManager.UserId;
- var now = DateTime.Now;
-
- // 1. 创建退款订单
- var refundOrder = new YD_Event.Core.Entities.Order
- {
- Id = _idGenerator.NextId(),
- OrderNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync(),
- OrderType = 2, // 退款
- MerchantId = order.MerchantId,
- MerchantCertificationId = order.MerchantCertificationId, // 继承原订单的单位认证ID
- EventId = order.EventId,
- RegistrationId = order.RegistrationId,
- RelatedOrderId = order.Id, // 关联原订单
- OriginalAmount = order.ActualAmount,
- ActualAmount = input.RefundAmount, // 退款金额
- OrderStatus = OrderStatus.Refunded, // 退款成功
- PaymentTime = now,
- Remark = input.Reason,
- CreatedTime = now,
- CreatedUserId = currentUserId
- };
- await _orderRepository.InsertAsync(refundOrder);
-
- _logger.LogInformation("退款成功:订单ID={OrderId}, 退款订单号={RefundOrderNo}, 退款金额={RefundAmount}, 操作人={UserId}",
- order.Id, refundOrder.OrderNo, input.RefundAmount, currentUserId);
-
- // 2. 更新原订单状态
- order.OrderStatus = OrderStatus.Refunded;
- order.UpdatedTime = now;
- order.UpdatedUserId = currentUserId;
- await _orderRepository.UpdateAsync(order);
-
- // 3. 更新报名状态
- var reg = await _registrationRepository.GetByIdAsync(order.RegistrationId);
- if (reg != null)
- {
- reg.PaymentStatus = 3; // 已退款
- reg.UpdatedTime = now;
- reg.UpdatedUserId = currentUserId;
- await _registrationRepository.UpdateAsync(reg);
- }
-
- // 4. 更新支付记录(如果有)
- var payment = await _paymentRepository.GetFirstAsync(p => p.RegistrationId == order.RegistrationId);
- if (payment != null)
- {
- payment.RefundStatus = 1; // 已退款
- payment.UpdatedTime = now;
- payment.UpdatedUserId = currentUserId;
- await _paymentRepository.UpdateAsync(payment);
- }
+ _logger.LogInformation("调用统一后台退款:订单ID={OrderId}, 操作人={UserId}", input.Id, currentUserId);
+ await _unifiedRegistrationService.ExecuteAdminRefundAsync(input, currentUserId);
}
///
diff --git a/YD_Event.Core/Entities/PointsRecord.cs b/YD_Event.Core/Entities/PointsRecord.cs
index 00a0ff8..1a81529 100644
--- a/YD_Event.Core/Entities/PointsRecord.cs
+++ b/YD_Event.Core/Entities/PointsRecord.cs
@@ -36,6 +36,12 @@ public class PointsRecord : Entity, ISoftDelete, ICreatedUserId, ICreatedT
[SugarColumn(IsNullable = true)]
public long? ProjectGroupId { get; set; }
+ ///
+ /// 关联成绩记录ID
+ ///
+ [SugarColumn(IsNullable = true)]
+ public long? ResultId { get; set; }
+
///
/// 单位认证ID(单位用户积分关联)
///
diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml
index 398e8f4..3700d68 100644
--- a/YD_Event.Core/YD_Event.Core.xml
+++ b/YD_Event.Core/YD_Event.Core.xml
@@ -2788,6 +2788,11 @@
关联项目组别ID
+
+
+ 关联成绩记录ID
+
+
单位认证ID(单位用户积分关联)