# سیستم باشگاه مشتریان و محاسبه کمیسیون شبکه

## خلاصه اجرایی
این سند تحلیل جامع و معماری پیشنهادی برای پیاده‌سازی سیستم باشگاه مشتریان (Club Membership) و محاسبه کمیسیون شبکه‌ای (MLM Binary Plan) را ارائه می‌دهد. این سیستم امکان مدیریت سه نوع کیف پول، فروشگاه اختصاصی با تخفیف، و توزیع عادلانه کمیسیون بر اساس تعادل شبکه را فراهم می‌کند.

---

## ۱. مفاهیم کلیدی

### ۱.۱ کیف پول‌های سه‌گانه
هر کاربر سه نوع کیف پول دارد:

1. **کیف پول اصلی (Balance)**: برای خرید از فروشگاه عمومی بازار
2. **کیف پول تخفیف (DiscountBalance)**: فقط برای خرید از فروشگاه باشگاه مشتریان (محدود به درصد تخفیف محصولات)
3. **کیف پول طلایی/کارمزد (NetworkBalance)**: دریافتی از کمیسیون شبکه‌ای - قابل برداشت نقدی یا خرید الماس از دایا

### ۱.۲ فعال‌سازی عضویت
- کاربر ۵۶ میلیون تومان پرداخت می‌کند (از طریق دایا یا درگاه)
- سیستم به صورت خودکار:
  - `Balance += 56M` (کیف پول اصلی)
  - `DiscountBalance += 56M` (کیف پول تخفیف)
- کاربر دکمه «عضویت در باشگاه» را می‌زند:
  - `25M` به استخر کمیسیون هفتگی اضافه می‌شود
  - کاربر در شبکه باینری (Binary Tree) قرار می‌گیرد

### ۱.۳ شبکه باینری (Binary MLM Plan)
- هر کاربر حداکثر دو زیرمجموعه دارد: **دست راست** و **دست چپ**
- تعادل (Balance): زمانی که هر دو شاخه دارای اعضای جدید شوند، یک تعادل ایجاد می‌شود
- **فرمول تعادل**: `UserBalances = MIN(LeftLegBalances, RightLegBalances)`
- تعادل‌ها به صورت هفتگی محاسبه و بعد از توزیع کمیسیون، ریست می‌شوند

### ۱.۴ محاسبه کمیسیون هفتگی
```
مبلغ ریالی هر امتیاز = (مجموع مبالغ استخر) ÷ (مجموع تعادل‌های کل سیستم)
کمیسیون هر کاربر = (تعداد تعادل کاربر) × (مبلغ ریالی هر امتیاز)
```

**مثال**:
- کاربر A: خودش ۱ تعادل + زیرمجموعه‌هایش ۲ تعادل = **۳ امتیاز**
- استخر هفتگی: `175M`
- مجموع امتیازهای سیستم: `5`
- ارزش هر امتیاز: `175M ÷ 5 = 35M`
- کمیسیون کاربر A: `3 × 35M = 105M`

---

## ۲. موجودیت‌های جدید (Domain Entities)

### ۲.۱ `ClubMembership` (عضویت باشگاه مشتریان)
```csharp
public class ClubMembership : BaseAuditableEntity
{
    // شناسه کاربر
    public long UserId { get; set; }
    public virtual User User { get; set; }
    
    // وضعیت عضویت
    public bool IsActive { get; set; }
    public DateTime? ActivatedAt { get; set; }
    
    // مبلغ اولیه پرداختی برای فعال‌سازی (معمولاً ۲۵ میلیون)
    public long InitialContribution { get; set; }
    
    // مجموع درآمد کارمزد تاکنون
    public long TotalEarned { get; set; }
    
    // UserClubFeature Collection Navigation Reference
    public virtual ICollection<UserClubFeature> UserClubFeatures { get; set; }
}
```

### ۲.۲ `ClubFeature` (فیچرهای باشگاه)
```csharp
public class ClubFeature : BaseAuditableEntity
{
    // نام فیچر
    public string Title { get; set; }
    
    // توضیحات
    public string? Description { get; set; }
    
    // وضعیت فعال/غیرفعال
    public bool IsActive { get; set; }
    
    // امتیاز لازم برای دریافت (اختیاری)
    public int? RequiredPoints { get; set; }
    
    // ترتیب نمایش
    public int SortOrder { get; set; }
    
    // UserClubFeature Collection Navigation Reference
    public virtual ICollection<UserClubFeature> UserClubFeatures { get; set; }
}
```

### ۲.۳ `UserClubFeature` (جدول واسط: کاربر–فیچر)
```csharp
public class UserClubFeature : BaseAuditableEntity
{
    // شناسه کاربر
    public long UserId { get; set; }
    public virtual User User { get; set; }
    
    // شناسه فیچر
    public long ClubFeatureId { get; set; }
    public virtual ClubFeature ClubFeature { get; set; }
    
    // تاریخ فعال‌سازی فیچر برای کاربر
    public DateTime GrantedAt { get; set; }
    
    // یادداشت اختیاری
    public string? Notes { get; set; }
}
```

### ۲.۴ `NetworkWeeklyBalance` (ساختار شبکه و تعادل‌های هفتگی)
```csharp
public class NetworkWeeklyBalance : BaseAuditableEntity
{
    // شناسه کاربر
    public long UserId { get; set; }
    public virtual User User { get; set; }
    
    // شماره هفته (مثال: "2025-W48")
    public string WeekNumber { get; set; }
    
    // تعداد تعادل شاخه چپ در این هفته
    public int LeftLegBalances { get; set; }
    
    // تعداد تعادل شاخه راست در این هفته
    public int RightLegBalances { get; set; }
    
    // امتیاز کاربر: MIN(LeftLegBalances, RightLegBalances)
    public int TotalBalances { get; set; }
    
    // مبلغی که از این کاربر به استخر هفتگی اضافه شد
    public long WeeklyPoolContribution { get; set; }
    
    // زمان محاسبه
    public DateTime? CalculatedAt { get; set; }
    
    // آیا منقضی شده (بعد از توزیع)
    public bool IsExpired { get; set; }
}
```

### ۲.۵ `WeeklyCommissionPool` (استخر کارمزد هفتگی)
```csharp
public class WeeklyCommissionPool : BaseAuditableEntity
{
    // شماره هفته
    public string WeekNumber { get; set; }
    
    // مجموع مبلغ جمع‌شده در استخر
    public long TotalPoolAmount { get; set; }
    
    // مجموع تعادل‌های کل سیستم در این هفته
    public int TotalBalances { get; set; }
    
    // مبلغ ریالی هر امتیاز
    public long ValuePerBalance { get; set; }
    
    // آیا محاسبه و توزیع شده
    public bool IsCalculated { get; set; }
    public DateTime? CalculatedAt { get; set; }
    
    // UserCommissionPayout Collection Navigation Reference
    public virtual ICollection<UserCommissionPayout> UserCommissionPayouts { get; set; }
}
```

### ۲.۶ `UserCommissionPayout` (پرداخت کمیسیون به کاربران)
```csharp
public class UserCommissionPayout : BaseAuditableEntity
{
    // شناسه کاربر
    public long UserId { get; set; }
    public virtual User User { get; set; }
    
    // شماره هفته
    public string WeekNumber { get; set; }
    
    // شناسه استخر
    public long WeeklyPoolId { get; set; }
    public virtual WeeklyCommissionPool WeeklyPool { get; set; }
    
    // تعداد امتیازی که کاربر داشت
    public int BalancesEarned { get; set; }
    
    // ارزش هر امتیاز
    public long ValuePerBalance { get; set; }
    
    // مبلغ کل: BalancesEarned × ValuePerBalance
    public long TotalAmount { get; set; }
    
    // وضعیت پرداخت
    public CommissionPayoutStatus Status { get; set; }
    
    // تاریخ واریز به کیف پول
    public DateTime? PaidAt { get; set; }
    
    // روش برداشت (اگر کاربر درخواست برداشت داده)
    public WithdrawalMethod? WithdrawalMethod { get; set; }
    
    // شماره شبای برداشت (اگر نقدی)
    public string? IbanNumber { get; set; }
    
    // تاریخ برداشت نقدی/الماس
    public DateTime? WithdrawnAt { get; set; }
}
```

---

### ۲.۷ موجودیت‌های History (جداول تاریخچه و Audit)

#### ۲.۷.۱ `ClubMembershipHistory`
لاگ تمام تغییرات مهم روی عضویت باشگاه برای Audit و Compliance:

```csharp
public class ClubMembershipHistory : BaseAuditableEntity
{
    public long ClubMembershipId { get; set; }
    public long UserId { get; set; }

    // وضعیت قبل و بعد
    public bool OldIsActive { get; set; }
    public bool NewIsActive { get; set; }

    // مبلغ مشارکت قبل و بعد
    public long? OldInitialContribution { get; set; }
    public long? NewInitialContribution { get; set; }

    // نوع عملیات
    public ClubMembershipAction Action { get; set; }
    
    // دلیل تغییر (اختیاری)
    public string? Reason { get; set; }
    
    // چه کسی انجام داده (UserId یا "System")
    public string? PerformedBy { get; set; }
}
```

**استفاده**: هر بار که `ActivateClubMembership`, `DeactivateClubMembership` یا `UpdateClubMembership` اجرا می‌شود، یک رکورد History ثبت می‌گردد.

#### ۲.۷.۲ `NetworkMembershipHistory`
برای ردیابی کامل جابجایی در شبکه باینری:

