67 KiB
سیستم باشگاه مشتریان و محاسبه کمیسیون شبکه
خلاصه اجرایی
این سند تحلیل جامع و معماری پیشنهادی برای پیادهسازی سیستم باشگاه مشتریان (Club Membership) و محاسبه کمیسیون شبکهای (MLM Binary Plan) را ارائه میدهد. این سیستم امکان مدیریت سه نوع کیف پول، فروشگاه اختصاصی با تخفیف، و توزیع عادلانه کمیسیون بر اساس تعادل شبکه را فراهم میکند.
۱. مفاهیم کلیدی
۱.۱ کیف پولهای سهگانه
هر کاربر سه نوع کیف پول دارد:
- کیف پول اصلی (Balance): برای خرید از فروشگاه عمومی بازار
- کیف پول تخفیف (DiscountBalance): فقط برای خرید از فروشگاه باشگاه مشتریان (محدود به درصد تخفیف محصولات)
- کیف پول طلایی/کارمزد (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 (عضویت باشگاه مشتریان)
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 (فیچرهای باشگاه)
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 (جدول واسط: کاربر–فیچر)
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 (ساختار شبکه و تعادلهای هفتگی)
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 (استخر کارمزد هفتگی)
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 (پرداخت کمیسیون به کاربران)
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:
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
برای ردیابی کامل جابجایی در شبکه باینری:
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
تاریخچه کامل تغییرات پرداخت کمیسیونها:
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
تاریخچه تغییرات تنظیمات سیستم:
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)
public enum ConfigurationScope
{
System = 0, // تنظیمات کلی سیستم
Network = 1, // تنظیمات شبکه باینری
Club = 2, // تنظیمات باشگاه مشتریان
Commission = 3 // تنظیمات کمیسیون
}
۲.۸.۲ SystemConfiguration
جدول نگهداری تنظیمات پویا که بدون تغییر کد قابل تغییر است:
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 جدید
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
// افزودن فیلدهای شبکه باینری
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
// موجودی ریالی اصلی
public long Balance { get; set; }
// موجودی شبکه/کارمزد (کیف پول طلایی)
public long NetworkBalance { get; set; }
// موجودی تخفیف (فقط برای خرید از فروشگاه باشگاه)
public long DiscountBalance { get; set; }
۳.۳ Products
// آیا این محصول فقط در فروشگاه باشگاه موجود است
public bool IsClubExclusive { get; set; }
// درصد تخفیف باشگاه (0 تا 100)
public int ClubDiscountPercent { get; set; }
۳.۴ UserWalletChangeLog
افزودن نوع جدید تراکنش:
// در 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
زمانبندی: هر یکشنبه ساعت ۲۳:۵۹ (یا دوشنبه ۰۰:۰۱)
مراحل اجرایی:
گام ۱: بستن هفته قبل و ایجاد استخر جدید
var currentWeek = GetCurrentWeekNumber(); // مثلاً "2025-W48"
var previousWeek = GetPreviousWeekNumber();
// بستن استخر هفته قبل (اگر هنوز باز است)
await CloseWeeklyPool(previousWeek);
// ایجاد استخر جدید
await InitializeWeeklyPool(currentWeek);
گام ۲: محاسبه تعادلهای شبکه
// دریافت تمام اعضای باشگاه فعال
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):
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;
}
گام ۳: محاسبه استخر و ارزش امتیاز
// جمع مبالغ استخر
var totalPoolAmount = await SumPoolContributions(previousWeek);
// جمع تعادلهای کل سیستم
var totalBalances = await SumTotalBalances(previousWeek);
// محاسبه ارزش هر امتیاز
var valuePerBalance = totalBalances > 0 ? totalPoolAmount / totalBalances : 0;
// بهروزرسانی استخر
await UpdatePoolValue(previousWeek, totalPoolAmount, totalBalances, valuePerBalance);
گام ۴: توزیع کمیسیونها
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"
});
}
گام ۵: ریست تعادلها
// علامتگذاری تعادلهای هفته قبل به عنوان منقضی
await ExpireWeeklyBalances(previousWeek);
۵.۲ نکات حیاتی Worker
- Transaction Scope: تمام مراحل باید داخل یک تراکنش دیتابیس باشند تا در صورت خطا Rollback شود
- Idempotency: بررسی اینکه استخر هفته قبل قبلاً محاسبه نشده باشد (
IsCalculated = false) - Logging: ثبت دقیق هر مرحله برای Audit و عیبیابی
- Notification: ارسال اعلان به کاربرانی که کمیسیون دریافت کردهاند
- Error Handling: در صورت خطا، ارسال آلارم به تیم فنی و تلاش مجدد
۶. لاجیک فروشگاه و سبد خرید
۶.۱ نمایش محصولات
// در 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 و محاسبه پرداخت
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
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
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
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 های پیشنهادی:
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
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.csWithdrawalMethod.csNetworkLeg.csClubMembershipAction.csNetworkMembershipAction.csCommissionPayoutAction.csConfigurationScope.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.csNetworkMembershipHistory.csCommissionPayoutHistory.csSystemConfigurationHistory.cs- Commit: "Add history entities for audit trail"
بعدازظهر:
- بهروزرسانی
User.cs:- افزودن
NetworkParentId,LegPosition - افزودن Navigation Properties
- افزودن
- بهروزرسانی
UserWallet.cs:- افزودن
NetworkBalance,DiscountBalance
- افزودن
- بهروزرسانی
Products.cs:- افزودن
IsClubExclusive,ClubDiscountPercent
- افزودن
- بهروزرسانی
TransactionTypeenum:- افزودن
NetworkCommission,ClubActivation,DiscountWalletCharge
- افزودن
- Commit: "Update existing entities for network-club integration"
روز ۵: EF Configurations و Migration
صبح:
- ایجاد Configuration کلاسها در
Infrastructure/Persistence/Configurations/:ClubMembershipConfiguration.csClubFeatureConfiguration.csUserClubFeatureConfiguration.csSystemConfigurationConfiguration.csNetworkWeeklyBalanceConfiguration.csWeeklyCommissionPoolConfiguration.csUserCommissionPayoutConfiguration.cs- History Configurations (۴ فایل)
بعدازظهر:
- اضافه کردن Index های حیاتی:
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:
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 نیاز دارند.
cd CMSMicroservice.Application
mkdir -p ConfigurationCQ/Commands ConfigurationCQ/Queries ConfigurationCQ/DTOs
صبح:
Commands/SetConfigurationValueCommand.cs+ HandlerCommands/DeactivateConfigurationCommand.cs+ Handler- Validators برای Commands
- افزودن ثبت
SystemConfigurationHistoryدر Handler
بعدازظهر:
Queries/GetConfigurationValueQuery.cs+ HandlerQueries/GetConfigurationsByScopeQuery.cs+ HandlerQueries/GetConfigurationHistoryQuery.cs+ HandlerDTOs/ConfigurationDto.cs,ConfigurationHistoryDto.cs- تست Unit برای Handlers
- Commit: "Implement ConfigurationCQ module"
روز ۷: ClubMembershipCQ - Commands
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+ HandlerQueries/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+ HandlerCommands/UpdateClubFeatureCommand.cs+ HandlerCommands/DeleteClubFeatureCommand.cs+ Handler (Soft Delete)
بعدازظهر:
Commands/GrantFeatureToUserCommand.cs+ HandlerCommands/RevokeFeatureFromUserCommand.cs+ HandlerQueries/GetAllClubFeaturesQuery.cs+ HandlerQueries/GetUserClubFeaturesQuery.cs+ Handler- Commit: "Implement ClubFeatureCQ module"
🌳 فاز ۳: شبکه باینری (روز ۱۰-۱۴)
روز ۱۰: NetworkBalanceCQ - Setup و RecordNetworkJoin
mkdir -p NetworkBalanceCQ/Commands NetworkBalanceCQ/Queries NetworkBalanceCQ/DTOs
صبح:
Commands/RecordNetworkJoinCommand.cs+ Handler- Validator پیچیده:
- بررسی ظرفیت شاخه والد (از Config:
MaxChildrenPerLeg) - بررسی عدم تکراری بودن
- بررسی عمق مجاز (از Config:
MaxNetworkDepth)
- بررسی ظرفیت شاخه والد (از Config:
- تنظیم
User.NetworkParentIdوUser.LegPosition - ثبت
NetworkMembershipHistoryبا Action=Join
- Validator پیچیده:
بعدازظهر:
- تست Validation کامل با سناریوهای مختلف
- Commit: "Implement RecordNetworkJoin command"
روز ۱۱: UpdateNetworkPosition و الگوریتم تعادل
صبح:
Commands/UpdateNetworkPositionCommand.cs+ Handler- ثبت History با OldParentId/NewParentId
- محدودیت: فقط قبل از محاسبات هفتگی
- نیاز به نقش Admin
بعدازظهر:
- پیادهسازی
CalculateLegBalancesService.cs: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+ HandlerQueries/GetNetworkStatisticsQuery.cs+ Handler- تعداد کل اعضا
- عمق متوسط شبکه
- توزیع چپ/راست
روز ۱۳:
Queries/GetNetworkMembershipHistoryQuery.cs+ HandlerDTOs/NetworkNodeDto.cs,NetworkStatisticsDto.cs- تست کامل ماژول
- Commit: "Complete NetworkBalanceCQ module"
روز ۱۴: تست عملکرد و بهینهسازی
- تست با ۱۰۰۰ کاربر Mock
- Profiling و شناسایی Bottlenecks
- بهینهسازی Queries (اضافه کردن Index در صورت نیاز)
- Commit: "Optimize network balance queries"
💰 فاز ۴: کمیسیون و Worker (روز ۱۵-۲۰)
روز ۱۵-۱۶: CommissionPoolCQ
mkdir -p CommissionPoolCQ/Commands CommissionPoolCQ/Queries
روز ۱۵ صبح:
Commands/InitializeWeeklyPoolCommand.cs+ HandlerCommands/AddToWeeklyPoolCommand.cs+ Handler- فراخوانی از
ActivateClubMembership
- فراخوانی از
روز ۱۵ بعدازظهر:
Commands/CalculatePoolValueCommand.cs+ Handler- محاسبه
ValuePerBalance = TotalPoolAmount ÷ TotalBalances
- محاسبه
Commands/CloseWeeklyPoolCommand.cs+ Handler- تنظیم
IsCalculated = true
- تنظیم
روز ۱۶:
Queries/GetCurrentWeekPoolQuery.cs+ HandlerQueries/GetPoolHistoryQuery.cs+ Handler (با Pagination)- تست ماژول
- Commit: "Implement CommissionPoolCQ module"
روز ۱۷-۱۸: CommissionPayoutCQ
mkdir -p CommissionPayoutCQ/Commands CommissionPayoutCQ/Queries
روز ۱۷ صبح:
Commands/CreatePayoutRecordCommand.cs+ Handler- ثبت
UserCommissionPayout - ثبت
CommissionPayoutHistoryبا Action=Created
- ثبت
روز ۱۷ بعدازظهر:
Commands/RequestWithdrawalCommand.cs+ Handler- Validator: بررسی
MinWithdrawalAmountاز Config - ثبت History با Action=WithdrawRequested
- Validator: بررسی
Commands/ProcessWithdrawalCommand.cs+ Handler- نیاز به نقش Admin
- ثبت History با Action=Withdrawn یا Cancelled
روز ۱۸ صبح:
Commands/CancelPayoutCommand.cs+ HandlerQueries/GetUserCommissionHistoryQuery.cs+ HandlerQueries/GetPendingWithdrawalsQuery.cs+ Handler (برای Admin)
روز ۱۸ بعدازظهر:
Queries/GetCommissionSummaryQuery.cs+ Handler- مجموع، ماهانه، سالانه
Queries/GetCommissionPayoutAuditQuery.cs+ Handler- نمایش تاریخچه کامل از History
- Commit: "Implement CommissionPayoutCQ module"
روز ۱۹-۲۰: Background Worker هفتگی
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
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
cd CMSMicroservice.WebApi/Services
روز ۲۲:
ClubMembershipGrpcService.csNetworkBalanceGrpcService.cs
روز ۲۳:
CommissionPayoutGrpcService.csConfigurationGrpcService.cs- تست gRPC با BloomRPC یا Postman
- Commit: "Implement gRPC service implementations"
📊 فاز ۶: History و Configuration Seed (روز ۲۴-۲۷)
روز ۲۴: Seed Data برای SystemConfiguration
cd CMSMicroservice.Infrastructure/Persistence/Seeds
- ایجاد
SystemConfigurationSeeder.cs - تعریف تنظیمات پیشفرض:
// 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 (روز ۳۵-۴۱)
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
# صبح هر روز:
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 و متریکها