feat: enhance network membership response with detailed stats and hierarchy
All checks were successful
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 2m14s

This commit is contained in:
masoodafar-web
2025-12-12 06:07:23 +03:30
parent f27418cff4
commit 3c7ac68eeb
5 changed files with 333 additions and 33 deletions

View File

@@ -11,6 +11,7 @@ public class GetUserNetworkPositionQueryHandler : IRequestHandler<GetUserNetwork
public async Task<UserNetworkPositionDto?> Handle(GetUserNetworkPositionQuery request, CancellationToken cancellationToken)
{
// واکشی اطلاعات اصلی کاربر
var user = await _context.Users
.AsNoTracking()
.Where(x => x.Id == request.UserId)
@@ -20,8 +21,17 @@ public class GetUserNetworkPositionQueryHandler : IRequestHandler<GetUserNetwork
x.Mobile,
x.FirstName,
x.LastName,
x.Email,
x.NationalCode,
x.ReferralCode,
x.IsMobileVerified,
x.BirthDate,
x.NetworkParentId,
x.LegPosition
x.LegPosition,
x.HasReceivedDayaCredit,
x.DayaCreditReceivedAt,
x.PackagePurchaseMethod,
x.Created
})
.FirstOrDefaultAsync(cancellationToken);
@@ -30,41 +40,196 @@ public class GetUserNetworkPositionQueryHandler : IRequestHandler<GetUserNetwork
return null;
}
// شمارش فرزندان
var childrenCount = await _context.Users
.CountAsync(x => x.NetworkParentId == request.UserId, cancellationToken);
var leftChildCount = await _context.Users
.CountAsync(x => x.NetworkParentId == request.UserId && x.LegPosition == NetworkLeg.Left,
cancellationToken);
var rightChildCount = await _context.Users
.CountAsync(x => x.NetworkParentId == request.UserId && x.LegPosition == NetworkLeg.Right,
cancellationToken);
// اطلاعات والد
string? parentMobile = null;
string? parentFullName = null;
if (user.NetworkParentId.HasValue)
{
parentMobile = await _context.Users
var parent = await _context.Users
.Where(x => x.Id == user.NetworkParentId)
.Select(x => x.Mobile)
.Select(x => new { x.Mobile, x.FirstName, x.LastName })
.FirstOrDefaultAsync(cancellationToken);
if (parent != null)
{
parentMobile = parent.Mobile;
parentFullName = $"{parent.FirstName} {parent.LastName}".Trim();
}
}
// شمارش فرزندان مستقیم
var directChildren = await _context.Users
.Where(x => x.NetworkParentId == request.UserId)
.Select(x => new { x.Id, x.LegPosition, x.FirstName, x.LastName, x.Mobile, x.Created })
.ToListAsync(cancellationToken);
var leftChild = directChildren.FirstOrDefault(x => x.LegPosition == NetworkLeg.Left);
var rightChild = directChildren.FirstOrDefault(x => x.LegPosition == NetworkLeg.Right);
// محاسبه تعداد کل اعضای هر شاخه (با استفاده از CTE برای عملکرد بهتر)
var leftLegCount = await GetLegMemberCountAsync(request.UserId, NetworkLeg.Left, cancellationToken);
var rightLegCount = await GetLegMemberCountAsync(request.UserId, NetworkLeg.Right, cancellationToken);
var totalNetworkSize = leftLegCount + rightLegCount;
// محاسبه حداکثر عمق شبکه
var maxDepth = await GetMaxNetworkDepthAsync(request.UserId, cancellationToken);
// آمار اعضای فعال/غیرفعال (کسانی که پکیج خریده‌اند)
var allDescendantIds = await GetAllDescendantIdsAsync(request.UserId, cancellationToken);
var activeCount = await _context.Users
.CountAsync(x => allDescendantIds.Contains(x.Id) &&
x.PackagePurchaseMethod != PackagePurchaseMethod.None,
cancellationToken);
var inactiveCount = allDescendantIds.Count - activeCount;
// آمار کمیسیون‌های کاربر
var commissionStats = await _context.UserCommissionPayouts
.Where(x => x.UserId == request.UserId)
.GroupBy(x => x.UserId)
.Select(g => new
{
TotalBalances = g.Sum(x => x.BalancesEarned),
TotalAmount = g.Sum(x => x.TotalAmount),
PaidAmount = g.Where(x => x.Status == CommissionPayoutStatus.Paid).Sum(x => x.TotalAmount),
PendingAmount = g.Where(x => x.Status == CommissionPayoutStatus.Pending).Sum(x => x.TotalAmount)
})
.FirstOrDefaultAsync(cancellationToken);
return new UserNetworkPositionDto
{
// اطلاعات اصلی
UserId = user.Id,
Mobile = user.Mobile,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
NationalCode = user.NationalCode,
ReferralCode = user.ReferralCode,
IsMobileVerified = user.IsMobileVerified,
BirthDate = user.BirthDate,
JoinedAt = user.Created,
// اطلاعات شبکه
NetworkParentId = user.NetworkParentId,
ParentMobile = parentMobile,
ParentFullName = parentFullName,
LegPosition = user.LegPosition,
TotalChildren = childrenCount,
LeftChildCount = leftChildCount,
RightChildCount = rightChildCount,
IsInNetwork = user.NetworkParentId.HasValue
IsInNetwork = user.NetworkParentId.HasValue,
// آمار فرزندان مستقیم
TotalChildren = directChildren.Count,
LeftChildCount = leftChild != null ? 1 : 0,
RightChildCount = rightChild != null ? 1 : 0,
// اطلاعات فرزند چپ
LeftChildId = leftChild?.Id,
LeftChildFullName = leftChild != null ? $"{leftChild.FirstName} {leftChild.LastName}".Trim() : null,
LeftChildMobile = leftChild?.Mobile,
LeftChildJoinedAt = leftChild?.Created,
// اطلاعات فرزند راست
RightChildId = rightChild?.Id,
RightChildFullName = rightChild != null ? $"{rightChild.FirstName} {rightChild.LastName}".Trim() : null,
RightChildMobile = rightChild?.Mobile,
RightChildJoinedAt = rightChild?.Created,
// آمار کل شبکه
TotalLeftLegMembers = leftLegCount,
TotalRightLegMembers = rightLegCount,
TotalNetworkSize = totalNetworkSize,
MaxNetworkDepth = maxDepth,
// اطلاعات پکیج و دایا
HasReceivedDayaCredit = user.HasReceivedDayaCredit,
DayaCreditReceivedAt = user.DayaCreditReceivedAt,
PackagePurchaseMethod = user.PackagePurchaseMethod,
HasPurchasedGoldenPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None,
// آمار مالی
TotalEarnedCommission = commissionStats?.TotalAmount ?? 0,
TotalPaidCommission = commissionStats?.PaidAmount ?? 0,
PendingCommission = commissionStats?.PendingAmount ?? 0,
TotalBalancesEarned = commissionStats?.TotalBalances ?? 0,
// آمار فعالیت
ActiveMembersInNetwork = activeCount,
InactiveMembersInNetwork = inactiveCount
};
}
/// <summary>
/// محاسبه تعداد اعضای یک شاخه (چپ یا راست) به صورت بازگشتی
/// </summary>
private async Task<int> GetLegMemberCountAsync(long userId, NetworkLeg leg, CancellationToken cancellationToken)
{
var directChild = await _context.Users
.Where(x => x.NetworkParentId == userId && x.LegPosition == leg)
.Select(x => x.Id)
.FirstOrDefaultAsync(cancellationToken);
if (directChild == 0)
return 0;
// تعداد کل زیرمجموعه این فرزند + خود فرزند
var descendants = await GetAllDescendantIdsAsync(directChild, cancellationToken);
return descendants.Count + 1; // +1 برای خود فرزند
}
/// <summary>
/// محاسبه حداکثر عمق شبکه
/// </summary>
private async Task<int> GetMaxNetworkDepthAsync(long userId, CancellationToken cancellationToken)
{
var maxDepth = 0;
var currentLevelIds = new List<long> { userId };
while (currentLevelIds.Any())
{
var nextLevelIds = await _context.Users
.Where(x => currentLevelIds.Contains(x.NetworkParentId ?? 0))
.Select(x => x.Id)
.ToListAsync(cancellationToken);
if (nextLevelIds.Any())
{
maxDepth++;
currentLevelIds = nextLevelIds;
}
else
{
break;
}
}
return maxDepth;
}
/// <summary>
/// دریافت تمام ID های زیرمجموعه یک کاربر
/// </summary>
private async Task<List<long>> GetAllDescendantIdsAsync(long userId, CancellationToken cancellationToken)
{
var allDescendants = new List<long>();
var currentLevelIds = new List<long> { userId };
while (currentLevelIds.Any())
{
var children = await _context.Users
.Where(x => currentLevelIds.Contains(x.NetworkParentId ?? 0))
.Select(x => x.Id)
.ToListAsync(cancellationToken);
if (children.Any())
{
allDescendants.AddRange(children);
currentLevelIds = children;
}
else
{
break;
}
}
return allDescendants;
}
}

