1936 lines
67 KiB
Markdown
1936 lines
67 KiB
Markdown
|
|
# سیستم باشگاه مشتریان و محاسبه کمیسیون شبکه
|
|||
|
|
|
|||
|
|
## خلاصه اجرایی
|
|||
|
|
این سند تحلیل جامع و معماری پیشنهادی برای پیادهسازی سیستم باشگاه مشتریان (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 و متریکها
|