- 系统配置管理模块 (Config) - 内容管理模块 (Banner, Promotion) - 测评管理模块 (Type, Question, Category, Mapping, Conclusion) - 用户管理模块 (User) - 订单管理模块 (Order) - 规划师管理模块 (Planner) - 分销管理模块 (InviteCode, Commission, Withdrawal) - 数据统计仪表盘模块 (Dashboard) - 权限控制集成 - 服务注册配置 全部381个测试通过
971 lines
33 KiB
C#
971 lines
33 KiB
C#
using MiAssessment.Admin.Business.Data;
|
||
using MiAssessment.Admin.Business.Entities;
|
||
using MiAssessment.Admin.Business.Models;
|
||
using MiAssessment.Admin.Business.Models.Common;
|
||
using MiAssessment.Admin.Business.Models.Distribution;
|
||
using MiAssessment.Admin.Business.Services.Interfaces;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
namespace MiAssessment.Admin.Business.Services;
|
||
|
||
/// <summary>
|
||
/// 分销服务实现
|
||
/// </summary>
|
||
public class DistributionService : IDistributionService
|
||
{
|
||
private readonly AdminBusinessDbContext _dbContext;
|
||
private readonly ILogger<DistributionService> _logger;
|
||
|
||
/// <summary>
|
||
/// 邀请码状态名称映射
|
||
/// </summary>
|
||
private static readonly Dictionary<int, string> InviteCodeStatusNames = new()
|
||
{
|
||
{ 1, "未分配" },
|
||
{ 2, "已分配" },
|
||
{ 3, "已使用" }
|
||
};
|
||
|
||
/// <summary>
|
||
/// 佣金状态名称映射
|
||
/// </summary>
|
||
private static readonly Dictionary<int, string> CommissionStatusNames = new()
|
||
{
|
||
{ 1, "待结算" },
|
||
{ 2, "已结算" }
|
||
};
|
||
|
||
/// <summary>
|
||
/// 佣金层级名称映射
|
||
/// </summary>
|
||
private static readonly Dictionary<int, string> CommissionLevelNames = new()
|
||
{
|
||
{ 1, "一级佣金" },
|
||
{ 2, "二级佣金" }
|
||
};
|
||
|
||
/// <summary>
|
||
/// 订单类型名称映射
|
||
/// </summary>
|
||
private static readonly Dictionary<int, string> OrderTypeNames = new()
|
||
{
|
||
{ 1, "测评订单" },
|
||
{ 2, "学业规划订单" }
|
||
};
|
||
|
||
/// <summary>
|
||
/// 提现状态名称映射
|
||
/// </summary>
|
||
private static readonly Dictionary<int, string> WithdrawalStatusNames = new()
|
||
{
|
||
{ 1, "待审核" },
|
||
{ 2, "处理中" },
|
||
{ 3, "已完成" },
|
||
{ 4, "已拒绝" }
|
||
};
|
||
|
||
/// <summary>
|
||
/// 用户等级名称映射
|
||
/// </summary>
|
||
private static readonly Dictionary<int, string> UserLevelNames = new()
|
||
{
|
||
{ 1, "普通用户" },
|
||
{ 2, "合伙人" },
|
||
{ 3, "渠道合伙人" }
|
||
};
|
||
|
||
/// <summary>
|
||
/// 邀请码字符集(大写字母)
|
||
/// </summary>
|
||
private const string InviteCodeCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||
|
||
/// <summary>
|
||
/// 邀请码长度
|
||
/// </summary>
|
||
private const int InviteCodeLength = 5;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="dbContext">数据库上下文</param>
|
||
/// <param name="logger">日志记录器</param>
|
||
public DistributionService(
|
||
AdminBusinessDbContext dbContext,
|
||
ILogger<DistributionService> logger)
|
||
{
|
||
_dbContext = dbContext;
|
||
_logger = logger;
|
||
}
|
||
|
||
#region 邀请码管理
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<InviteCodeDto>> GetInviteCodeListAsync(InviteCodeQueryRequest request)
|
||
{
|
||
// 构建查询,过滤软删除记录
|
||
var query = _dbContext.InviteCodes
|
||
.AsNoTracking()
|
||
.Include(c => c.AssignUser)
|
||
.Include(c => c.UseUser)
|
||
.Where(c => !c.IsDeleted);
|
||
|
||
// 应用过滤条件
|
||
query = ApplyInviteCodeQueryFilters(query, request);
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 分页查询,按创建时间降序排列
|
||
var items = await query
|
||
.OrderByDescending(c => c.CreateTime)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.Select(c => new InviteCodeDto
|
||
{
|
||
Id = c.Id,
|
||
Code = c.Code,
|
||
BatchNo = c.BatchNo,
|
||
AssignUserId = c.AssignUserId,
|
||
AssignUserNickname = c.AssignUser != null ? c.AssignUser.Nickname : null,
|
||
AssignTime = c.AssignTime,
|
||
UseUserId = c.UseUserId,
|
||
UseUserNickname = c.UseUser != null ? c.UseUser.Nickname : null,
|
||
UseOrderId = c.UseOrderId,
|
||
UseTime = c.UseTime,
|
||
Status = c.Status,
|
||
StatusName = GetInviteCodeStatusName(c.Status),
|
||
CreateTime = c.CreateTime
|
||
})
|
||
.ToListAsync();
|
||
|
||
return PagedResult<InviteCodeDto>.Create(items, total, request.Page, request.PageSize);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<GenerateResult> GenerateInviteCodesAsync(int count)
|
||
{
|
||
if (count <= 0 || count > 1000)
|
||
{
|
||
throw new BusinessException(ErrorCodes.ParamError, "生成数量必须在1-1000之间");
|
||
}
|
||
|
||
// 生成批次号:年月日时分秒 + 4位随机数
|
||
var batchNo = $"{DateTime.Now:yyyyMMddHHmmss}{new Random().Next(1000, 9999)}";
|
||
|
||
// 获取已存在的邀请码,用于去重
|
||
var existingCodesList = await _dbContext.InviteCodes
|
||
.AsNoTracking()
|
||
.Select(c => c.Code)
|
||
.ToListAsync();
|
||
var existingCodes = existingCodesList.ToHashSet();
|
||
|
||
// 生成唯一邀请码
|
||
var generatedCodes = GenerateUniqueCodes(count, existingCodes);
|
||
|
||
// 创建邀请码实体
|
||
var inviteCodes = generatedCodes.Select(code => new InviteCode
|
||
{
|
||
Code = code,
|
||
BatchNo = batchNo,
|
||
Status = 1, // 未分配
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now,
|
||
IsDeleted = false
|
||
}).ToList();
|
||
|
||
// 批量插入
|
||
_dbContext.InviteCodes.AddRange(inviteCodes);
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
_logger.LogInformation("生成邀请码成功,批次号: {BatchNo}, 数量: {Count}", batchNo, count);
|
||
|
||
return new GenerateResult
|
||
{
|
||
BatchNo = batchNo,
|
||
Count = generatedCodes.Count,
|
||
Codes = generatedCodes
|
||
};
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> AssignInviteCodesAsync(List<long> inviteCodeIds, long userId)
|
||
{
|
||
if (inviteCodeIds == null || inviteCodeIds.Count == 0)
|
||
{
|
||
throw new BusinessException(ErrorCodes.ParamError, "请选择要分配的邀请码");
|
||
}
|
||
|
||
// 验证用户是否存在
|
||
var userExists = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.AnyAsync(u => u.Id == userId && !u.IsDeleted);
|
||
|
||
if (!userExists)
|
||
{
|
||
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
|
||
}
|
||
|
||
// 获取要分配的邀请码
|
||
var inviteCodes = await _dbContext.InviteCodes
|
||
.Where(c => inviteCodeIds.Contains(c.Id) && !c.IsDeleted)
|
||
.ToListAsync();
|
||
|
||
if (inviteCodes.Count == 0)
|
||
{
|
||
throw new BusinessException(ErrorCodes.InviteCodeNotFound, "邀请码不存在");
|
||
}
|
||
|
||
// 检查是否有已分配或已使用的邀请码
|
||
var alreadyAssigned = inviteCodes.Where(c => c.Status != 1).ToList();
|
||
if (alreadyAssigned.Count > 0)
|
||
{
|
||
var codes = string.Join(", ", alreadyAssigned.Select(c => c.Code));
|
||
throw new BusinessException(ErrorCodes.InviteCodeAlreadyAssigned, $"以下邀请码已分配或已使用: {codes}");
|
||
}
|
||
|
||
// 分配邀请码
|
||
var now = DateTime.Now;
|
||
foreach (var inviteCode in inviteCodes)
|
||
{
|
||
inviteCode.AssignUserId = userId;
|
||
inviteCode.AssignTime = now;
|
||
inviteCode.Status = 2; // 已分配
|
||
inviteCode.UpdateTime = now;
|
||
}
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
_logger.LogInformation("分配邀请码成功,用户ID: {UserId}, 数量: {Count}", userId, inviteCodes.Count);
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<List<InviteCodeDto>> ExportInviteCodesAsync(InviteCodeQueryRequest request)
|
||
{
|
||
// 构建查询,过滤软删除记录
|
||
var query = _dbContext.InviteCodes
|
||
.AsNoTracking()
|
||
.Include(c => c.AssignUser)
|
||
.Include(c => c.UseUser)
|
||
.Where(c => !c.IsDeleted);
|
||
|
||
// 应用过滤条件
|
||
query = ApplyInviteCodeQueryFilters(query, request);
|
||
|
||
// 查询所有匹配的记录(不分页)
|
||
var items = await query
|
||
.OrderByDescending(c => c.CreateTime)
|
||
.Select(c => new InviteCodeDto
|
||
{
|
||
Id = c.Id,
|
||
Code = c.Code,
|
||
BatchNo = c.BatchNo,
|
||
AssignUserId = c.AssignUserId,
|
||
AssignUserNickname = c.AssignUser != null ? c.AssignUser.Nickname : null,
|
||
AssignTime = c.AssignTime,
|
||
UseUserId = c.UseUserId,
|
||
UseUserNickname = c.UseUser != null ? c.UseUser.Nickname : null,
|
||
UseOrderId = c.UseOrderId,
|
||
UseTime = c.UseTime,
|
||
Status = c.Status,
|
||
StatusName = GetInviteCodeStatusName(c.Status),
|
||
CreateTime = c.CreateTime
|
||
})
|
||
.ToListAsync();
|
||
|
||
_logger.LogInformation("导出邀请码成功,数量: {Count}", items.Count);
|
||
|
||
return items;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 佣金记录管理
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<CommissionDto>> GetCommissionListAsync(CommissionQueryRequest request)
|
||
{
|
||
// 构建查询,过滤软删除记录
|
||
var query = _dbContext.Commissions
|
||
.AsNoTracking()
|
||
.Include(c => c.User)
|
||
.Include(c => c.FromUser)
|
||
.Include(c => c.Order)
|
||
.Where(c => !c.IsDeleted);
|
||
|
||
// 应用过滤条件
|
||
query = ApplyCommissionQueryFilters(query, request);
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 分页查询,按创建时间降序排列
|
||
var items = await query
|
||
.OrderByDescending(c => c.CreateTime)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.Select(c => new CommissionDto
|
||
{
|
||
Id = c.Id,
|
||
UserId = c.UserId,
|
||
UserNickname = c.User != null ? c.User.Nickname : null,
|
||
FromUserId = c.FromUserId,
|
||
FromUserNickname = c.FromUser != null ? c.FromUser.Nickname : null,
|
||
OrderId = c.OrderId,
|
||
OrderNo = c.Order != null ? c.Order.OrderNo : null,
|
||
OrderAmount = c.OrderAmount,
|
||
CommissionRate = c.CommissionRate,
|
||
CommissionAmount = c.CommissionAmount,
|
||
Level = c.Level,
|
||
LevelName = GetCommissionLevelName(c.Level),
|
||
Status = c.Status,
|
||
StatusName = GetCommissionStatusName(c.Status),
|
||
SettleTime = c.SettleTime,
|
||
CreateTime = c.CreateTime
|
||
})
|
||
.ToListAsync();
|
||
|
||
return PagedResult<CommissionDto>.Create(items, total, request.Page, request.PageSize);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<CommissionDetailDto?> GetCommissionDetailAsync(long id)
|
||
{
|
||
var commission = await _dbContext.Commissions
|
||
.AsNoTracking()
|
||
.Include(c => c.User)
|
||
.Include(c => c.FromUser)
|
||
.Include(c => c.Order)
|
||
.Where(c => c.Id == id && !c.IsDeleted)
|
||
.Select(c => new CommissionDetailDto
|
||
{
|
||
Id = c.Id,
|
||
UserId = c.UserId,
|
||
UserNickname = c.User != null ? c.User.Nickname : null,
|
||
UserUid = c.User != null ? c.User.Uid : null,
|
||
UserPhone = c.User != null ? c.User.Phone : null,
|
||
FromUserId = c.FromUserId,
|
||
FromUserNickname = c.FromUser != null ? c.FromUser.Nickname : null,
|
||
FromUserUid = c.FromUser != null ? c.FromUser.Uid : null,
|
||
FromUserPhone = c.FromUser != null ? c.FromUser.Phone : null,
|
||
OrderId = c.OrderId,
|
||
OrderNo = c.Order != null ? c.Order.OrderNo : null,
|
||
OrderAmount = c.OrderAmount,
|
||
OrderType = c.Order != null ? c.Order.OrderType : null,
|
||
OrderTypeName = c.Order != null ? GetOrderTypeName(c.Order.OrderType) : null,
|
||
ProductName = c.Order != null ? c.Order.ProductName : null,
|
||
OrderPayTime = c.Order != null ? c.Order.PayTime : null,
|
||
CommissionRate = c.CommissionRate,
|
||
CommissionAmount = c.CommissionAmount,
|
||
Level = c.Level,
|
||
LevelName = GetCommissionLevelName(c.Level),
|
||
Status = c.Status,
|
||
StatusName = GetCommissionStatusName(c.Status),
|
||
SettleTime = c.SettleTime,
|
||
CreateTime = c.CreateTime
|
||
})
|
||
.FirstOrDefaultAsync();
|
||
|
||
return commission;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<CommissionStatisticsDto> GetCommissionStatisticsAsync(CommissionStatisticsRequest request)
|
||
{
|
||
// 构建查询,过滤软删除记录
|
||
var query = _dbContext.Commissions
|
||
.AsNoTracking()
|
||
.Where(c => !c.IsDeleted);
|
||
|
||
// 按用户ID筛选
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(c => c.UserId == request.UserId.Value);
|
||
}
|
||
|
||
// 按创建时间范围筛选
|
||
if (request.CreateTimeStart.HasValue)
|
||
{
|
||
query = query.Where(c => c.CreateTime >= request.CreateTimeStart.Value);
|
||
}
|
||
|
||
if (request.CreateTimeEnd.HasValue)
|
||
{
|
||
query = query.Where(c => c.CreateTime <= request.CreateTimeEnd.Value);
|
||
}
|
||
|
||
// 计算统计数据
|
||
var commissions = await query.ToListAsync();
|
||
|
||
var statistics = new CommissionStatisticsDto
|
||
{
|
||
TotalAmount = commissions.Sum(c => c.CommissionAmount),
|
||
PendingAmount = commissions.Where(c => c.Status == 1).Sum(c => c.CommissionAmount),
|
||
SettledAmount = commissions.Where(c => c.Status == 2).Sum(c => c.CommissionAmount),
|
||
TotalCount = commissions.Count,
|
||
PendingCount = commissions.Count(c => c.Status == 1),
|
||
SettledCount = commissions.Count(c => c.Status == 2)
|
||
};
|
||
|
||
return statistics;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<List<CommissionDto>> ExportCommissionsAsync(CommissionQueryRequest request)
|
||
{
|
||
// 构建查询,过滤软删除记录
|
||
var query = _dbContext.Commissions
|
||
.AsNoTracking()
|
||
.Include(c => c.User)
|
||
.Include(c => c.FromUser)
|
||
.Include(c => c.Order)
|
||
.Where(c => !c.IsDeleted);
|
||
|
||
// 应用过滤条件
|
||
query = ApplyCommissionQueryFilters(query, request);
|
||
|
||
// 查询所有匹配的记录(不分页)
|
||
var items = await query
|
||
.OrderByDescending(c => c.CreateTime)
|
||
.Select(c => new CommissionDto
|
||
{
|
||
Id = c.Id,
|
||
UserId = c.UserId,
|
||
UserNickname = c.User != null ? c.User.Nickname : null,
|
||
FromUserId = c.FromUserId,
|
||
FromUserNickname = c.FromUser != null ? c.FromUser.Nickname : null,
|
||
OrderId = c.OrderId,
|
||
OrderNo = c.Order != null ? c.Order.OrderNo : null,
|
||
OrderAmount = c.OrderAmount,
|
||
CommissionRate = c.CommissionRate,
|
||
CommissionAmount = c.CommissionAmount,
|
||
Level = c.Level,
|
||
LevelName = GetCommissionLevelName(c.Level),
|
||
Status = c.Status,
|
||
StatusName = GetCommissionStatusName(c.Status),
|
||
SettleTime = c.SettleTime,
|
||
CreateTime = c.CreateTime
|
||
})
|
||
.ToListAsync();
|
||
|
||
_logger.LogInformation("导出佣金记录成功,数量: {Count}", items.Count);
|
||
|
||
return items;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 提现审核管理
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<WithdrawalDto>> GetWithdrawalListAsync(WithdrawalQueryRequest request)
|
||
{
|
||
// 构建查询,过滤软删除记录
|
||
var query = _dbContext.Withdrawals
|
||
.AsNoTracking()
|
||
.Include(w => w.User)
|
||
.Where(w => !w.IsDeleted);
|
||
|
||
// 应用过滤条件
|
||
query = ApplyWithdrawalQueryFilters(query, request);
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 分页查询,按创建时间降序排列
|
||
var items = await query
|
||
.OrderByDescending(w => w.CreateTime)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.Select(w => new WithdrawalDto
|
||
{
|
||
Id = w.Id,
|
||
WithdrawalNo = w.WithdrawalNo,
|
||
UserId = w.UserId,
|
||
UserNickname = w.User != null ? w.User.Nickname : null,
|
||
UserPhone = w.User != null ? w.User.Phone : null,
|
||
Amount = w.Amount,
|
||
BeforeBalance = w.BeforeBalance,
|
||
AfterBalance = w.AfterBalance,
|
||
Status = w.Status,
|
||
StatusName = GetWithdrawalStatusName(w.Status),
|
||
AuditUserId = w.AuditUserId,
|
||
AuditUserName = null, // 审核人名称需要从管理员表获取,暂时为空
|
||
AuditTime = w.AuditTime,
|
||
AuditRemark = w.AuditRemark,
|
||
PayTime = w.PayTime,
|
||
PayTransactionId = w.PayTransactionId,
|
||
CreateTime = w.CreateTime
|
||
})
|
||
.ToListAsync();
|
||
|
||
return PagedResult<WithdrawalDto>.Create(items, total, request.Page, request.PageSize);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<WithdrawalDetailDto?> GetWithdrawalDetailAsync(long id)
|
||
{
|
||
var withdrawal = await _dbContext.Withdrawals
|
||
.AsNoTracking()
|
||
.Include(w => w.User)
|
||
.Where(w => w.Id == id && !w.IsDeleted)
|
||
.Select(w => new WithdrawalDetailDto
|
||
{
|
||
Id = w.Id,
|
||
WithdrawalNo = w.WithdrawalNo,
|
||
UserId = w.UserId,
|
||
UserNickname = w.User != null ? w.User.Nickname : null,
|
||
UserPhone = w.User != null ? w.User.Phone : null,
|
||
UserUid = w.User != null ? w.User.Uid : null,
|
||
UserCurrentBalance = w.User != null ? w.User.Balance : 0,
|
||
UserTotalIncome = w.User != null ? w.User.TotalIncome : 0,
|
||
UserWithdrawnAmount = w.User != null ? w.User.WithdrawnAmount : 0,
|
||
UserLevel = w.User != null ? w.User.UserLevel : 0,
|
||
UserLevelName = w.User != null ? GetUserLevelName(w.User.UserLevel) : null,
|
||
Amount = w.Amount,
|
||
BeforeBalance = w.BeforeBalance,
|
||
AfterBalance = w.AfterBalance,
|
||
Status = w.Status,
|
||
StatusName = GetWithdrawalStatusName(w.Status),
|
||
AuditUserId = w.AuditUserId,
|
||
AuditUserName = null,
|
||
AuditTime = w.AuditTime,
|
||
AuditRemark = w.AuditRemark,
|
||
PayTime = w.PayTime,
|
||
PayTransactionId = w.PayTransactionId,
|
||
CreateTime = w.CreateTime
|
||
})
|
||
.FirstOrDefaultAsync();
|
||
|
||
return withdrawal;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> ApproveWithdrawalAsync(ApproveWithdrawalRequest request)
|
||
{
|
||
// 获取提现记录
|
||
var withdrawal = await _dbContext.Withdrawals
|
||
.Where(w => w.Id == request.Id && !w.IsDeleted)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (withdrawal == null)
|
||
{
|
||
throw new BusinessException(ErrorCodes.WithdrawalNotFound, "提现记录不存在");
|
||
}
|
||
|
||
// 检查状态是否为待审核(1)
|
||
if (withdrawal.Status != 1)
|
||
{
|
||
throw new BusinessException(ErrorCodes.WithdrawalCannotApprove, "只有待审核状态的提现申请才能审批通过");
|
||
}
|
||
|
||
// 更新状态为处理中(2)
|
||
var now = DateTime.Now;
|
||
withdrawal.Status = 2;
|
||
withdrawal.AuditUserId = request.AuditUserId;
|
||
withdrawal.AuditTime = now;
|
||
withdrawal.AuditRemark = request.AuditRemark;
|
||
withdrawal.UpdateTime = now;
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
_logger.LogInformation("提现审批通过,提现单号: {WithdrawalNo}, 审核人ID: {AuditUserId}",
|
||
withdrawal.WithdrawalNo, request.AuditUserId);
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> RejectWithdrawalAsync(RejectWithdrawalRequest request)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(request.AuditRemark))
|
||
{
|
||
throw new BusinessException(ErrorCodes.ParamError, "拒绝原因不能为空");
|
||
}
|
||
|
||
// 获取提现记录
|
||
var withdrawal = await _dbContext.Withdrawals
|
||
.Where(w => w.Id == request.Id && !w.IsDeleted)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (withdrawal == null)
|
||
{
|
||
throw new BusinessException(ErrorCodes.WithdrawalNotFound, "提现记录不存在");
|
||
}
|
||
|
||
// 检查状态是否为待审核(1)
|
||
if (withdrawal.Status != 1)
|
||
{
|
||
throw new BusinessException(ErrorCodes.WithdrawalCannotReject, "只有待审核状态的提现申请才能拒绝");
|
||
}
|
||
|
||
// 获取用户,回滚余额
|
||
var user = await _dbContext.Users
|
||
.Where(u => u.Id == withdrawal.UserId && !u.IsDeleted)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (user == null)
|
||
{
|
||
throw new BusinessException(ErrorCodes.UserNotFound, "用户不存在");
|
||
}
|
||
|
||
// 使用事务确保数据一致性
|
||
using var transaction = await _dbContext.Database.BeginTransactionAsync();
|
||
try
|
||
{
|
||
var now = DateTime.Now;
|
||
|
||
// 回滚用户余额
|
||
user.Balance += withdrawal.Amount;
|
||
user.WithdrawnAmount -= withdrawal.Amount;
|
||
user.UpdateTime = now;
|
||
|
||
// 更新提现状态为已拒绝(4)
|
||
withdrawal.Status = 4;
|
||
withdrawal.AuditUserId = request.AuditUserId;
|
||
withdrawal.AuditTime = now;
|
||
withdrawal.AuditRemark = request.AuditRemark;
|
||
withdrawal.UpdateTime = now;
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
await transaction.CommitAsync();
|
||
|
||
_logger.LogInformation("提现已拒绝,提现单号: {WithdrawalNo}, 审核人ID: {AuditUserId}, 原因: {AuditRemark}, 已回滚余额: {Amount}",
|
||
withdrawal.WithdrawalNo, request.AuditUserId, request.AuditRemark, withdrawal.Amount);
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await transaction.RollbackAsync();
|
||
_logger.LogError(ex, "拒绝提现失败,提现单号: {WithdrawalNo}", withdrawal.WithdrawalNo);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> CompleteWithdrawalAsync(CompleteWithdrawalRequest request)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(request.PayTransactionId))
|
||
{
|
||
throw new BusinessException(ErrorCodes.ParamError, "打款交易号不能为空");
|
||
}
|
||
|
||
// 获取提现记录
|
||
var withdrawal = await _dbContext.Withdrawals
|
||
.Where(w => w.Id == request.Id && !w.IsDeleted)
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (withdrawal == null)
|
||
{
|
||
throw new BusinessException(ErrorCodes.WithdrawalNotFound, "提现记录不存在");
|
||
}
|
||
|
||
// 检查状态是否为处理中(2)
|
||
if (withdrawal.Status != 2)
|
||
{
|
||
throw new BusinessException(ErrorCodes.WithdrawalCannotComplete, "只有处理中状态的提现申请才能完成");
|
||
}
|
||
|
||
// 更新状态为已完成(3)
|
||
var now = DateTime.Now;
|
||
withdrawal.Status = 3;
|
||
withdrawal.PayTime = now;
|
||
withdrawal.PayTransactionId = request.PayTransactionId;
|
||
withdrawal.UpdateTime = now;
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
_logger.LogInformation("提现已完成,提现单号: {WithdrawalNo}, 打款交易号: {PayTransactionId}",
|
||
withdrawal.WithdrawalNo, request.PayTransactionId);
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<List<WithdrawalDto>> ExportWithdrawalsAsync(WithdrawalQueryRequest request)
|
||
{
|
||
// 构建查询,过滤软删除记录
|
||
var query = _dbContext.Withdrawals
|
||
.AsNoTracking()
|
||
.Include(w => w.User)
|
||
.Where(w => !w.IsDeleted);
|
||
|
||
// 应用过滤条件
|
||
query = ApplyWithdrawalQueryFilters(query, request);
|
||
|
||
// 查询所有匹配的记录(不分页)
|
||
var items = await query
|
||
.OrderByDescending(w => w.CreateTime)
|
||
.Select(w => new WithdrawalDto
|
||
{
|
||
Id = w.Id,
|
||
WithdrawalNo = w.WithdrawalNo,
|
||
UserId = w.UserId,
|
||
UserNickname = w.User != null ? w.User.Nickname : null,
|
||
UserPhone = w.User != null ? w.User.Phone : null,
|
||
Amount = w.Amount,
|
||
BeforeBalance = w.BeforeBalance,
|
||
AfterBalance = w.AfterBalance,
|
||
Status = w.Status,
|
||
StatusName = GetWithdrawalStatusName(w.Status),
|
||
AuditUserId = w.AuditUserId,
|
||
AuditUserName = null,
|
||
AuditTime = w.AuditTime,
|
||
AuditRemark = w.AuditRemark,
|
||
PayTime = w.PayTime,
|
||
PayTransactionId = w.PayTransactionId,
|
||
CreateTime = w.CreateTime
|
||
})
|
||
.ToListAsync();
|
||
|
||
_logger.LogInformation("导出提现记录成功,数量: {Count}", items.Count);
|
||
|
||
return items;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 应用邀请码查询过滤条件
|
||
/// </summary>
|
||
/// <param name="query">查询</param>
|
||
/// <param name="request">请求</param>
|
||
/// <returns>过滤后的查询</returns>
|
||
private IQueryable<InviteCode> ApplyInviteCodeQueryFilters(IQueryable<InviteCode> query, InviteCodeQueryRequest request)
|
||
{
|
||
// 按邀请码模糊搜索
|
||
if (!string.IsNullOrWhiteSpace(request.Code))
|
||
{
|
||
query = query.Where(c => c.Code.Contains(request.Code));
|
||
}
|
||
|
||
// 按批次号模糊搜索
|
||
if (!string.IsNullOrWhiteSpace(request.BatchNo))
|
||
{
|
||
query = query.Where(c => c.BatchNo != null && c.BatchNo.Contains(request.BatchNo));
|
||
}
|
||
|
||
// 按分配用户ID筛选
|
||
if (request.AssignUserId.HasValue)
|
||
{
|
||
query = query.Where(c => c.AssignUserId == request.AssignUserId.Value);
|
||
}
|
||
|
||
// 按状态筛选
|
||
if (request.Status.HasValue)
|
||
{
|
||
query = query.Where(c => c.Status == request.Status.Value);
|
||
}
|
||
|
||
return query;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成唯一邀请码
|
||
/// </summary>
|
||
/// <param name="count">生成数量</param>
|
||
/// <param name="existingCodes">已存在的邀请码集合</param>
|
||
/// <returns>生成的邀请码列表</returns>
|
||
internal List<string> GenerateUniqueCodes(int count, HashSet<string> existingCodes)
|
||
{
|
||
var result = new List<string>(count);
|
||
var generatedSet = new HashSet<string>();
|
||
var random = new Random();
|
||
var maxAttempts = count * 10; // 最大尝试次数
|
||
var attempts = 0;
|
||
|
||
while (result.Count < count && attempts < maxAttempts)
|
||
{
|
||
var code = GenerateRandomCode(random);
|
||
attempts++;
|
||
|
||
// 检查是否唯一(不在已存在的集合中,也不在本次生成的集合中)
|
||
if (!existingCodes.Contains(code) && generatedSet.Add(code))
|
||
{
|
||
result.Add(code);
|
||
}
|
||
}
|
||
|
||
if (result.Count < count)
|
||
{
|
||
throw new BusinessException(ErrorCodes.SystemError, "无法生成足够的唯一邀请码,请稍后重试");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成随机邀请码(5位大写字母)
|
||
/// </summary>
|
||
/// <param name="random">随机数生成器</param>
|
||
/// <returns>邀请码</returns>
|
||
private static string GenerateRandomCode(Random random)
|
||
{
|
||
var chars = new char[InviteCodeLength];
|
||
for (int i = 0; i < InviteCodeLength; i++)
|
||
{
|
||
chars[i] = InviteCodeCharset[random.Next(InviteCodeCharset.Length)];
|
||
}
|
||
return new string(chars);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取邀请码状态名称
|
||
/// </summary>
|
||
/// <param name="status">状态值</param>
|
||
/// <returns>状态名称</returns>
|
||
private static string GetInviteCodeStatusName(int status)
|
||
{
|
||
return InviteCodeStatusNames.TryGetValue(status, out var name) ? name : "未知";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用佣金记录查询过滤条件
|
||
/// </summary>
|
||
/// <param name="query">查询</param>
|
||
/// <param name="request">请求</param>
|
||
/// <returns>过滤后的查询</returns>
|
||
private IQueryable<Commission> ApplyCommissionQueryFilters(IQueryable<Commission> query, CommissionQueryRequest request)
|
||
{
|
||
// 按获得佣金的用户ID筛选
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(c => c.UserId == request.UserId.Value);
|
||
}
|
||
|
||
// 按来源用户ID筛选
|
||
if (request.FromUserId.HasValue)
|
||
{
|
||
query = query.Where(c => c.FromUserId == request.FromUserId.Value);
|
||
}
|
||
|
||
// 按订单ID筛选
|
||
if (request.OrderId.HasValue)
|
||
{
|
||
query = query.Where(c => c.OrderId == request.OrderId.Value);
|
||
}
|
||
|
||
// 按层级筛选
|
||
if (request.Level.HasValue)
|
||
{
|
||
query = query.Where(c => c.Level == request.Level.Value);
|
||
}
|
||
|
||
// 按状态筛选
|
||
if (request.Status.HasValue)
|
||
{
|
||
query = query.Where(c => c.Status == request.Status.Value);
|
||
}
|
||
|
||
// 按创建时间范围筛选
|
||
if (request.CreateTimeStart.HasValue)
|
||
{
|
||
query = query.Where(c => c.CreateTime >= request.CreateTimeStart.Value);
|
||
}
|
||
|
||
if (request.CreateTimeEnd.HasValue)
|
||
{
|
||
query = query.Where(c => c.CreateTime <= request.CreateTimeEnd.Value);
|
||
}
|
||
|
||
return query;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取佣金状态名称
|
||
/// </summary>
|
||
/// <param name="status">状态值</param>
|
||
/// <returns>状态名称</returns>
|
||
private static string GetCommissionStatusName(int status)
|
||
{
|
||
return CommissionStatusNames.TryGetValue(status, out var name) ? name : "未知";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取佣金层级名称
|
||
/// </summary>
|
||
/// <param name="level">层级值</param>
|
||
/// <returns>层级名称</returns>
|
||
private static string GetCommissionLevelName(int level)
|
||
{
|
||
return CommissionLevelNames.TryGetValue(level, out var name) ? name : "未知";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取订单类型名称
|
||
/// </summary>
|
||
/// <param name="orderType">订单类型</param>
|
||
/// <returns>订单类型名称</returns>
|
||
private static string GetOrderTypeName(int orderType)
|
||
{
|
||
return OrderTypeNames.TryGetValue(orderType, out var name) ? name : "未知";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用提现记录查询过滤条件
|
||
/// </summary>
|
||
/// <param name="query">查询</param>
|
||
/// <param name="request">请求</param>
|
||
/// <returns>过滤后的查询</returns>
|
||
private IQueryable<Withdrawal> ApplyWithdrawalQueryFilters(IQueryable<Withdrawal> query, WithdrawalQueryRequest request)
|
||
{
|
||
// 按提现单号模糊搜索
|
||
if (!string.IsNullOrWhiteSpace(request.WithdrawalNo))
|
||
{
|
||
query = query.Where(w => w.WithdrawalNo.Contains(request.WithdrawalNo));
|
||
}
|
||
|
||
// 按用户ID筛选
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(w => w.UserId == request.UserId.Value);
|
||
}
|
||
|
||
// 按状态筛选
|
||
if (request.Status.HasValue)
|
||
{
|
||
query = query.Where(w => w.Status == request.Status.Value);
|
||
}
|
||
|
||
// 按创建时间范围筛选
|
||
if (request.CreateTimeStart.HasValue)
|
||
{
|
||
query = query.Where(w => w.CreateTime >= request.CreateTimeStart.Value);
|
||
}
|
||
|
||
if (request.CreateTimeEnd.HasValue)
|
||
{
|
||
query = query.Where(w => w.CreateTime <= request.CreateTimeEnd.Value);
|
||
}
|
||
|
||
return query;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取提现状态名称
|
||
/// </summary>
|
||
/// <param name="status">状态值</param>
|
||
/// <returns>状态名称</returns>
|
||
private static string GetWithdrawalStatusName(int status)
|
||
{
|
||
return WithdrawalStatusNames.TryGetValue(status, out var name) ? name : "未知";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取用户等级名称
|
||
/// </summary>
|
||
/// <param name="level">等级值</param>
|
||
/// <returns>等级名称</returns>
|
||
private static string GetUserLevelName(int level)
|
||
{
|
||
return UserLevelNames.TryGetValue(level, out var name) ? name : "未知";
|
||
}
|
||
|
||
#endregion
|
||
}
|