diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index b4e8e84..91a6e43 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -64,11 +64,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler w.UserId) .ToHashSetAsync(cancellationToken); - var activeClubMemberUserIds = await _context.ClubMemberships + // دریافت عضویت‌های فعال به همراه PackageId هر کاربر + var activeClubMemberships = await _context.ClubMemberships .Where(c => c.IsActive && !magicModeUserIds.Contains(c.UserId)) + .Select(c => new { c.UserId, PackageId = c.LastPackageId }) + .ToListAsync(cancellationToken); + + var activeClubMemberUserIds = activeClubMemberships .Select(c => c.UserId) - .ToHashSetAsync(cancellationToken); + .ToHashSet(); + + // نگاشت کاربر → PackageId + var userPackageMap = activeClubMemberships + .Where(c => c.PackageId.HasValue) + .ToDictionary(c => c.UserId, c => c.PackageId!.Value); // دریافت کاربران فعال در شبکه که عضو باشگاه هستند var usersInNetwork = await _context.Users @@ -73,43 +83,52 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy .Select(x => new { x.Id }) .ToListAsync(cancellationToken); - // دریافت باقیمانده‌های هفته قبل + // بارگذاری همه پکیج‌های فعال (bulk load) + var allPackages = await _context.Packages + .Where(p => !p.IsDeleted) + .ToDictionaryAsync(p => p.Id, cancellationToken); + + // پکیج پیش‌فرض (fallback برای کاربرانی که PackageId ندارند) + var defaultPackage = allPackages.Values + .FirstOrDefault(p => p.IsBasePackage) + ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); + + // دریافت باقیمانده‌های هفته قبل — شامل PackageId var previousWeekDefinitionId = GetPreviousWeekDefinitionId(weekDefinitionId); - Dictionary previousWeekCarryovers; + Dictionary<(long UserId, long PackageId), (int LeftLegRemainder, int RightLegRemainder)> previousWeekCarryovers; if (previousWeekDefinitionId.HasValue) { previousWeekCarryovers = await _context.NetworkWeeklyBalances .Where(x => x.WeekDefinitionId == previousWeekDefinitionId.Value) .ToDictionaryAsync( - x => x.UserId, + x => (x.UserId, x.PackageId), x => (x.LeftLegRemainder, x.RightLegRemainder), cancellationToken); } else { - previousWeekCarryovers = new Dictionary(); + previousWeekCarryovers = new Dictionary<(long, long), (int, int)>(); } var balancesList = new List(); var calculatedAt = DateTime.Now; - // بارگذاری پکیج پایه برای خواندن سقف‌ها - var package = await _context.Packages - .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) - ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); - var maxBalancesPerLeg = package.MaxBalancesPerLeg; - var maxNetworkLevel = package.MaxNetworkLevel; - foreach (var user in usersInNetwork.OrderBy(o => o.Id)) { - // دریافت باقیمانده هفته قبل + // تعیین پکیج هر کاربر (per-user) + var userPackageId = userPackageMap.GetValueOrDefault(user.Id, defaultPackage.Id); + var package = allPackages.GetValueOrDefault(userPackageId, defaultPackage); + var maxBalancesPerLeg = package.MaxBalancesPerLeg; + var maxNetworkLevel = package.MaxNetworkLevel; + // دریافت باقیمانده هفته قبل — فیلتر per-package var leftCarryover = 0; var rightCarryover = 0; - if (previousWeekCarryovers.ContainsKey(user.Id)) + var carryoverKey = (UserId: user.Id, PackageId: userPackageId); + if (previousWeekCarryovers.ContainsKey(carryoverKey)) { - leftCarryover = previousWeekCarryovers[user.Id].LeftLegRemainder; - rightCarryover = previousWeekCarryovers[user.Id].RightLegRemainder; + leftCarryover = previousWeekCarryovers[carryoverKey].LeftLegRemainder; + rightCarryover = previousWeekCarryovers[carryoverKey].RightLegRemainder; } // محاسبه تعداد اعضای جدید در این هفته (تا maxNetworkLevel لول پایین‌تر) @@ -166,15 +185,16 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy await _context.NetworkWeeklyBalances.AddRangeAsync(balancesList, cancellationToken); await _context.SaveChangesAsync(cancellationToken); - // محاسبه تعادل زیرمجموعه + // محاسبه تعادل زیرمجموعه — per-user maxNetworkLevel var balancesDictionary = balancesList.ToDictionary(x => x.UserId); foreach (var balance in balancesList) { + var balancePackage = allPackages.GetValueOrDefault(balance.PackageId, defaultPackage); var subordinateBalances = await CalculateSubordinateBalancesAsync( balance.UserId, balancesDictionary, - maxNetworkLevel, + balancePackage.MaxNetworkLevel, cancellationToken ); @@ -193,11 +213,6 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy bool forceRecalculate, CancellationToken cancellationToken = default) { - // بارگذاری پکیج پایه برای تعیین PackageId روی پرداخت‌ها - var package = await _context.Packages - .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) - ?? throw new InvalidOperationException("پکیج پایه یافت نشد"); - var weekDefinition = _weekRepository.GetGregorianWeekNumber(weekDefinitionId); if (weekDefinition == null) { @@ -293,7 +308,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy UserId = balance.UserId, WeekDefinitionId = weekDefinitionId, WeeklyPoolId = existingPool.Id, - PackageId = package.Id, + PackageId = balance.PackageId, // per-user PackageId از محاسبه تعادل BalancesEarned = userBalance, ValuePerBalance = valuePerBalance, TotalAmount = totalAmount, diff --git a/src/CMSMicroservice.Infrastructure/Services/Commission/StoredProcedureCommissionCalculationStrategy.cs b/src/CMSMicroservice.Infrastructure/Services/Commission/StoredProcedureCommissionCalculationStrategy.cs index 221537d..71ea602 100644 --- a/src/CMSMicroservice.Infrastructure/Services/Commission/StoredProcedureCommissionCalculationStrategy.cs +++ b/src/CMSMicroservice.Infrastructure/Services/Commission/StoredProcedureCommissionCalculationStrategy.cs @@ -6,6 +6,7 @@ namespace CMSMicroservice.Infrastructure.Services.Commission; /// /// پیاده‌سازی محاسبه کمیسیون با استفاده از Stored Procedure /// این روش برای داده‌های زیاد بهینه‌تر است و از CTE استفاده می‌کند +/// حلقه روی پکیج‌های فعال — هر پکیج با تنظیمات خودش /// public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculationStrategy { @@ -22,45 +23,81 @@ public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculati bool forceRecalculate, CancellationToken cancellationToken = default) { - // دسترسی به DbContext واقعی برای اجرای SP var dbContext = _context as DbContext; if (dbContext == null) { throw new InvalidOperationException("DbContext برای اجرای Stored Procedure در دسترس نیست"); } + // بارگذاری پکیج‌های فعال + var activePackages = await _context.Packages + .Where(p => !p.IsDeleted && p.IsActive) + .ToListAsync(cancellationToken); + + if (!activePackages.Any()) + { + throw new InvalidOperationException("هیچ پکیج فعالی یافت نشد"); + } + + var totalRowCount = 0; + var connection = dbContext.Database.GetDbConnection(); await connection.OpenAsync(cancellationToken); try { - using var command = connection.CreateCommand(); - command.CommandText = "CMS.sp_CalculateWeeklyBalances"; - command.CommandType = System.Data.CommandType.StoredProcedure; - command.CommandTimeout = 300; // 5 دقیقه timeout + // حلقه روی پکیج‌ها — هر پکیج با تنظیمات خودش + foreach (var package in activePackages) + { + using var command = connection.CreateCommand(); + command.CommandText = "CMS.sp_CalculateWeeklyBalances"; + command.CommandType = System.Data.CommandType.StoredProcedure; + command.CommandTimeout = 300; // 5 دقیقه timeout - // پارامترها - var paramWeekDefinitionId = command.CreateParameter(); - paramWeekDefinitionId.ParameterName = "@WeekDefinitionId"; - paramWeekDefinitionId.Value = weekDefinitionId; - command.Parameters.Add(paramWeekDefinitionId); + // پارامتر WeekDefinitionId + var paramWeekDefinitionId = command.CreateParameter(); + paramWeekDefinitionId.ParameterName = "@WeekDefinitionId"; + paramWeekDefinitionId.Value = weekDefinitionId; + command.Parameters.Add(paramWeekDefinitionId); - var paramForceRecalculate = command.CreateParameter(); - paramForceRecalculate.ParameterName = "@ForceRecalculate"; - paramForceRecalculate.Value = forceRecalculate; - command.Parameters.Add(paramForceRecalculate); + // پارامتر ForceRecalculate + var paramForceRecalculate = command.CreateParameter(); + paramForceRecalculate.ParameterName = "@ForceRecalculate"; + paramForceRecalculate.Value = forceRecalculate; + command.Parameters.Add(paramForceRecalculate); - // پارامتر خروجی - var paramRowCount = command.CreateParameter(); - paramRowCount.ParameterName = "@RowCount"; - paramRowCount.Direction = System.Data.ParameterDirection.Output; - paramRowCount.DbType = System.Data.DbType.Int32; - command.Parameters.Add(paramRowCount); + // پارامتر PackageId — per-package + var paramPackageId = command.CreateParameter(); + paramPackageId.ParameterName = "@PackageId"; + paramPackageId.Value = package.Id; + command.Parameters.Add(paramPackageId); - await command.ExecuteNonQueryAsync(cancellationToken); + // پارامتر MaxBalancesPerLeg — per-package + var paramMaxBalances = command.CreateParameter(); + paramMaxBalances.ParameterName = "@InputMaxBalancesPerLeg"; + paramMaxBalances.Value = package.MaxBalancesPerLeg; + command.Parameters.Add(paramMaxBalances); - var rowCount = (int)(paramRowCount.Value ?? 0); - return rowCount; + // پارامتر MaxNetworkLevel — per-package + var paramMaxLevel = command.CreateParameter(); + paramMaxLevel.ParameterName = "@InputMaxNetworkLevel"; + paramMaxLevel.Value = package.MaxNetworkLevel; + command.Parameters.Add(paramMaxLevel); + + // پارامتر خروجی + var paramRowCount = command.CreateParameter(); + paramRowCount.ParameterName = "@RowCount"; + paramRowCount.Direction = System.Data.ParameterDirection.Output; + paramRowCount.DbType = System.Data.DbType.Int32; + command.Parameters.Add(paramRowCount); + + await command.ExecuteNonQueryAsync(cancellationToken); + + var rowCount = (int)(paramRowCount.Value ?? 0); + totalRowCount += rowCount; + } + + return totalRowCount; } finally { diff --git a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto index be37c2a..45ca30b 100644 --- a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto @@ -153,7 +153,7 @@ message GetUserNetworkResponse 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; + bool has_purchased_package = 35; // آمار مالی double total_earned_commission = 36; diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs index 9035a68..e50fe94 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs @@ -56,7 +56,7 @@ public class NetworkMembershipProfile : IRegister .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.HasPurchasedPackage, src => src.HasPurchasedPackage) .Map(dest => dest.TotalEarnedCommission, src => (double)src.TotalEarnedCommission) .Map(dest => dest.TotalPaidCommission, src => (double)src.TotalPaidCommission) .Map(dest => dest.PendingCommission, src => (double)src.PendingCommission)