# 📦 سیستم مبتنی بر پکیج (Package-Based System) > **وضعیت:** تایید‌شده — آماده پیاده‌سازی > **تاریخ بروزرسانی:** ۶ اسفند ۱۴۰۴ > **نسخه:** v2 (بازنویسی کامل بعد از تحلیل عمیق کدبیس) > **تاثیرگذاری:** زیاد — ۳۹ فایل در ۶ لایه --- ## ۱. خلاصه فیچر **وضعیت فعلی:** سیستم فقط یک پکیج پایه (۵۶ میلیون تومان) دارد و همه چیز حول آن hardcode شده. **وضعیت هدف:** سیستم چندین پکیج با قیمت‌ها و ویژگی‌های متفاوت پشتیبانی می‌کند. هر پکیج مقادیر مالی، فیچرها و Commission Pool مستقل خود را دارد. کاربر می‌تواند **N بار** پکیج بخرد (بعد از تکمیل چرخه Magic Wallet). --- ## ۲. تصمیمات تایید‌شده | # | سوال | تصمیم | |---|------|-------| | 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 | فیچرها | ✅ **داینامیک** — ادمین مدیریت می‌کند | --- ## ۳. تحلیل عمیق وضعیت فعلی (AS-IS) ### ۳.۱ باگ‌های کشف‌شده | # | باگ | شدت | فایل | |---|-----|------|------| | **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 ❌ MISSING: user.PackagePurchaseMethod = None ← Guards G1-G3 مسدود می‌مانند ❌ MISSING: membership.IsActive = false ← Guards G5-G6 مسدود می‌مانند ❌ MISSING: JWT CanRepurchase = true ← UI دکمه خرید نشان نمی‌دهد ``` **راه‌حل:** در 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; } // قیمت پکیج (ریال) // === فیلدهای جدید === public int SortOrder { get; set; } // ترتیب نمایش public bool IsActive { get; set; } = true; // فعال/غیرفعال 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 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; } } ``` **نسبت به v1:** - ❌ `GiftValue` حذف (= ActivationFee — تکراری) - ✅ `MagicWalletMultiplier` اضافه (داینامیک) - ❌ `PackageType` enum نیاز نیست (`IsBasePackage` کافیست) ### ۴.۲ 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 — سایر | 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: کاربر ثبت‌نام کرده NoPurchase --> PackagePurchased: خرید پکیج\n(هر پکیجی) PackagePurchased --> ClubActivated: فعالسازی باشگاه\n(OTP + قرارداد) 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) ```csharp // 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) { await strategy.CalculateWeeklyBalancesAsync(weekId, package.Id); await strategy.CalculateWeeklyPoolAsync(weekId, package.Id); } ``` --- ## ۷. Event-Driven Side Effects ### ۷.۱ ساخت پکیج جدید ```mermaid flowchart LR A["ساخت پکیج جدید
(از BackOffice)"] --> B["PackageCreatedEvent"] B --> C["ایجاد Pool خالی
برای هفته جاری"] B --> D["لاگ ادمین"] ``` ### ۷.۲ جدول رویدادها | رویداد | 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 ``` --- ## ۹. 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 $$; -- 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 — ۳۹ تغییر در ۶ لایه ### ۱۰.۱ لایه Domain (۸ تغییر) | # | فایل | نوع | شدت | |---|------|-----|------| | 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 | --- ## ۱۲. ریسک‌ها | ریسک | احتمال | شدت | راه‌حل | |------|--------|-----|--------| | Migration داده‌ها — PackageId اشتباه | کم | بحرانی | Verify query + بکاپ | | SP تغییر → محاسبات اشتباه | متوسط | بحرانی | تست staging قبل production | | ادغام handlers → رگرسیون | متوسط | زیاد | E2E test | | خرید مجدد بدون تکمیل چرخه | کم | زیاد | Validation: چرخه قبلی MagicCompletedAt | | Proto breaking change | قطعی | کم | backward compatible fields | --- ## ۱۳. تخمین زمانی | فاز | مدت | وابستگی | |-----|------|---------| | فاز ۰ — فیکس باگ‌ها | ۱ روز | — | | فاز ۱ — زیرساخت | ۳ روز | فاز ۰ | | فاز ۲ — منطق | ۴ روز | فاز ۱ | | فاز ۳ — پورسانت | ۳ روز | فاز ۱ | | فاز ۴ — UI | ۴ روز | فاز ۲ | | فاز ۵ — تست | ۲ روز | فاز ۳, ۴ | | **مجموع** | **~۱۷ روز** | | > فاز ۲ و ۳ **موازی** → مسیر بحرانی: ۰→۱→۲→۴→۵ = **~۱۴ روز**