```csharp
public class NetworkMembershipHistory : BaseAuditableEntity
{
    public long UserId { get; set; }

    // والد قبل و بعد
    public long? OldParentId { get; set; }
    public long? NewParentId { get; set; }

    // موقعیت قبل و بعد
    public NetworkLeg? OldLegPosition { get; set; }
    public NetworkLeg? NewLegPosition { get; set; }

    // نوع عملیات
    public NetworkMembershipAction Action { get; set; }
    
    public string? Reason { get; set; }
    public string? PerformedBy { get; set; }
}
```

**استفاده**: 
- هنگام `RecordNetworkJoin`: Action = Join
- هنگام `UpdateNetworkPosition`: Action = Move
- امکان بازسازی درخت شبکه در هر زمان گذشته

#### ۲.۷.۳ `CommissionPayoutHistory`
تاریخچه کامل تغییرات پرداخت کمیسیون‌ها:

```csharp
public class CommissionPayoutHistory : BaseAuditableEntity
{
    public long UserCommissionPayoutId { get; set; }
    public long UserId { get; set; }
    public string WeekNumber { get; set; }

    // مبلغ قبل و بعد
    public long AmountBefore { get; set; }
    public long AmountAfter { get; set; }

    // وضعیت قبل و بعد
    public CommissionPayoutStatus OldStatus { get; set; }
    public CommissionPayoutStatus NewStatus { get; set; }

    // نوع عملیات
    public CommissionPayoutAction Action { get; set; }
    
    public string? PerformedBy { get; set; }
    public string? Reason { get; set; }
}
```

**استفاده**:
- Worker: Action = Created, Paid
- کاربر: Action = WithdrawRequested
- ادمین: Action = Withdrawn, Cancelled, ManualFix

#### ۲.۷.۴ `SystemConfigurationHistory`
تاریخچه تغییرات تنظیمات سیستم:

```csharp
public class SystemConfigurationHistory : BaseAuditableEntity
{
    public long ConfigurationId { get; set; }

    public ConfigurationScope Scope { get; set; }
    public string Key { get; set; }

    // مقدار قبل و بعد
    public string OldValue { get; set; }
    public string NewValue { get; set; }

    public string? Reason { get; set; }
    public string? PerformedBy { get; set; }
}
```

**استفاده**: هر تغییر در `SystemConfiguration` باید در این جدول ثبت شود تا مشخص باشد در هر زمان چه محدودیتی فعال بوده است.

---

### ۲.۸ موجودیت‌های Configuration (تنظیمات پویا)

#### ۲.۸.۱ `ConfigurationScope` (Enum)
```csharp
public enum ConfigurationScope
{
    System = 0,      // تنظیمات کلی سیستم
    Network = 1,     // تنظیمات شبکه باینری
    Club = 2,        // تنظیمات باشگاه مشتریان
    Commission = 3   // تنظیمات کمیسیون
}
```

#### ۲.۸.۲ `SystemConfiguration`
جدول نگهداری تنظیمات پویا که بدون تغییر کد قابل تغییر است:

```csharp
public class SystemConfiguration : BaseAuditableEntity
{
    // محدوده تنظیمات
    public ConfigurationScope Scope { get; set; }

    // کلید تنظیم (مثلاً "MaxWeeklyBalancesPerUser")
    public string Key { get; set; }

    // مقدار به‌صورت رشته (تفسیر در Application Layer)
    public string Value { get; set; }

    // نوع داده برای Validation و UI (Int/Decimal/Bool/String/Json)
    public string? DataType { get; set; }

    // توضیحات برای ادمین
    public string? Description { get; set; }
    
    public bool IsActive { get; set; }
}
```

**مثال کانفیگ‌های کلیدی**:

| Scope | Key | Value | توضیح |
|-------|-----|-------|-------|
| Network | MaxWeeklyBalancesPerUser | 300 | سقف تعادل هفتگی برای هر کاربر |
| Network | MaxChildrenPerLeg | 1 | حداکثر فرزند مستقیم در هر شاخه |
| Network | MaxNetworkDepth | 15 | حداکثر عمق شبکه |
| Commission | DefaultInitialContribution | 25000000 | مبلغ پیش‌فرض مشارکت |
| Commission | MinWithdrawalAmount | 1000000 | حداقل مبلغ برداشت |
| Club | ActivationFee | 25000000 | هزینه فعال‌سازی عضویت |

**مزایا**:
- تغییر قوانین بیزینس بدون Deployment
- A/B Testing و آزمایش استراتژی‌های مختلف
- تاریخچه کامل در `SystemConfigurationHistory`

---

### ۲.۹ Enums جدید
```csharp
public enum CommissionPayoutStatus
{
    Pending = 0,        // در انتظار واریز
    Paid = 1,          // واریز شده به کیف پول
    WithdrawRequested = 2,  // درخواست برداشت داده شده
    Withdrawn = 3,     // برداشت شده
    Cancelled = 4      // لغو شده
}

public enum WithdrawalMethod
{
    Cash = 0,          // برداشت نقدی به حساب بانکی
    Diamond = 1        // خرید الماس از دایا
}

public enum NetworkLeg
{
    Left = 0,          // شاخه چپ
    Right = 1          // شاخه راست
}

public enum ClubMembershipAction
{
    Activated = 0,     // فعال‌سازی عضویت
    Deactivated = 1,   // غیرفعال‌سازی
    Updated = 2,       // ویرایش اطلاعات
    ManualFix = 3      // اصلاح دستی توسط ادمین
}

public enum NetworkMembershipAction
{
    Join = 0,          // ورود به شبکه
    Move = 1,          // جابجایی در شبکه
    Remove = 2         // حذف از شبکه
}

public enum CommissionPayoutAction
{
    Created = 0,       // ایجاد اولیه
    Paid = 1,          // واریز شده
    WithdrawRequested = 2,  // درخواست برداشت
    Withdrawn = 3,     // برداشت شده
    Cancelled = 4,     // لغو شده
    ManualFix = 5      // اصلاح دستی
}
```

---

## ۳. تغییرات در موجودیت‌های موجود

### ۳.۱ `User`
```csharp
// افزودن فیلدهای شبکه باینری
public long? NetworkParentId { get; set; }
public virtual User? NetworkParent { get; set; }

public NetworkLeg? LegPosition { get; set; }

// Collection Navigation References
public virtual ICollection<User> NetworkChildren { get; set; }
public virtual ClubMembership? ClubMembership { get; set; }
public virtual ICollection<NetworkWeeklyBalance> NetworkWeeklyBalances { get; set; }
public virtual ICollection<UserCommissionPayout> CommissionPayouts { get; set; }
```

### ۳.۲ `UserWallet`
```csharp
// موجودی ریالی اصلی
public long Balance { get; set; }

// موجودی شبکه/کارمزد (کیف پول طلایی)
public long NetworkBalance { get; set; }

// موجودی تخفیف (فقط برای خرید از فروشگاه باشگاه)
public long DiscountBalance { get; set; }
```

### ۳.۳ `Products`
```csharp
// آیا این محصول فقط در فروشگاه باشگاه موجود است
public bool IsClubExclusive { get; set; }

// درصد تخفیف باشگاه (0 تا 100)
public int ClubDiscountPercent { get; set; }
```

### ۳.۴ `UserWalletChangeLog`
افزودن نوع جدید تراکنش:
```csharp
// در enum TransactionType:
NetworkCommission = 10,    // دریافت کمیسیون شبکه
ClubActivation = 11,       // فعال‌سازی عضویت باشگاه
DiscountWalletCharge = 12, // شارژ کیف پول تخفیف
```

---

## ۴. معماری ماژول‌های جدید

### ۴.۱ `ClubMembershipCQ/`
#### Commands
- **ActivateClubMembership**: فعال‌سازی عضویت باشگاه (کسر ۲۵ میلیون و اضافه به استخر)
  - **نکته**: باید رکورد در `ClubMembershipHistory` با Action=Activated ثبت کند
- **DeactivateClubMembership**: غیرفعال‌سازی عضویت
  - **نکته**: ثبت History با Action=Deactivated
- **UpdateClubMembership**: به‌روزرسانی اطلاعات عضویت
  - **نکته**: ثبت History با Action=Updated

#### Queries
- **GetUserClubStatus**: دریافت وضعیت عضویت کاربر
- **GetAllClubMembersByFilter**: لیست اعضای باشگاه با فیلتر
- **GetClubMembershipHistory**: تاریخچه تغییرات عضویت یک کاربر

### ۴.۲ `ClubFeatureCQ/`
#### Commands
- **CreateClubFeature**: ایجاد فیچر جدید
- **UpdateClubFeature**: ویرایش فیچر
- **DeleteClubFeature**: حذف فیچر
- **GrantFeatureToUser**: فعال‌سازی فیچر برای کاربر
- **RevokeFeatureFromUser**: غیرفعال‌سازی فیچر از کاربر

#### Queries
- **GetAllClubFeatures**: لیست تمام فیچرها
- **GetUserClubFeatures**: لیست فیچرهای فعال یک کاربر

### ۴.۳ `NetworkBalanceCQ/`
#### Commands
- **RecordNetworkJoin**: ثبت ورود کاربر به شبکه باینری (تعیین والد و شاخه)
  - **نکته**: حتماً باید رکورد `NetworkMembershipHistory` با Action=Join ایجاد کند
  - **ولیدیشن**: بررسی ظرفیت شاخه (MaxChildrenPerLeg از Config)
