feat(network): add latest package leg totals to network statistics response
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 7m51s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 7m51s
- Introduced new properties for GoldLeftLegTotal, GoldRightLegTotal, SilverLeftLegTotal, and SilverRightLegTotal in GetNetworkStatisticsResponseDto. - Implemented LoadLatestPackageLegScoresAsync method in GetNetworkStatisticsQueryHandler to fetch the latest leg totals for the user. - Updated Protobuf definitions to include new fields for leg totals in GetMyNetworkStatisticsResponse. - Bumped Protobuf project version to reflect the addition of new features.
This commit is contained in:
+74
-1
@@ -1,4 +1,5 @@
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;
|
||||
|
||||
@@ -124,6 +125,9 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
|
||||
RightCount = x.RightCount
|
||||
}).ToList();
|
||||
|
||||
var (goldLeft, goldRight, silverLeft, silverRight) =
|
||||
await LoadLatestPackageLegScoresAsync(userId, cancellationToken);
|
||||
|
||||
return new GetNetworkStatisticsResponseDto
|
||||
{
|
||||
TotalMembers = totalMembers,
|
||||
@@ -136,10 +140,79 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
|
||||
MaxDepth = maxDepth,
|
||||
LevelDistribution = levelDistribution,
|
||||
MonthlyGrowth = monthlyGrowth,
|
||||
TopUsers = topUserModels
|
||||
TopUsers = topUserModels,
|
||||
GoldLeftLegTotal = goldLeft,
|
||||
GoldRightLegTotal = goldRight,
|
||||
SilverLeftLegTotal = silverLeft,
|
||||
SilverRightLegTotal = silverRight
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// آخرین Left/RightLegTotal طلایی و نقرهای فقط برای خود کاربر (نوار آمار شجره).
|
||||
/// </summary>
|
||||
private async Task<(int GoldLeft, int GoldRight, int SilverLeft, int SilverRight)> LoadLatestPackageLegScoresAsync(
|
||||
long userId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var packages = await _context.Packages
|
||||
.AsNoTracking()
|
||||
.Where(p => !p.IsDeleted && p.IsActive)
|
||||
.Select(p => new { p.Id, p.Title, p.IsBasePackage, p.MaxBalancesPerLeg })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var goldPackageId = packages.FirstOrDefault(p => p.IsBasePackage)?.Id
|
||||
?? packages.FirstOrDefault(p => p.Title.Contains("طلایی") || p.Title.Contains("طلايی"))?.Id;
|
||||
|
||||
var silverPackageId = packages.FirstOrDefault(p =>
|
||||
!p.IsBasePackage && (p.Title.Contains("نقره") || p.MaxBalancesPerLeg <= 30))?.Id
|
||||
?? packages.FirstOrDefault(p => !p.IsBasePackage)?.Id;
|
||||
|
||||
var packageIds = new List<long>();
|
||||
if (goldPackageId.HasValue) packageIds.Add(goldPackageId.Value);
|
||||
if (silverPackageId.HasValue && silverPackageId != goldPackageId)
|
||||
packageIds.Add(silverPackageId.Value);
|
||||
|
||||
if (packageIds.Count == 0)
|
||||
return (0, 0, 0, 0);
|
||||
|
||||
var balances = await _context.NetworkWeeklyBalances
|
||||
.AsNoTracking()
|
||||
.Where(b => b.UserId == userId && packageIds.Contains(b.PackageId))
|
||||
.Select(b => new { b.PackageId, b.WeekDefinitionId, b.LeftLegTotal, b.RightLegTotal })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
int goldLeft = 0, goldRight = 0, silverLeft = 0, silverRight = 0;
|
||||
|
||||
if (goldPackageId.HasValue)
|
||||
{
|
||||
var gold = balances
|
||||
.Where(b => b.PackageId == goldPackageId.Value)
|
||||
.OrderByDescending(b => b.WeekDefinitionId)
|
||||
.FirstOrDefault();
|
||||
if (gold != null)
|
||||
{
|
||||
goldLeft = gold.LeftLegTotal;
|
||||
goldRight = gold.RightLegTotal;
|
||||
}
|
||||
}
|
||||
|
||||
if (silverPackageId.HasValue)
|
||||
{
|
||||
var silver = balances
|
||||
.Where(b => b.PackageId == silverPackageId.Value)
|
||||
.OrderByDescending(b => b.WeekDefinitionId)
|
||||
.FirstOrDefault();
|
||||
if (silver != null)
|
||||
{
|
||||
silverLeft = silver.LeftLegTotal;
|
||||
silverRight = silver.RightLegTotal;
|
||||
}
|
||||
}
|
||||
|
||||
return (goldLeft, goldRight, silverLeft, silverRight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively get all descendants of a user
|
||||
/// </summary>
|
||||
|
||||
+8
@@ -13,6 +13,14 @@ public class GetNetworkStatisticsResponseDto
|
||||
public List<LevelDistributionModel> LevelDistribution { get; set; } = new();
|
||||
public List<MonthlyGrowthModel> MonthlyGrowth { get; set; } = new();
|
||||
public List<TopNetworkUserModel> TopUsers { get; set; } = new();
|
||||
|
||||
/// <summary>آخرین LeftLegTotal پکیج طلایی برای همین کاربر</summary>
|
||||
public int GoldLeftLegTotal { get; set; }
|
||||
public int GoldRightLegTotal { get; set; }
|
||||
public int SilverLeftLegTotal { get; set; }
|
||||
public int SilverRightLegTotal { get; set; }
|
||||
|
||||
public string WeakerLeg => LeftLegCount <= RightLegCount ? "Left" : "Right";
|
||||
}
|
||||
|
||||
public class LevelDistributionModel
|
||||
|
||||
-94
@@ -41,11 +41,6 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
||||
|
||||
// تبدیل نتایج flat به ساختار درختی
|
||||
var tree = BuildTreeFromFlatNodes(flatNodes);
|
||||
if (tree != null)
|
||||
{
|
||||
await EnrichWithPackageLegScoresAsync(tree, flatNodes, cancellationToken);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -198,93 +193,4 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
||||
var rootNode = flatNodes.FirstOrDefault(n => n.NetworkLevel == 0);
|
||||
return rootNode != null ? nodeDict[rootNode.UserId] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// آخرین Left/RightLegTotal طلایی و نقرهای برای هر نود درخت (برنامهریزی پای چپ/راست).
|
||||
/// </summary>
|
||||
private async Task EnrichWithPackageLegScoresAsync(
|
||||
NetworkTreeDto root,
|
||||
List<NetworkTreeNodeDto> flatNodes,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var userIds = flatNodes.Select(n => n.UserId).Distinct().ToList();
|
||||
if (userIds.Count == 0) return;
|
||||
|
||||
var packages = await _context.Packages
|
||||
.AsNoTracking()
|
||||
.Where(p => !p.IsDeleted && p.IsActive)
|
||||
.Select(p => new { p.Id, p.Title, p.IsBasePackage, p.MaxBalancesPerLeg })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var goldPackageId = packages.FirstOrDefault(p => p.IsBasePackage)?.Id
|
||||
?? packages.FirstOrDefault(p => p.Title.Contains("طلایی") || p.Title.Contains("طلايی"))?.Id;
|
||||
|
||||
var silverPackageId = packages.FirstOrDefault(p =>
|
||||
!p.IsBasePackage && (p.Title.Contains("نقره") || p.MaxBalancesPerLeg <= 30))?.Id
|
||||
?? packages.FirstOrDefault(p => !p.IsBasePackage)?.Id;
|
||||
|
||||
if (!goldPackageId.HasValue && !silverPackageId.HasValue)
|
||||
{
|
||||
_logger.LogWarning("GetNetworkTree: no gold/silver packages found for leg score enrichment");
|
||||
return;
|
||||
}
|
||||
|
||||
var packageIds = new List<long>();
|
||||
if (goldPackageId.HasValue) packageIds.Add(goldPackageId.Value);
|
||||
if (silverPackageId.HasValue && silverPackageId != goldPackageId)
|
||||
packageIds.Add(silverPackageId.Value);
|
||||
|
||||
var rawBalances = await _context.NetworkWeeklyBalances
|
||||
.AsNoTracking()
|
||||
.Where(b => userIds.Contains(b.UserId) && packageIds.Contains(b.PackageId))
|
||||
.Select(b => new LegScoreRow(
|
||||
b.UserId,
|
||||
b.PackageId,
|
||||
b.WeekDefinitionId,
|
||||
b.LeftLegTotal,
|
||||
b.RightLegTotal))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var latestByUserPackage = rawBalances
|
||||
.GroupBy(b => (b.UserId, b.PackageId))
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.OrderByDescending(x => x.WeekDefinitionId).First());
|
||||
|
||||
ApplyLegScores(root, goldPackageId, silverPackageId, latestByUserPackage);
|
||||
}
|
||||
|
||||
private static void ApplyLegScores(
|
||||
NetworkTreeDto node,
|
||||
long? goldPackageId,
|
||||
long? silverPackageId,
|
||||
Dictionary<(long UserId, long PackageId), LegScoreRow> latestByUserPackage)
|
||||
{
|
||||
if (goldPackageId.HasValue &&
|
||||
latestByUserPackage.TryGetValue((node.UserId, goldPackageId.Value), out var gold))
|
||||
{
|
||||
node.GoldLeftLegTotal = gold.LeftLegTotal;
|
||||
node.GoldRightLegTotal = gold.RightLegTotal;
|
||||
}
|
||||
|
||||
if (silverPackageId.HasValue &&
|
||||
latestByUserPackage.TryGetValue((node.UserId, silverPackageId.Value), out var silver))
|
||||
{
|
||||
node.SilverLeftLegTotal = silver.LeftLegTotal;
|
||||
node.SilverRightLegTotal = silver.RightLegTotal;
|
||||
}
|
||||
|
||||
if (node.LeftChild != null)
|
||||
ApplyLegScores(node.LeftChild, goldPackageId, silverPackageId, latestByUserPackage);
|
||||
|
||||
if (node.RightChild != null)
|
||||
ApplyLegScores(node.RightChild, goldPackageId, silverPackageId, latestByUserPackage);
|
||||
}
|
||||
|
||||
private sealed record LegScoreRow(
|
||||
long UserId,
|
||||
long PackageId,
|
||||
long WeekDefinitionId,
|
||||
int LeftLegTotal,
|
||||
int RightLegTotal);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>0.0.210</Version>
|
||||
<Version>0.0.211</Version>
|
||||
<DebugType>None</DebugType>
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
|
||||
@@ -328,6 +328,11 @@ message GetMyNetworkStatisticsResponse
|
||||
string my_network_leg = 11;
|
||||
string my_referral_code = 12;
|
||||
CustomerLastMemberModel last_member = 13;
|
||||
// آخرین NetworkWeeklyBalance خود کاربر — برای نوار آمار پایین شجره (D3)
|
||||
int32 gold_left_leg_total = 14;
|
||||
int32 gold_right_leg_total = 15;
|
||||
int32 silver_left_leg_total = 16;
|
||||
int32 silver_right_leg_total = 17;
|
||||
}
|
||||
|
||||
message CustomerLastMemberModel
|
||||
|
||||
Reference in New Issue
Block a user