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

67 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; }
    
    // UserClubFeature Collection Navigation Reference
    public virtual ICollection<UserClubFeature> UserClubFeatures { get; set; }
}

۲.۲ ClubFeature (فیچرهای باشگاه)

public class ClubFeature : BaseAuditableEntity
{
    // نام فیچر
    public string Title { get; set; }
    
    // توضیحات
    public string? Description { get; set; }
    
    // وضعیت فعال/غیرفعال
    public bool IsActive { get; set; }
    
    // امتیاز لازم برای دریافت (اختیاری)
    public int? RequiredPoints { get; set; }
    
    // ترتیب نمایش
    public int SortOrder { get; set; }
    
    // UserClubFeature Collection Navigation Reference
    public virtual ICollection<UserClubFeature> UserClubFeatures { get; set; }
}

۲.۳ UserClubFeature (جدول واسط: کاربر–فیچر)

public class UserClubFeature : BaseAuditableEntity
{
    // شناسه کاربر
    public long UserId { get; set; }
    public virtual User User { get; set; }
    
    // شناسه فیچر
    public long ClubFeatureId { get; set; }
    public virtual ClubFeature ClubFeature { get; set; }
    
    // تاریخ فعال‌سازی فیچر برای کاربر
    public DateTime GrantedAt { get; set; }
    
    // یادداشت اختیاری
    public string? Notes { get; set; }
}

۲.۴ NetworkWeeklyBalance (ساختار شبکه و تعادل‌های هفتگی)

public class NetworkWeeklyBalance : BaseAuditableEntity
{
    // شناسه کاربر
    public long UserId { get; set; }
    public virtual User User { get; set; }
    
    // شماره هفته (مثال: "2025-W48")
    public string WeekNumber { get; set; }
    
    // تعداد تعادل شاخه چپ در این هفته
    public int LeftLegBalances { get; set; }
    
    // تعداد تعادل شاخه راست در این هفته
    public int RightLegBalances { get; set; }
    
    // امتیاز کاربر: MIN(LeftLegBalances, RightLegBalances)
    public int TotalBalances { get; set; }
    
    // مبلغی که از این کاربر به استخر هفتگی اضافه شد
    public long WeeklyPoolContribution { get; set; }
    
    // زمان محاسبه
    public DateTime? CalculatedAt { get; set; }
    
    // آیا منقضی شده (بعد از توزیع)
    public bool IsExpired { get; set; }
}

۲.۵ WeeklyCommissionPool (استخر کارمزد هفتگی)

public class WeeklyCommissionPool : BaseAuditableEntity
{
    // شماره هفته
    public string WeekNumber { get; set; }
    
    // مجموع مبلغ جمع‌شده در استخر
    public long TotalPoolAmount { get; set; }
    
    // مجموع تعادل‌های کل سیستم در این هفته
    public int TotalBalances { get; set; }
    
    // مبلغ ریالی هر امتیاز
    public long ValuePerBalance { get; set; }
    
    // آیا محاسبه و توزیع شده
    public bool IsCalculated { get; set; }
    public DateTime? CalculatedAt { get; set; }
    
    // UserCommissionPayout Collection Navigation Reference
    public virtual ICollection<UserCommissionPayout> UserCommissionPayouts { get; set; }
}

۲.۶ UserCommissionPayout (پرداخت کمیسیون به کاربران)

public class UserCommissionPayout : BaseAuditableEntity
{
    // شناسه کاربر
    public long UserId { get; set; }
    public virtual User User { get; set; }
    
    // شماره هفته
    public string WeekNumber { get; set; }
    
    // شناسه استخر
    public long WeeklyPoolId { get; set; }
    public virtual WeeklyCommissionPool WeeklyPool { get; set; }
    
    // تعداد امتیازی که کاربر داشت
    public int BalancesEarned { get; set; }
    
    // ارزش هر امتیاز
    public long ValuePerBalance { get; set; }
    
    // مبلغ کل: BalancesEarned × ValuePerBalance
    public long TotalAmount { get; set; }
    
    // وضعیت پرداخت
    public CommissionPayoutStatus Status { get; set; }
    
    // تاریخ واریز به کیف پول
    public DateTime? PaidAt { get; set; }
    
    // روش برداشت (اگر کاربر درخواست برداشت داده)
    public WithdrawalMethod? WithdrawalMethod { get; set; }
    
    // شماره شبای برداشت (اگر نقدی)
    public string? IbanNumber { get; set; }
    
    // تاریخ برداشت نقدی/الماس
    public DateTime? WithdrawnAt { get; set; }
}

۲.۷ موجودیت‌های History (جداول تاریخچه و Audit)

۲.۷.۱ ClubMembershipHistory

لاگ تمام تغییرات مهم روی عضویت باشگاه برای Audit و Compliance:

public class ClubMembershipHistory : BaseAuditableEntity
{
    public long ClubMembershipId { get; set; }
    public long UserId { get; set; }

    // وضعیت قبل و بعد
    public bool OldIsActive { get; set; }
    public bool NewIsActive { get; set; }

    // مبلغ مشارکت قبل و بعد
    public long? OldInitialContribution { get; set; }
    public long? NewInitialContribution { get; set; }

    // نوع عملیات
    public ClubMembershipAction Action { get; set; }
    
    // دلیل تغییر (اختیاری)
    public string? Reason { get; set; }
    
    // چه کسی انجام داده (UserId یا "System")
    public string? PerformedBy { get; set; }
}

استفاده: هر بار که ActivateClubMembership, DeactivateClubMembership یا UpdateClubMembership اجرا می‌شود، یک رکورد History ثبت می‌گردد.

۲.۷NetworkMembershipHistory

برای ردیابی کامل جابجایی در شبکه باینری:

public class NetworkMembershipHistory : BaseAuditableEntity
{
    public long UserId { get; set; }

    // والد قبل و بعد
    public long? OldParentId { get; set; }
    public long? NewParentId { get; set; }

    // موقعیت قبل و بعد
    public NetworkLeg? OldLegPosition { get; set; }
    public NetworkLeg? NewLegPosition { get; set; }

    // نوع عملیات
    public NetworkMembershipAction Action { get; set; }
    
    public string? Reason { get; set; }
    public string? PerformedBy { get; set; }
}

استفاده:

  • هنگام RecordNetworkJoin: Action = Join
  • هنگام UpdateNetworkPosition: Action = Move
  • امکان بازسازی درخت شبکه در هر زمان گذشته

۲.۷CommissionPayoutHistory

تاریخچه کامل تغییرات پرداخت کمیسیون‌ها:

public class CommissionPayoutHistory : BaseAuditableEntity
{
    public long UserCommissionPayoutId { get; set; }
    public long UserId { get; set; }
    public string WeekNumber { get; set; }

    // مبلغ قبل و بعد
    public long AmountBefore { get; set; }
    public long AmountAfter { get; set; }

    // وضعیت قبل و بعد
    public CommissionPayoutStatus OldStatus { get; set; }
    public CommissionPayoutStatus NewStatus { get; set; }

    // نوع عملیات
    public CommissionPayoutAction Action { get; set; }
    
    public string? PerformedBy { get; set; }
    public string? Reason { get; set; }
}