- **UpdateNetworkPosition**: تغییر موقعیت در شبکه (مدیریتی)
  - **نکته**: ثبت History با Action=Move و ذکر OldParentId/NewParentId
  - **محدودیت**: فقط قبل از محاسبات هفتگی مجاز است
- **CalculateWeeklyBalances**: محاسبه تعادل‌های هفتگی (فراخوانی از Worker)
  - **نکته**: اعمال سقف از `MaxWeeklyBalancesPerUser` (Config)

#### Queries
- **GetUserNetworkTree**: دریافت درخت زیرمجموعه‌های کاربر (چند سطح)
- **GetUserWeeklyBalances**: دریافت تعادل‌های هفتگی یک کاربر
- **GetNetworkStatistics**: آمار کلی شبکه (تعداد اعضا، عمق، تعادل)
- **GetNetworkMembershipHistory**: تاریخچه جابجایی‌های یک کاربر در شبکه

### ۴.۴ `CommissionPoolCQ/`
#### Commands
- **InitializeWeeklyPool**: ایجاد استخر جدید برای هفته
- **AddToWeeklyPool**: افزودن مبلغ به استخر هفتگی (هنگام فعال‌سازی عضویت)
- **CalculatePoolValue**: محاسبه ارزش هر امتیاز
- **DistributeCommissions**: توزیع کمیسیون‌ها به کاربران (Worker)
- **CloseWeeklyPool**: بستن استخر پس از توزیع

#### Queries
- **GetCurrentWeekPool**: دریافت اطلاعات استخر هفته جاری
- **GetPoolHistory**: تاریخچه استخرهای قبلی با فیلتر

### ۴.۵ `CommissionPayoutCQ/`
#### Commands
- **CreatePayoutRecord**: ثبت پرداخت کمیسیون (اتوماتیک از Worker)
  - **نکته**: ایجاد رکورد `CommissionPayoutHistory` با Action=Created
- **RequestWithdrawal**: درخواست برداشت کمیسیون (نقدی یا الماس)
  - **نکته**: ثبت History با Action=WithdrawRequested
  - **ولیدیشن**: بررسی `MinWithdrawalAmount` از Config
- **ProcessWithdrawal**: پردازش درخواست برداشت (تایید/رد ادمین)
  - **نکته**: ثبت History با Action=Withdrawn یا Cancelled
- **CancelPayout**: لغو پرداخت
  - **نکته**: ثبت History با Action=Cancelled

#### Queries
- **GetUserCommissionHistory**: تاریخچه کمیسیون‌های دریافتی کاربر
- **GetPendingWithdrawals**: لیست درخواست‌های برداشت در انتظار (برای ادمین)
- **GetCommissionSummary**: خلاصه درآمد کمیسیون (مجموع، ماهانه، سالانه)
- **GetCommissionPayoutAudit**: تاریخچه کامل تغییرات یک پرداخت (از CommissionPayoutHistory)

---

### ۴.۶ `ConfigurationCQ/`
#### Commands
- **SetConfigurationValue**: ثبت یا ویرایش یک تنظیم
  - **نکته**: هر تغییر باید در `SystemConfigurationHistory` ثبت شود
  - **ولیدیشن**: بررسی DataType و محدوده مجاز
- **DeactivateConfiguration**: غیرفعال‌سازی یک تنظیم

#### Queries
- **GetConfigurationValue**: دریافت مقدار یک Key از Config
- **GetConfigurationsByScope**: لیست تنظیمات یک Scope (مثلاً Network)
- **GetConfigurationHistory**: تاریخچه تغییرات یک تنظیم

---

## ۵. Background Worker/Job (محاسبات هفتگی)

### ۵.۱ `WeeklyNetworkCommissionWorker`
**زمان‌بندی**: هر یکشنبه ساعت ۲۳:۵۹ (یا دوشنبه ۰۰:۰۱)

**مراحل اجرایی**:

#### گام ۱: بستن هفته قبل و ایجاد استخر جدید
```csharp
var currentWeek = GetCurrentWeekNumber(); // مثلاً "2025-W48"
var previousWeek = GetPreviousWeekNumber();

// بستن استخر هفته قبل (اگر هنوز باز است)
await CloseWeeklyPool(previousWeek);

// ایجاد استخر جدید
await InitializeWeeklyPool(currentWeek);
```

#### گام ۲: محاسبه تعادل‌های شبکه
```csharp
// دریافت تمام اعضای باشگاه فعال
var activeMembers = await GetActiveClubMembers();

// دریافت سقف تعادل از Config
var maxBalancesPerUser = await GetConfigValue<int>(
    "MaxWeeklyBalancesPerUser", 
    ConfigurationScope.Network
) ?? 300; // مقدار پیش‌فرض

foreach (var member in activeMembers)
{
    // محاسبه تعادل‌های شاخه چپ و راست
    var leftBalances = await CalculateLegBalances(member.UserId, NetworkLeg.Left, previousWeek);
    var rightBalances = await CalculateLegBalances(member.UserId, NetworkLeg.Right, previousWeek);
    
    // تعادل کاربر: حداقل دو شاخه
    var totalBalances = Math.Min(leftBalances, rightBalances);
    
    // اعمال محدودیت سقف (جلوگیری از سوءاستفاده)
    if (totalBalances > maxBalancesPerUser)
    {
        _logger.LogWarning(
            "User {UserId} exceeded max balances: {Total} > {Max}. Capping to {Max}.",
            member.UserId, totalBalances, maxBalancesPerUser, maxBalancesPerUser
        );
        totalBalances = maxBalancesPerUser;
    }
    
    // ثبت در NetworkWeeklyBalance
    await RecordWeeklyBalance(new NetworkWeeklyBalance {
        UserId = member.UserId,
        WeekNumber = previousWeek,
        LeftLegBalances = leftBalances,
        RightLegBalances = rightBalances,
        TotalBalances = totalBalances,
        WeeklyPoolContribution = member.InitialContribution,
        CalculatedAt = DateTime.UtcNow
    });
}
```

**الگوریتم محاسبه تعادل شاخه (Recursive)**:
```csharp
private async Task<int> CalculateLegBalances(long userId, NetworkLeg leg, string weekNumber)
{
    // دریافت فرزندان در شاخه مشخص
    var children = await GetNetworkChildren(userId, leg);
    
    int totalBalances = 0;
    
    foreach (var child in children)
    {
        // اگر فرزند در این هفته عضو شده باشد
        var childMembership = await GetClubMembership(child.Id);
        if (childMembership != null && IsInWeek(childMembership.ActivatedAt, weekNumber))
        {
            totalBalances++; // این فرزند یک تعادل ایجاد کرده
        }
        
        // بررسی زیرمجموعه‌های فرزند (عمق‌سنجی)
        var childLeftBalances = await CalculateLegBalances(child.Id, NetworkLeg.Left, weekNumber);
        var childRightBalances = await CalculateLegBalances(child.Id, NetworkLeg.Right, weekNumber);
        
        // تعادل‌های فرزند (حداقل دو شاخه)
        totalBalances += Math.Min(childLeftBalances, childRightBalances);
    }
    
    return totalBalances;
}
```

#### گام ۳: محاسبه استخر و ارزش امتیاز
```csharp
// جمع مبالغ استخر
var totalPoolAmount = await SumPoolContributions(previousWeek);

// جمع تعادل‌های کل سیستم
var totalBalances = await SumTotalBalances(previousWeek);

// محاسبه ارزش هر امتیاز
var valuePerBalance = totalBalances > 0 ? totalPoolAmount / totalBalances : 0;

// به‌روزرسانی استخر
await UpdatePoolValue(previousWeek, totalPoolAmount, totalBalances, valuePerBalance);
```

#### گام ۴: توزیع کمیسیون‌ها
```csharp
var weeklyBalances = await GetWeeklyBalances(previousWeek);

foreach (var balance in weeklyBalances.Where(b => b.TotalBalances > 0))
{
    var payoutAmount = balance.TotalBalances * valuePerBalance;
    
    // ثبت پرداخت
    var payout = new UserCommissionPayout {
        UserId = balance.UserId,
        WeekNumber = previousWeek,
        BalancesEarned = balance.TotalBalances,
        ValuePerBalance = valuePerBalance,
        TotalAmount = payoutAmount,
        Status = CommissionPayoutStatus.Pending
    };
    await CreatePayoutRecord(payout);
    
    // ثبت History برای ایجاد
    await RecordPayoutHistory(new CommissionPayoutHistory {
        UserCommissionPayoutId = payout.Id,
        UserId = balance.UserId,
        WeekNumber = previousWeek,
        AmountBefore = 0,
        AmountAfter = payoutAmount,
        OldStatus = CommissionPayoutStatus.Pending,
        NewStatus = CommissionPayoutStatus.Pending,
        Action = CommissionPayoutAction.Created,
        PerformedBy = "System"
    });
    
    // واریز به کیف پول طلایی
    await AddToNetworkBalance(balance.UserId, payoutAmount);
    
    // ثبت در ChangeLog
    await RecordWalletChange(new UserWalletChangeLog {
        WalletId = balance.UserId,
        PreviousBalance = ...,
        Amount = payoutAmount,
        AfterBalance = ...,
        TransactionType = TransactionType.NetworkCommission,
        ReferenceId = payout.Id.ToString()
    });
    
    // تنظیم وضعیت پرداخت
    payout.Status = CommissionPayoutStatus.Paid;
    payout.PaidAt = DateTime.UtcNow;
    await UpdatePayout(payout);
    
    // ثبت History برای پرداخت
    await RecordPayoutHistory(new CommissionPayoutHistory {
        UserCommissionPayoutId = payout.Id,
        UserId = balance.UserId,
        WeekNumber = previousWeek,
        AmountBefore = payoutAmount,
        AmountAfter = payoutAmount,
        OldStatus = CommissionPayoutStatus.Pending,
        NewStatus = CommissionPayoutStatus.Paid,
        Action = CommissionPayoutAction.Paid,
        PerformedBy = "System"
    });
}
```

