diff --git a/docs/DB-INTEGRITY-AUDIT-20260417.md b/docs/DB-INTEGRITY-AUDIT-20260417.md
new file mode 100644
index 0000000..61a4190
--- /dev/null
+++ b/docs/DB-INTEGRITY-AUDIT-20260417.md
@@ -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 ناسازگاری — آیا از ورود دستی موجودی بوده؟
+
+---
+
+*این گزارش فقط مستندات یافتهها است. هیچ تغییری در کد یا دیتابیس اعمال نشده است.*
diff --git a/technical/TECH-06-CLUB-POOL-CHARGE-FLOW.md b/technical/TECH-06-CLUB-POOL-CHARGE-FLOW.md
new file mode 100644
index 0000000..035f93b
--- /dev/null
+++ b/technical/TECH-06-CLUB-POOL-CHARGE-FLOW.md
@@ -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: بازگشت از درگاه
?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
HasPurchasedPackage=true
IsClubMemberActive=false
+
+ U->>PI: کلیک "بازگشت به پروفایل"
+ PI->>PI: OnAfterRenderAsync
CheckAndShowClubContractModal()
+
+ Note over PI: HasPurchasedPackage=true
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
IsCurrentCycle=true (باگ B6 — ریست نشده)
+
+ U->>CB: بازگشت از درگاه (خرید مجدد)
+ CB->>CMS: CustomerVerifyPackagePurchase(orderId, authority)
+
+ CMS->>CMS: ActivateClubMembership(ForceActivation=false)
+ Note over CMS: isNewMembership = false
existingMembership.IsActive=true
hasCurrentCycle=true
+
+ CMS->>CMS: return true زودهنگام ❌
+
+ Note over CMS: Cycle جدید ساخته نمیشه ❌
Pool شارژ نمیشه ❌
+
+ CMS-->>CB: Success=true
+ CB->>CB: RefreshToken → IsClubMemberActive=true
+
+ U->>PI: بازگشت به پروفایل
+ PI->>PI: IsClubMemberActive=true
→ مودال نمایش داده نمیشه ✓
+
+ Note over PI,CMS: Pool هرگز شارژ نشد ❌
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
skip همه validationهای مالی
+
+ alt کاربر جدید (isNewMembership=true)
+ CMS->>CMS: ClubMembership(IsActive=false) ایجاد
+ CMS->>CMS: Cycle #1 ایجاد
+ CMS->>CMS: ⚡ Pool += ActivationFee ← شارژ اول ❌
+ Note over CMS: کاربر هنوز عضو فعال نیست!
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 صدا زده نمیشود
❌ ClubMembershipCycle ساخته نمیشود
❌ Pool شارژ نمیشود
+
+ CMS-->>DL: ManualPaymentId
+ DL->>A: "پرداخت دستی با موفقیت ثبت شد"
+
+ Note over A,U: کاربر باید به FO مراجعه کند
+ U->>PI: ورود به پروفایل
+ PI->>PI: OnAfterRenderAsync → CheckAndShowClubContractModal()
+ Note over PI: HasPurchasedPackage=true (PackagePurchaseMethod=DirectPurchase)
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 هرگز ساخته نمیشود
(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 جبران میشد.
diff --git a/technical/TECH-07-SESSION-LOG-20260430.md b/technical/TECH-07-SESSION-LOG-20260430.md
new file mode 100644
index 0000000..c7da887
--- /dev/null
+++ b/technical/TECH-07-SESSION-LOG-20260430.md
@@ -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
+
+
+ @if (context.Item.IsNewActivation == true)
+ {
+ 🆕 عضو جدید
+ }
+ else if (context.Item.IsNewActivation == false)
+ {
+ 🔄 خرید مجدد
+ }
+
+
+
+
+```
+
+**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
+```
+
+بدون 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()
+ .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