Files
CMS/docs/network-club-commission-system-v1.1.md

906 lines
29 KiB
Markdown
Raw Normal View History

# سیستم باشگاه مشتریان و محاسبه کمیسیون شبکه
## خلاصه اجرایی
این سند تحلیل جامع و معماری پیشنهادی برای پیاده‌سازی سیستم باشگاه مشتریان (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)`
- تعادل‌ها به صورت هفتگی محاسبه و بعد از توزیع کمیسیون، ریست می‌شوند
### ۱.۴ محاسبه کمیسیون هفتگی
```text
مبلغ ریالی هر امتیاز = (مجموع مبالغ استخر) ÷ (مجموع تعادل‌های کل سیستم)
کمیسیون هر کاربر = (تعداد تعادل کاربر) × (مبلغ ریالی هر امتیاز)
```
**مثال**:
- کاربر 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; }
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; }
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; }
public int TotalBalances { get; set; }
// مبلغی که این کاربر همان هفته به استخر اضافه کرده (معمولاً InitialContribution)
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; }
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; }
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 (جداول لاگ)
#### ۲.۷.۱ `ClubMembershipHistory`
لاگ تغییرات مهم روی عضویت باشگاه (فعال‌سازی، غیرفعال‌سازی، ویرایش):
```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; }
// Activated / Deactivated / Updated / ManualFix
public string Action { get; set; }
public string? Reason { get; set; }
}
```
#### ۲.۷.۲ `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; }
// Join / Move / Remove
public string Action { get; set; }
public string? Reason { get; set; }
}
```
- هر بار `RecordNetworkJoin` یا `UpdateNetworkPosition` صدا زده می‌شود، باید یک رکورد در این جدول نوشته شود.
- این جدول مرجع اصلی برای بازسازی درخت شبکه در زمان‌های گذشته است.
#### ۲.۷.۳ `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; }
// Created / Paid / WithdrawRequested / Withdrawn / Cancelled / ManualFix
public string Action { get; set; }
public string? PerformedBy { get; set; } // UserId یا System
public string? Reason { get; set; }
}
```
- اگر بعداً بفهمیم یک پرداخت اشتباه بوده و اصلاحش کنیم، اینجا قابل ردیابی است.
- برای گزارش‌گیری Audit کامل پرداخت‌ها، این جدول استفاده می‌شود.
#### ۲.۷.۴ `SystemConfigurationHistory`
تاریخچه تغییرات تنظیمات (Config) برای این‌که بعداً بدانیم در هر زمان چه محدودیتی فعال بوده:
```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; }
}
```
---
### ۲.۸ موجودیت‌های 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; } // System / Network / Club / Commission
// مثل: "MaxWeeklyBalancesPerUser", "MinContributionAmount", ...
public string Key { get; set; }
// مقدار به‌صورت رشته - تفسیر در لایه Application
public string Value { get; set; }
// برای UI و Validation (Int / Decimal / Bool / String / Json)
public string? DataType { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
}
```
**مثال کانفیگ‌های مرتبط با شبکه:**
- `Scope = Network`, `Key = "MaxWeeklyBalancesPerUser"`, `Value = "300"`
- `Scope = Network`, `Key = "MaxChildrenPerLeg"`, `Value = "1"`
- `Scope = Commission`, `Key = "DefaultInitialContribution"`, `Value = "25000000"`
> نکته: هر بار که مقدار `SystemConfiguration` تغییر می‌کند، یک رکورد در `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
}
```
---
## ۳. تغییرات در موجودیت‌های موجود
### ۳.۱ `User`
افزودن فیلدهای مربوط به شبکه باینری و ناوبری:
```csharp
public class User : BaseAuditableEntity
{
// ...
public long? NetworkParentId { get; set; }
public virtual User? NetworkParent { get; set; }
public NetworkLeg? LegPosition { get; set; }
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; }
public virtual ICollection<UserClubFeature> UserClubFeatures { get; set; }
}
```
### ۳.۲ `UserWallet`
```csharp
public class UserWallet : BaseAuditableEntity
{
// موجودی ریالی اصلی
public long Balance { get; set; }
// موجودی شبکه/کارمزد (کیف پول طلایی)
public long NetworkBalance { get; set; }
// موجودی تخفیف (فقط برای خرید از فروشگاه باشگاه)
public long DiscountBalance { get; set; }
// ...
}
```
### ۳.۳ `Products`
```csharp
public class Product : BaseAuditableEntity
{
// ...
// آیا این محصول فقط در فروشگاه باشگاه موجود است
public bool IsClubExclusive { get; set; }
// درصد تخفیف باشگاه (0 تا 100)
public int ClubDiscountPercent { get; set; }
// ...
}
```
### ۳.۴ `UserWalletChangeLog`
افزودن نوع جدید تراکنش:
```csharp
public enum TransactionType
{
// ...
NetworkCommission = 10, // دریافت کمیسیون شبکه
ClubActivation = 11, // فعال‌سازی عضویت باشگاه
DiscountWalletCharge = 12, // شارژ کیف پول تخفیف
}
```
---
## ۴. معماری ماژول‌های جدید (Application / CQRS)
### ۴.۱ `ClubMembershipCQ/`
#### Commands
- **ActivateClubMembership**: فعال‌سازی عضویت باشگاه (کسر ۲۵ میلیون و اضافه به استخر)
- **DeactivateClubMembership**: غیرفعال‌سازی عضویت
- **UpdateClubMembership**: به‌روزرسانی اطلاعات عضویت
#### Queries
- **GetUserClubStatus**: دریافت وضعیت عضویت کاربر
- **GetAllClubMembersByFilter**: لیست اعضای باشگاه با فیلتر
### ۴.۲ `ClubFeatureCQ/`
#### Commands
- **CreateClubFeature**: ایجاد فیچر جدید
- **UpdateClubFeature**: ویرایش فیچر
- **DeleteClubFeature**: حذف فیچر
- **GrantFeatureToUser**: فعال‌سازی فیچر برای کاربر
- **RevokeFeatureFromUser**: غیرفعال‌سازی فیچر از کاربر
#### Queries
- **GetAllClubFeatures**: لیست تمام فیچرها
- **GetUserClubFeatures**: لیست فیچرهای فعال یک کاربر
### ۴.۳ `NetworkBalanceCQ/`
#### Commands
- **RecordNetworkJoin**: ثبت ورود کاربر به شبکه باینری (تعیین والد و شاخه)
- حتماً باید یک رکورد در `NetworkMembershipHistory` ایجاد کند.
- **UpdateNetworkPosition**: تغییر موقعیت در شبکه (مدیریتی)
- هر تغییر، یک رکورد History.
- **CalculateWeeklyBalances**: محاسبه تعادل‌های هفتگی (فراخوانی از Worker)
#### Queries
- **GetUserNetworkTree**: دریافت درخت زیرمجموعه‌های کاربر (چند سطح)
- **GetUserWeeklyBalances**: دریافت تعادل‌های هفتگی یک کاربر
- **GetNetworkStatistics**: آمار کلی شبکه (تعداد اعضا، عمق، تعادل)
### ۴.۴ `CommissionPoolCQ/`
#### Commands
- **InitializeWeeklyPool**: ایجاد استخر جدید برای هفته
- **AddToWeeklyPool**: افزودن مبلغ به استخر هفتگی (هنگام فعال‌سازی عضویت)
- **CalculatePoolValue**: محاسبه ارزش هر امتیاز
- **DistributeCommissions**: توزیع کمیسیون‌ها به کاربران (Worker)
- **CloseWeeklyPool**: بستن استخر پس از توزیع
#### Queries
- **GetCurrentWeekPool**: دریافت اطلاعات استخر هفته جاری
- **GetPoolHistory**: تاریخچه استخرهای قبلی با فیلتر
### ۴.۵ `CommissionPayoutCQ/`
#### Commands
- **CreatePayoutRecord**: ثبت پرداخت کمیسیون (اتوماتیک از Worker)
- همراه با ایجاد رکورد در `CommissionPayoutHistory` (Action = Created).
- **RequestWithdrawal**: درخواست برداشت کمیسیون (نقدی یا الماس)
- History با Action = WithdrawRequested.
- **ProcessWithdrawal**: پردازش درخواست برداشت (تایید/رد ادمین)
- تغییر Status + History.
- **CancelPayout**: لغو پرداخت
#### Queries
- **GetUserCommissionHistory**: تاریخچه کمیسیون‌های دریافتی کاربر
- **GetPendingWithdrawals**: لیست درخواست‌های برداشت در انتظار (برای ادمین)
- **GetCommissionSummary**: خلاصه درآمد کمیسیون (مجموع، ماهانه، سالانه)
### ۴.۶ `ConfigurationCQ/`
#### Commands
- **SetConfigurationValue**: ثبت/ویرایش یک تنظیم (SystemConfiguration)
- هر تغییر باید در `SystemConfigurationHistory` ثبت شود.
- **DeactivateConfiguration**: غیرفعال‌سازی یک تنظیم
#### Queries
- **GetConfigurationValue**: دریافت مقدار یک Key
- **GetConfigurationByScope**: لیست تنظیمات یک Scope (مثلاً Network)
---
## ۵. Background Worker/Job (محاسبات هفتگی)
### ۵.۱ `WeeklyNetworkCommissionWorker`
**زمان‌بندی**: هر یکشنبه ساعت ۲۳:۵۹ (یا دوشنبه ۰۰:۰۱)
**مراحل اجرایی (High-level):**
#### گام ۱: بستن هفته قبل و ایجاد استخر جدید
```csharp
var currentWeek = GetCurrentWeekNumber(); // مثلاً "2025-W48"
var previousWeek = GetPreviousWeekNumber();
await CloseWeeklyPool(previousWeek);
await InitializeWeeklyPool(currentWeek);
```
#### گام ۲: محاسبه تعادل‌های شبکه
```csharp
var maxBalancesPerUser = GetConfig<int>("MaxWeeklyBalancesPerUser", scope: ConfigurationScope.Network);
var activeMembers = await GetActiveClubMembers();
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);
// اعمال محدودیت کانفیگ (مثلاً حداکثر 300 تعادل برای هر کاربر)
if (totalBalances > maxBalancesPerUser)
totalBalances = maxBalancesPerUser;
await RecordWeeklyBalance(new NetworkWeeklyBalance {
UserId = member.UserId,
WeekNumber = previousWeek,
LeftLegBalances = leftBalances,
RightLegBalances = rightBalances,
TotalBalances = totalBalances,
WeeklyPoolContribution = member.InitialContribution,
CalculatedAt = DateTime.UtcNow
});
}
```
#### الگوریتم بازگشتی محاسبه تعادل شاخه
```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); // داخلش CommissionPayoutHistory هم ثبت می‌شود
await AddToNetworkBalance(balance.UserId, payoutAmount);
await RecordWalletChange(new UserWalletChangeLog {
WalletId = balance.UserId,
// PreviousBalance / AfterBalance پر می‌شود
Amount = payoutAmount,
TransactionType = TransactionType.NetworkCommission,
ReferenceId = payout.Id.ToString()
});
payout.Status = CommissionPayoutStatus.Paid;
payout.PaidAt = DateTime.UtcNow;
await UpdatePayout(payout);
await AddCommissionHistory(payout, "Paid");
}
```
#### گام ۵: ریست تعادل‌ها
```csharp
await ExpireWeeklyBalances(previousWeek);
```
---
## ۶. لاجیک فروشگاه و سبد خرید
### ۶.۱ نمایش محصولات
```csharp
var query = _context.Products.Where(p => !p.IsDeleted);
if (!user.ClubMembership?.IsActive ?? true)
{
query = query.Where(p => !p.IsClubExclusive);
}
// اگر کاربر عضو است، قیمت با تخفیف باشگاه محاسبه می‌شود
```
### ۶.۲ استفاده از کیف پول تخفیف در Checkout
(خلاصه‌سازی شده در کد اصلی از DiscountBalance استفاده می‌شود و ChangeLog ثبت می‌گردد.)
---
## ۷. سناریوی کامل فعال‌سازی عضویت
### مرحله ۱: شارژ اولیه
```text
کاربر → پرداخت ۵۶ میلیون (دایا/درگاه)
UserWallet.Balance += 56,000,000
UserWallet.DiscountBalance += 56,000,000
```
### مرحله ۲: فعال‌سازی عضویت
```text
کاربر → کلیک روی دکمه «عضویت در باشگاه»
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"
```
### مرحله ۳: محاسبه هفتگی (Worker)
(مطابق بخش ۵)
### مرحله ۴: برداشت کمیسیون
```text
کاربر → درخواست برداشت
API: RequestWithdrawal (Cash یا Diamond)
ادمین → تایید درخواست
1. اگر Cash:
- واریز به حساب بانکی
- NetworkBalance -= مبلغ
2. اگر Diamond:
- خرید الماس از دایا
- NetworkBalance -= مبلغ
```
همراه با ثبت رکورد در `CommissionPayoutHistory` (Action = WithdrawRequested / Withdrawn).
---
## ۸. پروتوباف و 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;
}
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
- حذف کاربر نباید ساختار شبکه را خراب کند
### ۹.۲ Transaction Management
- Worker باید تمام مراحل را در یک TransactionScope انجام دهد
- در صورت شکست، Rollback کامل
### ۹.۳ Idempotency
- محاسبه هفتگی برای یک WeekNumber فقط یک‌بار
- بررسی `WeeklyCommissionPool.IsCalculated` قبل از شروع
### ۹.۴ Performance
- Caching درخت شبکه برای کاربران پرحجم
- Index روی `WeekNumber`, `UserId`, `NetworkParentId`
### ۹.۵ Audit و Compliance
- همه تغییرات کیف پول در `UserWalletChangeLog`
- همه پرداخت‌های کمیسیون در `UserCommissionPayout` + `CommissionPayoutHistory`
- تغییرات شبکه در `NetworkMembershipHistory`
- تغییرات تنظیمات در `SystemConfigurationHistory`
### ۹.۶ Security
- محدودیت تعداد درخواست برداشت
- تایید دو مرحله‌ای برای برداشت‌های بالا
- Audit Log برای عملیات حساس
---
## ۱۰. مراحل پیاده‌سازی (Roadmap)
(مطابق نسخه قبلی فاز ۱ تا ۶)
---
## ۱۱. متریک‌های کلیدی (KPIs)
- تعداد اعضای فعال باشگاه
- مجموع کمیسیون‌های پرداختی هر ماه
- میانگین تعادل هر کاربر در هفته
- نرخ تبدیل به عضویت باشگاه
- زمان اجرای Worker، تعداد خطاها، عمق درخت، حجم داده History و …
---
## ۱۲. سوالات متداول (FAQ)
(همان سوالات قبلی + می‌توان سوالات مربوط به سقف تعادل و تنظیمات را اضافه کرد.)
---
## ۱۳. ضمیمه: مثال عددی کامل
(مثال دو هفته‌ای A, B, C, D, E, F, G مثل نسخه قبلی.)
---
## ۱۴. مسیرهای مرتبط
- Domain: `CMS/src/CMSMicroservice.Domain/Entities/`
- Application: `CMS/src/CMSMicroservice.Application/ClubMembershipCQ/`, `NetworkBalanceCQ/`, `CommissionPoolCQ/`, `CommissionPayoutCQ/`, `ConfigurationCQ/`
- Protobuf: `CMS/src/CMSMicroservice.Protobuf/Protos/`
- Worker: `CMS/src/CMSMicroservice.Infrastructure/BackgroundJobs/`
- مستند حاضر: `CMS/docs/network-club-commission-system.md`
**نسخه**: 1.1
**تاریخ**: 2025-11-29
**نویسنده**: تیم توسعه CMS
**وضعیت**: آماده پیاده‌سازی (با History و Config)