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

1936 lines
67 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# سیستم باشگاه مشتریان و محاسبه کمیسیون شبکه
## خلاصه اجرایی
این سند تحلیل جامع و معماری پیشنهادی برای پیاده‌سازی سیستم باشگاه مشتریان (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 و متریک‌ها