# سیستم باشگاه مشتریان و محاسبه کمیسیون شبکه ## خلاصه اجرایی این سند تحلیل جامع و معماری پیشنهادی برای پیاده‌سازی سیستم باشگاه مشتریان (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 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 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 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 NetworkChildren { get; set; } public virtual ClubMembership? ClubMembership { get; set; } public virtual ICollection NetworkWeeklyBalances { get; set; } public virtual ICollection 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( "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 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 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 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 و متریک‌ها