Phase 2: Replace all deprecated SystemConstants with Package entity reads

- ActivateClubMembership: Load Package, set First/Last fields, PackageId on Cycle+Pool
- AcceptClubMembershipContract: Load Package, set First/Last fields, PackageId on Pool
- CalculateWeeklyBalances: Load base package, use package.MaxBalancesPerLeg/MaxNetworkLevel, set PackageId on NetworkWeeklyBalance
- ProcessUserPayouts: Load base package, use package.MaxNetworkLevel, set PackageId on UserCommissionPayout
- CheckAndProcessDayaLoans: Load base package (IsBasePackage), replace DayaLoanAmount→package.Price, fix hardcoded p.Id==4
- CreateManualPayment: Load base package, replace BasePackageAmount→package.Price, use DiscountMultiplier
- InitiateBasePackagePayment: Remove hardcoded BasePackageId=4, load from IsBasePackage, use package.Price/Id
- VerifyBasePackagePayment: Load Package from order.PackageId, use package.Price+DiscountMultiplier
- ChargeMagicWallet: Load user's package from current cycle, use package.MagicWalletMaxDeposit
- VerifyMagicWalletCharge: Load user's package, use package.MagicWalletMultiplier/MagicWalletMaxDeposit
- OrmCommissionCalculationStrategy: Load base package in both methods, set PackageId on Balance+Payout
- ConfigurationService: Inject IApplicationDbContext, read package values from DB instead of SystemConstants
- UserOrderService: Load user's package for EXIT trigger MagicWalletMaxDeposit check
- UserWalletService: Load user's package for GetMagicWalletStatus display

