1db77b1a1b
- 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.
335 lines
13 KiB
Markdown
335 lines
13 KiB
Markdown
# 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
|