استفاده:

  • Worker: Action = Created, Paid
  • کاربر: Action = WithdrawRequested
  • ادمین: Action = Withdrawn, Cancelled, ManualFix

۲.۷SystemConfigurationHistory

تاریخچه تغییرات تنظیمات سیستم:

public class SystemConfigurationHistory : BaseAuditableEntity
{
    public long ConfigurationId { get; set; }

    public ConfigurationScope Scope { get; set; }
    public string Key { get; set; }

    // مقدار قبل و بعد
    public string OldValue { get; set; }
    public string NewValue { get; set; }

    public string? Reason { get; set; }
    public string? PerformedBy { get; set; }
}

استفاده: هر تغییر در SystemConfiguration باید در این جدول ثبت شود تا مشخص باشد در هر زمان چه محدودیتی فعال بوده است.


۲.۸ موجودیت‌های Configuration (تنظیمات پویا)

۲.۸.۱ ConfigurationScope (Enum)

public enum ConfigurationScope
{
    System = 0,      // تنظیمات کلی سیستم
    Network = 1,     // تنظیمات شبکه باینری
    Club = 2,        // تنظیمات باشگاه مشتریان
    Commission = 3   // تنظیمات کمیسیون
}

۲.۸.۲ SystemConfiguration

جدول نگهداری تنظیمات پویا که بدون تغییر کد قابل تغییر است:

public class SystemConfiguration : BaseAuditableEntity
{
    // محدوده تنظیمات
    public ConfigurationScope Scope { get; set; }

    // کلید تنظیم (مثلاً "MaxWeeklyBalancesPerUser")
    public string Key { get; set; }

    // مقدار به‌صورت رشته (تفسیر در Application Layer)
    public string Value { get; set; }

    // نوع داده برای Validation و UI (Int/Decimal/Bool/String/Json)
    public string? DataType { get; set; }

    // توضیحات برای ادمین
    public string? Description { get; set; }
    
    public bool IsActive { get; set; }
}

مثال کانفیگ‌های کلیدی:

Scope Key Value توضیح
Network MaxWeeklyBalancesPerUser 300 سقف تعادل هفتگی برای هر کاربر
Network MaxChildrenPerLeg 1 حداکثر فرزند مستقیم در هر شاخه
Network MaxNetworkDepth 15 حداکثر عمق شبکه
Commission DefaultInitialContribution 25000000 مبلغ پیش‌فرض مشارکت
Commission MinWithdrawalAmount 1000000 حداقل مبلغ برداشت
Club ActivationFee 25000000 هزینه فعال‌سازی عضویت

مزایا:

  • تغییر قوانین بیزینس بدون Deployment
  • A/B Testing و آزمایش استراتژی‌های مختلف
  • تاریخچه کامل در SystemConfigurationHistory

۲.۹ Enums جدید

public enum CommissionPayoutStatus
{
    Pending = 0,        // در انتظار واریز
    Paid = 1,          // واریز شده به کیف پول
    WithdrawRequested = 2,  // درخواست برداشت داده شده
    Withdrawn = 3,     // برداشت شده
    Cancelled = 4      // لغو شده
}

public enum WithdrawalMethod
{
    Cash = 0,          // برداشت نقدی به حساب بانکی
    Diamond = 1        // خرید الماس از دایا
}

public enum NetworkLeg
{
    Left = 0,          // شاخه چپ
    Right = 1          // شاخه راست
}

public enum ClubMembershipAction
{
    Activated = 0,     // فعال‌سازی عضویت
    Deactivated = 1,   // غیرفعال‌سازی
    Updated = 2,       // ویرایش اطلاعات
    ManualFix = 3      // اصلاح دستی توسط ادمین
}

public enum NetworkMembershipAction
{
    Join = 0,          // ورود به شبکه
    Move = 1,          // جابجایی در شبکه
    Remove = 2         // حذف از شبکه
}

public enum CommissionPayoutAction
{
    Created = 0,       // ایجاد اولیه
    Paid = 1,          // واریز شده
    WithdrawRequested = 2,  // درخواست برداشت
    Withdrawn = 3,     // برداشت شده
    Cancelled = 4,     // لغو شده
    ManualFix = 5      // اصلاح دستی
}

۳. تغییرات در موجودیت‌های موجود

۳.۱ User

// افزودن فیلدهای شبکه باینری
public long? NetworkParentId { get; set; }
public virtual User? NetworkParent { get; set; }

public NetworkLeg? LegPosition { get; set; }

// Collection Navigation References
public virtual ICollection<User> NetworkChildren { get; set; }
public virtual ClubMembership? ClubMembership { get; set; }
public virtual ICollection<NetworkWeeklyBalance> NetworkWeeklyBalances { get; set; }
public virtual ICollection<UserCommissionPayout> CommissionPayouts { get; set; }

۳.۲ UserWallet

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

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

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

۳.۳ Products

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

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

۳.۴ UserWalletChangeLog

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

// در enum TransactionType:
NetworkCommission = 10,    // دریافت کمیسیون شبکه
ClubActivation = 11,       // فعال‌سازی عضویت باشگاه
DiscountWalletCharge = 12, // شارژ کیف پول تخفیف

۴. معماری ماژول‌های جدید

۴.۱ ClubMembershipCQ/

Commands

  • ActivateClubMembership: فعال‌سازی عضویت باشگاه (کسر ۲۵ میلیون و اضافه به استخر)
    • نکته: باید رکورد در ClubMembershipHistory با Action=Activated ثبت کند
  • DeactivateClubMembership: غیرفعال‌سازی عضویت
    • نکته: ثبت History با Action=Deactivated
  • UpdateClubMembership: به‌روزرسانی اطلاعات عضویت
    • نکته: ثبت History با Action=Updated

Queries

  • GetUserClubStatus: دریافت وضعیت عضویت کاربر
  • GetAllClubMembersByFilter: لیست اعضای باشگاه با فیلتر
  • GetClubMembershipHistory: تاریخچه تغییرات عضویت یک کاربر

۴.۲ ClubFeatureCQ/

Commands

  • CreateClubFeature: ایجاد فیچر جدید
  • UpdateClubFeature: ویرایش فیچر
  • DeleteClubFeature: حذف فیچر
  • GrantFeatureToUser: فعال‌سازی فیچر برای کاربر
  • RevokeFeatureFromUser: غیرفعال‌سازی فیچر از کاربر

Queries

  • GetAllClubFeatures: لیست تمام فیچرها
  • GetUserClubFeatures: لیست فیچرهای فعال یک کاربر

۴.۳ NetworkBalanceCQ/

Commands

  • RecordNetworkJoin: ثبت ورود کاربر به شبکه باینری (تعیین والد و شاخه)
    • نکته: حتماً باید رکورد NetworkMembershipHistory با Action=Join ایجاد کند
    • ولیدیشن: بررسی ظرفیت شاخه (MaxChildrenPerLeg از Config)
  • UpdateNetworkPosition: تغییر موقعیت در شبکه (مدیریتی)
    • نکته: ثبت History با Action=Move و ذکر OldParentId/NewParentId
    • محدودیت: فقط قبل از محاسبات هفتگی مجاز است
  • CalculateWeeklyBalances: محاسبه تعادل‌های هفتگی (فراخوانی از Worker)
    • نکته: اعمال سقف از MaxWeeklyBalancesPerUser (Config)

