# سیستم باشگاه مشتریان و محاسبه کمیسیون شبکه ## خلاصه اجرایی این سند تحلیل جامع و معماری پیشنهادی برای پیاده‌سازی سیستم باشگاه مشتریان (Club Membership) و محاسبه کمیسیون شبکه‌ای (MLM Binary Plan) را ارائه می‌دهد. این سیستم امکان مدیریت سه نوع کیف پول، فروشگاه اختصاصی با تخفیف، و توزیع عادلانه کمیسیون بر اساس تعادل شبکه را فراهم می‌کند. --- ## ۱. مفاهیم کلیدی ### ۱.۱ کیف پول‌های سه‌گانه هر کاربر سه نوع کیف پول دارد: 1. **کیف پول اصلی (Balance)**: برای خرید از فروشگاه عمومی بازار 2. **کیف پول تخفیف (DiscountBalance)**: فقط برای خرید از فروشگاه باشگاه مشتریان (محدود به درصد تخفیف محصولات) 3. **کیف پول طلایی/کارمزد (NetworkBalance)**: دریافتی از کمیسیون شبکه‌ای - قابل برداشت نقدی یا خرید الماس از دایا ### ۱.۲ فعال‌سازی عضویت - کاربر ۵۶ میلیون تومان پرداخت می‌کند (از طریق دایا یا درگاه) - سیستم به صورت خودکار: - `Balance += 56M` (کیف پول اصلی) - `DiscountBalance += 56M` (کیف پول تخفیف) - کاربر دکمه «عضویت در باشگاه» را می‌زند: - `25M` به استخر کمیسیون هفتگی اضافه می‌شود - کاربر در شبکه باینری (Binary Tree) قرار می‌گیرد ### ۱.۳ شبکه باینری (Binary MLM Plan) - هر کاربر حداکثر دو زیرمجموعه دارد: **دست راست** و **دست چپ** - تعادل (Balance): زمانی که هر دو شاخه دارای اعضای جدید شوند، یک تعادل ایجاد می‌شود - **فرمول تعادل**: `UserBalances = MIN(LeftLegBalances, RightLegBalances)` - تعادل‌ها به صورت هفتگی محاسبه و بعد از توزیع کمیسیون، ریست می‌شوند ### ۱.۴ محاسبه کمیسیون هفتگی ```text مبلغ ریالی هر امتیاز = (مجموع مبالغ استخر) ÷ (مجموع تعادل‌های کل سیستم) کمیسیون هر کاربر = (تعداد تعادل کاربر) × (مبلغ ریالی هر امتیاز) ``` **مثال**: - کاربر A: خودش ۱ تعادل + زیرمجموعه‌هایش ۲ تعادل = **۳ امتیاز** - استخر هفتگی: `175M` - مجموع امتیازهای سیستم: `5` - ارزش هر امتیاز: `175M ÷ 5 = 35M` - کمیسیون کاربر A: `3 × 35M = 105M` --- ## ۲. موجودیت‌های جدید (Domain Entities) ### ۲.۱ `ClubMembership` (عضویت باشگاه مشتریان) ```csharp public class ClubMembership : BaseAuditableEntity { public long UserId { get; set; } public virtual User User { get; set; } public bool IsActive { get; set; } public DateTime? ActivatedAt { get; set; } // مبلغ اولیه پرداختی برای فعال‌سازی (معمولاً ۲۵ میلیون) public long InitialContribution { get; set; } // مجموع درآمد کارمزد تاکنون public long TotalEarned { get; set; } public virtual ICollection UserClubFeatures { get; set; } } ``` ### ۲.۲ `ClubFeature` (امکانات باشگاه) ```csharp public class ClubFeature : BaseAuditableEntity { public string Title { get; set; } public string? Description { get; set; } public bool IsActive { get; set; } public int? RequiredPoints { get; set; } public int SortOrder { get; set; } public virtual ICollection UserClubFeatures { get; set; } } ``` ### ۲.۳ `UserClubFeature` (امتیاز/فیچرهای فعال برای کاربر) ```csharp public class UserClubFeature : BaseAuditableEntity { public long UserId { get; set; } public virtual User User { get; set; } public long ClubFeatureId { get; set; } public virtual ClubFeature ClubFeature { get; set; } public DateTime GrantedAt { get; set; } public string? Notes { get; set; } } ``` ### ۲.۴ `NetworkWeeklyBalance` (تعادل هفتگی شبکه) ```csharp public class NetworkWeeklyBalance : BaseAuditableEntity { public long UserId { get; set; } public virtual User User { get; set; } // مثلاً "2025-W48" public string WeekNumber { get; set; } public int LeftLegBalances { get; set; } public int RightLegBalances { get; set; } public int TotalBalances { get; set; } // مبلغی که این کاربر همان هفته به استخر اضافه کرده (معمولاً InitialContribution) public long WeeklyPoolContribution { get; set; } public DateTime? CalculatedAt { get; set; } public bool IsExpired { get; set; } } ``` ### ۲.۵ `WeeklyCommissionPool` (استخر کمیسیون هفتگی) ```csharp public class WeeklyCommissionPool : BaseAuditableEntity { public string WeekNumber { get; set; } public long TotalPoolAmount { get; set; } public int TotalBalances { get; set; } public long ValuePerBalance { get; set; } public bool IsCalculated { get; set; } public DateTime? CalculatedAt { get; set; } public virtual ICollection UserCommissionPayouts { get; set; } } ``` ### ۲.۶ `UserCommissionPayout` (پرداخت کمیسیون به کاربر) ```csharp public class UserCommissionPayout : BaseAuditableEntity { public long UserId { get; set; } public virtual User User { get; set; } public string WeekNumber { get; set; } public long WeeklyPoolId { get; set; } public virtual WeeklyCommissionPool WeeklyPool { get; set; } public int BalancesEarned { get; set; } public long ValuePerBalance { get; set; } public long TotalAmount { get; set; } public CommissionPayoutStatus Status { get; set; } public DateTime? PaidAt { get; set; } public WithdrawalMethod? WithdrawalMethod { get; set; } public string? IbanNumber { get; set; } public DateTime? WithdrawnAt { get; set; } } ``` --- ### ۲.۷ موجودیت‌های History (جداول لاگ) #### ۲.۷.۱ `ClubMembershipHistory` لاگ تغییرات مهم روی عضویت باشگاه (فعال‌سازی، غیرفعال‌سازی، ویرایش): ```csharp public class ClubMembershipHistory : BaseAuditableEntity { public long ClubMembershipId { get; set; } public long UserId { get; set; } public bool OldIsActive { get; set; } public bool NewIsActive { get; set; } public long? OldInitialContribution { get; set; } public long? NewInitialContribution { get; set; } // Activated / Deactivated / Updated / ManualFix public string Action { get; set; } public string? Reason { get; set; } } ``` #### ۲.۷.۲ `NetworkMembershipHistory` برای اینکه همیشه بدانیم «چه کسی زیرمجموعه‌ی کی شده، چه زمانی، و اگر بعداً جابه‌جا شد چه اتفاقی افتاده»: ```csharp public class NetworkMembershipHistory : BaseAuditableEntity { public long UserId { get; set; } public long? OldParentId { get; set; } public long? NewParentId { get; set; } public NetworkLeg? OldLegPosition { get; set; } public NetworkLeg? NewLegPosition { get; set; } // Join / Move / Remove public string Action { get; set; } public string? Reason { get; set; } } ``` - هر بار `RecordNetworkJoin` یا `UpdateNetworkPosition` صدا زده می‌شود، باید یک رکورد در این جدول نوشته شود. - این جدول مرجع اصلی برای بازسازی درخت شبکه در زمان‌های گذشته است. #### ۲.۷.۳ `CommissionPayoutHistory` برای لاگ کامل همه‌ی تغییرات روی پرداخت کمیسیون‌ها (ایجاد، ویرایش دستی، تغییر وضعیت، برداشت و ...): ```csharp public class CommissionPayoutHistory : BaseAuditableEntity { public long UserCommissionPayoutId { get; set; } public long UserId { get; set; } public string WeekNumber { get; set; } public long AmountBefore { get; set; } public long AmountAfter { get; set; } public CommissionPayoutStatus OldStatus { get; set; } public CommissionPayoutStatus NewStatus { get; set; } // Created / Paid / WithdrawRequested / Withdrawn / Cancelled / ManualFix public string Action { get; set; } public string? PerformedBy { get; set; } // UserId یا System public string? Reason { get; set; } } ``` - اگر بعداً بفهمیم یک پرداخت اشتباه بوده و اصلاحش کنیم، اینجا قابل ردیابی است. - برای گزارش‌گیری Audit کامل پرداخت‌ها، این جدول استفاده می‌شود. #### ۲.۷.۴ `SystemConfigurationHistory` تاریخچه تغییرات تنظیمات (Config) برای این‌که بعداً بدانیم در هر زمان چه محدودیتی فعال بوده: ```csharp public class SystemConfigurationHistory : BaseAuditableEntity { public long ConfigurationId { get; set; } public ConfigurationScope Scope { get; set; } public string Key { get; set; } public string OldValue { get; set; } public string NewValue { get; set; } public string? Reason { get; set; } } ``` --- ### ۲.۸ موجودیت‌های Configuration (تنظیمات پویا) #### ۲.۸.۱ `ConfigurationScope` (Enum) ```csharp public enum ConfigurationScope { System = 0, Network = 1, Club = 2, Commission = 3 } ``` #### ۲.۸.۲ `SystemConfiguration` جدولی برای نگهداری تنظیمات پویا. هم تنظیمات عمومی سیستم، هم تنظیمات مخصوص شبکه، باشگاه و کمیسیون: ```csharp public class SystemConfiguration : BaseAuditableEntity { public ConfigurationScope Scope { get; set; } // System / Network / Club / Commission // مثل: "MaxWeeklyBalancesPerUser", "MinContributionAmount", ... public string Key { get; set; } // مقدار به‌صورت رشته - تفسیر در لایه Application public string Value { get; set; } // برای UI و Validation (Int / Decimal / Bool / String / Json) public string? DataType { get; set; } public string? Description { get; set; } public bool IsActive { get; set; } } ``` **مثال کانفیگ‌های مرتبط با شبکه:** - `Scope = Network`, `Key = "MaxWeeklyBalancesPerUser"`, `Value = "300"` - `Scope = Network`, `Key = "MaxChildrenPerLeg"`, `Value = "1"` - `Scope = Commission`, `Key = "DefaultInitialContribution"`, `Value = "25000000"` > نکته: هر بار که مقدار `SystemConfiguration` تغییر می‌کند، یک رکورد در `SystemConfigurationHistory` ثبت می‌شود تا تنظیمات گذشته قابل ردیابی باشد. --- ### ۲.۹ Enums جدید ```csharp public enum CommissionPayoutStatus { Pending = 0, Paid = 1, WithdrawRequested = 2, Withdrawn = 3, Cancelled = 4 } public enum WithdrawalMethod { Cash = 0, Diamond = 1 } public enum NetworkLeg { Left = 0, Right = 1 } ``` --- ## ۳. تغییرات در موجودیت‌های موجود ### ۳.۱ `User` افزودن فیلدهای مربوط به شبکه باینری و ناوبری: ```csharp public class User : BaseAuditableEntity { // ... public long? NetworkParentId { get; set; } public virtual User? NetworkParent { get; set; } public NetworkLeg? LegPosition { get; set; } public virtual ICollection NetworkChildren { get; set; } public virtual ClubMembership? ClubMembership { get; set; } public virtual ICollection NetworkWeeklyBalances { get; set; } public virtual ICollection CommissionPayouts { get; set; } public virtual ICollection UserClubFeatures { get; set; } } ``` ### ۳.۲ `UserWallet` ```csharp public class UserWallet : BaseAuditableEntity { // موجودی ریالی اصلی public long Balance { get; set; } // موجودی شبکه/کارمزد (کیف پول طلایی) public long NetworkBalance { get; set; } // موجودی تخفیف (فقط برای خرید از فروشگاه باشگاه) public long DiscountBalance { get; set; } // ... } ``` ### ۳.۳ `Products` ```csharp public class Product : BaseAuditableEntity { // ... // آیا این محصول فقط در فروشگاه باشگاه موجود است public bool IsClubExclusive { get; set; } // درصد تخفیف باشگاه (0 تا 100) public int ClubDiscountPercent { get; set; } // ... } ``` ### ۳.۴ `UserWalletChangeLog` افزودن نوع جدید تراکنش: ```csharp public enum TransactionType { // ... NetworkCommission = 10, // دریافت کمیسیون شبکه ClubActivation = 11, // فعال‌سازی عضویت باشگاه DiscountWalletCharge = 12, // شارژ کیف پول تخفیف } ``` --- ## ۴. معماری ماژول‌های جدید (Application / CQRS) ### ۴.۱ `ClubMembershipCQ/` #### Commands - **ActivateClubMembership**: فعال‌سازی عضویت باشگاه (کسر ۲۵ میلیون و اضافه به استخر) - **DeactivateClubMembership**: غیرفعال‌سازی عضویت - **UpdateClubMembership**: به‌روزرسانی اطلاعات عضویت #### Queries - **GetUserClubStatus**: دریافت وضعیت عضویت کاربر - **GetAllClubMembersByFilter**: لیست اعضای باشگاه با فیلتر ### ۴.۲ `ClubFeatureCQ/` #### Commands - **CreateClubFeature**: ایجاد فیچر جدید - **UpdateClubFeature**: ویرایش فیچر - **DeleteClubFeature**: حذف فیچر - **GrantFeatureToUser**: فعال‌سازی فیچر برای کاربر - **RevokeFeatureFromUser**: غیرفعال‌سازی فیچر از کاربر #### Queries - **GetAllClubFeatures**: لیست تمام فیچرها - **GetUserClubFeatures**: لیست فیچرهای فعال یک کاربر ### ۴.۳ `NetworkBalanceCQ/` #### Commands - **RecordNetworkJoin**: ثبت ورود کاربر به شبکه باینری (تعیین والد و شاخه) - حتماً باید یک رکورد در `NetworkMembershipHistory` ایجاد کند. - **UpdateNetworkPosition**: تغییر موقعیت در شبکه (مدیریتی) - هر تغییر، یک رکورد History. - **CalculateWeeklyBalances**: محاسبه تعادل‌های هفتگی (فراخوانی از Worker) #### Queries - **GetUserNetworkTree**: دریافت درخت زیرمجموعه‌های کاربر (چند سطح) - **GetUserWeeklyBalances**: دریافت تعادل‌های هفتگی یک کاربر - **GetNetworkStatistics**: آمار کلی شبکه (تعداد اعضا، عمق، تعادل) ### ۴.۴ `CommissionPoolCQ/` #### Commands - **InitializeWeeklyPool**: ایجاد استخر جدید برای هفته - **AddToWeeklyPool**: افزودن مبلغ به استخر هفتگی (هنگام فعال‌سازی عضویت) - **CalculatePoolValue**: محاسبه ارزش هر امتیاز - **DistributeCommissions**: توزیع کمیسیون‌ها به کاربران (Worker) - **CloseWeeklyPool**: بستن استخر پس از توزیع #### Queries - **GetCurrentWeekPool**: دریافت اطلاعات استخر هفته جاری - **GetPoolHistory**: تاریخچه استخرهای قبلی با فیلتر ### ۴.۵ `CommissionPayoutCQ/` #### Commands - **CreatePayoutRecord**: ثبت پرداخت کمیسیون (اتوماتیک از Worker) - همراه با ایجاد رکورد در `CommissionPayoutHistory` (Action = Created). - **RequestWithdrawal**: درخواست برداشت کمیسیون (نقدی یا الماس) - History با Action = WithdrawRequested. - **ProcessWithdrawal**: پردازش درخواست برداشت (تایید/رد ادمین) - تغییر Status + History. - **CancelPayout**: لغو پرداخت #### Queries - **GetUserCommissionHistory**: تاریخچه کمیسیون‌های دریافتی کاربر - **GetPendingWithdrawals**: لیست درخواست‌های برداشت در انتظار (برای ادمین) - **GetCommissionSummary**: خلاصه درآمد کمیسیون (مجموع، ماهانه، سالانه) ### ۴.۶ `ConfigurationCQ/` #### Commands - **SetConfigurationValue**: ثبت/ویرایش یک تنظیم (SystemConfiguration) - هر تغییر باید در `SystemConfigurationHistory` ثبت شود. - **DeactivateConfiguration**: غیرفعال‌سازی یک تنظیم #### Queries - **GetConfigurationValue**: دریافت مقدار یک Key - **GetConfigurationByScope**: لیست تنظیمات یک Scope (مثلاً Network) --- ## ۵. Background Worker/Job (محاسبات هفتگی) ### ۵.۱ `WeeklyNetworkCommissionWorker` **زمان‌بندی**: هر یکشنبه ساعت ۲۳:۵۹ (یا دوشنبه ۰۰:۰۱) **مراحل اجرایی (High-level):** #### گام ۱: بستن هفته قبل و ایجاد استخر جدید ```csharp var currentWeek = GetCurrentWeekNumber(); // مثلاً "2025-W48" var previousWeek = GetPreviousWeekNumber(); await CloseWeeklyPool(previousWeek); await InitializeWeeklyPool(currentWeek); ``` #### گام ۲: محاسبه تعادل‌های شبکه ```csharp var maxBalancesPerUser = GetConfig("MaxWeeklyBalancesPerUser", scope: ConfigurationScope.Network); var activeMembers = await GetActiveClubMembers(); foreach (var member in activeMembers) { var leftBalances = await CalculateLegBalances(member.UserId, NetworkLeg.Left, previousWeek); var rightBalances = await CalculateLegBalances(member.UserId, NetworkLeg.Right, previousWeek); var totalBalances = Math.Min(leftBalances, rightBalances); // اعمال محدودیت کانفیگ (مثلاً حداکثر 300 تعادل برای هر کاربر) if (totalBalances > maxBalancesPerUser) totalBalances = maxBalancesPerUser; await RecordWeeklyBalance(new NetworkWeeklyBalance { UserId = member.UserId, WeekNumber = previousWeek, LeftLegBalances = leftBalances, RightLegBalances = rightBalances, TotalBalances = totalBalances, WeeklyPoolContribution = member.InitialContribution, CalculatedAt = DateTime.UtcNow }); } ``` #### الگوریتم بازگشتی محاسبه تعادل شاخه ```csharp private async Task CalculateLegBalances(long userId, NetworkLeg leg, string weekNumber) { var children = await GetNetworkChildren(userId, leg); int totalBalances = 0; foreach (var child in children) { var childMembership = await GetClubMembership(child.Id); if (childMembership != null && IsInWeek(childMembership.ActivatedAt, weekNumber)) { totalBalances++; } var childLeftBalances = await CalculateLegBalances(child.Id, NetworkLeg.Left, weekNumber); var childRightBalances = await CalculateLegBalances(child.Id, NetworkLeg.Right, weekNumber); totalBalances += Math.Min(childLeftBalances, childRightBalances); } return totalBalances; } ``` #### گام ۳: محاسبه استخر و ارزش امتیاز ```csharp var totalPoolAmount = await SumPoolContributions(previousWeek); var totalBalances = await SumTotalBalances(previousWeek); var valuePerBalance = totalBalances > 0 ? totalPoolAmount / totalBalances : 0; await UpdatePoolValue(previousWeek, totalPoolAmount, totalBalances, valuePerBalance); ``` #### گام ۴: توزیع کمیسیون‌ها ```csharp var weeklyBalances = await GetWeeklyBalances(previousWeek); foreach (var balance in weeklyBalances.Where(b => b.TotalBalances > 0)) { var payoutAmount = balance.TotalBalances * valuePerBalance; var payout = new UserCommissionPayout { UserId = balance.UserId, WeekNumber = previousWeek, BalancesEarned = balance.TotalBalances, ValuePerBalance = valuePerBalance, TotalAmount = payoutAmount, Status = CommissionPayoutStatus.Pending }; await CreatePayoutRecord(payout); // داخلش CommissionPayoutHistory هم ثبت می‌شود await AddToNetworkBalance(balance.UserId, payoutAmount); await RecordWalletChange(new UserWalletChangeLog { WalletId = balance.UserId, // PreviousBalance / AfterBalance پر می‌شود Amount = payoutAmount, TransactionType = TransactionType.NetworkCommission, ReferenceId = payout.Id.ToString() }); payout.Status = CommissionPayoutStatus.Paid; payout.PaidAt = DateTime.UtcNow; await UpdatePayout(payout); await AddCommissionHistory(payout, "Paid"); } ``` #### گام ۵: ریست تعادل‌ها ```csharp await ExpireWeeklyBalances(previousWeek); ``` --- ## ۶. لاجیک فروشگاه و سبد خرید ### ۶.۱ نمایش محصولات ```csharp var query = _context.Products.Where(p => !p.IsDeleted); if (!user.ClubMembership?.IsActive ?? true) { query = query.Where(p => !p.IsClubExclusive); } // اگر کاربر عضو است، قیمت با تخفیف باشگاه محاسبه می‌شود ``` ### ۶.۲ استفاده از کیف پول تخفیف در Checkout (خلاصه‌سازی شده – در کد اصلی از DiscountBalance استفاده می‌شود و ChangeLog ثبت می‌گردد.) --- ## ۷. سناریوی کامل فعال‌سازی عضویت ### مرحله ۱: شارژ اولیه ```text کاربر → پرداخت ۵۶ میلیون (دایا/درگاه) ↓ UserWallet.Balance += 56,000,000 UserWallet.DiscountBalance += 56,000,000 ``` ### مرحله ۲: فعال‌سازی عضویت ```text کاربر → کلیک روی دکمه «عضویت در باشگاه» ↓ API: ActivateClubMembership ↓ 1. ایجاد رکورد ClubMembership: - IsActive = true - InitialContribution = 25,000,000 2. افزودن به استخر هفتگی: - WeeklyCommissionPool.TotalPoolAmount += 25,000,000 3. تعیین موقعیت در شبکه: - User.NetworkParentId = والد - User.LegPosition = Left یا Right 4. ثبت ChangeLog برای استخر: - TransactionType = ClubActivation 5. ثبت ClubMembershipHistory: - Action = "Activated" ``` ### مرحله ۳: محاسبه هفتگی (Worker) (مطابق بخش ۵) ### مرحله ۴: برداشت کمیسیون ```text کاربر → درخواست برداشت ↓ API: RequestWithdrawal (Cash یا Diamond) ↓ ادمین → تایید درخواست ↓ 1. اگر Cash: - واریز به حساب بانکی - NetworkBalance -= مبلغ 2. اگر Diamond: - خرید الماس از دایا - NetworkBalance -= مبلغ ``` همراه با ثبت رکورد در `CommissionPayoutHistory` (Action = WithdrawRequested / Withdrawn). --- ## ۸. پروتوباف و gRPC Services ### ۸.۱ `clubmembership.proto` ```protobuf syntax = "proto3"; import "google/protobuf/timestamp.proto"; package clubmembership; service ClubMembershipService { rpc ActivateMembership (ActivateMembershipRequest) returns (ActivateMembershipResponse); rpc GetClubStatus (GetClubStatusRequest) returns (GetClubStatusResponse); rpc GrantFeature (GrantFeatureRequest) returns (GrantFeatureResponse); rpc GetUserFeatures (GetUserFeaturesRequest) returns (GetUserFeaturesResponse); } message ActivateMembershipRequest { int64 user_id = 1; int64 contribution_amount = 2; int64 network_parent_id = 3; NetworkLeg leg_position = 4; } message ActivateMembershipResponse { bool success = 1; string message = 2; ClubMembershipDto membership = 3; } message GetClubStatusRequest { int64 user_id = 1; } message GetClubStatusResponse { bool is_member = 1; ClubMembershipDto membership = 2; } message ClubMembershipDto { int64 id = 1; int64 user_id = 2; bool is_active = 3; google.protobuf.Timestamp activated_at = 4; int64 initial_contribution = 5; int64 total_earned = 6; } enum NetworkLeg { LEFT = 0; RIGHT = 1; } ``` ### ۸.۲ `networkbalance.proto` ```protobuf syntax = "proto3"; package networkbalance; service NetworkBalanceService { rpc GetNetworkTree (GetNetworkTreeRequest) returns (GetNetworkTreeResponse); rpc GetWeeklyBalances (GetWeeklyBalancesRequest) returns (GetWeeklyBalancesResponse); rpc GetNetworkStats (GetNetworkStatsRequest) returns (GetNetworkStatsResponse); } message GetNetworkTreeRequest { int64 user_id = 1; int32 max_depth = 2; } message GetNetworkTreeResponse { NetworkNodeDto root = 1; } message NetworkNodeDto { int64 user_id = 1; string full_name = 2; NetworkLeg leg_position = 3; bool is_active = 4; repeated NetworkNodeDto children = 5; } message GetWeeklyBalancesRequest { int64 user_id = 1; string week_number = 2; } message GetWeeklyBalancesResponse { int32 left_leg_balances = 1; int32 right_leg_balances = 2; int32 total_balances = 3; int64 pool_contribution = 4; } ``` ### ۸.۳ `commissionpayout.proto` ```protobuf syntax = "proto3"; import "google/protobuf/timestamp.proto"; package commissionpayout; service CommissionPayoutService { rpc RequestWithdrawal (RequestWithdrawalRequest) returns (RequestWithdrawalResponse); rpc GetCommissionHistory (GetCommissionHistoryRequest) returns (GetCommissionHistoryResponse); rpc GetPendingWithdrawals (GetPendingWithdrawalsRequest) returns (GetPendingWithdrawalsResponse); rpc ProcessWithdrawal (ProcessWithdrawalRequest) returns (ProcessWithdrawalResponse); } message RequestWithdrawalRequest { int64 user_id = 1; int64 amount = 2; WithdrawalMethod method = 3; string iban_number = 4; } message RequestWithdrawalResponse { bool success = 1; string message = 2; int64 request_id = 3; } message GetCommissionHistoryRequest { int64 user_id = 1; int32 page_number = 2; int32 page_size = 3; } message GetCommissionHistoryResponse { repeated CommissionPayoutDto payouts = 1; int32 total_count = 2; } message CommissionPayoutDto { int64 id = 1; string week_number = 2; int32 balances_earned = 3; int64 value_per_balance = 4; int64 total_amount = 5; CommissionPayoutStatus status = 6; google.protobuf.Timestamp paid_at = 7; WithdrawalMethod withdrawal_method = 8; } enum WithdrawalMethod { CASH = 0; DIAMOND = 1; } enum CommissionPayoutStatus { PENDING = 0; PAID = 1; WITHDRAW_REQUESTED = 2; WITHDRAWN = 3; CANCELLED = 4; } ``` --- ## ۹. نکات حیاتی و بهترین رویه‌ها ### ۹.۱ یکپارچگی شبکه باینری - هر کاربر حداکثر دو فرزند (یکی Left، یکی Right) - هنگام اضافه کردن فرزند، کنترل Race Condition - حذف کاربر نباید ساختار شبکه را خراب کند ### ۹.۲ Transaction Management - Worker باید تمام مراحل را در یک TransactionScope انجام دهد - در صورت شکست، Rollback کامل ### ۹.۳ Idempotency - محاسبه هفتگی برای یک WeekNumber فقط یک‌بار - بررسی `WeeklyCommissionPool.IsCalculated` قبل از شروع ### ۹.۴ Performance - Caching درخت شبکه برای کاربران پرحجم - Index روی `WeekNumber`, `UserId`, `NetworkParentId` ### ۹.۵ Audit و Compliance - همه تغییرات کیف پول در `UserWalletChangeLog` - همه پرداخت‌های کمیسیون در `UserCommissionPayout` + `CommissionPayoutHistory` - تغییرات شبکه در `NetworkMembershipHistory` - تغییرات تنظیمات در `SystemConfigurationHistory` ### ۹.۶ Security - محدودیت تعداد درخواست برداشت - تایید دو مرحله‌ای برای برداشت‌های بالا - Audit Log برای عملیات حساس --- ## ۱۰. مراحل پیاده‌سازی (Roadmap) (مطابق نسخه قبلی – فاز ۱ تا ۶) --- ## ۱۱. متریک‌های کلیدی (KPIs) - تعداد اعضای فعال باشگاه - مجموع کمیسیون‌های پرداختی هر ماه - میانگین تعادل هر کاربر در هفته - نرخ تبدیل به عضویت باشگاه - زمان اجرای Worker، تعداد خطاها، عمق درخت، حجم داده History و … --- ## ۱۲. سوالات متداول (FAQ) (همان سوالات قبلی + می‌توان سوالات مربوط به سقف تعادل و تنظیمات را اضافه کرد.) --- ## ۱۳. ضمیمه: مثال عددی کامل (مثال دو هفته‌ای A, B, C, D, E, F, G مثل نسخه قبلی.) --- ## ۱۴. مسیرهای مرتبط - Domain: `CMS/src/CMSMicroservice.Domain/Entities/` - Application: `CMS/src/CMSMicroservice.Application/ClubMembershipCQ/`, `NetworkBalanceCQ/`, `CommissionPoolCQ/`, `CommissionPayoutCQ/`, `ConfigurationCQ/` - Protobuf: `CMS/src/CMSMicroservice.Protobuf/Protos/` - Worker: `CMS/src/CMSMicroservice.Infrastructure/BackgroundJobs/` - مستند حاضر: `CMS/docs/network-club-commission-system.md` **نسخه**: 1.1 **تاریخ**: 2025-11-29 **نویسنده**: تیم توسعه CMS **وضعیت**: آماده پیاده‌سازی (با History و Config)