#### گام ۵: ریست تعادل‌ها
```csharp
// علامت‌گذاری تعادل‌های هفته قبل به عنوان منقضی
await ExpireWeeklyBalances(previousWeek);
```

### ۵.۲ نکات حیاتی Worker
- **Transaction Scope**: تمام مراحل باید داخل یک تراکنش دیتابیس باشند تا در صورت خطا Rollback شود
- **Idempotency**: بررسی اینکه استخر هفته قبل قبلاً محاسبه نشده باشد (`IsCalculated = false`)
- **Logging**: ثبت دقیق هر مرحله برای Audit و عیب‌یابی
- **Notification**: ارسال اعلان به کاربرانی که کمیسیون دریافت کرده‌اند
- **Error Handling**: در صورت خطا، ارسال آلارم به تیم فنی و تلاش مجدد

---

## ۶. لاجیک فروشگاه و سبد خرید

### ۶.۱ نمایش محصولات
```csharp
// در Query لیست محصولات
var query = _context.Products.Where(p => !p.IsDeleted);

// اگر کاربر عضو باشگاه نیست، محصولات اختصاصی را حذف کن
if (!user.ClubMembership?.IsActive)
{
    query = query.Where(p => !p.IsClubExclusive);
}

// نمایش تخفیف باشگاه (اگر کاربر عضو باشد)
var products = await query.Select(p => new ProductDto {
    ...
    ClubDiscountPercent = user.ClubMembership?.IsActive ? p.ClubDiscountPercent : 0,
    FinalPrice = p.Price - (p.Price * p.ClubDiscountPercent / 100)
}).ToListAsync();
```

### ۶.۲ Checkout و محاسبه پرداخت
```csharp
public async Task<UserOrderDto> CheckoutWithClubDiscount(CheckoutCommand command)
{
    var user = await _context.Users.Include(u => u.ClubMembership)
                                     .Include(u => u.UserWallets)
                                     .FirstOrDefaultAsync(u => u.Id == command.UserId);
    
    var cartItems = await _context.UserCarts
                                    .Include(c => c.Product)
                                    .Where(c => c.UserId == command.UserId && !c.IsDeleted)
                                    .ToListAsync();
    
    long totalAmount = 0;
    long totalDiscountAmount = 0;
    long totalCashAmount = 0;
    
    foreach (var item in cartItems)
    {
        var product = item.Product;
        var itemTotal = product.Price * item.Count;
        
        // اگر کاربر عضو باشگاه باشد و محصول تخفیف داشته باشد
        if (user.ClubMembership?.IsActive == true && product.ClubDiscountPercent > 0)
        {
            var discountAmount = (itemTotal * product.ClubDiscountPercent) / 100;
            var cashAmount = itemTotal - discountAmount;
            
            totalDiscountAmount += discountAmount;
            totalCashAmount += cashAmount;
        }
        else
        {
            totalCashAmount += itemTotal;
        }
        
        totalAmount += itemTotal;
    }
    
    // بررسی موجودی کیف پول تخفیف
    var wallet = user.UserWallets.First();
    if (totalDiscountAmount > wallet.DiscountBalance)
    {
        throw new BusinessException("موجودی کیف پول تخفیف کافی نیست");
    }
    
    // ایجاد سفارش
    var order = new UserOrder {
        UserId = command.UserId,
        Amount = totalAmount,
        PaymentStatus = PaymentStatus.Pending,
        // ... سایر فیلدها
    };
    _context.UserOrders.Add(order);
    
    // ایجاد آیتم‌های فاکتور
    foreach (var item in cartItems)
    {
        var factorDetail = new FactorDetails {
            OrderId = order.Id,
            ProductId = item.ProductId,
            Count = item.Count,
            UnitPrice = item.Product.Price,
            UnitDiscount = item.Product.ClubDiscountPercent,
            // ...
        };
        _context.FactorDetails.Add(factorDetail);
    }
    
    // کسر از کیف پول تخفیف
    if (totalDiscountAmount > 0)
    {
        wallet.DiscountBalance -= totalDiscountAmount;
        
        // ثبت ChangeLog
        var changeLog = new UserWalletChangeLog {
            WalletId = wallet.Id,
            PreviousBalance = wallet.DiscountBalance + totalDiscountAmount,
            Amount = -totalDiscountAmount,
            AfterBalance = wallet.DiscountBalance,
            IsIncrease = false,
            TransactionType = TransactionType.Purchase,
            ReferenceId = order.Id.ToString()
        };
        _context.UserWalletChangeLogs.Add(changeLog);
    }
    
    await _context.SaveChangesAsync();
    
    // هدایت به درگاه برای پرداخت مبلغ نقدی (totalCashAmount)
    // یا اگر مبلغ نقدی صفر باشد، سفارش را Success کن
    if (totalCashAmount == 0)
    {
        order.PaymentStatus = PaymentStatus.Success;
        order.PaymentDate = DateTime.UtcNow;
        await _context.SaveChangesAsync();
    }
    
    return MapToDto(order);
}
```

---

## ۷. سناریوی کامل فعال‌سازی عضویت

### مرحله ۱: شارژ اولیه
```
کاربر → پرداخت ۵۶ میلیون (دایا/درگاه)
  ↓
UserWallet.Balance += 56,000,000
UserWallet.DiscountBalance += 56,000,000
```

### مرحله ۲: فعال‌سازی عضویت
```
کاربر → کلیک روی دکمه «عضویت در باشگاه»
  ↓
API: ActivateClubMembership
  ↓
1. ایجاد رکورد ClubMembership:
   - IsActive = true
   - InitialContribution = 25,000,000
   
2. افزودن به استخر هفتگی:
   - WeeklyCommissionPool.TotalPoolAmount += 25,000,000
   
3. تعیین موقعیت در شبکه:
   - User.NetworkParentId = والد
   - User.LegPosition = Left یا Right
   
4. ثبت ChangeLog برای استخر:
   - TransactionType = ClubActivation

5. ثبت تاریخچه‌ها:
   - ClubMembershipHistory (Action = Activated)
   - NetworkMembershipHistory (Action = Join)
```

### مرحله ۳: محاسبه هفتگی (Worker)
```
یکشنبه ۲۳:۵۹
  ↓
WeeklyNetworkCommissionWorker.Execute()
  ↓
1. محاسبه تعادل‌های کاربر
2. محاسبه ارزش هر امتیاز
3. توزیع کمیسیون به NetworkBalance
4. ریست تعادل‌های هفته قبل
```

### مرحله ۴: برداشت کمیسیون
```
کاربر → درخواست برداشت
  ↓
API: RequestWithdrawal (Cash یا Diamond)
  ↓
ادمین → تایید درخواست
  ↓
1. اگر Cash:
   - واریز به حساب بانکی
   - NetworkBalance -= مبلغ
   
2. اگر Diamond:
   - خرید الماس از دایا
   - NetworkBalance -= مبلغ
```

---

## ۸. پروتوباف و gRPC Services

### ۸.۱ `clubmembership.proto`
```protobuf
syntax = "proto3";
import "google/protobuf/timestamp.proto";

package clubmembership;

service ClubMembershipService {
  rpc ActivateMembership (ActivateMembershipRequest) returns (ActivateMembershipResponse);
  rpc GetClubStatus (GetClubStatusRequest) returns (GetClubStatusResponse);
  rpc GrantFeature (GrantFeatureRequest) returns (GrantFeatureResponse);
  rpc GetUserFeatures (GetUserFeaturesRequest) returns (GetUserFeaturesResponse);
}

message ActivateMembershipRequest {
  int64 user_id = 1;
  int64 contribution_amount = 2;
  int64 network_parent_id = 3;
  NetworkLeg leg_position = 4;
}

message ActivateMembershipResponse {
  bool success = 1;
  string message = 2;
  ClubMembershipDto membership = 3;
}

message GetClubStatusRequest {
  int64 user_id = 1;
}

message GetClubStatusResponse {
  bool is_member = 1;
  ClubMembershipDto membership = 2;
}

message ClubMembershipDto {
  int64 id = 1;
  int64 user_id = 2;
  bool is_active = 3;
  google.protobuf.Timestamp activated_at = 4;
  int64 initial_contribution = 5;
  int64 total_earned = 6;
}

enum NetworkLeg {
  LEFT = 0;
  RIGHT = 1;
}
```

### ۸.۲ `networkbalance.proto`
```protobuf
syntax = "proto3";

package networkbalance;

service NetworkBalanceService {
  rpc GetNetworkTree (GetNetworkTreeRequest) returns (GetNetworkTreeResponse);
  rpc GetWeeklyBalances (GetWeeklyBalancesRequest) returns (GetWeeklyBalancesResponse);
  rpc GetNetworkStats (GetNetworkStatsRequest) returns (GetNetworkStatsResponse);
}

message GetNetworkTreeRequest {
  int64 user_id = 1;
  int32 max_depth = 2; // حداکثر عمق درخت (مثلاً ۳ سطح)
}

message GetNetworkTreeResponse {
  NetworkNodeDto root = 1;
}

message NetworkNodeDto {
  int64 user_id = 1;
  string full_name = 2;
  NetworkLeg leg_position = 3;
  bool is_active = 4;
  repeated NetworkNodeDto children = 5;
}

message GetWeeklyBalancesRequest {
  int64 user_id = 1;
  string week_number = 2; // اختیاری - اگر خالی باشد هفته جاری
}

message GetWeeklyBalancesResponse {
  int32 left_leg_balances = 1;
  int32 right_leg_balances = 2;
  int32 total_balances = 3;
  int64 pool_contribution = 4;
}
```

