feat(network): enrich network tree with package leg scores and update Protobuf definitions
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 12m47s

- Added logic to enrich the network tree with the latest left/right leg totals for gold and silver packages in GetNetworkTreeQueryHandler.
- Introduced new properties in NetworkTreeDto for GoldLeftLegTotal, GoldRightLegTotal, SilverLeftLegTotal, and SilverRightLegTotal.
- Updated Protobuf definitions to include new fields for leg scores in NetworkTreeNodeModel.
- Enhanced mapping in NetworkMembershipProfile and NetworkMembershipService to accommodate new leg score properties.
- Bumped Protobuf project version to reflect the addition of new features.
This commit is contained in:
masoodafar-web
2026-08-25 22:39:19 +03:30
parent 2d23dbc798
commit ea1d2d4400
6 changed files with 122 additions and 3 deletions
@@ -1,5 +1,4 @@
using System.Data;
using System.Data.Common;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
@@ -42,6 +41,11 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
// تبدیل نتایج flat به ساختار درختی
var tree = BuildTreeFromFlatNodes(flatNodes);
if (tree != null)
{
await EnrichWithPackageLegScoresAsync(tree, flatNodes, cancellationToken);
}
return tree;
}
catch (Exception ex)
@@ -194,4 +198,93 @@ 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);
}
@@ -60,6 +60,18 @@ public class NetworkTreeDto
public long? PackageId { get; set; }
/// <summary>آخرین LeftLegTotal پکیج طلایی (پایه)</summary>
public int GoldLeftLegTotal { get; set; }
/// <summary>آخرین RightLegTotal پکیج طلایی (پایه)</summary>
public int GoldRightLegTotal { get; set; }
/// <summary>آخرین LeftLegTotal پکیج نقره‌ای</summary>
public int SilverLeftLegTotal { get; set; }
/// <summary>آخرین RightLegTotal پکیج نقره‌ای</summary>
public int SilverRightLegTotal { get; set; }
public NetworkTreeDto? LeftChild { get; set; }
public NetworkTreeDto? RightChild { get; set; }
}
@@ -3,7 +3,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.209</Version>
<Version>0.0.210</Version>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -209,6 +209,11 @@ message NetworkTreeNodeModel
google.protobuf.BoolValue is_new_activation = 22; // true=اولین فعال‌سازی، false=خرید مجدد، null=بدون Cycle
string package_name = 23; // نام پکیج موثر
google.protobuf.Int64Value package_id = 24; // شناسه پکیج
// آخرین NetworkWeeklyBalance: امتیاز پای چپ/راست برای پکیج طلایی و نقره‌ای (D3)
int32 gold_left_leg_total = 25;
int32 gold_right_leg_total = 26;
int32 silver_left_leg_total = 27;
int32 silver_right_leg_total = 28;
}
// GetHistory Query
@@ -95,7 +95,11 @@ public class NetworkMembershipProfile : IRegister
IsActivatedInTargetWeek = node.IsActivatedInTargetWeek,
PackageName = node.PackageName ?? string.Empty,
PackageId = node.PackageId.HasValue ? node.PackageId.Value : null,
IsNewActivation = node.IsNewActivation.HasValue ? node.IsNewActivation.Value : null
IsNewActivation = node.IsNewActivation.HasValue ? node.IsNewActivation.Value : null,
GoldLeftLegTotal = node.GoldLeftLegTotal,
GoldRightLegTotal = node.GoldRightLegTotal,
SilverLeftLegTotal = node.SilverLeftLegTotal,
SilverRightLegTotal = node.SilverRightLegTotal
};
if (parentId.HasValue)
@@ -185,6 +185,11 @@ public class NetworkMembershipService : NetworkMembershipContract.NetworkMembers
node.PackageId = dto.PackageId.Value;
}
node.GoldLeftLegTotal = dto.GoldLeftLegTotal;
node.GoldRightLegTotal = dto.GoldRightLegTotal;
node.SilverLeftLegTotal = dto.SilverLeftLegTotal;
node.SilverRightLegTotal = dto.SilverRightLegTotal;
if (dto.IsNewActivation.HasValue)
{
node.IsNewActivation = dto.IsNewActivation.Value;