View File

@@ -5,15 +5,62 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetUserNetwork
/// </summary>
public class UserNetworkPositionDto
{
// اطلاعات اصلی کاربر
public long UserId { get; set; }
public string? Mobile { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public string? NationalCode { get; set; }
public string ReferralCode { get; set; } = string.Empty;
public bool IsMobileVerified { get; set; }
public DateTime? BirthDate { get; set; }
public DateTime JoinedAt { get; set; }
// اطلاعات شبکه
public long? NetworkParentId { get; set; }
public string? ParentMobile { get; set; }
public string? ParentFullName { get; set; }
public NetworkLeg? LegPosition { get; set; }
public bool IsInNetwork { get; set; }
// آمار فرزندان مستقیم
public int TotalChildren { get; set; }
public int LeftChildCount { get; set; }
public int RightChildCount { get; set; }
public bool IsInNetwork { get; set; }
// آمار کل زیرمجموعه (تمام سطوح)
public int TotalLeftLegMembers { get; set; }
public int TotalRightLegMembers { get; set; }
public int TotalNetworkSize { get; set; }
// اطلاعات فرزندان مستقیم
public long? LeftChildId { get; set; }
public string? LeftChildFullName { get; set; }
public string? LeftChildMobile { get; set; }
public DateTime? LeftChildJoinedAt { get; set; }
public long? RightChildId { get; set; }
public string? RightChildFullName { get; set; }
public string? RightChildMobile { get; set; }
public DateTime? RightChildJoinedAt { get; set; }
// اطلاعات پکیج و دایا
public bool HasReceivedDayaCredit { get; set; }
public DateTime? DayaCreditReceivedAt { get; set; }
public PackagePurchaseMethod PackagePurchaseMethod { get; set; }
public bool HasPurchasedGoldenPackage { get; set; }
// آمار مالی
public decimal TotalEarnedCommission { get; set; }
public decimal TotalPaidCommission { get; set; }
public decimal PendingCommission { get; set; }
public int TotalBalancesEarned { get; set; }
// عمق شبکه
public int MaxNetworkDepth { get; set; }
// آمار فعالیت
public int ActiveMembersInNetwork { get; set; }
public int InactiveMembersInNetwork { get; set; }
}

View File

@@ -3,7 +3,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.147</Version>
<Version>0.0.148</Version>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>

View File

@@ -85,20 +85,68 @@ message GetUserNetworkRequest
message GetUserNetworkResponse
{
// اطلاعات اصلی کاربر
int64 id = 1;
int64 user_id = 2;
string user_name = 3;
google.protobuf.Int64Value parent_id = 4;
string parent_name = 5;
int32 network_leg = 6; // NetworkLeg enum
google.protobuf.Int64Value left_child_id = 7;
string left_child_name = 8;
google.protobuf.Int64Value right_child_id = 9;
string right_child_name = 10;
int32 network_level = 11;
string referral_code = 12;
google.protobuf.Timestamp joined_at = 13;
google.protobuf.Timestamp created = 14;
string mobile = 4;
string email = 5;
string national_code = 6;
string referral_code = 7;
bool is_mobile_verified = 8;
google.protobuf.Timestamp birth_date = 9;
google.protobuf.Timestamp joined_at = 10;
// اطلاعات والد
google.protobuf.Int64Value parent_id = 11;
string parent_name = 12;
string parent_mobile = 13;
// موقعیت در شبکه
int32 network_leg = 14; // NetworkLeg enum
int32 network_level = 15;
bool is_in_network = 16;
// اطلاعات فرزند چپ
google.protobuf.Int64Value left_child_id = 17;
string left_child_name = 18;
string left_child_mobile = 19;
google.protobuf.Timestamp left_child_joined_at = 20;
// اطلاعات فرزند راست
google.protobuf.Int64Value right_child_id = 21;
string right_child_name = 22;
string right_child_mobile = 23;
google.protobuf.Timestamp right_child_joined_at = 24;
// آمار فرزندان مستقیم
int32 total_children = 25;
int32 left_child_count = 26;
int32 right_child_count = 27;
// آمار کل شبکه
int32 total_left_leg_members = 28;
int32 total_right_leg_members = 29;
int32 total_network_size = 30;
int32 max_network_depth = 31;
// اطلاعات پکیج و دایا
bool has_received_daya_credit = 32;
google.protobuf.Timestamp daya_credit_received_at = 33;
int32 package_purchase_method = 34;
bool has_purchased_golden_package = 35;
// آمار مالی
double total_earned_commission = 36;
double total_paid_commission = 37;
double pending_commission = 38;
int32 total_balances_earned = 39;
// آمار فعالیت
int32 active_members_in_network = 40;
int32 inactive_members_in_network = 41;
google.protobuf.Timestamp created = 42;
}
// GetNetworkTree Query

View File

@@ -1,4 +1,5 @@
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetUserNetworkPosition;
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using CMSMicroservice.Domain.Enums;
@@ -15,7 +16,46 @@ public class NetworkMembershipProfile : IRegister
// Response mapping: تبدیل درخت به لیست مسطح
config.NewConfig<NetworkTreeDto, GetNetworkTreeResponse>()
.MapWith(src => ConvertTreeToResponse(src));
.MapWith(src => ConvertTreeToResponse(src));
config.NewConfig<UserNetworkPositionDto, GetUserNetworkResponse>()
.Map(dest => dest.Id, src => src.UserId)
.Map(dest => dest.UserId, src => src.UserId)
.Map(dest => dest.UserName, src => (src.FirstName + " " + src.LastName).Trim())
.Map(dest => dest.Mobile, src => src.Mobile ?? "")
.Map(dest => dest.Email, src => src.Email ?? "")
.Map(dest => dest.NationalCode, src => src.NationalCode ?? "")
.Map(dest => dest.ReferralCode, src => src.ReferralCode)
.Map(dest => dest.IsMobileVerified, src => src.IsMobileVerified)
.Map(dest => dest.ParentId, src => src.NetworkParentId)
.Map(dest => dest.ParentName, src => src.ParentFullName ?? "")
.Map(dest => dest.ParentMobile, src => src.ParentMobile ?? "")
.Map(dest => dest.NetworkLeg, src => (int)(src.LegPosition ?? NetworkLeg.Left))
.Map(dest => dest.NetworkLevel, src => 0) // Deprecated field
.Map(dest => dest.IsInNetwork, src => src.IsInNetwork)
.Map(dest => dest.LeftChildId, src => src.LeftChildId)
.Map(dest => dest.LeftChildName, src => src.LeftChildFullName ?? "")
.Map(dest => dest.LeftChildMobile, src => src.LeftChildMobile ?? "")
.Map(dest => dest.RightChildId, src => src.RightChildId)
.Map(dest => dest.RightChildName, src => src.RightChildFullName ?? "")
.Map(dest => dest.RightChildMobile, src => src.RightChildMobile ?? "")
.Map(dest => dest.TotalChildren, src => src.TotalChildren)
.Map(dest => dest.LeftChildCount, src => src.LeftChildCount)
.Map(dest => dest.RightChildCount, src => src.RightChildCount)
.Map(dest => dest.TotalLeftLegMembers, src => src.TotalLeftLegMembers)
.Map(dest => dest.TotalRightLegMembers, src => src.TotalRightLegMembers)
.Map(dest => dest.TotalNetworkSize, src => src.TotalNetworkSize)
.Map(dest => dest.MaxNetworkDepth, src => src.MaxNetworkDepth)
.Map(dest => dest.HasReceivedDayaCredit, src => src.HasReceivedDayaCredit)
.Map(dest => dest.PackagePurchaseMethod, src => (int)src.PackagePurchaseMethod)
.Map(dest => dest.HasPurchasedGoldenPackage, src => src.HasPurchasedGoldenPackage)
.Map(dest => dest.TotalEarnedCommission, src => (double)src.TotalEarnedCommission)
.Map(dest => dest.TotalPaidCommission, src => (double)src.TotalPaidCommission)
.Map(dest => dest.PendingCommission, src => (double)src.PendingCommission)
.Map(dest => dest.TotalBalancesEarned, src => src.TotalBalancesEarned)
.Map(dest => dest.ActiveMembersInNetwork, src => src.ActiveMembersInNetwork)
.Map(dest => dest.InactiveMembersInNetwork, src => src.InactiveMembersInNetwork);
}
private static GetNetworkTreeResponse ConvertTreeToResponse(NetworkTreeDto? treeDto)