Queries

  • GetUserNetworkTree: دریافت درخت زیرمجموعه‌های کاربر (چند سطح)
  • GetUserWeeklyBalances: دریافت تعادل‌های هفتگی یک کاربر
  • GetNetworkStatistics: آمار کلی شبکه (تعداد اعضا، عمق، تعادل)
  • GetNetworkMembershipHistory: تاریخچه جابجایی‌های یک کاربر در شبکه

۴.۴ CommissionPoolCQ/

Commands

  • InitializeWeeklyPool: ایجاد استخر جدید برای هفته
  • AddToWeeklyPool: افزودن مبلغ به استخر هفتگی (هنگام فعال‌سازی عضویت)
  • CalculatePoolValue: محاسبه ارزش هر امتیاز
  • DistributeCommissions: توزیع کمیسیون‌ها به کاربران (Worker)
  • CloseWeeklyPool: بستن استخر پس از توزیع

Queries

  • GetCurrentWeekPool: دریافت اطلاعات استخر هفته جاری
  • GetPoolHistory: تاریخچه استخرهای قبلی با فیلتر

۴.۵ CommissionPayoutCQ/

Commands

  • CreatePayoutRecord: ثبت پرداخت کمیسیون (اتوماتیک از Worker)
    • نکته: ایجاد رکورد CommissionPayoutHistory با Action=Created
  • RequestWithdrawal: درخواست برداشت کمیسیون (نقدی یا الماس)
    • نکته: ثبت History با Action=WithdrawRequested
    • ولیدیشن: بررسی MinWithdrawalAmount از Config
  • ProcessWithdrawal: پردازش درخواست برداشت (تایید/رد ادمین)
    • نکته: ثبت History با Action=Withdrawn یا Cancelled
  • CancelPayout: لغو پرداخت
    • نکته: ثبت History با Action=Cancelled

Queries

  • GetUserCommissionHistory: تاریخچه کمیسیون‌های دریافتی کاربر
  • GetPendingWithdrawals: لیست درخواست‌های برداشت در انتظار (برای ادمین)
  • GetCommissionSummary: خلاصه درآمد کمیسیون (مجموع، ماهانه، سالانه)
  • GetCommissionPayoutAudit: تاریخچه کامل تغییرات یک پرداخت (از CommissionPayoutHistory)

۴.۶ ConfigurationCQ/

Commands

  • SetConfigurationValue: ثبت یا ویرایش یک تنظیم
    • نکته: هر تغییر باید در SystemConfigurationHistory ثبت شود
    • ولیدیشن: بررسی DataType و محدوده مجاز
  • DeactivateConfiguration: غیرفعال‌سازی یک تنظیم

Queries

  • GetConfigurationValue: دریافت مقدار یک Key از Config
  • GetConfigurationsByScope: لیست تنظیمات یک Scope (مثلاً Network)
  • GetConfigurationHistory: تاریخچه تغییرات یک تنظیم

۵. Background Worker/Job (محاسبات هفتگی)

۵.۱ WeeklyNetworkCommissionWorker

زمان‌بندی: هر یکشنبه ساعت ۲۳:۵۹ (یا دوشنبه ۰۰:۰۱)

مراحل اجرایی:

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

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

// بستن استخر هفته قبل (اگر هنوز باز است)
await CloseWeeklyPool(previousWeek);

// ایجاد استخر جدید
await InitializeWeeklyPool(currentWeek);

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

// دریافت تمام اعضای باشگاه فعال
var activeMembers = await GetActiveClubMembers();

// دریافت سقف تعادل از Config
var maxBalancesPerUser = await GetConfigValue<int>(
    "MaxWeeklyBalancesPerUser", 
    ConfigurationScope.Network
) ?? 300; // مقدار پیش‌فرض

foreach (var member in activeMembers)
{
    // محاسبه تعادل‌های شاخه چپ و راست
    var leftBalances = await CalculateLegBalances(member.UserId, NetworkLeg.Left, previousWeek);
    var rightBalances = await CalculateLegBalances(member.UserId, NetworkLeg.Right, previousWeek);
    
    // تعادل کاربر: حداقل دو شاخه
    var totalBalances = Math.Min(leftBalances, rightBalances);
    
    // اعمال محدودیت سقف (جلوگیری از سوءاستفاده)
    if (totalBalances > maxBalancesPerUser)
    {
        _logger.LogWarning(
            "User {UserId} exceeded max balances: {Total} > {Max}. Capping to {Max}.",
            member.UserId, totalBalances, maxBalancesPerUser, maxBalancesPerUser
        );
        totalBalances = maxBalancesPerUser;
    }
    
    // ثبت در NetworkWeeklyBalance
    await RecordWeeklyBalance(new NetworkWeeklyBalance {
        UserId = member.UserId,
        WeekNumber = previousWeek,
        LeftLegBalances = leftBalances,
        RightLegBalances = rightBalances,
        TotalBalances = totalBalances,
        WeeklyPoolContribution = member.InitialContribution,
        CalculatedAt = DateTime.UtcNow
    });
}

الگوریتم محاسبه تعادل شاخه (Recursive):

private async Task<int> CalculateLegBalances(long userId, NetworkLeg leg, string weekNumber)
{
    // دریافت فرزندان در شاخه مشخص
    var children = await GetNetworkChildren(userId, leg);
    
    int totalBalances = 0;
    
    foreach (var child in children)
    {
        // اگر فرزند در این هفته عضو شده باشد
        var childMembership = await GetClubMembership(child.Id);
        if (childMembership != null && IsInWeek(childMembership.ActivatedAt, weekNumber))
        {
            totalBalances++; // این فرزند یک تعادل ایجاد کرده
        }
        
        // بررسی زیرمجموعه‌های فرزند (عمق‌سنجی)
        var childLeftBalances = await CalculateLegBalances(child.Id, NetworkLeg.Left, weekNumber);
        var childRightBalances = await CalculateLegBalances(child.Id, NetworkLeg.Right, weekNumber);
        
        // تعادل‌های فرزند (حداقل دو شاخه)
        totalBalances += Math.Min(childLeftBalances, childRightBalances);
    }
    
    return totalBalances;
}

گام ۳: محاسبه استخر و ارزش امتیاز

// جمع مبالغ استخر
var totalPoolAmount = await SumPoolContributions(previousWeek);

// جمع تعادل‌های کل سیستم
var totalBalances = await SumTotalBalances(previousWeek);

// محاسبه ارزش هر امتیاز
var valuePerBalance = totalBalances > 0 ? totalPoolAmount / totalBalances : 0;

// به‌روزرسانی استخر
await UpdatePoolValue(previousWeek, totalPoolAmount, totalBalances, valuePerBalance);

گام ۴: توزیع کمیسیون‌ها

var weeklyBalances = await GetWeeklyBalances(previousWeek);

