# گزارش تغییرات - 2025-12-12 ## خلاصه اجرایی این سشن شامل دو بخش اصلی بود: 1. **تبدیل نمایش تاریخ‌ها به شمسی** در فرانت‌اند BackOffice 2. **بهبود سرویس اطلاعات شبکه کاربران** با اضافه کردن 28+ فیلد جدید --- ## بخش 1: سیستم تبدیل تاریخ شمسی ### 1.1. ایجاد PersianDateTimeService **فایل:** `/BackOffice/src/BackOffice/Services/PersianDateTimeService.cs` سرویسی برای تبدیل تاریخ‌های میلادی به شمسی در لایه نمایش: ```csharp public interface IPersianDateTimeService { string GetCurrentWeekNumber(); // "1404-W23" string ConvertWeekNumberToPersian(string); // "2025-W48" → "1404-W23" string ConvertToPersianDate(DateTime); // DateTime → "1404/09/21" string ConvertToPersianDateTime(DateTime); // DateTime → "1404/09/21 - 14:30" string GetWeekRangeDisplay(string); // "شنبه 1404/09/15 تا جمعه 1404/09/21" } ``` **قابلیت‌های کلیدی:** - تبدیل شماره هفته میلادی به شمسی با حفظ هفته شنبه‌محور - فرمت‌دهی تاریخ و تاریخ‌وزمان شمسی - نمایش بازه هفتگی با نام روزهای فارسی ### 1.2. ثبت سرویس در DI Container **فایل:** `/BackOffice/src/BackOffice/ConfigureService.cs` ```csharp services.AddSingleton(); ``` ### 1.3. آپدیت صفحات فرانت‌اند #### Dashboard.razor + Dashboard.razor.cs **تغییرات:** - Inject کردن `IPersianDateTimeService` - اضافه کردن فیلد `_currentWeekNumberPersian` - تبدیل شماره هفته در `OnInitializedAsync` و `OnWeekChanged` - نمایش تاریخ محاسبه Pool به شمسی **نمونه کد:** ```csharp [Inject] public IPersianDateTimeService PersianDateTime { get; set; } private string _currentWeekNumberPersian = string.Empty; protected override async Task OnInitializedAsync() { _currentWeekNumber = GetCurrentWeekNumber(); // "2025-W48" _currentWeekNumberPersian = PersianDateTime.ConvertWeekNumberToPersian(_currentWeekNumber); // "1404-W23" } ``` ```razor هفته @(_currentWeekNumberPersian) @if (_poolData?.CalculatedAt != null) { var persianDate = PersianDateTime.ConvertToPersianDateTime(calculatedDate); @($"در تاریخ {persianDate}") } ``` #### UserPayouts.razor + UserPayouts.razor.cs **تغییرات:** - Inject کردن `IPersianDateTimeService` - تبدیل شماره هفته در ستون جدول - تبدیل تاریخ ایجاد Payout **نمونه کد:** ```razor @{ var persianWeek = PersianDateTime.ConvertWeekNumberToPersian(context.Item.WeekNumber); } @persianWeek ``` #### WorkerControl.razor **تغییرات:** - Inject کردن `IPersianDateTimeService` - تبدیل تاریخ آخرین اجرا و اجرای بعدی Worker - تبدیل شماره هفته و تاریخ در لاگ اجرا - نمایش پیام تایید با هفته شمسی **نمونه کد:** ```razor آخرین اجرا: @PersianDateTime.ConvertToPersianDateTime(_lastRunTime) @PersianDateTime.ConvertWeekNumberToPersian(context.WeekNumber) ``` ### 1.4. استراتژی معماری **بک‌اند (CMS):** - ✅ ذخیره و محاسبه با تاریخ میلادی - ✅ شماره هفته فرمت میلادی: `"2025-W48"` - ✅ هفته از شنبه شروع می‌شود **فرانت‌اند (BackOffice):** - ✅ دریافت داده‌های میلادی از API - ✅ تبدیل به شمسی فقط در لایه نمایش (Presentation Layer) - ✅ هیچ تغییری در API Call ها یا Database **مزایا:** - جداسازی کامل Business Logic از Presentation - امکان تغییر نمایش بدون تأثیر بر دیتابیس - سازگاری با APIهای خارجی که میلادی هستند --- ## بخش 2: بهبود سرویس GetUserNetworkPosition ### 2.1. آپدیت UserNetworkPositionDto (CMS) **فایل:** `/CMS/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/UserNetworkPositionDto.cs` **فیلدهای اضافه شده (28+ فیلد جدید):** #### اطلاعات شخصی کاربر ```csharp public string? Email { get; set; } public string? NationalCode { get; set; } public string ReferralCode { get; set; } public bool IsMobileVerified { get; set; } public DateTime? BirthDate { get; set; } public DateTime JoinedAt { get; set; } ``` #### اطلاعات والد (تکمیل شده) ```csharp public string? ParentFullName { get; set; } ``` #### اطلاعات فرزندان مستقیم (جزئیات کامل) ```csharp // فرزند چپ public long? LeftChildId { get; set; } public string? LeftChildFullName { get; set; } public string? LeftChildMobile { get; set; } public DateTime? LeftChildJoinedAt { get; set; } // فرزند راست public long? RightChildId { get; set; } public string? RightChildFullName { get; set; } public string? RightChildMobile { get; set; } public DateTime? RightChildJoinedAt { get; set; } ``` #### آمار کامل شبکه ```csharp public int TotalLeftLegMembers { get; set; } // کل اعضای شاخه چپ (همه سطوح) public int TotalRightLegMembers { get; set; } // کل اعضای شاخه راست (همه سطوح) public int TotalNetworkSize { get; set; } // کل اعضای شبکه public int MaxNetworkDepth { get; set; } // حداکثر عمق شبکه ``` #### اطلاعات پکیج و دایا ```csharp public bool HasReceivedDayaCredit { get; set; } public DateTime? DayaCreditReceivedAt { get; set; } public PackagePurchaseMethod PackagePurchaseMethod { get; set; } public bool HasPurchasedGoldenPackage { get; set; } ``` #### آمار مالی (کمیسیون) ```csharp public decimal TotalEarnedCommission { get; set; } // کل کمیسیون کسب شده public decimal TotalPaidCommission { get; set; } // کمیسیون پرداخت شده public decimal PendingCommission { get; set; } // کمیسیون در انتظار public int TotalBalancesEarned { get; set; } // تعداد بالانس‌های کسب شده ``` #### آمار فعالیت ```csharp public int ActiveMembersInNetwork { get; set; } // اعضای فعال (پکیج خریده) public int InactiveMembersInNetwork { get; set; } // اعضای غیرفعال ``` ### 2.2. آپدیت GetUserNetworkPositionQueryHandler **فایل:** `/CMS/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/GetUserNetworkPositionQueryHandler.cs` **متدهای کمکی جدید:** ```csharp /// /// محاسبه تعداد اعضای یک شاخه (چپ یا راست) به صورت بازگشتی /// private async Task GetLegMemberCountAsync(long userId, NetworkLeg leg, CancellationToken cancellationToken) /// /// محاسبه حداکثر عمق شبکه /// private async Task GetMaxNetworkDepthAsync(long userId, CancellationToken cancellationToken) /// /// دریافت تمام ID های زیرمجموعه یک کاربر /// private async Task> GetAllDescendantIdsAsync(long userId, CancellationToken cancellationToken) ``` **کوئری‌های جدید:** - محاسبه آمار کمیسیون از جدول `UserCommissionPayouts` - شمارش اعضای فعال/غیرفعال بر اساس `PackagePurchaseMethod` - واکشی اطلاعات کامل فرزندان با موبایل و تاریخ عضویت ### 2.3. آپدیت Protobuf Messages **فایل‌ها:** - `/CMS/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto` - `/BackOffice.BFF/src/Protobufs/BackOffice.BFF.NetworkMembership.Protobuf/Protos/networkmembership.proto` **تغییرات:** افزایش فیلدها از 14 به 42 فیلد ```protobuf message GetUserNetworkResponse { // اطلاعات اصلی کاربر int64 id = 1; int64 user_id = 2; string user_name = 3; string mobile = 4; string email = 5; string national_code = 6; string referral_code = 7; bool is_mobile_verified = 8; google.protobuf.Timestamp birth_date = 9; google.protobuf.Timestamp joined_at = 10; // اطلاعات والد google.protobuf.Int64Value parent_id = 11; string parent_name = 12; string parent_mobile = 13; // موقعیت در شبکه int32 network_leg = 14; int32 network_level = 15; bool is_in_network = 16; // اطلاعات فرزند چپ google.protobuf.Int64Value left_child_id = 17; string left_child_name = 18; string left_child_mobile = 19; google.protobuf.Timestamp left_child_joined_at = 20; // اطلاعات فرزند راست google.protobuf.Int64Value right_child_id = 21; string right_child_name = 22; string right_child_mobile = 23; google.protobuf.Timestamp right_child_joined_at = 24; // آمار فرزندان مستقیم int32 total_children = 25; int32 left_child_count = 26; int32 right_child_count = 27; // آمار کل شبکه int32 total_left_leg_members = 28; int32 total_right_leg_members = 29; int32 total_network_size = 30; int32 max_network_depth = 31; // اطلاعات پکیج و دایا bool has_received_daya_credit = 32; google.protobuf.Timestamp daya_credit_received_at = 33; int32 package_purchase_method = 34; bool has_purchased_golden_package = 35; // آمار مالی double total_earned_commission = 36; double total_paid_commission = 37; double pending_commission = 38; int32 total_balances_earned = 39; // آمار فعالیت int32 active_members_in_network = 40; int32 inactive_members_in_network = 41; google.protobuf.Timestamp created = 42; } ``` ### 2.4. آپدیت CMS Mapping Profile **فایل:** `/CMS/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs` **تغییرات:** 40+ خط mapping برای تمام فیلدهای جدید ```csharp config.NewConfig() .Map(dest => dest.Mobile, src => src.Mobile ?? "") .Map(dest => dest.Email, src => src.Email ?? "") .Map(dest => dest.NationalCode, src => src.NationalCode ?? "") .Map(dest => dest.ReferralCode, src => src.ReferralCode) .Map(dest => dest.IsMobileVerified, src => src.IsMobileVerified) // ... 35+ mappings دیگر .Map(dest => dest.TotalEarnedCommission, src => (double)src.TotalEarnedCommission) .Map(dest => dest.ActiveMembersInNetwork, src => src.ActiveMembersInNetwork); ``` ### 2.5. آپدیت BackOffice BFF #### GetUserNetworkInfoResponseDto **فایل:** `/BackOffice.BFF/src/BackOffice.BFF.Application/NetworkMembershipCQ/Queries/GetUserNetworkInfo/GetUserNetworkInfoResponseDto.cs` **تغییرات:** همان 42 فیلد CMS برای consistency #### NetworkMembershipProfile (BFF) **فایل:** `/BackOffice.BFF/src/BackOffice.BFF.WebApi/Common/Mappings/NetworkMembershipProfile.cs` **تغییرات:** Mapping کامل از DTO به Protobuf Response با تبدیل DateTime به Timestamp ```csharp config.NewConfig() .MapWith(src => new GetUserNetworkResponse { // ... 42 field mapping با تبدیل صحیح DateTime ها BirthDate = src.BirthDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.BirthDate.Value, DateTimeKind.Utc)) : null, // ... }); ``` ### 2.6. آپدیت صفحه UserNetworkInfo.razor **فایل:** `/BackOffice/src/BackOffice/Pages/Network/UserNetworkInfo.razor` **بازنویسی کامل UI با 6 کارت اصلی:** #### 1. کارت اطلاعات کاربر - شناسه، نام، موبایل (با badge تایید) - ایمیل، کد ملی - کد ارجاع - موقعیت در شبکه - تاریخ عضویت (شمسی) #### 2. کارت ساختار شبکه - اطلاعات والد (نام، موبایل، لینک) - فرزند چپ (نام، موبایل، تاریخ عضویت، لینک) - فرزند راست (نام، موبایل، تاریخ عضویت، لینک) #### 3. کارت آمار کامل شبکه (6 آیتم با آیکون) ```razor @_userInfo.TotalNetworkSize ``` #### 4. کارت آمار مالی و کمیسیون - کل کمیسیون کسب شده (با فرمت هزارگان) - کمیسیون پرداخت شده - کمیسیون در انتظار - تعداد بالانس کسب شده #### 5. کارت وضعیت پکیج و دایا - وضعیت پکیج طلایی (با روش خرید) - وضعیت اعتبار دایا (با تاریخ دریافت شمسی) #### 6. کارت عملیات - دکمه نمایش درخت کامل - دکمه Payout های کاربر (جدید) - دکمه بروزرسانی **ویژگی‌های UI:** - استفاده از MudBlazor Components - آیکون‌های Material Design - رنگ‌بندی semantic (Success, Warning, Info, Error) - فرمت هزارگان برای مبالغ ریالی - تاریخ‌های شمسی با `PersianDateTimeService` --- ## بخش 3: اصلاح الگوریتم محاسبه شماره هفته ### 3.1. مشکل اولیه **علت:** استفاده از `CalendarWeekRule.FirstDay` در C# که محاسبه اشتباه می‌کرد **نتیجه:** - C# (GetAvailableWeeksQueryHandler): هفته 50 ❌ - SQL (populate-weekly-commission-pools.sql): هفته 49 ✅ ### 3.2. محاسبه صحیح (Saturday-based) **برای تاریخ 2025-12-12 (پنجشنبه):** 1. اولین روز سال: 2025-01-01 = چهارشنبه 2. اولین شنبه سال: 2025-01-04 3. شنبه این هفته: 2025-12-07 4. فاصله: 337 روز 5. شماره هفته: 337 ÷ 7 = 48.14 → **هفته 49** ✅ ### 3.3. آپدیت GetAvailableWeeksQueryHandler **فایل:** `/CMS/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAvailableWeeks/GetAvailableWeeksQueryHandler.cs` **قبل:** ```csharp private static string GetWeekNumber(DateTime date) { var calendar = CultureInfo.InvariantCulture.Calendar; var weekOfYear = calendar.GetWeekOfYear( date, CalendarWeekRule.FirstDay, // ❌ اشتباه DayOfWeek.Saturday); return $"{date.Year}-W{weekOfYear:D2}"; } ``` **بعد:** ```csharp private static string GetWeekNumber(DateTime date) { var year = date.Year; // پیدا کردن اولین شنبه سال var jan1 = new DateTime(year, 1, 1); var jan1DayOfWeek = (int)jan1.DayOfWeek; // محاسبه offset تا اولین شنبه var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7; var firstSaturday = jan1.AddDays(daysToFirstSaturday); // پیدا کردن شنبه شروع هفته جاری var currentDayOfWeek = (int)date.DayOfWeek; var daysToCurrentSaturday = currentDayOfWeek == 6 ? 0 : (currentDayOfWeek + 1) % 7; var weekStartSaturday = date.Date.AddDays(-daysToCurrentSaturday); // محاسبه شماره هفته int weekNum; if (weekStartSaturday < firstSaturday) { weekNum = 1; } else { var daysSinceFirstSaturday = (weekStartSaturday - firstSaturday).Days; weekNum = (daysSinceFirstSaturday / 7) + 1; } return $"{year}-W{weekNum:D2}"; } ``` ### 3.4. آپدیت SQL Script **فایل:** `/dbbkup/populate-weekly-commission-pools.sql` **تغییرات مشابه در تابع `GetWeekNumber`:** ```sql CREATE FUNCTION dbo.GetWeekNumber (@Date DATETIME) RETURNS NVARCHAR(10) AS BEGIN DECLARE @Year INT = YEAR(@Date); -- پیدا کردن اولین شنبه سال DECLARE @Jan1 DATE = CAST(CAST(@Year AS VARCHAR(4)) + '-01-01' AS DATE); DECLARE @Jan1DayOfWeek INT = DATEPART(WEEKDAY, @Jan1); -- محاسبه offset DECLARE @DaysToFirstSaturday INT; IF @Jan1DayOfWeek = 7 SET @DaysToFirstSaturday = 0; ELSE SET @DaysToFirstSaturday = 7 - @Jan1DayOfWeek; DECLARE @FirstSaturday DATE = DATEADD(DAY, @DaysToFirstSaturday, @Jan1); -- پیدا کردن شنبه شروع هفته جاری DECLARE @CurrentDayOfWeek INT = DATEPART(WEEKDAY, @Date); DECLARE @DaysToCurrentSaturday INT; IF @CurrentDayOfWeek = 7 SET @DaysToCurrentSaturday = 0; ELSE SET @DaysToCurrentSaturday = @CurrentDayOfWeek - 1; DECLARE @WeekStartSaturday DATE = DATEADD(DAY, -@DaysToCurrentSaturday, @Date); -- محاسبه شماره هفته DECLARE @WeekNum INT; IF @WeekStartSaturday < @FirstSaturday SET @WeekNum = 1; ELSE BEGIN DECLARE @DaysSinceFirstSaturday INT = DATEDIFF(DAY, @FirstSaturday, @WeekStartSaturday); SET @WeekNum = (@DaysSinceFirstSaturday / 7) + 1; END RETURN CAST(@Year AS NVARCHAR(4)) + '-W' + RIGHT('0' + CAST(@WeekNum AS NVARCHAR(2)), 2); END ``` ### 3.5. سایر فایل‌های آپدیت شده **CalculateWeeklyBalancesCommandHandler.cs:** - متد `GetWeekDateRange()` با الگوریتم دقیق‌تر **GetAvailableWeeksQueryHandler.cs:** - متد `GetWeekRange()` برای محاسبه بازه شنبه تا جمعه **همه یکپارچه شدند:** C# ≡ SQL ≡ Frontend Display ✅ --- ## خلاصه فایل‌های تغییر یافته ### فایل‌های جدید 1. `/BackOffice/src/BackOffice/Services/PersianDateTimeService.cs` ⭐ جدید ### فایل‌های CMS 1. `/CMS/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/UserNetworkPositionDto.cs` 2. `/CMS/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/GetUserNetworkPositionQueryHandler.cs` 3. `/CMS/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto` 4. `/CMS/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs` 5. `/CMS/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAvailableWeeks/GetAvailableWeeksQueryHandler.cs` 6. `/CMS/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs` ### فایل‌های BackOffice.BFF 7. `/BackOffice.BFF/src/Protobufs/BackOffice.BFF.NetworkMembership.Protobuf/Protos/networkmembership.proto` 8. `/BackOffice.BFF/src/BackOffice.BFF.Application/NetworkMembershipCQ/Queries/GetUserNetworkInfo/GetUserNetworkInfoResponseDto.cs` 9. `/BackOffice.BFF/src/BackOffice.BFF.WebApi/Common/Mappings/NetworkMembershipProfile.cs` ### فایل‌های BackOffice (Frontend) 10. `/BackOffice/src/BackOffice/ConfigureService.cs` 11. `/BackOffice/src/BackOffice/Pages/Commission/Dashboard.razor` 12. `/BackOffice/src/BackOffice/Pages/Commission/Dashboard.razor.cs` 13. `/BackOffice/src/BackOffice/Pages/Commission/UserPayouts.razor` 14. `/BackOffice/src/BackOffice/Pages/Commission/UserPayouts.razor.cs` 15. `/BackOffice/src/BackOffice/Pages/SystemManagement/WorkerControl.razor` 16. `/BackOffice/src/BackOffice/Pages/Network/UserNetworkInfo.razor` ### فایل‌های SQL 17. `/dbbkup/populate-weekly-commission-pools.sql` --- ## نتایج و دستاوردها ### ✅ سیستم تاریخ شمسی - **3 صفحه** اصلی به شمسی تبدیل شد - **صفر تغییر** در Backend یا Database - **معماری پاک** با جداسازی Presentation از Business Logic - **Performance**: سرویس Singleton بدون overhead ### ✅ بهبود سرویس شبکه - **28+ فیلد جدید** اضافه شد - **3 متد بازگشتی** برای محاسبه آمار شبکه - **یکپارچگی کامل** از CMS تا UI - **UI کاملا بازنویسی** شد با 6 کارت اطلاعاتی ### ✅ اصلاح الگوریتم هفته - **یکپارچگی کامل** بین C#, SQL, Frontend - **محاسبه دقیق** Saturday-based - **صفر اختلاف** بین سیستم‌ها ### 📊 آمار کلی - **17 فایل** ویرایش شد - **1 فایل جدید** ایجاد شد - **42 فیلد Protobuf** به جای 14 فیلد - **3 صفحه Frontend** به شمسی تبدیل شد - **2 الگوریتم** (C# + SQL) یکپارچه شد --- ## تست و Validation ### Build Status - ✅ CMS: Build Successful (0 Errors, 465 Warnings - معمولی) - ✅ BackOffice.BFF: Build Successful (0 Errors, 199 Warnings - معمولی) - ✅ BackOffice: Build Successful (0 Errors, 239 Warnings - MudBlazor) ### محاسبات تست شده - ✅ تاریخ 2025-12-12 → هفته 49 (یکسان در همه سیستم‌ها) - ✅ تبدیل شمسی "1404/09/21" ← 2025-12-12 - ✅ محاسبه بازه هفته: شنبه 2025-12-07 تا جمعه 2025-12-13 --- ## توصیه‌های آینده ### کارهای تکمیلی پیشنهادی 1. **Component Reusability**: ایجاد Blazor Components مشترک برای نمایش تاریخ شمسی ```razor ``` 2. **Caching**: اضافه کردن Cache برای محاسبات تبدیل هفته (اگر Performance مشکل شد) 3. **Testing**: نوشتن Unit Test برای `GetWeekNumber` در C# و SQL 4. **Documentation**: اضافه کردن XML Comments بیشتر برای API Documentation 5. **صفحات باقیمانده**: اگر صفحات دیگری تاریخ نمایش می‌دهند، آن‌ها را هم تبدیل کنید --- ## نکات فنی مهم ### Saturday-based Week Calculation ``` هفته از شنبه شروع می‌شود: - شنبه: روز اول هفته - جمعه: روز آخر هفته - Week 1: اولین شنبه سال ``` ### DateTime to Timestamp Conversion ```csharp // در Protobuf mapping همیشه UTC specify کنید Timestamp.FromDateTime(DateTime.SpecifyKind(dateTime, DateTimeKind.Utc)) ``` ### Persian Calendar in C# ```csharp private readonly PersianCalendar _persianCalendar = new(); var persianYear = _persianCalendar.GetYear(dateTime); var persianMonth = _persianCalendar.GetMonth(dateTime); var persianDay = _persianCalendar.GetDayOfMonth(dateTime); ``` --- **تاریخ:** 2025-12-12 **مدت زمان:** 1 Session **وضعیت:** ✅ Completed & Tested **تیم:** Masoud + GitHub Copilot