赛事报名流程优化
This commit is contained in:
parent
ae03373047
commit
3b6e5508a2
@ -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)
|
||||
{
|
||||
|
||||
@ -752,11 +752,6 @@ public class RegistrationItemDTO
|
||||
/// </summary>
|
||||
public string TeamName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付状态:0-未支付/待审核,1-已支付/审核通过,2-支付失败/审核拒绝,3-已退款,4-已取消
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
@ -1177,6 +1172,128 @@ public class GroupTeamLimitOutput
|
||||
public int CurrentTeams { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查组别选择限制输入
|
||||
/// </summary>
|
||||
public class CheckGroupSelectionLimitInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 赛事ID
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long EventId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 要检查的组别ID
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前已选择的组别ID列表
|
||||
/// </summary>
|
||||
public List<long> SelectedGroupIds { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查组别选择限制输出
|
||||
/// </summary>
|
||||
public class CheckGroupSelectionLimitOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否可以选择该组别
|
||||
/// </summary>
|
||||
public bool CanSelect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 限制消息(如果不能选择)
|
||||
/// </summary>
|
||||
public string? LimitMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组别限制详情
|
||||
/// </summary>
|
||||
public GroupLimitDetail? GroupLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 项目限制详情
|
||||
/// </summary>
|
||||
public ProjectLimitDetail? ProjectLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 团队项目限制详情
|
||||
/// </summary>
|
||||
public EventLimitDetail? TeamProjectLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 个人项目限制详情
|
||||
/// </summary>
|
||||
public EventLimitDetail? IndividualProjectLimit { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组别限制详情
|
||||
/// </summary>
|
||||
public class GroupLimitDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// 组别限报数
|
||||
/// </summary>
|
||||
public int? GroupLimitCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前报名数
|
||||
/// </summary>
|
||||
public int CurrentRegistrations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否达到限制
|
||||
/// </summary>
|
||||
public bool IsLimitReached { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 项目限制详情
|
||||
/// </summary>
|
||||
public class ProjectLimitDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// 项目限报数
|
||||
/// </summary>
|
||||
public int? ProjectLimitCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前已选择数
|
||||
/// </summary>
|
||||
public int CurrentSelectedCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否达到限制
|
||||
/// </summary>
|
||||
public bool IsLimitReached { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 赛事项目限制详情
|
||||
/// </summary>
|
||||
public class EventLimitDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// 限报数
|
||||
/// </summary>
|
||||
public int? LimitCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前已选择数
|
||||
/// </summary>
|
||||
public int CurrentSelectedCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否达到限制
|
||||
/// </summary>
|
||||
public bool IsLimitReached { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组别队员分配
|
||||
/// </summary>
|
||||
|
||||
@ -88,7 +88,9 @@ public class EventArrangementService : BaseService<YD_Event.Core.Entities.Projec
|
||||
var eventProjects = await _eventProjectRepository.GetListAsync(x => 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<YD_Event.Core.Entities.Projec
|
||||
.ToList();
|
||||
|
||||
var result = new List<ArrangementProjectListOutput>();
|
||||
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<YD_Event.Core.Entities.Projec
|
||||
var eventProjects = await _eventProjectRepository.GetListAsync(x => 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<YD_Event.Core.Entities.Projec
|
||||
|
||||
var result = new List<ArrangementPreviewOutput>();
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
@ -755,21 +755,11 @@ public class EventScoreService : BaseService<EventResult>, 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<long>(processedResultIdsList);
|
||||
|
||||
int successCount = 0;
|
||||
int skipCount = 0;
|
||||
@ -805,8 +795,9 @@ public class EventScoreService : BaseService<EventResult>, 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
|
||||
};
|
||||
|
||||
@ -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<WechatTenpayClientOptions> _wechatTenpayClientOptionsMonitor;
|
||||
private readonly ILogger<UnifiedRegistrationService> _logger;
|
||||
private readonly OrderNoGeneratorService _orderNoGeneratorService;
|
||||
private readonly RefundConfigService _refundConfigService;
|
||||
private readonly ISqlSugarRepository<RefundRecord> _refundRecordRepository;
|
||||
|
||||
public UnifiedRegistrationService(
|
||||
ISqlSugarRepository<EventInfo> eventRepository,
|
||||
@ -75,7 +79,9 @@ public class UnifiedRegistrationService : ITransient
|
||||
WeChatPayService weChatPayService,
|
||||
IOptionsMonitor<WechatTenpayClientOptions> wechatTenpayClientOptionsMonitor,
|
||||
ILogger<UnifiedRegistrationService> logger,
|
||||
OrderNoGeneratorService orderNoGeneratorService)
|
||||
OrderNoGeneratorService orderNoGeneratorService,
|
||||
RefundConfigService refundConfigService,
|
||||
ISqlSugarRepository<RefundRecord> refundRecordRepository)
|
||||
{
|
||||
_eventRepository = eventRepository;
|
||||
_unitRepository = unitRepository;
|
||||
@ -105,6 +111,8 @@ public class UnifiedRegistrationService : ITransient
|
||||
_wechatTenpayClientOptionsMonitor = wechatTenpayClientOptionsMonitor;
|
||||
_logger = logger;
|
||||
_orderNoGeneratorService = orderNoGeneratorService;
|
||||
_refundConfigService = refundConfigService;
|
||||
_refundRecordRepository = refundRecordRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算成员项目报名数量
|
||||
/// 计算成员项目报名数量(包含本次提交的项目)
|
||||
/// </summary>
|
||||
private (int individualCount, int teamCount) CalculateMemberProjectCounts(
|
||||
long memberId,
|
||||
List<EventProject> eventProjects,
|
||||
List<YD_Event.Core.Entities.Project> projects,
|
||||
List<EventRegistrationPerson> registrationPersons,
|
||||
List<EventRegistrationItem> allRegistrationItems)
|
||||
List<EventRegistrationItem> 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<long>(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 统一退款相关方法
|
||||
|
||||
/// <summary>
|
||||
/// 计算退款金额(统一方法)
|
||||
/// </summary>
|
||||
/// <param name="totalAmount">总金额</param>
|
||||
/// <param name="eventStartTime">赛事开始时间</param>
|
||||
/// <returns>应退金额</returns>
|
||||
[NonAction]
|
||||
public async Task<decimal> CalculateRefundAmountAsync(decimal totalAmount, DateTime eventStartTime)
|
||||
{
|
||||
var refundAmount = await _refundConfigService.CalculateRefundAmount(totalAmount, eventStartTime);
|
||||
_logger.LogInformation("统一退款金额计算:总金额={TotalAmount}, 赛事开始时间={EventStartTime}, 计算后退款金额={RefundAmount}",
|
||||
totalAmount, eventStartTime, refundAmount);
|
||||
return refundAmount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一取消报名预览方法
|
||||
/// </summary>
|
||||
/// <param name="registrationId">报名ID</param>
|
||||
/// <param name="operatorUserId">操作人用户ID</param>
|
||||
/// <returns>取消预览输出</returns>
|
||||
[NonAction]
|
||||
public async Task<CancelPreviewOutput> 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<EventInfo>().InSingleAsync(registration.EventId);
|
||||
var items = await _db.Queryable<EventRegistrationItem>().Where(x => x.RegistrationId == registrationId).ToListAsync();
|
||||
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
|
||||
|
||||
var refundableAmount = 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一取消报名执行方法
|
||||
/// </summary>
|
||||
/// <param name="input">取消执行输入</param>
|
||||
/// <param name="operatorUserId">操作人用户ID</param>
|
||||
/// <returns>取消执行输出</returns>
|
||||
[UnitOfWork]
|
||||
[NonAction]
|
||||
public async Task<CancelExecuteOutput> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建退款记录
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新订单状态
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一后台管理退款方法
|
||||
/// </summary>
|
||||
/// <param name="input">退款订单输入</param>
|
||||
/// <param name="operatorUserId">操作人用户ID</param>
|
||||
[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
|
||||
}
|
||||
|
||||
@ -35,17 +35,14 @@ public class UserEventRecordService : IDynamicApiController, ITransient
|
||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Project> _projectRepository;
|
||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.AppUser> _userRepository;
|
||||
private readonly ISqlSugarRepository<EventCertificate> _certificateRepository;
|
||||
private readonly ISqlSugarRepository<WeChatPayConfig> _weChatPayConfigRepository;
|
||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
|
||||
private readonly ISqlSugarRepository<EventInfo> _eventInfoRepository;
|
||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Order> _orderRepository;
|
||||
private readonly ISqlSugarRepository<EventInfo> _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<UserEventRecordService> _logger;
|
||||
private readonly UnifiedRegistrationService _unifiedRegistrationService;
|
||||
|
||||
public UserEventRecordService(
|
||||
ISqlSugarRepository<EventRegistration> registrationRepository,
|
||||
@ -54,17 +51,14 @@ public class UserEventRecordService : IDynamicApiController, ITransient
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.Project> projectRepository,
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.AppUser> userRepository,
|
||||
ISqlSugarRepository<EventCertificate> certificateRepository,
|
||||
ISqlSugarRepository<WeChatPayConfig> weChatPayConfigRepository,
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
|
||||
ISqlSugarRepository<EventInfo> eventInfoRepository,
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
|
||||
ISqlSugarRepository<EventInfo> eventRepository,
|
||||
ISqlSugarClient db,
|
||||
AuthManager authManager,
|
||||
IIdGenerator idGenerator,
|
||||
FileService fileService,
|
||||
WeChatPayService weChatPayService,
|
||||
OrderNoGeneratorService orderNoGeneratorService,
|
||||
ILogger<UserEventRecordService> logger)
|
||||
ILogger<UserEventRecordService> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -98,6 +89,12 @@ public class UserEventRecordService : IDynamicApiController, ITransient
|
||||
.InnerJoin<EventInfo>((r, e) => r.EventId == e.Id)
|
||||
.LeftJoin<EventFeeConfig>((r, e, fc) => e.Id == fc.EventId)
|
||||
.Where((r, e, fc) => r.CreatedUserId == userId && !r.DeleteMark)
|
||||
// 支付状态筛选:
|
||||
// - 线下支付时:显示未支付(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<CancelPreviewOutput> CancelPreview([FromRoute] long registrationId)
|
||||
{
|
||||
var userId = _authManager.UserId;
|
||||
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == registrationId && x.CreatedUserId == userId);
|
||||
if (registration == null) throw Oops.Bah("报名记录不存在");
|
||||
|
||||
// 获取赛事信息
|
||||
var eventInfo = await _db.Queryable<EventInfo>().InSingleAsync(registration.EventId);
|
||||
|
||||
// 获取项目列表
|
||||
var items = await _db.Queryable<EventRegistrationItem>()
|
||||
.Where(x => x.RegistrationId == registrationId)
|
||||
.ToListAsync();
|
||||
|
||||
// 获取队员列表
|
||||
var persons = await _registrationPersonRepository.GetListAsync(x => x.RegistrationId == registrationId);
|
||||
|
||||
// 计算可退款金额(简化逻辑,实际需根据退款规则计算)
|
||||
var refundableAmount = registration.TotalAmount;
|
||||
|
||||
var output = new CancelPreviewOutput
|
||||
{
|
||||
RegistrationId = registration.Id,
|
||||
EventName = eventInfo?.EventName ?? "",
|
||||
TeamName = registration.TeamName ?? "",
|
||||
ChargeMode = registration.TotalAmount > 0 ? 1 : 0,
|
||||
ActualPaidAmount = registration.TotalAmount,
|
||||
RefundableAmount = refundableAmount
|
||||
};
|
||||
|
||||
// 添加项目
|
||||
foreach (var item in items)
|
||||
{
|
||||
output.Projects.Add(new CancelPreviewProjectDto
|
||||
{
|
||||
ProjectId = item.ProjectId,
|
||||
ProjectName = item.ProjectName ?? "",
|
||||
ProjectType = item.PersonCount > 1 ? "团队" : "个人",
|
||||
PersonCount = item.PersonCount,
|
||||
UnitPrice = item.UnitPrice
|
||||
});
|
||||
}
|
||||
|
||||
// 添加队员
|
||||
foreach (var person in persons)
|
||||
{
|
||||
output.Members.Add(new CancelPreviewMemberDto
|
||||
{
|
||||
MemberId = person.Id,
|
||||
Name = person.Name ?? "",
|
||||
IsLeader = person.PersonType == PersonType.Leader
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
_logger.LogInformation("调用统一取消报名预览:报名ID={RegistrationId}, 操作人={UserId}", registrationId, userId);
|
||||
return await _unifiedRegistrationService.GetCancelPreviewAsync(registrationId, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -438,81 +385,8 @@ public class UserEventRecordService : IDynamicApiController, ITransient
|
||||
public async Task<CancelExecuteOutput> CancelExecute([FromBody] CancelExecuteInput input)
|
||||
{
|
||||
var userId = _authManager.UserId;
|
||||
var registration = await _registrationRepository.GetFirstAsync(x => x.Id == input.RegistrationId && x.CreatedUserId == userId);
|
||||
if (registration == null) throw Oops.Bah("报名记录不存在");
|
||||
|
||||
// 检查状态
|
||||
if (registration.PaymentStatus == 4) throw Oops.Bah("订单已取消");
|
||||
if (registration.PaymentStatus != 1) throw Oops.Bah("未支付的订单无法退款");
|
||||
|
||||
var now = DateTime.Now;
|
||||
var output = new CancelExecuteOutput
|
||||
{
|
||||
Success = true,
|
||||
Message = "取消报名成功",
|
||||
CancelTime = now,
|
||||
RefundAmount = registration.TotalAmount
|
||||
};
|
||||
|
||||
// 如果有支付,先执行退款,成功后再更新状态
|
||||
if (registration.TotalAmount > 0 && !string.IsNullOrEmpty(registration.OrderNo))
|
||||
{
|
||||
var eventInfo = await _eventInfoRepository.GetFirstAsync(e => e.Id == registration.EventId);
|
||||
// 查找商户配置
|
||||
var merchant = await _merchantRepository.GetFirstAsync(x => x.Id == eventInfo.MerchantId);
|
||||
|
||||
if (merchant != null)
|
||||
{
|
||||
var wechatConfig = await _weChatPayConfigRepository.GetFirstAsync(x => x.MerchantId == merchant.Id);
|
||||
|
||||
if (wechatConfig != null)
|
||||
{
|
||||
// 生成退款单号(使用统一的订单号生成器)
|
||||
var outRefundNo = await _orderNoGeneratorService.GenerateRefundOrderNoAsync();
|
||||
output.RefundNo = outRefundNo;
|
||||
|
||||
// 调用微信退款服务
|
||||
var refundRequest = new Core.Infrastructure.WeChat.Dto.CreateRefundRequest
|
||||
{
|
||||
OutTradeNo = registration.OrderNo,
|
||||
OutRefundNo = outRefundNo,
|
||||
RefundAmount = AmountHelper.YuanToFen(registration.TotalAmount), // 转分
|
||||
TotalAmount = AmountHelper.YuanToFen(registration.TotalAmount),
|
||||
Reason = input.CancelReason
|
||||
};
|
||||
|
||||
_logger.LogInformation("开始执行退款:报名ID={RegistrationId}, 订单号={OrderNo}, 退款单号={RefundNo}, 退款金额={Amount}",
|
||||
registration.Id, registration.OrderNo, outRefundNo, registration.TotalAmount);
|
||||
|
||||
var refundResult = await _weChatPayService.CreateRefundAsync(wechatConfig, refundRequest);
|
||||
if (!refundResult.Success)
|
||||
{
|
||||
_logger.LogError("退款失败:报名ID={RegistrationId}, 错误信息={ErrorMessage}",
|
||||
registration.Id, refundResult.Message);
|
||||
output.Success = false;
|
||||
output.Message = refundResult.Message;
|
||||
output.RefundAmount = 0;
|
||||
output.RefundNo = null;
|
||||
return output;
|
||||
}
|
||||
|
||||
_logger.LogInformation("退款成功:报名ID={RegistrationId}, 退款单号={RefundNo}",
|
||||
registration.Id, outRefundNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 退款成功后才更新报名状态
|
||||
registration.PaymentStatus = 3; // 已退款
|
||||
registration.UpdatedUserId = userId;
|
||||
registration.UpdatedTime = now;
|
||||
registration.RegistrationRemark = input.CancelReason;
|
||||
await _registrationRepository.UpdateAsync(registration);
|
||||
|
||||
_logger.LogInformation("取消报名成功:报名ID={RegistrationId}, 操作人={UserId}",
|
||||
registration.Id, userId);
|
||||
|
||||
return output;
|
||||
_logger.LogInformation("调用统一执行取消报名:报名ID={RegistrationId}, 操作人={UserId}", input.RegistrationId, userId);
|
||||
return await _unifiedRegistrationService.ExecuteCancelRegistrationAsync(input, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查组别选择限制
|
||||
/// </summary>
|
||||
[DisplayName("检查组别选择限制")]
|
||||
[HttpPost("check-group-selection-limit")]
|
||||
public async Task<CheckGroupSelectionLimitOutput> 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<long>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,4 +311,14 @@ public class RefundOrderInput
|
||||
/// 退款原因
|
||||
/// </summary>
|
||||
public string Reason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 取消类型(0-全部取消,1-部分取消)
|
||||
/// </summary>
|
||||
public int CancelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 要取消的队员ID列表(部分取消时使用)
|
||||
/// </summary>
|
||||
public List<long>? PersonIds { get; set; }
|
||||
}
|
||||
|
||||
@ -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<YD_Event.Core.Entities.Order>, ITransien
|
||||
private readonly ISqlSugarRepository<EventRegistrationPayment> _paymentRepository;
|
||||
private readonly ISqlSugarRepository<EventInfo> _eventRepository;
|
||||
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Merchant> _merchantRepository;
|
||||
private readonly RefundConfigService _refundConfigService;
|
||||
private readonly IIdGenerator _idGenerator;
|
||||
private readonly AuthManager _authManager;
|
||||
private readonly ISqlSugarClient _db;
|
||||
private readonly OrderNoGeneratorService _orderNoGeneratorService;
|
||||
private readonly ILogger<OrderService> _logger;
|
||||
private readonly UnifiedRegistrationService _unifiedRegistrationService;
|
||||
|
||||
public OrderService(
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.Order> orderRepository,
|
||||
@ -39,12 +39,12 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
ISqlSugarRepository<EventRegistrationPayment> paymentRepository,
|
||||
ISqlSugarRepository<EventInfo> eventRepository,
|
||||
ISqlSugarRepository<YD_Event.Core.Entities.Merchant> merchantRepository,
|
||||
RefundConfigService refundConfigService,
|
||||
IIdGenerator idGenerator,
|
||||
AuthManager authManager,
|
||||
ISqlSugarClient db,
|
||||
OrderNoGeneratorService orderNoGeneratorService,
|
||||
ILogger<OrderService> logger) : base(orderRepository)
|
||||
ILogger<OrderService> logger,
|
||||
UnifiedRegistrationService unifiedRegistrationService) : base(orderRepository)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_registrationRepository = registrationRepository;
|
||||
@ -53,12 +53,12 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, ITransien
|
||||
_paymentRepository = paymentRepository;
|
||||
_eventRepository = eventRepository;
|
||||
_merchantRepository = merchantRepository;
|
||||
_refundConfigService = refundConfigService;
|
||||
_idGenerator = idGenerator;
|
||||
_authManager = authManager;
|
||||
_db = db;
|
||||
_orderNoGeneratorService = orderNoGeneratorService;
|
||||
_logger = logger;
|
||||
_unifiedRegistrationService = unifiedRegistrationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -432,7 +432,9 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -443,83 +445,9 @@ public class OrderService : BaseService<YD_Event.Core.Entities.Order>, 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -36,6 +36,12 @@ public class PointsRecord : Entity<long>, ISoftDelete, ICreatedUserId, ICreatedT
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public long? ProjectGroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联成绩记录ID
|
||||
/// </summary>
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public long? ResultId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 单位认证ID(单位用户积分关联)
|
||||
/// </summary>
|
||||
|
||||
@ -2788,6 +2788,11 @@
|
||||
关联项目组别ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Core.Entities.PointsRecord.ResultId">
|
||||
<summary>
|
||||
关联成绩记录ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:YD_Event.Core.Entities.PointsRecord.MerchantCertificationId">
|
||||
<summary>
|
||||
单位认证ID(单位用户积分关联)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user