- 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.
13 KiB
TECH-07 — لاگ کامل Session 1404/02/10 (2026-04-30)
نوع سند: گزارش کار
تاریخ: ۱۴۰۵/۰۲/۱۰
مرتبط با: CMS · BackOffice · FrontOffice · Database
فهرست مطالب
- بخش اول — رفع باگ Double-Charge Pool
- بخش دوم — بررسی دادههای هفتههای ۲۲ و ۲۳
- بخش سوم — ویژگی Network Tree (اطلاعات هفتگی)
- بخش چهارم — بهبود UI نمودار درختی
- بخش پنجم — رفع باگ Pagination فروشگاه تخفیف
- خلاصه فایلهای تغییریافته
- وظایف باقیمانده (Pending)
۱. رفع باگ Double-Charge Pool
مشکل
در جریان فعالسازی عضویت باشگاه، Pool کمیسیون هفتگی دوبار شارژ میشد:
- بار اول: در
ActivateClubMembership(از طریقVerifyPackagePurchase) - بار دوم: در
AcceptClubMembershipContract(تأیید قرارداد توسط کاربر)
همچنین CreateManualPayment هم یک مسیر مستقل داشت که بدون Check هفته، Pool اشتباه را شارژ میکرد.
ریشه مشکل
تابع GetOrCreateCurrentWeeklyPool بدون در نظر گرفتن هفته واقعی PackagePurchasedAt، Pool هفته جاری را انتخاب میکرد.
فایلهای اصلاحشده
ActivateClubMembershipCommandHandler.cs
// قبل: همیشه Pool هفته جاری را شارژ میکرد
// بعد: فقط یکبار در محل صحیح (AcceptContract) شارژ میشود
// حذف: شارژ Pool از داخل ActivateClubMembership (for isNewMembership scenario)
AcceptClubMembershipContractCommandHandler.cs
// اضافه: بررسی هفته قرارداد — اگر هفته PackagePurchasedAt با هفته جاری فرق دارد
// از Pool هفته مناسب استفاده میکند نه Pool هفته جاری
CreateManualPaymentCommandHandler.cs
// اصلاح: Cross-week fix — Pool هفته صحیح بر اساس تاریخ پرداخت دستی
sp_CalculateWeeklyCommissionPool.sql (SP در Infrastructure)
-- اصلاح: 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):
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):
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)
-- اضافه شد:
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 — فیلدهای جدید:
bool? IsNewActivation
string? PackageName
long? PackageId
NetworkTreeDto.cs — همین فیلدها
GetNetworkTreeQueryHandler.cs:
// خواندن از 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)
// 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)
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 — دو ستون جدید:
<!-- ستون نوع فعالسازی -->
<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):
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×92px
admin-org-chart.css (wwwroot/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 دو مشکل داشت:
request.SearchQuery (string)→query.SearchTerm (string?)→ نامتطابق نام، NULL میشدrequest.PageNumber (int)→query.PaginationQuery.PageNumber— Mapster نمیتوانست به nested object مپ کند →PaginationQuery = null→ default:PageNumber=1همیشه!
راهحل
فایل جدید: CMS/src/CMSMicroservice.WebApi/Common/Mappings/DiscountProductProfile.cs
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
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