Files
docs/01-BUSINESS/commission-system-refactoring.md
T
masoodafar-web 002e99f6bf Implement Persian Date Conversion and Enhance User Network Information Service
- Added PersianDateTimeService for converting Gregorian dates to Persian format in the BackOffice frontend.
- Updated multiple frontend pages (Dashboard, UserPayouts, WorkerControl, UserNetworkInfo) to utilize the new Persian date service.
- Enhanced GetUserNetworkPositionDto with 28+ new fields for comprehensive user network data.
- Updated GetUserNetworkPositionQueryHandler to include new methods for calculating network statistics.
- Modified Protobuf messages to accommodate the new fields, increasing from 14 to 42.
- Refined week number calculation algorithm to ensure consistency across C# and SQL implementations.
- Created new CSV and Excel files for binary plan calculations.
- Ensured all changes are tested and validated for accuracy and performance.
2025-12-20 06:15:59 +03:30

9.1 KiB
Raw Blame History

اصلاحات سیستم کمیسیون هفتگی

📋 خلاصه تغییرات

سیستم کمیسیون هفتگی از 3 مرحله به 2 مرحله ساده‌سازی شد:

قبل (3 مرحله):

  1. CalculateWeeklyBalances - محاسبه تعادل‌ها
  2. CalculateWeeklyCommissionPool - محاسبه استخر
  3. ProcessUserPayouts - پردازش پرداخت‌ها (تکراری!)

بعد (2 مرحله):

  1. CalculateWeeklyBalances - محاسبه تعادل‌ها تا 15 لول
  2. CalculateWeeklyCommissionPool - محاسبه استخر + پردازش پرداخت‌ها

🔧 تغییرات جزئی

1️⃣ اضافه شدن فیلدها به NetworkWeeklyBalance

فیلدهای جدید:

/// <summary>
/// مقدار فلش هر طرف (بعد از اعمال Cap)
/// </summary>
public int FlushedPerSide { get; set; }

/// <summary>
/// مجموع فلش از دو طرف (از دست رفته)
/// </summary>
public int TotalFlushed { get; set; }

Migration: AddFlushedFieldsToNetworkWeeklyBalance


2️⃣ اصلاح CalculateWeeklyBalances

تغییرات:

  • فیلدهای FlushedPerSide و TotalFlushed ذخیره می‌شوند
  • WeeklyPoolContribution = 0 (دیگر در این مرحله محاسبه نمیشه)
  • محدودیت 15 لول قبلاً موجود بود و درست کار می‌کند

کد:

// محاسبه فلش
var flushedPerSide = totalBalances - cappedBalances;
var totalFlushed = flushedPerSide * 2;

// ذخیره
balance.FlushedPerSide = flushedPerSide;
balance.TotalFlushed = totalFlushed;
balance.WeeklyPoolContribution = 0; // Pool در مرحله بعد محاسبه میشه

3️⃣ اصلاح کامل CalculateWeeklyCommissionPool

منطق جدید Pool:

// 1. Pool از فعالسازی‌های باشگاه این هفته میاد (نه از تعادل‌ها)
var newClubMembersCount = await _context.ClubMemberships
    .Where(c => c.ActivatedAt >= startDate && c.ActivatedAt <= endDate)
    .CountAsync();

var totalPoolAmount = newClubMembersCount * activationFee;

// 2. ارزش هر امتیاز
var totalBalancesInNetwork = weeklyBalances.Sum(x => x.TotalBalances);
var valuePerBalance = totalPoolAmount / totalBalancesInNetwork;

افزوده شدن محاسبه تعادل زیرمجموعه:

// برای هر کاربر:
// 1. تعادل خودش
var directBalances = balance.TotalBalances;

// 2. تعادل زیرمجموعه (تا 15 لول)
var subordinateBalances = await CalculateSubordinateBalancesAsync(
    balance.UserId, 
    request.WeekNumber, 
    maxLevels: 15
);

var totalBalancesForUser = directBalances + subordinateBalances;

ایجاد UserCommissionPayout:

var payout = new UserCommissionPayout
{
    UserId = balance.UserId,
    WeekNumber = request.WeekNumber,
    WeeklyPoolId = existingPool.Id,
    BalancesEarned = totalBalancesForUser,
    ValuePerBalance = valuePerBalance,
    TotalAmount = totalBalancesForUser * valuePerBalance,
    Status = CommissionPayoutStatus.Pending,
    // ... subordinate fields
};

ثبت تاریخچه:

var history = new CommissionPayoutHistory
{
    UserId = payout.UserId,
    PayoutId = payout.Id,
    Amount = payout.TotalAmount,
    Status = CommissionPayoutStatus.Pending,
    ChangeReason = "محاسبه اولیه کمیسیون هفتگی"
};

4️⃣ ساده‌سازی TriggerWeeklyCalculation

قبل:

// Step 1
await _mediator.Send(new CalculateWeeklyBalancesCommand { ... });

// Step 2
await _mediator.Send(new CalculateWeeklyCommissionPoolCommand { ... });

// Step 3
await _mediator.Send(new ProcessUserPayoutsCommand { ... });

بعد:

// Step 1: محاسبه تعادل‌ها
if (!request.SkipBalances)
{
    await _mediator.Send(new CalculateWeeklyBalancesCommand { ... });
}

// Step 2: محاسبه Pool و پرداخت‌ها
if (!request.SkipPayouts)
{
    await _mediator.Send(new CalculateWeeklyCommissionPoolCommand { ... });
}

