Files
docs/business/BIZ-PACKAGE-BASED-SYSTEM-v1-draft.md
masoodafar-web dd5a2617cf docs: BIZ-PACKAGE-BASED-SYSTEM v2 — deep analysis + approved decisions
- 6 bugs found (DiscountBalance, UserPackagePurchase, re-purchase blocked)
- 15 hardcodes identified for removal
- 7 guards blocking re-purchase analyzed
- 5 payment path inconsistencies documented
- 39 changes across 6 layers planned
- 5 phases: bugfix → infra → logic → commission → UI → test
- v1 draft preserved as BIZ-PACKAGE-BASED-SYSTEM-v1-draft.md
2026-02-24 21:34:59 +03:30

25 KiB
Raw Permalink Blame History

📦 سیستم مبتنی بر پکیج (Package-Based System)

وضعیت: تحلیل و بررسی — منتظر تایید
تاریخ: اسفند ۱۴۰۴
تاثیرگذاری: زیاد — بخش‌های متعدد سیستم تحت تاثیر قرار می‌گیرد


۱. خلاصه فیچر

وضعیت فعلی: سیستم فقط یک پکیج پایه (۵۶ میلیون تومان) دارد و همه چیز حول آن می‌چرخد.

وضعیت هدف: سیستم چندین پکیج با قیمت‌ها و ویژگی‌های متفاوت پشتیبانی می‌کند. هر پکیج روش‌های پرداخت، محاسبه پورسانت، شارژ کیف پول و فیچرهای مختص خود را دارد.

مثال پکیج‌ها:
┌──────────────┬──────────────┬──────────────┬──────────────┐
│  🥈 نقره‌ای   │  🥇 طلایی     │  💎 الماسی    │   ⭐ ویژه     │
│  ۵.۶M تومان  │  ۵۶M تومان   │   ؟؟ تومان   │   ؟؟ تومان   │
│              │  (پکیج پایه) │              │              │
│ فقط مستقیم   │ دایا+مستقیم  │ فقط مستقیم   │ فقط مستقیم   │
│ فیچر محدود   │ همه فیچرها   │ همه فیچرها   │ همه+اختصاصی  │
└──────────────┴──────────────┴──────────────┴──────────────┘

۲. وضعیت فعلی سیستم (AS-IS)

۲.۱ فلوی فعلی فعالسازی

flowchart TD
    A["کاربر وارد سیستم می‌شود"] --> B{"روش پرداخت"}
    B -->|"خرید الماس دایا"| C["DayaLoan — ۵۶M"]
    B -->|"پرداخت مستقیم"| D["درگاه بانکی — ۵۶M"]
    C --> E["بررسی موفقیت پرداخت"]
    D --> E
    E --> F["شارژ کیف پول"]
    F --> G["مدال قرارداد باشگاه مشتریان"]
    G --> H["تایید OTP + امضا"]
    H --> I["فعال‌سازی عضویت باشگاه"]
    I --> J["اختصاص فیچرها"]
    I --> K["ایجاد Cycle"]
    I --> L["اضافه به Commission Pool"]
    J --> M["✅ کاربر فعال — لینک معرف"]

۲.۲ جریان پول فعلی

کاربر ۵۶M پرداخت می‌کند
    │
    ├── Balance (کیف پول عادی)     += ۵۶,۰۰۰,۰۰۰ ریال
    ├── DiscountBalance (اعتباری)  += ۱۱۲,۰۰۰,۰۰۰ ریال (×۲)
    │
    └── Club Activation:
        ├── CommissionPool          += ۲۵,۲۰۰,۰۰۰ ریال (ClubActivationFee)
        └── GiftValue               = ۲۵,۲۰۰,۰۰۰ ریال (اطلاع‌رسانی)

۲.۳ مقادیر Hardcoded فعلی (SystemConstants.cs)

ثابت مقدار کاربرد
BasePackageAmount ۵۶,۰۰۰,۰۰۰ قیمت پکیج
DayaLoanAmount ۵۶,۰۰۰,۰۰۰ مبلغ وام دایا
ClubActivationFee ۲۵,۲۰۰,۰۰۰ سهم هفتگی Commission Pool
ClubMembershipGiftValue ۲۵,۲۰۰,۰۰۰ ارزش هدیه حق عضویت
MagicWalletMultiplier ×۲.۵ ضریب کیف پول جادویی