foreach (var balance in weeklyBalances.Where(b => b.TotalBalances > 0))
{
    var payoutAmount = balance.TotalBalances * valuePerBalance;
    
    // ثبت پرداخت
    var payout = new UserCommissionPayout {
        UserId = balance.UserId,
        WeekNumber = previousWeek,
        BalancesEarned = balance.TotalBalances,
        ValuePerBalance = valuePerBalance,
        TotalAmount = payoutAmount,
        Status = CommissionPayoutStatus.Pending
    };
    await CreatePayoutRecord(payout);
    
    // ثبت History برای ایجاد
    await RecordPayoutHistory(new CommissionPayoutHistory {
        UserCommissionPayoutId = payout.Id,
        UserId = balance.UserId,
        WeekNumber = previousWeek,
        AmountBefore = 0,
        AmountAfter = payoutAmount,
        OldStatus = CommissionPayoutStatus.Pending,
        NewStatus = CommissionPayoutStatus.Pending,
        Action = CommissionPayoutAction.Created,
        PerformedBy = "System"
    });
    
    // واریز به کیف پول طلایی
    await AddToNetworkBalance(balance.UserId, payoutAmount);
    
    // ثبت در ChangeLog
    await RecordWalletChange(new UserWalletChangeLog {
        WalletId = balance.UserId,
        PreviousBalance = ...,
        Amount = payoutAmount,
        AfterBalance = ...,
        TransactionType = TransactionType.NetworkCommission,
        ReferenceId = payout.Id.ToString()
    });
    
    // تنظیم وضعیت پرداخت
    payout.Status = CommissionPayoutStatus.Paid;
    payout.PaidAt = DateTime.UtcNow;
    await UpdatePayout(payout);
    
    // ثبت History برای پرداخت
    await RecordPayoutHistory(new CommissionPayoutHistory {
        UserCommissionPayoutId = payout.Id,
        UserId = balance.UserId,
        WeekNumber = previousWeek,
        AmountBefore = payoutAmount,
        AmountAfter = payoutAmount,
        OldStatus = CommissionPayoutStatus.Pending,
        NewStatus = CommissionPayoutStatus.Paid,
        Action = CommissionPayoutAction.Paid,
        PerformedBy = "System"
    });
}

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

// علامت‌گذاری تعادل‌های هفته قبل به عنوان منقضی
await ExpireWeeklyBalances(previousWeek);

۵.۲ نکات حیاتی Worker

  • Transaction Scope: تمام مراحل باید داخل یک تراکنش دیتابیس باشند تا در صورت خطا Rollback شود
  • Idempotency: بررسی اینکه استخر هفته قبل قبلاً محاسبه نشده باشد (IsCalculated = false)
  • Logging: ثبت دقیق هر مرحله برای Audit و عیب‌یابی
  • Notification: ارسال اعلان به کاربرانی که کمیسیون دریافت کرده‌اند
  • Error Handling: در صورت خطا، ارسال آلارم به تیم فنی و تلاش مجدد

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

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

// در Query لیست محصولات
var query = _context.Products.Where(p => !p.IsDeleted);

// اگر کاربر عضو باشگاه نیست، محصولات اختصاصی را حذف کن
if (!user.ClubMembership?.IsActive)
{
    query = query.Where(p => !p.IsClubExclusive);
}

// نمایش تخفیف باشگاه (اگر کاربر عضو باشد)
var products = await query.Select(p => new ProductDto {
    ...
    ClubDiscountPercent = user.ClubMembership?.IsActive ? p.ClubDiscountPercent : 0,
    FinalPrice = p.Price - (p.Price * p.ClubDiscountPercent / 100)
}).ToListAsync();

۶.۲ Checkout و محاسبه پرداخت

public async Task<UserOrderDto> CheckoutWithClubDiscount(CheckoutCommand command)
{
    var user = await _context.Users.Include(u => u.ClubMembership)
                                     .Include(u => u.UserWallets)
                                     .FirstOrDefaultAsync(u => u.Id == command.UserId);
    
    var cartItems = await _context.UserCarts
                                    .Include(c => c.Product)
                                    .Where(c => c.UserId == command.UserId && !c.IsDeleted)
                                    .ToListAsync();
    
    long totalAmount = 0;
    long totalDiscountAmount = 0;
    long totalCashAmount = 0;
    
    foreach (var item in cartItems)
    {
        var product = item.Product;
        var itemTotal = product.Price * item.Count;
        
        // اگر کاربر عضو باشگاه باشد و محصول تخفیف داشته باشد
        if (user.ClubMembership?.IsActive == true && product.ClubDiscountPercent > 0)
        {
            var discountAmount = (itemTotal * product.ClubDiscountPercent) / 100;
            var cashAmount = itemTotal - discountAmount;
            
            totalDiscountAmount += discountAmount;
            totalCashAmount += cashAmount;
        }
        else
        {
            totalCashAmount += itemTotal;
        }
        
        totalAmount += itemTotal;
    }
    
    // بررسی موجودی کیف پول تخفیف
    var wallet = user.UserWallets.First();
    if (totalDiscountAmount > wallet.DiscountBalance)
    {
        throw new BusinessException("موجودی کیف پول تخفیف کافی نیست");
    }
    
    // ایجاد سفارش
    var order = new UserOrder {
        UserId = command.UserId,
        Amount = totalAmount,
        PaymentStatus = PaymentStatus.Pending,
        // ... سایر فیلدها
    };
    _context.UserOrders.Add(order);
    
    // ایجاد آیتم‌های فاکتور
    foreach (var item in cartItems)
    {
        var factorDetail = new FactorDetails {
            OrderId = order.Id,
            ProductId = item.ProductId,
            Count = item.Count,
            UnitPrice = item.Product.Price,
            UnitDiscount = item.Product.ClubDiscountPercent,
            // ...
        };
        _context.FactorDetails.Add(factorDetail);
    }
    
    // کسر از کیف پول تخفیف
    if (totalDiscountAmount > 0)
    {
        wallet.DiscountBalance -= totalDiscountAmount;
        
        // ثبت ChangeLog
        var changeLog = new UserWalletChangeLog {
            WalletId = wallet.Id,
            PreviousBalance = wallet.DiscountBalance + totalDiscountAmount,
            Amount = -totalDiscountAmount,
            AfterBalance = wallet.DiscountBalance,
            IsIncrease = false,
            TransactionType = TransactionType.Purchase,
            ReferenceId = order.Id.ToString()
        };
        _context.UserWalletChangeLogs.Add(changeLog);
    }
    
    await _context.SaveChangesAsync();
    
    // هدایت به درگاه برای پرداخت مبلغ نقدی (totalCashAmount)
    // یا اگر مبلغ نقدی صفر باشد، سفارش را Success کن
    if (totalCashAmount == 0)
    {
        order.PaymentStatus = PaymentStatus.Success;
        order.PaymentDate = DateTime.UtcNow;
        await _context.SaveChangesAsync();
    }
    
    return MapToDto(order);
}

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

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

کاربر → پرداخت ۵۶ میلیون (دایا/درگاه)
  ↓
UserWallet.Balance += 56,000,000
UserWallet.DiscountBalance += 56,000,000

مرحله ۲: فعال‌سازی عضویت

کاربر → کلیک روی دکمه «عضویت در باشگاه»
  ↓
API: ActivateClubMembership
  ↓
1. ایجاد رکورد ClubMembership:
   - IsActive = true
   - InitialContribution = 25,000,000
   
