Phase 5: Per-package commission + golden ref cleanup

- Fix golden/طلایی string refs in user-facing messages (ActivateClubMembership)
- Rename HasPurchasedGoldenPackage → HasPurchasedPackage (DTO, Handler, Proto, Mapping)
- ORM commission: per-user-package calculation via ClubMembership.LastPackageId
  - Build userPackageMap, per-user maxBalancesPerLeg/maxNetworkLevel
  - Carryover keyed by (UserId, PackageId) tuple
- SP commission: loop over packages, pass @PackageId/@InputMaxBalancesPerLeg/@InputMaxNetworkLevel
- sp_CalculateWeeklyBalances: accept 3 new params, filter by PackageId, insert PackageId column
This commit is contained in:
masoodafar-web
2026-02-26 00:57:20 +03:30
parent 0002a5a6f2
commit 607f791b65
8 changed files with 144 additions and 63 deletions
@@ -62,10 +62,20 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
.Select(w => 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<long, (int LeftLegRemainder, int RightLegRemainder)> 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<long, (int, int)>();
previousWeekCarryovers = new Dictionary<(long, long), (int, int)>();
}
var balancesList = new List<NetworkWeeklyBalance>();
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,