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) |
| 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<PackageFeature> PackageFeatures { get; set; }
public virtual ICollection<UserPackagePurchase> 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