2. افزودن به استخر هفتگی:
   - WeeklyCommissionPool.TotalPoolAmount += 25,000,000
   
3. تعیین موقعیت در شبکه:
   - User.NetworkParentId = والد
   - User.LegPosition = Left یا Right
   
4. ثبت ChangeLog برای استخر:
   - TransactionType = ClubActivation

5. ثبت تاریخچه‌ها:
   - ClubMembershipHistory (Action = Activated)
   - NetworkMembershipHistory (Action = Join)

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

یکشنبه ۲۳:۵۹
  ↓
WeeklyNetworkCommissionWorker.Execute()
  ↓
1. محاسبه تعادل‌های کاربر
2. محاسبه ارزش هر امتیاز
3. توزیع کمیسیون به NetworkBalance
4. ریست تعادل‌های هفته قبل

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

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

۸. پروتوباف و gRPC Services

۸.۱ clubmembership.proto

syntax = "proto3";
import "google/protobuf/timestamp.proto";

package clubmembership;

service ClubMembershipService {
  rpc ActivateMembership (ActivateMembershipRequest) returns (ActivateMembershipResponse);
  rpc GetClubStatus (GetClubStatusRequest) returns (GetClubStatusResponse);
  rpc GrantFeature (GrantFeatureRequest) returns (GrantFeatureResponse);
  rpc GetUserFeatures (GetUserFeaturesRequest) returns (GetUserFeaturesResponse);
}

message ActivateMembershipRequest {
  int64 user_id = 1;
  int64 contribution_amount = 2;
  int64 network_parent_id = 3;
  NetworkLeg leg_position = 4;
}

message ActivateMembershipResponse {
  bool success = 1;
  string message = 2;
  ClubMembershipDto membership = 3;
}

message GetClubStatusRequest {
  int64 user_id = 1;
}

message GetClubStatusResponse {
  bool is_member = 1;
  ClubMembershipDto membership = 2;
}

message ClubMembershipDto {
  int64 id = 1;
  int64 user_id = 2;
  bool is_active = 3;
  google.protobuf.Timestamp activated_at = 4;
  int64 initial_contribution = 5;
  int64 total_earned = 6;
}

enum NetworkLeg {
  LEFT = 0;
  RIGHT = 1;
}

۸.۲ networkbalance.proto

syntax = "proto3";

package networkbalance;

service NetworkBalanceService {
  rpc GetNetworkTree (GetNetworkTreeRequest) returns (GetNetworkTreeResponse);
  rpc GetWeeklyBalances (GetWeeklyBalancesRequest) returns (GetWeeklyBalancesResponse);
  rpc GetNetworkStats (GetNetworkStatsRequest) returns (GetNetworkStatsResponse);
}

message GetNetworkTreeRequest {
  int64 user_id = 1;
  int32 max_depth = 2; // حداکثر عمق درخت (مثلاً ۳ سطح)
}

message GetNetworkTreeResponse {
  NetworkNodeDto root = 1;
}

message NetworkNodeDto {
  int64 user_id = 1;
  string full_name = 2;
  NetworkLeg leg_position = 3;
  bool is_active = 4;
  repeated NetworkNodeDto children = 5;
}

message GetWeeklyBalancesRequest {
  int64 user_id = 1;
  string week_number = 2; // اختیاری - اگر خالی باشد هفته جاری
}

message GetWeeklyBalancesResponse {
  int32 left_leg_balances = 1;
  int32 right_leg_balances = 2;
  int32 total_balances = 3;
  int64 pool_contribution = 4;
}

۸.۳ commissionpayout.proto

syntax = "proto3";
import "google/protobuf/timestamp.proto";

package commissionpayout;

service CommissionPayoutService {
  rpc RequestWithdrawal (RequestWithdrawalRequest) returns (RequestWithdrawalResponse);
  rpc GetCommissionHistory (GetCommissionHistoryRequest) returns (GetCommissionHistoryResponse);
  rpc GetPendingWithdrawals (GetPendingWithdrawalsRequest) returns (GetPendingWithdrawalsResponse);
  rpc ProcessWithdrawal (ProcessWithdrawalRequest) returns (ProcessWithdrawalResponse);
}

message RequestWithdrawalRequest {
  int64 user_id = 1;
  int64 amount = 2;
  WithdrawalMethod method = 3;
  string iban_number = 4; // فقط برای Cash
}

message RequestWithdrawalResponse {
  bool success = 1;
  string message = 2;
  int64 request_id = 3;
}

message GetCommissionHistoryRequest {
  int64 user_id = 1;
  int32 page_number = 2;
  int32 page_size = 3;
}

message GetCommissionHistoryResponse {
  repeated CommissionPayoutDto payouts = 1;
  int32 total_count = 2;
}

message CommissionPayoutDto {
  int64 id = 1;
  string week_number = 2;
  int32 balances_earned = 3;
  int64 value_per_balance = 4;
  int64 total_amount = 5;
  CommissionPayoutStatus status = 6;
  google.protobuf.Timestamp paid_at = 7;
  WithdrawalMethod withdrawal_method = 8;
}

enum WithdrawalMethod {
  CASH = 0;
  DIAMOND = 1;
}

enum CommissionPayoutStatus {
  PENDING = 0;
  PAID = 1;
  WITHDRAW_REQUESTED = 2;
  WITHDRAWN = 3;
  CANCELLED = 4;
}

۹. نکات حیاتی و بهترین رویه‌ها

۹.۱ یکپارچگی شبکه باینری

  • ولیدیشن: هر کاربر حداکثر دو فرزند (یکی Left، یکی Right)
  • قفل خوش‌بینانه: هنگام اضافه کردن فرزند، بررسی Race Condition
  • Cascade Delete: حذف کاربر نباید ساختار شبکه را خراب کند (باید جایگزین شود یا درخت بازسازی گردد)

۹.۲ Transaction Management

  • Worker باید تمام مراحل محاسبه و توزیع را در یک TransactionScope انجام دهد
  • در صورت شکست، Rollback کامل و ارسال آلارم

۹.۳ Idempotency

  • محاسبه هفتگی نباید دوبار برای یک هفته اجرا شود
  • بررسی WeeklyCommissionPool.IsCalculated قبل از شروع

۹.۴ Performance

  • برای کاربران با زیرمجموعه زیاد، Caching درخت شبکه
  • Pagination در API های لیست
  • Index های مناسب روی WeekNumber, UserId, NetworkParentId

۹.۵ Audit و Compliance

  • کیف پول: تمام تغییرات در UserWalletChangeLog ثبت شود
  • عضویت: تمام تغییرات در ClubMembershipHistory ثبت شود
  • شبکه: تمام جابجایی‌ها در NetworkMembershipHistory ثبت شود
  • کمیسیون: تمام تغییرات در CommissionPayoutHistory ثبت شود
  • تنظیمات: تمام تغییرات Config در SystemConfigurationHistory ثبت شود
  • امکانات:
    • بازسازی وضعیت سیستم در هر زمان گذشته
    • گزارش‌گیری کامل برای حسابرسی
    • شناسایی تغییرات غیرمجاز یا خطاهای سیستمی
  • Index های پیشنهادی:
    CREATE INDEX IX_History_UserId_Created ON ClubMembershipHistory(UserId, Created);
    CREATE INDEX IX_History_WeekNumber ON CommissionPayoutHistory(WeekNumber);
    CREATE INDEX IX_Config_Scope_Key ON SystemConfiguration(Scope, Key);
    

