diff --git a/business/BIZ-PACKAGE-BASED-SYSTEM.md b/business/BIZ-PACKAGE-BASED-SYSTEM.md index 861ea41..161bec0 100644 --- a/business/BIZ-PACKAGE-BASED-SYSTEM.md +++ b/business/BIZ-PACKAGE-BASED-SYSTEM.md @@ -42,14 +42,14 @@ | Q20 | فیچرها در خرید مجدد — چطور؟ | ✅ **DIFF/تفاضل** — مقایسه فیچرهای فعلی با پکیج جدید. فقط اختلاف اعمال می‌شود | | Q21 | ردیابی فعال‌سازی ClubMembership | ✅ **FirstActivation + LastActivation** — ۴ فیلد: `FirstActivationDate` + `FirstPackageId` + `LastActivationDate` + `LastPackageId` | | Q22 | تشخیص فعال‌شدگان هفته | ✅ **از `LastActivationDate`** — هر کسی که `LastActivationDate` در بازه هفته باشد | -| Q23 | Carryover تعادل هفتگی | ✅ **strictly per-package** — اگر کاربر پکیج عوض کرد، carryover پکیج قبلی شمرده نمی‌شود | +| Q23 | Carryover تعادل هفتگی | ✅ **per-downline-package** — تعادل بر اساس پکیج **زیرمجموعه‌ها** (نه پکیج خود کاربر). هر کاربر N رکورد تعادل دارد. تغییر پکیج خود کاربر تاثیری بر carryover ندارد | | Q24 | آستانه موجودی برای ورود Magic و خرید مجدد | ✅ **کمتر از ۱۰۰,۰۰۰ تومان** — چون قیمت محصولات متفاوته، `Balance == 0` عملاً غیرممکنه. آستانه ثابت ۱,۰۰۰,۰۰۰ ریال | | Q25 | DayaLoans محدودیت پکیج | ✅ **فقط پکیج پایه** — `SupportsDayaPurchase` فقط روی پکیج پایه `true` هست. تغییر نمی‌کنه | | Q26 | مدیریت Stored Procedures | ✅ **SP Worker (IHostedService)** — در startup، فایل‌های `.sql` از embedded resource خوانده و با checksum مقایسه و اعمال می‌شوند | | Q27 | History Tables یکسان‌سازی | ✅ **نام‌گذاری مشابه master** + ثبت خودکار تغییرات در EF interceptor/domain events | | Q28 | UI Guidance (آموزش/هشدار) | ✅ **مودال + متن inline** — در سراسر FO/BO توضیحات آموزشی و هشداری برای سیستم پکیج‌بیس | | Q29 | شرط EXIT Magic | ✅ **آخرین پکیج فعال** — از `ClubMembershipCycle.PackageId` (چرخه فعلی) → `Package.MagicWalletMaxDeposit` | -| Q30 | Carryover توضیح | ✅ **باقی‌مانده تعادل هفتگی** — پای قوی‌تر surplus نگه می‌داره. تغییر پکیج = ریست implicit (lookup پکیج جدید → ۰) | +| Q30 | Carryover توضیح | ✅ **باقی‌مانده تعادل هفتگی** — بر اساس پکیج **زیرمجموعه‌ها**. تغییر پکیج خود کاربر carryover را ریست **نمی‌کند**. هر Pool مجزا | --- @@ -561,10 +561,13 @@ WHERE c."UserId" = @UserId; / \ [Sara - نقره‌ای] [Reza - پایه] -Commission: - Ali → پاداش از Pool_پایه - Sara → پاداش از Pool_نقره‌ای - Reza → پاداش از Pool_پایه +Commission (بالاسری Ali): + Ali → از Pool_پایه (چون Reza پکیج پایه داره) + Ali → از Pool_نقره‌ای (چون Sara پکیج نقره‌ای داره) + → مجموع هر دو Pool → کیف پول شبکه Ali + → به تفکیک: X از پایه، Y از نقره‌ای + + ⚠️ پکیج خود Ali مهم نیست! مهم پکیج زیرمجموعه‌هاست ``` ### ۶.۳ فلوی کامل چرخه خرید مجدد و تاثیر بر پورسانت (v5) @@ -600,9 +603,8 @@ Commission: FirstActivationDate = حفظ, FirstPackageId = حفظ LastActivationDate = now (بروزرسانی), LastPackageId = پایه ✅ ClubMembershipCycle #2 ساخته می‌شود - ✅ NetworkWeeklyBalance جدید (PackageId=پایه, WeekId=هفته جاری) - ✅ carryover از هفته قبل: فقط carryover پکیج پایه (نه نقره‌ای!) - ✅ بالاسری‌ها: محاسبه مجدد از Pool_پایه + ✅ بالاسری علی: carryover همه Poolها (بر اساس پکیج زیرمجموعه‌ها) حفظ می‌شود + ✅ پورسانت بالاسری: از هر Pool که زیرمجموعه‌ای دارد ... همان چرخه Magic Wallet تکرار ... @@ -622,83 +624,114 @@ Commission: FirstActivationDate = حفظ, FirstPackageId = حفظ (پایه) LastActivationDate = now (بروزرسانی), LastPackageId = نقره‌ای ✅ ClubMembershipCycle #5 ساخته می‌شود (PackageId=نقره‌ای) - ✅ NetworkWeeklyBalance جدید (PackageId=نقره‌ای, WeekId=هفته جاری) - ✅ تعادل‌ها: MaxBalancesPerLeg=30 (نه 300!) + MaxNetworkLevel=15 (از پکیج) - ✅ carryover: فقط carryover نقره‌ای (Q23 — carryover پکیج پایه شمرده نمی‌شود!) - ✅ بالاسری‌ها: محاسبه از Pool_نقره‌ای → ValuePerBalance کمتر - ✅ پاداش بالاسری: ~۲,۵۲۰,۰۰۰ ÷ TotalBalances_نقره‌ای × BalancesEarned + ✅ بالاسری‌های علی: carryover همه Poolها حفظ — تغییر پکیج علی تاثیری ندارد! + (carryover بر اساس پکیج زیرمجموعه‌هاست نه خود علی) + ✅ پورسانت بالاسری از هر Pool جداگانه: + Pool_پایه: بر اساس زیرمجموعه‌هایی که پکیج پایه دارند + Pool_نقره‌ای: بر اساس زیرمجموعه‌هایی که پکیج نقره‌ای دارند + ✅ ActivationFee علی → Pool_نقره‌ای (۲,۵۲۰,۰۰۰) ``` -### ۶.۴ تعادل‌ها (NetworkWeeklyBalance) — per-package (v5) +### ۶.۴ تعادل‌ها (NetworkWeeklyBalance) — per-downline-package (v6) -> ⚠️ **تغییر اساسی:** هر کاربر **به‌ازای هر پکیج فعال** یک رکورد تعادل جداگانه دارد. +> ⚠️ **تغییر اساسی v6:** تعادل هر کاربر بر اساس پکیج **زیرمجموعه‌ها** گروه‌بندی می‌شود — **نه** پکیج خود کاربر. -> 🔴 **قانون carryover v5 (Q23):** اگر کاربر **پکیج عوض کرد**، carryover پکیج قبلی **شمرده نمی‌شود!** +> ✅ **قانون carryover v6 (Q23 اصلاح‌شده):** تغییر پکیج خود کاربر **هیچ تاثیری** بر carryover ندارد. carryover مال زیرمجموعه‌هاست. ``` قبل (تک‌پکیج): NetworkWeeklyBalance: [UserId, WeekId] → یک رکورد بعد (چند‌پکیج): - NetworkWeeklyBalance: [UserId, WeekId, PackageId] → N رکورد (N = تعداد پکیج) + NetworkWeeklyBalance: [UserId, WeekId, PackageId] → N رکورد + (N = تعداد پکیج‌های مختلف زیرمجموعه‌ها) ``` -**الگوریتم محاسبه تعادل per-package:** +**الگوریتم محاسبه تعادل per-downline-package:** ``` -برای هر پکیج فعال: - 1. واکشی کاربرانی که این پکیج را دارند: - ClubMembership.LastPackageId = X (v5 — Q21) - 2. carryover از هفته قبل: - فقط رکوردهای PackageId = X - ✅ مهم: فقط اگر پکیج فعلی کاربر = X (Q23) - ❌ اگر کاربر هفته قبل پکیج Y داشت و حالا X دارد → carryover Y شمرده نمی‌شود! - 3. اعضای جدید این هفته: - ClubMembership.LastActivationDate در بازه هفته - AND ClubMembership.LastPackageId = X (v5 — Q22) +برای هر بالاسری (کاربر): + برای هر پکیج فعال (X): + 1. واکشی زیرمجموعه‌هایی که پکیج X دارند: + زیرمجموعه‌ها WHERE ClubMembership.LastPackageId = X + 2. شمارش: چند نفر تیم چپ + چند نفر تیم راست + 3. carryover از هفته قبل: + خواندن رکورد [UserId, PrevWeekId, PackageId=X] + ✅ همیشه خوانده می‌شود — مستقل از پکیج خود کاربر! 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) -``` - -**مثال carryover با تغییر پکیج (v5 — Q23):** - -``` -هفته ۹: - علی: پکیج پایه → تعادل_پایه: Left=45, Right=30 - carryover_پایه: {Left: 15, Right: 0} ← باقیمانده - -هفته ۱۰: - علی: پکیج پایه → EXIT Magic → خرید پکیج نقره‌ای + 7. Remainder → carryover هفته بعد (برای PackageId = X) + 8. پورسانت: TotalBalances × ValuePerBalance_X → کیف پول شبکه - ❌ carryover_پایه {Left:15, Right:0} شمرده نمی‌شود! - (پکیج فعلی = نقره‌ای ≠ پایه) - ✅ carryover_نقره‌ای: {Left:0, Right:0} (تازه شروع) - ✅ تعادل_نقره‌ای: Left=NewLeft+0, Right=NewRight+0 + مجموع پورسانت از همه Poolها → واریز به کیف پول شبکه کاربر + (به تفکیک مشخص: هر مبلغ از کدام Pool) ``` -**مثال عملی:** +**مثال جامع — بالاسری "علی":** ``` -هفته ۱۰: - علی (پکیج پایه): - تعادل_پایه: 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} +علی (خودش پکیج پایه داره) +├── تیم چپ: +│ ├── سارا (پکیج پایه) +│ ├── رضا (پکیج پایه) +│ └── مریم (پکیج نقره‌ای) +└── تیم راست: + ├── حسین (پکیج پایه) + └── زهرا (پکیج نقره‌ای) +``` + +``` +هفته ۱۰ — محاسبه تعادل علی: +═══════════════════════════════════════════════════════ + +═══ Pool پکیج پایه (زیرمجموعه‌هایی که پایه دارن) ═══ + چپ: سارا + رضا = 2 + راست: حسین = 1 + carryover هفته ۹: {چپ: 3, راست: 0} + جمع: چپ = 2+3 = 5, راست = 1+0 = 1 + تعادل = MIN(5, 1) = 1 (cap 300 → OK) + carryover → هفته ۱۱: {چپ: 4, راست: 0} + پورسانت: 1 × ValuePerBalance_پایه = A ریال + +═══ Pool پکیج نقره‌ای (زیرمجموعه‌هایی که نقره‌ای دارن) ═══ + چپ: مریم = 1 + راست: زهرا = 1 + carryover هفته ۹: {چپ: 0, راست: 0} + جمع: چپ = 1+0 = 1, راست = 1+0 = 1 + تعادل = MIN(1, 1) = 1 (cap 30 → OK) + carryover → هفته ۱۱: {چپ: 0, راست: 0} + پورسانت: 1 × ValuePerBalance_نقره‌ای = B ریال + +═══ مجموع پورسانت علی هفته ۱۰: ═══ + کیف پول شبکه += (A + B) + ردیابی: A از Pool پایه، B از Pool نقره‌ای +``` + +**هفته ۱۱ — علی پکیج خودش رو عوض می‌کنه (پایه → نقره‌ای):** + +``` +═══ Pool پکیج پایه ═══ + carryover از هفته ۱۰: {چپ: 4, راست: 0} ← هنوز هست! + ✅ تغییر پکیج خود علی تاثیری نداره! + اعضای جدید: چپ = 0, راست = 1 + جمع: چپ = 0+4 = 4, راست = 1+0 = 1 + تعادل = MIN(4, 1) = 1 + ✅ علی هنوز از Pool پایه سود می‌بره (چون زیرمجموعه‌هایی با پکیج پایه داره) + +═══ Pool پکیج نقره‌ای ═══ + carryover از هفته ۱۰: {چپ: 0, راست: 0} + ... محاسبه عادی ... +``` + +**نکته کلیدی:** پکیج **خود کاربر** فقط تعیین می‌کنه ActivationFee‌اش به کدوم Pool بره. +**تعادل و carryover** بر اساس پکیج **زیرمجموعه‌ها** محاسبه می‌شه. + +**Cap (سقف):** +``` +اگه تعادل بیشتر از MaxBalancesPerLeg بشه → بریده می‌شه (flush): + تعادل = 500، سقف = 300 → CappedBalance = 300، Flushed = 200 + ⚠️ Flushed از بین می‌ره — carry نمی‌شه! ``` ### ۶.۵ تغییرات SP (v3 — بروزرسانی) @@ -1713,40 +1746,39 @@ public abstract class BaseHistoryEntity : BaseEntity --- -### Q30 — Carryover — توضیح ساده +### Q30 — Carryover — توضیح ساده (v6 اصلاح‌شده) -> **Carryover = باقی‌مانده تعادل از هفته قبل** +> **Carryover = باقی‌مانده تعادل از هفته قبل — بر اساس پکیج زیرمجموعه‌ها** **مثال ساده:** ``` +علی بالاسری — زیرمجموعه‌هاش پکیج پایه دارن: + هفته ۱۰: - علی: تیم چپ = ۵۰ نفر فعال، تیم راست = ۳۰ نفر فعال - تعادل = MIN(50, 30) = 30 - باقی‌مانده: چپ = 50-30 = 20 ← CARRYOVER راست = 0 + تیم چپ (پکیج پایه) = ۵۰ نفر، تیم راست (پکیج پایه) = ۳۰ نفر + تعادل_پایه = MIN(50, 30) = 30 + باقی‌مانده_پایه: {چپ: 20, راست: 0} ← CARRYOVER هفته ۱۱: - اعضای جدید: چپ = 5، راست = 10 + اعضای جدید (پکیج پایه): چپ = 5، راست = 10 جمع با carryover: چپ = 5+20 = 25، راست = 10+0 = 10 - تعادل = MIN(25, 10) = 10 - باقی‌مانده: چپ = 25-10 = 15 ← CARRYOVER بعدی + تعادل_پایه = MIN(25, 10) = 10 + باقی‌مانده_پایه: {چپ: 15, راست: 0} ← CARRYOVER بعدی ``` -**Per-package carryover (Q23):** +**تغییر پکیج خود کاربر — تاثیری ندارد (Q23 اصلاحی):** ``` -علی پکیج پایه داره: - carryover_پایه = {چپ: 20, راست: 0} +علی پکیج خودش رو عوض می‌کنه (پایه → نقره‌ای): -علی پکیج رو عوض می‌کنه → نقره‌ای: - ❌ carryover_پایه دیگه شمرده نمی‌شه! - ✅ carryover_نقره‌ای = {چپ: 0, راست: 0} ← شروع از صفر + carryover_پایه = {چپ: 20, راست: 0} ← هنوز هست! حذف نمی‌شه! + carryover_نقره‌ای = {چپ: 0, راست: 0} - (چون سیستم هفته بعد دنبال carryover با PackageId=نقره‌ای - می‌گرده و پیدا نمی‌کنه → default صفر) -``` - -**Cap (سقف):** -``` -اگه تعادل بیشتر از MaxBalancesPerLeg بشه → بریده می‌شه (flush): - تعادل = 500، سقف = 300 → CappedBalance = 300، Flushed = 200 - ⚠️ Flushed از بین می‌ره — carry نمی‌شه! + ✅ هفته بعد، علی هنوز از Pool پایه سود می‌بره + (چون زیرمجموعه‌هایی با پکیج پایه داره) + ✅ همزمان از Pool نقره‌ای هم سود می‌بره + (چون زیرمجموعه‌هایی با پکیج نقره‌ای هم داره) + + پکیج خود علی فقط تعیین می‌کنه: + → ActivationFee‌اش به کدوم Pool بره + → MagicWallet سقفش چقدره ``` diff --git a/deployment/PACKAGE-MIGRATION-GUIDE.md b/deployment/PACKAGE-MIGRATION-GUIDE.md index ab08bbe..8dfbe17 100644 --- a/deployment/PACKAGE-MIGRATION-GUIDE.md +++ b/deployment/PACKAGE-MIGRATION-GUIDE.md @@ -85,7 +85,7 @@ | تعادل هفتگی | ۱ رکورد per user/week | **N رکورد** per user/week/package | | MaxBalancesPerLeg | hardcoded `300` | per-package (پایه=۳۰۰, نقره‌ای=۳۰) | | MaxNetworkLevel | hardcoded `15` | per-package از دیتابیس | -| Carryover | یک‌پارچه | **per-package** — تغییر پکیج = ریست carryover (Q23) | +| Carryover | یک‌پارچه | **per-downline-package** — بر اساس پکیج زیرمجموعه‌ها (تغییر پکیج خود کاربر تاثیری ندارد) | | Stored Procedure | پارامترهای ثابت | پارامترهای داینامیک از Package entity | | گزارش مشتری | بدون تفکیک | **breakdown per-package** | | گزارش ادمین | بدون فیلتر | **فیلتر بر اساس پکیج** | @@ -572,7 +572,7 @@ kubectl set image deployment/backoffice bo=bo:rollback-point |---|------|-------------------| | 10 | Pool جداگانه هر پکیج | WeeklyCommissionPool با PackageId متفاوت | | 11 | MaxBalancesPerLeg متفاوت | پایه=۳۰۰, نقره‌ای=۳۰ | -| 12 | Carryover per-package | تغییر پکیج → ریست carryover | +| 12 | Carryover per-downline-package | تغییر پکیج خود کاربر → carryover حفظ (بر اساس زیرمجموعه‌ها) | | 13 | SP پارامترها از Package | بدون hardcoded ۳۰۰/۱۵ | ### 📊 گزارشات per-package