۲.۴ مشکلات فعلی

# مشکل فایل
۱ پکیج ID=4 hardcoded در InitiateBasePackagePaymentCommandHandler Application/Commands
۲ مبلغ ۵۶M hardcoded در SystemConstants و چندین handler Domain/Common
۳ فیچرها همه یکجا assign می‌شن (۴ فیچر ثابت: چتیکا، بیمه، تریپ، لرن) ActivateClubMembershipHandler
۴ Commission Pool فقط با ClubActivationFee ثابت پر می‌شه ActivateClubMembershipHandler
۵ DiscountBalance = Amount × 2 — ضریب hardcoded VerifyPayment handlers
۶ فرانت‌اند فقط یک مسیر خرید نشون میده FrontOffice pages

۳. طراحی پیشنهادی (TO-BE)

۳.۱ فلوی جدید فعالسازی

flowchart TD
    A["کاربر وارد سیستم"] --> B["صفحه پکیج‌ها<br/>(کاشی‌های نقره‌ای/طلایی/الماسی/...)"]
    
    B -->|"کلیک روی پکیج"| C{"نوع پکیج"}
    
    C -->|"پکیج پایه (طلایی)"| D["مدال با دو گزینه:<br/>۱. خرید الماس دایا<br/>۲. پرداخت مستقیم"]
    C -->|"پکیج‌های دیگر"| E["مدال با یک گزینه:<br/>فقط پرداخت مستقیم<br/>+ توضیحات + قیمت"]
    
    D -->|"دایا"| F["فلوی دایا"]
    D -->|"مستقیم"| G["درگاه پرداخت"]
    E --> G
    
    F --> H["پرداخت موفق"]
    G --> H
    
    H --> I["شارژ کیف پول<br/>(متناسب با قیمت پکیج)"]
    I --> J["مدال قرارداد باشگاه"]
    J --> K["OTP + امضا"]
    K --> L["فعال‌سازی<br/>+ اختصاص فیچرهای پکیج"]
    L --> M["✅ کاربر فعال"]

۳.۲ تغییرات Entity — Package

فعلی:

public class Package : BaseAuditableEntity
{
    public string Title { get; set; }
    public string Description { get; set; }
    public string ImagePath { get; set; }
    public long Price { get; set; }
}

پیشنهادی:

public class Package : BaseAuditableEntity
{
    public string Title { get; set; }
    public string Description { get; set; }
    public string ImagePath { get; set; }
    public long Price { get; set; }                          // قیمت پکیج (ریال)
    
    // === فیلدهای جدید ===
    public int SortOrder { get; set; }                       // ترتیب نمایش
    public bool IsActive { get; set; } = true;               // فعال/غیرفعال
    public bool IsBasePackage { get; set; }                   // آیا پکیج پایه است؟
    public bool SupportsDayaPurchase { get; set; }            // پشتیبانی از خرید دایا
    public bool SupportsDirectPurchase { get; set; } = true;  // پشتیبانی از پرداخت مستقیم
    
    // === محاسبات مالی ===
    public long ActivationFee { get; set; }                  // سهم Commission Pool
    public long GiftValue { get; set; }                      // ارزش هدیه
    public decimal DiscountMultiplier { get; set; } = 2.0m;  // ضریب شارژ DiscountBalance
    
    // === Navigation ===
    public virtual ICollection<PackageFeature> PackageFeatures { get; set; }
    public virtual ICollection<UserPackagePurchase> Purchases { get; set; }
}

۳.۳ Entity جدید — PackageFeature (پل بین پکیج و فیچر)

/// <summary>
/// مشخص می‌کند هر پکیج چه فیچرهایی را فعال می‌کند
/// </summary>
public class PackageFeature : BaseAuditableEntity
{
    public long PackageId { get; set; }
    public virtual Package Package { get; set; }
    
    public long ClubFeatureId { get; set; }
    public virtual ClubFeature ClubFeature { get; set; }
    
    public bool IsIncluded { get; set; } = true;  // آیا این فیچر در پکیج هست؟
}

۳.۴ تغییرات Entity — ClubMembership

public class ClubMembership : BaseAuditableEntity
{
    // ... فیلدهای فعلی حفظ می‌شوند ...
    