### ۸.۳ `commissionpayout.proto`
```protobuf
syntax = "proto3";
import "google/protobuf/timestamp.proto";

package commissionpayout;

service CommissionPayoutService {
  rpc RequestWithdrawal (RequestWithdrawalRequest) returns (RequestWithdrawalResponse);
  rpc GetCommissionHistory (GetCommissionHistoryRequest) returns (GetCommissionHistoryResponse);
  rpc GetPendingWithdrawals (GetPendingWithdrawalsRequest) returns (GetPendingWithdrawalsResponse);
  rpc ProcessWithdrawal (ProcessWithdrawalRequest) returns (ProcessWithdrawalResponse);
}

message RequestWithdrawalRequest {
  int64 user_id = 1;
  int64 amount = 2;
  WithdrawalMethod method = 3;
  string iban_number = 4; // فقط برای Cash
}

message RequestWithdrawalResponse {
  bool success = 1;
  string message = 2;
  int64 request_id = 3;
}

message GetCommissionHistoryRequest {
  int64 user_id = 1;
  int32 page_number = 2;
  int32 page_size = 3;
}

message GetCommissionHistoryResponse {
  repeated CommissionPayoutDto payouts = 1;
  int32 total_count = 2;
}

message CommissionPayoutDto {
  int64 id = 1;
  string week_number = 2;
  int32 balances_earned = 3;
  int64 value_per_balance = 4;
  int64 total_amount = 5;
  CommissionPayoutStatus status = 6;
  google.protobuf.Timestamp paid_at = 7;
  WithdrawalMethod withdrawal_method = 8;
}

enum WithdrawalMethod {
  CASH = 0;
  DIAMOND = 1;
}

enum CommissionPayoutStatus {
  PENDING = 0;
  PAID = 1;
  WITHDRAW_REQUESTED = 2;
  WITHDRAWN = 3;
  CANCELLED = 4;
}
```

---

## ۹. نکات حیاتی و بهترین رویه‌ها

### ۹.۱ یکپارچگی شبکه باینری
- **ولیدیشن**: هر کاربر حداکثر دو فرزند (یکی Left، یکی Right)
- **قفل خوش‌بینانه**: هنگام اضافه کردن فرزند، بررسی Race Condition
- **Cascade Delete**: حذف کاربر نباید ساختار شبکه را خراب کند (باید جایگزین شود یا درخت بازسازی گردد)

### ۹.۲ Transaction Management
- Worker باید تمام مراحل محاسبه و توزیع را در یک `TransactionScope` انجام دهد
- در صورت شکست، Rollback کامل و ارسال آلارم

### ۹.۳ Idempotency
- محاسبه هفتگی نباید دوبار برای یک هفته اجرا شود
- بررسی `WeeklyCommissionPool.IsCalculated` قبل از شروع

### ۹.۴ Performance
- برای کاربران با زیرمجموعه زیاد، Caching درخت شبکه
- Pagination در API های لیست
- Index های مناسب روی `WeekNumber`, `UserId`, `NetworkParentId`

### ۹.۵ Audit و Compliance
- **کیف پول**: تمام تغییرات در `UserWalletChangeLog` ثبت شود
- **عضویت**: تمام تغییرات در `ClubMembershipHistory` ثبت شود
- **شبکه**: تمام جابجایی‌ها در `NetworkMembershipHistory` ثبت شود
- **کمیسیون**: تمام تغییرات در `CommissionPayoutHistory` ثبت شود
- **تنظیمات**: تمام تغییرات Config در `SystemConfigurationHistory` ثبت شود
- **امکانات**:
  - بازسازی وضعیت سیستم در هر زمان گذشته
  - گزارش‌گیری کامل برای حسابرسی
  - شناسایی تغییرات غیرمجاز یا خطاهای سیستمی
- **Index های پیشنهادی**:
  ```sql
  CREATE INDEX IX_History_UserId_Created ON ClubMembershipHistory(UserId, Created);
  CREATE INDEX IX_History_WeekNumber ON CommissionPayoutHistory(WeekNumber);
  CREATE INDEX IX_Config_Scope_Key ON SystemConfiguration(Scope, Key);
  ```

### ۹.۶ Security
- محدودیت تعداد درخواست برداشت (Rate Limiting)
- تایید دو مرحله‌ای برای برداشت‌های بالا
- Audit Log برای تمام عملیات حساس

---

## ۱۰. مراحل پیاده‌سازی (Implementation Roadmap)

### 📅 زمان‌بندی کلی و اولویت‌بندی

| فاز | مدت زمان | اولویت | وابستگی‌ها |
|-----|----------|--------|------------|
| **فاز ۱**: پایه‌گذاری Domain | 3-5 روز | 🔴 حیاتی | - |
| **فاز ۲**: باشگاه مشتریان | 3-4 روز | 🔴 حیاتی | فاز ۱ |
| **فاز ۳**: شبکه باینری | 4-5 روز | 🔴 حیاتی | فاز ۱، ۲ |
| **فاز ۴**: کمیسیون و Worker | 5-6 روز | 🔴 حیاتی | فاز ۱، ۲، ۳ |
| **فاز ۵**: Protobuf Services | 2-3 روز | 🟡 متوسط | فاز ۱-۴ |
| **فاز ۶**: History و Configuration | 3-4 روز | 🟡 متوسط | فاز ۱-۴ |
| **فاز ۷**: Testing کامل | 5-7 روز | 🔴 حیاتی | همه فازها |
| **فاز ۸**: UI BackOffice | 5-7 روز | 🟢 عادی | فاز ۱-۶ |
| **فاز ۹**: فروشگاه باشگاه | 3-4 روز | 🟢 عادی | فاز ۲ |
| **فاز ۱۰**: برداشت و تسویه | 3-4 روز | 🟡 متوسط | فاز ۴ |

**⏱️ تخمین کل**: 36-49 روز کاری (7-10 هفته)

---

### 🚀 فاز ۱: پایه‌گذاری Domain Layer (روز ۱-۵)

#### روز ۱: آماده‌سازی و Enums
```bash
cd /home/masoud/Apps/project/FourSat/CMS/src/CMSMicroservice.Domain

# ایجاد ساختار پوشه‌ها
mkdir -p Entities/Club Entities/Network Entities/Commission Entities/Configuration Entities/History Enums
```

**Tasks:**
- [ ] ایجاد Branch جدید: `feature/network-club-system`
- [ ] ایجاد Enums (۷ فایل):
  - [ ] `CommissionPayoutStatus.cs`
  - [ ] `WithdrawalMethod.cs`
  - [ ] `NetworkLeg.cs`
  - [ ] `ClubMembershipAction.cs`
  - [ ] `NetworkMembershipAction.cs`
  - [ ] `CommissionPayoutAction.cs`
  - [ ] `ConfigurationScope.cs`
- [ ] Code Review Enums
- [ ] Commit: "Add enums for network-club system"

#### روز ۲-۳: Entities اصلی
**ترتیب پیاده‌سازی (به دلیل وابستگی‌ها):**

**روز ۲ صبح:**
- [ ] `SystemConfiguration.cs` (مستقل - اولویت بالا)
- [ ] `ClubMembership.cs` (مستقل)
- [ ] `ClubFeature.cs` (مستقل)

**روز ۲ بعدازظهر:**
- [ ] `UserClubFeature.cs` (وابسته به ClubMembership و ClubFeature)
- [ ] `WeeklyCommissionPool.cs` (مستقل)

**روز ۳ صبح:**
- [ ] `NetworkWeeklyBalance.cs` (وابسته به User)
- [ ] `UserCommissionPayout.cs` (وابسته به WeeklyCommissionPool)

**روز ۳ بعدازظهر:**
- [ ] Code Review Entities
- [ ] Commit: "Add core entities for network-club system"

#### روز ۴: History Entities و Entity Updates
**صبح:**
- [ ] `ClubMembershipHistory.cs`
- [ ] `NetworkMembershipHistory.cs`
- [ ] `CommissionPayoutHistory.cs`
- [ ] `SystemConfigurationHistory.cs`
- [ ] Commit: "Add history entities for audit trail"

**بعدازظهر:**
- [ ] به‌روزرسانی `User.cs`:
  - افزودن `NetworkParentId`, `LegPosition`
  - افزودن Navigation Properties
- [ ] به‌روزرسانی `UserWallet.cs`:
  - افزودن `NetworkBalance`, `DiscountBalance`
- [ ] به‌روزرسانی `Products.cs`:
  - افزودن `IsClubExclusive`, `ClubDiscountPercent`
- [ ] به‌روزرسانی `TransactionType` enum:
  - افزودن `NetworkCommission`, `ClubActivation`, `DiscountWalletCharge`
- [ ] Commit: "Update existing entities for network-club integration"

