From 33d5ae93057ccb51302a9b3a05736eeeb6c667fb Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Tue, 24 Feb 2026 23:48:09 +0330 Subject: [PATCH] =?UTF-8?q?docs:=20BIZ-PACKAGE-BASED-SYSTEM=20v3=20?= =?UTF-8?q?=E2=80=94=20per-package=20commission=20deep=20analysis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major v3 changes: - Q12-Q18: MaxWeeklyBalancesPerLeg, MaxNetworkLevel, MagicWalletMaxDeposit, MagicWalletMaxCredit all become per-package (not global SystemConstants) - NetworkWeeklyBalance gets PackageId + Unique(UserId,WeekId,PackageId) - SP changes: @MaxBalancesPerLeg and @MaxNetworkLevel as dynamic params (removing hardcoded 300/15) - SpCommissionCalculationStrategy: pass package settings to SPs - Carryover per-package: week-shifting only for same PackageId records - commission.proto: package_id+package_title in 4 message types, new CustomerCommissionPackageSummary message, package filter in requests - FrontOffice: commission dashboard with per-package summary cards - BackOffice: package filter dropdown in all commission reports + CSV - Package Create/Edit: Quick Access checkboxes for features inline - Seed data: silver MaxBalancesPerLeg=30, MagicWalletMax=100M/250M - Migration: NetworkWeeklyBalances existing records get base PackageId - Impact Analysis: 39 -> 48+ changes across 6 layers - Timeline: 14 -> 17 days critical path (+3 days for per-package work) Updated docs: - business/BIZ-PACKAGE-BASED-SYSTEM.md (v2 -> v3) - roadmap/PACKAGE-TRANSFORMATION-TASKS.md (synced with v3) --- business/BIZ-PACKAGE-BASED-SYSTEM.md | 517 +++++++++++++++++++----- roadmap/PACKAGE-TRANSFORMATION-TASKS.md | 301 +++++++++++--- 2 files changed, 650 insertions(+), 168 deletions(-) diff --git a/business/BIZ-PACKAGE-BASED-SYSTEM.md b/business/BIZ-PACKAGE-BASED-SYSTEM.md index e199faa..f4bb1cf 100644 --- a/business/BIZ-PACKAGE-BASED-SYSTEM.md +++ b/business/BIZ-PACKAGE-BASED-SYSTEM.md @@ -2,8 +2,8 @@ > **وضعیت:** تایید‌شده — آماده پیاده‌سازی > **تاریخ بروزرسانی:** ۶ اسفند ۱۴۰۴ -> **نسخه:** v2 (بازنویسی کامل بعد از تحلیل عمیق کدبیس) -> **تاثیرگذاری:** زیاد — ۳۹ فایل در ۶ لایه +> **نسخه:** v3 (تکمیل تحلیل پورسانت per-package + گزارش‌دهی + SystemConstants کامل) +> **تاثیرگذاری:** زیاد — ۵۵+ فایل در ۶ لایه --- @@ -30,6 +30,13 @@ | Q9 | MagicWallet Multiplier | ✅ **داینامیک** به‌ازای هر پکیج (فعلاً همه ×2.5) | | Q10 | کاربران دایا | ✅ **پکیج پایه** گرفتن — "طلایی" اشتباه نام‌گذاری بوده | | Q11 | فیچرها | ✅ **داینامیک** — ادمین مدیریت می‌کند | +| Q12 | MaxWeeklyBalancesPerLeg (300) | ✅ **per-package** — پکیج نقره‌ای ۳۰، پکیج پایه ۳۰۰ | +| Q13 | MaxNetworkLevel (15) | ✅ **per-package** — ادمین تنظیم کند | +| Q14 | MagicWalletMaxDeposit (1B) | ✅ **per-package** — سقف شارژ بر اساس پکیج | +| Q15 | MagicWalletMaxCredit (2.5B) | ✅ **per-package** — سقف اعتبار بر اساس پکیج | +| Q16 | NetworkWeeklyBalance + PackageId | ✅ **هر رکورد تعادل = per-package** — carryover هم جداگانه | +| Q17 | گزارش پورسانت FO | ✅ **breakdown per-package** — مشتری ببیند از هر پکیج چقدر | +| Q18 | گزارش پورسانت BO | ✅ **فیلتر بر اساس پکیج** — ادمین بر اساس پکیج فیلتر کند | --- @@ -132,6 +139,14 @@ public class Package : BaseAuditableEntity public decimal DiscountMultiplier { get; set; } = 2.0m; // ضریب شارژ DiscountBalance public decimal MagicWalletMultiplier { get; set; } = 2.5m; // ضریب کیف‌پول جادویی + // === تنظیمات پورسانت (v3 — انتقال از SystemConstants) === + public int MaxBalancesPerLeg { get; set; } = 300; // سقف تعادل هر پا (نقره‌ای=۳۰) + public int MaxNetworkLevel { get; set; } = 15; // عمق شبکه برای محاسبه + + // === تنظیمات کیف‌پول جادویی (v3) === + public long MagicWalletMaxDeposit { get; set; } = 1_000_000_000; // سقف شارژ + public long MagicWalletMaxCredit { get; set; } = 2_500_000_000; // سقف اعتبار + // === Navigation === public virtual ICollection PackageFeatures { get; set; } public virtual ICollection Purchases { get; set; } @@ -167,8 +182,14 @@ public class PackageFeature : BaseAuditableEntity | `ClubMembershipCycle` | `long PackageId` + FK | پکیج این چرخه | | `WeeklyCommissionPool` | `long PackageId` + FK | Pool جداگانه هر پکیج | | `UserCommissionPayout` | `long PackageId` + FK | از کدام Pool | +| **`NetworkWeeklyBalance`** | **`long PackageId` + FK** ← v3 | **هر رکورد تعادل = per-package** | -**Constraint جدید:** `WeeklyCommissionPool` → Unique(`WeekDefinitionId`, `PackageId`) +**Constraintهای جدید:** +- `WeeklyCommissionPool` → Unique(`WeekDefinitionId`, `PackageId`) +- `NetworkWeeklyBalance` → Unique(`UserId`, `WeekDefinitionId`, `PackageId`) ← v3 +- `UserCommissionPayout` → Unique(`UserId`, `WeekDefinitionId`, `PackageId`) ← v3 + +> ⚠️ **تاثیر حجم داده:** رکوردهای `NetworkWeeklyBalance` ضربدر تعداد پکیج‌های فعال می‌شوند. مثلاً ۱۰۰۰ کاربر × ۲ پکیج = ۲۰۰۰ رکورد تعادل هفتگی (بجای ۱۰۰۰). ### ۴.۴ حذف/تغییر SystemConstants @@ -179,9 +200,19 @@ public class PackageFeature : BaseAuditableEntity | `BasePackageAmount` | ❌ حذف | `Package.Price` | | `DayaLoanAmount` | ❌ حذف | `Package.Price` (base) | | `MagicWalletMultiplier` | ❌ حذف | `Package.MagicWalletMultiplier` | -| `CommissionMaxWeeklyBalancesPerLeg` | ✅ حفظ | عمومی | -| `CommissionMaxNetworkLevel` | ✅ حفظ | عمومی | +| `CommissionMaxWeeklyBalancesPerLeg` | ❌ **حذف** ← تغییر از v2 | `Package.MaxBalancesPerLeg` (نقره‌ای=۳۰، پایه=۳۰۰) | +| `CommissionMaxNetworkLevel` | ❌ **حذف** ← تغییر از v2 | `Package.MaxNetworkLevel` (قابل تنظیم ادمین) | +| `MagicWalletMaxDeposit` | ❌ **حذف** ← جدید v3 | `Package.MagicWalletMaxDeposit` (سقف شارژ) | +| `MagicWalletMaxCredit` | ❌ **حذف** ← جدید v3 | `Package.MagicWalletMaxCredit` (سقف اعتبار) | | `ShopVAT` | ✅ حفظ | عمومی | +| `CommissionCalculationStrategy` | ✅ حفظ | عمومی (ORM or SP) | +| `AllowDeletingParentsWithChildren` | ✅ حفظ | عمومی | +| `MaxDirectChildrenPerLeg` | ✅ حفظ | عمومی | +| `IsCommissionWithdrawalEnabled` | ✅ حفظ | عمومی | +| `CommissionMinWithdrawalAmount` | ✅ حفظ | عمومی | +| `IsMaintenanceMode` | ✅ حفظ | عمومی | +| `IsAuditLogEnabled` | ✅ حفظ | عمومی | +| `IsVATEnabled` | ✅ حفظ | عمومی | ### ۴.۵ فرمول مالی @@ -276,14 +307,142 @@ Commission: Reza → پاداش از Pool_پایه ``` -### ۶.۳ تغییرات SP +### ۶.۳ فلوی کامل چرخه خرید مجدد و تاثیر بر پورسانت (v3) -| SP | تغییر | -|----|-------| -| `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` — فیلتر کاربران بر اساس PackageId | -| `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` — Pool مخصوص آن پکیج | +``` +چرخه ۱ — کاربر "علی" پکیج پایه می‌خرد (۵۶M): +──────────────────────────────────────────────── + ✅ Balance += 56M + ✅ DiscountBalance += 112M (×2) + ✅ ActivationFee → Pool_پایه هفته جاری + ✅ فیچرهای پکیج پایه فعال + ✅ NetworkWeeklyBalance ساخته می‌شود (PackageId=پایه) + ✅ بالاسری‌ها: تعادل‌ها بر اساس MaxBalancesPerLeg=300 + MaxNetworkLevel=15 + ✅ پورسانت بالاسری‌ها از Pool_پایه -### ۶.۴ تغییرات Service + ... خرید از فروشگاه → Balance صفر شد → Magic Mode فعال ... + ... خرید جادویی → MagicBalance صفر + Deposit ≥ MaxDeposit → EXIT Magic ... + +چرخه ۲ — کاربر "علی" دوباره پکیج پایه می‌خرد: +────────────────────────────────────────────── + ✅ ریست وضعیت: PackagePurchaseMethod=None, membership.IsActive=false + ✅ Balance += 56M (مجدد شارژ) + ✅ DiscountBalance += 112M (مجدد شارژ) + ✅ ActivationFee → Pool_پایه هفته جاری + ✅ فیچرهای پکیج پایه فعال (مجدد) + ✅ NetworkWeeklyBalance جدید (PackageId=پایه, WeekId=هفته جاری) + ✅ carryover از هفته قبل: فقط carryover پکیج پایه (نه نقره‌ای!) + ✅ بالاسری‌ها: محاسبه مجدد از Pool_پایه + + ... همان چرخه Magic Wallet تکرار ... + +چرخه ۵ — کاربر "علی" پکیج نقره‌ای می‌خرد (۵.۶M): +──────────────────────────────────────────────────── + ✅ Balance += 5.6M + ✅ DiscountBalance += 11.2M (×2) + ✅ ActivationFee → Pool_نقره‌ای هفته جاری (۲,۵۲۰,۰۰۰) + ✅ فیچرهای پکیج نقره‌ای فعال (ممکنه کمتر از پایه باشه!) + ✅ NetworkWeeklyBalance جدید (PackageId=نقره‌ای, WeekId=هفته جاری) + ✅ تعادل‌ها: MaxBalancesPerLeg=30 (نه 300!) + MaxNetworkLevel=15 (از پکیج) + ✅ carryover: فقط carryover نقره‌ای (جداگانه از پایه) + ✅ بالاسری‌ها: محاسبه از Pool_نقره‌ای → ValuePerBalance کمتر + ✅ پاداش بالاسری: ~۲,۵۲۰,۰۰۰ ÷ TotalBalances_نقره‌ای × BalancesEarned +``` + +### ۶.۴ تعادل‌ها (NetworkWeeklyBalance) — per-package (v3) + +> ⚠️ **تغییر اساسی:** هر کاربر **به‌ازای هر پکیج فعال** یک رکورد تعادل جداگانه دارد. + +``` +قبل (تک‌پکیج): + NetworkWeeklyBalance: [UserId, WeekId] → یک رکورد + +بعد (چند‌پکیج): + NetworkWeeklyBalance: [UserId, WeekId, PackageId] → N رکورد (N = تعداد پکیج) +``` + +**الگوریتم محاسبه تعادل per-package:** + +``` +برای هر پکیج فعال: + 1. واکشی کاربرانی که این پکیج را دارند (PackageId = X) + 2. carryover از هفته قبل: فقط رکوردهای PackageId = X + 3. اعضای جدید: فقط کسانی که PackageId = X خریدند + JoinedAt در بازه هفته + 4. LeftLegTotal = NewLeft + CarryoverLeft + 5. RightLegTotal = NewRight + CarryoverRight + 6. TotalBalances = MIN(Left, Right) → cap at Package.MaxBalancesPerLeg + 7. Remainder → carryover هفته بعد (فقط برای PackageId = X) + 8. SubordinateBalances: مجموع TotalBalances زیرمجموعه (تا Package.MaxNetworkLevel) +``` + +**مثال عملی:** + +``` +هفته ۱۰: + علی (پکیج پایه): + تعادل_پایه: Left=45, Right=52, Min=45 (cap 300) → OK + تعادل_نقره‌ای: Left=0, Right=0 (علی پکیج نقره‌ای نداره) + + سارا (پکیج نقره‌ای): + تعادل_پایه: Left=0, Right=0 + تعادل_نقره‌ای: Left=12, Right=8, Min=8 (cap 30) → OK + + رضا (پکیج پایه + قبلاً نقره‌ای داشته): + تعادل_پایه: Left=30, Right=25, Min=25 (cap 300) → OK + تعادل_نقره‌ای: Left=2 (carryover), Right=0 (carryover) → Min=0 + +هفته ۱۱ (Shift): + علی: carryover_پایه = {Left: surplus_left, Right: surplus_right} + سارا: carryover_نقره‌ای = {Left: surplus_left, Right: surplus_right} + رضا: carryover_پایه = {...}, carryover_نقره‌ای = {Left:2, Right:0} +``` + +### ۶.۵ تغییرات SP (v3 — بروزرسانی) + +| SP | تغییرات v2 | تغییرات اضافی v3 | +|----|------------|------------------| +| `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` | `@MaxBalancesPerLeg` و `@MaxNetworkLevel` هم **پارامتر** شوند (نه hardcoded) | +| `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` | Pool فقط از تعادل‌های همان PackageId | + +**sp_CalculateWeeklyBalances — تغییرات ساختاری:** + +```sql +-- قبل (v2 — فقط PackageId فیلتر): +CREATE PROCEDURE sp_CalculateWeeklyBalances + @WeekDefinitionId BIGINT, + @PackageId BIGINT, + @MaxBalancesPerLeg INT = 300, -- ← hardcoded! + @MaxNetworkLevel INT = 15 -- ← hardcoded! + +-- بعد (v3 — همه داینامیک): +CREATE PROCEDURE sp_CalculateWeeklyBalances + @WeekDefinitionId BIGINT, + @PackageId BIGINT, + @MaxBalancesPerLeg INT, -- ← از Package entity خوانده می‌شود + @MaxNetworkLevel INT, -- ← از Package entity خوانده می‌شود + @ForceRecalculate BIT = 0, + @RowCount INT OUTPUT +``` + +**StoredProcedureCommissionCalculationStrategy — تغییرات:** + +```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 + }); +``` + +### ۶.۶ تغییرات Service (v3) ```csharp // WeeklyCommissionCalculationService — Loop روی پکیج‌ها: @@ -293,11 +452,118 @@ var activePackages = await _context.Packages foreach (var package in activePackages) { - await strategy.CalculateWeeklyBalancesAsync(weekId, package.Id); + _logger.LogInformation( + "Calculating commission for package {Id}: {Title} " + + "(MaxBalances={Max}, MaxLevel={Level})", + package.Id, package.Title, + package.MaxBalancesPerLeg, package.MaxNetworkLevel); + + // محاسبه تعادل‌ها — هر پکیج با تنظیمات خودش + await strategy.CalculateWeeklyBalancesAsync( + weekId, package.Id, + package.MaxBalancesPerLeg, package.MaxNetworkLevel); + + // محاسبه Pool — هر پکیج جداگانه await strategy.CalculateWeeklyPoolAsync(weekId, package.Id); } ``` +### ۶.۷ گزارش‌دهی پورسانت per-package (v3 — جدید) + +> ⚠️ **فعلاً `package_id` در هیچ‌کدام از پیام‌های commission.proto وجود ندارد!** + +#### تغییرات Proto (commission.proto): + +```diff +message UserWeeklyBalanceModel { + // ... فیلدهای فعلی ... ++ int64 package_id = 17; ++ string package_title = 18; +} + +message UserCommissionPayoutModel { + // ... فیلدهای فعلی ... ++ int64 package_id = 16; ++ string package_title = 17; +} + +message CustomerCommissionPayoutModel { + // ... فیلدهای فعلی ... ++ int64 package_id = 10; ++ string package_title = 11; +} + +message CustomerWeeklyBalanceModel { + // ... فیلدهای فعلی ... ++ int64 package_id = 15; ++ string package_title = 16; +} + ++ // مدل خلاصه پورسانت per-package برای مشتری ++ message CustomerCommissionPackageSummary { ++ int64 package_id = 1; ++ string package_title = 2; ++ int32 downline_count = 3; // تعداد زیرمجموعه با این پکیج ++ int32 left_leg_members = 4; ++ int32 right_leg_members = 5; ++ int32 total_balances = 6; ++ int64 commission_earned = 7; // پورسانت کسب‌شده از این پکیج ++ string commission_formatted = 8; ++ } + +// Request فیلتر بر اساس پکیج +message GetAllWeeklyBalancesByFilterRequest { + // ... فیلدهای فعلی ... ++ Int64Value package_id = 7; // فیلتر اختیاری +} + +message GetUserCommissionPayoutsRequest { + // ... فیلدهای فعلی ... ++ Int64Value package_id = 7; // فیلتر اختیاری +} +``` + +#### FrontOffice — گزارش پورسانت per-package (صفحه جدید/بهبود): + +``` +┌─── پاداش‌های من — هفته ۱۰ ────────────────────────────────────────────┐ +│ │ +│ 📊 خلاصه بر اساس پکیج: │ +│ │ +│ ┌── پکیج پایه ─────────────────┐ ┌── پکیج نقره‌ای ───────────────┐ │ +│ │ زیرمجموعه: ۱۲ نفر │ │ زیرمجموعه: ۵ نفر │ │ +│ │ تیم اول: ۷ │ تیم دوم: ۵ │ │ تیم اول: ۳ │ تیم دوم: ۲ │ │ +│ │ تعادل: ۵ │ │ تعادل: ۲ │ │ +│ │ 💰 پاداش: ۱,۲۵۰,۰۰۰ تومان │ │ 💰 پاداش: ۱۸۰,۰۰۰ تومان │ │ +│ └───────────────────────────────┘ └──────────────────────────────┘ │ +│ │ +│ 📦 مجموع پاداش هفته: ۱,۴۳۰,۰۰۰ تومان │ +│ ├── از پکیج پایه: ۱,۲۵۰,۰۰۰ │ +│ └── از پکیج نقره‌ای: ۱۸۰,۰۰۰ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +#### BackOffice — گزارش ادمین با فیلتر پکیج: + +``` +┌─── گزارش تعادل‌ها — هفته ۱۰ ───────────────────────────────────────┐ +│ │ +│ فیلتر: [▼ پکیج: همه ▼] [کاربر: ___] [هفته: ▼ هفته ۱۰ ▼] [جستجو]│ +│ ├── همه │ +│ ├── پکیج پایه │ +│ └── پکیج نقره‌ای │ +│ │ +│ # │ کاربر │ پکیج │ تیم اول │ تیم دوم │ تعادل │ سهم استخر │ +│ ──┼──────────┼─────────┼─────────┼─────────┼───────┼────────────────│ +│ 1 │ علی │ پایه │ ۴۵ │ ۵۲ │ ۴۵ │ ۲,۲۵۰,۰۰۰ │ +│ 2 │ سارا │ نقره‌ای │ ۱۲ │ ۸ │ ۸ │ ۱۴۴,۰۰۰ │ +│ 3 │ رضا │ پایه │ ۳۰ │ ۲۵ │ ۲۵ │ ۱,۲۵۰,۰۰۰ │ +│ 4 │ رضا │ نقره‌ای │ ۲ │ ۰ │ ۰ │ ۰ │ +│ │ +│ خلاصه: پکیج پایه: ۷۰ تعادل | پکیج نقره‌ای: ۸ تعادل │ +└──────────────────────────────────────────────────────────────────────┘ +``` + --- ## ۷. Event-Driven Side Effects @@ -330,12 +596,16 @@ INSERT INTO "CMS"."Packages" ( "Title", "Description", "Price", "IsActive", "IsBasePackage", "SupportsDayaPurchase", "SupportsDirectPurchase", "ActivationFee", "DiscountMultiplier", "MagicWalletMultiplier", + "MaxBalancesPerLeg", "MaxNetworkLevel", + "MagicWalletMaxDeposit", "MagicWalletMaxCredit", "SortOrder", "ImagePath" ) VALUES ( 'پکیج پایه', 'پکیج اصلی باشگاه مشتریان کارا بازار سلامت', 56000000, true, true, true, true, 25200000, 2.0, 2.5, + 300, 15, + 1000000000, 2500000000, 2, '' ); @@ -344,12 +614,16 @@ INSERT INTO "CMS"."Packages" ( "Title", "Description", "Price", "IsActive", "IsBasePackage", "SupportsDayaPurchase", "SupportsDirectPurchase", "ActivationFee", "DiscountMultiplier", "MagicWalletMultiplier", + "MaxBalancesPerLeg", "MaxNetworkLevel", + "MagicWalletMaxDeposit", "MagicWalletMaxCredit", "SortOrder", "ImagePath" ) VALUES ( 'پکیج نقره‌ای', 'پکیج سطح نقره‌ای باشگاه مشتریان', 5600000, true, false, false, true, 2520000, 2.0, 2.5, + 30, 15, + 100000000, 250000000, 1, '' ); @@ -393,35 +667,43 @@ BEGIN UPDATE "CMS"."UserCommissionPayouts" SET "PackageId" = base_pkg_id WHERE "PackageId" IS NULL; + -- STEP 6: NetworkWeeklyBalance (v3 — جدید) + UPDATE "CMS"."NetworkWeeklyBalances" + SET "PackageId" = base_pkg_id WHERE "PackageId" IS NULL; + RAISE NOTICE 'Migration completed for PackageId=%', base_pkg_id; END $$; --- STEP 6: Verify — همه باید 0 باشند +-- STEP 7: 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; +SELECT 'Payouts', COUNT(*) FROM "CMS"."UserCommissionPayouts" WHERE "PackageId" IS NULL +UNION ALL +SELECT 'Balances', COUNT(*) FROM "CMS"."NetworkWeeklyBalances" WHERE "PackageId" IS NULL; ``` --- -## ۱۰. Impact Analysis — ۳۹ تغییر در ۶ لایه +## ۱۰. Impact Analysis — ۴۸+ تغییر در ۶ لایه (v3) -### ۱۰.۱ لایه Domain (۸ تغییر) +### ۱۰.۱ لایه 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 | 🔴 | +| # | فایل | نوع | شدت | v3? | +|---|------|-----|------|-----| +| D1 | `Package.cs` | اضافه **۱۱ فیلد** جدید (v2: ۷ + v3: ۴) | 🟡 | 🔄 | +| D2 | `PackageFeature.cs` | Entity جدید + EF Config | 🔴 | | +| D3 | `ClubMembership.cs` | اضافه `PackageId` | 🟡 | | +| D4 | `ClubMembershipCycle.cs` | اضافه `PackageId` | 🟡 | | +| D5 | `WeeklyCommissionPool.cs` | اضافه `PackageId` + Unique | 🔴 | | +| D6 | `UserCommissionPayout.cs` | اضافه `PackageId` + Unique(UserId,WeekId,PackageId) | 🟡 | 🔄 | +| D7 | `SystemConstants.cs` | حذف **۷ ثابت** (v2: ۵ + v3: ۲)، حفظ ۱۱ | 🟡 | 🔄 | +| D8 | EF Migration + Seed | schema + data migration | 🔴 | | +| **D9** | **`NetworkWeeklyBalance.cs`** | **اضافه `PackageId` + Unique(UserId,WeekId,PackageId)** | **🔴** | **🆕** | +| **D10** | **Data volume impact** | **رکوردهای تعادل ×N (تعداد پکیج)** | **🟡** | **🆕** | ### ۱۰.۲ لایه Application (۱۲ تغییر) @@ -440,44 +722,53 @@ SELECT 'Payouts', COUNT(*) FROM "CMS"."UserCommissionPayouts" WHERE "PackageId" | A11 | `UserOrderService` (EXIT Magic) | ریست PackagePurchaseMethod + Deactivate | 🔴 | | A12 | Package CRUD handlers | فیلدهای جدید + PackageFeature CRUD | 🟡 | -### ۱۰.۳ لایه Infrastructure (۵ تغییر) +### ۱۰.۳ لایه Infrastructure (۶ تغییر) -| # | فایل | نوع | شدت | -|---|------|-----|------| -| I1 | `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` | 🔴 | -| I2 | `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` | 🔴 | -| I3 | `WeeklyCommissionCalculationService` | Loop روی پکیج‌ها | 🟡 | -| I4 | `OrmCommissionCalculationStrategy` | فیلتر PackageId | 🔴 | -| I5 | `SpCommissionCalculationStrategy` | پاس دادن PackageId | 🟡 | +| # | فایل | نوع | شدت | v3? | +|---|------|-----|------|-----| +| I1 | `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` + **`@MaxBalancesPerLeg`** + **`@MaxNetworkLevel`** (حذف hardcode) | 🔴 | 🔄 | +| I2 | `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` + فیلتر تعادل‌های همان پکیج | 🔴 | 🔄 | +| I3 | `WeeklyCommissionCalculationService` | Loop روی پکیج‌ها + ارسال تنظیمات هر پکیج | 🟡 | 🔄 | +| I4 | `OrmCommissionCalculationStrategy` | فیلتر PackageId + خواندن MaxBalancesPerLeg/MaxNetworkLevel از Package | 🔴 | 🔄 | +| I5 | `SpCommissionCalculationStrategy` | پاس دادن PackageId + MaxBalancesPerLeg + MaxNetworkLevel | 🟡 | 🔄 | +| **I6** | **Carryover logic** | **Week-shifting per-package: carryover فقط رکوردهای همان PackageId** | **🔴** | **🆕** | -### ۱۰.۴ لایه Proto/gRPC (۴ تغییر) +### ۱۰.۴ لایه 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 | 🟡 | +| # | فایل | نوع | شدت | v3? | +|---|------|-----|------|-----| +| P1 | `package.proto` | فیلدهای جدید Package (۱۱ فیلد) | 🟡 | 🔄 | +| P2 | `clubmembership.proto` | `package_id` در request/response | 🟡 | | +| P3 | `commission.proto` | **`package_id` + `package_title`** در ۴ message: UserWeeklyBalance, UserCommissionPayout, CustomerCommissionPayout, CustomerWeeklyBalance | **🔴** | **🔄** | +| P4 | `PackageGrpcService.cs` | Generic purchase + CRUD | 🟡 | | +| **P5** | **`commission.proto`** | **Message جدید: `CustomerCommissionPackageSummary`** (خلاصه per-package) | **🟡** | **🆕** | +| **P6** | **`commission.proto`** | **فیلتر اختیاری `package_id` در Request‌های** GetWeeklyBalances + GetPayouts | **🟡** | **🆕** | -### ۱۰.۵ لایه FrontOffice (۶ تغییر) +### ۱۰.۵ لایه 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 | 🟡 | +| # | فایل | نوع | شدت | v3? | +|---|------|-----|------|-----| +| 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 | 🟡 | | +| **F7** | **`CommissionDashboardPage.razor`** | **خلاصه پاداش per-package (کارت‌های جداگانه هر پکیج)** | **🔴** | **🆕** | +| **F8** | **`WeeklyBalancePage.razor`** | **تعادل‌ها per-package (تیم اول/دوم بر اساس پکیج)** | **🟡** | **🆕** | -### ۱۰.۶ لایه BackOffice (۴ تغییر) +### ۱۰.۶ لایه BackOffice (۸ تغییر) -| # | فایل | نوع | شدت | -|---|------|-----|------| -| BO1 | `PackageCreateDialog.razor` | فیلدهای جدید | 🟡 | -| BO2 | `PackageEditDialog.razor` | فیلدهای جدید | 🟡 | -| BO3 | `PackageFeatureMatrixPage` — **جدید** | ماتریس پکیج×فیچر | 🔴 | -| BO4 | `ActivateClubDialog.razor` | dropdown انتخاب پکیج | 🟡 | +| # | فایل | نوع | شدت | v3? | +|---|------|-----|------|-----| +| BO1 | `PackageCreateDialog.razor` | فیلدهای جدید (**۱۱ فیلد** شامل MaxBalancesPerLeg, MaxNetworkLevel, MagicWallet limits) | 🟡 | 🔄 | +| BO2 | `PackageEditDialog.razor` | فیلدهای جدید + **Quick Access فیچرها** (checkbox فیچرها در همان فرم) | 🟡 | 🔄 | +| BO3 | `PackageFeatureMatrixPage` — **جدید** | ماتریس پکیج×فیچر | 🔴 | | +| BO4 | `ActivateClubDialog.razor` | dropdown انتخاب پکیج | 🟡 | | +| **BO5** | **`Dashboard.razor` (Commission)** | **فیلتر dropdown پکیج + خلاصه per-package** | **🟡** | **🆕** | +| **BO6** | **`WeeklyReportsPage.razor`** | **فیلتر پکیج + CSV export per-package** | **🟡** | **🆕** | +| **BO7** | **`BalancesReportPage.razor`** | **فیلتر پکیج + ستون پکیج در جدول تعادل‌ها** | **🟡** | **🆕** | +| **BO8** | **`UserPayoutsPage.razor`** | **ستون پکیج در لیست پرداخت‌ها + فیلتر** | **🟡** | **🆕** | --- @@ -494,18 +785,20 @@ SELECT 'Payouts', COUNT(*) FROM "CMS"."UserCommissionPayouts" WHERE "PackageId" ### فاز ۱ — زیرساخت (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‌ها | +| تسک | شرح | v3? | +|-----|----- |-----| +| **T1.1** | بروزرسانی `Package` entity (**۱۱ فیلد** جدید: v2 ۷ + v3 ۴ شامل MaxBalancesPerLeg, MaxNetworkLevel, MagicWalletMaxDeposit, MagicWalletMaxCredit) | 🔄 | +| **T1.2** | ایجاد `PackageFeature` entity + EF Config | | +| **T1.3** | اضافه `PackageId` به `ClubMembership` | | +| **T1.4** | اضافه `PackageId` به `ClubMembershipCycle` | | +| **T1.5** | اضافه `PackageId` به `WeeklyCommissionPool` + Unique(WeekId,PackageId) | | +| **T1.6** | اضافه `PackageId` به `UserCommissionPayout` + Unique(UserId,WeekId,PackageId) | 🔄 | +| **T1.7** | حذف **۷ ثابت** از `SystemConstants` (v2: ۵ + v3: MaxWeeklyBalancesPerLeg, MaxNetworkLevel) | 🔄 | +| **T1.8** | Database Migration + Seed Data (۲ پکیج + فیچرها) | | +| **T1.9** | Data Migration: کاربران فعلی → PackageId = پکیج پایه | | +| **T1.10** | بروزرسانی Proto‌ها (package + clubmembership + commission) | 🔄 | +| **T1.11** | **اضافه `PackageId` به `NetworkWeeklyBalance` + Unique(UserId,WeekId,PackageId)** | **🆕** | +| **T1.12** | **Data Migration: `NetworkWeeklyBalance` موجود → PackageId = پکیج پایه** | **🆕** | ### فاز ۲ — منطق کسب‌وکار ≈ ۴ روز @@ -522,37 +815,48 @@ SELECT 'Payouts', COUNT(*) FROM "CMS"."UserCommissionPayouts" WHERE "PackageId" | **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** | تست محاسبات با داده واقعی | +| تسک | شرح | v3? | +|-----|----- |-----| +| **T3.1** | بروزرسانی `sp_CalculateWeeklyBalances` — `@PackageId` + **`@MaxBalancesPerLeg`** + **`@MaxNetworkLevel`** (حذف hardcode ۳۰۰/۱۵) | 🔄 | +| **T3.2** | بروزرسانی `sp_CalculateWeeklyCommissionPool` — `@PackageId` + فیلتر تعادل‌های همان پکیج | 🔄 | +| **T3.3** | بروزرسانی `WeeklyCommissionCalculationService` — Loop روی پکیج‌ها + ارسال تنظیمات | 🔄 | +| **T3.4** | بروزرسانی `OrmCommissionCalculationStrategy` — فیلتر PackageId + خواندن Max از Package | 🔄 | +| **T3.5** | **بروزرسانی `SpCommissionCalculationStrategy` — پاس دادن PackageId + MaxBalancesPerLeg + MaxNetworkLevel** | **🆕** | +| **T3.6** | **Carryover per-package: week-shifting فقط رکوردهای همان PackageId** | **🆕** | +| **T3.7** | تست محاسبات با داده واقعی (۲ پکیج موازی، carryover مجزا) | 🔄 | -### فاز ۴ — UI ≈ ۴ روز +### فاز ۴ — 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 | +| تسک | شرح | v3? | +|-----|----- |-----| +| **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 | | +| **T4.8** | **FrontOffice: CommissionDashboard — کارت‌های خلاصه per-package + مجموع پاداش** | **🆕** | +| **T4.9** | **FrontOffice: WeeklyBalance — تعادل تیم اول/دوم per-package** | **🆕** | +| **T4.10** | **BackOffice: Commission Dashboard — فیلتر dropdown پکیج** | **🆕** | +| **T4.11** | **BackOffice: Weekly Reports + CSV export — ستون پکیج + فیلتر** | **🆕** | +| **T4.12** | **BackOffice: BalancesReport + UserPayouts — ستون پکیج + فیلتر** | **🆕** | +| **T4.13** | **BackOffice: Package Create/Edit — Quick Access فیچرها (checkbox inline)** | **🆕** | -### فاز ۵ — تست و استقرار ≈ ۲ روز +### فاز ۵ — تست و استقرار ≈ ۳ روز -| تسک | شرح | -|-----|------| -| **T5.1** | تست خرید هر پکیج | -| **T5.2** | تست re-purchase بعد تکمیل چرخه | -| **T5.3** | تست Commission Pool جداگانه | -| **T5.4** | تست Migration | -| **T5.5** | Deploy staging → production | +| تسک | شرح | v3? | +|-----|----- |-----| +| **T5.1** | تست خرید هر پکیج | | +| **T5.2** | تست re-purchase بعد تکمیل چرخه | | +| **T5.3** | تست Commission Pool جداگانه | | +| **T5.4** | تست Migration | | +| **T5.5** | **تست تعادل per-package: carryover مجزا، MaxBalancesPerLeg متفاوت** | **🆕** | +| **T5.6** | **تست گزارش FO per-package: مشتری breakdown صحیح می‌بیند** | **🆕** | +| **T5.7** | **تست گزارش BO per-package: فیلتر پکیج + CSV** | **🆕** | +| **T5.8** | Deploy staging → production | --- @@ -568,16 +872,17 @@ SELECT 'Payouts', COUNT(*) FROM "CMS"."UserCommissionPayouts" WHERE "PackageId" --- -## ۱۳. تخمین زمانی +## ۱۳. تخمین زمانی (v3) -| فاز | مدت | وابستگی | -|-----|------|---------| -| فاز ۰ — فیکس باگ‌ها | ۱ روز | — | -| فاز ۱ — زیرساخت | ۳ روز | فاز ۰ | -| فاز ۲ — منطق | ۴ روز | فاز ۱ | -| فاز ۳ — پورسانت | ۳ روز | فاز ۱ | -| فاز ۴ — UI | ۴ روز | فاز ۲ | -| فاز ۵ — تست | ۲ روز | فاز ۳, ۴ | -| **مجموع** | **~۱۷ روز** | | +| فاز | مدت | وابستگی | v3 تغییر | +|-----|------|---------|----------| +| فاز ۰ — فیکس باگ‌ها | ۱ روز | — | | +| فاز ۱ — زیرساخت | ۴ روز | فاز ۰ | +۱ روز (NetworkWeeklyBalance + Unique) | +| فاز ۲ — منطق | ۴ روز | فاز ۱ | | +| فاز ۳ — پورسانت | **۴ روز** | فاز ۱ | **+۱ روز** (SP params + carryover per-package) | +| فاز ۴ — UI | **۵ روز** | فاز ۲ | **+۱ روز** (FO/BO گزارش per-package) | +| فاز ۵ — تست | **۳ روز** | فاز ۳, ۴ | **+۱ روز** (تست carryover + گزارش) | +| **مجموع** | **~۲۱ روز** | | | -> فاز ۲ و ۳ **موازی** → مسیر بحرانی: ۰→۱→۲→۴→۵ = **~۱۴ روز** +> فاز ۲ و ۳ **موازی** → مسیر بحرانی: ۰→۱→۲→۴→۵ = **~۱۷ روز** +> نسبت به v2 (**۱۴ روز**): +۳ روز بخاطر per-package commission + گزارش‌دهی + carryover diff --git a/roadmap/PACKAGE-TRANSFORMATION-TASKS.md b/roadmap/PACKAGE-TRANSFORMATION-TASKS.md index 59ba1cb..7ef8e21 100644 --- a/roadmap/PACKAGE-TRANSFORMATION-TASKS.md +++ b/roadmap/PACKAGE-TRANSFORMATION-TASKS.md @@ -2,8 +2,10 @@ > **وضعیت:** تایید‌شده — آماده شروع > **تاریخ:** ۱۴۰۴/۱۲/۰۶ -> **پیش‌نیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) v2 -> **هدف:** شکستن ۳۹ تغییر به تسک‌های اتمیک با ترتیب اجرا و وابستگی‌ها +> **پیش‌نیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) **v3** (تکمیل پورسانت per-package) +> **هدف:** شکستن **۴۸+ تغییر** به تسک‌های اتمیک با ترتیب اجرا و وابستگی‌ها + +> ⚠️ **تغییرات v3:** پورسانت per-package، carryover مجزا، SP parameters داینامیک، گزارش‌دهی FO/BO per-package --- @@ -11,14 +13,13 @@ ``` مرحله ۰: فیکس باگ فوری (۱ روز) - └─→ مرحله ۱: زیرساخت Domain + DB (۳ روز) + └─→ مرحله ۱: زیرساخت Domain + DB (۴ روز) ← +۱ روز (v3: NetworkWeeklyBalance) ├─→ مرحله ۲: منطق کسب‌وکار (۴ روز) ← موازی - │ └─→ مرحله ۴: FrontOffice UI (۳ روز) - └─→ مرحله ۳: پورسانت (۳ روز) ← موازی - └─→ مرحله ۵: BackOffice UI (۳ روز) - └─→ مرحله ۶: تست + استقرار (۲ روز) + │ └─→ مرحله ۴: UI (۵ روز) ← +۲ (v3: FO/BO گزارش per-package) + └─→ مرحله ۳: پورسانت (۴ روز) ← +۱ (v3: SP params + carryover) + └─→ مرحله ۵: تست + استقرار (۳ روز) ← +۱ -مسیر بحرانی: ۰→۱→۲→۴→۶ = ~۱۳ روز +مسیر بحرانی: ۰→۱→۲→۴→۵ = ~۱۷ روز (v2: ۱۳ روز) ``` --- @@ -69,7 +70,7 @@ > ⏱️ ۳ روز | وابستگی: مرحله ۰ | ریسک: متوسط (migration) -### T1.1 — بروزرسانی Package Entity +### T1.1 — بروزرسانی Package Entity (۱۱ فیلد جدید — v3) **فایل:** `CMS/src/CMSMicroservice.Domain/Entities/Package.cs` @@ -82,6 +83,13 @@ + 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; } ``` @@ -106,14 +114,15 @@ public class PackageFeature : BaseAuditableEntity ### T1.3-T1.6 — اضافه PackageId به entityها -| Entity | فیلد | Required? | توضیح | -|--------|------|-----------|-------| -| ClubMembership | `long? PackageId` | nullable (بعد migration → required) | آخرین پکیج | -| ClubMembershipCycle | `long PackageId` | required | پکیج این چرخه | -| WeeklyCommissionPool | `long PackageId` | required + Unique(WeekDefId, PkgId) | Pool هر پکیج | -| UserCommissionPayout | `long? PackageId` | nullable | ردیابی | +| 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 +### T1.7 — حذف SystemConstants (v3: ۷ ثابت) **فایل:** `CMS/src/CMSMicroservice.Domain/Common/SystemConstants.cs` @@ -122,10 +131,14 @@ public class PackageFeature : BaseAuditableEntity - 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; // اگر وجود داشت +- public const decimal MagicWalletMultiplier = 2.5m; +- // === v3: انتقال به Package entity === +- public const int CommissionMaxWeeklyBalancesPerLeg = 300; +- public const int CommissionMaxNetworkLevel = 15; ``` -> ⚠️ **قبل از حذف:** grep تمام مصرف‌کننده‌ها → جایگزین با `Package.Property` +> ⚠️ **قبل از حذف:** grep تمام مصرف‌کننده‌ها → جایگزین با `Package.Property` +> ⚠️ **v3:** `CommissionMaxWeeklyBalancesPerLeg` و `CommissionMaxNetworkLevel` هم باید per-package شوند ### T1.8 — Database Migration @@ -151,7 +164,12 @@ UPDATE "CMS"."Packages" SET "SupportsDirectPurchase" = true, "ActivationFee" = 25200000, "DiscountMultiplier" = 2.0, - "MagicWalletMultiplier" = 2.5 + "MagicWalletMultiplier" = 2.5, + -- v3: تنظیمات پورسانت + "MaxBalancesPerLeg" = 300, + "MaxNetworkLevel" = 15, + "MagicWalletMaxDeposit" = 1000000000, + "MagicWalletMaxCredit" = 2500000000 WHERE "Id" = 4; -- 2. Link existing data to base package @@ -159,18 +177,50 @@ 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 -INSERT INTO "CMS"."Packages" (...) VALUES ('پکیج نقره‌ای', 5600000, ...); +-- 3. Seed Silver package (شامل فیلدهای v3) +INSERT INTO "CMS"."Packages" (..., "MaxBalancesPerLeg", "MaxNetworkLevel", + "MagicWalletMaxDeposit", "MagicWalletMaxCredit", ...) +VALUES ('پکیج نقره‌ای', 5600000, ..., 30, 15, 100000000, 250000000, ...); ``` ### T1.10 — بروزرسانی Protoها -| Proto File | تغییر | -|-----------|-------| -| package.proto | فیلدهای جدید Package message | -| clubmembership.proto | package_id در request/response | -| commission.proto | package_id در pool/payout messages | +| 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; +``` --- @@ -256,29 +306,33 @@ INSERT INTO "CMS"."Packages" (...) VALUES ('پکیج نقره‌ای', 5600000, ## مرحله ۳ — محاسبه پورسانت (موازی با مرحله ۲) -> ⏱️ ۳ روز | وابستگی: مرحله ۱ | ریسک: بحرانی (مالی) +> ⏱️ **۴ روز** (v3: +۱) | وابستگی: مرحله ۱ | ریسک: بحرانی (مالی) -### T3.1-T3.2 — SPs + PackageId +### T3.1-T3.2 — SPs + PackageId + پارامترهای داینامیک (🔄 v3) ```sql --- sp_CalculateWeeklyBalances: +-- sp_CalculateWeeklyBalances — v3: حذف hardcode ALTER PROCEDURE sp_CalculateWeeklyBalances @WeekDefinitionId BIGINT, - @PackageId BIGINT -- ← جدید + @PackageId BIGINT, + @MaxBalancesPerLeg INT, -- v3: از Package entity (نه ۳۰۰ hardcode!) + @MaxNetworkLevel INT -- v3: از Package entity (نه ۱۵ hardcode!) AS BEGIN - -- فیلتر بالانس‌ها فقط کاربرانی که این پکیج را دارند - INSERT INTO "CMS"."WeeklyBalances" (...) - SELECT ... + -- فیلتر: فقط کاربرانی که این پکیج را دارند + -- 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 - AND w."WalletMode" = 0; -- Normal only + WHERE m."PackageId" = @PackageId + AND m."IsActive" = true; END; ``` -### T3.3 — Loop Service +### T3.3 — Loop Service (🔄 v3: ارسال تنظیمات پکیج) ```csharp // WeeklyCommissionCalculationService.cs @@ -288,14 +342,71 @@ var activePackages = await _context.Packages foreach (var package in activePackages) { - _logger.LogInformation("Calculating commission for package {Id}: {Title}", - package.Id, package.Title); + _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.CalculateWeeklyBalancesAsync(weekId, package.Id); 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 باید:** @@ -306,9 +417,9 @@ foreach (var package in activePackages) --- -## مرحله ۴ — FrontOffice UI +## مرحله ۴ — UI (FrontOffice + BackOffice) -> ⏱️ ۳ روز | وابستگی: مرحله ۲ | ریسک: متوسط +> ⏱️ **۵ روز** (v3: +۲) | وابستگی: مرحله ۲ + ۳ | ریسک: متوسط ### T4.1 — کاشی‌های پکیج داینامیک @@ -355,52 +466,118 @@ else } ``` ---- +### T4.8 — FrontOffice: CommissionDashboard per-package (🆕 v3) -## مرحله ۵ — BackOffice UI +**فایل:** `FrontOffice/src/.../Pages/Commission/CommissionDashboardPage.razor` -> ⏱️ ۳ روز | وابستگی: مرحله ۳ | ریسک: کم +```razor +@* v3: کارت‌های خلاصه بر اساس پکیج *@ + + @foreach (var summary in _packageSummaries) + { + + + @summary.PackageTitle + +