    // === فیلد جدید ===
    public long PackageId { get; set; }              // کدام پکیج خریداری شده
    public virtual Package Package { get; set; }
}

۳.۵ تغییرات Entity — ClubMembershipCycle

public class ClubMembershipCycle : BaseAuditableEntity
{
    // ... فیلدهای فعلی حفظ می‌شوند ...
    
    // === فیلد جدید ===
    public long PackageId { get; set; }              // پکیج این سایکل
    public virtual Package Package { get; set; }
    // PackageAmount قبلاً وجود دارد — از Package.Price پر می‌شود
}

۳.۶ تغییرات Entity — WeeklyCommissionPool

public class WeeklyCommissionPool : BaseAuditableEntity
{
    // ... فیلدهای فعلی حفظ می‌شوند ...
    
    // === فیلد جدید ===
    public long PackageId { get; set; }              // Pool جداگانه برای هر پکیج
    public virtual Package Package { get; set; }
}

۳.۷ جریان پول جدید

پکیج نقره‌ای (۵.۶M):
    ├── Balance        += ۵,۶۰۰,۰۰۰
    ├── DiscountBalance += ۱۱,۲۰۰,۰۰۰ (×۲)
    └── CommissionPool += ActivationFee مخصوص نقره‌ای

پکیج طلایی/پایه (۵۶M):
    ├── Balance        += ۵۶,۰۰۰,۰۰۰
    ├── DiscountBalance += ۱۱۲,۰۰۰,۰۰۰ (×۲)
    └── CommissionPool += ۲۵,۲۰۰,۰۰۰

پکیج الماسی (??M):
    ├── Balance        += ??
    ├── DiscountBalance += ?? (×۲)
    └── CommissionPool += ActivationFee مخصوص الماسی

۴. محاسبه پورسانت — تغییرات

۴.۱ وضعیت فعلی

یک WeeklyCommissionPool برای کل هفته
    ↓
TotalAmount = مجموع ActivationFee همه فعالسازی‌ها
    ↓
ValuePerBalance = TotalAmount ÷ مجموع Balance‌ها
    ↓
همه یکسان محاسبه می‌شوند

۴.۲ وضعیت هدف

برای هر پکیج، یک WeeklyCommissionPool جداگانه:

Pool_نقره‌ای:
    TotalAmount = مجموع ActivationFee خریداران نقره‌ای این هفته
    Balance‌ها = فقط از شبکه خریداران نقره‌ای
    ValuePerBalance = Pool_نقره‌ای ÷ Balance_نقره‌ای

Pool_طلایی:
    TotalAmount = مجموع ActivationFee خریداران طلایی این هفته
    Balance‌ها = فقط از شبکه خریداران طلایی
    ValuePerBalance = Pool_طلایی ÷ Balance_طلایی

۴.۳ نکته مهم: ساختار شبکه یکی است

                    [Ali]
                   /     \
              [Sara]     [Reza]        ← شبکه باینری یکی‌ست
             /    \      /    \
          [M1]  [M2]  [M3]  [M4]

ولی محاسبات جدا:
  - Ali با پکیج طلایی → پورسانت از Pool طلایی
  - Sara با پکیج نقره‌ای → پورسانت از Pool نقره‌ای
  - Reza با پکیج طلایی → پورسانت از Pool طلایی

۴.۴ تغییرات Stored Procedure

sp_CalculateWeeklyBalances باید:

  • پارامتر @PackageId بگیرد
  • فقط کاربرانی که این پکیج را خریده‌اند فیلتر کند
  • برای هر پکیج جداگانه اجرا شود

sp_CalculateWeeklyCommissionPool باید:

  • پارامتر @PackageId بگیرد
  • Pool مخصوص آن پکیج را بخواند
  • پرداخت‌ها فقط به خریداران آن پکیج اختصاص یابد

۵. فیچرهای باشگاه مشتریان بر اساس پکیج

۵.۱ وضعیت فعلی

وقتی کاربر فعال می‌شود، همه ۴ فیچر یکجا assign می‌شوند:

// ActivateClubMembershipCommandHandler — خط ~350
var allFeatureIds = ClubFeatureTypeExtensions.GetAllFeatureIds();
foreach (var featureId in allFeatureIds)
{
    userClubFeatures.Add(new UserClubFeature { ... });
}