#### روز ۵: EF Configurations و Migration
**صبح:**
- [ ] ایجاد Configuration کلاس‌ها در `Infrastructure/Persistence/Configurations/`:
  - [ ] `ClubMembershipConfiguration.cs`
  - [ ] `ClubFeatureConfiguration.cs`
  - [ ] `UserClubFeatureConfiguration.cs`
  - [ ] `SystemConfigurationConfiguration.cs`
  - [ ] `NetworkWeeklyBalanceConfiguration.cs`
  - [ ] `WeeklyCommissionPoolConfiguration.cs`
  - [ ] `UserCommissionPayoutConfiguration.cs`
  - [ ] History Configurations (۴ فایل)

**بعدازظهر:**
- [ ] اضافه کردن Index های حیاتی:
  ```csharp
  builder.HasIndex(e => e.UserId);
  builder.HasIndex(e => e.WeekNumber);
  builder.HasIndex(e => new { e.Scope, e.Key }); // Config
  builder.HasIndex(e => new { e.UserId, e.Created }); // History
  ```
- [ ] ایجاد Migration:
  ```bash
  cd CMSMicroservice.Infrastructure
  dotnet ef migrations add AddNetworkClubSystem --project ../CMSMicroservice.WebApi
  ```
- [ ] بررسی دقیق Migration Script
- [ ] تست Migration روی دیتابیس Development
- [ ] Commit: "Add EF configurations and migration for network-club system"

---

### 🎯 فاز ۲: باشگاه مشتریان (روز ۶-۹)

#### روز ۶: ConfigurationCQ (اولویت بالا)
**چرا اول؟** بقیه ماژول‌ها به Config نیاز دارند.

```bash
cd CMSMicroservice.Application
mkdir -p ConfigurationCQ/Commands ConfigurationCQ/Queries ConfigurationCQ/DTOs
```

**صبح:**
- [ ] `Commands/SetConfigurationValueCommand.cs` + Handler
- [ ] `Commands/DeactivateConfigurationCommand.cs` + Handler
- [ ] Validators برای Commands
- [ ] افزودن ثبت `SystemConfigurationHistory` در Handler

**بعدازظهر:**
- [ ] `Queries/GetConfigurationValueQuery.cs` + Handler
- [ ] `Queries/GetConfigurationsByScopeQuery.cs` + Handler
- [ ] `Queries/GetConfigurationHistoryQuery.cs` + Handler
- [ ] `DTOs/ConfigurationDto.cs`, `ConfigurationHistoryDto.cs`
- [ ] تست Unit برای Handlers
- [ ] Commit: "Implement ConfigurationCQ module"

#### روز ۷: ClubMembershipCQ - Commands
```bash
mkdir -p ClubMembershipCQ/Commands ClubMembershipCQ/Queries ClubMembershipCQ/DTOs
```

**صبح:**
- [ ] `Commands/ActivateClubMembershipCommand.cs` + Handler
  - کسر ۲۵M از Balance
  - افزودن به استخر هفتگی
  - ثبت `ClubMembershipHistory` با Action=Activated
  - ثبت `NetworkMembershipHistory` با Action=Join
  - Validator (بررسی موجودی کافی)
- [ ] تست Unit کامل

**بعدازظهر:**
- [ ] `Commands/DeactivateClubMembershipCommand.cs` + Handler
  - ثبت History با Action=Deactivated
- [ ] `Commands/UpdateClubMembershipCommand.cs` + Handler
  - ثبت History با Action=Updated
- [ ] Commit: "Implement ClubMembership commands"

#### روز ۸: ClubMembershipCQ - Queries
**صبح:**
- [ ] `Queries/GetUserClubStatusQuery.cs` + Handler
- [ ] `Queries/GetAllClubMembersByFilterQuery.cs` + Handler
  - Pagination
  - Filter: IsActive, DateRange
- [ ] `Queries/GetClubMembershipHistoryQuery.cs` + Handler

**بعدازظهر:**
- [ ] `DTOs/ClubMembershipDto.cs`, `ClubMembershipHistoryDto.cs`
- [ ] تست Integration کامل فلوی فعال‌سازی
- [ ] Commit: "Implement ClubMembership queries and complete module"

#### روز ۹: ClubFeatureCQ
**صبح:**
- [ ] `Commands/CreateClubFeatureCommand.cs` + Handler
- [ ] `Commands/UpdateClubFeatureCommand.cs` + Handler
- [ ] `Commands/DeleteClubFeatureCommand.cs` + Handler (Soft Delete)

**بعدازظهر:**
- [ ] `Commands/GrantFeatureToUserCommand.cs` + Handler
- [ ] `Commands/RevokeFeatureFromUserCommand.cs` + Handler
- [ ] `Queries/GetAllClubFeaturesQuery.cs` + Handler
- [ ] `Queries/GetUserClubFeaturesQuery.cs` + Handler
- [ ] Commit: "Implement ClubFeatureCQ module"

---

### 🌳 فاز ۳: شبکه باینری (روز ۱۰-۱۴)

#### روز ۱۰: NetworkBalanceCQ - Setup و RecordNetworkJoin
```bash
mkdir -p NetworkBalanceCQ/Commands NetworkBalanceCQ/Queries NetworkBalanceCQ/DTOs
```

**صبح:**
- [ ] `Commands/RecordNetworkJoinCommand.cs` + Handler
  - Validator پیچیده:
    - بررسی ظرفیت شاخه والد (از Config: `MaxChildrenPerLeg`)
    - بررسی عدم تکراری بودن
    - بررسی عمق مجاز (از Config: `MaxNetworkDepth`)
  - تنظیم `User.NetworkParentId` و `User.LegPosition`
  - ثبت `NetworkMembershipHistory` با Action=Join

**بعدازظهر:**
- [ ] تست Validation کامل با سناریوهای مختلف
- [ ] Commit: "Implement RecordNetworkJoin command"

#### روز ۱۱: UpdateNetworkPosition و الگوریتم تعادل
**صبح:**
- [ ] `Commands/UpdateNetworkPositionCommand.cs` + Handler
  - ثبت History با OldParentId/NewParentId
  - محدودیت: فقط قبل از محاسبات هفتگی
  - نیاز به نقش Admin

**بعدازظهر:**
- [ ] پیاده‌سازی `CalculateLegBalancesService.cs`:
  ```csharp
  public async Task<int> CalculateLegBalances(
      long userId, 
      NetworkLeg leg, 
      string weekNumber, 
      int maxBalances
  )
  ```
  - الگوریتم Recursive
  - Caching برای جلوگیری از محاسبات تکراری
  - اعمال سقف از Config
- [ ] تست الگوریتم با داده Mock
- [ ] Commit: "Implement network balance calculation algorithm"

#### روز ۱۲-۱۳: NetworkBalanceCQ - Queries
**روز ۱۲ صبح:**
- [ ] `Queries/GetUserNetworkTreeQuery.cs` + Handler
  - Recursive query با محدودیت عمق
  - DTO با ساختار درختی
  - Caching برای Performance

**روز ۱۲ بعدازظهر:**
- [ ] `Queries/GetUserWeeklyBalancesQuery.cs` + Handler
- [ ] `Queries/GetNetworkStatisticsQuery.cs` + Handler
  - تعداد کل اعضا
  - عمق متوسط شبکه
  - توزیع چپ/راست

**روز ۱۳:**
- [ ] `Queries/GetNetworkMembershipHistoryQuery.cs` + Handler
- [ ] `DTOs/NetworkNodeDto.cs`, `NetworkStatisticsDto.cs`
- [ ] تست کامل ماژول
- [ ] Commit: "Complete NetworkBalanceCQ module"

#### روز ۱۴: تست عملکرد و بهینه‌سازی
- [ ] تست با ۱۰۰۰ کاربر Mock
- [ ] Profiling و شناسایی Bottlenecks
- [ ] بهینه‌سازی Queries (اضافه کردن Index در صورت نیاز)
- [ ] Commit: "Optimize network balance queries"

---

### 💰 فاز ۴: کمیسیون و Worker (روز ۱۵-۲۰)

#### روز ۱۵-۱۶: CommissionPoolCQ
```bash
mkdir -p CommissionPoolCQ/Commands CommissionPoolCQ/Queries
```

**روز ۱۵ صبح:**
- [ ] `Commands/InitializeWeeklyPoolCommand.cs` + Handler
- [ ] `Commands/AddToWeeklyPoolCommand.cs` + Handler
  - فراخوانی از `ActivateClubMembership`

**روز ۱۵ بعدازظهر:**
- [ ] `Commands/CalculatePoolValueCommand.cs` + Handler
  - محاسبه `ValuePerBalance = TotalPoolAmount ÷ TotalBalances`
- [ ] `Commands/CloseWeeklyPoolCommand.cs` + Handler
  - تنظیم `IsCalculated = true`

**روز ۱۶:**
- [ ] `Queries/GetCurrentWeekPoolQuery.cs` + Handler
- [ ] `Queries/GetPoolHistoryQuery.cs` + Handler (با Pagination)
- [ ] تست ماژول
- [ ] Commit: "Implement CommissionPoolCQ module"

#### روز ۱۷-۱۸: CommissionPayoutCQ
```bash
mkdir -p CommissionPayoutCQ/Commands CommissionPayoutCQ/Queries
```

**روز ۱۷ صبح:**
- [ ] `Commands/CreatePayoutRecordCommand.cs` + Handler
  - ثبت `UserCommissionPayout`
  - ثبت `CommissionPayoutHistory` با Action=Created

**روز ۱۷ بعدازظهر:**
- [ ] `Commands/RequestWithdrawalCommand.cs` + Handler
  - Validator: بررسی `MinWithdrawalAmount` از Config
  - ثبت History با Action=WithdrawRequested