۹.۶ Security

  • محدودیت تعداد درخواست برداشت (Rate Limiting)
  • تایید دو مرحله‌ای برای برداشت‌های بالا
  • Audit Log برای تمام عملیات حساس

۱۰. مراحل پیاده‌سازی (Implementation Roadmap)

📅 زمان‌بندی کلی و اولویت‌بندی

فاز مدت زمان اولویت وابستگی‌ها
فاز ۱: پایه‌گذاری Domain 3-5 روز 🔴 حیاتی -
فاز ۲: باشگاه مشتریان 3-4 روز 🔴 حیاتی فاز ۱
فاز ۳: شبکه باینری 4-5 روز 🔴 حیاتی فاز ۱، ۲
فاز ۴: کمیسیون و Worker 5-6 روز 🔴 حیاتی فاز ۱، ۲، ۳
فاز ۵: Protobuf Services 2-3 روز 🟡 متوسط فاز ۱
فاز ۶: History و Configuration 3-4 روز 🟡 متوسط فاز ۱
فاز ۷: Testing کامل 5-7 روز 🔴 حیاتی همه فازها
فاز ۸: UI BackOffice 5-7 روز 🟢 عادی فاز ۱
فاز ۹: فروشگاه باشگاه 3-4 روز 🟢 عادی فاز ۲
فاز ۱۰: برداشت و تسویه 3-4 روز 🟡 متوسط فاز ۴

⏱️ تخمین کل: 36-49 روز کاری (7-10 هفته)


🚀 فاز ۱: پایه‌گذاری Domain Layer (روز ۱-۵)

روز ۱: آماده‌سازی و Enums

cd /home/masoud/Apps/project/FourSat/CMS/src/CMSMicroservice.Domain

# ایجاد ساختار پوشه‌ها
mkdir -p Entities/Club Entities/Network Entities/Commission Entities/Configuration Entities/History Enums

Tasks:

  • ایجاد Branch جدید: feature/network-club-system
  • ایجاد Enums (۷ فایل):
    • CommissionPayoutStatus.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 های حیاتی:
    builder.HasIndex(e => e.UserId);
    builder.HasIndex(e => e.WeekNumber);
    builder.HasIndex(e => new { e.Scope, e.Key }); // Config
    builder.HasIndex(e => new { e.UserId, e.Created }); // History
    
  • ایجاد Migration:
    cd CMSMicroservice.Infrastructure
    dotnet ef migrations add AddNetworkClubSystem --project ../CMSMicroservice.WebApi
    
  • بررسی دقیق Migration Script
  • تست Migration روی دیتابیس Development
  • Commit: "Add EF configurations and migration for network-club system"

🎯 فاز ۲: باشگاه مشتریان (روز ۶-۹)

روز ۶: ConfigurationCQ (اولویت بالا)

چرا اول؟ بقیه ماژول‌ها به Config نیاز دارند.

cd CMSMicroservice.Application
mkdir -p ConfigurationCQ/Commands ConfigurationCQ/Queries ConfigurationCQ/DTOs

صبح:

  • Commands/SetConfigurationValueCommand.cs + 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

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

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:
    public async Task<int> CalculateLegBalances(
        long userId, 
        NetworkLeg leg, 
        string weekNumber, 
        int maxBalances
    )
    
    • الگوریتم Recursive
    • Caching برای جلوگیری از محاسبات تکراری
    • اعمال سقف از Config
  • تست الگوریتم با داده Mock
  • Commit: "Implement network balance calculation algorithm"

روز ۱۲-۱۳: NetworkBalanceCQ - Queries

روز ۱۲ صبح:

  • Queries/GetUserNetworkTreeQuery.cs + Handler
    • Recursive query با محدودیت عمق
    • DTO با ساختار درختی
    • Caching برای Performance

روز ۱۲ بعدازظهر:

  • Queries/GetUserWeeklyBalancesQuery.cs + Handler
  • Queries/GetNetworkStatisticsQuery.cs + Handler
    • تعداد کل اعضا
    • عمق متوسط شبکه
    • توزیع چپ/راست

روز ۱۳:

  • Queries/GetNetworkMembershipHistoryQuery.cs + Handler
  • DTOs/NetworkNodeDto.cs, NetworkStatisticsDto.cs
  • تست کامل ماژول
  • Commit: "Complete NetworkBalanceCQ module"

روز ۱۴: تست عملکرد و بهینه‌سازی

  • تست با ۱۰۰۰ کاربر Mock
  • Profiling و شناسایی Bottlenecks
  • بهینه‌سازی Queries (اضافه کردن Index در صورت نیاز)
  • Commit: "Optimize network balance queries"

💰 فاز ۴: کمیسیون و Worker (روز ۱۵-۲۰)

روز ۱۵-۱۶: CommissionPoolCQ

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

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 هفتگی

cd CMSMicroservice.Infrastructure/BackgroundJobs

روز ۱۹ صبح:

  • ایجاد WeeklyNetworkCommissionWorker.cs
  • پیاده‌سازی گام ۱ و ۲:
    • بستن استخر قبل
    • ایجاد استخر جدید
    • محاسبه تعادل‌ها با استفاده از CalculateLegBalancesService

روز ۱۹ بعدازظهر:

  • پیاده‌سازی گام ۳ و ۴:
    • محاسبه ValuePerBalance
    • توزیع کمیسیون‌ها
    • واریز به NetworkBalance
    • ثبت UserWalletChangeLog

روز ۲۰ صبح:

  • پیاده‌سازی گام ۵:
    • ریست تعادل‌ها (IsExpired = true)
  • پیاده‌سازی Idempotency Check
  • پیاده‌سازی Transaction Scope کامل
  • Logging جامع

روز ۲۰ بعدازظهر:

  • تنظیم زمان اجرا (یکشنبه ۲۳:۵۹)
  • ایجاد Controller تست برای اجرای دستی
  • تست Worker با داده واقعی
  • بررسی Rollback در صورت خطا
  • Commit: "Implement weekly commission worker"

🔌 فاز ۵: Protobuf Services (روز ۲۱-۲۳)

روز ۲۱: تعریف Proto Files

cd CMSMicroservice.Protobuf/Protos
  • clubmembership.proto
    • Services: ActivateMembership, GetClubStatus, GrantFeature, GetUserFeatures
  • networkbalance.proto
    • Services: GetNetworkTree, GetWeeklyBalances, GetNetworkStats
  • commissionpayout.proto
    • Services: RequestWithdrawal, GetCommissionHistory, GetPendingWithdrawals, ProcessWithdrawal
  • configuration.proto
    • Services: GetConfiguration, SetConfiguration, GetConfigurationHistory
  • Build و بررسی Generated Code
  • Commit: "Add protobuf definitions for network-club system"

روز ۲۲-۲۳: پیاده‌سازی Service Implementations

cd CMSMicroservice.WebApi/Services