۵.۲ وضعیت هدف

فیچرها بر اساس جدول PackageFeature تعیین می‌شوند:

فیچر نقره‌ای طلایی (پایه) الماسی
چتیکا
بیمه
تریپ
لرن
فیچر VIP

مقادیر بالا نمونه‌ای هستند — قابل تنظیم از BackOffice

۵.۳ تغییر در ActivateClubMembershipHandler

قبلی:
  GetAllFeatureIds() → assign all

جدید:
  Package.PackageFeatures
    .Where(pf => pf.IsIncluded)
    .Select(pf => pf.ClubFeatureId)
    → assign only included features

۶. تغییرات UI — FrontOffice

۶.۱ صفحه پکیج‌ها (کاشی‌ها)

┌─────────────────────────────────────────────────────┐
│              انتخاب پکیج باشگاه مشتریان              │
├─────────────┬──────────────┬──────────────┬─────────┤
│             │              │              │         │
│  🥈 نقره‌ای  │  🥇 طلایی     │  💎 الماسی    │  ⭐ ویژه │
│  ۵.۶M       │  ۵۶M         │  ؟؟M         │  ؟؟M   │
│             │              │              │         │
│ ● لرن       │ ● چتیکا      │ ● همه        │ ● همه  │
│ ● تریپ      │ ● بیمه       │ ● + VIP      │ ● +... │
│             │ ● تریپ       │              │         │
│             │ ● لرن        │              │         │
│             │              │              │         │
│ [انتخاب]    │ [انتخاب]     │ [انتخاب]     │[انتخاب]│
└─────────────┴──────────────┴──────────────┴─────────┘

۶.۲ مدال پرداخت — پکیج پایه (طلایی)

┌─────────────────────────────────────────┐
│        خرید پکیج طلایی — ۵۶M تومان       │
│                                         │
│  توضیحات: ...                            │
│                                         │
│  روش‌های پرداخت:                         │
│  ┌─────────────────────────────────┐    │
│  │  💎 خرید از طریق الماس دایا      │    │
│  └─────────────────────────────────┘    │
│  ┌─────────────────────────────────┐    │
│  │  💳 پرداخت مستقیم (درگاه بانکی) │    │
│  └─────────────────────────────────┘    │
└─────────────────────────────────────────┘

۶.۳ مدال پرداخت — پکیج‌های دیگر (نقره‌ای و بالاتر)

┌─────────────────────────────────────────┐
│      خرید پکیج نقره‌ای — ۵.۶M تومان      │
│                                         │
│  توضیحات: ...                            │
│  ویژگی‌ها: لرن، تریپ                     │
│                                         │
│  ┌─────────────────────────────────┐    │
│  │  💳 پرداخت و فعال‌سازی           │    │
│  └─────────────────────────────────┘    │
└─────────────────────────────────────────┘

۷. بخش‌های تحت تاثیر (Impact Analysis)

۷.۱ جدول تاثیرپذیری

