diff --git a/business/BIZ-PACKAGE-BASED-SYSTEM-v1-draft.md b/business/BIZ-PACKAGE-BASED-SYSTEM-v1-draft.md new file mode 100644 index 0000000..549dd4a --- /dev/null +++ b/business/BIZ-PACKAGE-BASED-SYSTEM-v1-draft.md @@ -0,0 +1,554 @@ +# 📦 سیستم مبتنی بر پکیج (Package-Based System) + +> **وضعیت:** تحلیل و بررسی — منتظر تایید +> **تاریخ:** اسفند ۱۴۰۴ +> **تاثیرگذاری:** زیاد — بخش‌های متعدد سیستم تحت تاثیر قرار می‌گیرد + +--- + +## ۱. خلاصه فیچر + +**وضعیت فعلی:** سیستم فقط یک پکیج پایه (۵۶ میلیون تومان) دارد و همه چیز حول آن می‌چرخد. + +**وضعیت هدف:** سیستم چندین پکیج با قیمت‌ها و ویژگی‌های متفاوت پشتیبانی می‌کند. هر پکیج روش‌های پرداخت، محاسبه پورسانت، شارژ کیف پول و فیچرهای مختص خود را دارد. + +``` +مثال پکیج‌ها: +┌──────────────┬──────────────┬──────────────┬──────────────┐ +│ 🥈 نقره‌ای │ 🥇 طلایی │ 💎 الماسی │ ⭐ ویژه │ +│ ۵.۶M تومان │ ۵۶M تومان │ ؟؟ تومان │ ؟؟ تومان │ +│ │ (پکیج پایه) │ │ │ +│ فقط مستقیم │ دایا+مستقیم │ فقط مستقیم │ فقط مستقیم │ +│ فیچر محدود │ همه فیچرها │ همه فیچرها │ همه+اختصاصی │ +└──────────────┴──────────────┴──────────────┴──────────────┘ +``` + +--- + +## ۲. وضعیت فعلی سیستم (AS-IS) + +### ۲.۱ فلوی فعلی فعالسازی + +```mermaid +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) + +### ۳.۱ فلوی جدید فعالسازی + +```mermaid +flowchart TD + A["کاربر وارد سیستم"] --> B["صفحه پکیج‌ها
(کاشی‌های نقره‌ای/طلایی/الماسی/...)"] + + B -->|"کلیک روی پکیج"| C{"نوع پکیج"} + + C -->|"پکیج پایه (طلایی)"| D["مدال با دو گزینه:
۱. خرید الماس دایا
۲. پرداخت مستقیم"] + C -->|"پکیج‌های دیگر"| E["مدال با یک گزینه:
فقط پرداخت مستقیم
+ توضیحات + قیمت"] + + D -->|"دایا"| F["فلوی دایا"] + D -->|"مستقیم"| G["درگاه پرداخت"] + E --> G + + F --> H["پرداخت موفق"] + G --> H + + H --> I["شارژ کیف پول
(متناسب با قیمت پکیج)"] + I --> J["مدال قرارداد باشگاه"] + J --> K["OTP + امضا"] + K --> L["فعال‌سازی
+ اختصاص فیچرهای پکیج"] + L --> M["✅ کاربر فعال"] +``` + +### ۳.۲ تغییرات Entity — Package + +**فعلی:** +```csharp +public class Package : BaseAuditableEntity +{ + public string Title { get; set; } + public string Description { get; set; } + public string ImagePath { get; set; } + public long Price { get; set; } +} +``` + +**پیشنهادی:** +```csharp +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 PackageFeatures { get; set; } + public virtual ICollection Purchases { get; set; } +} +``` + +### ۳.۳ Entity جدید — PackageFeature (پل بین پکیج و فیچر) + +```csharp +/// +/// مشخص می‌کند هر پکیج چه فیچرهایی را فعال می‌کند +/// +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 + +```csharp +public class ClubMembership : BaseAuditableEntity +{ + // ... فیلدهای فعلی حفظ می‌شوند ... + + // === فیلد جدید === + public long PackageId { get; set; } // کدام پکیج خریداری شده + public virtual Package Package { get; set; } +} +``` + +### ۳.۵ تغییرات Entity — ClubMembershipCycle + +```csharp +public class ClubMembershipCycle : BaseAuditableEntity +{ + // ... فیلدهای فعلی حفظ می‌شوند ... + + // === فیلد جدید === + public long PackageId { get; set; } // پکیج این سایکل + public virtual Package Package { get; set; } + // PackageAmount قبلاً وجود دارد — از Package.Price پر می‌شود +} +``` + +### ۳.۶ تغییرات Entity — WeeklyCommissionPool + +```csharp +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 می‌شوند: +```csharp +// 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 — پکیج‌های اولیه + +```sql +-- 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 | ۴-۵ روز | فاز ۲ | +| فاز ۵ — تست | ۲-۳ روز | فاز ۳, ۴ | +| **مجموع** | **~۱۶-۲۱ روز کاری** | | + +> فازهای ۲ و ۳ قابل موازی‌سازی هستند. diff --git a/business/BIZ-PACKAGE-BASED-SYSTEM.md b/business/BIZ-PACKAGE-BASED-SYSTEM.md index 549dd4a..e199faa 100644 --- a/business/BIZ-PACKAGE-BASED-SYSTEM.md +++ b/business/BIZ-PACKAGE-BASED-SYSTEM.md @@ -1,131 +1,120 @@ # 📦 سیستم مبتنی بر پکیج (Package-Based System) -> **وضعیت:** تحلیل و بررسی — منتظر تایید -> **تاریخ:** اسفند ۱۴۰۴ -> **تاثیرگذاری:** زیاد — بخش‌های متعدد سیستم تحت تاثیر قرار می‌گیرد +> **وضعیت:** تایید‌شده — آماده پیاده‌سازی +> **تاریخ بروزرسانی:** ۶ اسفند ۱۴۰۴ +> **نسخه:** v2 (بازنویسی کامل بعد از تحلیل عمیق کدبیس) +> **تاثیرگذاری:** زیاد — ۳۹ فایل در ۶ لایه --- ## ۱. خلاصه فیچر -**وضعیت فعلی:** سیستم فقط یک پکیج پایه (۵۶ میلیون تومان) دارد و همه چیز حول آن می‌چرخد. +**وضعیت فعلی:** سیستم فقط یک پکیج پایه (۵۶ میلیون تومان) دارد و همه چیز حول آن hardcode شده. -**وضعیت هدف:** سیستم چندین پکیج با قیمت‌ها و ویژگی‌های متفاوت پشتیبانی می‌کند. هر پکیج روش‌های پرداخت، محاسبه پورسانت، شارژ کیف پول و فیچرهای مختص خود را دارد. - -``` -مثال پکیج‌ها: -┌──────────────┬──────────────┬──────────────┬──────────────┐ -│ 🥈 نقره‌ای │ 🥇 طلایی │ 💎 الماسی │ ⭐ ویژه │ -│ ۵.۶M تومان │ ۵۶M تومان │ ؟؟ تومان │ ؟؟ تومان │ -│ │ (پکیج پایه) │ │ │ -│ فقط مستقیم │ دایا+مستقیم │ فقط مستقیم │ فقط مستقیم │ -│ فیچر محدود │ همه فیچرها │ همه فیچرها │ همه+اختصاصی │ -└──────────────┴──────────────┴──────────────┴──────────────┘ -``` +**وضعیت هدف:** سیستم چندین پکیج با قیمت‌ها و ویژگی‌های متفاوت پشتیبانی می‌کند. هر پکیج مقادیر مالی، فیچرها و Commission Pool مستقل خود را دارد. کاربر می‌تواند **N بار** پکیج بخرد (بعد از تکمیل چرخه Magic Wallet). --- -## ۲. وضعیت فعلی سیستم (AS-IS) +## ۲. تصمیمات تایید‌شده -### ۲.۱ فلوی فعلی فعالسازی - -```mermaid -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 | +| # | سوال | تصمیم | +|---|------|-------| +| Q1 | باگ DiscountBalance در VerifyGoldenPackagePurchase | ✅ **باگه — باید فیکس بشه** | +| Q2 | سه مسیر پرداخت موازی | ✅ **ادغام به سرویس Generic** — نه به نام پکیج خاص | +| Q3 | پکیج‌های اولیه | ✅ **نقره‌ای (۵.۶M) + پایه (۵۶M)** — سیستم داینامیک | +| Q4 | ActivationFee و GiftValue | ✅ **یک فیلد (ActivationFee)** — GiftValue حذف (تکراری بود) | +| Q5 | DiscountMultiplier | ✅ **×2 برای همه** — ولی داینامیک در entity | +| Q6 | Migration کاربران فعلی | ✅ **Pipeline/Script** — کاربران فعلی → PackageId = پکیج پایه | +| Q7 | خرید چند پکیج | ✅ **N بار** — بعد تکمیل Magic Wallet و صفر شدن Balance | +| Q8 | Commission Pool | ✅ **جدا برای هر پکیج** — تمام جداول Commission با PackageId | +| Q9 | MagicWallet Multiplier | ✅ **داینامیک** به‌ازای هر پکیج (فعلاً همه ×2.5) | +| Q10 | کاربران دایا | ✅ **پکیج پایه** گرفتن — "طلایی" اشتباه نام‌گذاری بوده | +| Q11 | فیچرها | ✅ **داینامیک** — ادمین مدیریت می‌کند | --- -## ۳. طراحی پیشنهادی (TO-BE) +## ۳. تحلیل عمیق وضعیت فعلی (AS-IS) -### ۳.۱ فلوی جدید فعالسازی +### ۳.۱ باگ‌های کشف‌شده -```mermaid -flowchart TD - A["کاربر وارد سیستم"] --> B["صفحه پکیج‌ها
(کاشی‌های نقره‌ای/طلایی/الماسی/...)"] +| # | باگ | شدت | فایل | +|---|-----|------|------| +| **B1** | `VerifyGoldenPackagePurchase` → **DiscountBalance شارژ نمی‌شود** | 🔴 بحرانی | VerifyGoldenPackagePurchaseCommandHandler.cs | +| **B2** | `VerifyGoldenPackagePurchase` → **UserPackagePurchase ساخته نمی‌شود** | 🔴 بحرانی | VerifyGoldenPackagePurchaseCommandHandler.cs | +| **B3** | `VerifyPackagePurchase` → **UserPackagePurchase ساخته نمی‌شود** | 🔴 بحرانی | VerifyPackagePurchaseCommandHandler.cs | +| **B4** | `VerifyBasePackagePayment` → **UserPackagePurchase ساخته نمی‌شود** | 🔴 بحرانی | VerifyBasePackagePaymentCommandHandler.cs | +| **B5** | `PackageService.CustomerPurchasePackage` → **guard برای خرید تکراری ندارد** | 🟡 متوسط | PackageService.cs | +| **B6** | EXIT Magic Mode → **PackagePurchaseMethod ریست نمی‌شود** (خرید مجدد مسدود) | 🔴 بحرانی | UserOrderService.cs | + +### ۳.۲ ناسازگاری مسیرهای پرداخت + +| مسیر | Balance | Discount | UserPackagePurchase | WalletChangeLog | +|------|---------|----------|---------------------|-----------------| +| **BFF/PYMS** (InitiateBase→VerifyBase) | ✅ | ✅ | ❌ | ✅ | +| **ZarinPal Golden** (PurchaseGolden→VerifyGolden) | ✅ | ❌ | ❌ | ✅ (فقط Balance) | +| **ZarinPal Generic** (Purchase→VerifyPurchase) | ✅ | ✅ | ❌ | ✅ | +| **Daya Loan** (CheckAndProcess) | ✅ | ✅ | ✅ | ✅ | +| **Manual** (CreateManualPayment) | ✅ | ✅ | ❌ | ✅ | + +> **فقط Daya Loan** همه مراحل را کامل انجام می‌دهد. بقیه مسیرها ناقص هستند. + +### ۳.۳ مقادیر Hardcoded (۱۵ مورد) + +| # | مکان | مقدار | باید بشه | +|---|------|-------|----------| +| H1 | `InitiateBasePackagePaymentCommandHandler` | `BasePackageId = 4` | خوانش از پکیج فعال | +| H2 | `CheckAndProcessDayaLoansCommandHandler` | `p.Id == 4` | خوانش از پکیج پایه | +| H3 | `SystemConstants.BasePackageAmount` | `56_000_000` | `Package.Price` | +| H4 | `SystemConstants.DayaLoanAmount` | `56_000_000` | `Package.Price` | +| H5 | `SystemConstants.ClubActivationFee` | `25_200_000` | `Package.ActivationFee` | +| H6 | `SystemConstants.ClubMembershipGiftValue` | `25_200_000` | حذف (= ActivationFee) | +| H7 | `VerifyPackagePurchaseCommandHandler` | `Amount * 2` (×3 جا) | `Package.DiscountMultiplier` | +| H8 | `VerifyBasePackagePaymentCommandHandler` | `BasePackageAmount * 2` (×2) | `Package.DiscountMultiplier` | +| H9 | `CreateManualPaymentCommandHandler` | `BasePackageAmount * 2` | `Package.DiscountMultiplier` | +| H10 | `CheckAndProcessDayaLoansCommandHandler` | `DayaLoanAmount * 2` | `Package.DiscountMultiplier` | +| H11 | `ActivateClubMembershipCommandHandler` | `PackageAmount = BasePackageAmount` | `Package.Price` | +| H12 | `PurchaseGoldenPackageCommandHandler` | `Title.Contains("طلایی")` | حذف — Generic | +| H13 | `ActivateClubMembershipCommandHandler` | `GetAllFeatureIds()` | `Package.PackageFeatures` | +| H14 | `ActivationSection.razor` | `56_000_000 × months` | از پکیج خوانده شود | +| H15 | `ClubMembershipContractDialog.razor` | متن قرارداد ۵۶M | داینامیک از پکیج | + +### ۳.۴ Guardهای مسدودکننده خرید مجدد + +| # | فایل | Guard | وضعیت | تغییر | +|---|------|-------|-------|-------| +| G1 | `PurchaseGoldenPackageCommandHandler` | `PackagePurchaseMethod != None` → throw | مسدود | ✅ اجازه بعد تکمیل چرخه | +| G2 | `InitiateBasePackagePaymentCommandHandler` | `PackagePurchaseMethod != None` → fail | مسدود | ✅ اجازه بعد تکمیل چرخه | +| G3 | `PurchasePackageCommandHandler` | `PackagePurchaseMethod != None` → throw | مسدود | ✅ اجازه بعد تکمیل چرخه | +| G4 | `CheckAndProcessDayaLoansCommandHandler` | `hasPreviousCycle` → skip | عمدی ✅ | ❌ حفظ (دایا فقط دور اول) | +| G5 | `AcceptClubMembershipContractCommandHandler` | `IsActive == true` → fail | مسدود | ✅ اجازه re-contract | +| G6 | `ActivateClubMembershipCommandHandler` | `IsActive == true` → return true | short-circuit | ✅ باید چرخه جدید بسازه | +| G7 | JWT Claim `HasPurchasedPackage` | permanent true | UI مسدود | ✅ اضافه `CanRepurchase` | + +### ۳.۵ Root Cause — خرید مجدد کار نمی‌کند + +``` +EXIT Magic Mode (UserOrderService.cs): + ✅ wallet.WalletMode = Normal + ✅ wallet.MagicCompletedAt = now + ✅ cycle.MagicCompletedAt = now - B -->|"کلیک روی پکیج"| C{"نوع پکیج"} - - C -->|"پکیج پایه (طلایی)"| D["مدال با دو گزینه:
۱. خرید الماس دایا
۲. پرداخت مستقیم"] - C -->|"پکیج‌های دیگر"| E["مدال با یک گزینه:
فقط پرداخت مستقیم
+ توضیحات + قیمت"] - - D -->|"دایا"| F["فلوی دایا"] - D -->|"مستقیم"| G["درگاه پرداخت"] - E --> G - - F --> H["پرداخت موفق"] - G --> H - - H --> I["شارژ کیف پول
(متناسب با قیمت پکیج)"] - I --> J["مدال قرارداد باشگاه"] - J --> K["OTP + امضا"] - K --> L["فعال‌سازی
+ اختصاص فیچرهای پکیج"] - L --> M["✅ کاربر فعال"] + ❌ MISSING: user.PackagePurchaseMethod = None ← Guards G1-G3 مسدود می‌مانند + ❌ MISSING: membership.IsActive = false ← Guards G5-G6 مسدود می‌مانند + ❌ MISSING: JWT CanRepurchase = true ← UI دکمه خرید نشان نمی‌دهد ``` -### ۳.۲ تغییرات Entity — Package +**راه‌حل:** در EXIT Magic Mode، وضعیت کاربر ریست شود تا بتواند پکیج جدید بخرد. + +--- + +## ۴. طراحی نهایی (TO-BE) + +### ۴.۱ تغییرات Entity — Package -**فعلی:** -```csharp -public class Package : BaseAuditableEntity -{ - public string Title { get; set; } - public string Description { get; set; } - public string ImagePath { get; set; } - public long Price { get; set; } -} -``` - -**پیشنهادی:** ```csharp public class Package : BaseAuditableEntity { + // === فیلدهای فعلی (حفظ) === public string Title { get; set; } public string Description { get; set; } public string ImagePath { get; set; } @@ -134,27 +123,30 @@ public class Package : BaseAuditableEntity // === فیلدهای جدید === public int SortOrder { get; set; } // ترتیب نمایش public bool IsActive { get; set; } = true; // فعال/غیرفعال - public bool IsBasePackage { get; set; } // آیا پکیج پایه است؟ + public bool IsBasePackage { get; set; } // پکیج پایه؟ (فقط یکی true) 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 + public decimal MagicWalletMultiplier { get; set; } = 2.5m; // ضریب کیف‌پول جادویی // === Navigation === public virtual ICollection PackageFeatures { get; set; } public virtual ICollection Purchases { get; set; } + public virtual ICollection UserOrders { get; set; } } ``` -### ۳.۳ Entity جدید — PackageFeature (پل بین پکیج و فیچر) +**نسبت به v1:** +- ❌ `GiftValue` حذف (= ActivationFee — تکراری) +- ✅ `MagicWalletMultiplier` اضافه (داینامیک) +- ❌ `PackageType` enum نیاز نیست (`IsBasePackage` کافیست) + +### ۴.۲ Entity جدید — PackageFeature ```csharp -/// -/// مشخص می‌کند هر پکیج چه فیچرهایی را فعال می‌کند -/// public class PackageFeature : BaseAuditableEntity { public long PackageId { get; set; } @@ -163,392 +155,429 @@ public class PackageFeature : BaseAuditableEntity public long ClubFeatureId { get; set; } public virtual ClubFeature ClubFeature { get; set; } - public bool IsIncluded { get; set; } = true; // آیا این فیچر در پکیج هست؟ + public bool IsIncluded { get; set; } = true; } ``` -### ۳.۴ تغییرات Entity — ClubMembership +### ۴.۳ تغییرات Entity — سایر -```csharp -public class ClubMembership : BaseAuditableEntity -{ - // ... فیلدهای فعلی حفظ می‌شوند ... +| Entity | فیلد جدید | توضیح | +|--------|-----------|-------| +| `ClubMembership` | `long? PackageId` + FK | آخرین پکیج خریداری‌شده | +| `ClubMembershipCycle` | `long PackageId` + FK | پکیج این چرخه | +| `WeeklyCommissionPool` | `long PackageId` + FK | Pool جداگانه هر پکیج | +| `UserCommissionPayout` | `long PackageId` + FK | از کدام Pool | + +**Constraint جدید:** `WeeklyCommissionPool` → Unique(`WeekDefinitionId`, `PackageId`) + +### ۴.۴ حذف/تغییر SystemConstants + +| ثابت | تغییر | جایگزین | +|------|-------|---------| +| `ClubMembershipGiftValue` | ❌ حذف | تکراری بود | +| `ClubActivationFee` | ❌ حذف | `Package.ActivationFee` | +| `BasePackageAmount` | ❌ حذف | `Package.Price` | +| `DayaLoanAmount` | ❌ حذف | `Package.Price` (base) | +| `MagicWalletMultiplier` | ❌ حذف | `Package.MagicWalletMultiplier` | +| `CommissionMaxWeeklyBalancesPerLeg` | ✅ حفظ | عمومی | +| `CommissionMaxNetworkLevel` | ✅ حفظ | عمومی | +| `ShopVAT` | ✅ حفظ | عمومی | + +### ۴.۵ فرمول مالی + +``` +ActivationFee = Price × 0.45 + +پکیج نقره‌ای (۵,۶۰۰,۰۰۰ ریال): + ├── Balance += ۵,۶۰۰,۰۰۰ (Price) + ├── DiscountBalance += ۱۱,۲۰۰,۰۰۰ (Price × DiscountMultiplier) + └── CommissionPool += ۲,۵۲۰,۰۰۰ (ActivationFee) + +پکیج پایه (۵۶,۰۰۰,۰۰۰ ریال): + ├── Balance += ۵۶,۰۰۰,۰۰۰ (Price) + ├── DiscountBalance += ۱۱۲,۰۰۰,۰۰۰ (Price × DiscountMultiplier) + └── CommissionPool += ۲۵,۲۰۰,۰۰۰ (ActivationFee) +``` + +--- + +## ۵. فلوی خرید مجدد (Re-Purchase) + +### ۵.۱ چرخه حیات کامل + +```mermaid +stateDiagram-v2 + [*] --> NoPurchase: کاربر ثبت‌نام کرده - // === فیلد جدید === - public long PackageId { get; set; } // کدام پکیج خریداری شده - public virtual Package Package { get; set; } -} -``` - -### ۳.۵ تغییرات Entity — ClubMembershipCycle - -```csharp -public class ClubMembershipCycle : BaseAuditableEntity -{ - // ... فیلدهای فعلی حفظ می‌شوند ... + NoPurchase --> PackagePurchased: خرید پکیج\n(هر پکیجی) - // === فیلد جدید === - public long PackageId { get; set; } // پکیج این سایکل - public virtual Package Package { get; set; } - // PackageAmount قبلاً وجود دارد — از Package.Price پر می‌شود -} -``` - -### ۳.۶ تغییرات Entity — WeeklyCommissionPool - -```csharp -public class WeeklyCommissionPool : BaseAuditableEntity -{ - // ... فیلدهای فعلی حفظ می‌شوند ... + PackagePurchased --> ClubActivated: فعالسازی باشگاه\n(OTP + قرارداد) - // === فیلد جدید === - public long PackageId { get; set; } // Pool جداگانه برای هر پکیج - public virtual Package Package { get; set; } -} + ClubActivated --> Shopping: خرج Balance\nدر فروشگاه + + Shopping --> MagicMode: Balance == 0 + + MagicMode --> MagicCharging: شارژ + خرج\n(تا سقف 1B) + + MagicCharging --> MagicMode: ادامه + + MagicMode --> CycleComplete: Balance == 0\nAND Deposit ≥ 1B + + CycleComplete --> NoPurchase: ریست وضعیت\nآماده خرید مجدد ``` -### ۳.۷ جریان پول جدید +### ۵.۲ ریست وضعیت بعد تکمیل چرخه (EXIT Magic Mode) -``` -پکیج نقره‌ای (۵.۶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 می‌شوند: ```csharp -// ActivateClubMembershipCommandHandler — خط ~350 -var allFeatureIds = ClubFeatureTypeExtensions.GetAllFeatureIds(); -foreach (var featureId in allFeatureIds) +// UserOrderService.cs — EXIT Magic Mode — تغییرات لازم: +wallet.WalletMode = WalletMode.Normal; +wallet.MagicCompletedAt = DateTime.UtcNow; +cycle.MagicCompletedAt = DateTime.UtcNow; + +// ✅ اضافه شود: +user.PackagePurchaseMethod = PackagePurchaseMethod.None; // اجازه خرید مجدد +membership.IsActive = false; // اجازه re-contract +cycle.IsCurrentCycle = false; // آماده چرخه جدید +``` + +### ۵.۳ نکات مهم + +1. **دایا فقط دور اول** — بعد از دور اول، فقط IPG مجاز +2. **هر خرید = Commission contribution** — ActivationFee به Pool آن پکیج +3. **PackagePurchaseMethod ریست** بعد تکمیل چرخه +4. **فیچرها بر اساس پکیج جدید** — ممکنه متفاوت باشه + +--- + +## ۶. Commission Pool — تغییرات + +### ۶.۱ ساختار جدید + +``` +هفته ۱: + Pool_نقره‌ای (PackageId=X): TotalAmount = Σ ActivationFee نقره‌ای + Pool_پایه (PackageId=Y): TotalAmount = Σ ActivationFee پایه + +هر Pool مستقل: + ValuePerBalance = TotalPoolAmount ÷ TotalBalances + (فقط کاربران همان پکیج) +``` + +### ۶.۲ ساختار شبکه یکی‌ست + +``` + [Ali - پایه] + / \ + [Sara - نقره‌ای] [Reza - پایه] + +Commission: + Ali → پاداش از Pool_پایه + Sara → پاداش از Pool_نقره‌ای + Reza → پاداش از Pool_پایه +``` + +### ۶.۳ تغییرات SP + +| SP | تغییر | +|----|-------| +| `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` — فیلتر کاربران بر اساس PackageId | +| `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` — Pool مخصوص آن پکیج | + +### ۶.۴ تغییرات Service + +```csharp +// WeeklyCommissionCalculationService — Loop روی پکیج‌ها: +var activePackages = await _context.Packages + .Where(p => p.IsActive && !p.IsDeleted) + .ToListAsync(); + +foreach (var package in activePackages) { - userClubFeatures.Add(new UserClubFeature { ... }); + await strategy.CalculateWeeklyBalancesAsync(weekId, package.Id); + await strategy.CalculateWeeklyPoolAsync(weekId, package.Id); } ``` -### ۵.۲ وضعیت هدف +--- -فیچرها بر اساس جدول `PackageFeature` تعیین می‌شوند: +## ۷. Event-Driven Side Effects -| فیچر | نقره‌ای | طلایی (پایه) | الماسی | -|------|---------|-------------|--------| -| چتیکا | ❌ | ✅ | ✅ | -| بیمه | ❌ | ✅ | ✅ | -| تریپ | ✅ | ✅ | ✅ | -| لرن | ✅ | ✅ | ✅ | -| فیچر VIP | ❌ | ❌ | ✅ | - -*مقادیر بالا نمونه‌ای هستند — قابل تنظیم از BackOffice* - -### ۵.۳ تغییر در ActivateClubMembershipHandler +### ۷.۱ ساخت پکیج جدید +```mermaid +flowchart LR + A["ساخت پکیج جدید
(از BackOffice)"] --> B["PackageCreatedEvent"] + B --> C["ایجاد Pool خالی
برای هفته جاری"] + B --> D["لاگ ادمین"] ``` -قبلی: - GetAllFeatureIds() → assign all -جدید: - Package.PackageFeatures - .Where(pf => pf.IsIncluded) - .Select(pf => pf.ClubFeatureId) - → assign only included features +### ۷.۲ جدول رویدادها + +| رویداد | Side Effect | +|--------|------------| +| `PackageCreated` | ساخت WeeklyCommissionPool خالی هفته جاری | +| `PackageDeactivated` | هشدار ادمین — Pool موجود تکمیل شود | +| `PackagePurchased` | ActivationFee → Pool پکیج + شارژ wallets | +| `MagicCycleCompleted` | ریست PackagePurchaseMethod + Deactivate membership | + +--- + +## ۸. Seed Data + +```sql +-- پکیج پایه (۵۶ میلیون تومان) +INSERT INTO "CMS"."Packages" ( + "Title", "Description", "Price", "IsActive", "IsBasePackage", + "SupportsDayaPurchase", "SupportsDirectPurchase", + "ActivationFee", "DiscountMultiplier", "MagicWalletMultiplier", + "SortOrder", "ImagePath" +) VALUES ( + 'پکیج پایه', 'پکیج اصلی باشگاه مشتریان کارا بازار سلامت', + 56000000, true, true, + true, true, + 25200000, 2.0, 2.5, + 2, '' +); + +-- پکیج نقره‌ای (۵.۶ میلیون تومان) +INSERT INTO "CMS"."Packages" ( + "Title", "Description", "Price", "IsActive", "IsBasePackage", + "SupportsDayaPurchase", "SupportsDirectPurchase", + "ActivationFee", "DiscountMultiplier", "MagicWalletMultiplier", + "SortOrder", "ImagePath" +) VALUES ( + 'پکیج نقره‌ای', 'پکیج سطح نقره‌ای باشگاه مشتریان', + 5600000, true, false, + false, true, + 2520000, 2.0, 2.5, + 1, '' +); + +-- فیچرهای پکیج پایه: همه فیچرها +INSERT INTO "CMS"."PackageFeatures" ("PackageId", "ClubFeatureId", "IsIncluded") +SELECT base."Id", cf."Id", true +FROM "CMS"."Packages" base +CROSS JOIN "CMS"."ClubFeatures" cf +WHERE base."IsBasePackage" = true AND cf."IsDeleted" = false; + +-- فیچرهای پکیج نقره‌ای: تعیین می‌شود از BackOffice ``` --- -## ۶. تغییرات UI — FrontOffice +## ۹. Migration داده‌های فعلی -### ۶.۱ صفحه پکیج‌ها (کاشی‌ها) +```sql +-- ======================================== +-- STEP 1: مشخص کردن ID پکیج پایه +-- ======================================== +DO $$ +DECLARE base_pkg_id BIGINT; +BEGIN + SELECT "Id" INTO base_pkg_id + FROM "CMS"."Packages" WHERE "IsBasePackage" = true LIMIT 1; + + -- STEP 2: ClubMembership + UPDATE "CMS"."ClubMemberships" + SET "PackageId" = base_pkg_id WHERE "PackageId" IS NULL; + + -- STEP 3: ClubMembershipCycle + UPDATE "CMS"."ClubMembershipCycles" + SET "PackageId" = base_pkg_id WHERE "PackageId" IS NULL; + + -- STEP 4: WeeklyCommissionPool + UPDATE "CMS"."WeeklyCommissionPools" + SET "PackageId" = base_pkg_id WHERE "PackageId" IS NULL; + + -- STEP 5: UserCommissionPayout + UPDATE "CMS"."UserCommissionPayouts" + SET "PackageId" = base_pkg_id WHERE "PackageId" IS NULL; + + RAISE NOTICE 'Migration completed for PackageId=%', base_pkg_id; +END $$; -``` -┌─────────────────────────────────────────────────────┐ -│ انتخاب پکیج باشگاه مشتریان │ -├─────────────┬──────────────┬──────────────┬─────────┤ -│ │ │ │ │ -│ 🥈 نقره‌ای │ 🥇 طلایی │ 💎 الماسی │ ⭐ ویژه │ -│ ۵.۶M │ ۵۶M │ ؟؟M │ ؟؟M │ -│ │ │ │ │ -│ ● لرن │ ● چتیکا │ ● همه │ ● همه │ -│ ● تریپ │ ● بیمه │ ● + VIP │ ● +... │ -│ │ ● تریپ │ │ │ -│ │ ● لرن │ │ │ -│ │ │ │ │ -│ [انتخاب] │ [انتخاب] │ [انتخاب] │[انتخاب]│ -└─────────────┴──────────────┴──────────────┴─────────┘ -``` - -### ۶.۲ مدال پرداخت — پکیج پایه (طلایی) - -``` -┌─────────────────────────────────────────┐ -│ خرید پکیج طلایی — ۵۶M تومان │ -│ │ -│ توضیحات: ... │ -│ │ -│ روش‌های پرداخت: │ -│ ┌─────────────────────────────────┐ │ -│ │ 💎 خرید از طریق الماس دایا │ │ -│ └─────────────────────────────────┘ │ -│ ┌─────────────────────────────────┐ │ -│ │ 💳 پرداخت مستقیم (درگاه بانکی) │ │ -│ └─────────────────────────────────┘ │ -└─────────────────────────────────────────┘ -``` - -### ۶.۳ مدال پرداخت — پکیج‌های دیگر (نقره‌ای و بالاتر) - -``` -┌─────────────────────────────────────────┐ -│ خرید پکیج نقره‌ای — ۵.۶M تومان │ -│ │ -│ توضیحات: ... │ -│ ویژگی‌ها: لرن، تریپ │ -│ │ -│ ┌─────────────────────────────────┐ │ -│ │ 💳 پرداخت و فعال‌سازی │ │ -│ └─────────────────────────────────┘ │ -└─────────────────────────────────────────┘ +-- STEP 6: Verify — همه باید 0 باشند +SELECT 'ClubMemberships' AS tbl, COUNT(*) FROM "CMS"."ClubMemberships" WHERE "PackageId" IS NULL +UNION ALL +SELECT 'Cycles', COUNT(*) FROM "CMS"."ClubMembershipCycles" WHERE "PackageId" IS NULL +UNION ALL +SELECT 'Pools', COUNT(*) FROM "CMS"."WeeklyCommissionPools" WHERE "PackageId" IS NULL +UNION ALL +SELECT 'Payouts', COUNT(*) FROM "CMS"."UserCommissionPayouts" WHERE "PackageId" IS NULL; ``` --- -## ۷. بخش‌های تحت تاثیر (Impact Analysis) +## ۱۰. Impact Analysis — ۳۹ تغییر در ۶ لایه -### ۷.۱ جدول تاثیرپذیری +### ۱۰.۱ لایه Domain (۸ تغییر) -| # | لایه | فایل/بخش | نوع تغییر | شدت | -|---|------|----------|-----------|-----| -| ۱ | **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` | انتخاب پکیج | 🟡 متوسط | +| # | فایل | نوع | شدت | +|---|------|-----|------| +| D1 | `Package.cs` | اضافه ۷ فیلد جدید | 🟡 | +| D2 | `PackageFeature.cs` | Entity جدید + EF Config | 🔴 | +| D3 | `ClubMembership.cs` | اضافه `PackageId` | 🟡 | +| D4 | `ClubMembershipCycle.cs` | اضافه `PackageId` | 🟡 | +| D5 | `WeeklyCommissionPool.cs` | اضافه `PackageId` + Unique | 🔴 | +| D6 | `UserCommissionPayout.cs` | اضافه `PackageId` | 🟡 | +| D7 | `SystemConstants.cs` | حذف ۵ ثابت، حفظ بقیه | 🟡 | +| D8 | EF Migration + Seed | schema + data migration | 🔴 | -### ۷.۲ ریسک‌ها +### ۱۰.۲ لایه Application (۱۲ تغییر) + +| # | فایل | نوع | شدت | +|---|------|-----|------| +| A1 | `ActivateClubMembershipCommandHandler` | فیچر از PackageFeature + ActivationFee + re-activate | 🔴 | +| A2 | `VerifyPackagePurchaseCommandHandler` | DiscountMultiplier + UserPackagePurchase + generic | 🔴 | +| A3 | `VerifyBasePackagePaymentCommandHandler` | DiscountMultiplier + UserPackagePurchase + generic | 🔴 | +| A4 | `VerifyGoldenPackagePurchaseCommandHandler` | فیکس DiscountBalance + UserPackagePurchase + generic | 🔴 | +| A5 | `InitiateBasePackagePaymentCommandHandler` | حذف ID=4 + generic | 🟡 | +| A6 | `PurchaseGoldenPackageCommandHandler` | حذف فیلتر "طلایی" + generic | 🟡 | +| A7 | `PurchasePackageCommandHandler` | اجازه re-purchase | 🟡 | +| A8 | `CreateManualPaymentCommandHandler` | DiscountMultiplier از Package | 🟡 | +| A9 | `CheckAndProcessDayaLoansCommandHandler` | حذف ID=4 + DiscountMultiplier | 🟡 | +| A10 | `AcceptClubMembershipContractCommandHandler` | اجازه re-contract بعد چرخه | 🟡 | +| A11 | `UserOrderService` (EXIT Magic) | ریست PackagePurchaseMethod + Deactivate | 🔴 | +| A12 | Package CRUD handlers | فیلدهای جدید + PackageFeature CRUD | 🟡 | + +### ۱۰.۳ لایه Infrastructure (۵ تغییر) + +| # | فایل | نوع | شدت | +|---|------|-----|------| +| I1 | `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` | 🔴 | +| I2 | `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` | 🔴 | +| I3 | `WeeklyCommissionCalculationService` | Loop روی پکیج‌ها | 🟡 | +| I4 | `OrmCommissionCalculationStrategy` | فیلتر PackageId | 🔴 | +| I5 | `SpCommissionCalculationStrategy` | پاس دادن PackageId | 🟡 | + +### ۱۰.۴ لایه Proto/gRPC (۴ تغییر) + +| # | فایل | نوع | شدت | +|---|------|-----|------| +| P1 | `package.proto` | فیلدهای جدید Package | 🟡 | +| P2 | `clubmembership.proto` | `package_id` در request/response | 🟡 | +| P3 | `commission.proto` | `package_id` در pool/payout | 🟡 | +| P4 | `PackageGrpcService.cs` | Generic purchase + CRUD | 🟡 | + +### ۱۰.۵ لایه FrontOffice (۶ تغییر) + +| # | فایل | نوع | شدت | +|---|------|-----|------| +| F1 | `Packages.razor` | کاشی‌های پکیج از API | 🔴 | +| F2 | `PackageDetail.razor` | فیچرها از PackageFeature | 🟡 | +| F3 | `ActivationSection.razor` | حذف hardcoded 56M | 🟡 | +| F4 | `ClubMembershipContractDialog.razor` | متن قرارداد داینامیک | 🟡 | +| F5 | `MyPackages.razor` | نمایش نوع پکیج + re-purchase | 🟡 | +| F6 | `PackageService.cs` | فیکس stub GetPurchaseHistory | 🟡 | + +### ۱۰.۶ لایه BackOffice (۴ تغییر) + +| # | فایل | نوع | شدت | +|---|------|-----|------| +| BO1 | `PackageCreateDialog.razor` | فیلدهای جدید | 🟡 | +| BO2 | `PackageEditDialog.razor` | فیلدهای جدید | 🟡 | +| BO3 | `PackageFeatureMatrixPage` — **جدید** | ماتریس پکیج×فیچر | 🔴 | +| BO4 | `ActivateClubDialog.razor` | dropdown انتخاب پکیج | 🟡 | + +--- + +## ۱۱. فازبندی پیاده‌سازی + +### فاز ۰ — فیکس باگ‌های فوری ≈ ۱ روز + +| تسک | شرح | +|-----|------| +| **T0.1** | فیکس `VerifyGoldenPackagePurchase` — اضافه DiscountBalance (`Amount × 2`) | +| **T0.2** | فیکس `VerifyGoldenPackagePurchase` — ساخت `UserPackagePurchase` | +| **T0.3** | فیکس `VerifyPackagePurchase` — ساخت `UserPackagePurchase` | +| **T0.4** | فیکس `VerifyBasePackagePayment` — ساخت `UserPackagePurchase` | + +### فاز ۱ — زیرساخت (Domain + DB) ≈ ۳ روز + +| تسک | شرح | +|-----|------| +| **T1.1** | بروزرسانی `Package` entity (۷ فیلد جدید) | +| **T1.2** | ایجاد `PackageFeature` entity + EF Config | +| **T1.3** | اضافه `PackageId` به `ClubMembership` | +| **T1.4** | اضافه `PackageId` به `ClubMembershipCycle` | +| **T1.5** | اضافه `PackageId` به `WeeklyCommissionPool` + Unique | +| **T1.6** | اضافه `PackageId` به `UserCommissionPayout` | +| **T1.7** | حذف ۵ ثابت از `SystemConstants` | +| **T1.8** | Database Migration + Seed Data (۲ پکیج + فیچرها) | +| **T1.9** | Data Migration: کاربران فعلی → PackageId = پکیج پایه | +| **T1.10** | بروزرسانی Proto‌ها | + +### فاز ۲ — منطق کسب‌وکار ≈ ۴ روز + +| تسک | شرح | +|-----|------| +| **T2.1** | ادغام Verify handlers → Generic (DiscountMultiplier + UserPackagePurchase) | +| **T2.2** | ادغام Purchase handlers → Generic (حذف "طلایی"، حذف ID=4) | +| **T2.3** | بروزرسانی `ActivateClubMembership` — فیچر از PackageFeature | +| **T2.4** | بروزرسانی `ActivateClubMembership` — ActivationFee از Package | +| **T2.5** | اجازه re-purchase در Guards (G1–G3) | +| **T2.6** | ریست وضعیت در EXIT Magic Mode | +| **T2.7** | اجازه re-contract (G5) + بروزرسانی JWT (G7) | +| **T2.8** | بروزرسانی `CreateManualPayment` + `DayaLoan` | +| **T2.9** | PackageFeature CRUD | +| **T2.10** | Event: PackageCreated → ساخت Pool خالی | + +### فاز ۳ — محاسبه پورسانت ≈ ۳ روز (موازی با فاز ۲) + +| تسک | شرح | +|-----|------| +| **T3.1** | بروزرسانی `sp_CalculateWeeklyBalances` — `@PackageId` | +| **T3.2** | بروزرسانی `sp_CalculateWeeklyCommissionPool` — `@PackageId` | +| **T3.3** | بروزرسانی `WeeklyCommissionCalculationService` — Loop | +| **T3.4** | بروزرسانی `OrmCommissionCalculationStrategy` — فیلتر | +| **T3.5** | تست محاسبات با داده واقعی | + +### فاز ۴ — UI ≈ ۴ روز + +| تسک | شرح | +|-----|------| +| **T4.1** | FrontOffice: کاشی‌های پکیج (داینامیک) | +| **T4.2** | FrontOffice: مدال پرداخت (دایا+مستقیم / فقط مستقیم) | +| **T4.3** | FrontOffice: MyPackages — re-purchase | +| **T4.4** | FrontOffice: ActivationSection + Contract — داینامیک | +| **T4.5** | BackOffice: CRUD پکیج — فیلدهای جدید | +| **T4.6** | BackOffice: ماتریس PackageFeature | +| **T4.7** | BackOffice: ActivateClubDialog — dropdown | + +### فاز ۵ — تست و استقرار ≈ ۲ روز + +| تسک | شرح | +|-----|------| +| **T5.1** | تست خرید هر پکیج | +| **T5.2** | تست re-purchase بعد تکمیل چرخه | +| **T5.3** | تست Commission Pool جداگانه | +| **T5.4** | تست Migration | +| **T5.5** | Deploy staging → production | + +--- + +## ۱۲. ریسک‌ها | ریسک | احتمال | شدت | راه‌حل | |------|--------|-----|--------| -| داده‌های فعلی — کاربران بدون PackageId | قطعی | زیاد | Migration: کاربران فعلی → PackageId = پکیج پایه | -| Commission Pool فعلی بدون PackageId | قطعی | زیاد | Migration: Pool‌های موجود → PackageId = پکیج پایه | -| SP تغییر → محاسبات اشتباه | متوسط | بحرانی | تست جامع + محیط staging | -| مبالغ hardcoded در جاهای پراکنده | زیاد | متوسط | Audit کامل کدبیس | -| عدم سازگاری FrontOffice/BackOffice | متوسط | متوسط | تست end-to-end | +| Migration داده‌ها — PackageId اشتباه | کم | بحرانی | Verify query + بکاپ | +| SP تغییر → محاسبات اشتباه | متوسط | بحرانی | تست staging قبل production | +| ادغام handlers → رگرسیون | متوسط | زیاد | E2E test | +| خرید مجدد بدون تکمیل چرخه | کم | زیاد | Validation: چرخه قبلی MagicCompletedAt | +| Proto breaking change | قطعی | کم | backward compatible fields | --- -## ۸. فازبندی پیاده‌سازی - -### فاز ۱ — زیرساخت (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 — پکیج‌های اولیه - -```sql --- 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 | ۴-۵ روز | فاز ۲ | -| فاز ۵ — تست | ۲-۳ روز | فاز ۳, ۴ | -| **مجموع** | **~۱۶-۲۱ روز کاری** | | +| فاز ۰ — فیکس باگ‌ها | ۱ روز | — | +| فاز ۱ — زیرساخت | ۳ روز | فاز ۰ | +| فاز ۲ — منطق | ۴ روز | فاز ۱ | +| فاز ۳ — پورسانت | ۳ روز | فاز ۱ | +| فاز ۴ — UI | ۴ روز | فاز ۲ | +| فاز ۵ — تست | ۲ روز | فاز ۳, ۴ | +| **مجموع** | **~۱۷ روز** | | -> فازهای ۲ و ۳ قابل موازی‌سازی هستند. +> فاز ۲ و ۳ **موازی** → مسیر بحرانی: ۰→۱→۲→۴→۵ = **~۱۴ روز**