Compare commits
10 Commits
df1affa46e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4218d08597 | |||
| 1db77b1a1b | |||
| e3850f9dd8 | |||
| 39590d2cbe | |||
| de69bf862c | |||
| 6fc15b474e | |||
| e9f1fb9911 | |||
| 085583c274 | |||
| f8908d8e2b | |||
| ba10b6485b |
@@ -1,10 +1,10 @@
|
|||||||
# 📦 سیستم مبتنی بر پکیج (Package-Based System)
|
# 📦 سیستم مبتنی بر پکیج (Package-Based System)
|
||||||
|
|
||||||
> **وضعیت:** در حال پیادهسازی — **فاز ۰-۴ تکمیل ✅** | فاز ۵-۶ در انتظار
|
> **وضعیت:** پیادهسازی کد تکمیل — **فاز ۰-۱۰ ✅** | تست نهایی باقیمانده
|
||||||
> **تاریخ بروزرسانی:** ۷ اسفند ۱۴۰۴
|
> **تاریخ بروزرسانی:** ۸ اسفند ۱۴۰۴
|
||||||
> **نسخه:** v5 (قرارداد یکبار + فیچر DIFF + تاریخچه فعالسازی)
|
> **نسخه:** v7 (DataMigration + EF Staging + PackagePurchaseDialog + UI Fixes)
|
||||||
> **تاثیرگذاری:** زیاد — **۹۵+** تغییر در ۶ لایه (۵۱ اصلی + ۴۴ سایدافکت)
|
> **تاثیرگذاری:** زیاد — **۱۰۰+** تغییر در ۶ لایه (۵۱ اصلی + ۴۴ سایدافکت + ۵ استقرار)
|
||||||
> **کامیتها:** `8b9c317` (Phase 0) → `ae92ab8` (Phase 1) → `a9cd2fd` (Phase 1.5) → `8e5c7c5` (Phase 2) → `ccb938e` (Phase 3) → `0002a5a` (Phase 4)
|
> **کامیتها:** `8b9c317` (Phase 0) → `ae92ab8` (Phase 1) → `a9cd2fd` (Phase 1.5) → `8e5c7c5` (Phase 2) → `ccb938e` (Phase 3) → `0002a5a` (Phase 4) → `a1024a3` (Q24+Q26) → `fdbb91d` (Q27) → `10d2ca2` (Rename+Interceptor+Migration) → `a3681a8` (PackagePurchaseDialog) → `3c1a8ff` (UI Fixes)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -42,7 +42,14 @@
|
|||||||
| Q20 | فیچرها در خرید مجدد — چطور؟ | ✅ **DIFF/تفاضل** — مقایسه فیچرهای فعلی با پکیج جدید. فقط اختلاف اعمال میشود |
|
| Q20 | فیچرها در خرید مجدد — چطور؟ | ✅ **DIFF/تفاضل** — مقایسه فیچرهای فعلی با پکیج جدید. فقط اختلاف اعمال میشود |
|
||||||
| Q21 | ردیابی فعالسازی ClubMembership | ✅ **FirstActivation + LastActivation** — ۴ فیلد: `FirstActivationDate` + `FirstPackageId` + `LastActivationDate` + `LastPackageId` |
|
| Q21 | ردیابی فعالسازی ClubMembership | ✅ **FirstActivation + LastActivation** — ۴ فیلد: `FirstActivationDate` + `FirstPackageId` + `LastActivationDate` + `LastPackageId` |
|
||||||
| Q22 | تشخیص فعالشدگان هفته | ✅ **از `LastActivationDate`** — هر کسی که `LastActivationDate` در بازه هفته باشد |
|
| 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 توضیح | ✅ **باقیمانده تعادل هفتگی** — بر اساس پکیج **زیرمجموعهها**. تغییر پکیج خود کاربر carryover را ریست **نمیکند**. هر Pool مجزا |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -554,10 +561,13 @@ WHERE c."UserId" = @UserId;
|
|||||||
/ \
|
/ \
|
||||||
[Sara - نقرهای] [Reza - پایه]
|
[Sara - نقرهای] [Reza - پایه]
|
||||||
|
|
||||||
Commission:
|
Commission (بالاسری Ali):
|
||||||
Ali → پاداش از Pool_پایه
|
Ali → از Pool_پایه (چون Reza پکیج پایه داره)
|
||||||
Sara → پاداش از Pool_نقرهای
|
Ali → از Pool_نقرهای (چون Sara پکیج نقرهای داره)
|
||||||
Reza → پاداش از Pool_پایه
|
→ مجموع هر دو Pool → کیف پول شبکه Ali
|
||||||
|
→ به تفکیک: X از پایه، Y از نقرهای
|
||||||
|
|
||||||
|
⚠️ پکیج خود Ali مهم نیست! مهم پکیج زیرمجموعههاست
|
||||||
```
|
```
|
||||||
|
|
||||||
### ۶.۳ فلوی کامل چرخه خرید مجدد و تاثیر بر پورسانت (v5)
|
### ۶.۳ فلوی کامل چرخه خرید مجدد و تاثیر بر پورسانت (v5)
|
||||||
@@ -593,9 +603,8 @@ Commission:
|
|||||||
FirstActivationDate = حفظ, FirstPackageId = حفظ
|
FirstActivationDate = حفظ, FirstPackageId = حفظ
|
||||||
LastActivationDate = now (بروزرسانی), LastPackageId = پایه
|
LastActivationDate = now (بروزرسانی), LastPackageId = پایه
|
||||||
✅ ClubMembershipCycle #2 ساخته میشود
|
✅ ClubMembershipCycle #2 ساخته میشود
|
||||||
✅ NetworkWeeklyBalance جدید (PackageId=پایه, WeekId=هفته جاری)
|
✅ بالاسری علی: carryover همه Poolها (بر اساس پکیج زیرمجموعهها) حفظ میشود
|
||||||
✅ carryover از هفته قبل: فقط carryover پکیج پایه (نه نقرهای!)
|
✅ پورسانت بالاسری: از هر Pool که زیرمجموعهای دارد
|
||||||
✅ بالاسریها: محاسبه مجدد از Pool_پایه
|
|
||||||
|
|
||||||
... همان چرخه Magic Wallet تکرار ...
|
... همان چرخه Magic Wallet تکرار ...
|
||||||
|
|
||||||
@@ -615,83 +624,114 @@ Commission:
|
|||||||
FirstActivationDate = حفظ, FirstPackageId = حفظ (پایه)
|
FirstActivationDate = حفظ, FirstPackageId = حفظ (پایه)
|
||||||
LastActivationDate = now (بروزرسانی), LastPackageId = نقرهای
|
LastActivationDate = now (بروزرسانی), LastPackageId = نقرهای
|
||||||
✅ ClubMembershipCycle #5 ساخته میشود (PackageId=نقرهای)
|
✅ ClubMembershipCycle #5 ساخته میشود (PackageId=نقرهای)
|
||||||
✅ NetworkWeeklyBalance جدید (PackageId=نقرهای, WeekId=هفته جاری)
|
✅ بالاسریهای علی: carryover همه Poolها حفظ — تغییر پکیج علی تاثیری ندارد!
|
||||||
✅ تعادلها: MaxBalancesPerLeg=30 (نه 300!) + MaxNetworkLevel=15 (از پکیج)
|
(carryover بر اساس پکیج زیرمجموعههاست نه خود علی)
|
||||||
✅ carryover: فقط carryover نقرهای (Q23 — carryover پکیج پایه شمرده نمیشود!)
|
✅ پورسانت بالاسری از هر Pool جداگانه:
|
||||||
✅ بالاسریها: محاسبه از Pool_نقرهای → ValuePerBalance کمتر
|
Pool_پایه: بر اساس زیرمجموعههایی که پکیج پایه دارند
|
||||||
✅ پاداش بالاسری: ~۲,۵۲۰,۰۰۰ ÷ TotalBalances_نقرهای × BalancesEarned
|
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] → یک رکورد
|
||||||
|
|
||||||
بعد (چندپکیج):
|
بعد (چندپکیج):
|
||||||
NetworkWeeklyBalance: [UserId, WeekId, PackageId] → N رکورد (N = تعداد پکیج)
|
NetworkWeeklyBalance: [UserId, WeekId, PackageId] → N رکورد
|
||||||
|
(N = تعداد پکیجهای مختلف زیرمجموعهها)
|
||||||
```
|
```
|
||||||
|
|
||||||
**الگوریتم محاسبه تعادل per-package:**
|
**الگوریتم محاسبه تعادل per-downline-package:**
|
||||||
|
|
||||||
```
|
```
|
||||||
برای هر پکیج فعال:
|
برای هر بالاسری (کاربر):
|
||||||
1. واکشی کاربرانی که این پکیج را دارند:
|
برای هر پکیج فعال (X):
|
||||||
ClubMembership.LastPackageId = X (v5 — Q21)
|
1. واکشی زیرمجموعههایی که پکیج X دارند:
|
||||||
2. carryover از هفته قبل:
|
زیرمجموعهها WHERE ClubMembership.LastPackageId = X
|
||||||
فقط رکوردهای PackageId = X
|
2. شمارش: چند نفر تیم چپ + چند نفر تیم راست
|
||||||
✅ مهم: فقط اگر پکیج فعلی کاربر = X (Q23)
|
3. carryover از هفته قبل:
|
||||||
❌ اگر کاربر هفته قبل پکیج Y داشت و حالا X دارد → carryover Y شمرده نمیشود!
|
خواندن رکورد [UserId, PrevWeekId, PackageId=X]
|
||||||
3. اعضای جدید این هفته:
|
✅ همیشه خوانده میشود — مستقل از پکیج خود کاربر!
|
||||||
ClubMembership.LastActivationDate در بازه هفته
|
|
||||||
AND ClubMembership.LastPackageId = X (v5 — Q22)
|
|
||||||
4. LeftLegTotal = NewLeft + CarryoverLeft
|
4. LeftLegTotal = NewLeft + CarryoverLeft
|
||||||
5. RightLegTotal = NewRight + CarryoverRight
|
5. RightLegTotal = NewRight + CarryoverRight
|
||||||
6. TotalBalances = MIN(Left, Right) → cap at Package.MaxBalancesPerLeg
|
6. TotalBalances = MIN(Left, Right) → cap at Package.MaxBalancesPerLeg
|
||||||
7. Remainder → carryover هفته بعد (فقط برای PackageId = X)
|
7. Remainder → carryover هفته بعد (برای PackageId = X)
|
||||||
8. SubordinateBalances: مجموع TotalBalances زیرمجموعه (تا Package.MaxNetworkLevel)
|
8. پورسانت: TotalBalances × ValuePerBalance_X → کیف پول شبکه
|
||||||
|
|
||||||
|
مجموع پورسانت از همه Poolها → واریز به کیف پول شبکه کاربر
|
||||||
|
(به تفکیک مشخص: هر مبلغ از کدام Pool)
|
||||||
```
|
```
|
||||||
|
|
||||||
**مثال carryover با تغییر پکیج (v5 — Q23):**
|
**مثال جامع — بالاسری "علی":**
|
||||||
|
|
||||||
```
|
```
|
||||||
هفته ۹:
|
علی (خودش پکیج پایه داره)
|
||||||
علی: پکیج پایه → تعادل_پایه: Left=45, Right=30
|
├── تیم چپ:
|
||||||
carryover_پایه: {Left: 15, Right: 0} ← باقیمانده
|
│ ├── سارا (پکیج پایه)
|
||||||
|
│ ├── رضا (پکیج پایه)
|
||||||
هفته ۱۰:
|
│ └── مریم (پکیج نقرهای)
|
||||||
علی: پکیج پایه → EXIT Magic → خرید پکیج نقرهای
|
└── تیم راست:
|
||||||
|
├── حسین (پکیج پایه)
|
||||||
❌ carryover_پایه {Left:15, Right:0} شمرده نمیشود!
|
└── زهرا (پکیج نقرهای)
|
||||||
(پکیج فعلی = نقرهای ≠ پایه)
|
|
||||||
✅ carryover_نقرهای: {Left:0, Right:0} (تازه شروع)
|
|
||||||
✅ تعادل_نقرهای: Left=NewLeft+0, Right=NewRight+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} ← هنوز هست!
|
||||||
تعادل_پایه: Left=45, Right=52, Min=45 (cap 300) → OK
|
✅ تغییر پکیج خود علی تاثیری نداره!
|
||||||
تعادل_نقرهای: Left=0, Right=0 (علی پکیج نقرهای نداره)
|
اعضای جدید: چپ = 0, راست = 1
|
||||||
|
جمع: چپ = 0+4 = 4, راست = 1+0 = 1
|
||||||
|
تعادل = MIN(4, 1) = 1
|
||||||
|
✅ علی هنوز از Pool پایه سود میبره (چون زیرمجموعههایی با پکیج پایه داره)
|
||||||
|
|
||||||
سارا (پکیج نقرهای):
|
═══ Pool پکیج نقرهای ═══
|
||||||
تعادل_پایه: Left=0, Right=0
|
carryover از هفته ۱۰: {چپ: 0, راست: 0}
|
||||||
تعادل_نقرهای: Left=12, Right=8, Min=8 (cap 30) → OK
|
... محاسبه عادی ...
|
||||||
|
```
|
||||||
|
|
||||||
رضا (پکیج پایه + قبلاً نقرهای داشته):
|
**نکته کلیدی:** پکیج **خود کاربر** فقط تعیین میکنه ActivationFeeاش به کدوم Pool بره.
|
||||||
تعادل_پایه: Left=30, Right=25, Min=25 (cap 300) → OK
|
**تعادل و carryover** بر اساس پکیج **زیرمجموعهها** محاسبه میشه.
|
||||||
تعادل_نقرهای: Left=2 (carryover), Right=0 (carryover) → Min=0
|
|
||||||
|
|
||||||
هفته ۱۱ (Shift):
|
**Cap (سقف):**
|
||||||
علی: carryover_پایه = {Left: surplus_left, Right: surplus_right}
|
```
|
||||||
سارا: carryover_نقرهای = {Left: surplus_left, Right: surplus_right}
|
اگه تعادل بیشتر از MaxBalancesPerLeg بشه → بریده میشه (flush):
|
||||||
رضا: carryover_پایه = {...}, carryover_نقرهای = {Left:2, Right:0}
|
تعادل = 500، سقف = 300 → CappedBalance = 300، Flushed = 200
|
||||||
|
⚠️ Flushed از بین میره — carry نمیشه!
|
||||||
```
|
```
|
||||||
|
|
||||||
### ۶.۵ تغییرات SP (v3 — بروزرسانی)
|
### ۶.۵ تغییرات SP (v3 — بروزرسانی)
|
||||||
@@ -1391,8 +1431,8 @@ SELECT 'Balances', COUNT(*) FROM "CMS"."NetworkWeeklyBalances" WHERE "PackageId"
|
|||||||
|
|
||||||
| تسک | شرح | v3? |
|
| تسک | شرح | v3? |
|
||||||
|-----|----- |-----|
|
|-----|----- |-----|
|
||||||
| **T4.1** | FrontOffice: کاشیهای پکیج (داینامیک) | |
|
| **T4.1** | FrontOffice: کاشیهای پکیج (داینامیک) — ✅ `PackagePurchaseDialog.razor` | |
|
||||||
| **T4.2** | FrontOffice: مدال پرداخت (دایا+مستقیم / فقط مستقیم) | |
|
| **T4.2** | FrontOffice: مدال پرداخت (دایا+مستقیم / فقط مستقیم) — ✅ Step 2 in dialog | |
|
||||||
| **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 پکیج — فیلدهای جدید (۱۱ فیلد) | 🔄 |
|
||||||
@@ -1466,3 +1506,279 @@ SELECT 'Balances', COUNT(*) FROM "CMS"."NetworkWeeklyBalances" WHERE "PackageId"
|
|||||||
> فاز ۲ و ۳ **موازی** → مسیر بحرانی: ۰→۱→۲→۴→۵ = **~۲۴ روز**
|
> فاز ۲ و ۳ **موازی** → مسیر بحرانی: ۰→۱→۲→۴→۵ = **~۲۴ روز**
|
||||||
> نسبت به v4 (**۲۲ روز**): **+۲ روز** بخاطر FeatureDiffService + قرارداد یکبار + First/Last Activation
|
> نسبت به v4 (**۲۲ روز**): **+۲ روز** بخاطر FeatureDiffService + قرارداد یکبار + First/Last Activation
|
||||||
> نسبت به v3 (**۱۷ روز**): **+۷ روز** — بزرگترین سهم: Magic Wallet + WalletChangeLog + فیچر DIFF + First/Last
|
> نسبت به v3 (**۱۷ روز**): **+۷ روز** — بزرگترین سهم: Magic Wallet + WalletChangeLog + فیچر DIFF + First/Last
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱۴. تصمیمات جدید v6 (Q24–Q30)
|
||||||
|
|
||||||
|
> **تاریخ:** ۸ اسفند ۱۴۰۴
|
||||||
|
> **زمینه:** مرور نهایی قبل از تست و استقرار — ۷ نکته جدید شناسایی شد
|
||||||
|
|
||||||
|
### Q24 — آستانه موجودی برای ورود به Magic و خرید مجدد 🔴
|
||||||
|
|
||||||
|
> **مشکل:** شرط فعلی `wallet.Balance == 0` عملاً غیرممکنه — چون قیمت محصولات متفاوته (مثلاً ۳۵۰,۰۰۰ تومان، ۱,۲۰۰,۰۰۰ تومان، ...) کاربر هیچوقت نمیتونه دقیقاً به صفر برسه.
|
||||||
|
|
||||||
|
**تصمیم:** آستانه ثابت **۱,۰۰۰,۰۰۰ ریال (۱۰۰,۰۰۰ تومان)** — هم برای ورود به Magic، هم برای تشخیص تکمیل چرخه.
|
||||||
|
|
||||||
|
**مکانهای تاثیر:**
|
||||||
|
|
||||||
|
| # | فایل | شرط فعلی | شرط جدید |
|
||||||
|
|---|------|----------|----------|
|
||||||
|
| 1 | `UserOrderService.cs` L342 | `if (wallet.Balance == 0)` — ورود به Magic | `if (wallet.Balance <= MagicWalletEntryThreshold)` |
|
||||||
|
| 2 | `UserOrderService.cs` L342 | `if (wallet.Balance == 0)` — EXIT Magic check | `if (wallet.Balance <= MagicWalletEntryThreshold)` |
|
||||||
|
| 3 | خرید مجدد guard | `PackagePurchaseMethod == None` | + `Balance <= Threshold` |
|
||||||
|
|
||||||
|
**تعریف ثابت:**
|
||||||
|
```csharp
|
||||||
|
// SystemConstants.cs یا Package entity
|
||||||
|
public const long MagicWalletEntryThreshold = 1_000_000; // 100,000 تومان = 1,000,000 ریال
|
||||||
|
```
|
||||||
|
|
||||||
|
**فلوی جدید:**
|
||||||
|
```
|
||||||
|
کاربر خرید میکند → Balance = 850,000 ریال (کمتر از 1M)
|
||||||
|
→ سیستم: Balance <= 1,000,000 ✅ → ورود به Magic Mode
|
||||||
|
→ قبلاً: Balance == 0 ❌ → کاربر گیر میافتاد!
|
||||||
|
|
||||||
|
Magic تکمیل → Balance = 200,000 ریال
|
||||||
|
→ سیستم: Balance <= 1,000,000 ✅ → EXIT Magic → مجاز به خرید مجدد
|
||||||
|
→ قبلاً: Balance == 0 ❌ → کاربر تا ابد در Magic!
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **نکته:** این آستانه **ثابت (global)** تعریف میشه — نه per-package. چون مربوط به قیمت محصولات فروشگاهه، نه پکیج.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q25 — DayaLoans فقط پکیج پایه
|
||||||
|
|
||||||
|
> **تایید:** وام دایا فعلاً فقط برای پکیج پایه فعاله و تغییر نمیکنه.
|
||||||
|
|
||||||
|
**وضعیت فعلی:**
|
||||||
|
- فیلد `Package.SupportsDayaPurchase` روی پکیج پایه = `true`، نقرهای = `false`
|
||||||
|
- `CheckAndProcessDayaLoansCommandHandler` فقط پکیجهایی که `SupportsDayaPurchase = true` دارن رو بررسی میکنه
|
||||||
|
- این یک تصمیم بیزینسی ثابته — اگر در آینده تغییر کرد، ادمین از BackOffice فلگ رو تغییر میده
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q26 — SP Worker (مدیریت خودکار Stored Procedures) 🔴
|
||||||
|
|
||||||
|
> **مشکل:** الان SPها **کاملاً دستی** deploy میشن. اگه SP تغییر کنه، باید یادمون باشه دستی اجرا کنیم.
|
||||||
|
|
||||||
|
**تصمیم:** یک `IHostedService` به نام `StoredProcedureDeploymentService` ایجاد بشه.
|
||||||
|
|
||||||
|
**الگوریتم:**
|
||||||
|
```
|
||||||
|
1. Startup → خواندن فایلهای .sql از Embedded Resources
|
||||||
|
2. برای هر فایل:
|
||||||
|
a. محاسبه SHA256 checksum محتوا
|
||||||
|
b. خواندن checksum قبلی از جدول "CMS"."StoredProcedureVersions"
|
||||||
|
c. اگر وجود نداشت یا checksum فرق داشت:
|
||||||
|
→ اجرای CREATE OR ALTER PROCEDURE
|
||||||
|
→ ذخیره checksum جدید
|
||||||
|
→ لاگ: "SP {name} deployed/updated"
|
||||||
|
d. اگر checksum برابر بود:
|
||||||
|
→ لاگ: "SP {name} is up-to-date, skipping"
|
||||||
|
3. اجرا بعد از EF Migration (ترتیب startup)
|
||||||
|
```
|
||||||
|
|
||||||
|
**جدول جدید:**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE "CMS"."StoredProcedureVersions" (
|
||||||
|
"Id" BIGSERIAL PRIMARY KEY,
|
||||||
|
"Name" VARCHAR(256) NOT NULL UNIQUE, -- نام SP
|
||||||
|
"Checksum" VARCHAR(64) NOT NULL, -- SHA256 hash
|
||||||
|
"DeployedAt" TIMESTAMP NOT NULL, -- آخرین deploy
|
||||||
|
"Version" INT NOT NULL DEFAULT 1 -- شمارنده نسخه
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**فایلهای SP فعلی (embed شوند):**
|
||||||
|
|
||||||
|
| # | فایل | خطوط |
|
||||||
|
|---|------|------|
|
||||||
|
| 1 | `sp_CalculateWeeklyBalances.sql` | ۳۷۲ |
|
||||||
|
| 2 | `sp_CalculateWeeklyCommissionPool.sql` | ۲۶۷ |
|
||||||
|
|
||||||
|
**csproj تغییرات:**
|
||||||
|
```xml
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Persistence\StoredProcedures\*.sql" />
|
||||||
|
</ItemGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q27 — History Tables — یکسانسازی و ثبت خودکار 🟡
|
||||||
|
|
||||||
|
> **مشکل:** نامگذاری history tableها ناسازگاره و ثبت تاریخچه دستیه.
|
||||||
|
|
||||||
|
**وضعیت فعلی:**
|
||||||
|
|
||||||
|
| Master Entity | History Entity | نامگذاری |
|
||||||
|
|---------------|---------------|----------|
|
||||||
|
| `UserCommissionPayout` | `CommissionPayoutStatusHistory` | ❌ نام متفاوت |
|
||||||
|
| `CommissionCashWithdrawal` | `CommissionCashWithdrawalStatusHistory` | ✅ مشابه |
|
||||||
|
| `DiscountOrder` | `DiscountOrderStatusHistory` | ✅ مشابه |
|
||||||
|
| `UserWallet` | `UserWalletChangeLog` | ❌ نام متفاوت |
|
||||||
|
| `Package` | ❌ ندارد | 🔴 |
|
||||||
|
| `ClubMembership` | ❌ ندارد | 🔴 |
|
||||||
|
| `ClubMembershipCycle` | ❌ ندارد | 🔴 |
|
||||||
|
|
||||||
|
**تصمیم — ۲ بخش:**
|
||||||
|
|
||||||
|
**بخش ۱: نامگذاری یکسان**
|
||||||
|
```
|
||||||
|
الگو: {MasterEntityName}History
|
||||||
|
|
||||||
|
مثال:
|
||||||
|
UserCommissionPayout → UserCommissionPayoutHistory (rename از CommissionPayoutStatusHistory)
|
||||||
|
UserWallet → UserWalletHistory (rename از UserWalletChangeLog)
|
||||||
|
Package → PackageHistory (جدید)
|
||||||
|
ClubMembership → ClubMembershipHistory (جدید)
|
||||||
|
ClubMembershipCycle → ClubMembershipCycleHistory (جدید)
|
||||||
|
```
|
||||||
|
|
||||||
|
**بخش ۲: ثبت خودکار تغییرات**
|
||||||
|
```
|
||||||
|
دو رویکرد:
|
||||||
|
|
||||||
|
A) EF Interceptor (پیشنهادی):
|
||||||
|
→ در SaveChangesInterceptor، entityهای تغییریافته شناسایی
|
||||||
|
→ برای هر entity که IHasHistory پیاده کرده:
|
||||||
|
snapshot فعلی → رکورد History جدید
|
||||||
|
→ مزیت: یکجا و خودکار
|
||||||
|
|
||||||
|
B) Domain Events:
|
||||||
|
→ هر entity یک EntityChangedEvent publish کنه
|
||||||
|
→ Handler مربوطه History ثبت کنه
|
||||||
|
→ مزیت: async + decoupled
|
||||||
|
```
|
||||||
|
|
||||||
|
**Interface پیشنهادی:**
|
||||||
|
```csharp
|
||||||
|
public interface IHasHistory<THistory> where THistory : BaseHistoryEntity
|
||||||
|
{
|
||||||
|
THistory CreateHistorySnapshot(string action, long? changedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class BaseHistoryEntity : BaseEntity
|
||||||
|
{
|
||||||
|
public long MasterEntityId { get; set; } // FK به entity اصلی
|
||||||
|
public string Action { get; set; } // Created, Updated, StatusChanged, ...
|
||||||
|
public long? ChangedBy { get; set; } // UserId تغییردهنده
|
||||||
|
public DateTime ChangedAt { get; set; } // زمان تغییر
|
||||||
|
public string? Snapshot { get; set; } // JSON snapshot (اختیاری)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q28 — UI Guidance (آموزش و هشدار در FO/BO) 🟡
|
||||||
|
|
||||||
|
> **تصمیم:** در سراسر FrontOffice و BackOffice، متنهای آموزشی و هشداری اضافه بشه.
|
||||||
|
|
||||||
|
**انواع Guidance:**
|
||||||
|
|
||||||
|
| نوع | جایگاه | مثال |
|
||||||
|
|-----|--------|------|
|
||||||
|
| **مودال آموزشی** | اولین بار باز کردن صفحه | «سیستم پکیجبیس: شما میتوانید بعد از تکمیل چرخه، پکیج جدید بخرید» |
|
||||||
|
| **متن inline** | کنار فیلد/آیتم | «💡 هزینه فعالسازی از قیمت پکیج محاسبه میشود» |
|
||||||
|
| **هشدار** | قبل از اقدام بحرانی | «⚠️ با تغییر پکیج، فیچرهای قبلی ممکنه غیرفعال بشن» |
|
||||||
|
| **Tooltip** | روی آیکون اطلاعات | «این مبلغ بر اساس پکیج فعال شما محاسبه شده» |
|
||||||
|
|
||||||
|
**صفحات هدف FrontOffice:**
|
||||||
|
|
||||||
|
| # | صفحه | نوع | محتوا |
|
||||||
|
|---|-------|-----|-------|
|
||||||
|
| G1 | صفحه پکیجها | مودال (اولین بار) | توضیح سیستم پکیجبیس، تفاوت پکیجها، نحوه خرید |
|
||||||
|
| G2 | Checkout | متن inline | «این پکیج {features} را شامل میشود» |
|
||||||
|
| G3 | MyPackages | متن inline | «بعد تکمیل چرخه جادویی، میتوانید پکیج جدید بخرید» |
|
||||||
|
| G4 | Magic Wallet | هشدار مودال | «سقف واریز و ضریب بر اساس پکیج شماست» |
|
||||||
|
| G5 | Commission Dashboard | tooltip | «پاداش بر اساس هر پکیج جداگانه محاسبه میشود» |
|
||||||
|
| G6 | قرارداد باشگاه | متن inline | «این قرارداد فقط یکبار امضا میشود» |
|
||||||
|
| G7 | ActivationSection | متن inline | «هزینه تخمینی بر اساس پکیج {name} محاسبه شده» |
|
||||||
|
|
||||||
|
**صفحات هدف BackOffice:**
|
||||||
|
|
||||||
|
| # | صفحه | نوع | محتوا |
|
||||||
|
|---|-------|-----|-------|
|
||||||
|
| G8 | Package CRUD | متن inline | «تغییر قیمت روی کاربران فعلی اثر ندارد — فقط خریدهای جدید» |
|
||||||
|
| G9 | Feature Matrix | tooltip | «فیچرهای تیکخورده برای خریداران این پکیج فعال میشود» |
|
||||||
|
| G10 | Manual Payment | هشدار | «مبلغ پرداخت باید مطابق قیمت پکیج انتخابی باشد» |
|
||||||
|
| G11 | Commission Reports | متن inline | «هر پکیج Pool پورسانت مستقل دارد» |
|
||||||
|
| G12 | User Detail | متن inline | «پکیج فعلی: {name} — چرخه: {n}» |
|
||||||
|
| G13 | Club Members | tooltip | «فیلتر بر اساس آخرین پکیج خریداریشده» |
|
||||||
|
|
||||||
|
**پیادهسازی فنی:**
|
||||||
|
```razor
|
||||||
|
@* FrontOffice — کامپوننت Guidance قابل استفاده مجدد *@
|
||||||
|
<PackageGuidance
|
||||||
|
Page="Packages"
|
||||||
|
Type="Modal"
|
||||||
|
ShowOnce="true"
|
||||||
|
Title="سیستم پکیجبیس"
|
||||||
|
Content="@_guidanceContent" />
|
||||||
|
|
||||||
|
@* BackOffice — متن inline *@
|
||||||
|
<MudAlert Severity="Severity.Info" Dense="true" Class="mb-2">
|
||||||
|
💡 تغییر قیمت روی کاربران فعلی اثر ندارد — فقط خریدهای جدید
|
||||||
|
</MudAlert>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q29 — شرط EXIT Magic — تایید: آخرین پکیج فعال
|
||||||
|
|
||||||
|
> **تایید:** شرط EXIT از `ClubMembershipCycle` فعلی (`IsCurrentCycle=true`) → `PackageId` → `Package.MagicWalletMaxDeposit` خوانده میشه.
|
||||||
|
|
||||||
|
**فلوی دقیق:**
|
||||||
|
```
|
||||||
|
1. UserOrderService.ProcessOrderAsync → wallet.Balance کم میشه
|
||||||
|
2. if (Balance <= Threshold): ← Q24 آستانه جدید
|
||||||
|
a. اگر Normal + خرید کرده + باشگاه فعال → ENTER Magic
|
||||||
|
b. اگر Magic:
|
||||||
|
→ خواندن چرخه فعلی → PackageId → Package.MagicWalletMaxDeposit
|
||||||
|
→ if (MagicTotalDeposited >= MaxDeposit) → EXIT Magic
|
||||||
|
→ else: هنوز Magic (میتونه شارژ کنه)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Fallback:** اگه چرخه فعالی نبود → `IsBasePackage = true` (پکیج پایه)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q30 — Carryover — توضیح ساده (v6 اصلاحشده)
|
||||||
|
|
||||||
|
> **Carryover = باقیمانده تعادل از هفته قبل — بر اساس پکیج زیرمجموعهها**
|
||||||
|
|
||||||
|
**مثال ساده:**
|
||||||
|
```
|
||||||
|
علی بالاسری — زیرمجموعههاش پکیج پایه دارن:
|
||||||
|
|
||||||
|
هفته ۱۰:
|
||||||
|
تیم چپ (پکیج پایه) = ۵۰ نفر، تیم راست (پکیج پایه) = ۳۰ نفر
|
||||||
|
تعادل_پایه = MIN(50, 30) = 30
|
||||||
|
باقیمانده_پایه: {چپ: 20, راست: 0} ← CARRYOVER
|
||||||
|
|
||||||
|
هفته ۱۱:
|
||||||
|
اعضای جدید (پکیج پایه): چپ = 5، راست = 10
|
||||||
|
جمع با carryover: چپ = 5+20 = 25، راست = 10+0 = 10
|
||||||
|
تعادل_پایه = MIN(25, 10) = 10
|
||||||
|
باقیمانده_پایه: {چپ: 15, راست: 0} ← CARRYOVER بعدی
|
||||||
|
```
|
||||||
|
|
||||||
|
**تغییر پکیج خود کاربر — تاثیری ندارد (Q23 اصلاحی):**
|
||||||
|
```
|
||||||
|
علی پکیج خودش رو عوض میکنه (پایه → نقرهای):
|
||||||
|
|
||||||
|
carryover_پایه = {چپ: 20, راست: 0} ← هنوز هست! حذف نمیشه!
|
||||||
|
carryover_نقرهای = {چپ: 0, راست: 0}
|
||||||
|
|
||||||
|
✅ هفته بعد، علی هنوز از Pool پایه سود میبره
|
||||||
|
(چون زیرمجموعههایی با پکیج پایه داره)
|
||||||
|
✅ همزمان از Pool نقرهای هم سود میبره
|
||||||
|
(چون زیرمجموعههایی با پکیج نقرهای هم داره)
|
||||||
|
|
||||||
|
پکیج خود علی فقط تعیین میکنه:
|
||||||
|
→ ActivationFeeاش به کدوم Pool بره
|
||||||
|
→ MagicWallet سقفش چقدره
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 💰 سیستم مالی، پرداخت و درگاهها
|
# 💰 سیستم مالی، پرداخت و درگاهها
|
||||||
|
|
||||||
> **منابع ادغامشده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md`
|
> **منابع ادغامشده:** `payment-gateway.md`, `payment-architecture-pyms.md`, `daya-loan-integration.md`, `manual-payment-system.md`, `discount-shop-business.md`
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس WalletChangeLog + validation کیفپول اعتباری + نامگذاری جدید کیفپولها)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: تصحیح مدل تومان/ریال + فیکس ZarinPal Verify + امنیت Callback URL)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -47,21 +47,41 @@ flowchart TD
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A["کاربر → انتخاب محصول\nدرخواست پرداخت"] --> B["CMS → CreatePaymentRequest\ngRPC to PYMS"]
|
A["کاربر → انتخاب محصول\nدرخواست پرداخت"] --> B["CMS → CreatePaymentRequest\ngRPC to PYMS"]
|
||||||
B --> C["PYMS → ZarinPal API\nدریافت Authority"]
|
B --> C["PYMS → ZarinPal API\nمبلغ ×۱۰ (تومان→ریال)\nدریافت Authority"]
|
||||||
C --> D["Redirect کاربر\nصفحه پرداخت ZarinPal"]
|
C --> D["Redirect کاربر\nصفحه پرداخت ZarinPal"]
|
||||||
D --> E["بازگشت با Authority\nCMS VerifyPayment"]
|
D --> E["بازگشت با Authority\nCMS VerifyPayment (مبلغ ×۱۰)"]
|
||||||
E -->|موفق| F["✅ ثبت سفارش\n+ شارژ کیفپول"]
|
E -->|موفق| F["✅ ثبت سفارش\n+ شارژ کیفپول"]
|
||||||
E -->|ناموفق| G["❌ نمایش پیام خطا"]
|
E -->|ناموفق| G["❌ نمایش پیام خطا"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **✅ فیکس ZarinPal Verify (اسفند ۱۴۰۴ — `721661a`):**
|
||||||
|
> - **باگ:** `VerifyPaymentAsync(authority)` با ۲ آرگومان → amount=0 → ZarinPal Code=-1
|
||||||
|
> - **فیکس:** lookup `PaymentTransaction.Amount` از DB + استفاده از overload ۳ آرگومانه `VerifyPaymentAsync(authority, orderId, amount)`
|
||||||
|
> - `IPaymentGatewayService` — default impl ۳ آرگومانه اضافه شد
|
||||||
|
> - ۷ فایل تغییر: PackageService, TransactionsService, VerifyDiscountWalletChargeCommandHandler, VerifyPackagePurchaseCommandHandler, IPaymentGatewayService, MockPaymentGatewayService, DayaPaymentService
|
||||||
|
|
||||||
### ۲.۲ تنظیمات ZarinPal
|
### ۲.۲ تنظیمات ZarinPal
|
||||||
|
|
||||||
| پارامتر | مقدار |
|
| پارامتر | مقدار |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
| `MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
|
| `MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
|
||||||
| `CallbackUrl` | `/payment/callback` |
|
| `CallbackUrl` | از `appsettings.json` خوانده میشود (نه از ورودی کاربر) |
|
||||||
| `Sandbox` | `true` (staging) / `false` (production) |
|
| `Sandbox` | `true` (staging) / `false` (production) |
|
||||||
| `Currency` | IRR (ریال → تبدیل به تومان در UI) |
|
| `Currency` | DB: تومان — ZarinPal: ریال (×۱۰ هنگام ارسال) |
|
||||||
|
|
||||||
|
> **✅ مدل ارزی (تصحیح اسفند ۱۴۰۴):**
|
||||||
|
> - **DB:** `Package.Price` و همه مبالغ مالی به **تومان** ذخیره میشوند
|
||||||
|
> - **CMS → ZarinPal:** `ZarinPalPaymentService` مبلغ را ×۱۰ میکند (`amountInRials = amount * 10`)
|
||||||
|
> - **FrontOffice UI:** مبالغ مستقیم به تومان نمایش داده میشوند (بدون تبدیل)
|
||||||
|
> - **FrontOffice → CMS:** مبالغ به تومان ارسال میشوند (FO هیچ تبدیلی انجام نمیدهد)
|
||||||
|
> - **باگ قبلی ۱:** FO مبلغ تومان را ×۱۰ تبدیل میکرد + CMS/ZarinPal دوباره ×۱۰ → مبلغ ۱۰۰ برابر (فیکس: `2f9ef15`)
|
||||||
|
> - **باگ قبلی ۲:** `FormattedPrice = Price / 10` اشتباه بود — Price از قبل تومان است (فیکس: `3c1a8ff` اصلاح شد)
|
||||||
|
|
||||||
|
> **✅ امنیت Callback URL (اسفند ۱۴۰۴):**
|
||||||
|
> - هیچ callback URL از ورودی کاربر خوانده نمیشود — همه از `appsettings.json` خوانده میشوند
|
||||||
|
> - `PackageService` و `TransactionsService`: از `FrontOfficeBaseUrl` config
|
||||||
|
> - `MagicWallet` و `DiscountWallet`: از `CmsBaseUrl` config
|
||||||
|
> - جلوگیری از حمله Open Redirect
|
||||||
|
|
||||||
> **تنظیمات محیطی:**
|
> **تنظیمات محیطی:**
|
||||||
> - `appsettings.json` + `appsettings.Staging.json`: `UseSandbox: true` (تست)
|
> - `appsettings.json` + `appsettings.Staging.json`: `UseSandbox: true` (تست)
|
||||||
@@ -239,11 +259,16 @@ service PaymentService {
|
|||||||
| ماژول | وضعیت | یادداشت |
|
| ماژول | وضعیت | یادداشت |
|
||||||
|-------|--------|---------|
|
|-------|--------|---------|
|
||||||
| ZarinPal IPG | ✅ کامل | **Production فعال** — MerchantId: `4225d555...` |
|
| ZarinPal IPG | ✅ کامل | **Production فعال** — MerchantId: `4225d555...` |
|
||||||
|
| ZarinPal Verify | ✅ فیکس شده | رفع amount=0 با overload ۳ آرگومانه (`721661a`) |
|
||||||
|
| Callback URL امنیت | ✅ فیکس شده | همه از config خوانده میشوند — جلوگیری از Open Redirect |
|
||||||
| وام دایا | ✅ کامل | Mock mode فعال در staging |
|
| وام دایا | ✅ کامل | Mock mode فعال در staging |
|
||||||
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
|
| پرداخت ترکیبی | ✅ کامل | Discount + IPG |
|
||||||
| Pool هفتگی | ✅ کامل | SP + Hangfire |
|
| Pool هفتگی | ✅ کامل | SP + Hangfire |
|
||||||
| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیفپول در ۳ هندلر اضافه شد |
|
| WalletChangeLog | ✅ فیکس شده | لاگ تغییرات کیفپول در ۳ هندلر اضافه شد |
|
||||||
| Validation کیفپول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد |
|
| Validation کیفپول اعتباری | ✅ فیکس شده | ارور اگر موجودی کافی نباشد |
|
||||||
|
| Toman/Rial مدل | ✅ تصحیح شده | DB=تومان، فقط ZarinPal ریال (×۱۰) — FO بدون تبدیل |
|
||||||
|
| صفحه موفقیت پرداخت | ✅ بهبود | TransactionId + موجودی واقعی + دکمه بازگشت |
|
||||||
|
| PackagePurchaseDialog | ✅ کامل | دیالوگ داینامیک کاشیای با انتخاب روش پرداخت |
|
||||||
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
|
| پرداخت دستی | ⬜ طراحی | نیاز به تصمیم مدیریت |
|
||||||
| Refund | ⬜ طراحی | فقط در PYMS تعریفشده |
|
| Refund | ⬜ طراحی | فقط در PYMS تعریفشده |
|
||||||
| کیفپول جادویی (Magic) | ✅ کامل | فاز 1-6 پیادهسازی شده — Production فعال |
|
| کیفپول جادویی (Magic) | ✅ کامل | فاز 1-6 پیادهسازی شده — Production فعال |
|
||||||
|
|||||||
@@ -0,0 +1,700 @@
|
|||||||
|
# 📦 راهنمای مهاجرت سیستم پکیجبیس — خلاصه تغییرات و پلن استقرار
|
||||||
|
|
||||||
|
> **وضعیت:** آماده تست و استقرار — **Q1-Q30 تکمیلشده ✅ | F1-F11 تکمیلشده ✅**
|
||||||
|
> **تاریخ:** ۸ اسفند ۱۴۰۴ (27 Feb 2026) — آپدیت ۱۰ اسفند
|
||||||
|
> **نسخه NuGet:** v0.0.189
|
||||||
|
> **تعداد کامیتها:** ۵۱+ کامیت در ۴ ریپازیتوری (۲۱ CMS + ۹ FO + ۷ BO + ۱۴+ docs)
|
||||||
|
> **مدت پیادهسازی:** ۶ روز (۲۴ فوریه – ۱ مارس ۲۰۲۶)
|
||||||
|
> **ریپوها:** CMS (`gitea`/`kub-stage`) · FrontOffice (`kub-stage`) · BackOffice (`kub-stage`) · totalDoc (`foursatDocs`/`main`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست مطالب
|
||||||
|
|
||||||
|
1. [خلاصه اجرایی](#1-خلاصه-اجرایی)
|
||||||
|
2. [چه چیزی تغییر کرده؟ — نمای بیزینسی](#2-چه-چیزی-تغییر-کرده--نمای-بیزینسی)
|
||||||
|
3. [بخشهای تحت تاثیر سیستم](#3-بخشهای-تحت-تاثیر-سیستم)
|
||||||
|
4. [جزئیات تغییرات هر ریپو](#4-جزئیات-تغییرات-هر-ریپو)
|
||||||
|
5. [پلن مهاجرت مرحلهبهمرحله](#5-پلن-مهاجرت-مرحلهبهمرحله)
|
||||||
|
6. [Rollback Plan](#6-rollback-plan)
|
||||||
|
7. [چکلیست تست قبل از Production](#7-چکلیست-تست-قبل-از-production)
|
||||||
|
8. [ریسکها و نکات بحرانی](#8-ریسکها-و-نکات-بحرانی)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. خلاصه اجرایی
|
||||||
|
|
||||||
|
### قبل (سیستم تکپکیج):
|
||||||
|
- فقط **یک پکیج پایه** (۵۶ میلیون تومان) وجود داشت
|
||||||
|
- تمام مقادیر مالی (قیمت، هزینه فعالسازی، ضرایب، سقفها) **hardcoded** در کد بودند
|
||||||
|
- خرید مجدد پکیج **غیرممکن** بود (حتی بعد تکمیل چرخه)
|
||||||
|
- پورسانت فقط از **یک Pool واحد** محاسبه میشد
|
||||||
|
- همه کاربران **همه فیچرها** را دریافت میکردند
|
||||||
|
|
||||||
|
### بعد (سیستم چندپکیجی):
|
||||||
|
- سیستم **N پکیج** با قیمت و ویژگیهای متفاوت پشتیبانی میکند
|
||||||
|
- تمام مقادیر مالی از **دیتابیس (Package entity)** خوانده میشوند
|
||||||
|
- خرید مجدد بعد تکمیل چرخه Magic Wallet **فعال** شده
|
||||||
|
- هر پکیج **Commission Pool مستقل** خود را دارد
|
||||||
|
- فیچرها **per-package** هستند و با الگوریتم **DIFF** مدیریت میشوند
|
||||||
|
- قرارداد باشگاه **فقط یک بار** (اولین خرید) امضا میشود
|
||||||
|
|
||||||
|
### آمار تغییرات:
|
||||||
|
|
||||||
|
| شاخص | مقدار |
|
||||||
|
|-------|-------|
|
||||||
|
| فایلهای تغییریافته | **۲۲۷+ فایل** |
|
||||||
|
| خطوط اضافهشده | **+۱۷,۰۰۰+** |
|
||||||
|
| خطوط حذفشده | **−۲,۶۶۰+** |
|
||||||
|
| تصمیمات بیزینسی پیادهشده | **۳۰ تصمیم** (Q1–Q30) |
|
||||||
|
| باگهای فیکسشده | **۶ باگ بحرانی** |
|
||||||
|
| مقادیر hardcoded حذفشده | **۱۵+ مورد** |
|
||||||
|
| Handlerهای deprecated حذفشده | **۴ handler** (۱۲ فایل) |
|
||||||
|
| RPCهای deprecated حذفشده | **۴ RPC** + ۸ message type |
|
||||||
|
| فایلهای rename شده | **۳۴ فایل** + ۱۱ دایرکتوری (UserWalletChangeLog → UserWalletHistory) |
|
||||||
|
| History Tables جدید | **۳ جدول** (PackageHistories, ClubMembershipCycleHistories, UserWalletHistories) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. چه چیزی تغییر کرده؟ — نمای بیزینسی
|
||||||
|
|
||||||
|
### 2.1 🏪 مدل فروش پکیج
|
||||||
|
|
||||||
|
| قابلیت | قبل | بعد |
|
||||||
|
|--------|-----|------|
|
||||||
|
| تعداد پکیج | ۱ (پایه ۵۶M) | **N پکیج** (پایه ۵۶M + نقرهای ۵.۶M + ...) |
|
||||||
|
| قیمتگذاری | hardcoded `56_000_000` | از `Package.Price` در دیتابیس |
|
||||||
|
| هزینه فعالسازی | hardcoded `25_200_000` | از `Package.ActivationFee` |
|
||||||
|
| ضریب تخفیف | hardcoded `× 2` | از `Package.DiscountMultiplier` |
|
||||||
|
| پشتیبانی دایا | فقط پکیج پایه | بر اساس `Package.SupportsDayaPurchase` |
|
||||||
|
| پرداخت مستقیم | همه | بر اساس `Package.SupportsDirectPurchase` |
|
||||||
|
|
||||||
|
### 2.2 🔄 چرخه خرید مجدد (Re-Purchase)
|
||||||
|
|
||||||
|
| مرحله | قبل | بعد |
|
||||||
|
|-------|-----|------|
|
||||||
|
| تکمیل چرخه Magic | کاربر در بنبست | `PackagePurchaseMethod = None` ریست میشود |
|
||||||
|
| خرید مجدد | **مسدود** (guard G1-G3) | **مجاز** — بعد تکمیل چرخه Magic |
|
||||||
|
| قرارداد باشگاه | هر بار | **فقط یک بار** — خرید مجدد Skip (Q19) |
|
||||||
|
| فیچرها | همه فیچرها بدون توجه به پکیج | **DIFF/تفاضل** — فقط اختلاف اعمال میشود (Q20) |
|
||||||
|
| تاریخچه | فقط `ActivatedAt` | `FirstActivationDate` + `LastActivationDate` (Q21) |
|
||||||
|
|
||||||
|
### 2.3 💰 پورسانت و تعادلها
|
||||||
|
|
||||||
|
| ویژگی | قبل | بعد |
|
||||||
|
|-------|-----|------|
|
||||||
|
| Commission Pool | ۱ Pool واحد | **Pool جداگانه هر پکیج** |
|
||||||
|
| تعادل هفتگی | ۱ رکورد per user/week | **N رکورد** per user/week/package |
|
||||||
|
| MaxBalancesPerLeg | hardcoded `300` | per-package (پایه=۳۰۰, نقرهای=۳۰) |
|
||||||
|
| MaxNetworkLevel | hardcoded `15` | per-package از دیتابیس |
|
||||||
|
| Carryover | یکپارچه | **per-downline-package** — بر اساس پکیج زیرمجموعهها (تغییر پکیج خود کاربر تاثیری ندارد) |
|
||||||
|
| Stored Procedure | پارامترهای ثابت | پارامترهای داینامیک از Package entity |
|
||||||
|
| گزارش مشتری | بدون تفکیک | **breakdown per-package** |
|
||||||
|
| گزارش ادمین | بدون فیلتر | **فیلتر بر اساس پکیج** |
|
||||||
|
|
||||||
|
### 2.4 🪄 کیف پول جادویی (Magic Wallet)
|
||||||
|
|
||||||
|
| ویژگی | قبل | بعد |
|
||||||
|
|-------|-----|------|
|
||||||
|
| ضریب جادویی | hardcoded `× 2.5` | از `Package.MagicWalletMultiplier` |
|
||||||
|
| سقف واریز | hardcoded `1,000,000,000` | از `Package.MagicWalletMaxDeposit` |
|
||||||
|
| سقف اعتبار | hardcoded `2,500,000,000` | از `Package.MagicWalletMaxCredit` |
|
||||||
|
| شرط EXIT | بررسی سقف global | بررسی سقف **per-package** |
|
||||||
|
|
||||||
|
### 2.5 📋 فیچرهای باشگاه
|
||||||
|
|
||||||
|
| ویژگی | قبل | بعد |
|
||||||
|
|-------|-----|------|
|
||||||
|
| تخصیص فیچر | `GetAllFeatureIds()` — همه فیچرها | از `Package.PackageFeatures` — per-package |
|
||||||
|
| خرید مجدد | — | الگوریتم **DIFF**: مقایسه فیچرهای فعلی با پکیج جدید |
|
||||||
|
| مدیریت ادمین | — | ماتریس checkbox پکیج × فیچر در BackOffice |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. بخشهای تحت تاثیر سیستم
|
||||||
|
|
||||||
|
### 3.1 نقشه تاثیرگذاری
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 🏗️ سیستم پکیجبیس — Impact Map │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─── CMS (Backend) ──────────────────────────────────────────────────┐ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 📦 Domain Layer (Entity تغییرات) │ │
|
||||||
|
│ │ ├── Package.cs ← +۱۱ فیلد جدید │ │
|
||||||
|
│ │ ├── PackageFeature.cs ← Entity کاملاً جدید │ │
|
||||||
|
│ │ ├── ClubMembership.cs ← ActivatedAt → ۴ فیلد First/Last │ │
|
||||||
|
│ │ ├── ClubMembershipCycle.cs ← +PackageId │ │
|
||||||
|
│ │ ├── WeeklyCommissionPool.cs ← +PackageId │ │
|
||||||
|
│ │ ├── UserCommissionPayout.cs ← +PackageId │ │
|
||||||
|
│ │ ├── NetworkWeeklyBalance.cs ← +PackageId │ │
|
||||||
|
│ │ └── SystemConstants.cs ← حذف ۹ ثابت منسوخ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ⚙️ Application Layer (Handler تغییرات) │ │
|
||||||
|
│ │ ├── ActivateClubMembershipCommandHandler ← فیچر DIFF + re-activate│ │
|
||||||
|
│ │ ├── AcceptClubMembershipContractCommandHandler ← فیچر DIFF │ │
|
||||||
|
│ │ ├── VerifyPackagePurchaseCommandHandler ← حذف fallback 2.0m │ │
|
||||||
|
│ │ ├── CustomerPurchasePackage/Verify ← Generic purchase flow │ │
|
||||||
|
│ │ ├── ChargeMagicWalletCommandHandler ← سقف per-package │ │
|
||||||
|
│ │ ├── VerifyMagicWalletChargeCommandHandler ← ضریب per-package │ │
|
||||||
|
│ │ ├── UserOrderService (EXIT Magic) ← ریست + سقف per-package │ │
|
||||||
|
│ │ ├── CreateManualPaymentCommandHandler ← ضریب از Package │ │
|
||||||
|
│ │ └── CheckAndProcessDayaLoansCommandHandler ← حذف ID=4 │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 🔌 Infrastructure Layer │ │
|
||||||
|
│ │ ├── sp_CalculateWeeklyBalances ← @PackageId + @Max params │ │
|
||||||
|
│ │ ├── sp_CalculateWeeklyCommissionPool ← @PackageId │ │
|
||||||
|
│ │ ├── WeeklyCommissionCalculationService ← Loop per-package │ │
|
||||||
|
│ │ ├── OrmCommissionCalculationStrategy ← فیلتر PackageId │ │
|
||||||
|
│ │ └── SpCommissionCalculationStrategy ← پارامترهای داینامیک │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ 📡 Proto/gRPC Layer │ │
|
||||||
|
│ │ ├── package.proto ← ۱۱ فیلد + PackageFeature CRUD │ │
|
||||||
|
│ │ ├── commission.proto ← package_id/title در ۴ model + فیلتر │ │
|
||||||
|
│ │ ├── حذف ۴ RPC deprecated (Golden/Base) │ │
|
||||||
|
│ │ └── حذف ۸ message type deprecated │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─── FrontOffice (مشتری) ─────────────────────────────────────────────┐│
|
||||||
|
│ │ ├── Packages.razor ← کاشیهای داینامیک (نه hardcoded) ││
|
||||||
|
│ │ ├── PackageDetail.razor ← فیچرها از API (نه ثابت) ││
|
||||||
|
│ │ ├── Checkout.razor ← پرداخت شرطی (دایا/مستقیم) ││
|
||||||
|
│ │ ├── MyPackages.razor ← خرید مجدد + پیشرفت Magic ││
|
||||||
|
│ │ ├── ActivationSection.razor ← قیمت داینامیک (نه ۵۶M hardcoded) ││
|
||||||
|
│ │ ├── ClubMembershipContractDialog ← متن قرارداد داینامیک ││
|
||||||
|
│ │ ├── CommissionDashboard ← فیلتر + ستون پکیج ││
|
||||||
|
│ │ ├── WeeklyBalancePage ← فیلتر per-package ││
|
||||||
|
│ │ ├── PaymentCallback ← مهاجرت به Customer* RPCs ││
|
||||||
|
│ │ └── حذف "پکیج طلایی" hardcoded (۵+ جا) ││
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
||||||
|
│ │
|
||||||
|
│ ┌─── BackOffice (ادمین) ──────────────────────────────────────────────┐│
|
||||||
|
│ │ ├── Package CRUD ← +۱۲ فیلد جدید در Create/Update ││
|
||||||
|
│ │ ├── PackageFeature Matrix ← checkbox فیچرها ││
|
||||||
|
│ │ ├── ManualPaymentDialog ← حذف ۵۶M hardcoded + Amount editable ││
|
||||||
|
│ │ ├── ChangeParentDialog ← جابجایی در شبکه (جدید) ││
|
||||||
|
│ │ ├── UserPayouts ← فیلتر + ستون پکیج ││
|
||||||
|
│ │ ├── BalancesReport ← فیلتر + ستون پکیج ││
|
||||||
|
│ │ ├── PackageSelect Component ← dropdown قابل استفاده مجدد ││
|
||||||
|
│ │ └── حذف "پکیج طلایی" → "خرید پکیج" ││
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
||||||
|
│ │
|
||||||
|
│ ┌─── Database ────────────────────────────────────────────────────────┐│
|
||||||
|
│ │ ├── Packages ← ۱۱ ستون جدید + Seed نقرهای ││
|
||||||
|
│ │ ├── PackageFeatures ← جدول جدید ││
|
||||||
|
│ │ ├── ClubMemberships ← ۴ ستون First/Last + حذف ActivatedAt ││
|
||||||
|
│ │ ├── ClubMembershipCycles ← +PackageId ││
|
||||||
|
│ │ ├── WeeklyCommissionPools ← +PackageId + Unique ││
|
||||||
|
│ │ ├── UserCommissionPayouts ← +PackageId + Unique ││
|
||||||
|
│ │ ├── NetworkWeeklyBalances ← +PackageId + Unique ││
|
||||||
|
│ │ └── EF Migration + Data Backfill ││
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘│
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 خلاصه آماری per-repo
|
||||||
|
|
||||||
|
| ریپو | کامیت | فایل | اضافه | حذف | شرح اصلی |
|
||||||
|
|-------|-------|------|-------|-----|-----------|
|
||||||
|
| **CMS** | ۲۰ | ۱۷۹+ | +۱۳,۵۸۶ | −۲,۳۲۷ | Domain + Business + Commission + Proto + History + Rename + Interceptor |
|
||||||
|
| **FrontOffice** | ۸ | ۲۹ | +۵۵۰ | −۱۳۶ | Dynamic UI + Customer RPCs + Per-package Reports + UI Guidance |
|
||||||
|
| **BackOffice** | ۶ | ۲۴ | +۵۸۰ | −۳۱ | Package CRUD + Feature Matrix + Per-package Reports + UI Guidance |
|
||||||
|
| **totalDoc** | ۱۴ | ۱۳ | +۲,۷۰۰ | −۱۹۴ | مستندات بیزینسی + تکنیکال + Phase 9 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. جزئیات تغییرات هر ریپو
|
||||||
|
|
||||||
|
### 4.1 CMS — ۲۰ کامیت
|
||||||
|
|
||||||
|
| فاز | کامیت | شرح |
|
||||||
|
|-----|--------|------|
|
||||||
|
| **Phase 0** | `8b9c317` | فیکس ۴ باگ بحرانی: DiscountBalance + UserPackagePurchase |
|
||||||
|
| **Phase 0** | `fe3edd1` | فیکس EXIT Magic Mode — ریست PackagePurchaseMethod + بستن چرخه |
|
||||||
|
| **Phase 1** | `ae92ab8` | زیرساخت Domain: Package +۱۱ فیلد، PackageFeature entity، FKهای جدید |
|
||||||
|
| **Phase 1.5** | `a9cd2fd` | EF Migration + Seed Data + Data Backfill |
|
||||||
|
| **Phase 2** | `8e5c7c5` | جایگزینی همه SystemConstants با Package entity reads |
|
||||||
|
| **Phase 3** | `ccb938e` | بازسازی لایه Package + Proto enhancement + باگفیکس |
|
||||||
|
| **Phase 4** | `0002a5a` | CRUD DTOs + Legacy fixes |
|
||||||
|
| **Phase 5** | `607f791` | پورسانت per-package + حذف ref طلایی |
|
||||||
|
| **SP Fix** | `7176fe4` | فیکس SP: `cm.PackageId` → `cm.LastPackageId` |
|
||||||
|
| **Phase 6** | `d19c569` | Deprecation cleanup + ConfigurationService MagicWallet |
|
||||||
|
| **Phase 7a** | `469d97b` | Cosmetic cleanup + حذف orphan handler |
|
||||||
|
| **Phase 7b** | `161f796` | Embed orderId در callback URL |
|
||||||
|
| **Phase 7c** | `8446e0e` | حذف ۴ handler deprecated (۱۴ فایل، −۱,۱۲۵ خط) |
|
||||||
|
| **Phase 8b** | `ce8e248` | NuGet bump → 0.0.185 |
|
||||||
|
| **Phase 8d** | `7554d70` | حذف ۴ RPC + ۸ message deprecated از Proto |
|
||||||
|
| **Phase 8e** | `aaaf7fc` | Per-package filtering در Commission queries |
|
||||||
|
| **Phase 8f** | `dcd1135` | PackageFeature CRUD support |
|
||||||
|
| **Audit** | `1ac2366` | Compliance audit — Feature DIFF + حذف fallbackهای hardcoded |
|
||||||
|
| **Phase 9a** | `a1024a3` | Q24: آستانه موجودی `≤1M` ریال + Q26: SP Worker auto-deploy (IHostedService + checksum) |
|
||||||
|
| **Phase 9b** | `fdbb91d` | Q27: PackageHistory + ClubMembershipCycleHistory entities + enums + EF configs |
|
||||||
|
| **Phase 9d** | `10d2ca2` | Rename UserWalletChangeLog→UserWalletHistory (86 فایل) + IHasHistory + Interceptor + Migration |
|
||||||
|
|
||||||
|
### 4.2 FrontOffice — ۸ کامیت
|
||||||
|
|
||||||
|
| فاز | کامیت | شرح |
|
||||||
|
|-----|--------|------|
|
||||||
|
| **Phase 7a** | `b82cac4` | حذف "پکیج طلایی" + PackageTitle در DTO |
|
||||||
|
| **Phase 7b** | `71f391a` | مهاجرت به Customer* RPCs |
|
||||||
|
| **Phase 8a** | `0bbc11e` | Checkout wire-up به Customer RPCs |
|
||||||
|
| **Phase 8c** | `d71d463` | صفحات پکیج — فیچرهای داینامیک |
|
||||||
|
| **Phase 8d** | `40882c8` | NuGet bump Proto cleanup |
|
||||||
|
| **Phase 8e** | `a956cb9` | Per-package filtering در Commission pages |
|
||||||
|
| **Phase 8f** | `3bffc13` | T4.2+T4.3+F3: پرداخت شرطی + خرید مجدد + PV |
|
||||||
|
| **Audit** | `816dcb7` | حذف ۵۶M hardcoded — قیمتگذاری داینامیک |
|
||||||
|
| **Phase 9c** | `474d364` | Q28: UI Guidance alerts (G1-G7) — ۷ صفحه MudAlert آموزشی |
|
||||||
|
|
||||||
|
### 4.3 BackOffice — ۶ کامیت
|
||||||
|
|
||||||
|
| فاز | کامیت | شرح |
|
||||||
|
|-----|--------|------|
|
||||||
|
| **Phase 7a** | `f1b0085` | تغییر label "پکیج طلایی" → "خرید پکیج" |
|
||||||
|
| **Phase 8b** | `89f5241` | Package CRUD expansion — ۱۲ فیلد جدید |
|
||||||
|
| **Phase 8d** | `c96377a` | NuGet bump Proto cleanup |
|
||||||
|
| **Phase 8e** | `8be98ae` | Per-package commission filtering + PackageSelect component |
|
||||||
|
| **Phase 8f** | `e020354` | ChangeParentDialog + PackageFeature checkbox matrix |
|
||||||
|
| **Audit** | `e6cf90e` | ManualPaymentDialog — حذف ۵۶M + Amount editable |
|
||||||
|
| **Phase 9c** | `6939780` | Q28: UI Guidance alerts (G8-G13) — ۶ صفحه MudAlert |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. پلن مهاجرت مرحلهبهمرحله
|
||||||
|
|
||||||
|
### 📋 پیشنیازها
|
||||||
|
|
||||||
|
- [ ] بکاپ کامل از دیتابیس Production
|
||||||
|
- [ ] بکاپ از stateهای Kubernetes (Deployments, ConfigMaps)
|
||||||
|
- [ ] اطمینان از دسترسی به Container Registry (تصاویر فعلی)
|
||||||
|
- [ ] زمانبندی Maintenance Window (ترجیحاً شب یا آخر هفته)
|
||||||
|
- [ ] اطلاعرسانی به کاربران (در صورت نیاز به downtime)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۱ از ۶: بکاپ و آمادهسازی محیط 🛡️
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۳۰ دقیقه
|
||||||
|
|
||||||
|
```
|
||||||
|
1.1 بکاپ کامل دیتابیس
|
||||||
|
└── pg_dump -Fc cms_db > cms_backup_pre_package_migration.dump
|
||||||
|
|
||||||
|
1.2 بکاپ دیتابیس BO (اگر جداست)
|
||||||
|
└── pg_dump -Fc bo_db > bo_backup_pre_package_migration.dump
|
||||||
|
|
||||||
|
1.3 ثبت وضعیت فعلی
|
||||||
|
└── تعداد رکوردها:
|
||||||
|
• ClubMemberships: SELECT COUNT(*) ...
|
||||||
|
• ClubMembershipCycles: SELECT COUNT(*) ...
|
||||||
|
• WeeklyCommissionPools: SELECT COUNT(*) ...
|
||||||
|
• UserCommissionPayouts: SELECT COUNT(*) ...
|
||||||
|
• NetworkWeeklyBalances: SELECT COUNT(*) ...
|
||||||
|
• Packages: SELECT COUNT(*) ...
|
||||||
|
|
||||||
|
1.4 ذخیره نسخه فعلی Docker images
|
||||||
|
└── docker tag <current-cms> cms:rollback-point
|
||||||
|
└── docker tag <current-fo> fo:rollback-point
|
||||||
|
└── docker tag <current-bo> bo:rollback-point
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** بکاپها ذخیره شدهاند و قابل restore هستند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۲ از ۶: استقرار CMS (Backend) 🏗️
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۴۵ دقیقه
|
||||||
|
> ⚠️ **ترتیب بحرانی:** CMS باید **اول** deploy شود چون FO و BO به آن وابستهاند.
|
||||||
|
|
||||||
|
```
|
||||||
|
2.1 Build CMS Docker image
|
||||||
|
└── cd CMS/src
|
||||||
|
└── docker build -t cms:package-based .
|
||||||
|
|
||||||
|
2.2 اجرای EF Migration
|
||||||
|
└── این migration شامل:
|
||||||
|
• ۱۱ ستون جدید به جدول Packages
|
||||||
|
• جدول جدید PackageFeatures
|
||||||
|
• ستون PackageId به ۵ جدول (ClubMemberships, Cycles, Pools, Payouts, Balances)
|
||||||
|
• ۴ ستون First/Last به ClubMemberships
|
||||||
|
• Unique Indexها
|
||||||
|
|
||||||
|
⚠️ Migration خودکار اجرا میشود در startup اگر EF auto-migration فعال باشد.
|
||||||
|
✅ اگر دستی: dotnet ef database update
|
||||||
|
|
||||||
|
2.3 Data Backfill — مقداردهی پکیج پایه
|
||||||
|
└── اسکریپت SQL:
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ -- مشخص کردن ID پکیج پایه │
|
||||||
|
│ DO $$ │
|
||||||
|
│ DECLARE base_pkg_id BIGINT; │
|
||||||
|
│ BEGIN │
|
||||||
|
│ SELECT "Id" INTO base_pkg_id │
|
||||||
|
│ FROM "CMS"."Packages" │
|
||||||
|
│ WHERE "IsBasePackage" = true LIMIT 1; │
|
||||||
|
│ │
|
||||||
|
│ -- ClubMemberships │
|
||||||
|
│ UPDATE "CMS"."ClubMemberships" │
|
||||||
|
│ SET "FirstActivationDate" = "ActivatedAt", │
|
||||||
|
│ "LastActivationDate" = "ActivatedAt", │
|
||||||
|
│ "FirstPackageId" = base_pkg_id, │
|
||||||
|
│ "LastPackageId" = base_pkg_id │
|
||||||
|
│ WHERE "FirstActivationDate" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- ClubMembershipCycles │
|
||||||
|
│ UPDATE "CMS"."ClubMembershipCycles" │
|
||||||
|
│ SET "PackageId" = base_pkg_id │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- WeeklyCommissionPools │
|
||||||
|
│ UPDATE "CMS"."WeeklyCommissionPools" │
|
||||||
|
│ SET "PackageId" = base_pkg_id │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- UserCommissionPayouts │
|
||||||
|
│ UPDATE "CMS"."UserCommissionPayouts" │
|
||||||
|
│ SET "PackageId" = base_pkg_id │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- NetworkWeeklyBalances │
|
||||||
|
│ UPDATE "CMS"."NetworkWeeklyBalances" │
|
||||||
|
│ SET "PackageId" = base_pkg_id │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ RAISE NOTICE 'Migration done: PackageId=%', │
|
||||||
|
│ base_pkg_id; │
|
||||||
|
│ END $$; │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
2.4 Verification — بررسی migration
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ SELECT 'ClubMemberships' AS tbl, COUNT(*) │
|
||||||
|
│ FROM "CMS"."ClubMemberships" │
|
||||||
|
│ WHERE "LastPackageId" 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 │
|
||||||
|
│ UNION ALL │
|
||||||
|
│ SELECT 'Balances', COUNT(*) │
|
||||||
|
│ FROM "CMS"."NetworkWeeklyBalances" │
|
||||||
|
│ WHERE "PackageId" IS NULL; │
|
||||||
|
│ │
|
||||||
|
│ -- ✅ همه باید 0 باشند! │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
2.5 Seed پکیج نقرهای (اگر توسط EF Seed انجام نشده)
|
||||||
|
└── INSERT پکیج نقرهای + PackageFeatures
|
||||||
|
|
||||||
|
2.5b اجرای Migration دوم: Q27_HistoryTables_And_RenameWalletHistory
|
||||||
|
└── این migration شامل:
|
||||||
|
• RenameTable: UserWalletChangeLogs → UserWalletHistories (حفظ دادهها!)
|
||||||
|
• RenameIndex × 2 + sp_rename PK + FK × 2
|
||||||
|
• CreateTable: PackageHistories (فیلدهای Old*/New*)
|
||||||
|
• CreateTable: ClubMembershipCycleHistories (فیلدهای Old*/New*)
|
||||||
|
⚠️ دادههای قبلی UserWalletChangeLogs حفظ میشوند (RenameTable نه DropTable)
|
||||||
|
|
||||||
|
2.6 Deploy CMS به Kubernetes
|
||||||
|
└── kubectl set image deployment/cms cms=cms:package-based
|
||||||
|
└── kubectl rollout status deployment/cms
|
||||||
|
|
||||||
|
2.7 Health Check
|
||||||
|
└── curl http://cms-service/health
|
||||||
|
└── بررسی لاگها: kubectl logs deployment/cms --tail=100
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** CMS جدید بالا آمده، migration اجرا شده، همه رکوردها PackageId دارند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۳ از ۶: استقرار FrontOffice 🖥️
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۲۰ دقیقه
|
||||||
|
> پیشنیاز: CMS باید بالا و سالم باشد
|
||||||
|
|
||||||
|
```
|
||||||
|
3.1 Build FrontOffice Docker image
|
||||||
|
└── cd FrontOffice/src
|
||||||
|
└── docker build -t fo:package-based .
|
||||||
|
|
||||||
|
3.2 Deploy به Kubernetes
|
||||||
|
└── kubectl set image deployment/frontoffice fo=fo:package-based
|
||||||
|
└── kubectl rollout status deployment/frontoffice
|
||||||
|
|
||||||
|
3.3 Smoke Test
|
||||||
|
└── ✅ صفحه پکیجها باز میشود (کاشیهای داینامیک)
|
||||||
|
└── ✅ جزئیات پکیج — فیچرها نمایش داده میشود
|
||||||
|
└── ✅ صفحه پاداشها — فیلتر پکیج کار میکند
|
||||||
|
└── ✅ صفحه تعادلها — per-package نمایش داده میشود
|
||||||
|
└── ✅ متن قرارداد — مبلغ داینامیک (نه ۵۶M hardcoded)
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** FrontOffice جدید بالا آمده و صفحات اصلی کار میکنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۴ از ۶: استقرار BackOffice 🛠️
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۲۰ دقیقه
|
||||||
|
> پیشنیاز: CMS باید بالا و سالم باشد
|
||||||
|
|
||||||
|
```
|
||||||
|
4.1 Build BackOffice Docker image
|
||||||
|
└── cd BackOffice/src
|
||||||
|
└── docker build -t bo:package-based .
|
||||||
|
|
||||||
|
4.2 Deploy به Kubernetes
|
||||||
|
└── kubectl set image deployment/backoffice bo=bo:package-based
|
||||||
|
└── kubectl rollout status deployment/backoffice
|
||||||
|
|
||||||
|
4.3 Smoke Test
|
||||||
|
└── ✅ CRUD پکیج — ۱۲ فیلد جدید نمایش داده میشود
|
||||||
|
└── ✅ ماتریس فیچر — checkboxها load میشوند
|
||||||
|
└── ✅ گزارش تعادلها — فیلتر پکیج کار میکند
|
||||||
|
└── ✅ گزارش پرداختها — ستون پکیج نمایش داده میشود
|
||||||
|
└── ✅ ManualPayment — مبلغ editable (نه ۵۶M disabled)
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** BackOffice جدید بالا آمده و CRUD + گزارشات کار میکنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۵ از ۶: بررسی پورسانت (بحرانی!) 💰
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۳۰ دقیقه
|
||||||
|
> ⚠️ پورسانت = پول واقعی — دقت مضاعف لازم است
|
||||||
|
|
||||||
|
```
|
||||||
|
5.1 بررسی SP پارامترها
|
||||||
|
└── محاسبه پورسانت هفته تستی (staging)
|
||||||
|
└── بررسی: هر پکیج Pool جداگانه دارد
|
||||||
|
└── بررسی: MaxBalancesPerLeg صحیح (پایه=۳۰۰, نقرهای=۳۰)
|
||||||
|
└── بررسی: MaxNetworkLevel صحیح
|
||||||
|
|
||||||
|
5.2 مقایسه نتایج
|
||||||
|
└── اجرای محاسبه در staging
|
||||||
|
└── مقایسه Pool مبلغ با محاسبه دستی
|
||||||
|
└── ✅ تفاوت < ۱% قابل قبول
|
||||||
|
|
||||||
|
5.3 بررسی carryover
|
||||||
|
└── ✅ carryover فقط per-package
|
||||||
|
└── ✅ تغییر پکیج → ریست carryover
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Checkpoint:** محاسبات پورسانت per-package صحیح هستند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### مرحله ۶ از ۶: تنظیمات نهایی و بررسی سلامت ✅
|
||||||
|
|
||||||
|
> ⏱️ تخمین: ۱۵ دقیقه
|
||||||
|
|
||||||
|
```
|
||||||
|
6.1 بررسی PackageFeatures seed شدهاند
|
||||||
|
└── SELECT * FROM "CMS"."PackageFeatures";
|
||||||
|
└── پکیج پایه: همه فیچرها ✅
|
||||||
|
└── پکیج نقرهای: فیچرهای تعیینشده ✅
|
||||||
|
|
||||||
|
6.2 بررسی JWT Claims (اختیاری)
|
||||||
|
└── لاگین یک کاربر تست → decode JWT
|
||||||
|
└── ✅ PackageId وجود دارد
|
||||||
|
└── ✅ CanRepurchase صحیح
|
||||||
|
|
||||||
|
6.3 غیرفعال کردن Maintenance Mode (اگر فعال بود)
|
||||||
|
|
||||||
|
6.4 مانیتورینگ ۲۴ ساعته
|
||||||
|
└── بررسی لاگ خطاها
|
||||||
|
└── بررسی response timeها
|
||||||
|
└── بررسی پرداختهای جدید
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ مهاجرت تکمیل شد!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Rollback Plan
|
||||||
|
|
||||||
|
### سناریو ۱: مشکل در Migration دیتابیس
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore از بکاپ
|
||||||
|
pg_restore -d cms_db cms_backup_pre_package_migration.dump
|
||||||
|
|
||||||
|
# Rollback CMS image
|
||||||
|
kubectl set image deployment/cms cms=cms:rollback-point
|
||||||
|
```
|
||||||
|
|
||||||
|
### سناریو ۲: مشکل در CMS (بعد Migration موفق)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ⚠️ نکته: migration undo ممکن نیست (ستونهای جدید اضافه شدهاند)
|
||||||
|
# اما کد قدیمی با ستونهای nullable مشکلی ندارد
|
||||||
|
|
||||||
|
# Rollback فقط CMS image
|
||||||
|
kubectl set image deployment/cms cms=cms:rollback-point
|
||||||
|
```
|
||||||
|
|
||||||
|
### سناریو ۳: مشکل در FO/BO
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# FO و BO مستقل از هم هستند — هرکدام جداگانه rollback
|
||||||
|
kubectl set image deployment/frontoffice fo=fo:rollback-point
|
||||||
|
kubectl set image deployment/backoffice bo=bo:rollback-point
|
||||||
|
```
|
||||||
|
|
||||||
|
### نکته مهم Rollback:
|
||||||
|
- ستونهای جدید **nullable** هستند → کد قدیمی بدون مشکل کار میکند
|
||||||
|
- جدول `PackageFeatures` جدید است → کد قدیمی آن را ignore میکند
|
||||||
|
- **فقط Data Backfill** غیرقابلبرگشت است (ولی ضرری ندارد — فقط NULL → مقدار)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. چکلیست تست قبل از Production
|
||||||
|
|
||||||
|
### 🛒 خرید و فعالسازی
|
||||||
|
|
||||||
|
| # | تست | روش | نتیجه مورد انتظار |
|
||||||
|
|---|------|------|-------------------|
|
||||||
|
| 1 | خرید پکیج نقرهای (ZarinPal) | از FO → پکیجها → نقرهای → پرداخت | Balance = ۵.۶M, Discount = ۱۱.۲M |
|
||||||
|
| 2 | خرید پکیج پایه (ZarinPal) | از FO → پکیجها → پایه → پرداخت | Balance = ۵۶M, Discount = ۱۱۲M |
|
||||||
|
| 3 | خرید پکیج پایه (Daya Loan) | از FO → پکیجها → پایه → دایا | Balance = ۵۶M + loan created |
|
||||||
|
| 4 | پرداخت دستی (BO) | از BO → ManualPayment → مبلغ دلخواه | Amount editable, not hardcoded |
|
||||||
|
| 5 | فعالسازی با نقرهای | فعالسازی باشگاه بعد خرید نقرهای | فقط فیچرهای نقرهای فعال (نه همه) |
|
||||||
|
| 6 | فعالسازی با پایه | فعالسازی باشگاه بعد خرید پایه | همه فیچرها فعال |
|
||||||
|
|
||||||
|
### 🔄 چرخه Magic + خرید مجدد
|
||||||
|
|
||||||
|
| # | تست | نتیجه مورد انتظار |
|
||||||
|
|---|------|-------------------|
|
||||||
|
| 7 | تکمیل چرخه Magic → ریست | PackagePurchaseMethod = None |
|
||||||
|
| 8 | خرید مجدد همان پکیج | بدون قرارداد مجدد، فقط شارژ wallet |
|
||||||
|
| 9 | خرید مجدد پکیج متفاوت (پایه → نقرهای) | DIFF اجرا: فیچرهای اضافی غیرفعال |
|
||||||
|
|
||||||
|
### 💰 پورسانت per-package
|
||||||
|
|
||||||
|
| # | تست | نتیجه مورد انتظار |
|
||||||
|
|---|------|-------------------|
|
||||||
|
| 10 | Pool جداگانه هر پکیج | WeeklyCommissionPool با PackageId متفاوت |
|
||||||
|
| 11 | MaxBalancesPerLeg متفاوت | پایه=۳۰۰, نقرهای=۳۰ |
|
||||||
|
| 12 | Carryover per-downline-package | تغییر پکیج خود کاربر → carryover حفظ (بر اساس زیرمجموعهها) |
|
||||||
|
| 13 | SP پارامترها از Package | بدون hardcoded ۳۰۰/۱۵ |
|
||||||
|
|
||||||
|
### 📊 گزارشات per-package
|
||||||
|
|
||||||
|
| # | تست | نتیجه مورد انتظار |
|
||||||
|
|---|------|-------------------|
|
||||||
|
| 14 | FO — فیلتر dropdown پکیج | فیلتر عملکرد صحیح |
|
||||||
|
| 15 | FO — breakdown پاداش per-package | مبالغ صحیح به تفکیک |
|
||||||
|
| 16 | BO — فیلتر پکیج در تعادلها | فیلتر عملکرد صحیح |
|
||||||
|
| 17 | BO — ستون پکیج در پرداختها | نام پکیج نمایش داده میشود |
|
||||||
|
|
||||||
|
### 📋 UI / قرارداد
|
||||||
|
|
||||||
|
| # | تست | نتیجه مورد انتظار |
|
||||||
|
|---|------|-------------------|
|
||||||
|
| 18 | متن قرارداد — مبلغ داینامیک | مبلغ و نام پکیج صحیح (نه ۵۶M hardcoded) |
|
||||||
|
| 19 | ActivationSection — قیمت | از API خوانده میشود |
|
||||||
|
| 20 | BO — ManualPayment editable | مبلغ قابل ویرایش با validation |
|
||||||
|
| 21 | BO — Package CRUD ۱۲ فیلد | همه فیلدهای جدید ذخیره/بارگذاری |
|
||||||
|
| 22 | BO — Feature Matrix | checkboxها sync با DB |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. ریسکها و نکات بحرانی
|
||||||
|
|
||||||
|
### 🔴 ریسکهای بحرانی
|
||||||
|
|
||||||
|
| # | ریسک | احتمال | تاثیر | کاهشدهنده |
|
||||||
|
|---|-------|--------|-------|------------|
|
||||||
|
| R1 | Migration دیتابیس — PackageId اشتباه | کم | **فاجعه** | Verification query (مرحله 2.4) + بکاپ |
|
||||||
|
| R2 | SP تغییریافته → محاسبات مالی اشتباه | متوسط | **فاجعه** | تست staging + مقایسه دستی |
|
||||||
|
| R3 | Magic Wallet EXIT — سقف global بهجای per-package | متوسط | **بالا** | بررسی MW1-MW3 در CMS handlers |
|
||||||
|
| R4 | قرارداد حقوقی — مبلغ اشتباه | کم | **حقوقی** | متن قرارداد داینامیک ✅ فیکس شده |
|
||||||
|
|
||||||
|
### 🟡 ریسکهای متوسط
|
||||||
|
|
||||||
|
| # | ریسک | کاهشدهنده |
|
||||||
|
|---|-------|------------|
|
||||||
|
| R5 | Proto breaking change | Field numberها backward compatible (فقط اضافه) |
|
||||||
|
| R6 | NuGet version mismatch بین repos | همه روی v0.0.189 ✅ |
|
||||||
|
| R7 | JWT claims — cache invalidation | کاربران باید re-login کنند |
|
||||||
|
| R8 | ~~Validator hardcoded 1B~~ | ✅ فیکس شد — `SystemConstants.WalletMaxSafeAmount` (10B) حصار ایمنی |
|
||||||
|
|
||||||
|
### ⚠️ تغییرات آینده (هنوز پیادهنشده — Phase بعدی)
|
||||||
|
|
||||||
|
این موارد در BIZ spec شناسایی شدهاند ولی **هنوز پیاده نشدهاند**:
|
||||||
|
|
||||||
|
| # | مورد | شدت | شرح |
|
||||||
|
|---|------|------|------|
|
||||||
|
| ~~F1~~ | ~~WalletChangeLog + PackageId~~ | ✅ انجامشده | CMS:`e5bc3a9` — PackageId در UserWalletHistory |
|
||||||
|
| ~~F2~~ | ~~Notification + PackageId~~ | ✅ انجامشده | CMS:`61b7e4f` — SmsTemplates+IUserNotificationService+UserNotificationService با packageName |
|
||||||
|
| ~~F3~~ | ~~Background Services + PackageId~~ | ✅ بررسیشده | بدون تغییر — هر ۳ worker از قبل per-package صحیح کار میکنند |
|
||||||
|
| ~~F4~~ | ~~CSV exports + ستون پکیج~~ | ✅ انجامشده | CMS:`61b7e4f` BO:`92c9922` — proto+handler+CSV برای ManualPayments/WithdrawalRequests |
|
||||||
|
| ~~F5~~ | ~~SystemConfiguration per-package~~ | ✅ بررسیشده | بدون تغییر — مقادیر per-package قبلاً به Package entity منتقل شدهاند |
|
||||||
|
| ~~F6~~ | ~~MagicWalletChargePage hardcoded~~ | ✅ انجامشده | CMS:`61b7e4f` FO:`ecc4f44` — magic_multiplier+magic_max_credit از API، داشبورد "شارژ چندبرابری" |
|
||||||
|
| ~~F7~~ | ~~Validators async per-package~~ | ✅ انجامشده | CMS:`61b7e4f` FO:`ecc4f44` — SystemConstants.WalletMaxSafeAmount (10B) حصار ایمنی، سقف واقعی per-package در هندلر |
|
||||||
|
| ~~F8~~ | ~~آستانه موجودی ورود به Magic (Q24)~~ | ✅ انجامشده | CMS:`a1024a3` — `Balance <= 1_000_000` |
|
||||||
|
| ~~F9~~ | ~~SP Worker — مدیریت خودکار SP (Q26)~~ | ✅ انجامشده | CMS:`a1024a3` — `StoredProcedureDeploymentService` |
|
||||||
|
| ~~F10~~ | ~~History Tables — یکسانسازی + خودکار (Q27)~~ | ✅ انجامشده | CMS:`fdbb91d`+`10d2ca2` — IHasHistory + Interceptor + RenameTable migration |
|
||||||
|
| ~~F11~~ | ~~UI Guidance — آموزش و هشدار (Q28)~~ | ✅ انجامشده | FO:`474d364` BO:`6939780` — ۱۳ صفحه MudAlert |
|
||||||
|
|
||||||
|
> ✅ **F1-F11 همه پیادهسازی شدند.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ضمیمه: ۳۰ تصمیم بیزینسی (Q1–Q30)
|
||||||
|
|
||||||
|
### پیادهشده (Q1–Q23):
|
||||||
|
|
||||||
|
| # | تصمیم | وضعیت |
|
||||||
|
|---|-------|-------|
|
||||||
|
| Q1 | باگ DiscountBalance → فیکس | ✅ `8b9c317` |
|
||||||
|
| Q2 | ادغام ۳ مسیر پرداخت → Generic | ✅ `ccb938e` + `8446e0e` |
|
||||||
|
| Q3 | پکیج نقرهای + پایه — داینامیک | ✅ `ae92ab8` + `a9cd2fd` |
|
||||||
|
| Q4 | ActivationFee یک فیلد (حذف GiftValue) | ✅ `ae92ab8` |
|
||||||
|
| Q5 | DiscountMultiplier داینامیک | ✅ `8e5c7c5` |
|
||||||
|
| Q6 | Migration کاربران فعلی → پکیج پایه | ✅ `a9cd2fd` |
|
||||||
|
| Q7 | خرید N بار بعد تکمیل چرخه | ✅ `fe3edd1` + `8e5c7c5` |
|
||||||
|
| Q8 | Commission Pool جدا per-package | ✅ `607f791` |
|
||||||
|
| Q9 | MagicWallet Multiplier داینامیک | ✅ `8e5c7c5` |
|
||||||
|
| Q10 | دایا = پکیج پایه (نه طلایی) | ✅ `ccb938e` |
|
||||||
|
| Q11 | فیچرها داینامیک per-package | ✅ `dcd1135` |
|
||||||
|
| Q12 | MaxBalancesPerLeg per-package | ✅ `607f791` |
|
||||||
|
| Q13 | MaxNetworkLevel per-package | ✅ `607f791` |
|
||||||
|
| Q14 | MagicWalletMaxDeposit per-package | ✅ `ae92ab8` |
|
||||||
|
| Q15 | MagicWalletMaxCredit per-package | ✅ `ae92ab8` |
|
||||||
|
| Q16 | NetworkWeeklyBalance + PackageId | ✅ `ae92ab8` |
|
||||||
|
| Q17 | گزارش FO breakdown per-package | ✅ `a956cb9` |
|
||||||
|
| Q18 | گزارش BO فیلتر per-package | ✅ `8be98ae` |
|
||||||
|
| Q19 | قرارداد فقط یک بار | ✅ `1ac2366` |
|
||||||
|
| Q20 | فیچر DIFF/تفاضل | ✅ `1ac2366` |
|
||||||
|
| Q21 | First/Last ActivationDate | ✅ `ae92ab8` |
|
||||||
|
| Q22 | تشخیص هفته از LastActivationDate | ✅ `607f791` |
|
||||||
|
| Q23 | Carryover strictly per-package | ✅ `607f791` |
|
||||||
|
|
||||||
|
### تصمیمات v6 (Q24–Q30) — ✅ تکمیلشده:
|
||||||
|
|
||||||
|
| # | تصمیم | وضعیت | کامیت |
|
||||||
|
|---|-------|-------|-------|
|
||||||
|
| Q24 | آستانه موجودی ≤ ۱,۰۰۰,۰۰۰ ریال (ورود Magic + خرید مجدد) | ✅ | CMS:`a1024a3` |
|
||||||
|
| Q25 | DayaLoans فقط پکیج پایه — تایید (بدون تغییر کد) | ✅ تایید | — |
|
||||||
|
| Q26 | SP Worker — auto-deploy با checksum (IHostedService) | ✅ | CMS:`a1024a3` |
|
||||||
|
| Q27 | History Tables — PackageHistory + CycleHistory + IHasHistory + Interceptor + Rename UserWalletChangeLog→UserWalletHistory | ✅ | CMS:`fdbb91d`+`10d2ca2` |
|
||||||
|
| Q28 | UI Guidance — ۱۳ صفحه MudAlert آموزشی/هشداری در FO/BO | ✅ | FO:`474d364` BO:`6939780` |
|
||||||
|
| Q29 | شرط EXIT Magic — تایید: آخرین پکیج فعال (بدون تغییر کد) | ✅ تایید | — |
|
||||||
|
| Q30 | Carryover — تایید: توضیح مستند شد (بدون تغییر کد) | ✅ تایید | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*آخرین بروزرسانی: ۱۰ اسفند ۱۴۰۴ — v7: F1-F7 همه تکمیلشده ✅ | ۵۱+ کامیت (۲۱ CMS + ۹ FO + ۷ BO + ۱۴+ docs) | NuGet v0.0.189 | Notifications+PackageName, CSV ستون پکیج, Dynamic MagicWallet, SystemConstants validators*
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
# گزارش ممیزی صحت دادههای دیتابیس CMS
|
||||||
|
|
||||||
|
**تاریخ بررسی:** 1405/01/28 (2026-04-17)
|
||||||
|
**فایل بکاپ:** `dbbkup/CMS-20260417.sql` (4.9MB, 21,397 خط)
|
||||||
|
**تعداد جداول:** ~47 جدول | **تعداد کاربران:** 115 | **تعداد سفارشات:** 72
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست مطالب
|
||||||
|
|
||||||
|
1. [خلاصه اجرایی](#خلاصه-اجرایی)
|
||||||
|
2. [اصلاحیه مهم — PaymentStatus](#اصلاحیه-مهم)
|
||||||
|
3. [دسته ۱ — ورود دستی / مهاجرت دیتا](#دسته-۱--ورود-دستی--مهاجرت-دیتا)
|
||||||
|
4. [دسته ۲ — باگهای کد](#دسته-۲--باگهای-کد)
|
||||||
|
5. [دسته ۳ — وضعیت Stored Procedureها](#دسته-۳--وضعیت-stored-procedureها)
|
||||||
|
6. [آمار کلی جداول](#آمار-کلی-جداول)
|
||||||
|
7. [خلاصه مالی](#خلاصه-مالی)
|
||||||
|
8. [اقدامات پیشنهادی](#اقدامات-پیشنهادی)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خلاصه اجرایی
|
||||||
|
|
||||||
|
بکاپ دیتابیس CMS در تاریخ 17 آوریل 2026 تحلیل شد. تحلیل شامل بررسی صحت دادهها، ارجاعات خارجی (FK)، زنجیره مالی، و تطبیق با کد سورس C# و Stored Procedureها بود.
|
||||||
|
|
||||||
|
**وضعیت کلی:** سیستم در حال مهاجرت از ساختار استاتیک به پکیجمحور بوده. بخش عمده مشکلات ناشی از ورود دستی داده و مهاجرت سیستم دایا است. چند باگ کد نیز در SP کمیسیون و Worker دایا شناسایی شد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اصلاحیه مهم
|
||||||
|
|
||||||
|
> **`PaymentStatus=0` در enum کد یعنی `Success` نه `Pending`!**
|
||||||
|
>
|
||||||
|
> ```csharp
|
||||||
|
> // PaymentStatus.cs
|
||||||
|
> Success = 0,
|
||||||
|
> Reject = 1,
|
||||||
|
> Pending = 2
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> بنابراین تمام 72 سفارش واقعاً **موفق** هستند. این مشکل نیست.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## دسته ۱ — ورود دستی / مهاجرت دیتا
|
||||||
|
|
||||||
|
### 1A) موجودی 56M بدون فلگ `HasReceivedDayaCredit`
|
||||||
|
|
||||||
|
**شدت:** 🟠 متوسط
|
||||||
|
**علت:** ورود دستی / مهاجرت
|
||||||
|
|
||||||
|
- **23 کاربر** دقیقاً 56,000,000 ریال در `Balance` دارند ولی `HasReceivedDayaCredit = 0`
|
||||||
|
- فقط **16 کاربر** از طریق Daya Worker صحیح اعتبار گرفتند (`HasReceivedDayaCredit = 1`)
|
||||||
|
- بقیه احتمالاً دستی شارژ شدند بدون ثبت تاریخچه
|
||||||
|
|
||||||
|
**کاربران آسیبپذیر:**
|
||||||
|
|
||||||
|
| UserId | نام | Balance | HasReceivedDayaCredit |
|
||||||
|
|--------|-----|---------|----------------------|
|
||||||
|
| 51 | مرتضی اینالو | 56,000,000 | 0 |
|
||||||
|
| 58 | وحید حقگو | 56,000,000 | 0 |
|
||||||
|
| 87 | امیررضا محمدی | 56,000,000 | 0 |
|
||||||
|
| 43 | کریم خادمی | 56,000,000 | 0 |
|
||||||
|
| 52 | حمیدرضا اسمعیلی | 56,000,000 | 0 |
|
||||||
|
| 88 | کریم رعیتپیشه | 56,000,000 | 0 |
|
||||||
|
| 91 | رحیم رعیتپیشه | 56,000,000 | 0 |
|
||||||
|
| 93 | ریحانه سادات هاشمینصر | 56,000,000 | 0 |
|
||||||
|
| 99 | هستی خادمی | 56,000,000 | 0 |
|
||||||
|
| 110 | علی وفائی | 56,000,000 | 0 |
|
||||||
|
| 113 | کاوس بیگاینالو | 56,000,000 | 0 |
|
||||||
|
| 119 | علیرضا کریمیپیروز | 56,000,000 | 0 |
|
||||||
|
| 122 | ابوالقاسم عابدی | 56,000,000 | 0 |
|
||||||
|
| 123 | ناصر کریمیپیروز | 56,000,000 | 0 |
|
||||||
|
| 124 | محمدرضا باغجری | 56,000,000 | 0 |
|
||||||
|
| 126 | امیرعباس میرزایی | 56,000,000 | 0 |
|
||||||
|
| 138 | سیما اکبرزاده | 56,000,000 | 0 |
|
||||||
|
| 139 | مسعود توسلیان | 56,000,000 | 0 |
|
||||||
|
| 142 | صغری شبانکاره | 56,000,000 | 0 |
|
||||||
|
| 170 | لیلا خدارحمی | 56,000,000 | 0 |
|
||||||
|
| 172 | مهرافشان زاهدنیا | 56,000,000 | 0 |
|
||||||
|
| 175 | ناهید حسنزاده | 56,000,000 | 0 |
|
||||||
|
| 176 | مهریدخت میکانیکی | 56,000,000 | 0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1B) کد ملی تکراری — اکانتهای تستی
|
||||||
|
|
||||||
|
**شدت:** 🟡 پایین
|
||||||
|
**علت:** ورود دستی / تست
|
||||||
|
|
||||||
|
| کد ملی | تعداد اکانت | UserIdها | نام |
|
||||||
|
|--------|------------|----------|-----|
|
||||||
|
| مشترک #1 | 6 | 9, 11, 12, 13, 40, 41 | مهدی مرجانی |
|
||||||
|
| مشترک #2 | 3 | 7, 8, 10 | مهدی صیفی |
|
||||||
|
| مشترک #3 | 2 | 42, 43 | کریم خادمی |
|
||||||
|
| مشترک #4 | 2 | 50, 51 | مرتضی اینالو |
|
||||||
|
| مشترک #5 | 2 | 120, 190 | عسلی |
|
||||||
|
|
||||||
|
- شماره موبایل تکراری: `09038888074` بین کاربران 120 و 190
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1C) `NetworkInfos` خالی — مهاجرت صحیح انجام شده
|
||||||
|
|
||||||
|
**شدت:** ✅ مشکل نیست
|
||||||
|
|
||||||
|
جدول `NetworkInfos` خالی است چون دادههای شبکه به فیلدهای مستقیم `Users` مهاجرت شدند:
|
||||||
|
- `Users.NetworkParentId` ← FK به والد شبکه
|
||||||
|
- `Users.LegPosition` ← Left(0) / Right(1)
|
||||||
|
|
||||||
|
مهاجرت در `20250601_MigrateParentIdToNetworkParentId.sql` انجام شده. entity `NetworkInfo` در C# وجود ندارد. SPها هم از `Users.NetworkParentId` استفاده میکنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1D) `PasswordHash = NULL` برای تمام 115 کاربر
|
||||||
|
|
||||||
|
**شدت:** 🟡 نیاز به بررسی
|
||||||
|
**علت:** احتمالاً بکاپ شامل فیلد پسورد نشده، یا سیستم OTP/موبایل استفاده میکند
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1E) دورههای عضویت باشگاه — `PaidAmount = 0`
|
||||||
|
|
||||||
|
**شدت:** 🟡 نیاز به بررسی
|
||||||
|
|
||||||
|
- 87 دوره `ClubMembershipCycles` همه `PaidAmount = 0`
|
||||||
|
- ممکن است عضویت باشگاه خودکار با خرید پکیج فعال شود (نه پرداخت جداگانه)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## دسته ۲ — باگهای کد
|
||||||
|
|
||||||
|
### 2A) تراکنشهای تکراری دایا — Race Condition در `CheckAndProcessDayaLoansCommandHandler`
|
||||||
|
|
||||||
|
**شدت:** 🔴 بحرانی
|
||||||
|
**فایل:** `CMSMicroservice.Application/DayaLoanCQ/Commands/CheckAndProcessDayaLoans/CheckAndProcessDayaLoansCommandHandler.cs`
|
||||||
|
|
||||||
|
**یافتهها:**
|
||||||
|
- **109 رکورد `DayaLoanContracts`** ولی فقط **16 کاربر** `HasReceivedDayaCredit=1`
|
||||||
|
- **64 تراکنش** با «دریافت اعتبار دایا» ساخته شده ولی فقط **11 رکورد `UserPackagePurchases`**
|
||||||
|
- تراکنشها با `RefId` منحصربهفرد ساخته شدند (مثل `C4_T8579002`) — همه در `2025-11-19 01:24:24` ایجاد شدند
|
||||||
|
|
||||||
|
**تحلیل ریشهای:**
|
||||||
|
- Daya Worker (Hangfire هر 15 دقیقه) احتمالاً برای بعضی کاربران **چند بار** اجرا شده
|
||||||
|
- `CreateTransaction` و `DayaLoanContract` ساخته شده ولی `HasReceivedDayaCredit=true` ست نشده (exception بعد از SaveChanges اول ولی قبل از SaveChanges دوم)
|
||||||
|
- یا: چون همه در یک لحظه ساخته شدند (`2025-11-19 01:24:24`)، ممکن است **یک بار bulk import دستی** بوده
|
||||||
|
|
||||||
|
**ریسک:** کاربرانی که `HasReceivedDayaCredit=0` دارند ممکن است **دوباره** از Worker اعتبار بگیرند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2B) SP `sp_CalculateWeeklyCommissionPool` — `DistributedAmount` آپدیت نمیشود
|
||||||
|
|
||||||
|
**شدت:** 🔴 بحرانی
|
||||||
|
**فایل:** `dbbkup/CMS-20260417.sql` خط ~18830
|
||||||
|
|
||||||
|
در Step 10 (آپدیت نهایی Pool):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- کد فعلی (باگدار):
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET
|
||||||
|
IsCalculated = 1,
|
||||||
|
CalculatedAt = @CalculatedAt,
|
||||||
|
TotalBalances = @TotalBalances,
|
||||||
|
ValuePerBalance = @ValuePerBalance,
|
||||||
|
LastModified = @CalculatedAt,
|
||||||
|
LastModifiedBy = 'SP'
|
||||||
|
WHERE Id = @PoolId;
|
||||||
|
```
|
||||||
|
|
||||||
|
**مشکل:** فیلد `DistributedAmount` **هرگز مقداردهی نمیشود** و 0 باقی میماند.
|
||||||
|
|
||||||
|
**نتیجه در دیتا:**
|
||||||
|
- 15 استخر، مجموع `TotalPoolAmount = 2,016,000,000` ریال
|
||||||
|
- همه `DistributedAmount = 0`
|
||||||
|
- ولی 46 پرداخت واقعاً ثبت و به `NetworkBalance` اضافه شدند
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2C) `UserWalletChangeLogs` خالی
|
||||||
|
|
||||||
|
**شدت:** 🟠 متوسط
|
||||||
|
**فایل:** SP Step 9 + `CalculateWeeklyCommissionPoolCommandHandler.cs`
|
||||||
|
|
||||||
|
- SP باید در Step 9 لاگ تغییرات کیفپول را در `UserWalletChangeLogs` ذخیره کند
|
||||||
|
- جدول **صفر رکورد** دارد
|
||||||
|
- **احتمال 1:** SP هرگز Step 9 را درست اجرا نکرده
|
||||||
|
- **احتمال 2:** ORM Strategy (نه SP) استفاده شده و آن `UserWalletChangeLogs` نمینویسد
|
||||||
|
- **احتمال 3:** لاگها در حین ForceRecalculate حذف شدند
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2D) `WalletHistory.ChangeType = NULL` در تمام 239 رکورد
|
||||||
|
|
||||||
|
**شدت:** 🟠 متوسط
|
||||||
|
**فایل:** `UserOrderService.cs` و `PackageService.cs` — هرجا `UserWalletHistory` ساخته میشود
|
||||||
|
|
||||||
|
- فیلد `ChangeType` هرگز ست نمیشود
|
||||||
|
- کد از `IsIncrease` (bool) برای تفکیک واریز/برداشت استفاده میکند
|
||||||
|
- `ChangeType` احتمالاً فیلد قدیمی deprecated شده
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2E) `NetworkWeeklyBalances.WeeklyCommissionPoolId = NULL` (805 رکورد)
|
||||||
|
|
||||||
|
**شدت:** 🟡 پایین
|
||||||
|
|
||||||
|
- SP مقدار `WeeklyPoolContribution = 0` ثبت میکند و PoolId ست نمیشود
|
||||||
|
- ارتباط بین `NetworkWeeklyBalances` و `WeeklyCommissionPools` از طریق `WeekDefinitionId` برقرار است، نه FK مستقیم
|
||||||
|
- **عملاً مشکل عملکردی ایجاد نمیکند** ولی tracking سختتر میشود
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2F) 30 سفارش کیفپولی — بررسی WalletHistory
|
||||||
|
|
||||||
|
**شدت:** 🟠 نیاز به تأیید
|
||||||
|
|
||||||
|
- 30 سفارش با `PaymentMethod=1` (Wallet) ثبت شدند
|
||||||
|
- اولین بررسی نشان داد «هیچ برداشتی ثبت نشده» — **اما** این بررسی بر اساس `ChangeType` بود که همه NULL هستند
|
||||||
|
- **باید بر اساس `IsIncrease=0` (false = decrease)** دوباره بررسی شود
|
||||||
|
- `SubmitShopBuyOrder()` در کد `UserWalletHistory` میسازد — احتمالاً رکوردها وجود دارند ولی `ChangeType` NULL است
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## دسته ۳ — وضعیت Stored Procedureها
|
||||||
|
|
||||||
|
### `GetNetworkTree`
|
||||||
|
|
||||||
|
| آیتم | وضعیت |
|
||||||
|
|------|--------|
|
||||||
|
| از `Users.NetworkParentId` استفاده میکند | ✅ صحیح (بعد از مهاجرت) |
|
||||||
|
| JOIN با `ClubMemberships` | ✅ صحیح |
|
||||||
|
| `MAXRECURSION 0` | ✅ صحیح |
|
||||||
|
| فیلتر `IsDeleted = 0` | ✅ صحیح |
|
||||||
|
|
||||||
|
### `sp_CalculateWeeklyBalances`
|
||||||
|
|
||||||
|
| آیتم | وضعیت |
|
||||||
|
|------|--------|
|
||||||
|
| پارامتر `@PackageId` از `Packages.IsBasePackage` | ✅ صحیح |
|
||||||
|
| `MaxBalancesPerLeg` و `MaxNetworkLevel` از Package | ✅ صحیح |
|
||||||
|
| Carryover از هفته قبل | ✅ صحیح |
|
||||||
|
| CTE recursive برای چپ/راست | ✅ صحیح |
|
||||||
|
| `TotalBalances = MIN(left, right)` | ✅ صحیح |
|
||||||
|
| `SubordinateBalances` محاسبه | ✅ صحیح |
|
||||||
|
| 805 رکورد تولید شده | ✅ کار میکند |
|
||||||
|
|
||||||
|
### `sp_CalculateWeeklyCommissionPool`
|
||||||
|
|
||||||
|
| آیتم | وضعیت |
|
||||||
|
|------|--------|
|
||||||
|
| `ValuePerBalance = TotalPoolAmount / TotalBalances` | ✅ صحیح |
|
||||||
|
| ایجاد `UserCommissionPayouts` | ✅ صحیح (46 رکورد) |
|
||||||
|
| ثبت `CommissionPayoutHistories` | ✅ صحیح |
|
||||||
|
| شارژ `NetworkBalance` کیفپول | ✅ صحیح |
|
||||||
|
| آپدیت `DistributedAmount` در Pool | ❌ **انجام نمیشود** |
|
||||||
|
| ثبت `UserWalletChangeLogs` | ⚠️ نامشخص |
|
||||||
|
| ForceRecalculate — Revert | ✅ منطق صحیح |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## آمار کلی جداول
|
||||||
|
|
||||||
|
| جدول | تعداد | وضعیت |
|
||||||
|
|------|--------|--------|
|
||||||
|
| Users | 115 | |
|
||||||
|
| UserWallets | 115 | |
|
||||||
|
| UserWalletHistories | 239 | ChangeType همه NULL |
|
||||||
|
| UserWalletChangeLogs | 0 | ⚠️ خالی |
|
||||||
|
| UserOrders | 72 | همه PaymentStatus=0 (Success) |
|
||||||
|
| FactorDetails | 177 | |
|
||||||
|
| Transactions | 175 | 64 تراکنش دایا |
|
||||||
|
| PaymentTransactions | 22 | فقط DiscountOrders + شارژ |
|
||||||
|
| Products | 163 | |
|
||||||
|
| Categories | 14 | |
|
||||||
|
| InventoryItems | 175 | |
|
||||||
|
| StockMovements | 242 | 11 chain issue |
|
||||||
|
| ClubMemberships | 88 | همه IsActive=1 |
|
||||||
|
| ClubMembershipCycles | 87 | همه PaidAmount=0 |
|
||||||
|
| UserClubFeatures | 249 | |
|
||||||
|
| NetworkInfos | 0 | ✅ deprecated — مهاجرت شده |
|
||||||
|
| NetworkWeeklyBalances | 805 | PoolId همه NULL |
|
||||||
|
| WeeklyCommissionPools | 15 | DistributedAmount همه 0 |
|
||||||
|
| UserCommissionPayouts | 46 | Status=3, مبالغ کلان |
|
||||||
|
| WeekDefinitions | 59 | |
|
||||||
|
| Packages | 2 | Base=56M, Secondary=5.6M |
|
||||||
|
| DayaLoanContracts | 109 | |
|
||||||
|
| UserPackagePurchases | 11 | |
|
||||||
|
| DiscountOrders | 13 | |
|
||||||
|
| DiscountOrderDetails | 14 | |
|
||||||
|
| DiscountCategories | 8 | |
|
||||||
|
| OrderVATs | 44 | ✅ محاسبات صحیح |
|
||||||
|
| UserAddresses | 127 | |
|
||||||
|
| Roles | 3 | user, admin, Administrator |
|
||||||
|
| UserRoles | 119 | 115 user + 2 admin + 2 Administrator |
|
||||||
|
| ProductImages | 4 | |
|
||||||
|
| ShippingMethods | 0 | ⚠️ خالی |
|
||||||
|
| SitePages | 0 | ⚠️ خالی |
|
||||||
|
| SystemConfigurations | 0 | ⚠️ خالی |
|
||||||
|
| Coupons | 0 | ⚠️ خالی |
|
||||||
|
| ProductProperties | 0 | ⚠️ خالی |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خلاصه مالی
|
||||||
|
|
||||||
|
### موجودیهای کل سیستم
|
||||||
|
|
||||||
|
| فیلد | مبلغ (ریال) |
|
||||||
|
|------|-------------|
|
||||||
|
| مجموع `Balance` کل کیفپولها | 2,548,684,394 |
|
||||||
|
| مجموع `NetworkBalance` (کمیسیون) | 453,599,993 |
|
||||||
|
| مجموع `DiscountBalance` (تخفیف) | 7,326,400,000 |
|
||||||
|
| مجموع سفارشات (UserOrders) | 1,039,410,812 |
|
||||||
|
| مجموع استخر کمیسیون (WeeklyPools) | 2,016,000,000 |
|
||||||
|
|
||||||
|
### پکیجها
|
||||||
|
|
||||||
|
| Package | قیمت | IsBase | MaxBalancesPerLeg | MaxNetworkLevel | DiscountMultiplier |
|
||||||
|
|---------|-------|--------|-------------------|-----------------|-------------------|
|
||||||
|
| Package 1 | 56,000,000 | ✅ | 300 | 1,000,000 | 2.0 |
|
||||||
|
| Package 4 | 5,600,000 | ❌ | 30 | 1,000,000 | 2.0 |
|
||||||
|
|
||||||
|
### ارجاعات شکسته (FK)
|
||||||
|
|
||||||
|
| ارجاع | تعداد |
|
||||||
|
|-------|--------|
|
||||||
|
| FactorDetails → OrderId ناموجود | 2 (DetailId=22,23 → OrderId=21) |
|
||||||
|
| InventoryItems ≠ آخرین StockMovement | 3 |
|
||||||
|
| StockMovement chain breaks | 11 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## اقدامات پیشنهادی
|
||||||
|
|
||||||
|
### اولویت بالا (انجام ندهید تا بررسی بیشتر)
|
||||||
|
|
||||||
|
1. **فیکس SP `sp_CalculateWeeklyCommissionPool`:** اضافه کردن `DistributedAmount` به UPDATE نهایی
|
||||||
|
2. **بررسی Daya Worker:** race condition در `CheckAndProcessDayaLoansCommandHandler` — ممکن است تراکنش تکراری بسازد
|
||||||
|
3. **23 کاربر با 56M بدون فلگ دایا:** تعیین اینکه آیا دستی شارژ شدند یا از Worker — سپس اصلاح `HasReceivedDayaCredit`
|
||||||
|
|
||||||
|
### اولویت متوسط
|
||||||
|
|
||||||
|
4. **WalletHistory ChangeType:** تأیید اینکه deprecated شده و `IsIncrease` جایگزین است
|
||||||
|
5. **UserWalletChangeLogs خالی:** بررسی اینکه ORM Strategy استفاده شده یا SP Strategy
|
||||||
|
6. **اکانتهای تکراری:** تصمیمگیری درباره 13 اکانت تکراری (حذف/ادغام)
|
||||||
|
|
||||||
|
### اولویت پایین
|
||||||
|
|
||||||
|
7. **جداول خالی:** SystemConfigurations, ShippingMethods, SitePages — آیا باید از seed پر شوند؟
|
||||||
|
8. **StockMovement chain issues:** 11 ناسازگاری — آیا از ورود دستی موجودی بوده؟
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*این گزارش فقط مستندات یافتهها است. هیچ تغییری در کد یا دیتابیس اعمال نشده است.*
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
> **تاریخ تجمیع:** اسفند ۱۴۰۴
|
> **تاریخ تجمیع:** اسفند ۱۴۰۴
|
||||||
> **تعداد فایلهای مبدأ:** ۵۳ فایل (~۳۲,۰۰۰ خط)
|
> **تعداد فایلهای مبدأ:** ۵۳ فایل (~۳۲,۰۰۰ خط)
|
||||||
> **تعداد فایلهای نهایی:** ۲۲ فایل (۱۵ اصلی + ۷ roadmap/business)
|
> **تعداد فایلهای نهایی:** ۲۲ فایل (۱۵ اصلی + ۷ roadmap/business)
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (تحول پکیجبیس فاز ۰-۴ + فعالسازی درگاه + مرج Production)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (فاز ۱۰: DataMigration + EF Staging + PackagePurchaseDialog + UI Fixes | NuGet v0.0.189)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,10 +81,10 @@ totalDoc/
|
|||||||
|---|------|--------|--------|
|
|---|------|--------|--------|
|
||||||
| R1 | [MAGIC-WALLET-SPEC](../roadmap/MAGIC-WALLET-SPEC.md) | کیفپول جادویی — مشخصات | State Machine، ضریب ×2.5، سقف 100M، قوانین، API، مدل داده |
|
| R1 | [MAGIC-WALLET-SPEC](../roadmap/MAGIC-WALLET-SPEC.md) | کیفپول جادویی — مشخصات | State Machine، ضریب ×2.5، سقف 100M، قوانین، API، مدل داده |
|
||||||
| R2 | [MAGIC-WALLET-PLAN](../roadmap/MAGIC-WALLET-PLAN.md) | کیفپول جادویی — پلن | ۶ فاز، **فاز 1-6 تکمیل ✅** |
|
| R2 | [MAGIC-WALLET-PLAN](../roadmap/MAGIC-WALLET-PLAN.md) | کیفپول جادویی — پلن | ۶ فاز، **فاز 1-6 تکمیل ✅** |
|
||||||
| R3 | [PACKAGE-TRANSFORMATION-TASKS](../roadmap/PACKAGE-TRANSFORMATION-TASKS.md) | تحول پکیجبیس — تسکها | ۵ فاز، **فاز 0-4 تکمیل ✅**، فاز 5-6 در انتظار |
|
| R3 | [PACKAGE-TRANSFORMATION-TASKS](../roadmap/PACKAGE-TRANSFORMATION-TASKS.md) | تحول پکیجبیس — تسکها | ۱۰ فاز، **فاز 0-10 تکمیل ✅**، تست + deploy در انتظار |
|
||||||
| R4 | [PACKAGE-TRANSFORMATION-UX](../roadmap/PACKAGE-TRANSFORMATION-UX.md) | تاثیر بر UX | تحلیل تاثیر بر FrontOffice + BackOffice |
|
| R4 | [PACKAGE-TRANSFORMATION-UX](../roadmap/PACKAGE-TRANSFORMATION-UX.md) | تاثیر بر UX | تحلیل تاثیر بر FrontOffice + BackOffice |
|
||||||
| R5 | [FEATURE-BACKLOG](../roadmap/FEATURE-BACKLOG.md) | بکلاگ فیچرها | ۱۲ RPC آماده بدون UI، اولویتبندیشده |
|
| R5 | [FEATURE-BACKLOG](../roadmap/FEATURE-BACKLOG.md) | بکلاگ فیچرها | ۱۲ RPC آماده بدون UI، اولویتبندیشده |
|
||||||
| R6 | [BIZ-PACKAGE-BASED-SYSTEM](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی سیستم پکیجبیس | v5، ۲۳ تصمیم + ۵۱ تغییر + ۴۴ سایدافکت |
|
| R6 | [BIZ-PACKAGE-BASED-SYSTEM](../business/BIZ-PACKAGE-BASED-SYSTEM.md) | طراحی سیستم پکیجبیس | v6، ۳۰ تصمیم (Q1-Q30) + ۵۱ تغییر + ۴۴ سایدافکت |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 📜 تاریخچه کارهای انجامشده
|
# 📜 تاریخچه کارهای انجامشده
|
||||||
|
|
||||||
> **همه فعالیتهای پروژه به صورت بولت با توضیح یکخطی و درصد تکمیل**
|
> **همه فعالیتهای پروژه به صورت بولت با توضیح یکخطی و درصد تکمیل**
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۸e — Per-Package Commission Reports | NuGet v0.0.187)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۱ — فیکسهای پرداخت ZarinPal + امنیت Callback URL + اصلاح تومان/ریال)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
| حوزه | تعداد آیتم | تکمیلشده | درصد کل |
|
| حوزه | تعداد آیتم | تکمیلشده | درصد کل |
|
||||||
|------|-----------|----------|---------|
|
|------|-----------|----------|---------|
|
||||||
| **BackOffice** | 61 | 61 | **100%** |
|
| **BackOffice** | 67 | 67 | **100%** |
|
||||||
| **FrontOffice** | 40 | 40 | **100%** |
|
| **FrontOffice** | 60 | 60 | **100%** |
|
||||||
| **CMS Core** | 72 | 72 | **100%** |
|
| **CMS Core** | 89 | 89 | **100%** |
|
||||||
| **Package-Based System** | 35 | 35 | **100%** |
|
| **Package-Based System** | 52 | 52 | **100%** |
|
||||||
| **Deployment** | 22 | 21 | **95%** |
|
| **Deployment** | 22 | 21 | **95%** |
|
||||||
| **Migration** | 16 | 16 | **100%** |
|
| **Migration** | 21 | 21 | **100%** |
|
||||||
| **مجموع** | **246** | **245** | **99%** |
|
| **مجموع** | **311** | **310** | **99.5%** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -106,6 +106,12 @@
|
|||||||
- ✅ دکمه پرداخت شارژ کیفپول جادویی — MagicWallet.razor فعال شد (حذف «بزودی»)
|
- ✅ دکمه پرداخت شارژ کیفپول جادویی — MagicWallet.razor فعال شد (حذف «بزودی»)
|
||||||
- ✅ دکمههای پرداخت مستقیم خرید پکیج — Index.razor هر دو شاخه فعال شدند (حذف «بزودی»)
|
- ✅ دکمههای پرداخت مستقیم خرید پکیج — Index.razor هر دو شاخه فعال شدند (حذف «بزودی»)
|
||||||
|
|
||||||
|
### فیکسهای پرداخت و UX (اسفند ۱۴۰۴ — Phase 11)
|
||||||
|
- ✅ **صفحه موفقیت پرداخت** — `TransactionId` بجای `RefId` + موجودی واقعی + `Href="/profile"` (FO:`5ded91a`)
|
||||||
|
- ✅ **حذف دوبار ×۱۰** — FO مستقیم تومان ارسال میکند، CMS/ZarinPal ×۱۰ میکند (FO:`2f9ef15`)
|
||||||
|
- ✅ **حذف CallbackUrl از درخواست** — `Index.razor.cs` و `Checkout.razor.cs` دیگر URL ارسال نمیکنند (FO:`2b1dc47`)
|
||||||
|
- ✅ ۳ کامیت، ۷ فایل تغییر
|
||||||
|
|
||||||
### Phase 8a+8c: Checkout + Package Pages (اسفند ۱۴۰۴)
|
### Phase 8a+8c: Checkout + Package Pages (اسفند ۱۴۰۴)
|
||||||
- ✅ Checkout wire-up — مهاجرت به `CustomerPurchasePackageAsync` (حذف dead code قدیمی)
|
- ✅ Checkout wire-up — مهاجرت به `CustomerPurchasePackageAsync` (حذف dead code قدیمی)
|
||||||
- ✅ PackageDetail: `GetPackageAsync` → `GetCustomerPackageDetailsAsync` — features/specs از API (نه hardcoded)
|
- ✅ PackageDetail: `GetPackageAsync` → `GetCustomerPackageDetailsAsync` — features/specs از API (نه hardcoded)
|
||||||
@@ -114,6 +120,75 @@
|
|||||||
- ✅ NuGet bump 0.0.182 → 0.0.186 + local feed source
|
- ✅ NuGet bump 0.0.182 → 0.0.186 + local feed source
|
||||||
- ✅ فیکس GwUrl پروداکشن — تصحیح از cms.kbs1.ir به cms.kbs2.ir
|
- ✅ فیکس GwUrl پروداکشن — تصحیح از cms.kbs1.ir به cms.kbs2.ir
|
||||||
|
|
||||||
|
### Phase 10a: PackagePurchaseDialog — دیالوگ داینامیک خرید پکیج (اسفند ۱۴۰۴)
|
||||||
|
- ✅ `PackagePurchaseDialog.razor` — دیالوگ ۲ مرحلهای: مرحله ۱ = کاشیهای پکیج (responsive grid)، مرحله ۲ = انتخاب روش پرداخت
|
||||||
|
- ✅ حذف دیالوگ inline خرید «پکیج پایه» از `Index.razor` — جایگزین با دیالوگ داینامیک
|
||||||
|
- ✅ بارگذاری پکیجها از `PackageService.GetAllPackagesAsync()` — نمایش عنوان + قیمت + ویژگیها
|
||||||
|
- ✅ پشتیبانی از ۲ روش پرداخت: مستقیم (درگاه بانکی) + اعتبار دایا (فقط پکیج پایه + دور اول)
|
||||||
|
- ✅ CSS کلاسهای جدید: `.pkg-tile`, `.pkg-tile-badge`, `.pkg-payment-option`
|
||||||
|
- ✅ کامیت: `a3681a8` (FO)
|
||||||
|
|
||||||
|
### Phase 10b: ۴ فیکس UI پکیج (اسفند ۱۴۰۴)
|
||||||
|
- ✅ **Toman/Rial**: قیمت از سرور به ریال ← `FormattedPrice` حالا `Price / 10` برای نمایش صحیح تومان
|
||||||
|
- ✅ **لیبل**: «ضریب تخفیف» → «ضریب اعتبار» (دیالوگ + صفحه لیست پکیجها)
|
||||||
|
- ✅ **دکمه بازگشت**: وجود داشت (`ArrowForward` + `BackToList`) — تأیید عملکرد
|
||||||
|
- ✅ **HTML Description**: `@((MarkupString)pkg.Description)` بجای متن ساده
|
||||||
|
- ✅ کامیت: `3c1a8ff` (FO)
|
||||||
|
|
||||||
|
### Phase 11: فیکسهای پرداخت + تومان/ریال + امنیت Callback URL (اسفند ۱۴۰۴)
|
||||||
|
|
||||||
|
#### 11a: اصلاح مدل تومان/ریال (CMS+FO)
|
||||||
|
> **تصحیح مهم:** دیتابیس به **تومان** ذخیره میکند نه ریال. فقط درگاه ZarinPal ریال نیاز دارد (×۱۰).
|
||||||
|
- ✅ `ZarinPalPaymentService.InitiatePaymentAsync` — مبلغ ×۱۰ تبدیل به ریال فقط هنگام ارسال به ZarinPal
|
||||||
|
- ✅ `ZarinPalPaymentService.VerifyPaymentWithAmountAsync` — مبلغ ×۱۰ هنگام verify
|
||||||
|
- ✅ FrontOffice نمایش مستقیم مبلغ تومان (بدون `/10`) — فیکس `MagicWallet.razor`, `ChargeDiscountWallet.razor.cs`
|
||||||
|
- ✅ حذف `Price / 10` اضافی در `ClubMembershipContractDialog.razor`
|
||||||
|
|
||||||
|
#### 11b: فیکس ZarinPal Verify — رفع خطای Code=-1 (CMS:`721661a`)
|
||||||
|
> **باگ:** `VerifyPaymentAsync` با ۲ آرگومان مبلغ صفر (0) ارسال میکرد → ZarinPal Code=-1 برمیگرداند
|
||||||
|
- ✅ `PackageService` — lookup `PaymentTransaction.Amount` + استفاده از overload ۳ آرگومانه
|
||||||
|
- ✅ `TransactionsService` — همان فیکس
|
||||||
|
- ✅ `VerifyDiscountWalletChargeCommandHandler` — مبلغ از `PaymentTransaction` + رفع کپیپیست باگ
|
||||||
|
- ✅ `VerifyPackagePurchaseCommandHandler` — مبلغ از `PaymentTransaction`
|
||||||
|
- ✅ `IPaymentGatewayService` — default impl ۳ آرگومانه با `NotImplementedException`
|
||||||
|
- ✅ `MockPaymentGatewayService` + `DayaPaymentService` — اضافه overload ۳ آرگومانه
|
||||||
|
- ✅ ۷ فایل تغییر
|
||||||
|
|
||||||
|
#### 11c: بهبود صفحه موفقیت پرداخت (FO:`5ded91a`)
|
||||||
|
- ✅ `PaymentCallback.razor` — نمایش `TransactionId` بجای `RefId` برای کد رهگیری
|
||||||
|
- ✅ نمایش موجودی واقعی کیفپول از `WalletService.GetBalancesAsync()` (نه مقدار ثابت)
|
||||||
|
- ✅ دکمه بازگشت: `Href="/profile"` بجای `history.back()` (جلوگیری از حلقه بازگشت به درگاه)
|
||||||
|
|
||||||
|
#### 11d: حذف دوبار ×۱۰ شارژ کیفپول (FO:`2f9ef15`)
|
||||||
|
> **باگ:** FO مبلغ تومان را ×۱۰ تبدیل به ریال میکرد، سپس CMS/ZarinPal دوباره ×۱۰ → مبلغ ۱۰۰ برابر
|
||||||
|
- ✅ `MagicWallet.razor.cs` — حذف تبدیل ×۱۰ (ارسال مستقیم تومان)
|
||||||
|
- ✅ `MagicWallet.razor` — فیکس Max و فیلتر preset مبالغ
|
||||||
|
- ✅ `ChargeDiscountWallet.razor.cs` — حذف تبدیل ×۱۰
|
||||||
|
- ✅ `ClubMembershipContractDialog.razor` — حذف `Price/10` اضافی
|
||||||
|
- ✅ ۴ فایل تغییر
|
||||||
|
|
||||||
|
#### 11e: فیکس مسیر Callback کیفپول (CMS:`ed2b20a`)
|
||||||
|
- ✅ `PaymentCallbackController` — مسیر redirect از `/magic-wallet` به `/profile/magic-wallet`
|
||||||
|
- ✅ ایجاد `appsettings.Development.json` — URLهای محلی (`localhost:32846` و `localhost:5268`)
|
||||||
|
- ✅ تصحیح کامنتهای proto: «ریال» → «تومان»
|
||||||
|
|
||||||
|
#### 11f: امنیت Callback URL — حذف از ورودی کاربر (CMS:`0107308`, FO:`2b1dc47`)
|
||||||
|
> **اصلاح امنیتی:** هیچ callback URL نباید از ورودی کاربر بیاید — همه از `appsettings.json` خوانده شوند
|
||||||
|
- ✅ `PackageService` — خواندن `FrontOfficeBaseUrl` از `IConfiguration` بجای `request.CallbackUrl`
|
||||||
|
- ✅ `TransactionsService` — همان فیکس، خواندن از config
|
||||||
|
- ✅ تأیید: `MagicWallet` و `DiscountWallet` از قبل از `CmsBaseUrl` config میخوانند ✅
|
||||||
|
- ✅ تأیید: `DiscountShop PlaceOrder` از قبل از `CmsBaseUrl` config میخواند ✅
|
||||||
|
- ✅ FO: حذف `CallbackUrl` از `Index.razor.cs` و `Checkout.razor.cs`
|
||||||
|
- ✅ جدول Callback URLها:
|
||||||
|
|
||||||
|
| فلو | Callback URL | منبع |
|
||||||
|
|-----|-------------|------|
|
||||||
|
| خرید پکیج | `FrontOfficeBaseUrl/profile/payment-callback?orderId=X` | config |
|
||||||
|
| کیفپول جادویی | `CmsBaseUrl/api/wallet/verify-magic-charge` | config |
|
||||||
|
| کیفپول اعتباری | `CmsBaseUrl/api/wallet/verify-discount-charge` | config |
|
||||||
|
| فروشگاه اعتباری | `CmsBaseUrl/api/payment/discount-order/callback?orderId=X` | config |
|
||||||
|
| تراکنش عمومی | `FrontOfficeBaseUrl/profile/payment-callback` | config |
|
||||||
|
|
||||||
### محتوا و ناوبری
|
### محتوا و ناوبری
|
||||||
- ✅ بلاگ — لیست + جزئیات + pagination
|
- ✅ بلاگ — لیست + جزئیات + pagination
|
||||||
- ✅ صفحات سایت — About, Contact, FAQ, Terms, Privacy, Licenses
|
- ✅ صفحات سایت — About, Contact, FAQ, Terms, Privacy, Licenses
|
||||||
@@ -176,6 +251,14 @@
|
|||||||
- ✅ اغنای پاسخ UserWalletService — join با جدول Users برای نمایش نام کاربر
|
- ✅ اغنای پاسخ UserWalletService — join با جدول Users برای نمایش نام کاربر
|
||||||
- ✅ ارتقای Proto NuGet به نسخه 0.0.183
|
- ✅ ارتقای Proto NuGet به نسخه 0.0.183
|
||||||
|
|
||||||
|
### فیکسهای پرداخت و امنیت (اسفند ۱۴۰۴ — Phase 11)
|
||||||
|
- ✅ **ZarinPal Verify fix** — رفع باگ amount=0 در VerifyPaymentAsync (Code=-1) — ۳ آرگومانه overload
|
||||||
|
- ✅ **تصحیح مدل تومان/ریال** — DB به تومان ذخیره میکند، فقط ZarinPal ریال (×۱۰) نیاز دارد
|
||||||
|
- ✅ **Callback URL از config** — `PackageService` و `TransactionsService` از `FrontOfficeBaseUrl` config میخوانند (نه از ورودی)
|
||||||
|
- ✅ **فیکس مسیر redirect** — `/magic-wallet` → `/profile/magic-wallet` در PaymentCallbackController
|
||||||
|
- ✅ **appsettings.Development.json** — URLهای محلی برای توسعه (CmsBaseUrl + FrontOfficeBaseUrl)
|
||||||
|
- ✅ کامیتها: `721661a` → `ed2b20a` → `0107308`
|
||||||
|
|
||||||
### محتوا
|
### محتوا
|
||||||
- ✅ Blog CRUD — با pagination
|
- ✅ Blog CRUD — با pagination
|
||||||
- ✅ SitePage Settings — JSON typed
|
- ✅ SitePage Settings — JSON typed
|
||||||
@@ -219,6 +302,21 @@
|
|||||||
- ✅ Proto Package Unification — یک package مشترک
|
- ✅ Proto Package Unification — یک package مشترک
|
||||||
- ✅ Binary Tree Reconstruction — از سیستم قدیم
|
- ✅ Binary Tree Reconstruction — از سیستم قدیم
|
||||||
|
|
||||||
|
### DataMigration Tool (اسفند ۱۴۰۴)
|
||||||
|
- ✅ ابزار مستقل مهاجرت داده — .NET 9 Console + Dapper + Polly + Serilog
|
||||||
|
- ✅ Smart Retry Policy — فقط خطاهای transient SQL (deadlock, timeout, transport) — نه خطاهای منطقی
|
||||||
|
- ✅ FK Disable/Enable — `NOCHECK`/`CHECK` حول مهاجرت برای حل FK violation
|
||||||
|
- ✅ TruncateTargetTables — حل مشکل duplicate key (IX_ClubMembership_UserId)
|
||||||
|
- ✅ Fallback Table Name — اگر جدول مقصد rename شده (`UserWalletChangeLogs` → `UserWalletHistories`)
|
||||||
|
- ✅ PostMigration SQL — همه مراحل با `IF COL_LENGTH` / `OBJECT_ID` guard شده
|
||||||
|
- ✅ کامیتها: `0e8c6fd` → `8385c90` → `31cc464`
|
||||||
|
|
||||||
|
### EF Migration — Staging (اسفند ۱۴۰۴)
|
||||||
|
- ✅ اعمال migrations روی DB استیجینگ KBS (`185.252.31.42,2019/KBS`) — موفق
|
||||||
|
- ✅ اعمال migrations روی DB اپلیکیشن (`194.5.195.53,31433/Foursat`) — موفق
|
||||||
|
- ✅ آخرین migration: `20260227024734_Q27_HistoryTables_And_RenameWalletHistory` (۵۵ migration مجموع)
|
||||||
|
- ✅ حل خطای لاگین `Invalid column name 'FirstActivationDate'` — دو DB مختلف بودند
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ۶. مستندات (100% کامل)
|
## ۶. مستندات (100% کامل)
|
||||||
@@ -321,6 +419,65 @@
|
|||||||
- ✅ FO: فیلتر و ستون پکیج در CommissionDashboard + WeeklyBalance
|
- ✅ FO: فیلتر و ستون پکیج در CommissionDashboard + WeeklyBalance
|
||||||
- ✅ NuGet: `0.0.186` → `0.0.187`
|
- ✅ NuGet: `0.0.186` → `0.0.187`
|
||||||
|
|
||||||
|
### Phase 9 — Q24-Q30 Business Decisions + History Infrastructure ✅
|
||||||
|
|
||||||
|
#### 9a: Q24+Q26 — Balance Threshold + SP Worker ✅ (CMS:`a1024a3`)
|
||||||
|
- ✅ Q24: آستانه موجودی `Balance <= 1_000_000` ریال برای ورود Magic و خرید مجدد (بجای `== 0`)
|
||||||
|
- ✅ Q26: `StoredProcedureDeploymentService` (IHostedService) — خواندن فایلهای `.sql` از embedded resource، مقایسه checksum و اعمال خودکار در startup
|
||||||
|
|
||||||
|
#### 9b: Q27 — History Tables Entities ✅ (CMS:`fdbb91d`)
|
||||||
|
- ✅ `PackageHistory` entity — فیلدهای Old*/New* برای Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive
|
||||||
|
- ✅ `ClubMembershipCycleHistory` entity — فیلدهای Old*/New* برای IsCurrentCycle, MagicStartedAt, MagicCompletedAt
|
||||||
|
- ✅ `PackageAction` و `ClubMembershipCycleAction` enums
|
||||||
|
- ✅ EF Configurations + DbSets + Navigation Properties
|
||||||
|
|
||||||
|
#### 9c: Q28 — UI Guidance ✅ (FO:`474d364` BO:`6939780`)
|
||||||
|
- ✅ FrontOffice: ۷ صفحه با MudAlert (G1-G7) — Packages, Checkout, MyPackages, MagicWallet, Commission, Membership, ActivationSection
|
||||||
|
- ✅ BackOffice: ۶ صفحه با MudAlert (G8-G13) — PackageCRUD, ClubFeatures, ManualPayments, Commission Dashboard, UserPayouts, ClubMembers
|
||||||
|
|
||||||
|
#### 9d: Rename + History Interceptor + Migration ✅ (CMS:`10d2ca2`)
|
||||||
|
- ✅ تغییر نام `UserWalletChangeLog` → `UserWalletHistory` در ۵۴+ فایل (entities, configs, DTOs, commands, queries, protos, services)
|
||||||
|
- ✅ تغییر نام ۳۴ فایل و ۱۱ دایرکتوری
|
||||||
|
- ✅ تغییر نام proto: `userwalletchangelog.proto` → `userwallethistory.proto`
|
||||||
|
- ✅ `IHasHistory<T>` generic interface — متد `CreateHistorySnapshot` برای ثبت خودکار
|
||||||
|
- ✅ `HistoryTrackingSaveChangesInterceptor` — reflection-based، auto-fill Old* از OriginalValues
|
||||||
|
- ✅ `Package` implements `IHasHistory<PackageHistory>`
|
||||||
|
- ✅ EF Migration `Q27_HistoryTables_And_RenameWalletHistory` — **RenameTable** (حفظ داده) + rename PK/FK/Index via sp_rename
|
||||||
|
- ✅ NuGet: `0.0.187` → `0.0.188`
|
||||||
|
|
||||||
|
### Phase 10 — استقرار + DataMigration + UI خرید پکیج ✅
|
||||||
|
|
||||||
|
#### 10a: DataMigration Tool ✅ (Local — بدون remote)
|
||||||
|
- ✅ ابزار مستقل مهاجرت داده — .NET 9 Console app + Dapper (bulk copy) + Polly (retry) + Serilog (logging)
|
||||||
|
- ✅ مهاجرت ۱۸ جدول از DB پروداکشن (`185.252.31.42,2019/Foursat`) به استیجینگ (`KBS`)
|
||||||
|
- ✅ Smart Retry — فقط خطاهای transient (deadlock/timeout/transport)، نه خطاهای منطقی
|
||||||
|
- ✅ FK Disable/Enable — `ALTER TABLE NOCHECK/CHECK CONSTRAINT` حول هر مهاجرت
|
||||||
|
- ✅ TruncateTargetTables — حل duplicate key (`IX_ClubMembership_UserId`) هنگام اجرای مجدد
|
||||||
|
- ✅ Fallback Table Name — جدول مقصد rename شده؟ (`UserWalletChangeLogs` → `UserWalletHistories`)
|
||||||
|
- ✅ PostMigration SQL — همه مراحل با `IF COL_LENGTH`/`OBJECT_ID` guard شده (سازگار با هر دو schema)
|
||||||
|
- ✅ کامیتها: `0e8c6fd` → `8385c90` (MERGE fix) → `31cc464` (FK+truncate+PostMigration)
|
||||||
|
|
||||||
|
#### 10b: EF Migration Staging ✅
|
||||||
|
- ✅ اعمال ۵۵ migration روی DB استیجینگ KBS (`185.252.31.42,2019;Database=KBS`)
|
||||||
|
- ✅ اعمال ۵۵ migration روی DB اپلیکیشن (`194.5.195.53,31433;Database=Foursat`)
|
||||||
|
- ✅ آخرین migration: `20260227024734_Q27_HistoryTables_And_RenameWalletHistory`
|
||||||
|
- ✅ حل خطای لاگین: `Invalid column name 'FirstActivationDate'` — CMS به DB دیگری وصل بود
|
||||||
|
|
||||||
|
#### 10c: PackagePurchaseDialog — دیالوگ داینامیک خرید (FO:`a3681a8`)
|
||||||
|
- ✅ `PackagePurchaseDialog.razor` — دیالوگ ۲ مرحلهای جایگزین دیالوگ hardcoded «پکیج پایه»
|
||||||
|
- ✅ مرحله ۱: نمایش کاشیهای پکیج (responsive grid 2-3 ستونه) با عنوان + قیمت + ویژگیها + badge «پایه»
|
||||||
|
- ✅ مرحله ۲: انتخاب روش پرداخت (مستقیم + اعتبار دایا) با خلاصه پکیج انتخابی
|
||||||
|
- ✅ بارگذاری از `PackageService.GetAllPackagesAsync()` + `PackagePurchaseResult` record
|
||||||
|
- ✅ محدودیت دایا: فقط `SupportsDayaPurchase && PurchaseCycleCount == 0`
|
||||||
|
- ✅ CSS: `.pkg-tile`, `.pkg-tile-badge`, `.pkg-payment-option` در `site.css`
|
||||||
|
- ✅ NuGet: `0.0.188` → `0.0.189`
|
||||||
|
|
||||||
|
#### 10d: ۴ فیکس UI پکیج (FO:`3c1a8ff`)
|
||||||
|
- ✅ **Toman/Rial**: قیمت از سرور به ریال ← `FormattedPrice` حالا `Price / 10` برای نمایش صحیح تومان
|
||||||
|
- ✅ **لیبل**: «ضریب تخفیف» → «ضریب اعتبار» (دیالوگ + صفحه لیست پکیجها)
|
||||||
|
- ✅ **دکمه بازگشت**: وجود داشت (`ArrowForward` + `BackToList`) — تأیید عملکرد
|
||||||
|
- ✅ **HTML Description**: `@((MarkupString)pkg.Description)` بجای متن ساده
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ۸. Timeline (جدول زمانی)
|
## ۸. Timeline (جدول زمانی)
|
||||||
@@ -340,3 +497,6 @@
|
|||||||
| اسفند ۱۴۰۴ (هفته ۳) | 🚀 فعالسازی درگاه + بهبود UI ادمین + مرج پروداکشن | 97% |
|
| اسفند ۱۴۰۴ (هفته ۳) | 🚀 فعالسازی درگاه + بهبود UI ادمین + مرج پروداکشن | 97% |
|
||||||
| اسفند ۱۴۰۴ (هفته ۴) | 📦 تحول پکیجبیس فاز ۰-۶ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation) | 97% |
|
| اسفند ۱۴۰۴ (هفته ۴) | 📦 تحول پکیجبیس فاز ۰-۶ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation) | 97% |
|
||||||
| اسفند ۱۴۰۴ (هفته ۵) | 📦 فاز 8e: گزارشهای پورسانت per-package (Proto + CMS + BO + FO) | 98% |
|
| اسفند ۱۴۰۴ (هفته ۵) | 📦 فاز 8e: گزارشهای پورسانت per-package (Proto + CMS + BO + FO) | 98% |
|
||||||
|
| اسفند ۱۴۰۴ (هفته ۶) | 📦 فاز ۹: Q24-Q30 (آستانه + SP Worker + History Tables + UI Guidance + Rename + Interceptor + Migration) | 99% |
|
||||||
|
| اسفند ۱۴۰۴ (هفته ۷) | 📦 فاز ۱۰: DataMigration Tool + EF Staging + PackagePurchaseDialog + UI Fixes (Toman/Rial + لیبل + HTML) | 99.5% |
|
||||||
|
| اسفند ۱۴۰۴ (هفته ۸) | 💳 فاز ۱۱: فیکس ZarinPal Verify + اصلاح تومان/ریال + صفحه موفقیت + حذف دوبار ×۱۰ + امنیت Callback URL | 99.5% |
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 🗺️ نقشه راه، ریسکها و کارهای باقیمانده
|
# 🗺️ نقشه راه، ریسکها و کارهای باقیمانده
|
||||||
|
|
||||||
> **Roadmap + Risk Register + Dependencies + Priorities**
|
> **Roadmap + Risk Register + Dependencies + Priorities**
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: تحول پکیجبیس فاز ۰-۶ + per-package commission + deprecation cleanup)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فاز ۱۱ — فیکسهای پرداخت ZarinPal + امنیت Callback URL + تومان/ریال)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
Core Platform ████████████████████████████████████████████████ 98%
|
Core Platform ████████████████████████████████████████████████ 98%
|
||||||
Club System ████████████████████████████████████████████████ 98%
|
Club System ████████████████████████████████████████████████ 98%
|
||||||
E-Commerce ████████████████████████████████████████████████ 98%
|
E-Commerce ████████████████████████████████████████████████ 98%
|
||||||
Payment ████████████████████████████████████████████████ 97%
|
Payment ████████████████████████████████████████████████ 99%
|
||||||
Magic Wallet ████████████████████████████████████████████████ 100%
|
Magic Wallet ████████████████████████████████████████████████ 100%
|
||||||
Package-Based ██████████████████████████████████████████████░░ 93%
|
Package-Based ███████████████████████████████████████████████░░ 97%
|
||||||
UI/UX █████████████████████████████████████████████░░░ 93%
|
UI/UX ██████████████████████████████████████████████░░░ 95%
|
||||||
Deployment ██████████████████████████████████████████████░░ 95%
|
Deployment ██████████████████████████████████████████████░░ 95%
|
||||||
Documentation ████████████████████████████████████████████████ 100%
|
Documentation ████████████████████████████████████████████████ 100%
|
||||||
```
|
```
|
||||||
@@ -209,11 +209,13 @@ DONE (اسفند ۱۴۰۴):
|
|||||||
→ مرج پروداکشن هر ۳ ریپو (CMS + FO + BO) ✅
|
→ مرج پروداکشن هر ۳ ریپو (CMS + FO + BO) ✅
|
||||||
→ اجرای Migration روی پروداکشن ✅
|
→ اجرای Migration روی پروداکشن ✅
|
||||||
→ 📦 تحول پکیجبیس فاز 0-6 ✅ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation)
|
→ 📦 تحول پکیجبیس فاز 0-6 ✅ (Domain → Migration → Business → Package → CRUD → Commission per-pkg → Deprecation)
|
||||||
|
→ 📦 فاز ۹: Q24-Q30 + History + Rename + Interceptor ✅
|
||||||
|
→ 📦 فاز ۱۰: DataMigration Tool + EF Staging + PackagePurchaseDialog + UI Fixes ✅
|
||||||
|
→ 💳 فاز ۱۱: فیکس ZarinPal Verify (amount=0) + تصحیح مدل تومان/ریال + صفحه موفقیت پرداخت + حذف دوبار ×۱۰ + امنیت Callback URL ✅
|
||||||
|
|
||||||
NOW (این ماه):
|
NOW (این ماه):
|
||||||
→ 📦 فاز 7: UI (کاشیهای داینامیک + فیلتر پکیج + MyPackages)
|
|
||||||
→ EF Migration نهایی
|
|
||||||
→ تست کامل پروداکشن
|
→ تست کامل پروداکشن
|
||||||
|
→ فیکس باگهای کشفشده در تست
|
||||||
|
|
||||||
NEXT (فروردین):
|
NEXT (فروردین):
|
||||||
→ Mobile Responsive (H2)
|
→ Mobile Responsive (H2)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# 🔄 نقشهراه تحول پکیجبیس — تسکهای گامبهگام
|
# 🔄 نقشهراه تحول پکیجبیس — تسکهای گامبهگام
|
||||||
|
|
||||||
> **وضعیت:** در حال اجرا — **فاز ۰-۸f (UI تکمیل) ✅** | NuGet v0.0.188 | تست باقیمانده
|
> **وضعیت:** در حال اجرا — **فاز ۰-۱۰ (تکمیل کد + استقرار staging) ✅** | NuGet v0.0.189 | تست باقیمانده
|
||||||
> **تاریخ:** ۱۴۰۴/۱۲/۱۰
|
> **تاریخ:** ۱۴۰۴/۱۲/۰۸
|
||||||
> **پیشنیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) **v3** (تکمیل پورسانت per-package)
|
> **پیشنیاز:** [BIZ-PACKAGE-BASED-SYSTEM.md](../business/BIZ-PACKAGE-BASED-SYSTEM.md) **v6** (تکمیل Q24-Q30 + History + Rename)
|
||||||
> **هدف:** شکستن **۴۸+ تغییر** به تسکهای اتمیک با ترتیب اجرا و وابستگیها
|
> **هدف:** شکستن **۴۸+ تغییر** به تسکهای اتمیک با ترتیب اجرا و وابستگیها
|
||||||
> **کامیتها:**
|
> **کامیتها:**
|
||||||
> CMS: `8b9c317`→`fe3edd1`→`ae92ab8`→`a9cd2fd`→`8e5c7c5`→`ccb938e`→`0002a5a`→`607f791`→`7176fe4`→`d19c569`→`469d97b`→`161f796`→`8446e0e`→`ce8e248`→`7554d70`→`aaaf7fc`→`dcd1135`
|
> CMS: `8b9c317`→`fe3edd1`→`ae92ab8`→`a9cd2fd`→`8e5c7c5`→`ccb938e`→`0002a5a`→`607f791`→`7176fe4`→`d19c569`→`469d97b`→`161f796`→`8446e0e`→`ce8e248`→`7554d70`→`aaaf7fc`→`dcd1135`→`a1024a3`→`fdbb91d`→`10d2ca2`
|
||||||
> FrontOffice: `b82cac4`→`71f391a`→`0bbc11e`→`d71d463`→`40882c8`→`a956cb9`→`3bffc13`
|
> FrontOffice: `b82cac4`→`71f391a`→`0bbc11e`→`d71d463`→`40882c8`→`a956cb9`→`3bffc13`→`474d364`
|
||||||
> BackOffice: `f1b0085`→`89f5241`→`c96377a`→`8be98ae`→`e020354`
|
> BackOffice: `f1b0085`→`89f5241`→`c96377a`→`8be98ae`→`e020354`→`6939780`
|
||||||
|
|
||||||
> ⚠️ **تغییرات v3:** پورسانت per-package، carryover مجزا، SP parameters داینامیک، گزارشدهی FO/BO per-package
|
> ⚠️ **تغییرات v3:** پورسانت per-package، carryover مجزا، SP parameters داینامیک، گزارشدهی FO/BO per-package
|
||||||
|
|
||||||
@@ -36,9 +36,19 @@
|
|||||||
└─→ 8d: Proto cleanup ✅ CMS:`7554d70` FO:`40882c8` BO:`c96377a`
|
└─→ 8d: Proto cleanup ✅ CMS:`7554d70` FO:`40882c8` BO:`c96377a`
|
||||||
└─→ 8e: Per-package reports ✅ CMS:`aaaf7fc` FO:`a956cb9` BO:`8be98ae`
|
└─→ 8e: Per-package reports ✅ CMS:`aaaf7fc` FO:`a956cb9` BO:`8be98ae`
|
||||||
└─→ 8f: UI completion ✅ CMS:`dcd1135` FO:`3bffc13` BO:`e020354`
|
└─→ 8f: UI completion ✅ CMS:`dcd1135` FO:`3bffc13` BO:`e020354`
|
||||||
└─→ مرحله ۵: تست + استقرار (۳ روز) ⬜
|
└─→ مرحله ۹: Q24-Q30 + History + Rename
|
||||||
|
├─→ 9a: Q24+Q26 (threshold+SP) ✅ CMS:`a1024a3`
|
||||||
|
├─→ 9b: Q27 History entities ✅ CMS:`fdbb91d`
|
||||||
|
├─→ 9c: Q28 UI Guidance ✅ FO:`474d364` BO:`6939780`
|
||||||
|
└─→ 9d: Rename+Interceptor+Mig ✅ CMS:`10d2ca2`
|
||||||
|
└─→ مرحله ۱۰: استقرار + DataMigration + UI
|
||||||
|
├─→ 10a: DataMigration Tool ✅ Local: `0e8c6fd`→`31cc464`
|
||||||
|
├─→ 10b: EF Staging Migrations ✅
|
||||||
|
├─→ 10c: PackagePurchaseDialog ✅ FO:`a3681a8`
|
||||||
|
└─→ 10d: UI Fixes (Rial/Toman+لیبل+HTML) ✅ FO:`3c1a8ff`
|
||||||
|
└─→ مرحله ۵: تست + نهایی ⬜
|
||||||
|
|
||||||
مسیر بحرانی: ۰→۱→۱.۵→۲→۳→۴→UI→۵ = ~۱۷ روز | انجامشده: ۰→8f (~۱۷ روز)
|
مسیر بحرانی: ۰→۱→۱.۵→۲→۳→۴→UI→۹→۱۰→۵ = ~۲۲ روز | انجامشده: ۰→10d (~۲۰ روز)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -710,6 +720,79 @@ await connection.ExecuteAsync("CMS.sp_CalculateWeeklyBalances",
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## مرحله ۹ — Q24-Q30 Business Decisions + History Infrastructure ✅
|
||||||
|
|
||||||
|
> ✅ تکمیلشده | وابستگی: مرحله ۸ | کامیتها: CMS:`a1024a3`→`fdbb91d`→`10d2ca2` FO:`474d364` BO:`6939780`
|
||||||
|
|
||||||
|
### 9a: Q24 آستانه موجودی + Q26 SP Worker ✅ (CMS:`a1024a3`)
|
||||||
|
|
||||||
|
**Q24 — آستانه موجودی:**
|
||||||
|
- شرط ورود به Magic و خرید مجدد از `Balance == 0` به `Balance <= 1_000_000` ریال تغییر کرد
|
||||||
|
- چون قیمت محصولات متفاوته، Balance دقیقاً صفر نمیشه
|
||||||
|
- فایلها: `UserOrderService.cs` (شرط EXIT Magic + Re-purchase guard)
|
||||||
|
|
||||||
|
**Q26 — SP Worker:**
|
||||||
|
- `StoredProcedureDeploymentService` (IHostedService) — در startup فایلهای `.sql` از embedded resource خوانده میشوند
|
||||||
|
- مقایسه checksum با جدول `__SPChecksums` — فقط SPهای تغییریافته re-deploy میشوند
|
||||||
|
- فایلها: `StoredProcedureDeploymentService.cs`, embedded `.sql` resources
|
||||||
|
|
||||||
|
### 9b: Q27 History Tables Entities ✅ (CMS:`fdbb91d`)
|
||||||
|
|
||||||
|
**Entityهای جدید:**
|
||||||
|
- `PackageHistory`: فیلدهای Old*/New* برای Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive + Action + PerformedBy + Reason
|
||||||
|
- `ClubMembershipCycleHistory`: فیلدهای Old*/New* برای IsCurrentCycle, MagicStartedAt, MagicCompletedAt + Action + UserId + CycleNumber
|
||||||
|
|
||||||
|
**Enums جدید:**
|
||||||
|
- `PackageAction`: Created, Updated, Activated, Deactivated, PriceChanged, FeaturesChanged
|
||||||
|
- `ClubMembershipCycleAction`: Created, MagicStarted, MagicCompleted, Closed, AdminModified
|
||||||
|
|
||||||
|
**زیرساخت:**
|
||||||
|
- EF Configurations (indexes, maxLength, precision)
|
||||||
|
- DbSets در `IApplicationDbContext` و `ApplicationDbContext`
|
||||||
|
- Navigation Properties: `Package.Histories`, `ClubMembershipCycle.Histories`
|
||||||
|
|
||||||
|
### 9c: Q28 UI Guidance ✅ (FO:`474d364` BO:`6939780`)
|
||||||
|
|
||||||
|
**FrontOffice — ۷ صفحه با MudAlert آموزشی:**
|
||||||
|
- G1: Packages.razor — توضیح سیستم پکیجبیس
|
||||||
|
- G2: Checkout — هشدار شارژ کیفپول اعتباری
|
||||||
|
- G3: MyPackages — توضیح وضعیت پکیجها
|
||||||
|
- G4: MagicWallet — هشدار شرایط خروج + سقف شارژ
|
||||||
|
- G5: CommissionDashboard — توضیح per-package
|
||||||
|
- G6: ClubMembership — آموزش چرخه عضویت
|
||||||
|
- G7: ActivationSection — هشدار هزینه فعالسازی
|
||||||
|
|
||||||
|
**BackOffice — ۶ صفحه با MudAlert:**
|
||||||
|
- G8: PackageCRUD — هشدار ثبت تغییرات در History
|
||||||
|
- G9: ClubFeatures — توضیح ارتباط فیچر-پکیج
|
||||||
|
- G10: ManualPayments — هشدار مبلغ بر اساس پکیج
|
||||||
|
- G11: Commission Dashboard — توضیح Pool per-package
|
||||||
|
- G12: UserPayouts — توضیح فیلتر پکیج
|
||||||
|
- G13: ClubMembers — اطلاعات چرخه عضویت
|
||||||
|
|
||||||
|
### 9d: Rename + History Interceptor + EF Migration ✅ (CMS:`10d2ca2`)
|
||||||
|
|
||||||
|
**Rename (86 فایل):**
|
||||||
|
- `UserWalletChangeLog` → `UserWalletHistory` در 54+ فایل (entities, configs, DTOs, commands, queries, protos, services)
|
||||||
|
- 34 فایل rename شده + 11 دایرکتوری rename شده
|
||||||
|
- Proto: `userwalletchangelog.proto` → `userwallethistory.proto`
|
||||||
|
|
||||||
|
**History Interceptor:**
|
||||||
|
- `IHasHistory<T>` generic interface در `Domain/Common` — متد `CreateHistorySnapshot(action, performedBy)`
|
||||||
|
- `HistoryTrackingSaveChangesInterceptor` در `Infrastructure/Persistence/Interceptors` — reflection-based
|
||||||
|
- شناسایی entityهای `IHasHistory<>` از ChangeTracker
|
||||||
|
- فراخوانی `CreateHistorySnapshot` برای Modified/Added
|
||||||
|
- Auto-fill فیلدهای `Old*` از `OriginalValues` با naming convention
|
||||||
|
- `Package` implements `IHasHistory<PackageHistory>` — اولین entity
|
||||||
|
|
||||||
|
**EF Migration (`Q27_HistoryTables_And_RenameWalletHistory`):**
|
||||||
|
- ⚠️ EF Core اتوماتیک `DropTable` + `CreateTable` تولید کرد → **دستی اصلاح شد** به `RenameTable` (حفظ دادهها)
|
||||||
|
- `RenameTable` + `RenameIndex` × 2 + `sp_rename` برای PK و FKها
|
||||||
|
- `CreateTable` برای `ClubMembershipCycleHistories` و `PackageHistories` (جداول جدید)
|
||||||
|
- Down method: reverse rename + drop new tables
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## مرحله ۵ — تست و استقرار
|
## مرحله ۵ — تست و استقرار
|
||||||
|
|
||||||
> ⏱️ **۳ روز** (v3: +۱) | وابستگی: مرحله ۴
|
> ⏱️ **۳ روز** (v3: +۱) | وابستگی: مرحله ۴
|
||||||
@@ -778,4 +861,4 @@ await connection.ExecuteAsync("CMS.sp_CalculateWeeklyBalances",
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۱۰ — فاز ۰-۸f تکمیل (۲۷ کامیت: ۱۷ CMS + ۷ FO + ۵ BO) | NuGet v0.0.188 | T4.2+T4.3+T4.13+F2+F3 کامل | باقیمانده: تست + deploy*
|
*آخرین بروزرسانی: ۱۴۰۴/۱۲/۰۸ — فاز ۰-۹d تکمیل (۳۰ کامیت: ۲۰ CMS + ۸ FO + ۶ BO) | NuGet v0.0.188 | باقیمانده: تست + deploy*
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ⚙️ معماری CMS و زیرساخت فنی
|
# ⚙️ معماری CMS و زیرساخت فنی
|
||||||
|
|
||||||
> **منابع ادغامشده:** `CMS-README.md`, `ICURRENTUSERSERVICE-IMPLEMENTATION.md`, `FILE-MANAGEMENT-ARCHITECTURE.md`, `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `BFF-REMOVAL-PLAN.md`, `system-constants.md`
|
> **منابع ادغامشده:** `CMS-README.md`, `ICURRENTUSERSERVICE-IMPLEMENTATION.md`, `FILE-MANAGEMENT-ARCHITECTURE.md`, `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `BFF-REMOVAL-PLAN.md`, `system-constants.md`
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Magic Wallet entities + gRPC)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس ZarinPal Verify + Callback URL امنیت + appsettings.Development.json)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -140,6 +140,7 @@ public class CreateProductCommandHandler
|
|||||||
| `CategoryService` | category.proto | GetCategories, Create, Update |
|
| `CategoryService` | category.proto | GetCategories, Create, Update |
|
||||||
| `SystemConfigService` | config.proto | GetConfig, UpdateConfig |
|
| `SystemConfigService` | config.proto | GetConfig, UpdateConfig |
|
||||||
| `UserWalletService` | userwallet.proto | GetCustomerWallet, InitiateMagicCharge, GetMagicWalletStatus |
|
| `UserWalletService` | userwallet.proto | GetCustomerWallet, InitiateMagicCharge, GetMagicWalletStatus |
|
||||||
|
| `UserWalletHistoryService` | userwallethistory.proto | *(renamed from UserWalletChangeLogService)* |
|
||||||
|
|
||||||
### ۴.۲ PaginationState (مشترک)
|
### ۴.۲ PaginationState (مشترک)
|
||||||
|
|
||||||
@@ -195,6 +196,45 @@ Engine: MSSQL 2022-CU16, Collation=Arabic_CI_AS
|
|||||||
| `sp_CalculateWeeklyBalances` | محاسبه بالانس هفتگی هر عضو |
|
| `sp_CalculateWeeklyBalances` | محاسبه بالانس هفتگی هر عضو |
|
||||||
| `sp_CalculateWeeklyCommissionPool` | توزیع Pool هفتگی |
|
| `sp_CalculateWeeklyCommissionPool` | توزیع Pool هفتگی |
|
||||||
|
|
||||||
|
### ۵.۴ SP Auto-Deploy Worker (Q26)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// StoredProcedureDeploymentService : IHostedService
|
||||||
|
// در startup:
|
||||||
|
// 1. خواندن فایلهای .sql از embedded resource
|
||||||
|
// 2. مقایسه checksum با جدول __SPChecksums
|
||||||
|
// 3. فقط SPهای تغییریافته re-deploy میشوند
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵.۵ History Tracking System (Q27)
|
||||||
|
|
||||||
|
### IHasHistory<T> Interface
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IHasHistory<THistory> where THistory : BaseAuditableEntity, new()
|
||||||
|
{
|
||||||
|
THistory CreateHistorySnapshot(string action, string? performedBy);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HistoryTrackingSaveChangesInterceptor
|
||||||
|
|
||||||
|
- **مکان:** `Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs`
|
||||||
|
- **مکانیسم:** `SaveChangesInterceptor` — قبل از `SaveChanges` اجرا میشود
|
||||||
|
- **شناسایی:** از `ChangeTracker` entityهایی که `IHasHistory<>` پیادهسازی کردن (Modified/Added)
|
||||||
|
- **Auto-fill:** فیلدهای `Old*` از `entry.OriginalValues` با naming convention (مثلاً `OldPrice` ← `OriginalValues["Price"]`)
|
||||||
|
- **Entityهای فعال:** `Package` → `PackageHistory`
|
||||||
|
|
||||||
|
### History Tables
|
||||||
|
|
||||||
|
| جدول | Entity مرتبط | فیلدهای Old/New |
|
||||||
|
|------|-------------|----------------|
|
||||||
|
| `PackageHistories` | Package | Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive |
|
||||||
|
| `ClubMembershipCycleHistories` | ClubMembershipCycle | IsCurrentCycle, MagicStartedAt, MagicCompletedAt |
|
||||||
|
| `UserWalletHistories` | UserWallet | *(renamed from UserWalletChangeLogs — RenameTable migration)* |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ۶. حذف BFF / Gateway
|
## ۶. حذف BFF / Gateway
|
||||||
@@ -265,7 +305,15 @@ FrontOffice Service Layer:
|
|||||||
"WorkerCount": 4
|
"WorkerCount": 4
|
||||||
},
|
},
|
||||||
"Kavenegar": { "ApiKey": "***" },
|
"Kavenegar": { "ApiKey": "***" },
|
||||||
"ZarinPal": { "MerchantId": "***" },
|
"ZarinPal": { "MerchantId": "***", "UseSandbox": true },
|
||||||
"DayaLoan": { "UseMock": true }
|
"DayaLoan": { "UseMock": true },
|
||||||
|
"CmsBaseUrl": "https://cms.se.kbs1.ir",
|
||||||
|
"FrontOfficeBaseUrl": "http://localhost:5268"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **⚠️ نکات مهم appsettings:**
|
||||||
|
> - `CmsBaseUrl` — برای callback URLهای درگاه (شارژ کیفپول جادویی/اعتباری)
|
||||||
|
> - `FrontOfficeBaseUrl` — برای redirect بعد پرداخت (خرید پکیج/تراکنش عمومی)
|
||||||
|
> - `appsettings.Development.json` — URLهای localhost برای توسعه محلی
|
||||||
|
> - همه callback URLها از config خوانده میشوند — هیچ URL از ورودی کاربر نمیآید (امنیت Open Redirect)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 🖥️ BackOffice و FrontOffice — معماری UI
|
# 🖥️ BackOffice و FrontOffice — معماری UI
|
||||||
|
|
||||||
> **منابع ادغامشده:** `BACKOFFICE-ARCHITECTURE.md`, `BACKOFFICE-STORE-UNIFICATION.md`, `UI-MODERNIZATION-PLAN.md`, `UI-UNIFICATION-PLAN.md`, `PHASE-1-COMPLETE.md`, `PHASE-3-COMPLETE.md`, `PRODUCT-IMAGES-SQUARE.md`
|
> **منابع ادغامشده:** `BACKOFFICE-ARCHITECTURE.md`, `BACKOFFICE-STORE-UNIFICATION.md`, `UI-MODERNIZATION-PLAN.md`, `UI-UNIFICATION-PLAN.md`, `PHASE-1-COMPLETE.md`, `PHASE-3-COMPLETE.md`, `PRODUCT-IMAGES-SQUARE.md`
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: UserAutoComplete + فعالسازی درگاه)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکسهای پرداخت Phase 11 + صفحه موفقیت + تومان/ریال + امنیت Callback)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -119,7 +119,8 @@ FrontOffice/src/FrontOffice/
|
|||||||
│ │ └── Contract.razor ← امضای قرارداد
|
│ │ └── Contract.razor ← امضای قرارداد
|
||||||
│ ├── Profile/
|
│ ├── Profile/
|
||||||
│ │ ├── Index.razor ← داشبورد پروفایل + تایل Magic
|
│ │ ├── Index.razor ← داشبورد پروفایل + تایل Magic
|
||||||
│ │ └── MagicWallet.razor ← 🪄 کیفپول جادویی (NEW)
|
│ │ ├── MagicWallet.razor ← 🪄 کیفپول جادویی
|
||||||
|
│ │ └── PaymentCallback.razor ← 💳 صفحه نتیجه پرداخت (TransactionId + موجودی واقعی)
|
||||||
│ ├── Blog/
|
│ ├── Blog/
|
||||||
│ ├── Auth/
|
│ ├── Auth/
|
||||||
│ │ ├── Login.razor
|
│ │ ├── Login.razor
|
||||||
@@ -134,6 +135,7 @@ FrontOffice/src/FrontOffice/
|
|||||||
└── Shared/
|
└── Shared/
|
||||||
├── AppImage.razor
|
├── AppImage.razor
|
||||||
├── ProductCard.razor ← مشترک بین Store و DiscountStore
|
├── ProductCard.razor ← مشترک بین Store و DiscountStore
|
||||||
|
├── PackagePurchaseDialog.razor ← دیالوگ ۲-مرحلهای خرید پکیج (NEW)
|
||||||
└── LoadMoreButton.razor
|
└── LoadMoreButton.razor
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -263,6 +265,10 @@ if (user.Identity?.IsAuthenticated == true) {
|
|||||||
| UserAutoComplete کامپوننت | ✅ | 100% |
|
| UserAutoComplete کامپوننت | ✅ | 100% |
|
||||||
| نمایش نام کاربر در Wallet | ✅ | 100% |
|
| نمایش نام کاربر در Wallet | ✅ | 100% |
|
||||||
| فعالسازی دکمههای درگاه | ✅ | 100% |
|
| فعالسازی دکمههای درگاه | ✅ | 100% |
|
||||||
|
| PackagePurchaseDialog | ✅ | 100% |
|
||||||
|
| Toman/Rial فیکس نمایش قیمت | ✅ | 100% |
|
||||||
|
| صفحه نتیجه پرداخت (PaymentCallback) | ✅ | 100% |
|
||||||
|
| امنیت Callback URL | ✅ | 100% |
|
||||||
| Mobile Responsive (Phase 7) | ⬜ | 0% |
|
| Mobile Responsive (Phase 7) | ⬜ | 0% |
|
||||||
| Dark Mode | ⬜ | 0% |
|
| Dark Mode | ⬜ | 0% |
|
||||||
| PWA | ⬜ | 0% |
|
| PWA | ⬜ | 0% |
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 🔄 مهاجرت داده، BFF و Gateway
|
# 🔄 مهاجرت داده، BFF و Gateway
|
||||||
|
|
||||||
> **منابع ادغامشده:** `BACKOFFICE-BFF-MIGRATION.md`, `customer-facing-capabilities-codex.md`, `DATA-TABLE-MAPPINGS.md`, `DATAMIGRATION-README.md`, `FRONTOFFICE-TO-CMS-MIGRATION.md`, `GATEWAY-REMOVAL-MIGRATION-PLAN.md`, `MIGRATION-PROGRESS.md`
|
> **منابع ادغامشده:** `BACKOFFICE-BFF-MIGRATION.md`, `customer-facing-capabilities-codex.md`, `DATA-TABLE-MAPPINGS.md`, `DATAMIGRATION-README.md`, `FRONTOFFICE-TO-CMS-MIGRATION.md`, `GATEWAY-REMOVAL-MIGRATION-PLAN.md`, `MIGRATION-PROGRESS.md`
|
||||||
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: Migration پروداکشن)
|
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: DataMigration Tool + EF Staging Migrations)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -136,15 +136,15 @@ Proto-generated classes مستقیم در UI استفاده میشوند
|
|||||||
|
|
||||||
```
|
```
|
||||||
DataMigration/
|
DataMigration/
|
||||||
├── FourSat.DataMigration/ ← Console app
|
├── FourSat.DataMigration/ ← Console app (.NET 9 + Dapper + Polly + Serilog)
|
||||||
│ ├── Program.cs
|
│ ├── Program.cs ← Entry point
|
||||||
│ ├── Migrators/
|
│ ├── appsettings.json ← Source/Target connection strings + TruncateTargetTables
|
||||||
│ │ ├── UserMigrator.cs
|
│ ├── Services/
|
||||||
│ │ ├── ProductMigrator.cs
|
│ │ └── MigrationService.cs ← Smart retry, FK disable/enable, fallback table names
|
||||||
│ │ ├── OrderMigrator.cs
|
│ ├── Scripts/
|
||||||
│ │ └── ClubMigrator.cs
|
│ │ └── PostMigration_DataTransformation.sql ← Guardشده با IF COL_LENGTH/OBJECT_ID
|
||||||
│ └── Mappings/
|
│ └── Mappings/
|
||||||
│ └── TableMappings.cs
|
│ └── TableMappings.cs ← Source → Target table/column mappings
|
||||||
└── FourSat.GeographySeeder/ ← Seed geography data
|
└── FourSat.GeographySeeder/ ← Seed geography data
|
||||||
├── Program.cs
|
├── Program.cs
|
||||||
└── Data/
|
└── Data/
|
||||||
@@ -152,6 +152,21 @@ DataMigration/
|
|||||||
└── cities.json
|
└── cities.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ۵.۱.۱ ویژگیهای DataMigration Tool (اسفند ۱۴۰۴)
|
||||||
|
|
||||||
|
| ویژگی | توضیح |
|
||||||
|
|--------|--------|
|
||||||
|
| **Smart Retry** | فقط خطاهای transient SQL (deadlock, timeout, transport) — نه خطاهای منطقی |
|
||||||
|
| **FK Disable/Enable** | `ALTER TABLE NOCHECK/CHECK CONSTRAINT ALL` حول هر مهاجرت |
|
||||||
|
| **TruncateTargetTables** | حل duplicate key (`IX_ClubMembership_UserId`) هنگام re-run |
|
||||||
|
| **Fallback Table Name** | اگر جدول rename شده (`UserWalletChangeLogs` → `UserWalletHistories`) |
|
||||||
|
| **PostMigration Guards** | همه مراحل با `IF COL_LENGTH`/`OBJECT_ID` برای سازگاری با هر دو schema |
|
||||||
|
| **Polly Retry** | exponential backoff (2s, 8s, 32s) + لاگ structured |
|
||||||
|
| **Serilog** | لاگ فایل + کنسول با جزئیات هر جدول |
|
||||||
|
|
||||||
|
> **کامیتها:** `0e8c6fd` → `8385c90` (MERGE fix) → `31cc464` (FK+truncate+PostMigration)
|
||||||
|
> **وضعیت:** Local only — بدون remote (در workspace `DataMigration/` قرار دارد)
|
||||||
|
|
||||||
### ۵.۲ Data Table Mappings
|
### ۵.۲ Data Table Mappings
|
||||||
|
|
||||||
| جدول مبدأ (قدیم) | جدول مقصد (CMS) | نکات |
|
| جدول مبدأ (قدیم) | جدول مقصد (CMS) | نکات |
|
||||||
@@ -178,14 +193,17 @@ DataMigration/
|
|||||||
| `populate-weekly-commission-pools.sql` | داده تاریخی Pool |
|
| `populate-weekly-commission-pools.sql` | داده تاریخی Pool |
|
||||||
| `update_products_price_10_percent.sql` | افزایش قیمت ۱۰% |
|
| `update_products_price_10_percent.sql` | افزایش قیمت ۱۰% |
|
||||||
|
|
||||||
### ۵.۴ Migrationهای EF Core اجراشده روی Production (اسفند ۱۴۰۴)
|
### ۵.۴ Migrationهای EF Core اجراشده روی Production/Staging (اسفند ۱۴۰۴)
|
||||||
|
|
||||||
| Migration | توضیح | DB |
|
| Migration | توضیح | DB |
|
||||||
|-----------|--------|----|
|
|-----------|--------|----||
|
||||||
| `ExpandDiscountProductFullInformation` | گسترش فیلدهای محصول تخفیفی | KBS (Production `45.149.79.127`) |
|
| `ExpandDiscountProductFullInformation` | گسترش فیلدهای محصول تخفیفی | KBS (Production `45.149.79.127`) |
|
||||||
| `AddMagicWalletFields` (u21) | فیلدهای کیفپول جادویی + ClubMembershipCycle | KBS (Production) |
|
| `AddMagicWalletFields` (u21) | فیلدهای کیفپول جادویی + ClubMembershipCycle | KBS (Production) |
|
||||||
|
| ۵۵ migration کامل | از Initial تا `Q27_HistoryTables_And_RenameWalletHistory` | KBS Staging (`185.252.31.42,2019/KBS`) |
|
||||||
|
| ۵۵ migration کامل | از Initial تا `Q27_HistoryTables_And_RenameWalletHistory` | App DB (`194.5.195.53,31433/Foursat`) |
|
||||||
|
|
||||||
> ⚠️ نکته: Migration `u21` در زمان merge تکراری بود (timestampهای `20260222155925` و `20260222160755`). فایل تکراری حذف شد.
|
> ✅ **نکته:** CMS به ۲ DB مختلف وصل میشود — هر دو باید migrate شوند.
|
||||||
|
> ✅ Migration `u21` در زمان merge تکراری بود — فایل تکراری حذف شد.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -236,3 +254,5 @@ FourSat.GeographySeeder:
|
|||||||
| Geography Seeder | ✅ | 100% |
|
| Geography Seeder | ✅ | 100% |
|
||||||
| Proto package unification | ✅ | 100% |
|
| Proto package unification | ✅ | 100% |
|
||||||
| EF Migration پروداکشن | ✅ | 100% |
|
| EF Migration پروداکشن | ✅ | 100% |
|
||||||
|
| DataMigration Tool (Prod→Staging) | ✅ | 100% |
|
||||||
|
| EF Migration استیجینگ (KBS + Foursat) | ✅ | 100% |
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
# TECH-06 — جریان شارژ Pool کمیسیون هفتگی
|
||||||
|
|
||||||
|
> تاریخ: ۱۴۰۵/۰۲/۱۰
|
||||||
|
> وضعیت: **باگ شناساییشده — منتظر Fix**
|
||||||
|
> مرتبط با: `ActivateClubMembershipCommandHandler.cs` · `AcceptClubMembershipContractCommandHandler.cs` · `CreateManualPaymentCommandHandler.cs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱. مسیر مشتری جدید (اولین خرید پکیج)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor U as کاربر (FrontOffice)
|
||||||
|
participant CB as PaymentCallback.razor
|
||||||
|
participant PI as Profile/Index.razor
|
||||||
|
participant CD as ClubMembershipContractDialog
|
||||||
|
participant CMS as CMS (gRPC)
|
||||||
|
|
||||||
|
U->>CB: بازگشت از درگاه<br/>?type=package&orderId=X&Authority=Y
|
||||||
|
CB->>CMS: CustomerVerifyPackagePurchase(orderId, authority)
|
||||||
|
|
||||||
|
Note over CMS: PackageService.VerifyPackagePurchase()
|
||||||
|
CMS->>CMS: تأیید با درگاه ✓
|
||||||
|
CMS->>CMS: ActivateClubMembership(ForceActivation=false)
|
||||||
|
|
||||||
|
Note over CMS: isNewMembership = true
|
||||||
|
CMS->>CMS: ClubMembership(IsActive=false) ایجاد
|
||||||
|
CMS->>CMS: ClubMembershipCycle #1 ایجاد
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ اول ❌
|
||||||
|
|
||||||
|
CMS-->>CB: Success=true
|
||||||
|
CB->>CB: RefreshToken<br/>HasPurchasedPackage=true<br/>IsClubMemberActive=false
|
||||||
|
|
||||||
|
U->>PI: کلیک "بازگشت به پروفایل"
|
||||||
|
PI->>PI: OnAfterRenderAsync<br/>CheckAndShowClubContractModal()
|
||||||
|
|
||||||
|
Note over PI: HasPurchasedPackage=true<br/>AND IsClubMemberActive=false → نمایش مودال
|
||||||
|
|
||||||
|
PI->>CD: DialogService.ShowAsync (غیرقابل بستن)
|
||||||
|
U->>CD: مطالعه قرارداد + درخواست OTP
|
||||||
|
CD->>CMS: CreateNewOtpToken(purpose=signClubContract)
|
||||||
|
CMS-->>CD: OTP ارسال شد
|
||||||
|
U->>CD: وارد کردن OTP ۶ رقمی
|
||||||
|
CD->>CMS: AcceptClubMembershipContract(otp, signGuid)
|
||||||
|
|
||||||
|
Note over CMS: AcceptClubMembershipContractCommandHandler
|
||||||
|
CMS->>CMS: IsActive == false → guard رد میشه ✓
|
||||||
|
CMS->>CMS: IsActive = true
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ دوم ❌
|
||||||
|
|
||||||
|
CMS-->>CD: Success=true
|
||||||
|
CD->>PI: dialog.Close(Ok)
|
||||||
|
PI->>PI: LoadUserAuthInfo → IsClubMemberActive=true
|
||||||
|
```
|
||||||
|
|
||||||
|
> **نتیجه**: Pool برای عضو جدید **۲ برابر** شارژ میشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲. مسیر خرید مجدد (بعد از تکمیل چرخه Magic)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor U as کاربر (FrontOffice)
|
||||||
|
participant CB as PaymentCallback.razor
|
||||||
|
participant PI as Profile/Index.razor
|
||||||
|
participant CMS as CMS (gRPC)
|
||||||
|
|
||||||
|
Note over CMS: وضعیت: IsActive=true<br/>IsCurrentCycle=true (باگ B6 — ریست نشده)
|
||||||
|
|
||||||
|
U->>CB: بازگشت از درگاه (خرید مجدد)
|
||||||
|
CB->>CMS: CustomerVerifyPackagePurchase(orderId, authority)
|
||||||
|
|
||||||
|
CMS->>CMS: ActivateClubMembership(ForceActivation=false)
|
||||||
|
Note over CMS: isNewMembership = false<br/>existingMembership.IsActive=true<br/>hasCurrentCycle=true
|
||||||
|
|
||||||
|
CMS->>CMS: return true زودهنگام ❌
|
||||||
|
|
||||||
|
Note over CMS: Cycle جدید ساخته نمیشه ❌<br/>Pool شارژ نمیشه ❌
|
||||||
|
|
||||||
|
CMS-->>CB: Success=true
|
||||||
|
CB->>CB: RefreshToken → IsClubMemberActive=true
|
||||||
|
|
||||||
|
U->>PI: بازگشت به پروفایل
|
||||||
|
PI->>PI: IsClubMemberActive=true<br/>→ مودال نمایش داده نمیشه ✓
|
||||||
|
|
||||||
|
Note over PI,CMS: Pool هرگز شارژ نشد ❌<br/>Cycle جدید وجود ندارد ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
> **نتیجه**: Pool برای خرید مجدد **هرگز** شارژ نمیشود. ریشه مشکل: باگ B6 — `IsCurrentCycle` هنگام خروج از Magic ریست نمیشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۳. مسیر ادمین (BackOffice — فعالسازی دستی)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor A as ادمین (BackOffice)
|
||||||
|
participant DL as ActivateClubDialog.razor
|
||||||
|
participant CMS as CMS (gRPC)
|
||||||
|
|
||||||
|
A->>DL: باز کردن دیالوگ فعالسازی برای کاربر X
|
||||||
|
DL->>DL: انتخاب UserId و PackageId
|
||||||
|
A->>DL: کلیک "تایید و فعالسازی"
|
||||||
|
|
||||||
|
DL->>CMS: ActivateClubMembership(UserId=X, ForceActivation=true)
|
||||||
|
Note over CMS: ActivateClubMembershipCommandHandler<br/>skip همه validationهای مالی
|
||||||
|
|
||||||
|
alt کاربر جدید (isNewMembership=true)
|
||||||
|
CMS->>CMS: ClubMembership(IsActive=false) ایجاد
|
||||||
|
CMS->>CMS: Cycle #1 ایجاد
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ اول ❌
|
||||||
|
Note over CMS: کاربر هنوز عضو فعال نیست!<br/>IsActive=false
|
||||||
|
|
||||||
|
Note over A,CMS: کاربر باید به FO رود و قرارداد امضا کند
|
||||||
|
Note over A,CMS: AcceptContract → Pool += fee ← شارژ دوم ❌
|
||||||
|
else خرید مجدد (isNewMembership=false، IsCurrentCycle ریست شده)
|
||||||
|
CMS->>CMS: IsActive=true, hasCurrentCycle=false → ادامه میدهد
|
||||||
|
CMS->>CMS: Cycle جدید ایجاد
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ یک بار ✅
|
||||||
|
end
|
||||||
|
|
||||||
|
CMS-->>DL: Empty (success)
|
||||||
|
DL->>A: "عضویت با موفقیت فعال شد"
|
||||||
|
Note over A,CMS: AcceptContract از BO هرگز فراخوانی نمیشود
|
||||||
|
```
|
||||||
|
|
||||||
|
> **نتیجه**: ادمین برای کاربر جدید نیز باعث double-charge میشود (چون کاربر بعداً از FO قرارداد امضا میکند). برای خرید مجدد رفتار درست است.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. خلاصه باگها
|
||||||
|
|
||||||
|
| سناریو | Pool شارژ واقعی | Pool شارژ انتظاری | Cycle ساخته میشود | وضعیت |
|
||||||
|
|--------|----------------|-------------------|--------------------|--------|
|
||||||
|
| مشتری جدید (IPG) | **2×fee** | 1×fee | ✅ بله | ❌ Double-charge |
|
||||||
|
| خرید مجدد مشتری | **0×fee** | 1×fee | ❌ خیر (B6) | ❌ هرگز شارژ نمیشود |
|
||||||
|
| ادمین — ForceActivate کاربر جدید | **2×fee** | 1×fee | ✅ بله | ❌ Double-charge |
|
||||||
|
| ادمین — ForceActivate خرید مجدد | **1×fee** | 1×fee | ✅ بله | ✅ درست |
|
||||||
|
| ادمین — ManualPayment (پرداخت دستی) | **1×fee** | 1×fee | ❌ خیر | ⚠️ Pool درست، ولی والدین امتیاز نمیگیرند |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. ریشه مشکلات
|
||||||
|
|
||||||
|
### باگ A — Double-charge در عضو جدید
|
||||||
|
**فایل**: `ActivateClubMembershipCommandHandler.cs` — بخش Pool (خط ~۳۱۱)
|
||||||
|
**علت**: هنگامی که `isNewMembership=true`، Pool شارژ میشود؛ بعداً `AcceptContract` هم Pool را شارژ میکند.
|
||||||
|
**Fix**: شارژ Pool در `ActivateClubMembership` را فقط برای `!isNewMembership` انجام بده:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ⭐ 8. اضافه کردن مبلغ به Pool هفته جاری
|
||||||
|
// عضو جدید: Pool توسط AcceptClubMembershipContract شارژ میشه (هنگام امضای قرارداد)
|
||||||
|
// خرید مجدد: قرارداد مجدد امضا نمیشه — Pool همینجا شارژ میشه
|
||||||
|
if (!isNewMembership)
|
||||||
|
{
|
||||||
|
// ... کد موجود شارژ Pool ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### باگ B6 — خرید مجدد کار نمیکند
|
||||||
|
**فایل**: `UserOrderService.cs` — بخش خروج از Magic
|
||||||
|
**علت**: هنگام خروج از Magic، `cycle.IsCurrentCycle` به `false` ریست نمیشود → `ActivateClubMembership` با `hasCurrentCycle=true` زودهنگام برمیگردد.
|
||||||
|
**Fix**: در `ExitMagicMode`:
|
||||||
|
```csharp
|
||||||
|
cycle.IsCurrentCycle = false; // ← اضافه شود
|
||||||
|
```
|
||||||
|
|
||||||
|
### باگ C — پرداخت دستی: Cycle هرگز ساخته نمیشود
|
||||||
|
**فایل**: `CreateManualPaymentCommandHandler.cs`
|
||||||
|
**علت**: پرداخت دستی `ActivateClubMembership` را صدا نمیزند → هیچ `ClubMembershipCycle` ساخته نمیشود → SP این کاربر را به عنوان "عضو جدید" برای والدینش حساب نمیکند.
|
||||||
|
**تأثیر**: Pool یکبار شارژ میشود (توسط AcceptContract ✓) ولی balance والدین در sp_CalculateWeeklyBalances افزایش نمییابد (چون Cycle ندارد ❌).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۴. مسیر پرداخت دستی (BackOffice — ManualPayment)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor A as ادمین (BackOffice)
|
||||||
|
participant DL as ManualPaymentDialog.razor
|
||||||
|
participant CMS as CMS (gRPC)
|
||||||
|
actor U as کاربر (FrontOffice)
|
||||||
|
participant PI as Profile/Index.razor
|
||||||
|
participant CD as ClubMembershipContractDialog
|
||||||
|
|
||||||
|
A->>DL: باز کردن دیالوگ پرداخت دستی
|
||||||
|
DL->>DL: انتخاب کاربر + پکیج + نوع پرداخت + تصویر رسید
|
||||||
|
A->>DL: کلیک "ثبت پرداخت"
|
||||||
|
|
||||||
|
DL->>CMS: CreateManualPayment(userId, packageId, type, referenceNumber)
|
||||||
|
Note over CMS: CreateManualPaymentCommandHandler
|
||||||
|
|
||||||
|
CMS->>CMS: Transaction(DepositExternal1) ایجاد
|
||||||
|
CMS->>CMS: ManualPayment(Status=Approved) ایجاد ← بدون نیاز به تایید دو مرحله
|
||||||
|
CMS->>CMS: wallet.Balance += package.Price
|
||||||
|
CMS->>CMS: wallet.DiscountBalance += package.Price × DiscountMultiplier
|
||||||
|
CMS->>CMS: user.PackagePurchaseMethod = DirectPurchase
|
||||||
|
|
||||||
|
Note over CMS: ❌ ActivateClubMembership صدا زده نمیشود<br/>❌ ClubMembershipCycle ساخته نمیشود<br/>❌ Pool شارژ نمیشود
|
||||||
|
|
||||||
|
CMS-->>DL: ManualPaymentId
|
||||||
|
DL->>A: "پرداخت دستی با موفقیت ثبت شد"
|
||||||
|
|
||||||
|
Note over A,U: کاربر باید به FO مراجعه کند
|
||||||
|
U->>PI: ورود به پروفایل
|
||||||
|
PI->>PI: OnAfterRenderAsync → CheckAndShowClubContractModal()
|
||||||
|
Note over PI: HasPurchasedPackage=true (PackagePurchaseMethod=DirectPurchase)<br/>IsClubMemberActive=false → نمایش مودال
|
||||||
|
|
||||||
|
PI->>CD: DialogService.ShowAsync (غیرقابل بستن)
|
||||||
|
U->>CD: امضای قرارداد + OTP
|
||||||
|
CD->>CMS: AcceptClubMembershipContract(otp, signGuid)
|
||||||
|
|
||||||
|
Note over CMS: AcceptClubMembershipContractCommandHandler
|
||||||
|
CMS->>CMS: user.ClubMembership == null → isNewMembership = true
|
||||||
|
CMS->>CMS: ClubMembership(IsActive=true) ایجاد
|
||||||
|
CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ یکبار ✅
|
||||||
|
|
||||||
|
Note over CMS: ❌ ClubMembershipCycle هرگز ساخته نمیشود<br/>(AcceptContract از Cycle خبری ندارد)
|
||||||
|
|
||||||
|
CMS-->>CD: Success=true
|
||||||
|
CD->>PI: dialog.Close(Ok)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **نتیجه**:
|
||||||
|
> - Pool: **1×** شارژ میشود ✅ (درست)
|
||||||
|
> - `ClubMembershipCycle`: **هرگز ساخته نمیشود** ❌
|
||||||
|
> - در `sp_CalculateWeeklyBalances`: کاربر `IsActive=true` دارد → خودش میتواند کمیسیون دریافت کند ✅
|
||||||
|
> - ولی والدین این کاربر **هیچ "عضو جدید" برای این هفته دریافت نمیکنند** ❌ (چون SP از `ClubMembershipCycles.PackagePurchasedAt` میخواند)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۶. validation داشبورد کمیسیون
|
||||||
|
|
||||||
|
پس از رفع باگ A، validation باید از **فقط یک منبع** استفاده کند:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// درست: فقط ClubMembershipCycles.PackagePurchasedAt
|
||||||
|
// این جدول برای هر خرید (چه جدید چه مجدد) یک رکورد دارد
|
||||||
|
var activations = await _context.ClubMembershipCycles
|
||||||
|
.CountAsync(c => c.PackageId == packageId
|
||||||
|
&& c.PackagePurchasedAt >= weekDef.StartDate
|
||||||
|
&& c.PackagePurchasedAt < weekDef.EndDate);
|
||||||
|
```
|
||||||
|
|
||||||
|
> قبل از رفع باگ A، validation فعلی (firstActivations + cycleActivations) تصادفاً با double-charge جبران میشد.
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
# TECH-07 — لاگ کامل Session 1404/02/10 (2026-04-30)
|
||||||
|
|
||||||
|
> نوع سند: **گزارش کار**
|
||||||
|
> تاریخ: ۱۴۰۵/۰۲/۱۰
|
||||||
|
> مرتبط با: CMS · BackOffice · FrontOffice · Database
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست مطالب
|
||||||
|
|
||||||
|
1. [بخش اول — رفع باگ Double-Charge Pool](#۱-رفع-باگ-double-charge-pool)
|
||||||
|
2. [بخش دوم — بررسی دادههای هفتههای ۲۲ و ۲۳](#۲-بررسی-دادههای-هفتههای-۲۲-و-۲۳)
|
||||||
|
3. [بخش سوم — ویژگی Network Tree (اطلاعات هفتگی)](#۳-ویژگی-network-tree-نوع-فعالسازی--پکیج)
|
||||||
|
4. [بخش چهارم — بهبود UI نمودار درختی](#۴-بهبود-ui-نمودار-درختی)
|
||||||
|
5. [بخش پنجم — رفع باگ Pagination فروشگاه تخفیف](#۵-رفع-باگ-pagination-فروشگاه-تخفیف)
|
||||||
|
6. [خلاصه فایلهای تغییریافته](#خلاصه-فایلهای-تغییریافته)
|
||||||
|
7. [وظایف باقیمانده (Pending)](#وظایف-باقیمانده)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۱. رفع باگ Double-Charge Pool
|
||||||
|
|
||||||
|
### مشکل
|
||||||
|
در جریان فعالسازی عضویت باشگاه، Pool کمیسیون هفتگی **دوبار** شارژ میشد:
|
||||||
|
- بار اول: در `ActivateClubMembership` (از طریق `VerifyPackagePurchase`)
|
||||||
|
- بار دوم: در `AcceptClubMembershipContract` (تأیید قرارداد توسط کاربر)
|
||||||
|
|
||||||
|
همچنین `CreateManualPayment` هم یک مسیر مستقل داشت که بدون Check هفته، Pool اشتباه را شارژ میکرد.
|
||||||
|
|
||||||
|
### ریشه مشکل
|
||||||
|
تابع `GetOrCreateCurrentWeeklyPool` بدون در نظر گرفتن هفته واقعی `PackagePurchasedAt`، Pool هفته جاری را انتخاب میکرد.
|
||||||
|
|
||||||
|
### فایلهای اصلاحشده
|
||||||
|
|
||||||
|
#### `ActivateClubMembershipCommandHandler.cs`
|
||||||
|
```csharp
|
||||||
|
// قبل: همیشه Pool هفته جاری را شارژ میکرد
|
||||||
|
// بعد: فقط یکبار در محل صحیح (AcceptContract) شارژ میشود
|
||||||
|
// حذف: شارژ Pool از داخل ActivateClubMembership (for isNewMembership scenario)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `AcceptClubMembershipContractCommandHandler.cs`
|
||||||
|
```csharp
|
||||||
|
// اضافه: بررسی هفته قرارداد — اگر هفته PackagePurchasedAt با هفته جاری فرق دارد
|
||||||
|
// از Pool هفته مناسب استفاده میکند نه Pool هفته جاری
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CreateManualPaymentCommandHandler.cs`
|
||||||
|
```csharp
|
||||||
|
// اصلاح: Cross-week fix — Pool هفته صحیح بر اساس تاریخ پرداخت دستی
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `sp_CalculateWeeklyCommissionPool.sql` (SP در Infrastructure)
|
||||||
|
```sql
|
||||||
|
-- اصلاح: IsCurrentCycle check برای جلوگیری از Double-Count
|
||||||
|
-- هر کاربر فقط یکبار در محاسبه Pool شمرده میشود
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۲. بررسی دادههای هفتههای ۲۲ و ۲۳
|
||||||
|
|
||||||
|
### تشخیص
|
||||||
|
|
||||||
|
با اجرای diagnostic SQL روی DB، دو anomaly کشف شد:
|
||||||
|
|
||||||
|
#### هفته ۲۲ — Pool Ghost (PoolId=10056)
|
||||||
|
| فیلد | مقدار |
|
||||||
|
|------|-------|
|
||||||
|
| TotalPoolAmount | 2,520,000 |
|
||||||
|
| AllCycles | 0 |
|
||||||
|
| ریشه | هانیه سادات عشاقی (UserId=189) — خرید 1404/01/12 (هفته ۲۱) ولی AcceptContract در 17:02 دقیقه بعد Pool هفته ۲۲ را شارژ کرد |
|
||||||
|
|
||||||
|
**دلیل:** CreatedAt و ModifiedAt timestamp مغایرت داشت — Pool در هفته ۲۲ ایجاد شد اما Cycle در هفته ۲۱ بود.
|
||||||
|
|
||||||
|
**اصلاح دستی DB (Pending):**
|
||||||
|
```sql
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET TotalPoolAmount = 0, LastModified = GETUTCDATE()
|
||||||
|
WHERE Id = 10056 AND WeekDefinitionId = 22;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### هفته ۲۳ — Pool ناقص (PoolId=10054)
|
||||||
|
| فیلد | مقدار |
|
||||||
|
|------|-------|
|
||||||
|
| TotalPoolAmount | 0 |
|
||||||
|
| IsCalculated | False |
|
||||||
|
| ریشه | محمدصادق عسلی (UserId=190) — خرید هفته ۲۳، Cycle وجود دارد ولی Pool=0 (قبل از fix) |
|
||||||
|
|
||||||
|
**اصلاح دستی DB (Pending):**
|
||||||
|
```sql
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET TotalPoolAmount = 2520000, LastModified = GETUTCDATE()
|
||||||
|
WHERE Id = 10054 AND WeekDefinitionId = 23;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۳. ویژگی Network Tree (نوع فعالسازی + پکیج)
|
||||||
|
|
||||||
|
### هدف
|
||||||
|
صفحه `/network/tree` در BackOffice باید در هر node نشان دهد:
|
||||||
|
- آیا این کاربر در هفته انتخابی **عضو جدید** بوده یا **تمدید کرده**؟
|
||||||
|
- نام پکیجی که خریداری کرده؟
|
||||||
|
|
||||||
|
### پیادهسازی Full-Stack
|
||||||
|
|
||||||
|
#### الف) SP_GetNetworkTree (dbbkup/SP_GetNetworkTree.sql)
|
||||||
|
```sql
|
||||||
|
-- اضافه شد:
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 cc.*
|
||||||
|
FROM CMS.ClubMembershipCycles cc
|
||||||
|
WHERE cc.ClubMembershipId = cm.Id
|
||||||
|
AND cc.PackagePurchasedAt >= @WeekStartDate
|
||||||
|
AND cc.PackagePurchasedAt < @WeekEndDate
|
||||||
|
AND (@ActivationWeekDefinitionId IS NULL OR @WeekStartDate IS NOT NULL)
|
||||||
|
) AS cc_target
|
||||||
|
|
||||||
|
-- ستونهای جدید در output:
|
||||||
|
IsActivatedInTargetWeek -- آیا در هفته انتخابی فعال شده؟
|
||||||
|
IsNewActivation -- 1=اولین فعالسازی (CycleNumber=1), 0=تمدید, NULL=بدون Cycle
|
||||||
|
PackageName -- نام پکیج اون هفته
|
||||||
|
PackageId -- شناسه پکیج
|
||||||
|
```
|
||||||
|
|
||||||
|
**نکته:** منطق هفتهبندی از `cm.ActivatedAt` به `Cycle.PackagePurchasedAt` تغییر کرد.
|
||||||
|
|
||||||
|
**Deploy SP:**
|
||||||
|
```
|
||||||
|
SP مستقیم روی DB اجرا شد (نه EmbeddedResource Infrastructure)
|
||||||
|
اجرا شد در: /tmp/DbDiag با C# script
|
||||||
|
تأیید شد: SELECT OBJECT_ID('[CMS].[GetNetworkTree]') → موجود
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ب) Application Layer
|
||||||
|
**`NetworkTreeNodeDto.cs`** — فیلدهای جدید:
|
||||||
|
```csharp
|
||||||
|
bool? IsNewActivation
|
||||||
|
string? PackageName
|
||||||
|
long? PackageId
|
||||||
|
```
|
||||||
|
|
||||||
|
**`NetworkTreeDto.cs`** — همین فیلدها
|
||||||
|
|
||||||
|
**`GetNetworkTreeQueryHandler.cs`**:
|
||||||
|
```csharp
|
||||||
|
// خواندن از DataReader:
|
||||||
|
IsNewActivation = reader.IsDBNull(reader.GetOrdinal("IsNewActivation"))
|
||||||
|
? null
|
||||||
|
: reader.GetInt32(reader.GetOrdinal("IsNewActivation")) == 1,
|
||||||
|
PackageName = reader["PackageName"] as string,
|
||||||
|
PackageId = reader.IsDBNull(reader.GetOrdinal("PackageId"))
|
||||||
|
? null
|
||||||
|
: reader.GetInt64(reader.GetOrdinal("PackageId"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ج) Proto (networkmembership.proto)
|
||||||
|
```protobuf
|
||||||
|
// NetworkTreeNodeModel — فیلدهای جدید:
|
||||||
|
google.protobuf.BoolValue is_new_activation = 22;
|
||||||
|
string package_name = 23;
|
||||||
|
google.protobuf.Int64Value package_id = 24;
|
||||||
|
```
|
||||||
|
|
||||||
|
**NuGet Package:** `Foursat.CMSMicroservice.Protobuf` → از `0.0.194` به **`0.0.195`** bump و push شد.
|
||||||
|
|
||||||
|
#### د) Mapping (NetworkMembershipProfile.cs)
|
||||||
|
```csharp
|
||||||
|
PackageName = node.PackageName ?? string.Empty,
|
||||||
|
PackageId = node.PackageId.HasValue ? node.PackageId.Value : null,
|
||||||
|
IsNewActivation = node.IsNewActivation.HasValue ? node.IsNewActivation.Value : null
|
||||||
|
```
|
||||||
|
|
||||||
|
#### هـ) BackOffice — NetworkTreeViewer.razor
|
||||||
|
**DataGrid — دو ستون جدید:**
|
||||||
|
```razor
|
||||||
|
<!-- ستون نوع فعالسازی -->
|
||||||
|
<PropertyColumn Property="x => x.IsNewActivation" Title="نوع فعالسازی">
|
||||||
|
@if (context.Item.IsNewActivation == true)
|
||||||
|
{
|
||||||
|
<MudChip Color="Color.Success">🆕 عضو جدید</MudChip>
|
||||||
|
}
|
||||||
|
else if (context.Item.IsNewActivation == false)
|
||||||
|
{
|
||||||
|
<MudChip Color="Color.Secondary">🔄 خرید مجدد</MudChip>
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
|
||||||
|
<!-- ستون پکیج -->
|
||||||
|
<PropertyColumn Property="x => x.PackageName" Title="پکیج" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**JS (jsNodes):**
|
||||||
|
```js
|
||||||
|
isNewActivation: n.IsNewActivation,
|
||||||
|
packageName: n.PackageName ?? ""
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۴. بهبود UI نمودار درختی
|
||||||
|
|
||||||
|
### مشکل اولیه
|
||||||
|
بج «🆕 جدید» با `position: absolute` از گوشه کارت بیرون میزد و با محتوای دیگر برخورد میکرد.
|
||||||
|
|
||||||
|
### فایلهای تغییریافته
|
||||||
|
|
||||||
|
#### `admin-org-chart.js` (wwwroot/js)
|
||||||
|
|
||||||
|
**ساختار کارت بازنویسی شد:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ [Avatar] نام کاربر │
|
||||||
|
│ پکیج نقره... │ ← inline زیر اسم
|
||||||
|
│ L13 چپ عضو جدید │ ← pill در meta row
|
||||||
|
├─────────────────────────────┤
|
||||||
|
│ ✓ فعال 1404/12/24 │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**تغییرات:**
|
||||||
|
- `activationTypeBadge` (absolute positioning) → `activationTypePill` (inline span)
|
||||||
|
- `highlightBadge` (✨ floating) → حذف شد
|
||||||
|
- `packageBadge` به زیر اسم کاربر منتقل شد (نه footer)
|
||||||
|
- ابعاد کارت: `160×80` → `178×92` px
|
||||||
|
|
||||||
|
#### `admin-org-chart.css` (wwwroot/css)
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* جدید: activation pill به جای badge */
|
||||||
|
.admin-node-card .activation-pill { /* inline flex */ }
|
||||||
|
.admin-node-card .new-member-pill { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
|
||||||
|
.admin-node-card .renewal-pill { background: #ede7f6; color: #5e35b1; border: 1px solid #b39ddb; }
|
||||||
|
|
||||||
|
/* بهبود: پکیج روشنتر */
|
||||||
|
.admin-node-card .package-name-badge { background: #eceff1; color: #546e7a; border: 1px solid #b0bec5; }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ۵. رفع باگ Pagination فروشگاه تخفیف
|
||||||
|
|
||||||
|
### مشکل
|
||||||
|
در صفحه `/discount-store/products`، دکمه «نمایش محصولات بیشتر» کار نمیکرد — همیشه صفحه اول برمیگشت.
|
||||||
|
|
||||||
|
### ریشه مشکل
|
||||||
|
|
||||||
|
**فایل غایب:** `DiscountProductProfile.cs` (Mapster) وجود نداشت.
|
||||||
|
|
||||||
|
**جریان mapping:**
|
||||||
|
```
|
||||||
|
GetDiscountProductsRequest (proto)
|
||||||
|
↓ request.Adapt<GetDiscountProductsQuery>()
|
||||||
|
GetDiscountProductsQuery
|
||||||
|
```
|
||||||
|
|
||||||
|
بدون profile، auto-mapping دو مشکل داشت:
|
||||||
|
1. `request.SearchQuery (string)` → `query.SearchTerm (string?)` → نامتطابق نام، NULL میشد
|
||||||
|
2. `request.PageNumber (int)` → `query.PaginationQuery.PageNumber` — **Mapster نمیتوانست به nested object مپ کند** → `PaginationQuery = null` → default: `PageNumber=1` همیشه!
|
||||||
|
|
||||||
|
### راهحل
|
||||||
|
|
||||||
|
**فایل جدید:** `CMS/src/CMSMicroservice.WebApi/Common/Mappings/DiscountProductProfile.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
config.NewConfig<GetDiscountProductsRequest, GetDiscountProductsQuery>()
|
||||||
|
.Map(dest => dest.SearchTerm,
|
||||||
|
src => string.IsNullOrEmpty(src.SearchQuery) ? null : src.SearchQuery)
|
||||||
|
.Map(dest => dest.CategoryId,
|
||||||
|
src => src.CategoryId != null ? src.CategoryId.Value : (long?)null)
|
||||||
|
.Map(dest => dest.IsActive,
|
||||||
|
src => src.IsActive != null ? src.IsActive.Value : (bool?)null)
|
||||||
|
.Map(dest => dest.PaginationQuery, src => new PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = src.PageNumber > 0 ? src.PageNumber : 1,
|
||||||
|
PageSize = src.PageSize > 0 ? src.PageSize : 12
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
همچنین `GetDiscountProductsResponseDto → GetDiscountProductsResponse` هم به صورت صریح مپ شد تا `MetaData` و `Models` درست انتقال یابند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خلاصه فایلهای تغییریافته
|
||||||
|
|
||||||
|
| فایل | نوع تغییر | پروژه |
|
||||||
|
|------|-----------|-------|
|
||||||
|
| `ActivateClubMembershipCommandHandler.cs` | Fix — حذف Double-Charge | CMS Application |
|
||||||
|
| `AcceptClubMembershipContractCommandHandler.cs` | Fix — Cross-week Pool | CMS Application |
|
||||||
|
| `CreateManualPaymentCommandHandler.cs` | Fix — Cross-week Pool | CMS Application |
|
||||||
|
| `sp_CalculateWeeklyCommissionPool.sql` | Fix — IsCurrentCycle | CMS Infrastructure |
|
||||||
|
| `SP_GetNetworkTree.sql` | Feature — IsNewActivation, PackageName, PackageId | DB/dbbkup |
|
||||||
|
| `NetworkTreeNodeDto.cs` | Feature — فیلدهای جدید | CMS Application |
|
||||||
|
| `NetworkTreeDto.cs` | Feature — فیلدهای جدید | CMS Application |
|
||||||
|
| `GetNetworkTreeQueryHandler.cs` | Feature — خواندن فیلدهای جدید | CMS Application |
|
||||||
|
| `networkmembership.proto` | Feature — ۳ فیلد جدید در NetworkTreeNodeModel | Protobuf |
|
||||||
|
| `NetworkMembershipProfile.cs` | Feature — mapping فیلدهای جدید | CMS WebApi |
|
||||||
|
| `NetworkTreeViewer.razor` | Feature — DataGrid ستونهای جدید | BackOffice |
|
||||||
|
| `admin-org-chart.js` | Feature+Fix — inline pill، پکیج زیر اسم | BackOffice wwwroot |
|
||||||
|
| `admin-org-chart.css` | Feature+Fix — استایل pillهای مرتب | BackOffice wwwroot |
|
||||||
|
| `DiscountProductProfile.cs` | Fix — Pagination mapping صحیح | CMS WebApi (جدید) |
|
||||||
|
|
||||||
|
### NuGet Package
|
||||||
|
| پکیج | نسخه قبل | نسخه جدید |
|
||||||
|
|------|----------|-----------|
|
||||||
|
| `Foursat.CMSMicroservice.Protobuf` | 0.0.194 | **0.0.195** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## وظایف باقیمانده
|
||||||
|
|
||||||
|
### ضروری — اصلاح دادههای DB
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- هفته ۲۲: Pool Ghost (هانیه سادات عشاقی ← AcceptContract هفته اشتباه)
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET TotalPoolAmount = 0, LastModified = GETUTCDATE()
|
||||||
|
WHERE Id = 10056 AND WeekDefinitionId = 22;
|
||||||
|
|
||||||
|
-- هفته ۲۳: Pool ناقص (محمدصادق عسلی ← Pool قبل از Fix ایجاد شده بود)
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET TotalPoolAmount = 2520000, LastModified = GETUTCDATE()
|
||||||
|
WHERE Id = 10054 AND WeekDefinitionId = 23;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### بهبود آینده
|
||||||
|
- [ ] `SP_GetNetworkTree.sql` به Infrastructure EmbeddedResource اضافه شود (auto-deploy)
|
||||||
|
- [ ] `DiscountProductDto` در Application — اضافه کردن فیلد `Created` از DB
|
||||||
|
- [ ] تست pagination فروشگاه پس از restart CMS
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
# TECH-08 — لاگ Session 1404/02/23 (2026-05-13)
|
||||||
|
|
||||||
|
> نوع سند: **گزارش کار**
|
||||||
|
> تاریخ: ۱۴۰۵/۰۲/۲۳
|
||||||
|
> مرتبط با: CMS · FrontOffice
|
||||||
|
> کامیت CMS: `683ed37` (branch: `kub-stage`)
|
||||||
|
> کامیت FrontOffice: `231da2c` (branch: `kub-stage`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فهرست مطالب
|
||||||
|
|
||||||
|
1. [هدف و خلاصه](#هدف-و-خلاصه)
|
||||||
|
2. [تغییرات CMS (Backend)](#تغییرات-cms-backend)
|
||||||
|
3. [تغییرات FrontOffice](#تغییرات-frontoffice)
|
||||||
|
4. [معماری GuestActionGate](#معماری-guestactiongate)
|
||||||
|
5. [فلوچارت تجربه کاربر](#فلوچارت-تجربه-کاربر)
|
||||||
|
6. [فایلهای تغییر یافته](#فایلهای-تغییر-یافته)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## هدف و خلاصه
|
||||||
|
|
||||||
|
هدف این session:
|
||||||
|
|
||||||
|
1. **نمایش ۶ محصول پرفروش معمولی + ۶ محصول پرفروش فروشگاه اعتباری** در لندینگ پیج FrontOffice، زیر هدر اصلی (۳ محصول در هر ردیف، دو section مجزا).
|
||||||
|
2. **دسترسی guest** (کاربر بدون لاگین) به مرور محصولات برای پرزنت به مشتریان بالقوه.
|
||||||
|
3. **Hybrid auth flow**: کاربر guest محصولات را میبیند؛ اگر روی "افزودن به سبد" کلیک کرد، مودال لاگین باز میشود و پس از ورود موفق، عمل به صورت خودکار انجام میشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## تغییرات CMS (Backend)
|
||||||
|
|
||||||
|
### ۱. `discountproduct.proto`
|
||||||
|
|
||||||
|
```proto
|
||||||
|
// اضافه شده به GetDiscountProductsRequest
|
||||||
|
google.protobuf.StringValue sort_by = 9;
|
||||||
|
|
||||||
|
// اضافه شده به DiscountProductDto
|
||||||
|
int32 sale_count = 12;
|
||||||
|
```
|
||||||
|
|
||||||
|
**چرا:** برای واکشی پرفروشترین محصولات فروشگاه اعتباری باید امکان sort بر اساس `sale_count` وجود داشته باشد. قبلاً این فیلد در DTO برگردانده نمیشد.
|
||||||
|
|
||||||
|
### ۲. `CMSMicroservice.Protobuf.csproj`
|
||||||
|
|
||||||
|
نسخه از `0.0.195` به `0.0.196` بالا رفت تا پکیج NuGet جدید publish شود.
|
||||||
|
|
||||||
|
### ۳. `GetDiscountProductsQuery.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public string? SortBy { get; set; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۴. `GetDiscountProductsQueryHandler.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// قبل: همیشه OrderByDescending(p => p.Created)
|
||||||
|
// بعد: dynamic sort با fallback
|
||||||
|
if (!string.IsNullOrEmpty(request.SortBy))
|
||||||
|
query = query.ApplyOrder(request.SortBy);
|
||||||
|
else
|
||||||
|
query = query.OrderByDescending(p => p.Created);
|
||||||
|
|
||||||
|
// و در SELECT:
|
||||||
|
SaleCount = p.SaleCount,
|
||||||
|
```
|
||||||
|
|
||||||
|
از extension method موجود `ApplyOrder` (کتابخانه `System.Linq.Dynamic.Core`) استفاده شد تا نیازی به تغییر جداگانه نباشد.
|
||||||
|
|
||||||
|
### ۵. `DiscountProductProfile.cs` (Mapster)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Request mapping
|
||||||
|
.Map(dest => dest.SortBy, src => string.IsNullOrEmpty(src.SortBy) ? null : src.SortBy)
|
||||||
|
|
||||||
|
// Response mapping
|
||||||
|
SaleCount = p.SaleCount,
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## تغییرات FrontOffice
|
||||||
|
|
||||||
|
### ۱. `GuestActionGate.cs` (فایل جدید)
|
||||||
|
|
||||||
|
```
|
||||||
|
FrontOffice.Main/Utilities/GuestActionGate.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
سرویس utility جدید که هر action نیازمند لاگین را wrap میکند:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public async Task<bool> RunAsync(Func<Task> action)
|
||||||
|
{
|
||||||
|
if (await _authService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
await _authDialogService.ShowAuthDialogAsync();
|
||||||
|
if (await _authService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
در `ConfigureServices.cs` به صورت Scoped ثبت شد:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
services.AddScoped<GuestActionGate>();
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۲. `ProductService.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public Task<ProductListResult> GetTopSellingAsync(int count = 6)
|
||||||
|
=> GetProductsPagedAsync(sortBy: "SaleCount desc", page: 1, pageSize: count);
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۳. `DiscountProductService.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// پارامتر جدید به GetProductsAsync اضافه شد
|
||||||
|
public async Task<DiscountProductListResult> GetProductsAsync(
|
||||||
|
..., string? sortBy = null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(sortBy))
|
||||||
|
request.SortBy = sortBy;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DiscountProductListResult> GetTopSellingAsync(int count = 6)
|
||||||
|
=> GetProductsAsync(page: 1, pageSize: count, sortBy: "SaleCount desc");
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۴. `Index.razor` و `Index.razor.cs`
|
||||||
|
|
||||||
|
دو section جدید در لندینگ پیج زیر hero اضافه شد:
|
||||||
|
|
||||||
|
**Section 1 — محصولات پرفروش معمولی:**
|
||||||
|
- عنوان: "محصولات پرفروش"
|
||||||
|
- ۶ کارت (۳ در هر ردیف با MudGrid)
|
||||||
|
- هر کارت: تصویر، نام، قیمت با VAT، دکمه "افزودن به سبد"
|
||||||
|
- دکمه "بیشتر" → `/products`
|
||||||
|
|
||||||
|
**Section 2 — محصولات پرفروش فروشگاه اعتباری:**
|
||||||
|
- عنوان: "فروشگاه اعتباری"
|
||||||
|
- ۶ کارت (۳ در هر ردیف)
|
||||||
|
- هر کارت: تصویر، نام، قیمت، درصد تخفیف
|
||||||
|
- دکمه "بیشتر" → `/discount-store`
|
||||||
|
|
||||||
|
**Loading state:** در حین بارگذاری یک spinner نشان داده میشود و سپس sectionها fade-in میشوند.
|
||||||
|
|
||||||
|
**Data loading (parallel):**
|
||||||
|
```csharp
|
||||||
|
var topRegTask = ProductService.GetTopSellingAsync(6);
|
||||||
|
var topDiscTask = DiscountProductService.GetTopSellingAsync(6);
|
||||||
|
var featuredPostsTask = BlogPostService.GetFeaturedPostsAsync(2);
|
||||||
|
await Task.WhenAll(topRegTask, topDiscTask, featuredPostsTask);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cart actions با GuestActionGate:**
|
||||||
|
```csharp
|
||||||
|
private async Task AddRegularToCart(Product p)
|
||||||
|
=> await GuestGate.RunAsync(() => Cart.Add(p, 1));
|
||||||
|
|
||||||
|
private async Task AddDiscountToCart(DiscountProductCard p)
|
||||||
|
=> await GuestGate.RunAsync(() => DiscountCart.AddAsync(p.Id));
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۵. Hybridize کردن صفحات موجود
|
||||||
|
|
||||||
|
#### صفحات لیست و جزئیات محصول (GuestActionGate):
|
||||||
|
|
||||||
|
| فایل | تغییر |
|
||||||
|
|------|-------|
|
||||||
|
| `Store/Products.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` |
|
||||||
|
| `Store/ProductDetail.razor.cs` | `AddToCart` و `RemoveFromCart` → `GuestGate.RunAsync(...)` |
|
||||||
|
| `DiscountStore/Products.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` |
|
||||||
|
| `DiscountStore/ProductDetail.razor.cs` | `AddToCart` → `GuestGate.RunAsync(...)` |
|
||||||
|
|
||||||
|
#### صفحات Cart و Checkout (Soft Auth Gate):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (!await AuthService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await AuthDialogService.ShowAuthDialogAsync();
|
||||||
|
}
|
||||||
|
// ادامه بارگذاری...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
این pattern روی:
|
||||||
|
- `Store/Cart.razor.cs`
|
||||||
|
- `Store/CheckoutSummary.razor.cs`
|
||||||
|
- `DiscountStore/Cart.razor.cs`
|
||||||
|
- `DiscountStore/Checkout.razor.cs`
|
||||||
|
|
||||||
|
اعمال شد. اگر guest مستقیماً وارد سبد خرید شود، مودال لاگین نشان داده میشود.
|
||||||
|
|
||||||
|
### ۶. `MembershipPage.razor` (fix متنی)
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- شارژ ۵۶ میلیون تومان کیف پول فروشگاه اعتباری
|
||||||
|
+ شارژ برابر ارزش پکیج فعال در کیف پول فروشگاه اعتباری
|
||||||
|
```
|
||||||
|
|
||||||
|
متن hardcodeشده با مقدار دینامیک جایگزین شد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## معماری GuestActionGate
|
||||||
|
|
||||||
|
```
|
||||||
|
کاربر کلیک میکند
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
GuestActionGate.RunAsync(action)
|
||||||
|
│
|
||||||
|
├─► آیا لاگین است؟ ──YES──► action() اجرا میشود ✅
|
||||||
|
│
|
||||||
|
NO
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
AuthDialogService.ShowAuthDialogAsync()
|
||||||
|
(مودال OTP باز میشود)
|
||||||
|
│
|
||||||
|
├─► آیا لاگین شد؟ ──YES──► action() اجرا میشود ✅
|
||||||
|
│
|
||||||
|
NO (بستن مودال)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
return false (هیچ اتفاقی نمیافتد) ❌
|
||||||
|
```
|
||||||
|
|
||||||
|
این pattern **defense-in-depth** است: `CartService.Add` هم به تنهایی چک `IsAuthenticatedAsync` دارد؛ `GuestActionGate` لایه UX روی آن اضافه میکند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فلوچارت تجربه کاربر
|
||||||
|
|
||||||
|
```
|
||||||
|
کاربر وارد لندینگ پیج میشود (بدون لاگین)
|
||||||
|
│
|
||||||
|
├─► ۶ محصول پرفروش معمولی نمایش داده میشود
|
||||||
|
├─► ۶ محصول پرفروش اعتباری نمایش داده میشود
|
||||||
|
│
|
||||||
|
├─► "بیشتر" کلیک → /products یا /discount-store
|
||||||
|
│ (صفحات لیست کامل، بدون لاگین قابل مرور)
|
||||||
|
│
|
||||||
|
├─► روی محصول کلیک → صفحه جزئیات
|
||||||
|
│ (بدون لاگین قابل مشاهده)
|
||||||
|
│
|
||||||
|
└─► "افزودن به سبد" کلیک
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
مودال لاگین (OTP)
|
||||||
|
│
|
||||||
|
├─► ورود موفق → محصول به سبد اضافه میشود ✅
|
||||||
|
└─► بستن مودال → هیچ اتفاقی نمیافتد
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فایلهای تغییر یافته
|
||||||
|
|
||||||
|
### CMS — کامیت `683ed37`
|
||||||
|
|
||||||
|
```
|
||||||
|
src/CMSMicroservice.Protobuf/Protos/discountproduct.proto (+2)
|
||||||
|
src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj (~2)
|
||||||
|
src/CMSMicroservice.Application/DiscountShopCQ/Queries/
|
||||||
|
GetDiscountProducts/GetDiscountProductsQuery.cs (+1)
|
||||||
|
GetDiscountProducts/GetDiscountProductsQueryHandler.cs (+7 -3)
|
||||||
|
src/CMSMicroservice.WebApi/Common/Mappings/DiscountProductProfile.cs (+3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### FrontOffice — کامیت `231da2c`
|
||||||
|
|
||||||
|
```
|
||||||
|
src/FrontOffice.Main/Utilities/GuestActionGate.cs (NEW +42)
|
||||||
|
src/FrontOffice.Main/ConfigureServices.cs (+1)
|
||||||
|
src/FrontOffice.Main/Utilities/ProductService.cs (+3)
|
||||||
|
src/FrontOffice.Main/Utilities/DiscountProductService.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/Index.razor (+~180)
|
||||||
|
src/FrontOffice.Main/Pages/Index.razor.cs (+45)
|
||||||
|
src/FrontOffice.Main/Pages/Store/Products.razor.cs (+5)
|
||||||
|
src/FrontOffice.Main/Pages/Store/ProductDetail.razor.cs (+5)
|
||||||
|
src/FrontOffice.Main/Pages/Store/Cart.razor.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/DiscountStore/Products.razor.cs (+5)
|
||||||
|
src/FrontOffice.Main/Pages/DiscountStore/ProductDetail.razor.cs (+5)
|
||||||
|
src/FrontOffice.Main/Pages/DiscountStore/Cart.razor.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/DiscountStore/Checkout.razor.cs (+8)
|
||||||
|
src/FrontOffice.Main/Pages/Club/MembershipPage.razor (~1)
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user