روز ۲۲:

  • ClubMembershipGrpcService.cs
  • NetworkBalanceGrpcService.cs

روز ۲۳:

  • CommissionPayoutGrpcService.cs
  • ConfigurationGrpcService.cs
  • تست gRPC با BloomRPC یا Postman
  • Commit: "Implement gRPC service implementations"

📊 فاز ۶: History و Configuration Seed (روز ۲۴-۲۷)

روز ۲۴: Seed Data برای SystemConfiguration

cd CMSMicroservice.Infrastructure/Persistence/Seeds
  • ایجاد SystemConfigurationSeeder.cs
  • تعریف تنظیمات پیش‌فرض:
    // Network Scope
    MaxWeeklyBalancesPerUser = 300
    MaxChildrenPerLeg = 1
    MaxNetworkDepth = 15
    
    // Commission Scope
    DefaultInitialContribution = 25000000
    MinWithdrawalAmount = 1000000
    
    // Club Scope
    ActivationFee = 25000000
    
  • اجرای Seeder
  • Commit: "Add system configuration seed data"

روز ۲۵-۲۶: Integration Testing کامل History

  • تست ثبت History در تمام Commands
  • تست Query های History
  • تست Performance با ۱۰،۰۰۰ رکورد History
  • بهینه‌سازی Index ها در صورت نیاز
  • Commit: "Verify and optimize history recording"

روز ۲۷: مستندسازی Config و History

  • ایجاد CONFIG_GUIDE.md با لیست تمام کلیدها
  • ایجاد AUDIT_GUIDE.md برای نحوه استفاده از History
  • Commit: "Add configuration and audit documentation"

🧪 فاز ۷: Testing کامل (روز ۲۸-۳۴)

روز ۲۸-۲۹: Unit Tests

  • Tests برای تمام Handlers (با Mock)
  • Tests برای Validators
  • Tests برای CalculateLegBalancesService
  • Coverage حداقل ۸۰٪

روز ۳۰-۳۱: Integration Tests

  • تست کامل فلوی فعال‌سازی عضویت
  • تست Worker با داده واقعی
  • تست اتصال به دیتابیس
  • تست Transaction Rollback

روز ۳۲-۳۳: End-to-End Tests

سناریوی کامل:

1. کاربر A شارژ می‌کند (56M)
2. فعال‌سازی عضویت (25M به استخر)
3. کاربر B و C به زیرمجموعه A می‌پیوندند
4. کاربر B دو نفر جذب می‌کند (D و E)
5. کاربر C دو نفر جذب می‌کند (F و G)
6. اجرای Worker هفتگی
7. بررسی کمیسیون دریافتی A, B, C
8. درخواست برداشت از کاربر A
9. تایید برداشت توسط Admin
10. بررسی تاریخچه کامل در History

روز ۳۴: Load Testing و Performance

  • تست با ۱۰،۰۰۰ کاربر
  • تست Worker با ۵۰۰ پرداخت همزمان
  • Profiling و شناسایی Memory Leaks
  • Commit: "Complete comprehensive testing"

🎨 فاز ۸: UI در BackOffice (روز ۳۵-۴۱)

cd /home/masoud/Apps/project/FourSat/BackOffice/src/BackOffice/Pages
mkdir -p Club Network Commission Configuration

روز ۳۵-۳۶: صفحات Club

  • Club/MembersList.razor
    • لیست اعضای باشگاه
    • فیلتر IsActive، تاریخ
    • Pagination
  • Club/MemberDetails.razor
    • جزئیات عضویت
    • تاریخچه تغییرات
  • Club/Features.razor
    • مدیریت فیچرها
    • Grant/Revoke به کاربران

روز ۳۷-۳۸: صفحات Network

  • Network/Tree.razor
    • نمایش درخت شبکه (Tree View Component)
    • Expand/Collapse
    • جستجوی کاربر
  • Network/Statistics.razor
    • Dashboard آماری
    • Charts و نمودارها

روز ۳۹-۴۰: صفحات Commission و Configuration

  • Commission/Withdrawals.razor
    • لیست درخواست‌های برداشت
    • تایید/رد
    • جستجو و فیلتر
  • Configuration/Settings.razor
    • پنل تنظیمات
    • ویرایش مقادیر Config
    • نمایش تاریخچه تغییرات

روز ۴۱: تست و بهبود UI/UX

  • تست تمام صفحات
  • Responsive Design
  • بهبود User Experience
  • Commit: "Complete BackOffice UI for network-club system"

🛒 فاز ۹: فروشگاه باشگاه (روز ۴۲-۴۵)

روز ۴۲: به‌روزرسانی لیست محصولات

  • فیلتر محصولات اختصاصی (IsClubExclusive)
  • نمایش تخفیف باشگاه
  • محاسبه قیمت نهایی

روز ۴۳-۴۴: لاجیک Checkout

  • CheckoutWithClubDiscountCommand.cs + Handler
  • محاسبه totalDiscountAmount و totalCashAmount
  • کسر از DiscountBalance
  • ثبت در UserWalletChangeLog
  • Validator (بررسی موجودی کافی)

روز ۴۵: تست End-to-End خرید

  • تست خرید با تخفیف
  • تست خرید بدون تخفیف
  • تست محصولات اختصاصی
  • Commit: "Implement club shop with discount logic"

💸 فاز ۱۰: برداشت و تسویه (روز ۴۶-۴۹)

روز ۴۶: اتصال به سرویس پرداخت

  • ایجاد IPaymentGatewayService.cs
  • پیاده‌سازی برای درگاه مورد نظر
  • تست اتصال

روز ۴۷: اتصال به دایا (خرید الماس)

  • ایجاد IDayaService.cs
  • API برای خرید الماس
  • تست اتصال

روز ۴۸: پیاده‌سازی Withdrawal Flow

  • اتصال ProcessWithdrawalCommand به سرویس‌ها
  • Handle کردن خطاها
  • Retry Mechanism

روز ۴۹: UAT و تست نهایی

  • تست کامل فلوی برداشت نقدی
  • تست کامل فلوی خرید الماس
  • بررسی Audit Trail
  • Commit: "Complete withdrawal and settlement flow"

Checklist نهایی قبل از Production

کد و معماری

  • Code Review کامل توسط تیم
  • تست Coverage بالای ۸۰٪
  • تمام TODO ها رفع شده
  • مستندسازی کامل API

دیتابیس

  • بررسی دقیق Migration Scripts
  • Backup Strategy تعیین شده
  • Index های بهینه اضافه شده
  • Seed Data اجرا شده

Performance

  • Load Testing انجام شده
  • Memory Leaks بررسی شده
  • Caching استراتژی تعیین شده
  • Query Optimization انجام شده

Security

  • Authorization برای تمام Endpoints
  • Validation کامل ورودی‌ها
  • Rate Limiting برای API های حساس
  • Audit Logging فعال

Monitoring

  • Logging جامع
  • Health Checks تعریف شده
  • Alert ها تنظیم شده
  • Dashboard های مانیتورینگ

📝 نکات مهم حین پیاده‌سازی

Daily Routine

# صبح هر روز:
git pull origin develop
git checkout feature/network-club-system
dotnet restore
dotnet build

