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
All checks were successful
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 2m14s
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user