- [ ] `Commands/ProcessWithdrawalCommand.cs` + Handler
  - نیاز به نقش Admin
  - ثبت History با Action=Withdrawn یا Cancelled

**روز ۱۸ صبح:**
- [ ] `Commands/CancelPayoutCommand.cs` + Handler
- [ ] `Queries/GetUserCommissionHistoryQuery.cs` + Handler
- [ ] `Queries/GetPendingWithdrawalsQuery.cs` + Handler (برای Admin)

**روز ۱۸ بعدازظهر:**
- [ ] `Queries/GetCommissionSummaryQuery.cs` + Handler
  - مجموع، ماهانه، سالانه
- [ ] `Queries/GetCommissionPayoutAuditQuery.cs` + Handler
  - نمایش تاریخچه کامل از History
- [ ] Commit: "Implement CommissionPayoutCQ module"

#### روز ۱۹-۲۰: Background Worker هفتگی
```bash
cd CMSMicroservice.Infrastructure/BackgroundJobs
```

**روز ۱۹ صبح:**
- [ ] ایجاد `WeeklyNetworkCommissionWorker.cs`
- [ ] پیاده‌سازی گام ۱ و ۲:
  - بستن استخر قبل
  - ایجاد استخر جدید
  - محاسبه تعادل‌ها با استفاده از `CalculateLegBalancesService`

**روز ۱۹ بعدازظهر:**
- [ ] پیاده‌سازی گام ۳ و ۴:
  - محاسبه `ValuePerBalance`
  - توزیع کمیسیون‌ها
  - واریز به `NetworkBalance`
  - ثبت `UserWalletChangeLog`

**روز ۲۰ صبح:**
- [ ] پیاده‌سازی گام ۵:
  - ریست تعادل‌ها (`IsExpired = true`)
- [ ] پیاده‌سازی Idempotency Check
- [ ] پیاده‌سازی Transaction Scope کامل
- [ ] Logging جامع

**روز ۲۰ بعدازظهر:**
- [ ] تنظیم زمان اجرا (یکشنبه ۲۳:۵۹)
- [ ] ایجاد Controller تست برای اجرای دستی
- [ ] تست Worker با داده واقعی
- [ ] بررسی Rollback در صورت خطا
- [ ] Commit: "Implement weekly commission worker"

---

### 🔌 فاز ۵: Protobuf Services (روز ۲۱-۲۳)

#### روز ۲۱: تعریف Proto Files
```bash
cd CMSMicroservice.Protobuf/Protos
```

- [ ] `clubmembership.proto`
  - Services: ActivateMembership, GetClubStatus, GrantFeature, GetUserFeatures
- [ ] `networkbalance.proto`
  - Services: GetNetworkTree, GetWeeklyBalances, GetNetworkStats
- [ ] `commissionpayout.proto`
  - Services: RequestWithdrawal, GetCommissionHistory, GetPendingWithdrawals, ProcessWithdrawal
- [ ] `configuration.proto`
  - Services: GetConfiguration, SetConfiguration, GetConfigurationHistory
- [ ] Build و بررسی Generated Code
- [ ] Commit: "Add protobuf definitions for network-club system"

#### روز ۲۲-۲۳: پیاده‌سازی Service Implementations
```bash
cd CMSMicroservice.WebApi/Services
```

**روز ۲۲:**
- [ ] `ClubMembershipGrpcService.cs`
- [ ] `NetworkBalanceGrpcService.cs`

**روز ۲۳:**
- [ ] `CommissionPayoutGrpcService.cs`
- [ ] `ConfigurationGrpcService.cs`
- [ ] تست gRPC با BloomRPC یا Postman
- [ ] Commit: "Implement gRPC service implementations"

---

### 📊 فاز ۶: History و Configuration Seed (روز ۲۴-۲۷)

#### روز ۲۴: Seed Data برای SystemConfiguration
```bash
cd CMSMicroservice.Infrastructure/Persistence/Seeds
```

- [ ] ایجاد `SystemConfigurationSeeder.cs`
- [ ] تعریف تنظیمات پیش‌فرض:
  ```csharp
  // Network Scope
  MaxWeeklyBalancesPerUser = 300
  MaxChildrenPerLeg = 1
  MaxNetworkDepth = 15
  
  // Commission Scope
  DefaultInitialContribution = 25000000
  MinWithdrawalAmount = 1000000
  
  // Club Scope
  ActivationFee = 25000000
  ```
- [ ] اجرای Seeder
- [ ] Commit: "Add system configuration seed data"

#### روز ۲۵-۲۶: Integration Testing کامل History
- [ ] تست ثبت History در تمام Commands
- [ ] تست Query های History
- [ ] تست Performance با ۱۰،۰۰۰ رکورد History
- [ ] بهینه‌سازی Index ها در صورت نیاز
- [ ] Commit: "Verify and optimize history recording"

#### روز ۲۷: مستندسازی Config و History
- [ ] ایجاد `CONFIG_GUIDE.md` با لیست تمام کلیدها
- [ ] ایجاد `AUDIT_GUIDE.md` برای نحوه استفاده از History
- [ ] Commit: "Add configuration and audit documentation"

---

### 🧪 فاز ۷: Testing کامل (روز ۲۸-۳۴)

#### روز ۲۸-۲۹: Unit Tests
- [ ] Tests برای تمام Handlers (با Mock)
- [ ] Tests برای Validators
- [ ] Tests برای `CalculateLegBalancesService`
- [ ] Coverage حداقل ۸۰٪

#### روز ۳۰-۳۱: Integration Tests
- [ ] تست کامل فلوی فعال‌سازی عضویت
- [ ] تست Worker با داده واقعی
- [ ] تست اتصال به دیتابیس
- [ ] تست Transaction Rollback

#### روز ۳۲-۳۳: End-to-End Tests
**سناریوی کامل:**
```
1. کاربر A شارژ می‌کند (56M)
2. فعال‌سازی عضویت (25M به استخر)
3. کاربر B و C به زیرمجموعه A می‌پیوندند
4. کاربر B دو نفر جذب می‌کند (D و E)
5. کاربر C دو نفر جذب می‌کند (F و G)
6. اجرای Worker هفتگی
7. بررسی کمیسیون دریافتی A, B, C
8. درخواست برداشت از کاربر A
9. تایید برداشت توسط Admin
10. بررسی تاریخچه کامل در History
```

#### روز ۳۴: Load Testing و Performance
- [ ] تست با ۱۰،۰۰۰ کاربر
- [ ] تست Worker با ۵۰۰ پرداخت همزمان
- [ ] Profiling و شناسایی Memory Leaks
- [ ] Commit: "Complete comprehensive testing"

---

### 🎨 فاز ۸: UI در BackOffice (روز ۳۵-۴۱)

```bash
cd /home/masoud/Apps/project/FourSat/BackOffice/src/BackOffice/Pages
mkdir -p Club Network Commission Configuration
```

#### روز ۳۵-۳۶: صفحات Club
- [ ] `Club/MembersList.razor`
  - لیست اعضای باشگاه
  - فیلتر IsActive، تاریخ
  - Pagination
- [ ] `Club/MemberDetails.razor`
  - جزئیات عضویت
  - تاریخچه تغییرات
- [ ] `Club/Features.razor`
  - مدیریت فیچرها
  - Grant/Revoke به کاربران

#### روز ۳۷-۳۸: صفحات Network
- [ ] `Network/Tree.razor`
  - نمایش درخت شبکه (Tree View Component)
  - Expand/Collapse
  - جستجوی کاربر
- [ ] `Network/Statistics.razor`
  - Dashboard آماری
  - Charts و نمودارها

#### روز ۳۹-۴۰: صفحات Commission و Configuration
- [ ] `Commission/Withdrawals.razor`
  - لیست درخواست‌های برداشت
  - تایید/رد
  - جستجو و فیلتر
- [ ] `Configuration/Settings.razor`
  - پنل تنظیمات
  - ویرایش مقادیر Config
  - نمایش تاریخچه تغییرات

#### روز ۴۱: تست و بهبود UI/UX
- [ ] تست تمام صفحات
- [ ] Responsive Design
- [ ] بهبود User Experience
- [ ] Commit: "Complete BackOffice UI for network-club system"

---

### 🛒 فاز ۹: فروشگاه باشگاه (روز ۴۲-۴۵)

#### روز ۴۲: به‌روزرسانی لیست محصولات
- [ ] فیلتر محصولات اختصاصی (`IsClubExclusive`)
- [ ] نمایش تخفیف باشگاه
- [ ] محاسبه قیمت نهایی

#### روز ۴۳-۴۴: لاجیک Checkout
- [ ] `CheckoutWithClubDiscountCommand.cs` + Handler
- [ ] محاسبه `totalDiscountAmount` و `totalCashAmount`
- [ ] کسر از `DiscountBalance`
- [ ] ثبت در `UserWalletChangeLog`
- [ ] Validator (بررسی موجودی کافی)

#### روز ۴۵: تست End-to-End خرید
- [ ] تست خرید با تخفیف
- [ ] تست خرید بدون تخفیف
- [ ] تست محصولات اختصاصی
- [ ] Commit: "Implement club shop with discount logic"

---

### 💸 فاز ۱۰: برداشت و تسویه (روز ۴۶-۴۹)

#### روز ۴۶: اتصال به سرویس پرداخت
- [ ] ایجاد `IPaymentGatewayService.cs`
- [ ] پیاده‌سازی برای درگاه مورد نظر
- [ ] تست اتصال

#### روز ۴۷: اتصال به دایا (خرید الماس)
- [ ] ایجاد `IDayaService.cs`
- [ ] API برای خرید الماس
- [ ] تست اتصال

