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 CMSMicroservice.Domain.Enums;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;
|
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;
|
||||||
|
|
||||||
@@ -124,6 +125,9 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
|
|||||||
RightCount = x.RightCount
|
RightCount = x.RightCount
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
var (goldLeft, goldRight, silverLeft, silverRight) =
|
||||||
|
await LoadLatestPackageLegScoresAsync(userId, cancellationToken);
|
||||||
|
|
||||||
return new GetNetworkStatisticsResponseDto
|
return new GetNetworkStatisticsResponseDto
|
||||||
{
|
{
|
||||||
TotalMembers = totalMembers,
|
TotalMembers = totalMembers,
|
||||||
@@ -136,10 +140,79 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
|
|||||||
MaxDepth = maxDepth,
|
MaxDepth = maxDepth,
|
||||||
LevelDistribution = levelDistribution,
|
LevelDistribution = levelDistribution,
|
||||||
MonthlyGrowth = monthlyGrowth,
|
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>
|
/// <summary>
|
||||||
/// Recursively get all descendants of a user
|
/// Recursively get all descendants of a user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
+8
@@ -13,6 +13,14 @@ public class GetNetworkStatisticsResponseDto
|
|||||||
public List<LevelDistributionModel> LevelDistribution { get; set; } = new();
|
public List<LevelDistributionModel> LevelDistribution { get; set; } = new();
|
||||||
public List<MonthlyGrowthModel> MonthlyGrowth { get; set; } = new();
|
public List<MonthlyGrowthModel> MonthlyGrowth { get; set; } = new();
|
||||||
public List<TopNetworkUserModel> TopUsers { 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
|
public class LevelDistributionModel
|
||||||
|
|||||||
-94
@@ -41,11 +41,6 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
|||||||
|
|
||||||
// تبدیل نتایج flat به ساختار درختی
|
// تبدیل نتایج flat به ساختار درختی
|
||||||
var tree = BuildTreeFromFlatNodes(flatNodes);
|
var tree = BuildTreeFromFlatNodes(flatNodes);
|
||||||
if (tree != null)
|
|
||||||
{
|
|
||||||
await EnrichWithPackageLegScoresAsync(tree, flatNodes, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -198,93 +193,4 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
|||||||
var rootNode = flatNodes.FirstOrDefault(n => n.NetworkLevel == 0);
|
var rootNode = flatNodes.FirstOrDefault(n => n.NetworkLevel == 0);
|
||||||
return rootNode != null ? nodeDict[rootNode.UserId] : null;
|
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>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.0.210</Version>
|
<Version>0.0.211</Version>
|
||||||
<DebugType>None</DebugType>
|
<DebugType>None</DebugType>
|
||||||
<DebugSymbols>False</DebugSymbols>
|
<DebugSymbols>False</DebugSymbols>
|
||||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||||
|
|||||||
@@ -328,6 +328,11 @@ message GetMyNetworkStatisticsResponse
|
|||||||
string my_network_leg = 11;
|
string my_network_leg = 11;
|
||||||
string my_referral_code = 12;
|
string my_referral_code = 12;
|
||||||
CustomerLastMemberModel last_member = 13;
|
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
|
message CustomerLastMemberModel
|
||||||
|
|||||||
Reference in New Issue
Block a user