docs: BIZ-PACKAGE-BASED-SYSTEM v3 — per-package commission deep analysis

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)
This commit is contained in:
masoodafar-web
2026-02-24 23:48:09 +03:30
parent 01244f426e
commit 33d5ae9305
2 changed files with 650 additions and 168 deletions
+411 -106
View File
@@ -2,8 +2,8 @@
> **وضعیت:** تایید‌شده — آماده پیاده‌سازی > **وضعیت:** تایید‌شده — آماده پیاده‌سازی
> **تاریخ بروزرسانی:** ۶ اسفند ۱۴۰۴ > **تاریخ بروزرسانی:** ۶ اسفند ۱۴۰۴
> **نسخه:** v2 (بازنویسی کامل بعد از تحلیل عمیق کدبیس) > **نسخه:** v3 (تکمیل تحلیل پورسانت per-package + گزارش‌دهی + SystemConstants کامل)
> **تاثیرگذاری:** زیاد — ۳۹ فایل در ۶ لایه > **تاثیرگذاری:** زیاد — ۵۵+ فایل در ۶ لایه
--- ---
@@ -30,6 +30,13 @@
| Q9 | MagicWallet Multiplier | ✅ **داینامیک** به‌ازای هر پکیج (فعلاً همه ×2.5) | | Q9 | MagicWallet Multiplier | ✅ **داینامیک** به‌ازای هر پکیج (فعلاً همه ×2.5) |
| Q10 | کاربران دایا | ✅ **پکیج پایه** گرفتن — "طلایی" اشتباه نام‌گذاری بوده | | Q10 | کاربران دایا | ✅ **پکیج پایه** گرفتن — "طلایی" اشتباه نام‌گذاری بوده |
| Q11 | فیچرها | ✅ **داینامیک** — ادمین مدیریت می‌کند | | 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 DiscountMultiplier { get; set; } = 2.0m; // ضریب شارژ DiscountBalance
public decimal MagicWalletMultiplier { get; set; } = 2.5m; // ضریب کیف‌پول جادویی 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 === // === Navigation ===
public virtual ICollection<PackageFeature> PackageFeatures { get; set; } public virtual ICollection<PackageFeature> PackageFeatures { get; set; }
public virtual ICollection<UserPackagePurchase> Purchases { get; set; } public virtual ICollection<UserPackagePurchase> Purchases { get; set; }
@@ -167,8 +182,14 @@ public class PackageFeature : BaseAuditableEntity
| `ClubMembershipCycle` | `long PackageId` + FK | پکیج این چرخه | | `ClubMembershipCycle` | `long PackageId` + FK | پکیج این چرخه |
| `WeeklyCommissionPool` | `long PackageId` + FK | Pool جداگانه هر پکیج | | `WeeklyCommissionPool` | `long PackageId` + FK | Pool جداگانه هر پکیج |
| `UserCommissionPayout` | `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 ### ۴.۴ حذف/تغییر SystemConstants
@@ -179,9 +200,19 @@ public class PackageFeature : BaseAuditableEntity
| `BasePackageAmount` | ❌ حذف | `Package.Price` | | `BasePackageAmount` | ❌ حذف | `Package.Price` |
| `DayaLoanAmount` | ❌ حذف | `Package.Price` (base) | | `DayaLoanAmount` | ❌ حذف | `Package.Price` (base) |
| `MagicWalletMultiplier` | ❌ حذف | `Package.MagicWalletMultiplier` | | `MagicWalletMultiplier` | ❌ حذف | `Package.MagicWalletMultiplier` |
| `CommissionMaxWeeklyBalancesPerLeg` | ✅ حفظ | عمومی | | `CommissionMaxWeeklyBalancesPerLeg` | **حذف** ← تغییر از v2 | `Package.MaxBalancesPerLeg` (نقره‌ای=۳۰، پایه=۳۰۰) |
| `CommissionMaxNetworkLevel` | ✅ حفظ | عمومی | | `CommissionMaxNetworkLevel` | **حذف** ← تغییر از v2 | `Package.MaxNetworkLevel` (قابل تنظیم ادمین) |
| `MagicWalletMaxDeposit` | ❌ **حذف** ← جدید v3 | `Package.MagicWalletMaxDeposit` (سقف شارژ) |
| `MagicWalletMaxCredit` | ❌ **حذف** ← جدید v3 | `Package.MagicWalletMaxCredit` (سقف اعتبار) |
| `ShopVAT` | ✅ حفظ | عمومی | | `ShopVAT` | ✅ حفظ | عمومی |
| `CommissionCalculationStrategy` | ✅ حفظ | عمومی (ORM or SP) |
| `AllowDeletingParentsWithChildren` | ✅ حفظ | عمومی |
| `MaxDirectChildrenPerLeg` | ✅ حفظ | عمومی |
| `IsCommissionWithdrawalEnabled` | ✅ حفظ | عمومی |
| `CommissionMinWithdrawalAmount` | ✅ حفظ | عمومی |
| `IsMaintenanceMode` | ✅ حفظ | عمومی |
| `IsAuditLogEnabled` | ✅ حفظ | عمومی |
| `IsVATEnabled` | ✅ حفظ | عمومی |
### ۴.۵ فرمول مالی ### ۴.۵ فرمول مالی
@@ -276,14 +307,142 @@ Commission:
Reza → پاداش از Pool_پایه Reza → پاداش از Pool_پایه
``` ```
### ۶.۳ تغییرات SP ### ۶.۳ فلوی کامل چرخه خرید مجدد و تاثیر بر پورسانت (v3)
| SP | تغییر | ```
|----|-------| چرخه ۱ — کاربر "علی" پکیج پایه می‌خرد (۵۶M):
| `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` — فیلتر کاربران بر اساس PackageId | ────────────────────────────────────────────────
| `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` — Pool مخصوص آن پکیج | ✅ 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 ```csharp
// WeeklyCommissionCalculationService — Loop روی پکیج‌ها: // WeeklyCommissionCalculationService — Loop روی پکیج‌ها:
@@ -293,11 +452,118 @@ var activePackages = await _context.Packages
foreach (var package in activePackages) 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); 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 ## ۷. Event-Driven Side Effects
@@ -330,12 +596,16 @@ INSERT INTO "CMS"."Packages" (
"Title", "Description", "Price", "IsActive", "IsBasePackage", "Title", "Description", "Price", "IsActive", "IsBasePackage",
"SupportsDayaPurchase", "SupportsDirectPurchase", "SupportsDayaPurchase", "SupportsDirectPurchase",
"ActivationFee", "DiscountMultiplier", "MagicWalletMultiplier", "ActivationFee", "DiscountMultiplier", "MagicWalletMultiplier",
"MaxBalancesPerLeg", "MaxNetworkLevel",
"MagicWalletMaxDeposit", "MagicWalletMaxCredit",
"SortOrder", "ImagePath" "SortOrder", "ImagePath"
) VALUES ( ) VALUES (
'پکیج پایه', 'پکیج اصلی باشگاه مشتریان کارا بازار سلامت', 'پکیج پایه', 'پکیج اصلی باشگاه مشتریان کارا بازار سلامت',
56000000, true, true, 56000000, true, true,
true, true, true, true,
25200000, 2.0, 2.5, 25200000, 2.0, 2.5,
300, 15,
1000000000, 2500000000,
2, '' 2, ''
); );
@@ -344,12 +614,16 @@ INSERT INTO "CMS"."Packages" (
"Title", "Description", "Price", "IsActive", "IsBasePackage", "Title", "Description", "Price", "IsActive", "IsBasePackage",
"SupportsDayaPurchase", "SupportsDirectPurchase", "SupportsDayaPurchase", "SupportsDirectPurchase",
"ActivationFee", "DiscountMultiplier", "MagicWalletMultiplier", "ActivationFee", "DiscountMultiplier", "MagicWalletMultiplier",
"MaxBalancesPerLeg", "MaxNetworkLevel",
"MagicWalletMaxDeposit", "MagicWalletMaxCredit",
"SortOrder", "ImagePath" "SortOrder", "ImagePath"
) VALUES ( ) VALUES (
'پکیج نقره‌ای', 'پکیج سطح نقره‌ای باشگاه مشتریان', 'پکیج نقره‌ای', 'پکیج سطح نقره‌ای باشگاه مشتریان',
5600000, true, false, 5600000, true, false,
false, true, false, true,
2520000, 2.0, 2.5, 2520000, 2.0, 2.5,
30, 15,
100000000, 250000000,
1, '' 1, ''
); );
@@ -393,35 +667,43 @@ BEGIN
UPDATE "CMS"."UserCommissionPayouts" UPDATE "CMS"."UserCommissionPayouts"
SET "PackageId" = base_pkg_id WHERE "PackageId" IS NULL; 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; RAISE NOTICE 'Migration completed for PackageId=%', base_pkg_id;
END $$; END $$;
-- STEP 6: Verify — همه باید 0 باشند -- STEP 7: Verify — همه باید 0 باشند
SELECT 'ClubMemberships' AS tbl, COUNT(*) FROM "CMS"."ClubMemberships" WHERE "PackageId" IS NULL SELECT 'ClubMemberships' AS tbl, COUNT(*) FROM "CMS"."ClubMemberships" WHERE "PackageId" IS NULL
UNION ALL UNION ALL
SELECT 'Cycles', COUNT(*) FROM "CMS"."ClubMembershipCycles" WHERE "PackageId" IS NULL SELECT 'Cycles', COUNT(*) FROM "CMS"."ClubMembershipCycles" WHERE "PackageId" IS NULL
UNION ALL UNION ALL
SELECT 'Pools', COUNT(*) FROM "CMS"."WeeklyCommissionPools" WHERE "PackageId" IS NULL SELECT 'Pools', COUNT(*) FROM "CMS"."WeeklyCommissionPools" WHERE "PackageId" IS NULL
UNION ALL 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 (۱۰ تغییر)
| # | فایل | نوع | شدت | | # | فایل | نوع | شدت | v3? |
|---|------|-----|------| |---|------|-----|------|-----|
| D1 | `Package.cs` | اضافه ۷ فیلد جدید | 🟡 | | D1 | `Package.cs` | اضافه **۱۱ فیلد** جدید (v2: ۷ + v3: ۴) | 🟡 | 🔄 |
| D2 | `PackageFeature.cs` | Entity جدید + EF Config | 🔴 | | D2 | `PackageFeature.cs` | Entity جدید + EF Config | 🔴 | |
| D3 | `ClubMembership.cs` | اضافه `PackageId` | 🟡 | | D3 | `ClubMembership.cs` | اضافه `PackageId` | 🟡 | |
| D4 | `ClubMembershipCycle.cs` | اضافه `PackageId` | 🟡 | | D4 | `ClubMembershipCycle.cs` | اضافه `PackageId` | 🟡 | |
| D5 | `WeeklyCommissionPool.cs` | اضافه `PackageId` + Unique | 🔴 | | D5 | `WeeklyCommissionPool.cs` | اضافه `PackageId` + Unique | 🔴 | |
| D6 | `UserCommissionPayout.cs` | اضافه `PackageId` | 🟡 | | D6 | `UserCommissionPayout.cs` | اضافه `PackageId` + Unique(UserId,WeekId,PackageId) | 🟡 | 🔄 |
| D7 | `SystemConstants.cs` | حذف ۵ ثابت، حفظ بقیه | 🟡 | | D7 | `SystemConstants.cs` | حذف **۷ ثابت** (v2: ۵ + v3: ۲)، حفظ ۱۱ | 🟡 | 🔄 |
| D8 | EF Migration + Seed | schema + data migration | 🔴 | | D8 | EF Migration + Seed | schema + data migration | 🔴 | |
| **D9** | **`NetworkWeeklyBalance.cs`** | **اضافه `PackageId` + Unique(UserId,WeekId,PackageId)** | **🔴** | **🆕** |
| **D10** | **Data volume impact** | **رکوردهای تعادل ×N (تعداد پکیج)** | **🟡** | **🆕** |
### ۱۰.۲ لایه Application (۱۲ تغییر) ### ۱۰.۲ لایه Application (۱۲ تغییر)
@@ -440,44 +722,53 @@ SELECT 'Payouts', COUNT(*) FROM "CMS"."UserCommissionPayouts" WHERE "PackageId"
| A11 | `UserOrderService` (EXIT Magic) | ریست PackagePurchaseMethod + Deactivate | 🔴 | | A11 | `UserOrderService` (EXIT Magic) | ریست PackagePurchaseMethod + Deactivate | 🔴 |
| A12 | Package CRUD handlers | فیلدهای جدید + PackageFeature CRUD | 🟡 | | A12 | Package CRUD handlers | فیلدهای جدید + PackageFeature CRUD | 🟡 |
### ۱۰.۳ لایه Infrastructure (۵ تغییر) ### ۱۰.۳ لایه Infrastructure (۶ تغییر)
| # | فایل | نوع | شدت | | # | فایل | نوع | شدت | v3? |
|---|------|-----|------| |---|------|-----|------|-----|
| I1 | `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` | 🔴 | | I1 | `sp_CalculateWeeklyBalances` | پارامتر `@PackageId` + **`@MaxBalancesPerLeg`** + **`@MaxNetworkLevel`** (حذف hardcode) | 🔴 | 🔄 |
| I2 | `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` | 🔴 | | I2 | `sp_CalculateWeeklyCommissionPool` | پارامتر `@PackageId` + فیلتر تعادل‌های همان پکیج | 🔴 | 🔄 |
| I3 | `WeeklyCommissionCalculationService` | Loop روی پکیج‌ها | 🟡 | | I3 | `WeeklyCommissionCalculationService` | Loop روی پکیج‌ها + ارسال تنظیمات هر پکیج | 🟡 | 🔄 |
| I4 | `OrmCommissionCalculationStrategy` | فیلتر PackageId | 🔴 | | I4 | `OrmCommissionCalculationStrategy` | فیلتر PackageId + خواندن MaxBalancesPerLeg/MaxNetworkLevel از Package | 🔴 | 🔄 |
| I5 | `SpCommissionCalculationStrategy` | پاس دادن PackageId | 🟡 | | I5 | `SpCommissionCalculationStrategy` | پاس دادن PackageId + MaxBalancesPerLeg + MaxNetworkLevel | 🟡 | 🔄 |
| **I6** | **Carryover logic** | **Week-shifting per-package: carryover فقط رکوردهای همان PackageId** | **🔴** | **🆕** |
### ۱۰.۴ لایه Proto/gRPC (۴ تغییر) ### ۱۰.۴ لایه Proto/gRPC (۶ تغییر)
| # | فایل | نوع | شدت | | # | فایل | نوع | شدت | v3? |
|---|------|-----|------| |---|------|-----|------|-----|
| P1 | `package.proto` | فیلدهای جدید Package | 🟡 | | P1 | `package.proto` | فیلدهای جدید Package (۱۱ فیلد) | 🟡 | 🔄 |
| P2 | `clubmembership.proto` | `package_id` در request/response | 🟡 | | P2 | `clubmembership.proto` | `package_id` در request/response | 🟡 | |
| P3 | `commission.proto` | `package_id` در pool/payout | 🟡 | | P3 | `commission.proto` | **`package_id` + `package_title`** در ۴ message: UserWeeklyBalance, UserCommissionPayout, CustomerCommissionPayout, CustomerWeeklyBalance | **🔴** | **🔄** |
| P4 | `PackageGrpcService.cs` | Generic purchase + CRUD | 🟡 | | P4 | `PackageGrpcService.cs` | Generic purchase + CRUD | 🟡 | |
| **P5** | **`commission.proto`** | **Message جدید: `CustomerCommissionPackageSummary`** (خلاصه per-package) | **🟡** | **🆕** |
| **P6** | **`commission.proto`** | **فیلتر اختیاری `package_id` در Request‌های** GetWeeklyBalances + GetPayouts | **🟡** | **🆕** |
### ۱۰.۵ لایه FrontOffice (۶ تغییر) ### ۱۰.۵ لایه FrontOffice (۸ تغییر)
| # | فایل | نوع | شدت | | # | فایل | نوع | شدت | v3? |
|---|------|-----|------| |---|------|-----|------|-----|
| F1 | `Packages.razor` | کاشی‌های پکیج از API | 🔴 | | F1 | `Packages.razor` | کاشی‌های پکیج از API | 🔴 | |
| F2 | `PackageDetail.razor` | فیچرها از PackageFeature | 🟡 | | F2 | `PackageDetail.razor` | فیچرها از PackageFeature | 🟡 | |
| F3 | `ActivationSection.razor` | حذف hardcoded 56M | 🟡 | | F3 | `ActivationSection.razor` | حذف hardcoded 56M | 🟡 | |
| F4 | `ClubMembershipContractDialog.razor` | متن قرارداد داینامیک | 🟡 | | F4 | `ClubMembershipContractDialog.razor` | متن قرارداد داینامیک | 🟡 | |
| F5 | `MyPackages.razor` | نمایش نوع پکیج + re-purchase | 🟡 | | F5 | `MyPackages.razor` | نمایش نوع پکیج + re-purchase | 🟡 | |
| F6 | `PackageService.cs` | فیکس stub GetPurchaseHistory | 🟡 | | F6 | `PackageService.cs` | فیکس stub GetPurchaseHistory | 🟡 | |
| **F7** | **`CommissionDashboardPage.razor`** | **خلاصه پاداش per-package (کارت‌های جداگانه هر پکیج)** | **🔴** | **🆕** |
| **F8** | **`WeeklyBalancePage.razor`** | **تعادل‌ها per-package (تیم اول/دوم بر اساس پکیج)** | **🟡** | **🆕** |
### ۱۰.۶ لایه BackOffice (۴ تغییر) ### ۱۰.۶ لایه BackOffice (۸ تغییر)
| # | فایل | نوع | شدت | | # | فایل | نوع | شدت | v3? |
|---|------|-----|------| |---|------|-----|------|-----|
| BO1 | `PackageCreateDialog.razor` | فیلدهای جدید | 🟡 | | BO1 | `PackageCreateDialog.razor` | فیلدهای جدید (**۱۱ فیلد** شامل MaxBalancesPerLeg, MaxNetworkLevel, MagicWallet limits) | 🟡 | 🔄 |
| BO2 | `PackageEditDialog.razor` | فیلدهای جدید | 🟡 | | BO2 | `PackageEditDialog.razor` | فیلدهای جدید + **Quick Access فیچرها** (checkbox فیچرها در همان فرم) | 🟡 | 🔄 |
| BO3 | `PackageFeatureMatrixPage`**جدید** | ماتریس پکیج×فیچر | 🔴 | | BO3 | `PackageFeatureMatrixPage`**جدید** | ماتریس پکیج×فیچر | 🔴 | |
| BO4 | `ActivateClubDialog.razor` | dropdown انتخاب پکیج | 🟡 | | 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) ≈ ۳ روز ### فاز ۱ — زیرساخت (Domain + DB) ≈ ۳ روز
| تسک | شرح | | تسک | شرح | v3? |
|-----|------| |-----|----- |-----|
| **T1.1** | بروزرسانی `Package` entity (۷ فیلد جدید) | | **T1.1** | بروزرسانی `Package` entity (**۱۱ فیلد** جدید: v2 ۷ + v3 ۴ شامل MaxBalancesPerLeg, MaxNetworkLevel, MagicWalletMaxDeposit, MagicWalletMaxCredit) | 🔄 |
| **T1.2** | ایجاد `PackageFeature` entity + EF Config | | **T1.2** | ایجاد `PackageFeature` entity + EF Config | |
| **T1.3** | اضافه `PackageId` به `ClubMembership` | | **T1.3** | اضافه `PackageId` به `ClubMembership` | |
| **T1.4** | اضافه `PackageId` به `ClubMembershipCycle` | | **T1.4** | اضافه `PackageId` به `ClubMembershipCycle` | |
| **T1.5** | اضافه `PackageId` به `WeeklyCommissionPool` + Unique | | **T1.5** | اضافه `PackageId` به `WeeklyCommissionPool` + Unique(WeekId,PackageId) | |
| **T1.6** | اضافه `PackageId` به `UserCommissionPayout` | | **T1.6** | اضافه `PackageId` به `UserCommissionPayout` + Unique(UserId,WeekId,PackageId) | 🔄 |
| **T1.7** | حذف ۵ ثابت از `SystemConstants` | | **T1.7** | حذف **۷ ثابت** از `SystemConstants` (v2: ۵ + v3: MaxWeeklyBalancesPerLeg, MaxNetworkLevel) | 🔄 |
| **T1.8** | Database Migration + Seed Data (۲ پکیج + فیچرها) | | **T1.8** | Database Migration + Seed Data (۲ پکیج + فیچرها) | |
| **T1.9** | Data Migration: کاربران فعلی → PackageId = پکیج پایه | | **T1.9** | Data Migration: کاربران فعلی → PackageId = پکیج پایه | |
| **T1.10** | بروزرسانی Proto‌ها | | **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.9** | PackageFeature CRUD |
| **T2.10** | Event: PackageCreated → ساخت Pool خالی | | **T2.10** | Event: PackageCreated → ساخت Pool خالی |
### فاز ۳ — محاسبه پورسانت ≈ ۳ روز (موازی با فاز ۲) ### فاز ۳ — محاسبه پورسانت ≈ ۴ روز (موازی با فاز ۲)
| تسک | شرح | | تسک | شرح | v3? |
|-----|------| |-----|----- |-----|
| **T3.1** | بروزرسانی `sp_CalculateWeeklyBalances``@PackageId` | | **T3.1** | بروزرسانی `sp_CalculateWeeklyBalances``@PackageId` + **`@MaxBalancesPerLeg`** + **`@MaxNetworkLevel`** (حذف hardcode ۳۰۰/۱۵) | 🔄 |
| **T3.2** | بروزرسانی `sp_CalculateWeeklyCommissionPool``@PackageId` | | **T3.2** | بروزرسانی `sp_CalculateWeeklyCommissionPool``@PackageId` + فیلتر تعادل‌های همان پکیج | 🔄 |
| **T3.3** | بروزرسانی `WeeklyCommissionCalculationService` — Loop | | **T3.3** | بروزرسانی `WeeklyCommissionCalculationService` — Loop روی پکیج‌ها + ارسال تنظیمات | 🔄 |
| **T3.4** | بروزرسانی `OrmCommissionCalculationStrategy` — فیلتر | | **T3.4** | بروزرسانی `OrmCommissionCalculationStrategy` — فیلتر PackageId + خواندن Max از Package | 🔄 |
| **T3.5** | تست محاسبات با داده واقعی | | **T3.5** | **بروزرسانی `SpCommissionCalculationStrategy` — پاس دادن PackageId + MaxBalancesPerLeg + MaxNetworkLevel** | **🆕** |
| **T3.6** | **Carryover per-package: week-shifting فقط رکوردهای همان PackageId** | **🆕** |
| **T3.7** | تست محاسبات با داده واقعی (۲ پکیج موازی، carryover مجزا) | 🔄 |
### فاز ۴ — UI ≈ ۴ روز ### فاز ۴ — UI ≈ ۵ روز
| تسک | شرح | | تسک | شرح | v3? |
|-----|------| |-----|----- |-----|
| **T4.1** | FrontOffice: کاشی‌های پکیج (داینامیک) | | **T4.1** | FrontOffice: کاشی‌های پکیج (داینامیک) | |
| **T4.2** | FrontOffice: مدال پرداخت (دایا+مستقیم / فقط مستقیم) | | **T4.2** | FrontOffice: مدال پرداخت (دایا+مستقیم / فقط مستقیم) | |
| **T4.3** | FrontOffice: MyPackages — re-purchase | | **T4.3** | FrontOffice: MyPackages — re-purchase | |
| **T4.4** | FrontOffice: ActivationSection + Contract — داینامیک | | **T4.4** | FrontOffice: ActivationSection + Contract — داینامیک | |
| **T4.5** | BackOffice: CRUD پکیج — فیلدهای جدید | | **T4.5** | BackOffice: CRUD پکیج — فیلدهای جدید (۱۱ فیلد) | 🔄 |
| **T4.6** | BackOffice: ماتریس PackageFeature | | **T4.6** | BackOffice: ماتریس PackageFeature | |
| **T4.7** | BackOffice: ActivateClubDialog — dropdown | | **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)** | **🆕** |
### فاز ۵ — تست و استقرار ≈ ۲ روز ### فاز ۵ — تست و استقرار ≈ ۳ روز
| تسک | شرح | | تسک | شرح | v3? |
|-----|------| |-----|----- |-----|
| **T5.1** | تست خرید هر پکیج | | **T5.1** | تست خرید هر پکیج | |
| **T5.2** | تست re-purchase بعد تکمیل چرخه | | **T5.2** | تست re-purchase بعد تکمیل چرخه | |
| **T5.3** | تست Commission Pool جداگانه | | **T5.3** | تست Commission Pool جداگانه | |
| **T5.4** | تست Migration | | **T5.4** | تست Migration | |
| **T5.5** | Deploy staging → production | | **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)
| فاز | مدت | وابستگی | | فاز | مدت | وابستگی | v3 تغییر |
|-----|------|---------| |-----|------|---------|----------|
| فاز ۰ — فیکس باگ‌ها | ۱ روز | — | | فاز ۰ — فیکس باگ‌ها | ۱ روز | — | |
| فاز ۱ — زیرساخت | ۳ روز | فاز ۰ | | فاز ۱ — زیرساخت | ۴ روز | فاز ۰ | +۱ روز (NetworkWeeklyBalance + Unique) |
| فاز ۲ — منطق | ۴ روز | فاز ۱ | | فاز ۲ — منطق | ۴ روز | فاز ۱ | |
| فاز ۳ — پورسانت | ۳ روز | فاز ۱ | | فاز ۳ — پورسانت | **۴ روز** | فاز ۱ | **+۱ روز** (SP params + carryover per-package) |
| فاز ۴ — UI | ۴ روز | فاز ۲ | | فاز ۴ — UI | **۵ روز** | فاز ۲ | **+۱ روز** (FO/BO گزارش per-package) |
| فاز ۵ — تست | ۲ روز | فاز ۳, ۴ | | فاز ۵ — تست | **۳ روز** | فاز ۳, ۴ | **+۱ روز** (تست carryover + گزارش) |
| **مجموع** | **~۱۷ روز** | | | **مجموع** | **~۲۱ روز** | | |
> فاز ۲ و ۳ **موازی** → مسیر بحرانی: ۰→۱→۲→۴→۵ = **~۱۴ روز** > فاز ۲ و ۳ **موازی** → مسیر بحرانی: ۰→۱→۲→۴→۵ = **~۱۷ روز**
> نسبت به v2 (**۱۴ روز**): +۳ روز بخاطر per-package commission + گزارش‌دهی + carryover
+238 -61
View File
@@ -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 (۳ روز) │ └─→ مرحله ۴: UI (۵ روز) ← +۲ (v3: FO/BO گزارش per-package)
└─→ مرحله ۳: پورسانت (۳ روز) ← موازی └─→ مرحله ۳: پورسانت (۴ روز) ← +۱ (v3: SP params + carryover)
└─→ مرحله ۵: BackOffice UI (۳ روز) └─→ مرحله ۵: تست + استقرار (۳ روز) ← +۱
└─→ مرحله ۶: تست + استقرار (۲ روز)
مسیر بحرانی: ۰→۱→۲→۴→۶ = ~۱۳ روز مسیر بحرانی: ۰→۱→۲→۴→۵ = ~۱۷ روز (v2: ۱۳ روز)
``` ```
--- ---
@@ -69,7 +70,7 @@
> ⏱️ ۳ روز | وابستگی: مرحله ۰ | ریسک: متوسط (migration) > ⏱️ ۳ روز | وابستگی: مرحله ۰ | ریسک: متوسط (migration)
### T1.1 — بروزرسانی Package Entity ### T1.1 — بروزرسانی Package Entity (۱۱ فیلد جدید — v3)
**فایل:** `CMS/src/CMSMicroservice.Domain/Entities/Package.cs` **فایل:** `CMS/src/CMSMicroservice.Domain/Entities/Package.cs`
@@ -82,6 +83,13 @@
+ public long ActivationFee { get; set; } + public long ActivationFee { get; set; }
+ public decimal DiscountMultiplier { get; set; } = 2.0m; + public decimal DiscountMultiplier { get; set; } = 2.0m;
+ public decimal MagicWalletMultiplier { get; set; } = 2.5m; + 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<PackageFeature> PackageFeatures { get; set; } + public virtual ICollection<PackageFeature> PackageFeatures { get; set; }
``` ```
@@ -106,14 +114,15 @@ public class PackageFeature : BaseAuditableEntity
### T1.3-T1.6 — اضافه PackageId به entityها ### T1.3-T1.6 — اضافه PackageId به entityها
| Entity | فیلد | Required? | توضیح | | Entity | فیلد | Required? | توضیح | v3? |
|--------|------|-----------|-------| |--------|------|-----------|-------|-----|
| ClubMembership | `long? PackageId` | nullable (بعد migration → required) | آخرین پکیج | | ClubMembership | `long? PackageId` | nullable (بعد migration → required) | آخرین پکیج | |
| ClubMembershipCycle | `long PackageId` | required | پکیج این چرخه | | ClubMembershipCycle | `long PackageId` | required | پکیج این چرخه | |
| WeeklyCommissionPool | `long PackageId` | required + Unique(WeekDefId, PkgId) | Pool هر پکیج | | WeeklyCommissionPool | `long PackageId` | required + Unique(WeekDefId, PkgId) | Pool هر پکیج | |
| UserCommissionPayout | `long? PackageId` | nullable | ردیابی | | 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` **فایل:** `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 DayaLoanAmount = 56_000_000;
- public const long ClubActivationFee = 25_200_000; - public const long ClubActivationFee = 25_200_000;
- public const long ClubMembershipGiftValue = 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 ### T1.8 — Database Migration
@@ -151,7 +164,12 @@ UPDATE "CMS"."Packages" SET
"SupportsDirectPurchase" = true, "SupportsDirectPurchase" = true,
"ActivationFee" = 25200000, "ActivationFee" = 25200000,
"DiscountMultiplier" = 2.0, "DiscountMultiplier" = 2.0,
"MagicWalletMultiplier" = 2.5 "MagicWalletMultiplier" = 2.5,
-- v3: تنظیمات پورسانت
"MaxBalancesPerLeg" = 300,
"MaxNetworkLevel" = 15,
"MagicWalletMaxDeposit" = 1000000000,
"MagicWalletMaxCredit" = 2500000000
WHERE "Id" = 4; WHERE "Id" = 4;
-- 2. Link existing data to base package -- 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"."ClubMembershipCycles" SET "PackageId" = 4 WHERE "PackageId" IS NULL;
UPDATE "CMS"."WeeklyCommissionPools" 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; 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 -- 3. Seed Silver package (شامل فیلدهای v3)
INSERT INTO "CMS"."Packages" (...) VALUES ('پکیج نقره‌ای', 5600000, ...); INSERT INTO "CMS"."Packages" (..., "MaxBalancesPerLeg", "MaxNetworkLevel",
"MagicWalletMaxDeposit", "MagicWalletMaxCredit", ...)
VALUES ('پکیج نقره‌ای', 5600000, ..., 30, 15, 100000000, 250000000, ...);
``` ```
### T1.10 — بروزرسانی Protoها ### T1.10 — بروزرسانی Protoها
| Proto File | تغییر | | Proto File | تغیر | v3? |
|-----------|-------| |-----------|-------|-----|
| package.proto | فیلدهای جدید Package message | | package.proto | فیلدهای جدید Package message (۱۱ فیلد) | 🔄 |
| clubmembership.proto | package_id در request/response | | clubmembership.proto | package_id در request/response | |
| commission.proto | package_id در pool/payout messages | | 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 ```sql
-- sp_CalculateWeeklyBalances: -- sp_CalculateWeeklyBalances — v3: حذف hardcode
ALTER PROCEDURE sp_CalculateWeeklyBalances ALTER PROCEDURE sp_CalculateWeeklyBalances
@WeekDefinitionId BIGINT, @WeekDefinitionId BIGINT,
@PackageId BIGINT -- ← جدید @PackageId BIGINT,
@MaxBalancesPerLeg INT, -- v3: از Package entity (نه ۳۰۰ hardcode!)
@MaxNetworkLevel INT -- v3: از Package entity (نه ۱۵ hardcode!)
AS AS
BEGIN BEGIN
-- فیلتر بالانس‌ها فقط کاربرانی که این پکیج را دارند -- فیلتر: فقط کاربرانی که این پکیج را دارند
INSERT INTO "CMS"."WeeklyBalances" (...) -- carryover: فقط رکوردهای PackageId = @PackageId
SELECT ... -- cap: از @MaxBalancesPerLeg (نه ۳۰۰)
-- depth: CTE تا @MaxNetworkLevel (نه ۱۵)
INSERT INTO "CMS"."NetworkWeeklyBalances" ("PackageId", ...)
SELECT @PackageId, ...
FROM "CMS"."UserWallets" w FROM "CMS"."UserWallets" w
INNER JOIN "CMS"."ClubMemberships" m ON m."UserId" = w."UserId" INNER JOIN "CMS"."ClubMemberships" m ON m."UserId" = w."UserId"
WHERE m."PackageId" = @PackageId -- ← فیلتر WHERE m."PackageId" = @PackageId
AND m."IsActive" = true AND m."IsActive" = true;
AND w."WalletMode" = 0; -- Normal only
END; END;
``` ```
### T3.3 — Loop Service ### T3.3 — Loop Service (🔄 v3: ارسال تنظیمات پکیج)
```csharp ```csharp
// WeeklyCommissionCalculationService.cs // WeeklyCommissionCalculationService.cs
@@ -288,14 +342,71 @@ var activePackages = await _context.Packages
foreach (var package in activePackages) foreach (var package in activePackages)
{ {
_logger.LogInformation("Calculating commission for package {Id}: {Title}", _logger.LogInformation(
package.Id, package.Title); "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); 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 باید:** > پورسانت = پول واقعی. **هر تغییر در SPs باید:**
@@ -306,9 +417,9 @@ foreach (var package in activePackages)
--- ---
## مرحله ۴ — FrontOffice UI ## مرحله ۴ — UI (FrontOffice + BackOffice)
> ⏱️ ۳ روز | وابستگی: مرحله ۲ | ریسک: متوسط > ⏱️ **۵ روز** (v3: +۲) | وابستگی: مرحله ۲ + ۳ | ریسک: متوسط
### T4.1 — کاشی‌های پکیج داینامیک ### T4.1 — کاشی‌های پکیج داینامیک
@@ -355,52 +466,118 @@ else
} }
``` ```
--- ### T4.8 — FrontOffice: CommissionDashboard per-package (🆕 v3)
## مرحله ۵ — BackOffice UI **فایل:** `FrontOffice/src/.../Pages/Commission/CommissionDashboardPage.razor`
> ⏱️ ۳ روز | وابستگی: مرحله ۳ | ریسک: کم ```razor
@* v3: کارت‌های خلاصه بر اساس پکیج *@
<MudGrid>
@foreach (var summary in _packageSummaries)
{
<MudItem xs="12" md="6">
<MudCard>
<MudCardHeader>@summary.PackageTitle</MudCardHeader>
<MudCardContent>
<p>زیرمجموعه: @summary.DownlineCount نفر</p>
<p>تیم اول: @summary.LeftLegMembers | تیم دوم: @summary.RightLegMembers</p>
<p>تعادل: @summary.TotalBalances</p>
<p>💰 پاداش: @summary.CommissionFormatted</p>
</MudCardContent>
</MudCard>
</MudItem>
}
</MudGrid>
<MudAlert>📦 مجموع پاداش هفته: @_totalCommission</MudAlert>
```
_(تسک‌ها در 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: *@
<MudText Typo="Typo.h6">فیچرهای پکیج</MudText>
@foreach (var feature in _allFeatures)
{
<MudCheckBox @bind-Checked="feature.IsIncluded" Label="@feature.Title" />
}
```
--- ---
## مرحله ۶ — تست و استقرار ## مرحله ۵ — تست و استقرار
> ⏱️ ۲ روز | وابستگی: مرحله ۴ و ۵ > ⏱️ **۳ روز** (v3: +۱) | وابستگی: مرحله ۴
### Checklist تست ### Checklist تست
**خرید + فعال‌سازی:**
- [ ] خرید پکیج نقره‌ای (ZarinPal) - [ ] خرید پکیج نقره‌ای (ZarinPal)
- [ ] خرید پکیج پایه (ZarinPal) - [ ] خرید پکیج پایه (ZarinPal)
- [ ] خرید پکیج پایه (Daya Loan) - [ ] خرید پکیج پایه (Daya Loan)
- [ ] خرید پکیج پایه (Manual Payment) - [ ] خرید پکیج پایه (Manual Payment)
- [ ] فعالسازی باشگاه با پکیج نقره‌ای → فیچرهای محدود - [ ] فعالسازی باشگاه با پکیج نقره‌ای → فیچرهای محدود
- [ ] فعالسازی باشگاه با پکیج پایه → همه فیچرها - [ ] فعالسازی باشگاه با پکیج پایه → همه فیچرها
**چرخه Magic + خرید مجدد:**
- [ ] تکمیل چرخه Magic → ریست وضعیت - [ ] تکمیل چرخه Magic → ریست وضعیت
- [ ] خرید مجدد بعد تکمیل چرخه - [ ] خرید مجدد بعد تکمیل چرخه (همان پکیج)
- [ ] خرید مجدد با پکیج متفاوت (پایه → نقره‌ای)
**پورسانت per-package (v3):**
- [ ] Commission Pool جداگانه هر پکیج - [ ] 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) - [ ] JWT claims جدید (CanRepurchase, PackageId)
- [ ] UI: کاشی‌های داینامیک FrontOffice - [ ] UI: کاشی‌های داینامیک FrontOffice
- [ ] UI: ماتریس فیچر BackOffice - [ ] UI: ماتریس فیچر + Quick Access BackOffice
- [ ] Rollback: بدون data loss - [ ] Rollback: بدون data loss
--- ---
## 📅 تقویم پیشنهادی ## 📅 تقویم پیشنهادی (v3)
| هفته | روز | تسک | | هفته | روز | تسک |
|------|-----|------| |------|-----|------|
| هفته ۱ | روز ۱ | مرحله ۰: فیکس ۴ باگ | | هفته ۱ | روز ۱ | مرحله ۰: فیکس ۴ باگ |
| | روز ۲-۳ | مرحله ۱: Package entity + PackageFeature | | | روز ۲-۳ | مرحله ۱: Package entity (۱۱ فیلد) + PackageFeature |
| | روز ۴ | مرحله ۱: FKها + Migration | | | روز ۴-۵ | مرحله ۱: FKها + NetworkWeeklyBalance + Migration |
| هفته ۲ | روز ۵-۶ | مرحله ۲: Generic handlers + re-purchase | | هفته ۲ | روز ۶-۷ | مرحله ۲: Generic handlers + re-purchase |
| | روز ۵-۶ | مرحله ۳: SP + Loop (موازی) | | | روز ۶ | مرحله ۳: SP params + carryover per-package (موازی) |
| | روز ۷-۸ | مرحله ۲: Guards + JWT + Manual | | | روز ۸-۱۰ | مرحله ۲: Guards + JWT + Manual |
| هفته ۳ | روز ۹-۱۰ | مرحله ۴: FrontOffice UI | | هفته ۳ | روز ۱۱-۱۲ | مرحله ۴: FrontOffice UI + گزارش per-package |
| | روز ۱۱ | مرحله ۵: BackOffice UI | | | روز ۱۳-۱۴ | مرحله ۴: BackOffice UI + گزارش per-package |
| | روز ۱۲-۱۳ | مرحله ۶: تست + deploy | | | روز ۱۵-۱۷ | مرحله ۵: تست + 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 بر فرانت‌ها | | [PACKAGE-TRANSFORMATION-UX.md](PACKAGE-TRANSFORMATION-UX.md) | تاثیر UX بر فرانت‌ها |
| [FEATURE-BACKLOG.md](FEATURE-BACKLOG.md) | بکلاگ ۱۲ RPC آماده | | [FEATURE-BACKLOG.md](FEATURE-BACKLOG.md) | بکلاگ ۱۲ RPC آماده |
| [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت ۳۴۲ RPC | | [GRPC-SERVICES-AUDIT.md](../cms/GRPC-SERVICES-AUDIT.md) | آدیت ۳۴۲ RPC |
--- ---
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶* *آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۶ — v3 (پورسانت per-package + گزارش‌دهی + carryover)*