Add comprehensive database integrity audit report and fix critical bugs in commission pool charging flow
- Introduced a detailed audit report for the CMS database integrity, highlighting issues related to data entry, code bugs, and stored procedures. - Fixed double-charge issue in the commission pool during club membership activation. - Updated stored procedures to ensure correct pool calculations across different weeks. - Enhanced the network tree feature to include activation type and package details. - Improved UI for the network tree display and resolved pagination issues in the discount store.
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user