All 9 deprecated SystemConstants now have ZERO usages outside SystemConstants.cs
Build: 0 errors, 521 warnings (all pre-existing)
This commit is contained in:
masoodafar-web
2026-02-25 23:25:18 +03:30
parent a9cd2fd67a
commit 8e5c7c5205
14 changed files with 239 additions and 120 deletions
@@ -135,13 +135,25 @@ public class AcceptClubMembershipContractCommandHandler
};
await _context.UserContracts.AddAsync(userContract, cancellationToken);
// 6. دریافت مقادیر از SystemConstants (استاتیک)
long giftValue = SystemConstants.ClubMembershipGiftValue;
long activationFeeValue = SystemConstants.ClubActivationFee;
// 6. بارگذاری پکیج از آخرین خرید کاربر یا پکیج پایه
var latestPurchase = await _context.UserPackagePurchases
.Where(p => p.UserId == userId)
.OrderByDescending(p => p.PurchasedAt)
.FirstOrDefaultAsync(cancellationToken);
var package = latestPurchase != null
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == latestPurchase.PackageId && !p.IsDeleted, cancellationToken)
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);
if (package == null)
throw new NotFoundException("پکیج یافت نشد");
long giftValue = package.ActivationFee;
long activationFeeValue = package.ActivationFee;
_logger.LogInformation(
"Using Club.MembershipGiftValue: {GiftValue}, Club.ActivationFee: {ActivationFee}",
giftValue, activationFeeValue
"Using Package {PackageId} ({PackageName}): GiftValue={GiftValue}, ActivationFee={ActivationFee}",
package.Id, package.Title, giftValue, activationFeeValue
);
// 7. فعال‌سازی باشگاه مشتریان
@@ -156,6 +168,10 @@ public class AcceptClubMembershipContractCommandHandler
UserId = user.Id,
IsActive = true,
ActivatedAt = activationDate,
FirstActivationDate = activationDate,
FirstPackageId = package.Id,
LastActivationDate = activationDate,
LastPackageId = package.Id,
InitialContribution = activationFeeValue,
GiftValue = giftValue,
TotalEarned = 0,
@@ -175,6 +191,8 @@ public class AcceptClubMembershipContractCommandHandler
clubMembership = user.ClubMembership!;
clubMembership.IsActive = true;
clubMembership.ActivatedAt = activationDate;
clubMembership.LastActivationDate = activationDate;
clubMembership.LastPackageId = package.Id;
clubMembership.PurchaseMethod = user.PackagePurchaseMethod;
_context.ClubMemberships.Update(clubMembership);
@@ -206,13 +224,14 @@ public class AcceptClubMembershipContractCommandHandler
// 9. اضافه کردن مبلغ به Pool هفته جاری
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
var weeklyPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken);
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
if (weeklyPool == null)
{
weeklyPool = new WeeklyCommissionPool
{
WeekDefinitionId = currentWeekDefinitionId,
PackageId = package.Id,
TotalPoolAmount = activationFeeValue,
TotalBalances = 0,
ValuePerBalance = 0,
@@ -54,6 +54,9 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
throw new NotFoundException(nameof(User), request.UserId);
}
// متغیر پکیج — در هر دو مسیر (عادی و Force) مقداردهی می‌شود
Package package;
// 2-5: بررسی‌های مالی — در حالت ForceActivation (ادمین) رد می‌شود
if (!request.ForceActivation)
{
@@ -79,17 +82,8 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
throw new NotFoundException("کیف پول کاربر یافت نشد");
}
if (wallet.Balance < SystemConstants.BasePackageAmount)
{
_logger.LogWarning(
"User {UserId} has insufficient balance: {Balance}",
request.UserId,
wallet.Balance
);
throw new BadRequestException(
$"برای فعالسازی باشگاه مشتریان باید حداقل {SystemConstants.BasePackageAmount:N0} ریال موجودی اصلی داشته باشید"
);
}
// NOTE: balance check uses package.Price — loaded after finding order (step 4)
// Moved to after package loading
// 3.5. بررسی وضعیت کیف‌پول جادویی — اگر در حالت Magic است، فعالسازی مجاز نیست
if (wallet.WalletMode == WalletMode.Magic)
@@ -146,6 +140,19 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
"تراکنش معتبر برای فعالسازی باشگاه یافت نشد"
);
}
// 5.5. بارگذاری پکیج از سفارش
package = await _context.Packages
.FirstOrDefaultAsync(p => p.Id == packageOrder.PackageId && !p.IsDeleted, cancellationToken)
?? throw new NotFoundException("پکیج یافت نشد");
// بررسی موجودی با مبلغ پکیج واقعی
if (wallet.Balance < package.Price)
{
throw new BadRequestException(
$"برای فعالسازی باشگاه مشتریان باید حداقل {package.Price:N0} ریال موجودی اصلی داشته باشید"
);
}
}
else
{
@@ -161,19 +168,24 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
_context.Users.Update(user);
await _context.SaveChangesAsync(cancellationToken);
}
// بارگذاری پکیج پایه برای فعال‌سازی اجباری
package = await _context.Packages
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
?? throw new NotFoundException("پکیج پایه یافت نشد");
}
// 6. بررسی عضویت فعلی
var existingMembership = await _context.ClubMemberships
.FirstOrDefaultAsync(c => c.UserId == user.Id, cancellationToken);
// 6.1. دریافت مبلغ هدیه و هزینه فعالسازی از SystemConstants
long giftValue = SystemConstants.ClubMembershipGiftValue;
long activationFeeValue = SystemConstants.ClubActivationFee;
// 6.1. دریافت مبلغ هدیه و هزینه فعالسازی از پکیج
long giftValue = package.ActivationFee;
long activationFeeValue = package.ActivationFee;
_logger.LogInformation(
"Using Club.MembershipGiftValue: {GiftValue}, Club.ActivationFee: {ActivationFee}",
giftValue, activationFeeValue
"Using Package {PackageId} ({PackageName}): GiftValue={GiftValue}, ActivationFee={ActivationFee}, Price={Price}",
package.Id, package.Title, giftValue, activationFeeValue, package.Price
);
ClubMembership entity;
@@ -188,8 +200,12 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
UserId = user.Id,
IsActive = true,
ActivatedAt = activationDate,
InitialContribution =activationFeeValue,
GiftValue = giftValue, // مقدار از تنظیمات
FirstActivationDate = activationDate,
FirstPackageId = package.Id,
LastActivationDate = activationDate,
LastPackageId = package.Id,
InitialContribution = activationFeeValue,
GiftValue = giftValue,
TotalEarned = 0,
PurchaseMethod = user.PackagePurchaseMethod
};
@@ -214,9 +230,11 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
return true;
}
// فعال‌سازی مجدد — ActivatedAt حفظ میشه (overwrite نمیشه)
// فعال‌سازی مجدد — FirstActivation حفظ میشه (Q21), Last بروزرسانی میشه
entity = existingMembership;
entity.IsActive = true;
entity.LastActivationDate = activationDate;
entity.LastPackageId = package.Id;
entity.PurchaseMethod = user.PackagePurchaseMethod;
_context.ClubMemberships.Update(entity);
@@ -250,7 +268,8 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
CycleNumber = maxCycleNumber + 1,
PackagePurchasedAt = activationDate,
PurchaseMethod = user.PackagePurchaseMethod,
PackageAmount = SystemConstants.BasePackageAmount,
PackageAmount = package.Price,
PackageId = package.Id,
IsCurrentCycle = true
};
@@ -284,15 +303,16 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
var weeklyPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId, cancellationToken);
.FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);
if (weeklyPool == null)
{
// ایجاد Pool جدید برای این هفته
// ایجاد Pool جدید برای این هفته × این پکیج
weeklyPool = new WeeklyCommissionPool
{
WeekDefinitionId = currentWeekDefinitionId,
TotalPoolAmount = activationFeeValue, // مبلغ هدیه به Pool اضافه میشه
PackageId = package.Id,
TotalPoolAmount = activationFeeValue,
TotalBalances = 0, // در CalculateWeeklyBalances محاسبه میشه
ValuePerBalance = 0, // در CalculateWeeklyCommissionPool محاسبه میشه
IsCalculated = false,
@@ -82,11 +82,14 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
var balancesList = new List<NetworkWeeklyBalance>();
var calculatedAt = DateTime.Now;
// استفاده از SystemConstants (استاتیک - بدون کوئری به دیتابیس)
// سقف تعادل هفتگی برای هر دست (نه کل) - 300 برای چپ + 300 برای راست = حداکثر 600 تعادل
var maxBalancesPerLeg = SystemConstants.CommissionMaxWeeklyBalancesPerLeg;
// حداکثر عمق شبکه برای شمارش اعضا (15 لول)
var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel;
// بارگذاری پکیج پایه برای خواندن سقف‌ها
var package = await _context.Packages
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
// خواندن سقف‌ها از پکیج (قبلاً از SystemConstants بود)
var maxBalancesPerLeg = package.MaxBalancesPerLeg;
var maxNetworkLevel = package.MaxNetworkLevel;
foreach (var user in usersInNetwork.OrderBy(o=>o.Id))
{
@@ -140,6 +143,7 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
{
UserId = user.Id,
WeekDefinitionId = request.WeekDefinitionId,
PackageId = package.Id,
// اطلاعات جدید
LeftLegNewMembers = leftNewMembers,
@@ -49,8 +49,11 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
await _context.SaveChangesAsync(cancellationToken);
}
// ⭐ خواندن MaxNetworkLevel از SystemConstants (استاتیک)
var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel;
// بارگذاری پکیج پایه برای خواندن سقف‌ها
var package = await _context.Packages
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
var maxNetworkLevel = package.MaxNetworkLevel;
// دریافت همه تعادل‌های هفتگی (شامل صفرها هم برای محاسبه زیرمجموعه)
var allWeeklyBalances = await _context.NetworkWeeklyBalances
@@ -113,6 +116,7 @@ public class ProcessUserPayoutsCommandHandler : IRequestHandler<ProcessUserPayou
UserId = userId,
WeekDefinitionId = request.WeekDefinitionId,
WeeklyPoolId = pool.Id,
PackageId = package.Id,
BalancesEarned = totalBalancesWithSubordinates, // ⭐ شامل زیرمجموعه
ValuePerBalance = pool.ValuePerBalance,
TotalAmount = totalAmount,
@@ -186,10 +186,15 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
string contractNumber,
CancellationToken cancellationToken)
{
// بارگذاری پکیج پایه
var package = await _context.Packages
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
?? throw new InvalidOperationException("پکیج پایه یافت نشد");
// 1. ایجاد تراکنش
var transaction = new Transaction
{
Amount = SystemConstants.DayaLoanAmount,
Amount = package.Price,
Description = $"دریافت اعتبار دایا - قرارداد {contractNumber}",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.Now,
@@ -216,13 +221,13 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
}
// 3. شارژ کیف پول عادی
wallet.Balance += SystemConstants.DayaLoanAmount;
wallet.Balance += package.Price;
var mainLog = new UserWalletChangeLog
{
WalletId = wallet.Id,
CurrentBalance = 0,
ChangeValue = SystemConstants.DayaLoanAmount,
ChangeValue = package.Price,
CurrentNetworkBalance = wallet.NetworkBalance,
ChangeNerworkValue = 0,
IsIncrease = true,
@@ -230,8 +235,8 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
};
await _context.UserWalletChangeLogs.AddAsync(mainLog, cancellationToken);
// 4. شارژ کیف پول تخفیف (دو برابر)
var discountAmount = SystemConstants.DayaLoanAmount * 2;
// 4. شارژ کیف پول تخفیف
var discountAmount = (long)(package.Price * package.DiscountMultiplier);
wallet.DiscountBalance += discountAmount;
var discountLog = new UserWalletChangeLog
@@ -253,27 +258,18 @@ public class CheckAndProcessDayaLoansCommandHandler : IRequestHandler<CheckAndPr
user.DayaCreditReceivedAt = DateTime.Now;
user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan;
// 6. ثبت Order پکیج پایه
var goldenPackage = await _context.Packages
.FirstOrDefaultAsync(p => p.Id == 4, cancellationToken);
if (goldenPackage != null)
// 6. ثبت UserPackagePurchase برای پکیج پایه
var packagePurchase = new UserPackagePurchase
{
// 6. ثبت UserPackagePurchase برای پکیج پایه
var goldenPackageId =goldenPackage.Id;
var packagePurchase = new UserPackagePurchase
{
UserId = user.Id,
PackageId = goldenPackageId,
PurchaseMethod = PackagePurchaseMethod.DayaLoan,
PurchasedAt = DateTime.Now,
Amount = SystemConstants.DayaLoanAmount,
TransactionId = transaction.Id
};
UserId = user.Id,
PackageId = package.Id,
PurchaseMethod = PackagePurchaseMethod.DayaLoan,
PurchasedAt = DateTime.Now,
Amount = package.Price,
TransactionId = transaction.Id
};
await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken);
}
await _context.UserPackagePurchases.AddAsync(packagePurchase, cancellationToken);
// 7. Domain Event
user.AddDomainEvent(new DayaLoanApprovedEvent(user, transaction, contractNumber));
@@ -70,9 +70,13 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
throw new NotFoundException($"کیف پول کاربر {request.UserId} یافت نشد");
}
// 4. محاسبه مبالغ
var balanceAmount = SystemConstants.BasePackageAmount; // 56M
var discountBalanceAmount = SystemConstants.BasePackageAmount * 2; // 112M
// 4. بارگذاری پکیج پایه و محاسبه مبالغ
var package = await _context.Packages
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
?? throw new NotFoundException("پکیج پایه یافت نشد");
var balanceAmount = package.Price;
var discountBalanceAmount = (long)(package.Price * package.DiscountMultiplier);
// 5. ثبت تراکنش
@@ -20,10 +20,7 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler<Initiate
private readonly IApplicationDbContext _context;
private readonly ILogger<InitiateBasePackagePaymentCommandHandler> _logger;
/// <summary>
/// شناسه پکیج پایه در دیتابیس
/// </summary>
private const long BasePackageId = 4;
// شناسه پکیج پایه از دیتابیس خوانده می‌شود (Package.IsBasePackage)
public InitiateBasePackagePaymentCommandHandler(
IApplicationDbContext context,
@@ -68,12 +65,17 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler<Initiate
};
}
// 2.5. بارگذاری پکیج پایه
var package = await _context.Packages
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
?? throw new NotFoundException("پکیج پایه یافت نشد");
// 3. بررسی عدم وجود سفارش Pending
var pendingOrder = await _context.UserOrders
.FirstOrDefaultAsync(o =>
o.UserId == request.UserId &&
o.PaymentStatus == PaymentStatus.Pending &&
o.PackageId == BasePackageId,
o.PackageId == package.Id,
cancellationToken);
if (pendingOrder != null)
@@ -93,7 +95,7 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler<Initiate
Message = "سفارش قبلی در انتظار پرداخت یافت شد.",
OrderId = pendingOrder.Id,
TransactionId = existingTransaction?.Id ?? 0,
Amount = SystemConstants.BasePackageAmount
Amount = package.Price
};
}
@@ -112,8 +114,8 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler<Initiate
// 5. ایجاد Transaction با وضعیت Pending
var transaction = new Transaction
{
Amount = SystemConstants.BasePackageAmount,
Description = $"خرید پکیج پایه ۵۶ میلیونی - کاربر #{user.Id}",
Amount = package.Price,
Description = $"خرید پکیج {package.Title} - کاربر #{user.Id}",
PaymentStatus = PaymentStatus.Pending,
Type = TransactionType.DepositIpg
};
@@ -130,8 +132,8 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler<Initiate
var order = new UserOrder
{
UserId = user.Id,
PackageId = BasePackageId,
Amount = SystemConstants.BasePackageAmount,
PackageId = package.Id,
Amount = package.Price,
PaymentStatus = PaymentStatus.Pending,
DeliveryStatus = DeliveryStatus.None,
UserAddressId = defaultAddress.Id,
@@ -154,7 +156,7 @@ public class InitiateBasePackagePaymentCommandHandler : IRequestHandler<Initiate
Message = "تراکنش و سفارش با موفقیت ثبت شد.",
OrderId = order.Id,
TransactionId = transaction.Id,
Amount = SystemConstants.BasePackageAmount
Amount = package.Price
};
}
catch (Exception ex)
@@ -128,12 +128,20 @@ public class VerifyBasePackagePaymentCommandHandler : IRequestHandler<VerifyBase
await _context.SaveChangesAsync(cancellationToken);
}
// 5.5. بارگذاری پکیج از سفارش
var package = order.PackageId.HasValue
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == order.PackageId.Value, cancellationToken)
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);
if (package == null)
throw new NotFoundException("پکیج یافت نشد");
// 6. شارژ کیف پول (هم Balance و هم DiscountBalance)
var oldBalance = userWallet.Balance;
var oldDiscountBalance = userWallet.DiscountBalance;
userWallet.Balance += SystemConstants.BasePackageAmount;
userWallet.DiscountBalance += SystemConstants.BasePackageAmount * 2;
userWallet.Balance += package.Price;
userWallet.DiscountBalance += (long)(package.Price * package.DiscountMultiplier);
_logger.LogInformation(
"Charging wallet for user {UserId}. Balance: {OldBalance} -> {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}",
@@ -153,11 +161,11 @@ public class VerifyBasePackagePaymentCommandHandler : IRequestHandler<VerifyBase
{
WalletId = userWallet.Id,
CurrentBalance = userWallet.Balance,
ChangeValue = SystemConstants.BasePackageAmount,
ChangeValue = package.Price,
CurrentNetworkBalance = userWallet.NetworkBalance,
ChangeNerworkValue = 0,
CurrentDiscountBalance = userWallet.DiscountBalance,
ChangeDiscountValue = SystemConstants.BasePackageAmount * 2,
ChangeDiscountValue = (long)(package.Price * package.DiscountMultiplier),
IsIncrease = true,
RefrenceId = transaction.Id
};
@@ -173,7 +181,7 @@ public class VerifyBasePackagePaymentCommandHandler : IRequestHandler<VerifyBase
PackageId = order.PackageId.Value,
PurchaseMethod = PackagePurchaseMethod.DirectPurchase,
PurchasedAt = DateTime.Now,
Amount = SystemConstants.BasePackageAmount,
Amount = package.Price,
OrderId = order.Id,
TransactionId = transaction.Id
};
@@ -76,8 +76,17 @@ public class ChargeMagicWalletCommandHandler
);
}
// 3.5. بارگذاری پکیج کاربر برای سقف کیف‌پول جادویی
var currentCycle = await _context.ClubMembershipCycles
.FirstOrDefaultAsync(c => c.UserId == request.UserId && c.IsCurrentCycle, cancellationToken);
var package = currentCycle != null
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, cancellationToken)
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);
if (package == null)
throw new NotFoundException("پکیج یافت نشد");
// 4. بررسی سقف واریزی (per-cycle)
var remainingDeposit = SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited;
var remainingDeposit = package.MagicWalletMaxDeposit - wallet.MagicTotalDeposited;
if (remainingDeposit <= 0)
{
@@ -121,8 +121,17 @@ public class VerifyMagicWalletChargeCommandHandler
throw new BadRequestException("کیف‌پول در حالت جادویی نیست");
}
// 5. محاسبه اعتبار ×2.5
var creditAmount = (long)(depositAmount * SystemConstants.MagicWalletMultiplier); // مبلغ × 2.5
// 4.5. بارگذاری پکیج کاربر برای ضریب جادویی
var currentCycle = await _context.ClubMembershipCycles
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, cancellationToken);
var package = currentCycle != null
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, cancellationToken)
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);
if (package == null)
throw new NotFoundException("پکیج یافت نشد");
// 5. محاسبه اعتبار
var creditAmount = (long)(depositAmount * package.MagicWalletMultiplier); // مبلغ × 2.5
var bonusAmount = creditAmount - depositAmount; // بونوس = مبلغ × 1.5
// 6. ثبت تراکنش واریز واقعی (MagicWalletDeposit)
@@ -143,7 +152,7 @@ public class VerifyMagicWalletChargeCommandHandler
var bonusTransaction = new Transaction
{
Amount = bonusAmount,
Description = $"شارژ کیف‌پول جادویی - بونوس ×{SystemConstants.MagicWalletMultiplier - 1} - کاربر {userId}",
Description = $"شارژ کیف‌پول جادویی - بونوس ×{package.MagicWalletMultiplier - 1} - کاربر {userId}",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.UtcNow,
RefId = $"MAGIC_BONUS_{depositTransaction.Id}",
@@ -186,10 +195,10 @@ public class VerifyMagicWalletChargeCommandHandler
userId,
depositAmount,
creditAmount,
SystemConstants.MagicWalletMultiplier,
package.MagicWalletMultiplier,
bonusAmount,
wallet.MagicTotalDeposited,
SystemConstants.MagicWalletMaxDeposit,
package.MagicWalletMaxDeposit,
wallet.Balance
);
@@ -94,9 +94,12 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
var balancesList = new List<NetworkWeeklyBalance>();
var calculatedAt = DateTime.Now;
// استفاده از SystemConstants به جای دیتابیس
var maxBalancesPerLeg = SystemConstants.CommissionMaxWeeklyBalancesPerLeg;
var maxNetworkLevel = SystemConstants.CommissionMaxNetworkLevel;
// بارگذاری پکیج پایه برای خواندن سقف‌ها
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))
{
@@ -135,6 +138,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
{
UserId = user.Id,
WeekDefinitionId = weekDefinitionId,
PackageId = package.Id,
LeftLegNewMembers = leftNewMembers,
RightLegNewMembers = rightNewMembers,
LeftLegCarryover = leftCarryover,
@@ -189,6 +193,11 @@ 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)
{
@@ -284,6 +293,7 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
UserId = balance.UserId,
WeekDefinitionId = weekDefinitionId,
WeeklyPoolId = existingPool.Id,
PackageId = package.Id,
BalancesEarned = userBalance,
ValuePerBalance = valuePerBalance,
TotalAmount = totalAmount,
@@ -2,37 +2,42 @@ using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
using CMSMicroservice.Application.Common.Authorization;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Protobuf.Protos.Configuration;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.WebApi.Services;
/// <summary>
/// سرویس تنظیمات سیستم - خواندن از SystemConstants
/// سرویس تنظیمات سیستم — مقادیر پکیجی از Package entity خوانده می‌شوند
/// </summary>
public class ConfigurationService : ConfigurationContract.ConfigurationContractBase
{
private readonly ILogger<ConfigurationService> _logger;
private readonly ICurrentUserService _currentUserService;
private readonly IMediator _mediator;
private readonly IApplicationDbContext _context;
public ConfigurationService(
ILogger<ConfigurationService> logger,
ICurrentUserService currentUserService,
IMediator mediator)
IMediator mediator,
IApplicationDbContext context)
{
_logger = logger;
_currentUserService = currentUserService;
_mediator = mediator;
_context = context;
}
/// <summary>
/// دریافت تنظیمات با کلید خاص
/// </summary>
public override Task<GetConfigurationByKeyResponse> GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context)
public override async Task<GetConfigurationByKeyResponse> GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context)
{
var response = new GetConfigurationByKeyResponse
{
@@ -43,30 +48,33 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
LastModified = Timestamp.FromDateTime(DateTime.UtcNow)
};
// خواندن مقدار از SystemConstants بر اساس کلید
response.Value = GetConfigurationValue(request.Key);
// برای کلیدهای پکیجی، از دیتابیس می‌خوانیم
var package = await GetBasePackageAsync(context.CancellationToken);
response.Value = GetConfigurationValue(request.Key, package);
response.Description = GetConfigurationDescription(request.Key);
_logger.LogDebug("Configuration requested: Key={Key}, Value={Value}", request.Key, response.Value);
return Task.FromResult(response);
return response;
}
/// <summary>
/// دریافت تنظیمات باشگاه مشتریان
/// </summary>
public override Task<GetClubConfigurationResponse> GetClubConfiguration(Empty request, ServerCallContext context)
public override async Task<GetClubConfigurationResponse> GetClubConfiguration(Empty request, ServerCallContext context)
{
var package = await GetBasePackageAsync(context.CancellationToken);
var response = new GetClubConfigurationResponse
{
ActivationFee = SystemConstants.ClubActivationFee,
MembershipGiftValue = SystemConstants.ClubMembershipGiftValue
ActivationFee = package.ActivationFee,
MembershipGiftValue = package.ActivationFee
};
_logger.LogDebug("Club configuration requested: ActivationFee={ActivationFee}, GiftValue={GiftValue}",
response.ActivationFee, response.MembershipGiftValue);
return Task.FromResult(response);
return response;
}
/// <summary>
@@ -111,18 +119,19 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
/// دریافت تمام تنظیمات
/// </summary>
[RequiresPermission(PermissionNames.SettingsView)]
public override Task<GetAllConfigurationsResponse> GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context)
public override async Task<GetAllConfigurationsResponse> GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context)
{
var package = await GetBasePackageAsync(context.CancellationToken);
var response = new GetAllConfigurationsResponse();
// Club Settings
response.Models.Add(CreateConfigModel("Club.ActivationFee", SystemConstants.ClubActivationFee.ToString(), "هزینه فعال‌سازی عضویت باشگاه", 2));
response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", SystemConstants.ClubMembershipGiftValue.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2));
// Club Settings (از Package)
response.Models.Add(CreateConfigModel("Club.ActivationFee", package.ActivationFee.ToString(), "هزینه فعال‌سازی عضویت باشگاه", 2));
response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", package.ActivationFee.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2));
// Commission Settings
// Commission Settings (سقف‌ها از Package)
response.Models.Add(CreateConfigModel("Commission.MinWithdrawalAmount", SystemConstants.CommissionMinWithdrawalAmount.ToString(), "حداقل مبلغ برداشت", 3));
response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", SystemConstants.CommissionMaxWeeklyBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3));
response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", SystemConstants.CommissionMaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3));
response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", package.MaxBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3));
response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", package.MaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3));
response.Models.Add(CreateConfigModel("Commission.CashWithdrawalEnabled", SystemConstants.CommissionCashWithdrawalEnabled.ToString(), "امکان برداشت نقدی", 3));
response.Models.Add(CreateConfigModel("Commission.CalculationStrategy", SystemConstants.CommissionCalculationStrategy, "روش محاسبه کمیسیون", 3));
@@ -130,9 +139,9 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
response.Models.Add(CreateConfigModel("Network.AllowOrphanNodes", SystemConstants.NetworkAllowOrphanNodes.ToString(), "اجازه حذف والدین با فرزند", 1));
response.Models.Add(CreateConfigModel("Network.MaxChildrenPerLeg", SystemConstants.NetworkMaxChildrenPerLeg.ToString(), "حداکثر تعداد فرزند مستقیم در هر پا", 1));
// Package Settings
response.Models.Add(CreateConfigModel("Package.BasePackageAmount", SystemConstants.BasePackageAmount.ToString(), "مبلغ پکیج پایه", 0));
response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", SystemConstants.DayaLoanAmount.ToString(), "مبلغ وام دایا", 0));
// Package Settings (از Package)
response.Models.Add(CreateConfigModel("Package.BasePackageAmount", package.Price.ToString(), "مبلغ پکیج پایه", 0));
response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", package.Price.ToString(), "مبلغ وام دایا", 0));
// System Settings
response.Models.Add(CreateConfigModel("System.MaintenanceMode", SystemConstants.SystemMaintenanceMode.ToString(), "حالت تعمیر و نگهداری", 0));
@@ -141,7 +150,7 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
// Shop Settings
response.Models.Add(CreateConfigModel("Shop.VAT", SystemConstants.ShopVAT.ToString(), "مالیات بر ارزش افزوده", 0));
return Task.FromResult(response);
return response;
}
/// <summary>
@@ -173,21 +182,21 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
#region Private Helpers
private static string GetConfigurationValue(string key)
private static string GetConfigurationValue(string key, Package? package)
{
return key switch
{
"Club.ActivationFee" => SystemConstants.ClubActivationFee.ToString(),
"Club.MembershipGiftValue" => SystemConstants.ClubMembershipGiftValue.ToString(),
"Club.ActivationFee" => (package?.ActivationFee ?? 0).ToString(),
"Club.MembershipGiftValue" => (package?.ActivationFee ?? 0).ToString(),
"Commission.MinWithdrawalAmount" => SystemConstants.CommissionMinWithdrawalAmount.ToString(),
"Commission.MaxWeeklyBalancesPerLeg" => SystemConstants.CommissionMaxWeeklyBalancesPerLeg.ToString(),
"Commission.MaxNetworkLevel" => SystemConstants.CommissionMaxNetworkLevel.ToString(),
"Commission.MaxWeeklyBalancesPerLeg" => (package?.MaxBalancesPerLeg ?? 0).ToString(),
"Commission.MaxNetworkLevel" => (package?.MaxNetworkLevel ?? 0).ToString(),
"Commission.CashWithdrawalEnabled" => SystemConstants.CommissionCashWithdrawalEnabled.ToString(),
"Commission.CalculationStrategy" => SystemConstants.CommissionCalculationStrategy,
"Network.AllowOrphanNodes" => SystemConstants.NetworkAllowOrphanNodes.ToString(),
"Network.MaxChildrenPerLeg" => SystemConstants.NetworkMaxChildrenPerLeg.ToString(),
"Package.BasePackageAmount" => SystemConstants.BasePackageAmount.ToString(),
"Package.DayaLoanAmount" => SystemConstants.DayaLoanAmount.ToString(),
"Package.BasePackageAmount" => (package?.Price ?? 0).ToString(),
"Package.DayaLoanAmount" => (package?.Price ?? 0).ToString(),
"System.MaintenanceMode" => SystemConstants.SystemMaintenanceMode.ToString(),
"System.EnableAuditLog" => SystemConstants.SystemEnableAuditLog.ToString(),
"Shop.VAT" => SystemConstants.ShopVAT.ToString(),
@@ -195,6 +204,13 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
};
}
private async Task<Package> GetBasePackageAsync(CancellationToken cancellationToken)
{
return await _context.Packages
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
?? throw new RpcException(new Status(StatusCode.Internal, "پکیج پایه یافت نشد"));
}
private static string GetConfigurationDescription(string key)
{
return key switch
@@ -344,9 +344,18 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
.Include(u => u.ClubMembership)
.FirstOrDefaultAsync(u => u.Id == userId, context.CancellationToken);
if (wallet.WalletMode == WalletMode.Magic
&& wallet.MagicTotalDeposited >= SystemConstants.MagicWalletMaxDeposit)
if (wallet.WalletMode == WalletMode.Magic)
{
// بارگذاری پکیج کاربر برای سقف کیف‌پول جادویی
var userCycle = await _context.ClubMembershipCycles
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken);
var userPackage = userCycle != null
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == userCycle.PackageId, context.CancellationToken)
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
var magicMaxDeposit = userPackage?.MagicWalletMaxDeposit ?? 1_000_000_000;
if (wallet.MagicTotalDeposited >= magicMaxDeposit)
{
// ── EXIT Magic Mode ──
// هر دو شرط: Balance=0 و سقف 100M پر شده
wallet.WalletMode = WalletMode.Normal;
@@ -366,6 +375,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
{
user.PackagePurchaseMethod = PackagePurchaseMethod.None;
}
}
}
else if (wallet.WalletMode == WalletMode.Normal
&& user != null
@@ -227,13 +227,21 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
var cycleCount = await _context.ClubMembershipCycles
.CountAsync(c => c.UserId == userId, context.CancellationToken);
// بارگذاری پکیج کاربر برای سقف کیف‌پول جادویی
var currentCycle = await _context.ClubMembershipCycles
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken);
var package = currentCycle != null
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == currentCycle.PackageId, context.CancellationToken)
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
var magicMaxDeposit = package?.MagicWalletMaxDeposit ?? 1_000_000_000;
var response = new GetMagicWalletStatusResponse
{
WalletMode = (int)wallet.WalletMode,
MagicTotalDeposited = wallet.MagicTotalDeposited,
MagicTotalCredited = wallet.MagicTotalCredited,
MagicMaxDeposit = SystemConstants.MagicWalletMaxDeposit,
MagicRemainingDeposit = Math.Max(0, SystemConstants.MagicWalletMaxDeposit - wallet.MagicTotalDeposited),
MagicMaxDeposit = magicMaxDeposit,
MagicRemainingDeposit = Math.Max(0, magicMaxDeposit - wallet.MagicTotalDeposited),
Balance = wallet.Balance,
PurchaseCycleCount = cycleCount
};