# لایه فایل/بخش نوع تغییر شدت
۱ Domain Package.cs اضافه فیلد 🟡 متوسط
۲ Domain PackageFeature.csجدید Entity جدید 🔴 زیاد
۳ Domain ClubMembership.cs اضافه PackageId 🟡 متوسط
۴ Domain ClubMembershipCycle.cs اضافه PackageId 🟡 متوسط
۵ Domain WeeklyCommissionPool.cs اضافه PackageId 🔴 زیاد
۶ Domain SystemConstants.cs حذف hardcode‌ها → خوانش از Package 🟡 متوسط
۷ Application ActivateClubMembershipCommandHandler فیچر بر اساس پکیج 🔴 زیاد
۸ Application InitiateBasePackagePaymentCommandHandler حذف ID=4 hardcoded 🟡 متوسط
۹ Application VerifyBasePackagePaymentCommandHandler شارژ متناسب با پکیج 🔴 زیاد
۱۰ Application VerifyPackagePurchasePaymentCommandHandler شارژ متناسب با پکیج 🔴 زیاد
۱۱ Application ManualPaymentCommandHandler شارژ متناسب با پکیج 🟡 متوسط
۱۲ Application CustomerPurchasePackageCommandHandler پشتیبانی روش‌های پرداخت پکیج 🟡 متوسط
۱۳ Infra sp_CalculateWeeklyBalances پارامتر PackageId 🔴 زیاد
۱۴ Infra sp_CalculateWeeklyCommissionPool Pool جداگانه هر پکیج 🔴 زیاد
۱۵ Infra WeeklyCommissionCalculationService Loop روی پکیج‌ها 🟡 متوسط
۱۶ Infra EF Configurations جدول جدید + FK‌ها 🟡 متوسط
۱۷ Infra Database Migration schema changes 🟡 متوسط
۱۸ Proto package.proto فیلدهای جدید پکیج 🟢 کم
۱۹ Proto clubmembership.proto PackageId در response 🟢 کم
۲۰ Proto commission.proto PackageId در pool/payout 🟢 کم
۲۱ FrontOffice صفحه انتخاب پکیج UI جدید (کاشی‌ها) 🔴 زیاد
۲۲ FrontOffice مدال پرداخت دو مدال متفاوت 🔴 زیاد
۲۳ FrontOffice MyPackages.razor نمایش نوع پکیج 🟡 متوسط
۲۴ FrontOffice ActivateClubDialog.razor ارتباط با پکیج 🟡 متوسط
۲۵ BackOffice صفحه مدیریت پکیج‌ها CRUD فیلدهای جدید 🟡 متوسط
۲۶ BackOffice صفحه فیچر پکیج‌ها — جدید ماتریس پکیج×فیچر 🔴 زیاد
۲۷ BackOffice ActivateClubDialog.razor انتخاب پکیج 🟡 متوسط

۷.۲ ریسک‌ها

ریسک احتمال شدت راه‌حل
داده‌های فعلی — کاربران بدون PackageId قطعی زیاد Migration: کاربران فعلی → PackageId = پکیج پایه
Commission Pool فعلی بدون PackageId قطعی زیاد Migration: Pool‌های موجود → PackageId = پکیج پایه
SP تغییر → محاسبات اشتباه متوسط بحرانی تست جامع + محیط staging
مبالغ hardcoded در جاهای پراکنده زیاد متوسط Audit کامل کدبیس
عدم سازگاری FrontOffice/BackOffice متوسط متوسط تست end-to-end

۸. فازبندی پیاده‌سازی

فاز ۱ — زیرساخت (Domain + DB) ≈ ۳-۴ روز

تسک شرح
T1.1 بروزرسانی Package entity (فیلدهای جدید)
T1.2 ایجاد PackageFeature entity + EF Configuration
T1.3 اضافه کردن PackageId به ClubMembership
T1.4 اضافه کردن PackageId به ClubMembershipCycle
T1.5 اضافه کردن PackageId به WeeklyCommissionPool
T1.6 Database Migration + Seed data (پکیج پایه + فیچرها)
T1.7 Migration: کاربران/Pool‌های فعلی → PackageId = پکیج پایه
T1.8 بروزرسانی Proto‌ها

فاز ۲ — منطق کسب‌وکار (Application) ≈ ۴-۵ روز

تسک شرح
T2.1 بروزرسانی ActivateClubMembershipCommandHandler — فیچر بر اساس پکیج
T2.2 بروزرسانی Verify handlers — شارژ کیف پول متناسب با پکیج
T2.3 حذف مقادیر hardcoded از SystemConstants → خوانش از Package
T2.4 بروزرسانی InitiateBasePackagePayment → Generic InitiatePackagePayment
T2.5 بروزرسانی ManualPaymentCommandHandler — پشتیبانی پکیج متغیر
T2.6 CRUD پکیج با فیلدهای جدید (gRPC handlers)
T2.7 CRUD PackageFeature (ماتریس پکیج×فیچر)

فاز ۳ — محاسبه پورسانت ≈ ۳-۴ روز

تسک شرح
T3.1 بروزرسانی sp_CalculateWeeklyBalances — فیلتر بر اساس PackageId
T3.2 بروزرسانی sp_CalculateWeeklyCommissionPool — Pool جداگانه
T3.3 بروزرسانی WeeklyCommissionCalculationService — Loop روی پکیج‌ها
T3.4 تست محاسبات با داده واقعی

فاز ۴ — UI (FrontOffice + BackOffice) ≈ ۴-۵ روز