# عصر هر روز:
git add .
git commit -m "feat: [توضیح کار انجام شده]"
git push origin feature/network-club-system

# هفته‌ای یک بار:
git merge develop  # برای همگام‌سازی

Commit Message Convention

feat: Add ClubMembership entity
fix: Fix balance calculation in Worker
refactor: Optimize network tree query
test: Add unit tests for ActivateClubMembership
docs: Update configuration guide

Code Review Checklist

  • کد Clean و قابل فهم است
  • Validation کامل است
  • Exception Handling مناسب است
  • Logging کافی است
  • History ثبت می‌شود
  • Test coverage کافی است

🚨 Risk Management

ریسک احتمال تاثیر راه‌حل
پیچیدگی الگوریتم تعادل متوسط بالا شروع زودتر + تست گسترده
Performance Worker متوسط بالا Profiling + بهینه‌سازی Query
Race Condition در Network کم بالا Transaction + Lock
حجم داده History بالا متوسط Archiving + Partitioning
تاخیر در UI متوسط کم شروع موازی با Backend

📞 نقاط ارتباطی و پشتیبانی

  • سوالات فنی Domain: [نام مسئول Backend]
  • سوالات UI/UX: [نام مسئول Frontend]
  • سوالات دیتابیس: [نام DBA]
  • مدیریت پروژه: [نام PM]

🎯 Definition of Done

یک Task زمانی Done است که:

  • کد نوشته و Commit شده
  • Unit Test نوشته شده (اگر لازم باشد)
  • Code Review انجام شده
  • مستندات به‌روز شده (اگر لازم باشد)
  • در محیط Development تست شده
  • هیچ Warning یا Error در Build نباشد

۱۱. متریک‌های کلیدی (KPIs)

برای Business

  • تعداد اعضای فعال باشگاه
  • مجموع کمیسیون‌های پرداختی هر ماه
  • میانگین تعادل هر کاربر در هفته
  • نرخ تبدیل (Conversion Rate) به عضویت باشگاه
  • درآمد از فروشگاه باشگاه

برای Technical

  • زمان اجرای Worker هفتگی
  • تعداد خطاها در محاسبات
  • میانگین زمان پاسخ API های شبکه
  • حجم داده جداول جدید
  • عمق متوسط درخت شبکه
  • حجم رشد جداول History (رکورد/ماه)
  • تعداد تغییرات Config در ماه
  • میانگین زمان Query های History
  • تعداد Audit Log های غیرمعمول

۱۲. سوالات متداول (FAQ)

Q: اگر کاربری دو بار ۵۶ میلیون شارژ کند، چه می‌شود؟ A: هر بار Balance و DiscountBalance افزایش می‌یابد، اما فقط یک‌بار می‌تواند عضو باشگاه شود (دکمه غیرفعال می‌شود).

Q: آیا می‌توان موقعیت کاربر در شبکه را تغییر داد؟ A: فقط با دسترسی مدیریتی و در شرایط خاص (مثلاً خطای ثبت) - تغییر بعد از محاسبات هفتگی ممنوع است.

Q: اگر کاربری تعادل نزند، آیا امتیازش صفر می‌شود؟ A: بله، در آن هفته امتیاز صفر دارد و کمیسیون نمی‌گیرد.

Q: آیا تعادل‌های قبلی انباشته می‌شوند؟ A: خیر، تعادل‌ها هفتگی محاسبه و ریست می‌شوند.

Q: چه کسی می‌تواند درخواست برداشت را تایید کند؟ A: فقط ادمین‌ها با نقش مشخص در BackOffice.

Q: آیا می‌توان تنظیمات سیستم را بدون Deployment تغییر داد؟ A: بله، تمام تنظیمات کلیدی در SystemConfiguration ذخیره می‌شوند و قابل تغییر آنلاین هستند. تاریخچه تغییرات نیز حفظ می‌شود.

Q: چگونه می‌توان تاریخچه تغییرات یک کاربر را بررسی کرد؟ A: از جداول History استفاده کنید:

  • عضویت: ClubMembershipHistory
  • شبکه: NetworkMembershipHistory
  • کمیسیون: CommissionPayoutHistory
  • کیف پول: UserWalletChangeLog

Q: حداکثر تعداد تعادل هفتگی برای هر کاربر چقدر است؟ A: توسط تنظیم MaxWeeklyBalancesPerUser در Config تعیین می‌شود (پیش‌فرض: ۳۰۰). این محدودیت از سوءاستفاده جلوگیری می‌کند.

Q: اگر تنظیمات Config اشتباه وارد شود چه می‌شود؟ A: تمام تغییرات در SystemConfigurationHistory ثبت می‌شود و قابل بازگردانی است. همچنین Validation در Handler ها وجود دارد.


۱۳. ضمیمه: مثال عددی کامل

هفته اول:

کاربر A: فعال‌سازی (۲۵M به استخر)
  ├─ فرزند Left: کاربر B (فعال‌سازی ۲۵M)
  └─ فرزند Right: کاربر C (فعال‌سازی ۲۵M)

استخر هفته اول: ۷۵M
تعادل کاربر A: MIN(1, 1) = 1
تعادل کاربر B: 0
تعادل کاربر C: 0

مجموع تعادل‌ها: 1
ارزش هر امتیاز: 75M ÷ 1 = 75M

کمیسیون کاربر A: 1 × 75M = 75M

هفته دوم:

کاربر B: جذب دو نفر (D و E) → تعادل ۱
کاربر C: جذب دو نفر (F و G) → تعادل ۱

استخر هفته دوم: ۴ × ۲۵M = ۱۰۰M
تعادل کاربر A: MIN(1, 1) = 1 (از B و C)
تعادل کاربر B: 1
تعادل کاربر C: 1

مجموع تعادل‌ها: 3
ارزش هر امتیاز: 100M ÷ 3 ≈ 33.33M

کمیسیون کاربر A: 1 × 33.33M = 33.33M
کمیسیون کاربر B: 1 × 33.33M = 33.33M
کمیسیون کاربر C: 1 × 33.33M = 33.33M

۱۴. مسیرهای مرتبط

  • کد Domain: CMS/src/CMSMicroservice.Domain/Entities/
  • کد Application: CMS/src/CMSMicroservice.Application/ClubMembershipCQ/, NetworkBalanceCQ/, ...
  • Protobuf: CMS/src/CMSMicroservice.Protobuf/Protos/
  • Worker: CMS/src/CMSMicroservice.Infrastructure/BackgroundJobs/
  • مستند حاضر: CMS/docs/network-club-commission-system.md

نسخه: 1.1 (با History و Configuration)
تاریخ: 2025-11-29
نویسنده: تیم توسعه CMS
وضعیت: آماده پیاده‌سازی - شامل Audit Trail و تنظیمات پویا

تغییرات نسخه 1.1:

  • اضافه شدن ۴ جدول History برای Audit کامل
  • اضافه شدن SystemConfiguration برای تنظیمات پویا
  • سقف تعادل هفتگی قابل تنظیم از Config
  • Enum های Action برای تاریخچه‌ها
  • گسترش ماژول ConfigurationCQ
  • بهبود بخش Audit و Compliance
  • افزودن فاز ۷ به Roadmap
  • گسترش FAQ و متریک‌ها