حذف شد:

  • SkipPool flag
  • Step 3 کاملاً حذف شد

🎯 فرآیند نهایی

مرحله 1: محاسبه تعادل‌ها

1. برای هر کاربر در شبکه
2. تا 15 لول پایین‌تر شمارش کن
3. محاسبه تعادل (MIN of left/right)
4. محاسبه باقیمانده
5. محاسبه فلش
6. ذخیره در NetworkWeeklyBalance

مرحله 2: محاسبه Pool و توزیع

1. شمارش فعالسازی‌های باشگاه این هفته
2. Pool = تعداد × ActivationFee
3. ارزش هر امتیاز = Pool ÷ مجموع تعادل‌ها
4. برای هر کاربر:
   a. تعادل خودش + تعادل زیرمجموعه (تا 15 لول)
   b. سهم = تعادل × ارزش
   c. ثبت در UserCommissionPayout
   d. ثبت تاریخچه

📊 جداول درگیر

NetworkWeeklyBalance (فیلدهای جدید)

ALTER TABLE [Network].[NetworkWeeklyBalances]
ADD [FlushedPerSide] INT NOT NULL DEFAULT 0,
    [TotalFlushed] INT NOT NULL DEFAULT 0;

WeeklyCommissionPool

- TotalPoolAmount: از فعالسازی‌های باشگاه
- TotalBalances: مجموع تعادل‌های شبکه
- ValuePerBalance: Pool ÷ TotalBalances

UserCommissionPayout

- BalancesEarned: تعادل خودش + زیرمجموعه
- DirectBalances: فقط تعادل خودش
- SubordinateBalances: فقط زیرمجموعه
- TotalAmount: BalancesEarned × ValuePerBalance
- Status: Pending

CommissionPayoutHistory

- PayoutId: شناسه UserCommissionPayout
- Status: Pending (در این مرحله)
- ChangeReason: "محاسبه اولیه کمیسیون هفتگی"

مزایا

  1. ساده‌تر: 2 مرحله به جای 3
  2. بدون تکرار: دیگر UserCommissionPayout دوبار ساخته نمیشه
  3. واضح‌تر: Pool از کجا میاد مشخصه
  4. قابل نگهداری: منطق مشابه یکجا هست
  5. کامل: تاریخچه + subordinate balances همه جا هست

🔄 مراحل بعدی (اختیاری)

مرحله 3: پرداخت واقعی (جدا از محاسبه)

می‌توان یک Command جدید داشت که:

  1. UserCommissionPayout با status=Pending رو بخونه
  2. به کیف پول واریز کنه
  3. Status رو به Paid تغییر بده
  4. تاریخچه اضافه کنه

این مرحله جدا از محاسبات است و می‌تواند:

  • دستی توسط ادمین اجرا شود
  • یا به صورت خودکار بعد از تایید

📝 نکات مهم

Pool چطور پُر میشه؟

1. کاربر عضو Club میشه
2. در ActivateClubMembership مبلغی کسر میشه
3. این مبلغ به Pool اضافه **نمیشه** (فقط شمارش میشه)
4. در محاسبه Pool: تعداد × ActivationFee

چرا subordinate balances؟

در سیستم باینری، کاربر از تعادل زیرمجموعه‌های خود 
(تا 15 لول پایین‌تر) هم کمیسیون می‌گیرد.

چرا 15 لول؟

محدودیت عمق برای جلوگیری از بارگذاری بیش از حد 
و تشویق به ایجاد شبکه متعادل

🧪 تست

تست مرحله 1

// 1. ایجاد کاربران در شبکه
// 2. فعالسازی Club برای برخی
// 3. اجرای CalculateWeeklyBalances
// 4. بررسی NetworkWeeklyBalance
//    - TotalBalances
//    - FlushedPerSide
//    - TotalFlushed

تست مرحله 2

// 1. اجرای مرحله 1
// 2. اجرای CalculateWeeklyCommissionPool
// 3. بررسی WeeklyCommissionPool
//    - TotalPoolAmount = تعداد فعالسازی‌ها × ActivationFee
//    - ValuePerBalance صحیح باشد
// 4. بررسی UserCommissionPayout
//    - برای هر کاربر ایجاد شده
//    - BalancesEarned شامل subordinate هم هست
//    - TotalAmount = BalancesEarned × ValuePerBalance
// 5. بررسی CommissionPayoutHistory
//    - برای هر پرداخت ثبت شده

📚 فایل‌های تغییر یافته

  1. NetworkWeeklyBalance.cs - اضافه شدن فیلدها
  2. CalculateWeeklyBalancesCommandHandler.cs - ذخیره فلش
  3. CalculateWeeklyCommissionPoolCommandHandler.cs - منطق کامل جدید
  4. TriggerWeeklyCalculationCommandHandler.cs - حذف مرحله 3
  5. TriggerWeeklyCalculationCommand.cs - حذف SkipPool flag
  6. Migration: AddFlushedFieldsToNetworkWeeklyBalance

🎉 نتیجه

سیستم کمیسیون هفتگی حالا:

  • ساده‌تر و قابل فهم‌تر
  • بدون تکرار در کد
  • Pool از منبع صحیح (فعالسازی‌های Club)
  • تعادل زیرمجموعه محاسبه میشه
  • تاریخچه کامل ثبت میشه
  • فلش دقیق ذخیره میشه

آماده برای استفاده در Production! 🚀