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

29 KiB
Raw Blame History

سیستم باشگاه مشتریان و محاسبه کمیسیون شبکه

خلاصه اجرایی

این سند تحلیل جامع و معماری پیشنهادی برای پیاده‌سازی سیستم باشگاه مشتریان (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 (عضویت باشگاه مشتریان)

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<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; }

    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; }
    public int TotalBalances { get; set; }

    // مبلغی که این کاربر همان هفته به استخر اضافه کرده (معمولاً InitialContribution)
    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; }

    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; }
    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

لاگ تغییرات مهم روی عضویت باشگاه (فعال‌سازی، غیرفعال‌سازی، ویرایش):

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

برای اینکه همیشه بدانیم «چه کسی زیرمجموعه‌ی کی شده، چه زمانی، و اگر بعداً جابه‌جا شد چه اتفاقی افتاده»:

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

برای لاگ کامل همه‌ی تغییرات روی پرداخت کمیسیون‌ها (ایجاد، ویرایش دستی، تغییر وضعیت، برداشت و ...):

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) برای این‌که بعداً بدانیم در هر زمان چه محدودیتی فعال بوده:

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)

public enum ConfigurationScope
{
    System = 0,
    Network = 1,
    Club = 2,
    Commission = 3
}

۲.۸.۲ SystemConfiguration

جدولی برای نگهداری تنظیمات پویا. هم تنظیمات عمومی سیستم، هم تنظیمات مخصوص شبکه، باشگاه و کمیسیون:

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 جدید

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

افزودن فیلدهای مربوط به شبکه باینری و ناوبری:

public class User : BaseAuditableEntity
{
    // ...

    public long? NetworkParentId { get; set; }
    public virtual User? NetworkParent { get; set; }

    public NetworkLeg? LegPosition { get; set; }

    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; }

    public virtual ICollection<UserClubFeature> UserClubFeatures { get; set; }
}

۳.۲ UserWallet

public class UserWallet : BaseAuditableEntity
{
    // موجودی ریالی اصلی
    public long Balance { get; set; }

    // موجودی شبکه/کارمزد (کیف پول طلایی)
    public long NetworkBalance { get; set; }

    // موجودی تخفیف (فقط برای خرید از فروشگاه باشگاه)
    public long DiscountBalance { get; set; }

    // ...
}

۳.۳ Products

public class Product : BaseAuditableEntity
{
    // ...

    // آیا این محصول فقط در فروشگاه باشگاه موجود است
    public bool IsClubExclusive { get; set; }

    // درصد تخفیف باشگاه (0 تا 100)
    public int ClubDiscountPercent { get; set; }

    // ...
}

۳.۴ UserWalletChangeLog

افزودن نوع جدید تراکنش:

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):

گام ۱: بستن هفته قبل و ایجاد استخر جدید

var currentWeek = GetCurrentWeekNumber(); // مثلاً "2025-W48"
var previousWeek = GetPreviousWeekNumber();

await CloseWeeklyPool(previousWeek);
await InitializeWeeklyPool(currentWeek);

گام ۲: محاسبه تعادل‌های شبکه

var maxBalancesPerUser = GetConfig<int>("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
    });
}

الگوریتم بازگشتی محاسبه تعادل شاخه

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); // داخلش 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");
}

گام ۵: ریست تعادل‌ها

await ExpireWeeklyBalances(previousWeek);

۶. لاجیک فروشگاه و سبد خرید

۶.۱ نمایش محصولات

var query = _context.Products.Where(p => !p.IsDeleted);

if (!user.ClubMembership?.IsActive ?? true)
{
    query = query.Where(p => !p.IsClubExclusive);
}

// اگر کاربر عضو است، قیمت با تخفیف باشگاه محاسبه می‌شود

۶.۲ استفاده از کیف پول تخفیف در Checkout

(خلاصه‌سازی شده در کد اصلی از DiscountBalance استفاده می‌شود و ChangeLog ثبت می‌گردد.)


۷. سناریوی کامل فعال‌سازی عضویت

مرحله ۱: شارژ اولیه

کاربر → پرداخت ۵۶ میلیون (دایا/درگاه)
  ↓
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"

مرحله ۳: محاسبه هفتگی (Worker)

(مطابق بخش ۵)

مرحله ۴: برداشت کمیسیون

کاربر → درخواست برداشت
  ↓
API: RequestWithdrawal (Cash یا Diamond)
  ↓
ادمین → تایید درخواست
  ↓
1. اگر Cash:
   - واریز به حساب بانکی
   - NetworkBalance -= مبلغ

2. اگر Diamond:
   - خرید الماس از دایا
   - NetworkBalance -= مبلغ

همراه با ثبت رکورد در CommissionPayoutHistory (Action = WithdrawRequested / Withdrawn).


۸. پروتوباف و 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;
}

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)