feat: Implement customer profile and referral queries
- Add GetCustomerProfileResponseDto for retrieving customer profile information. - Create GetCustomerReferralsQuery and GetCustomerReferralsQueryHandler to fetch customer referrals with pagination and filtering options. - Introduce GetCustomerReferralsResponseDto to structure the response for customer referrals. - Implement GetCustomerSettingsQuery and GetCustomerSettingsQueryHandler to retrieve user settings. - Add GetCustomerOrder and GetCustomerOrderQueryHandler for fetching specific customer orders. - Create GetCustomerOrderHistoryQuery and GetCustomerOrderHistoryQueryHandler to retrieve order history with filtering options. - Implement GetCustomerOrdersQuery and GetCustomerOrdersQueryHandler for fetching multiple customer orders with filters. - Add GetCustomerWalletChangeLogQuery and GetCustomerWalletChangeLogQueryHandler for retrieving wallet change logs. - Implement GetCustomerWithdrawalSettingsQuery and GetCustomerWithdrawalSettingsQueryHandler for fetching withdrawal settings. - Create GetCustomerWithdrawalsQuery and GetCustomerWithdrawalsQueryHandler to retrieve customer withdrawal requests.
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
|
||||
|
||||
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت درخت شبکه کاربر جاری (Customer-facing)
|
||||
/// از ICurrentUserService برای دریافت UserId استفاده میکند
|
||||
/// </summary>
|
||||
public record GetMyNetworkTreeQuery : IRequest<NetworkTreeDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// تعداد سطوح (Depth) که میخواهیم نمایش دهیم (پیشفرض: 3)
|
||||
/// </summary>
|
||||
public int MaxDepth { get; init; } = 3;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
|
||||
|
||||
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree;
|
||||
|
||||
/// <summary>
|
||||
/// Handler برای دریافت درخت شبکه کاربر جاری
|
||||
/// </summary>
|
||||
public class GetMyNetworkTreeQueryHandler : IRequestHandler<GetMyNetworkTreeQuery, NetworkTreeDto?>
|
||||
{
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
private readonly ISender _sender;
|
||||
|
||||
public GetMyNetworkTreeQueryHandler(
|
||||
ICurrentUserService currentUser,
|
||||
ISender sender)
|
||||
{
|
||||
_currentUser = currentUser;
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
public async Task<NetworkTreeDto?> Handle(GetMyNetworkTreeQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// دریافت UserId از JWT
|
||||
var userId = long.TryParse(_currentUser.UserId, out var id) ? id : 0;
|
||||
if (userId == 0)
|
||||
{
|
||||
throw new UnauthorizedAccessException("User not authenticated");
|
||||
}
|
||||
|
||||
// استفاده از GetNetworkTreeQuery موجود با UserId از JWT
|
||||
var query = new GetNetworkTreeQuery
|
||||
{
|
||||
UserId = userId,
|
||||
MaxDepth = request.MaxDepth > 0 ? request.MaxDepth : 3
|
||||
};
|
||||
|
||||
return await _sender.Send(query, cancellationToken);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetMyNetworkTree;
|
||||
|
||||
public class GetMyNetworkTreeQueryValidator : AbstractValidator<GetMyNetworkTreeQuery>
|
||||
{
|
||||
public GetMyNetworkTreeQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.MaxDepth)
|
||||
.InclusiveBetween(1, 100)
|
||||
.WithMessage("عمق درخت باید بین 1 تا 100 باشد");
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<GetMyNetworkTreeQuery>.CreateWithOptions(
|
||||
(GetMyNetworkTreeQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
+4
-1
@@ -2,5 +2,8 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStat
|
||||
|
||||
public class GetNetworkStatisticsQuery : IRequest<GetNetworkStatisticsResponseDto>
|
||||
{
|
||||
// No parameters - returns overall statistics
|
||||
/// <summary>
|
||||
/// شناسه کاربر برای محاسبه آمار شبکه او - 0 یا null یعنی کاربر جاری
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
}
|
||||
|
||||
+108
-47
@@ -5,61 +5,79 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStat
|
||||
public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatisticsQuery, GetNetworkStatisticsResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
|
||||
public GetNetworkStatisticsQueryHandler(IApplicationDbContext context)
|
||||
public GetNetworkStatisticsQueryHandler(
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
_context = context;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
public async Task<GetNetworkStatisticsResponseDto> Handle(GetNetworkStatisticsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Basic statistics - using Users table with NetworkParentId
|
||||
var totalMembers = await _context.Users
|
||||
.Where(x => x.NetworkParentId != null)
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
var activeMembers = await _context.Users
|
||||
.Where(x => x.NetworkParentId != null)
|
||||
.CountAsync(cancellationToken);
|
||||
// Get userId - use current user if not specified or is 0
|
||||
var userId = request.UserId == 0
|
||||
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
|
||||
: request.UserId;
|
||||
|
||||
var leftLegCount = await _context.Users
|
||||
.Where(x => x.LegPosition == NetworkLeg.Left)
|
||||
.CountAsync(cancellationToken);
|
||||
if (userId == 0)
|
||||
{
|
||||
throw new UnauthorizedAccessException("User ID not found");
|
||||
}
|
||||
|
||||
var rightLegCount = await _context.Users
|
||||
.Where(x => x.LegPosition == NetworkLeg.Right)
|
||||
.CountAsync(cancellationToken);
|
||||
// Get all descendants recursively
|
||||
var allUsers = await _context.Users.ToListAsync(cancellationToken);
|
||||
var allDescendants = GetAllDescendants(userId, allUsers);
|
||||
|
||||
// Statistics for the user's network (all descendants)
|
||||
var totalMembers = allDescendants.Count;
|
||||
var activeMembers = allDescendants.Count(x => !x.IsDeleted);
|
||||
|
||||
// Get direct left and right children
|
||||
var leftChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left);
|
||||
var rightChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right);
|
||||
|
||||
// Count all descendants in left and right subtrees
|
||||
var leftLegCount = leftChild != null ? GetAllDescendants(leftChild.Id, allUsers).Count + 1 : 0; // +1 for leftChild itself
|
||||
var rightLegCount = rightChild != null ? GetAllDescendants(rightChild.Id, allUsers).Count + 1 : 0; // +1 for rightChild itself
|
||||
|
||||
double leftPercentage = totalMembers > 0 ? (leftLegCount / (double)totalMembers) * 100 : 0;
|
||||
double rightPercentage = totalMembers > 0 ? (rightLegCount / (double)totalMembers) * 100 : 0;
|
||||
|
||||
// Calculate depth based on network parent relationships
|
||||
// For simplicity, we'll estimate average depth as 3-5 levels
|
||||
double averageDepth = 4.5; // Estimated average
|
||||
int maxDepth = 10; // Estimated max depth
|
||||
|
||||
// Level distribution - simplified estimation based on growth pattern
|
||||
var levelDistribution = new List<LevelDistributionModel>();
|
||||
if (totalMembers > 0)
|
||||
// Calculate actual depth
|
||||
int maxDepth = 0;
|
||||
double totalDepthSum = 0;
|
||||
var userDepths = new Dictionary<long, int>();
|
||||
CalculateDepths(userId, allUsers, 0, userDepths, ref maxDepth);
|
||||
|
||||
if (allDescendants.Count > 0)
|
||||
{
|
||||
// Approximate distribution: Level 1 (10%), Level 2 (20%), Level 3 (30%), Level 4 (20%), Level 5+ (20%)
|
||||
levelDistribution = new List<LevelDistributionModel>
|
||||
{
|
||||
new() { Level = 1, Count = (int)(totalMembers * 0.1) },
|
||||
new() { Level = 2, Count = (int)(totalMembers * 0.2) },
|
||||
new() { Level = 3, Count = (int)(totalMembers * 0.3) },
|
||||
new() { Level = 4, Count = (int)(totalMembers * 0.2) },
|
||||
new() { Level = 5, Count = (int)(totalMembers * 0.15) },
|
||||
new() { Level = 6, Count = totalMembers - (int)(totalMembers * 0.95) }
|
||||
};
|
||||
totalDepthSum = allDescendants.Sum(d => userDepths.ContainsKey(d.Id) ? userDepths[d.Id] : 0);
|
||||
}
|
||||
double averageDepth = allDescendants.Count > 0 ? totalDepthSum / allDescendants.Count : 0;
|
||||
|
||||
// Level distribution - calculate from depths
|
||||
var levelDistribution = new List<LevelDistributionModel>();
|
||||
if (allDescendants.Count > 0)
|
||||
{
|
||||
var levelCounts = allDescendants
|
||||
.Where(d => userDepths.ContainsKey(d.Id))
|
||||
.GroupBy(d => userDepths[d.Id])
|
||||
.OrderBy(g => g.Key)
|
||||
.Select(g => new LevelDistributionModel { Level = g.Key, Count = g.Count() })
|
||||
.ToList();
|
||||
|
||||
levelDistribution = levelCounts;
|
||||
}
|
||||
|
||||
// Monthly growth (last 6 months) - using Created date
|
||||
// Monthly growth (last 6 months) - using descendants Created date
|
||||
var sixMonthsAgo = DateTime.Now.AddMonths(-6);
|
||||
var monthlyGrowthRaw = await _context.Users
|
||||
.Where(x => x.NetworkParentId != null && x.Created >= sixMonthsAgo)
|
||||
var monthlyGrowthRaw = allDescendants
|
||||
.Where(x => x.Created >= sixMonthsAgo)
|
||||
.Select(x => new { x.Created.Year, x.Created.Month })
|
||||
.ToListAsync(cancellationToken);
|
||||
.ToList();
|
||||
|
||||
var monthlyGrowth = monthlyGrowthRaw
|
||||
.GroupBy(x => new { x.Year, x.Month })
|
||||
@@ -71,27 +89,34 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
|
||||
.OrderBy(x => x.Month)
|
||||
.ToList();
|
||||
|
||||
// Top users by total children count
|
||||
var topUsers = await _context.Users
|
||||
.Where(x => x.NetworkParentId != null)
|
||||
// Top users by total descendants count
|
||||
var userDescendantCounts = new Dictionary<long, int>();
|
||||
foreach (var user in allDescendants)
|
||||
{
|
||||
var descendants = GetAllDescendants(user.Id, allUsers);
|
||||
userDescendantCounts[user.Id] = descendants.Count;
|
||||
}
|
||||
|
||||
var topUserData = allDescendants
|
||||
.Where(x => x.Id != userId && userDescendantCounts[x.Id] > 0)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
UserName = (x.FirstName + " " + x.LastName).Trim(),
|
||||
LeftCount = _context.Users.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Left),
|
||||
RightCount = _context.Users.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Right)
|
||||
DescendantCount = userDescendantCounts[x.Id],
|
||||
LeftCount = allUsers.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Left),
|
||||
RightCount = allUsers.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Right)
|
||||
})
|
||||
.Where(x => x.LeftCount + x.RightCount > 0)
|
||||
.OrderByDescending(x => x.LeftCount + x.RightCount)
|
||||
.OrderByDescending(x => x.DescendantCount)
|
||||
.Take(10)
|
||||
.ToListAsync(cancellationToken);
|
||||
.ToList();
|
||||
|
||||
var topUserModels = topUsers.Select((x, index) => new TopNetworkUserModel
|
||||
var topUserModels = topUserData.Select((x, index) => new TopNetworkUserModel
|
||||
{
|
||||
Rank = index + 1,
|
||||
UserId = x.Id,
|
||||
UserName = x.UserName,
|
||||
TotalChildren = x.LeftCount + x.RightCount,
|
||||
TotalChildren = x.DescendantCount,
|
||||
LeftCount = x.LeftCount,
|
||||
RightCount = x.RightCount
|
||||
}).ToList();
|
||||
@@ -111,4 +136,40 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
|
||||
TopUsers = topUserModels
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively get all descendants of a user
|
||||
/// </summary>
|
||||
private List<User> GetAllDescendants(long userId, List<User> allUsers)
|
||||
{
|
||||
var descendants = new List<User>();
|
||||
var directChildren = allUsers.Where(x => x.NetworkParentId == userId).ToList();
|
||||
|
||||
foreach (var child in directChildren)
|
||||
{
|
||||
descendants.Add(child);
|
||||
descendants.AddRange(GetAllDescendants(child.Id, allUsers));
|
||||
}
|
||||
|
||||
return descendants;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate depth for all descendants recursively
|
||||
/// </summary>
|
||||
private void CalculateDepths(long userId, List<User> allUsers, int currentDepth, Dictionary<long, int> depths, ref int maxDepth)
|
||||
{
|
||||
var children = allUsers.Where(x => x.NetworkParentId == userId).ToList();
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
var childDepth = currentDepth + 1;
|
||||
depths[child.Id] = childDepth;
|
||||
|
||||
if (childDepth > maxDepth)
|
||||
maxDepth = childDepth;
|
||||
|
||||
CalculateDepths(child.Id, allUsers, childDepth, depths, ref maxDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -8,21 +8,37 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ILogger<GetNetworkTreeQueryHandler> _logger;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
|
||||
public GetNetworkTreeQueryHandler(
|
||||
IApplicationDbContext context,
|
||||
ILogger<GetNetworkTreeQueryHandler> logger)
|
||||
ILogger<GetNetworkTreeQueryHandler> logger,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
public async Task<NetworkTreeDto?> Handle(GetNetworkTreeQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Get userId - use current user if UserId is 0
|
||||
var userId = request.UserId == 0
|
||||
? (long.TryParse(_currentUser.UserId, out var currentUserId) ? currentUserId : 0)
|
||||
: request.UserId;
|
||||
|
||||
if (userId == 0)
|
||||
{
|
||||
throw new UnauthorizedAccessException("User ID not found");
|
||||
}
|
||||
|
||||
// Create a new request with the resolved userId
|
||||
var resolvedRequest = request with { UserId = userId };
|
||||
|
||||
try
|
||||
{
|
||||
// دریافت نتایج flat از Stored Procedure
|
||||
var flatNodes = await ExecuteStoredProcedureAsync(request, cancellationToken);
|
||||
var flatNodes = await ExecuteStoredProcedureAsync(resolvedRequest, cancellationToken);
|
||||
|
||||
if (flatNodes == null || !flatNodes.Any())
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user