#### روز ۴۸: پیاده‌سازی Withdrawal Flow
- [ ] اتصال `ProcessWithdrawalCommand` به سرویس‌ها
- [ ] Handle کردن خطاها
- [ ] Retry Mechanism

#### روز ۴۹: UAT و تست نهایی
- [ ] تست کامل فلوی برداشت نقدی
- [ ] تست کامل فلوی خرید الماس
- [ ] بررسی Audit Trail
- [ ] Commit: "Complete withdrawal and settlement flow"

---

### ✅ Checklist نهایی قبل از Production

#### کد و معماری
- [ ] Code Review کامل توسط تیم
- [ ] تست Coverage بالای ۸۰٪
- [ ] تمام TODO ها رفع شده
- [ ] مستندسازی کامل API

#### دیتابیس
- [ ] بررسی دقیق Migration Scripts
- [ ] Backup Strategy تعیین شده
- [ ] Index های بهینه اضافه شده
- [ ] Seed Data اجرا شده

#### Performance
- [ ] Load Testing انجام شده
- [ ] Memory Leaks بررسی شده
- [ ] Caching استراتژی تعیین شده
- [ ] Query Optimization انجام شده

#### Security
- [ ] Authorization برای تمام Endpoints
- [ ] Validation کامل ورودی‌ها
- [ ] Rate Limiting برای API های حساس
- [ ] Audit Logging فعال

#### Monitoring
- [ ] Logging جامع
- [ ] Health Checks تعریف شده
- [ ] Alert ها تنظیم شده
- [ ] Dashboard های مانیتورینگ

---

### 📝 نکات مهم حین پیاده‌سازی

#### Daily Routine
```bash
# صبح هر روز:
git pull origin develop
git checkout feature/network-club-system
dotnet restore
dotnet build

# عصر هر روز:
git add .
git commit -m "feat: [توضیح کار انجام شده]"
git push origin feature/network-club-system

# هفته‌ای یک بار:
git merge develop  # برای همگام‌سازی
```

#### Commit Message Convention
```
feat: Add ClubMembership entity
fix: Fix balance calculation in Worker
refactor: Optimize network tree query
test: Add unit tests for ActivateClubMembership
docs: Update configuration guide
```

#### Code Review Checklist
- [ ] کد Clean و قابل فهم است
- [ ] Validation کامل است
- [ ] Exception Handling مناسب است
- [ ] Logging کافی است
- [ ] History ثبت می‌شود
- [ ] Test coverage کافی است

---

### 🚨 Risk Management

| ریسک | احتمال | تاثیر | راه‌حل |
|------|--------|-------|--------|
| پیچیدگی الگوریتم تعادل | متوسط | بالا | شروع زودتر + تست گسترده |
| Performance Worker | متوسط | بالا | Profiling + بهینه‌سازی Query |
| Race Condition در Network | کم | بالا | Transaction + Lock |
| حجم داده History | بالا | متوسط | Archiving + Partitioning |
| تاخیر در UI | متوسط | کم | شروع موازی با Backend |

---

### 📞 نقاط ارتباطی و پشتیبانی

- **سوالات فنی Domain**: [نام مسئول Backend]
- **سوالات UI/UX**: [نام مسئول Frontend]
- **سوالات دیتابیس**: [نام DBA]
- **مدیریت پروژه**: [نام PM]

---

### 🎯 Definition of Done

یک Task زمانی Done است که:
- [ ] کد نوشته و Commit شده
- [ ] Unit Test نوشته شده (اگر لازم باشد)
- [ ] Code Review انجام شده
- [ ] مستندات به‌روز شده (اگر لازم باشد)
- [ ] در محیط Development تست شده
- [ ] هیچ Warning یا Error در Build نباشد

---

## ۱۱. متریک‌های کلیدی (KPIs)

### برای Business
- تعداد اعضای فعال باشگاه
- مجموع کمیسیون‌های پرداختی هر ماه
- میانگین تعادل هر کاربر در هفته
- نرخ تبدیل (Conversion Rate) به عضویت باشگاه
- درآمد از فروشگاه باشگاه

### برای Technical
- زمان اجرای Worker هفتگی
- تعداد خطاها در محاسبات
- میانگین زمان پاسخ API های شبکه
- حجم داده جداول جدید
- عمق متوسط درخت شبکه
- حجم رشد جداول History (رکورد/ماه)
- تعداد تغییرات Config در ماه
- میانگین زمان Query های History
- تعداد Audit Log های غیرمعمول

---

## ۱۲. سوالات متداول (FAQ)

**Q: اگر کاربری دو بار ۵۶ میلیون شارژ کند، چه می‌شود؟**
A: هر بار Balance و DiscountBalance افزایش می‌یابد، اما فقط یک‌بار می‌تواند عضو باشگاه شود (دکمه غیرفعال می‌شود).

**Q: آیا می‌توان موقعیت کاربر در شبکه را تغییر داد؟**
A: فقط با دسترسی مدیریتی و در شرایط خاص (مثلاً خطای ثبت) - تغییر بعد از محاسبات هفتگی ممنوع است.

**Q: اگر کاربری تعادل نزند، آیا امتیازش صفر می‌شود؟**
A: بله، در آن هفته امتیاز صفر دارد و کمیسیون نمی‌گیرد.

**Q: آیا تعادل‌های قبلی انباشته می‌شوند؟**
A: خیر، تعادل‌ها هفتگی محاسبه و ریست می‌شوند.

**Q: چه کسی می‌تواند درخواست برداشت را تایید کند؟**
A: فقط ادمین‌ها با نقش مشخص در BackOffice.

**Q: آیا می‌توان تنظیمات سیستم را بدون Deployment تغییر داد؟**
A: بله، تمام تنظیمات کلیدی در `SystemConfiguration` ذخیره می‌شوند و قابل تغییر آنلاین هستند. تاریخچه تغییرات نیز حفظ می‌شود.

**Q: چگونه می‌توان تاریخچه تغییرات یک کاربر را بررسی کرد؟**
A: از جداول History استفاده کنید:
- عضویت: `ClubMembershipHistory`
- شبکه: `NetworkMembershipHistory`
- کمیسیون: `CommissionPayoutHistory`
- کیف پول: `UserWalletChangeLog`

**Q: حداکثر تعداد تعادل هفتگی برای هر کاربر چقدر است؟**
A: توسط تنظیم `MaxWeeklyBalancesPerUser` در Config تعیین می‌شود (پیش‌فرض: ۳۰۰). این محدودیت از سوءاستفاده جلوگیری می‌کند.

**Q: اگر تنظیمات Config اشتباه وارد شود چه می‌شود؟**
A: تمام تغییرات در `SystemConfigurationHistory` ثبت می‌شود و قابل بازگردانی است. همچنین Validation در Handler ها وجود دارد.

---

## ۱۳. ضمیمه: مثال عددی کامل

### هفته اول:
```
کاربر A: فعال‌سازی (۲۵M به استخر)
  ├─ فرزند Left: کاربر B (فعال‌سازی ۲۵M)
  └─ فرزند Right: کاربر C (فعال‌سازی ۲۵M)

استخر هفته اول: ۷۵M
تعادل کاربر A: MIN(1, 1) = 1
تعادل کاربر B: 0
تعادل کاربر C: 0

مجموع تعادل‌ها: 1
ارزش هر امتیاز: 75M ÷ 1 = 75M

کمیسیون کاربر A: 1 × 75M = 75M
```

### هفته دوم:
```
کاربر B: جذب دو نفر (D و E) → تعادل ۱
کاربر C: جذب دو نفر (F و G) → تعادل ۱

استخر هفته دوم: ۴ × ۲۵M = ۱۰۰M
تعادل کاربر A: MIN(1, 1) = 1 (از B و C)
تعادل کاربر B: 1
تعادل کاربر C: 1

مجموع تعادل‌ها: 3
ارزش هر امتیاز: 100M ÷ 3 ≈ 33.33M

کمیسیون کاربر A: 1 × 33.33M = 33.33M
کمیسیون کاربر B: 1 × 33.33M = 33.33M
کمیسیون کاربر C: 1 × 33.33M = 33.33M
```

---

## ۱۴. مسیرهای مرتبط
- کد Domain: `CMS/src/CMSMicroservice.Domain/Entities/`
- کد Application: `CMS/src/CMSMicroservice.Application/ClubMembershipCQ/`, `NetworkBalanceCQ/`, ...
- Protobuf: `CMS/src/CMSMicroservice.Protobuf/Protos/`
- Worker: `CMS/src/CMSMicroservice.Infrastructure/BackgroundJobs/`
- مستند حاضر: `CMS/docs/network-club-commission-system.md`

---

**نسخه**: 1.1 (با History و Configuration)  
**تاریخ**: 2025-11-29  
**نویسنده**: تیم توسعه CMS  
**وضعیت**: آماده پیاده‌سازی - شامل Audit Trail و تنظیمات پویا

### تغییرات نسخه 1.1:
- ✅ اضافه شدن ۴ جدول History برای Audit کامل
- ✅ اضافه شدن SystemConfiguration برای تنظیمات پویا
- ✅ سقف تعادل هفتگی قابل تنظیم از Config
- ✅ Enum های Action برای تاریخچه‌ها
- ✅ گسترش ماژول ConfigurationCQ
- ✅ بهبود بخش Audit و Compliance
- ✅ افزودن فاز ۷ به Roadmap
- ✅ گسترش FAQ و متریک‌ها