زیرمجموعه: @summary.DownlineCount نفر

+

تیم اول: @summary.LeftLegMembers | تیم دوم: @summary.RightLegMembers

+

تعادل: @summary.TotalBalances

+

💰 پاداش: @summary.CommissionFormatted

+
+
+
+ } +
+📦 مجموع پاداش هفته: @_totalCommission +``` -_(تسک‌ها در PACKAGE-TRANSFORMATION-UX.md بخش ۳ مستند شده)_ +### T4.9 — FrontOffice: WeeklyBalance per-package (🆕 v3) + +> تعادل تیم اول/دوم به تفکیک هر پکیج نمایش داده شود + +### T4.10-T4.12 — BackOffice: گزارش‌های پورسانت per-package (🆕 v3) + +**تغییرات مشترک:** +- اضافه dropdown فیلتر پکیج (همه / پایه / نقره‌ای / ...) +- اضافه ستون پکیج در جدول‌ها +- CSV export شامل ستون پکیج + +| صفحه | تغییر | +|------|-------| +| Commission Dashboard | فیلتر + خلاصه per-package | +| Weekly Reports | فیلتر + CSV per-package | +| Balances Report | فیلتر + ستون پکیج | +| User Payouts | ستون پکیج + فیلتر | + +### 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 جداگانه هر پکیج -- [ ] Data Migration — PackageId در رکوردهای قبلی +- [ ] تعادل 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: ماتریس فیچر BackOffice +- [ ] UI: ماتریس فیچر + Quick Access BackOffice - [ ] Rollback: بدون data loss --- -## 📅 تقویم پیشنهادی +## 📅 تقویم پیشنهادی (v3) | هفته | روز | تسک | |------|-----|------| | هفته ۱ | روز ۱ | مرحله ۰: فیکس ۴ باگ | -| | روز ۲-۳ | مرحله ۱: Package entity + PackageFeature | -| | روز ۴ | مرحله ۱: FKها + Migration | -| هفته ۲ | روز ۵-۶ | مرحله ۲: Generic handlers + re-purchase | -| | روز ۵-۶ | مرحله ۳: SP + Loop (موازی) | -| | روز ۷-۸ | مرحله ۲: Guards + JWT + Manual | -| هفته ۳ | روز ۹-۱۰ | مرحله ۴: FrontOffice UI | -| | روز ۱۱ | مرحله ۵: BackOffice UI | -| | روز ۱۲-۱۳ | مرحله ۶: تست + deploy | +| | روز ۲-۳ | مرحله ۱: 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 | --- @@ -408,11 +585,11 @@ _(تسک‌ها در PACKAGE-TRANSFORMATION-UX.md بخش ۳ مستند شده)_ | مستند | محتوا | |-------|-------| -| [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی فنی — ۳۹ تغییر + باگ‌ها | +| [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 | --- -*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶* +*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶ — v3 (پورسانت per-package + گزارش‌دهی + carryover)*