# 🔄 نقشه‌راه تحول پکیج‌بیس — تسک‌های گام‌به‌گام > **وضعیت:** در حال اجرا — **فاز ۰-۹ (per-package reports) تکمیل ✅** | NuGet v0.0.187 | تست باقی‌مانده > **تاریخ:** ۱۴۰۴/۱۲/۰۹ > **پیش‌نیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) **v3** (تکمیل پورسانت per-package) > **هدف:** شکستن **۴۸+ تغییر** به تسک‌های اتمیک با ترتیب اجرا و وابستگی‌ها > **کامیت‌ها:** > CMS: `8b9c317`→`fe3edd1`→`ae92ab8`→`a9cd2fd`→`8e5c7c5`→`ccb938e`→`0002a5a`→`607f791`→`7176fe4`→`d19c569`→`469d97b`→`161f796`→`8446e0e`→`ce8e248`→`7554d70`→`aaaf7fc` > FrontOffice: `b82cac4`→`71f391a`→`0bbc11e`→`d71d463`→`40882c8`→`a956cb9` > BackOffice: `f1b0085`→`89f5241`→`c96377a`→`8be98ae` > ⚠️ **تغییرات v3:** پورسانت per-package، carryover مجزا، SP parameters داینامیک، گزارش‌دهی FO/BO per-package --- ## 📊 نمای کلی ``` مرحله ۰: فیکس باگ فوری (۱ روز) ✅ `8b9c317` + `fe3edd1` └─→ مرحله ۱: زیرساخت Domain + DB (۴ روز) ✅ `ae92ab8` └─→ مرحله ۱.۵: Migration + Seed ✅ `a9cd2fd` ├─→ مرحله ۲: منطق کسب‌وکار (۴ روز) ✅ `8e5c7c5` │ └─→ مرحله ۳: بازسازی لایه Package ✅ `ccb938e` │ └─→ مرحله ۴: CRUD + Legacy Fixes ✅ `0002a5a` │ └─→ مرحله UI (۵ روز) ⬜ └─→ مرحله ۳: پورسانت (۴ روز) ✅ `607f791`+`7176fe4` └─→ مرحله ۶: Deprecation cleanup ✅ `d19c569` └─→ مرحله ۷: Migration + Cleanup ✅ ├─→ 7a: Cosmetic cleanup ✅ CMS:`469d97b` FO:`b82cac4` BO:`f1b0085` ├─→ 7b: FO RPC migration ✅ CMS:`161f796` FO:`71f391a` └─→ 7c: Delete deprecated ✅ CMS:`8446e0e` └─→ مرحله ۸: FO/BO Completion ├─→ 8a: Checkout wire-up ✅ FO:`0bbc11e` ├─→ 8b: BO CRUD expansion ✅ CMS:`ce8e248` BO:`89f5241` ├─→ 8c: FO Package pages ✅ FO:`d71d463` └─→ 8d: Proto cleanup ✅ CMS:`7554d70` FO:`40882c8` BO:`c96377a` └─→ 8e: Per-package reports ✅ CMS:`aaaf7fc` FO:`a956cb9` BO:`8be98ae` └─→ مرحله ۵: تست + استقرار (۳ روز) ⬜ مسیر بحرانی: ۰→۱→۱.۵→۲→۳→۴→UI→۵ = ~۱۷ روز | انجام‌شده: ۰→8e (~۱۶ روز) ``` --- ## مرحله ۰ — فیکس باگ‌های فوری ✅ > ✅ تکمیل‌شده | کامیت: `8b9c317` + `fe3edd1` ### ✅ وضعیت باگ‌ها (بررسی اولیه لازم) | # | باگ | Handler | شرح فیکس | |---|------|---------|----------| | B1 | DiscountBalance شارژ نمی‌شود | `VerifyGoldenPackagePurchaseCommandHandler` | اضافه `DiscountBalance += Amount × 2` + WalletChangeLog | | B2 | UserPackagePurchase ساخته نمی‌شود | `VerifyGoldenPackagePurchaseCommandHandler` | ساخت record بعد verify موفق | | B3 | UserPackagePurchase ساخته نمی‌شود | `VerifyPackagePurchaseCommandHandler` | ساخت record بعد verify موفق | | B4 | UserPackagePurchase ساخته نمی‌شود | `VerifyBasePackagePaymentCommandHandler` | ساخت record بعد verify موفق | #### دستور کار B1: ``` 1. باز کردن VerifyGoldenPackagePurchaseCommandHandler.cs 2. پیدا کردن جایی که Balance شارژ می‌شود 3. اضافه کردن: wallet.DiscountBalance += command.Amount * 2; // + ساخت WalletChangeLog برای DiscountBalance 4. تست: verify → چک DiscountBalance در DB ``` #### دستور کار B2-B4 (الگوی مشترک): ``` 1. بعد از verify موفق و شارژ wallet: var purchase = new UserPackagePurchase { UserId = userId, PackageId = packageId, // فعلاً BasePackageId = 4 PurchaseDate = DateTime.UtcNow, Amount = amount, PurchaseMethod = purchaseMethod, // ZarinPal, BFF, etc. TransactionId = transactionId, IsVerified = true }; _context.UserPackagePurchases.Add(purchase); 2. تست: verify → چک UserPackagePurchases table ``` --- ## مرحله ۱ — زیرساخت (Domain + DB) ✅ > ✅ تکمیل‌شده | کامیت: `ae92ab8` (Phase 1) + `a9cd2fd` (Phase 1.5 Migration) ### T1.1 — بروزرسانی Package Entity (۱۱ فیلد جدید — v3) **فایل:** `CMS/src/CMSMicroservice.Domain/Entities/Package.cs` ```diff + 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; } + public decimal DiscountMultiplier { get; set; } = 2.0m; + public decimal MagicWalletMultiplier { get; set; } = 2.5m; + // === v3: تنظیمات پورسانت per-package === + public int MaxBalancesPerLeg { get; set; } = 300; // نقره‌ای=۳۰ + public int MaxNetworkLevel { get; set; } = 15; + // === v3: سقف کیف‌پول جادویی per-package === + public long MagicWalletMaxDeposit { get; set; } = 1_000_000_000; + public long MagicWalletMaxCredit { get; set; } = 2_500_000_000; + + public virtual ICollection PackageFeatures { get; set; } ``` **EF Config:** `PackageConfiguration.cs` - حداکثر یک `IsBasePackage = true` (Index filter) - Precision for decimal fields ### T1.2 — ایجاد PackageFeature Entity **فایل جدید:** `CMS/src/CMSMicroservice.Domain/Entities/PackageFeature.cs` ```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; } ``` ### T1.3-T1.6 — اضافه PackageId به entityها | Entity | فیلد | Required? | توضیح | v3? | |--------|------|-----------|-------|-----| | ClubMembership | `long? PackageId` | nullable (بعد migration → required) | آخرین پکیج | | | ClubMembershipCycle | `long PackageId` | required | پکیج این چرخه | | | WeeklyCommissionPool | `long PackageId` | required + Unique(WeekDefId, PkgId) | Pool هر پکیج | | | UserCommissionPayout | `long? PackageId` | nullable + **Unique(UserId, WeekId, PkgId)** | ردیابی | 🔄 | | **NetworkWeeklyBalance** | **`long PackageId`** | **required + Unique(UserId, WeekId, PkgId)** | **تعادل per-package** | **🆕** | ### T1.7 — حذف SystemConstants (v3: ۷ ثابت) **فایل:** `CMS/src/CMSMicroservice.Domain/Common/SystemConstants.cs` ```diff - public const long BasePackageAmount = 56_000_000; - public const long DayaLoanAmount = 56_000_000; - public const long ClubActivationFee = 25_200_000; - public const long ClubMembershipGiftValue = 25_200_000; - public const decimal MagicWalletMultiplier = 2.5m; - // === v3: انتقال به Package entity === - public const int CommissionMaxWeeklyBalancesPerLeg = 300; - public const int CommissionMaxNetworkLevel = 15; ``` > ⚠️ **قبل از حذف:** grep تمام مصرف‌کننده‌ها → جایگزین با `Package.Property` > ⚠️ **v3:** `CommissionMaxWeeklyBalancesPerLeg` و `CommissionMaxNetworkLevel` هم باید per-package شوند ### T1.8 — Database Migration ```bash dotnet ef migrations add AddPackageBasedSystem ``` **شامل:** - ستون‌های جدید Package - جدول PackageFeatures - FKها در 4 entity - Unique constraint ### T1.9 — Data Migration Script ```sql -- 1. بروزرسانی پکیج فعلی (ID=4 → اضافه فیلدهای جدید) UPDATE "CMS"."Packages" SET "SortOrder" = 2, "IsActive" = true, "IsBasePackage" = true, "SupportsDayaPurchase" = true, "SupportsDirectPurchase" = true, "ActivationFee" = 25200000, "DiscountMultiplier" = 2.0, "MagicWalletMultiplier" = 2.5, -- v3: تنظیمات پورسانت "MaxBalancesPerLeg" = 300, "MaxNetworkLevel" = 15, "MagicWalletMaxDeposit" = 1000000000, "MagicWalletMaxCredit" = 2500000000 WHERE "Id" = 4; -- 2. Link existing data to base package UPDATE "CMS"."ClubMemberships" SET "PackageId" = 4 WHERE "PackageId" IS NULL; UPDATE "CMS"."ClubMembershipCycles" SET "PackageId" = 4 WHERE "PackageId" IS NULL; UPDATE "CMS"."WeeklyCommissionPools" SET "PackageId" = 4 WHERE "PackageId" IS NULL; UPDATE "CMS"."UserCommissionPayouts" SET "PackageId" = 4 WHERE "PackageId" IS NULL; -- v3: NetworkWeeklyBalance هم PackageId می‌گیره UPDATE "CMS"."NetworkWeeklyBalances" SET "PackageId" = 4 WHERE "PackageId" IS NULL; -- 3. Seed Silver package (شامل فیلدهای v3) INSERT INTO "CMS"."Packages" (..., "MaxBalancesPerLeg", "MaxNetworkLevel", "MagicWalletMaxDeposit", "MagicWalletMaxCredit", ...) VALUES ('پکیج نقره‌ای', 5600000, ..., 30, 15, 100000000, 250000000, ...); ``` ### T1.10 — بروزرسانی Protoها | Proto File | تغیر | v3? | |-----------|-------|-----| | package.proto | فیلدهای جدید Package message (۱۱ فیلد) | 🔄 | | clubmembership.proto | package_id در request/response | | | commission.proto | **`package_id` + `package_title`** در ۴ message | **🆕** | | commission.proto | **Message جدید: `CustomerCommissionPackageSummary`** | **🆕** | | commission.proto | **فیلتر `package_id` در Requestها** | **🆕** | ### T1.11 — اضافه PackageId به NetworkWeeklyBalance (🆕 v3) **فایل:** `CMS/src/CMSMicroservice.Domain/Entities/NetworkWeeklyBalance.cs` ```diff + public long PackageId { get; set; } + public virtual Package Package { get; set; } ``` **EF Config:** اضافه Unique Index: ```csharp builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId, e.PackageId }).IsUnique(); builder.HasOne(e => e.Package).WithMany().HasForeignKey(e => e.PackageId); ``` > ⚠️ **تاثیر حجم:** رکوردهای تعادل ×N (تعداد پکیج). مثلاً ۱۰۰۰ کاربر × ۲ پکیج = ۲۰۰۰ رکورد هفتگی ### T1.12 — Data Migration: NetworkWeeklyBalance (🆕 v3) ```sql -- رکوردهای موجود → پکیج پایه UPDATE "CMS"."NetworkWeeklyBalances" SET "PackageId" = (SELECT "Id" FROM "CMS"."Packages" WHERE "IsBasePackage" = true LIMIT 1) WHERE "PackageId" IS NULL; ``` --- ## مرحله ۲ — منطق کسب‌وکار ✅ > ✅ تکمیل‌شده | کامیت: `8e5c7c5` (Phase 2) + `ccb938e` (Phase 3) + `0002a5a` (Phase 4) ### T2.1 — Generic Verify Handler **هدف:** ادغام VerifyGolden + VerifyBase + VerifyGeneric → یک handler **الگوریتم:** ``` 1. دریافت TransactionId از request 2. خواندن Transaction → PackageId → Package entity 3. verify با درگاه (ZarinPal/BFF/...) 4. اگر موفق: a. wallet.Balance += Package.Price b. wallet.DiscountBalance += Package.Price × Package.DiscountMultiplier c. ساخت WalletChangeLog (Balance) d. ساخت WalletChangeLog (DiscountBalance) e. ساخت UserPackagePurchase record f. اگر اولین خرید: JoinNetwork g. بروزرسانی ClubMembershipCycle.PackageId 5. return success + receipt ``` ### T2.2 — Generic Purchase Handler **هدف:** ادغام PurchaseGolden + PurchasePackage + InitiateBase → یک handler **تغییرات:** - حذف فیلتر `Title.Contains("طلایی")` - حذف `BasePackageId = 4` - خواندن Package entity از DB بر اساس `request.PackageId` - Gateway URL + Amount از Package.Price ### T2.3-T2.4 — ActivateClubMembership بهبود **تغییرات:** ```diff - var features = await GetAllFeatureIds(); // همه فیچرها + var features = await GetPackageFeatures(packageId); // فیچرهای پکیج - membership.PackageAmount = SystemConstants.BasePackageAmount; + membership.PackageAmount = package.Price; - var activationFee = SystemConstants.ClubActivationFee; + var activationFee = package.ActivationFee; ``` ### T2.5-T2.6 — Re-Purchase Logic **EXIT Magic Mode — تغییرات:** ```diff wallet.WalletMode = WalletMode.Normal; wallet.MagicCompletedAt = DateTime.UtcNow; cycle.MagicCompletedAt = DateTime.UtcNow; + user.PackagePurchaseMethod = PackagePurchaseMethod.None; + membership.IsActive = false; + cycle.IsCurrentCycle = false; ``` **Guard تغییرات:** ```diff - if (user.PackagePurchaseMethod != PackagePurchaseMethod.None) - throw new RpcException("قبلاً پکیج خریداری شده"); + if (user.PackagePurchaseMethod != PackagePurchaseMethod.None + && !HasCompletedMagicCycle(membership)) + throw new RpcException("چرخه جاری هنوز تکمیل نشده"); ``` ### T2.7 — JWT Claims جدید ```diff claims.Add("HasPurchasedPackage", "true"); + claims.Add("CanRepurchase", HasCompletedMagicCycle(membership).ToString()); + claims.Add("PackageId", membership.PackageId?.ToString() ?? ""); + claims.Add("PackageTitle", package?.Title ?? ""); ``` --- ## مرحله ۳ — محاسبه پورسانت (موازی با مرحله ۲) ✅ > ✅ تکمیل‌شده | کامیت: `607f791` + `7176fe4` | ⏱️ **۴ روز** | ریسک: بحرانی (مالی) ### T3.1-T3.2 — SPs + PackageId + پارامترهای داینامیک (🔄 v3) ```sql -- sp_CalculateWeeklyBalances — v3: حذف hardcode ALTER PROCEDURE sp_CalculateWeeklyBalances @WeekDefinitionId BIGINT, @PackageId BIGINT, @MaxBalancesPerLeg INT, -- v3: از Package entity (نه ۳۰۰ hardcode!) @MaxNetworkLevel INT -- v3: از Package entity (نه ۱۵ hardcode!) AS BEGIN -- فیلتر: فقط کاربرانی که این پکیج را دارند -- carryover: فقط رکوردهای PackageId = @PackageId -- cap: از @MaxBalancesPerLeg (نه ۳۰۰) -- depth: CTE تا @MaxNetworkLevel (نه ۱۵) INSERT INTO "CMS"."NetworkWeeklyBalances" ("PackageId", ...) SELECT @PackageId, ... FROM "CMS"."UserWallets" w INNER JOIN "CMS"."ClubMemberships" m ON m."UserId" = w."UserId" WHERE m."PackageId" = @PackageId AND m."IsActive" = true; END; ``` ### T3.3 — Loop Service (🔄 v3: ارسال تنظیمات پکیج) ```csharp // WeeklyCommissionCalculationService.cs var activePackages = await _context.Packages .Where(p => p.IsActive && !p.IsDeleted) .ToListAsync(); foreach (var package in activePackages) { _logger.LogInformation( "Calculating commission for package {Id}: {Title} " + "(MaxBalances={Max}, MaxLevel={Level})", package.Id, package.Title, package.MaxBalancesPerLeg, package.MaxNetworkLevel); // v3: پاس دادن تنظیمات پکیج await strategy.CalculateWeeklyBalancesAsync( weekId, package.Id, package.MaxBalancesPerLeg, package.MaxNetworkLevel); await strategy.CalculateWeeklyPoolAsync(weekId, package.Id); } ``` ### T3.4 — OrmCommissionCalculationStrategy (🔄 v3) **تغییرات:** ```diff - var maxBalances = SystemConstants.CommissionMaxWeeklyBalancesPerLeg; // 300 - var maxLevel = SystemConstants.CommissionMaxNetworkLevel; // 15 + // پارامتر از بیرون — per-package + int maxBalances = maxBalancesPerLeg; // e.g., نقره‌ای=30, پایه=300 + int maxLevel = maxNetworkLevel; - // فیلتر کاربران + // فیلتر کاربران بر اساس پکیج + .Where(m => m.PackageId == packageId && m.IsActive) - // carryover + // carryover: فقط رکوردهای همان PackageId + .Where(b => b.PackageId == packageId && b.WeekDefinitionId == prevWeekId) ``` ### T3.5 — SpCommissionCalculationStrategy (🆕 v3) ```csharp // قبل: فقط WeekDefinitionId await connection.ExecuteAsync("CMS.sp_CalculateWeeklyBalances", new { WeekDefinitionId = weekId, ForceRecalculate = true }); // بعد (v3): پکیج + تنظیمات داینامیک await connection.ExecuteAsync("CMS.sp_CalculateWeeklyBalances", new { WeekDefinitionId = weekId, PackageId = package.Id, MaxBalancesPerLeg = package.MaxBalancesPerLeg, MaxNetworkLevel = package.MaxNetworkLevel, ForceRecalculate = true }); ``` ### T3.6 — Carryover per-package (🆕 v3) > ⚠️ **بحرانی:** week-shifting باید فقط رکوردهای همان PackageId را shift کند ``` هفته ۱۰ → هفته ۱۱: علی: carryover_پایه = {Left: surplus, Right: surplus} ← جداگانه علی: carryover_نقره‌ای = {Left: 0, Right: 0} ← جداگانه ✖ اشتباه: قاطی کردن carryover پایه و نقره‌ای! ✔ صحیح: هر PackageId فقط carryover خودش را می‌بینه ``` ### ⚠️ نکته بحرانی > پورسانت = پول واقعی. **هر تغییر در SPs باید:** > 1. ابتدا در staging با داده واقعی تست شود > 2. نتایج قبل و بعد مقایسه شوند > 3. Rollback plan آماده باشد > 4. در production ابتدا read-only اجرا شود (بدون commit) --- ## مرحله ۷ — Migration + Cleanup (سه‌گانه) ✅ > ✅ تکمیل‌شده | ⏱️ **۱ روز** | ریسک: پایین ### فاز 7a — Cosmetic Cleanup ✅ > کامیت: CMS `469d97b` | FO `b82cac4` | BO `f1b0085` **CMS:** - حذف orphaned `PurchasePackage` handler (۳ فایل، بدون caller) - فیکس doc-comments: `طلایی` → `پکیج` در ۶ فایل (enums, entities, handlers) **FrontOffice:** - حذف hardcoded `پکیج طلایی` از `MyPackages.razor` و `Packages.razor` - اضافه `PackageTitle` property به `UserPackageStatusDto` record **BackOffice:** - تغییر label `پکیج طلایی` → `خرید پکیج` در `UserNetworkInfo.razor` ### فاز 7b — FrontOffice RPC Migration ✅ > کامیت: CMS `161f796` | FO `71f391a` **CMS:** - `CustomerPurchasePackage`: embed `orderId` در callback URL قبل از ارسال به درگاه - `$"{request.CallbackUrl}{separator}orderId={purchase.Id}"` **FrontOffice:** - `Profile/Index.razor.cs`: مهاجرت `InitiateBasePackagePaymentAsync` → `CustomerPurchasePackageAsync` - `Profile/PaymentCallback.razor`: مهاجرت `VerifyBasePackagePaymentAsync` → `CustomerVerifyPackagePurchaseAsync` - پارامترهای جدید: `PackageId`, `CallbackUrl`, `PurchaseMethod`, `OrderId`, `Authority`, `Status` ### فاز 7c — Delete Deprecated Handlers ✅ > کامیت: CMS `8446e0e` (14 فایل، 1125 حذف) **حذف ۴ handler CQRS (۱۲ فایل):** - `PurchaseGoldenPackage/` (Command, Handler, Validator) - `VerifyGoldenPackagePurchase/` (Command, Handler, Validator) - `InitiateBasePackagePayment/` (Command, Handler, Validator) - `VerifyBasePackagePayment/` (Command, Handler, Validator) **Cleanup:** - `PackageService.cs`: حذف ۴ gRPC override method (proto RPCs حالا auto-throw `Unimplemented`) - `PackageProfile.cs`: حذف ۶ Mapster mapping block + ۴ using directive - Build: 0 Error ✅ --- ## مرحله ۸ — FrontOffice Checkout + NuGet > 🔄 در حال اجرا | فاز 8a تکمیل ✅ ### فاز 8a — Checkout Wire-up ✅ > کامیت: FO `0bbc11e` **Checkout.razor.cs:** - حذف dead code: `ProcessPayment()` از flow قدیمی `TransactionsContract + UserOrderContract` استفاده می‌کرد - Rewrite با `CustomerPurchasePackageAsync` (مثل Profile/Index.razor.cs) - Callback URL → `/profile/payment-callback` (از صفحه verify موجود استفاده مجدد) - حذف DI بلااستفاده: `UserOrderContract`, `TransactionContract` - حذف usings: `Transactions`, `UserOrder`, `WellKnownTypes` **Profile/Index.razor.cs (cosmetic):** - Rename `basePackage` → `selectedPackage`, `tempCallbackUrl` → `callbackUrl` ### فاز 8b — BackOffice Package CRUD Expansion ✅ > کامیت: CMS `ce8e248` | BO `89f5241` (7 فایل، +138/-23) **NuGet Rebuild:** - Proto version bump: `0.0.184` → `0.0.185` - Pack و deploy به local feed (`/nupkg`) - BackOffice NuGet.config: اضافه local feed source **CreateDialog.razor (۱۲ فیلد جدید):** - `SortOrder` — MudNumericField ترتیب نمایش - `ActivationFee` — MudNumericField هزینه فعال‌سازی - `DiscountMultiplier` — MudNumericField ضریب تخفیف - `MagicWalletMultiplier` — MudNumericField ضریب کیف پول جادویی - `MagicWalletMaxDeposit` — MudNumericField سقف واریز جادویی - `MagicWalletMaxCredit` — MudNumericField سقف اعتبار جادویی - `MaxBalancesPerLeg` — MudNumericField حداکثر تعادل هر پا - `MaxNetworkLevel` — MudNumericField حداکثر سطح شبکه - `IsActive` — MudCheckBox فعال/غیرفعال - `IsBasePackage` — MudCheckBox پکیج پایه - `SupportsDirectPurchase` — MudCheckBox پرداخت مستقیم - `SupportsDayaPurchase` — MudCheckBox اعتبار دایا **UpdateDialog.razor:** همان ۱۲ فیلد **PackageMainPage Grid (۴ ستون جدید):** - `Price` — فرمت‌شده با N0 - `SortOrder` — ترتیب - `IsActive` — MudChip فعال/غیرفعال - `IsBasePackage` — MudChip پایه/عادی **سایر:** - Dialog size: `MaxWidth.Small` → `MaxWidth.Medium` - CreateNew defaults: `IsActive=true, DiscountMultiplier=2.0, MagicWalletMultiplier=2.5, ...` - فیکس `HasPurchasedGoldenPackage` → `HasPurchasedPackage` در `UserNetworkInfo.razor` ### فاز 8c — FrontOffice Package Pages ✅ > کامیت: FO `d71d463` (5 فایل، +109/-56) **NuGet:** `0.0.182` → `0.0.185` + local feed source **PackageDetail.razor.cs:** - مهاجرت `GetPackageAsync` (admin RPC) → `GetCustomerPackageDetailsAsync` (customer RPC) - Features: از hardcoded ثابت → از `PackageFeature` API داینامیک - Specifications: از hardcoded → از `PackageFeature.IsHighlighted` API - حذف ۵ hardcoded feature string + ۴ hardcoded specification **PackageService.cs:** - `PackageDto`: اضافه ۸ فیلد جدید (ActivationFee, DiscountMultiplier, MagicWalletMultiplier, etc.) - `GetAllPackagesAsync`: مپ فیلدهای جدید از `CustomerPackageModel` - `GetUserPackageStatusAsync`: از stub → اتصال واقعی به `GetUserPackageStatusAsync` RPC **Packages.razor:** - Un-exclude از build (حذف `` + ``) - جایگزینی ۳ feature bullet hardcoded → dynamic features: - `SupportsDirectPurchase` → پرداخت مستقیم - `SupportsDayaPurchase` → پرداخت با اعتبار دایا - `DiscountMultiplier` → ضریب تخفیف: X.Xx - `MagicWalletMultiplier` → کیف پول جادویی: X.Xx - `IsBasePackage` → پکیج پایه ⭐ ### فاز 8d — Proto Cleanup ✅ > کامیت: CMS `7554d70` (2 فایل، -103) | FO `40882c8` | BO `c96377a` **حذف ۴ deprecated RPC:** - `PurchaseGoldenPackage` — جایگزین: `CustomerPurchasePackage` - `VerifyGoldenPackagePurchase` — جایگزین: `CustomerVerifyPackagePurchase` - `InitiateBasePackagePayment` — جایگزین: `CustomerPurchasePackage` - `VerifyBasePackagePayment` — جایگزین: `CustomerVerifyPackagePurchase` **حذف ۸ deprecated message type:** - `PurchaseGoldenPackageRequest` / `PurchaseGoldenPackageResponse` - `VerifyGoldenPackagePurchaseRequest` / `VerifyGoldenPackagePurchaseResponse` - `InitiateBasePackagePaymentRequest` / `InitiateBasePackagePaymentResponse` - `VerifyBasePackagePaymentRequest` / `VerifyBasePackagePaymentResponse` **حفظ شده:** `GetUserPackageStatus` RPC + messages (هنوز در استفاده) **NuGet:** `0.0.185` → `0.0.186` (همه ریپوها) ### فاز 8e — Per-Package Commission Reports ✅ > کامیت: CMS `aaaf7fc` | FO `a956cb9` | BO `8be98ae` **Proto (commission.proto):** - اضافه `package_id` فیلتر به ۴ request message: `GetUserCommissionPayoutsRequest`, `GetUserWeeklyBalancesRequest`, `GetMyCommissionPayoutsRequest`, `GetMyWeeklyBalancesRequest` - اضافه `package_id` + `package_title` به ۴ response model: `UserCommissionPayoutModel`, `UserWeeklyBalanceModel`, `CustomerCommissionPayoutModel`, `CustomerWeeklyBalanceModel` **CMS (12 فایل):** - ۴ Query record: اضافه `public long? PackageId { get; init; }` - ۴ Handler: اضافه `.Include(x => x.Package)` + فیلتر `Where(x => x.PackageId == request.PackageId.Value)` + map `PackageId`/`PackageTitle` - ۳ Response DTO: اضافه `PackageId` + `PackageTitle` - `CommissionProfile.cs`: تنظیم mapping‌های Mapster برای admin + customer **BackOffice (6 فایل):** - کامپوننت جدید `PackageSelect.razor/.cs`: dropdown قابل استفاده مجدد با بارگذاری پکیج‌ها از `PackageContract` - `UserPayouts.razor/.cs`: فیلتر PackageSelect + ستون پکیج با MudChip - `BalancesReport.razor`: فیلتر PackageSelect + ستون پکیج با MudChip + mapping PackageTitle **FrontOffice (7 فایل):** - `CommissionDtos.cs`: اضافه `PackageId` + `PackageTitle` به `CommissionPayoutDto` و `WeeklyBalanceDto` - `CommissionService.cs`: اضافه پارامتر `packageId` به `GetMyCommissionPayoutsAsync` و `GetMyWeeklyBalanceAsync` - `CommissionDashboardPage.razor/.cs`: فیلتر dropdown پکیج + ستون «پکیج» با MudChip (دسکتاپ + موبایل) - `WeeklyBalancePage.razor/.cs`: فیلتر MudSelect پکیج + نمایش MudChip پکیج در بخش اطلاعات هفته **NuGet:** `0.0.186` → `0.0.187` (همه ریپوها) --- ## مرحله ۴ — UI (FrontOffice + BackOffice) > ⏱️ **۵ روز** (v3: +۲) | وابستگی: مرحله ۲ + ۳ | ریسک: متوسط ### T4.1 — کاشی‌های پکیج داینامیک **فایل:** `FrontOffice/src/.../Pages/Package/Packages.razor` ```razor @* قبل: hardcoded *@ @* بعد: *@ @foreach (var package in _packages.OrderBy(p => p.SortOrder)) { } ``` ### T4.2 — مودال پرداخت شرطی ```razor @if (_selectedPackage.SupportsDirectPurchase) { پرداخت آنلاین } @if (_selectedPackage.SupportsDayaPurchase) { اقساط دایا } ``` ### T4.3 — MyPackages + Re-Purchase ```razor @if (_canRepurchase) { 🎉 چرخه جادویی تکمیل شد! می‌توانید پکیج جدید بخرید. خرید پکیج جدید } else { } ``` ### T4.8 — FrontOffice: CommissionDashboard per-package (🆕 v3) ✅ FO:`a956cb9` **پیاده‌سازی:** - `CommissionDtos.cs`: اضافه `PackageId` + `PackageTitle` به `CommissionPayoutDto` و `WeeklyBalanceDto` - `CommissionService.cs`: اضافه پارامتر `packageId` به `GetMyCommissionPayoutsAsync` و `GetMyWeeklyBalanceAsync` - `CommissionDashboardPage.razor`: اضافه dropdown فیلتر پکیج + ستون «پکیج» با MudChip + نمایش پکیج در card موبایل - `CommissionDashboardPage.razor.cs`: inject `PackageService`، فیلد `_filterPackageId`، بارگذاری لیست پکیج‌ها ### T4.9 — FrontOffice: WeeklyBalance per-package (🆕 v3) ✅ FO:`a956cb9` **پیاده‌سازی:** - `WeeklyBalancePage.razor`: اضافه MudSelect فیلتر پکیج کنار WeekSelector + نمایش MudChip پکیج در بخش اطلاعات هفته - `WeeklyBalancePage.razor.cs`: inject `PackageService`، فیلد `_filterPackageId`، ارسال به `CommissionService.GetMyWeeklyBalanceAsync` ### T4.10-T4.12 — BackOffice: گزارش‌های پورسانت per-package (🆕 v3) ✅ BO:`8be98ae` **پیاده‌سازی:** - کامپوننت جدید `PackageSelect.razor/.cs`: dropdown قابل استفاده مجدد با بارگذاری پکیج‌ها از `PackageContract` - `UserPayouts.razor/.cs`: فیلتر PackageSelect + ستون پکیج با MudChip - `BalancesReport.razor`: فیلتر PackageSelect + ستون پکیج با MudChip + mapping `PackageTitle` ### T4.13 — BackOffice: Package CRUD + Quick Access فیچرها (🆕 v3) > ادمین موقع ساخت/ویرایش پکیج، فیچرها را با checkbox در همان فرم تعیین کند ```razor @* در PackageCreateDialog / PackageEditDialog: *@ فیچرهای پکیج @foreach (var feature in _allFeatures) { } ``` --- ## مرحله ۵ — تست و استقرار > ⏱️ **۳ روز** (v3: +۱) | وابستگی: مرحله ۴ ### Checklist تست **خرید + فعال‌سازی:** - [ ] خرید پکیج نقره‌ای (ZarinPal) - [ ] خرید پکیج پایه (ZarinPal) - [ ] خرید پکیج پایه (Daya Loan) - [ ] خرید پکیج پایه (Manual Payment) - [ ] فعالسازی باشگاه با پکیج نقره‌ای → فیچرهای محدود - [ ] فعالسازی باشگاه با پکیج پایه → همه فیچرها **چرخه Magic + خرید مجدد:** - [ ] تکمیل چرخه Magic → ریست وضعیت - [ ] خرید مجدد بعد تکمیل چرخه (همان پکیج) - [ ] خرید مجدد با پکیج متفاوت (پایه → نقره‌ای) **پورسانت per-package (v3):** - [ ] Commission Pool جداگانه هر پکیج - [ ] تعادل per-package: MaxBalancesPerLeg متفاوت (پایه=۳۰۰, نقره‌ای=۳۰) - [ ] Carryover مجزا: shift فقط رکوردهای همان PackageId - [ ] SP پارامترها صحیح: @MaxBalancesPerLeg و @MaxNetworkLevel از Package - [ ] NetworkWeeklyBalance رکوردها: ۲ پکیج = ۲× رکورد **گزارش per-package (v3):** - [ ] FO: مشتری کارت‌های خلاصه per-package را می‌بیند - [ ] FO: مجموع پاداش = جمع همه پکیج‌ها - [ ] BO: فیلتر dropdown پکیج کار می‌کند - [ ] BO: CSV export شامل ستون پکیج **Migration + سایر:** - [ ] Data Migration — PackageId در رکوردهای قبلی (شامل NetworkWeeklyBalance) - [ ] JWT claims جدید (CanRepurchase, PackageId) - [ ] UI: کاشی‌های داینامیک FrontOffice - [ ] UI: ماتریس فیچر + Quick Access BackOffice - [ ] Rollback: بدون data loss --- ## 📅 تقویم پیشنهادی (v3) | هفته | روز | تسک | |------|-----|------| | هفته ۱ | روز ۱ | مرحله ۰: فیکس ۴ باگ | | | روز ۲-۳ | مرحله ۱: Package entity (۱۱ فیلد) + PackageFeature | | | روز ۴-۵ | مرحله ۱: FKها + NetworkWeeklyBalance + Migration | | هفته ۲ | روز ۶-۷ | مرحله ۲: Generic handlers + re-purchase | | | روز ۶-۸ | مرحله ۳: SP params + carryover per-package (موازی) | | | روز ۸-۱۰ | مرحله ۲: Guards + JWT + Manual | | هفته ۳ | روز ۱۱-۱۲ | مرحله ۴: FrontOffice UI + گزارش per-package | | | روز ۱۳-۱۴ | مرحله ۴: BackOffice UI + گزارش per-package | | | روز ۱۵-۱۷ | مرحله ۵: تست + deploy | --- ## 🔗 ارجاعات | مستند | محتوا | |-------|-------| | [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی فنی — **۴۸+ تغییر** (v3) + باگ‌ها | | [PACKAGE-TRANSFORMATION-UX.md](PACKAGE-TRANSFORMATION-UX.md) | تاثیر UX بر فرانت‌ها | | [FEATURE-BACKLOG.md](FEATURE-BACKLOG.md) | بکلاگ ۱۲ RPC آماده | | [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت ۳۴۲ RPC | --- *آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۹ — فاز ۰-۸e تکمیل (۲۴ کامیت: ۱۶ CMS + ۶ FO + ۴ BO) | NuGet v0.0.187 | Proto: +package_id in commission messages | FO+BO per-package commission reports کامل | باقی‌مانده: تست + deploy*