diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/GetUserNetworkPositionQueryHandler.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/GetUserNetworkPositionQueryHandler.cs index 42c1294..8a27ee1 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/GetUserNetworkPositionQueryHandler.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/GetUserNetworkPositionQueryHandler.cs @@ -11,6 +11,7 @@ public class GetUserNetworkPositionQueryHandler : IRequestHandler 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 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 }; } + + /// + /// محاسبه تعداد اعضای یک شاخه (چپ یا راست) به صورت بازگشتی + /// + private async Task 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 برای خود فرزند + } + + /// + /// محاسبه حداکثر عمق شبکه + /// + private async Task GetMaxNetworkDepthAsync(long userId, CancellationToken cancellationToken) + { + var maxDepth = 0; + var currentLevelIds = new List { 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; + } + + /// + /// دریافت تمام ID های زیرمجموعه یک کاربر + /// + private async Task> GetAllDescendantIdsAsync(long userId, CancellationToken cancellationToken) + { + var allDescendants = new List(); + var currentLevelIds = new List { 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; + } } diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/UserNetworkPositionDto.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/UserNetworkPositionDto.cs index d3a292b..358d1a8 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/UserNetworkPositionDto.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/UserNetworkPositionDto.cs @@ -5,15 +5,62 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetUserNetwork /// 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; } } diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 2c8ca54..ea9d226 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.147 + 0.0.148 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto index be9a22a..81a8c7e 100644 --- a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto @@ -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 diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs index 73d4c2a..19d13d3 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs @@ -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() - .MapWith(src => ConvertTreeToResponse(src)); + .MapWith(src => ConvertTreeToResponse(src)); + + + config.NewConfig() + .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)