diff --git a/Dockerfile b/Dockerfile index 68dcb91..633a367 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY src/NuGet.config ./ # Copy solution and project files COPY src/ ./ -# Restore with Nexus config and publish +# Restore with Nexus config and publish RUN dotnet restore "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" --configfile NuGet.config RUN dotnet publish "CMSMicroservice.WebApi/CMSMicroservice.WebApi.csproj" -c Release -o /app/publish --no-restore diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs index 5621355..8e2dc3d 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs @@ -35,17 +35,28 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler !x.IsDeleted); - // Get direct left and right children var leftChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left); var rightChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right); - // Count all descendants in left and right subtrees - var leftLegCount = leftChild != null ? GetAllDescendants(leftChild.Id, allUsers).Count + 1 : 0; // +1 for leftChild itself - var rightLegCount = rightChild != null ? GetAllDescendants(rightChild.Id, allUsers).Count + 1 : 0; // +1 for rightChild itself + var leftLegUserIds = leftChild != null ? GetSubtreeUserIds(leftChild.Id, allUsers) : new HashSet(); + var rightLegUserIds = rightChild != null ? GetSubtreeUserIds(rightChild.Id, allUsers) : new HashSet(); + var allLegUserIds = leftLegUserIds.Union(rightLegUserIds).ToList(); + + // سازمان چپ/راست = فقط اعضای فعال باشگاه در هر پا + var activeClubUserIds = allLegUserIds.Count == 0 + ? new HashSet() + : (await _context.ClubMemberships + .AsNoTracking() + .Where(cm => cm.IsActive && !cm.IsDeleted && allLegUserIds.Contains(cm.UserId)) + .Select(cm => cm.UserId) + .ToListAsync(cancellationToken)) + .ToHashSet(); + + var leftLegCount = leftLegUserIds.Count(id => activeClubUserIds.Contains(id)); + var rightLegCount = rightLegUserIds.Count(id => activeClubUserIds.Contains(id)); + var totalMembers = leftLegCount + rightLegCount; + var activeMembers = totalMembers; double leftPercentage = totalMembers > 0 ? (leftLegCount / (double)totalMembers) * 100 : 0; double rightPercentage = totalMembers > 0 ? (rightLegCount / (double)totalMembers) * 100 : 0; @@ -126,7 +137,7 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler private async Task<(int GoldLeft, int GoldRight, int SilverLeft, int SilverRight)> LoadPackageHolderCountsByLegAsync( - User? leftChild, - User? rightChild, - List allUsers, + HashSet leftLegUserIds, + HashSet rightLegUserIds, CancellationToken cancellationToken) { - var leftLegUserIds = leftChild != null ? GetSubtreeUserIds(leftChild.Id, allUsers) : new HashSet(); - var rightLegUserIds = rightChild != null ? GetSubtreeUserIds(rightChild.Id, allUsers) : new HashSet(); - if (leftLegUserIds.Count == 0 && rightLegUserIds.Count == 0) return (0, 0, 0, 0); diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsResponseDto.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsResponseDto.cs index 19e0dc6..1f52052 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsResponseDto.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsResponseDto.cs @@ -2,9 +2,13 @@ namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStat public class GetNetworkStatisticsResponseDto { + /// مجموع اعضای فعال باشگاه در پای چپ و راست (= LeftLegCount + RightLegCount) public int TotalMembers { get; set; } + /// همان TotalMembers — اعضای فعال باشگاه در شبکه کاربر public int ActiveMembers { get; set; } + /// تعداد اعضای فعال باشگاه در زیردرخت پای چپ public int LeftLegCount { get; set; } + /// تعداد اعضای فعال باشگاه در زیردرخت پای راست public int RightLegCount { get; set; } public double LeftPercentage { get; set; } public double RightPercentage { get; set; } diff --git a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto index 3170492..9030461 100644 --- a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto @@ -315,6 +315,7 @@ message GetMyNetworkTreeResponse // GetMyNetworkStatistics - for frontend customer display message GetMyNetworkStatisticsResponse { + // اعضای فعال باشگاه: کل = چپ + راست int32 total_members = 1; int32 active_members = 2; int32 left_leg_count = 3; diff --git a/src/dbbkup/11-Diagnose_PackageHolderCounts_ByLeg.sql b/src/dbbkup/11-Diagnose_PackageHolderCounts_ByLeg.sql index 2d01f66..284ace3 100644 --- a/src/dbbkup/11-Diagnose_PackageHolderCounts_ByLeg.sql +++ b/src/dbbkup/11-Diagnose_PackageHolderCounts_ByLeg.sql @@ -1,6 +1,5 @@ -- ============================================= --- Diagnose: Package holder counts per leg (read-only) --- Compares OLD source (NetworkWeeklyBalances) vs NEW (active members + current cycle) +-- Diagnose: Leg counts — all subtree vs active-only vs gold/silver -- Usage: SET @RootUserId to the tree owner UserId -- ============================================= @@ -44,6 +43,18 @@ Subtree AS ( FROM Subtree s INNER JOIN CMS.Users u ON u.NetworkParentId = s.UserId AND u.IsDeleted = 0 ), +AllSubtreeCounts AS ( + SELECT LegSide, COUNT(DISTINCT UserId) AS Cnt + FROM Subtree + GROUP BY LegSide +), +ActiveClubCounts AS ( + SELECT s.LegSide, COUNT(DISTINCT s.UserId) AS Cnt + FROM Subtree s + INNER JOIN CMS.ClubMemberships cm + ON cm.UserId = s.UserId AND cm.IsActive = 1 AND cm.IsDeleted = 0 + GROUP BY s.LegSide +), ActivePackageHolders AS ( SELECT s.LegSide, @@ -53,25 +64,27 @@ ActivePackageHolders AS ( INNER JOIN CMS.ClubMemberships cm ON cm.UserId = s.UserId AND cm.IsActive = 1 AND cm.IsDeleted = 0 INNER JOIN CMS.ClubMembershipCycles cc ON cc.UserId = s.UserId AND cc.IsCurrentCycle = 1 GROUP BY s.LegSide, cc.PackageId -), -LatestNwb AS ( - SELECT PackageId, LeftLegTotal, RightLegTotal, WeekDefinitionId, - ROW_NUMBER() OVER (PARTITION BY PackageId ORDER BY WeekDefinitionId DESC) AS rn - FROM CMS.NetworkWeeklyBalances - WHERE UserId = @RootUserId - AND PackageId IN (@GoldPackageId, @SilverPackageId) ) SELECT @RootUserId AS RootUserId, - @GoldPackageId AS GoldPackageId, - @SilverPackageId AS SilverPackageId, - -- NEW: real holder counts - ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 0 AND PackageId = @GoldPackageId), 0) AS New_GoldLeft, - ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 1 AND PackageId = @GoldPackageId), 0) AS New_GoldRight, - ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 0 AND PackageId = @SilverPackageId), 0) AS New_SilverLeft, - ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 1 AND PackageId = @SilverPackageId), 0) AS New_SilverRight, - -- OLD: weekly balance scores (previous wrong source) - ISNULL((SELECT LeftLegTotal FROM LatestNwb WHERE PackageId = @GoldPackageId AND rn = 1), 0) AS Old_Nwb_GoldLeft, - ISNULL((SELECT RightLegTotal FROM LatestNwb WHERE PackageId = @GoldPackageId AND rn = 1), 0) AS Old_Nwb_GoldRight, - ISNULL((SELECT LeftLegTotal FROM LatestNwb WHERE PackageId = @SilverPackageId AND rn = 1), 0) AS Old_Nwb_SilverLeft, - ISNULL((SELECT RightLegTotal FROM LatestNwb WHERE PackageId = @SilverPackageId AND rn = 1), 0) AS Old_Nwb_SilverRight; + -- All subtree (old سازمان چپ/راست) + ISNULL((SELECT Cnt FROM AllSubtreeCounts WHERE LegSide = 0), 0) AS AllLeft, + ISNULL((SELECT Cnt FROM AllSubtreeCounts WHERE LegSide = 1), 0) AS AllRight, + -- Active club only (new سازمان چپ/راست / کل) + ISNULL((SELECT Cnt FROM ActiveClubCounts WHERE LegSide = 0), 0) AS ActiveLeft, + ISNULL((SELECT Cnt FROM ActiveClubCounts WHERE LegSide = 1), 0) AS ActiveRight, + ISNULL((SELECT Cnt FROM ActiveClubCounts WHERE LegSide = 0), 0) + + ISNULL((SELECT Cnt FROM ActiveClubCounts WHERE LegSide = 1), 0) AS ActiveTotal, + -- Gold / Silver package holders (active + current cycle) + ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 0 AND PackageId = @GoldPackageId), 0) AS GoldLeft, + ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 1 AND PackageId = @GoldPackageId), 0) AS GoldRight, + ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 0 AND PackageId = @SilverPackageId), 0) AS SilverLeft, + ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 1 AND PackageId = @SilverPackageId), 0) AS SilverRight, + -- Gaps: Active − (Gold+Silver) per leg + ISNULL((SELECT Cnt FROM ActiveClubCounts WHERE LegSide = 0), 0) + - ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 0 AND PackageId = @GoldPackageId), 0) + - ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 0 AND PackageId = @SilverPackageId), 0) AS GapLeft_ActiveMinusPackages, + ISNULL((SELECT Cnt FROM ActiveClubCounts WHERE LegSide = 1), 0) + - ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 1 AND PackageId = @GoldPackageId), 0) + - ISNULL((SELECT HolderCount FROM ActivePackageHolders WHERE LegSide = 1 AND PackageId = @SilverPackageId), 0) AS GapRight_ActiveMinusPackages +OPTION (MAXRECURSION 0);