تسک شرح
T4.1 صفحه کاشی‌های پکیج (FrontOffice)
T4.2 مدال پرداخت پکیج پایه (دایا + مستقیم)
T4.3 مدال پرداخت پکیج‌های دیگر (فقط مستقیم)
T4.4 بروزرسانی MyPackages.razor — نمایش نوع پکیج
T4.5 بروزرسانی ActivateClubDialog.razor — ارتباط با پکیج
T4.6 BackOffice: CRUD پکیج با فیلدهای جدید
T4.7 BackOffice: صفحه ماتریس فیچرهای پکیج
T4.8 BackOffice: ActivateClubDialog — انتخاب پکیج

فاز ۵ — تست و استقرار ≈ ۲-۳ روز

تسک شرح
T5.1 تست end-to-end فلوی خرید هر پکیج
T5.2 تست محاسبه پورسانت جداگانه
T5.3 تست migration داده‌های فعلی
T5.4 Deploy به staging + تست
T5.5 Deploy به production

۹. Seed Data — پکیج‌های اولیه

-- Migration: Seed packages
INSERT INTO Packages (Title, Description, Price, IsActive, IsBasePackage, 
    SupportsDayaPurchase, SupportsDirectPurchase, ActivationFee, GiftValue, 
    DiscountMultiplier, SortOrder)
VALUES
    ('نقره‌ای', 'پکیج نقره‌ای باشگاه مشتریان', 5600000, 1, 0, 
     0, 1, ???, ???, 2.0, 1),
    ('طلایی', 'پکیج طلایی باشگاه مشتریان (پایه)', 56000000, 1, 1, 
     1, 1, 25200000, 25200000, 2.0, 2);

-- Migration: ربط فیچرها به پکیج‌ها
INSERT INTO PackageFeatures (PackageId, ClubFeatureId, IsIncluded) VALUES
    -- نقره‌ای: فقط تریپ و لرن
    (@silverId, @tripId, 1),
    (@silverId, @learnId, 1),
    -- طلایی: همه فیچرها
    (@goldId, @chatikaId, 1),
    (@goldId, @bimeId, 1),
    (@goldId, @tripId, 1),
    (@goldId, @learnId, 1);

-- Migration: کاربران فعلی → پکیج پایه
UPDATE ClubMemberships SET PackageId = @goldId WHERE PackageId IS NULL;
UPDATE ClubMembershipCycles SET PackageId = @goldId WHERE PackageId IS NULL;
UPDATE WeeklyCommissionPools SET PackageId = @goldId WHERE PackageId IS NULL;

۱۰. سوالات باز (نیاز به تصمیم‌گیری)

# سوال گزینه‌ها
۱ ActivationFee و GiftValue پکیج نقره‌ای چقدر باشد؟ نسبت به قیمت؟ مقدار ثابت؟
۲ آیا کاربر می‌تواند بعداً پکیج خود را ارتقا دهد (upgrade)؟ بله → فقط مابه‌التفاوت / خیر
۳ DiscountMultiplier برای همه پکیج‌ها ×۲ باشد؟ یکسان / متفاوت به ازای هر پکیج
۴ ضریب MagicWallet (×۲.۵) برای پکیج‌های کوچکتر هم همان باشد؟ بله / خیر
۵ فیچرهای پکیج نقره‌ای دقیقاً کدام‌ها هستند؟ لرن+تریپ؟ فقط لرن؟
۶ آیا یک کاربر می‌تواند چند پکیج همزمان داشته باشد؟ فقط یکی / امکان خرید چندتا
۷ نام و تعداد دقیق پکیج‌ها چیست؟ نقره‌ای+طلایی؟ بیشتر؟
۸ کاربرانی که با دایا فعال شدن، چه پکیجی دارند؟ طلایی (پایه)

۱۱. تخمین زمانی

فاز مدت وابستگی
فاز ۱ — زیرساخت ۳-۴ روز
فاز ۲ — منطق ۴-۵ روز فاز ۱
فاز ۳ — پورسانت ۳-۴ روز فاز ۱
فاز ۴ — UI ۴-۵ روز فاز ۲
فاز ۵ — تست ۲-۳ روز فاز ۳, ۴
مجموع ~۱۶-۲۱ روز کاری

فازهای ۲ و ۳ قابل موازی‌سازی هستند.