179 lines
6.9 KiB
C#
179 lines
6.9 KiB
C#
using CMSMicroservice.Domain.Enums;
|
|
|
|
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;
|
|
|
|
public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatisticsQuery, GetNetworkStatisticsResponseDto>
|
|
{
|
|
private readonly IApplicationDbContext _context;
|
|
|
|
public GetNetworkStatisticsQueryHandler(
|
|
IApplicationDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<GetNetworkStatisticsResponseDto> Handle(GetNetworkStatisticsQuery request, CancellationToken cancellationToken)
|
|
{
|
|
// UserId > 0 → stats for that user's network
|
|
// UserId == 0 → global stats (find root user of the network)
|
|
var userId = request.UserId;
|
|
|
|
// Get all users (needed for descendant calculation anyway)
|
|
var allUsers = await _context.Users.ToListAsync(cancellationToken);
|
|
|
|
if (userId == 0)
|
|
{
|
|
// Find root user (user with no NetworkParentId)
|
|
var rootUser = allUsers.FirstOrDefault(x => x.NetworkParentId == null || x.NetworkParentId == 0);
|
|
if (rootUser != null)
|
|
userId = rootUser.Id;
|
|
else if (allUsers.Count > 0)
|
|
userId = allUsers.First().Id;
|
|
else
|
|
throw new InvalidOperationException("No users found in the system");
|
|
}
|
|
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 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)
|
|
{
|
|
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 descendants Created date
|
|
var sixMonthsAgo = DateTime.Now.AddMonths(-6);
|
|
var monthlyGrowthRaw = allDescendants
|
|
.Where(x => x.Created >= sixMonthsAgo)
|
|
.Select(x => new { x.Created.Year, x.Created.Month })
|
|
.ToList();
|
|
|
|
var monthlyGrowth = monthlyGrowthRaw
|
|
.GroupBy(x => new { x.Year, x.Month })
|
|
.Select(g => new MonthlyGrowthModel
|
|
{
|
|
Month = $"{g.Key.Year}-{g.Key.Month:D2}",
|
|
NewMembers = g.Count()
|
|
})
|
|
.OrderBy(x => x.Month)
|
|
.ToList();
|
|
|
|
// 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(),
|
|
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)
|
|
})
|
|
.OrderByDescending(x => x.DescendantCount)
|
|
.Take(10)
|
|
.ToList();
|
|
|
|
var topUserModels = topUserData.Select((x, index) => new TopNetworkUserModel
|
|
{
|
|
Rank = index + 1,
|
|
UserId = x.Id,
|
|
UserName = x.UserName,
|
|
TotalChildren = x.DescendantCount,
|
|
LeftCount = x.LeftCount,
|
|
RightCount = x.RightCount
|
|
}).ToList();
|
|
|
|
return new GetNetworkStatisticsResponseDto
|
|
{
|
|
TotalMembers = totalMembers,
|
|
ActiveMembers = activeMembers,
|
|
LeftLegCount = leftLegCount,
|
|
RightLegCount = rightLegCount,
|
|
LeftPercentage = leftPercentage,
|
|
RightPercentage = rightPercentage,
|
|
AverageDepth = averageDepth,
|
|
MaxDepth = maxDepth,
|
|
LevelDistribution = levelDistribution,
|
|
MonthlyGrowth = monthlyGrowth,
|
|
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);
|
|
}
|
|
}
|
|
}
|