23 KiB
گزارش تغییرات - 2025-12-12
خلاصه اجرایی
این سشن شامل دو بخش اصلی بود:
- تبدیل نمایش تاریخها به شمسی در فرانتاند BackOffice
- بهبود سرویس اطلاعات شبکه کاربران با اضافه کردن 28+ فیلد جدید
بخش 1: سیستم تبدیل تاریخ شمسی
1.1. ایجاد PersianDateTimeService
فایل: /BackOffice/src/BackOffice/Services/PersianDateTimeService.cs
سرویسی برای تبدیل تاریخهای میلادی به شمسی در لایه نمایش:
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
services.AddSingleton<BackOffice.Services.IPersianDateTimeService,
BackOffice.Services.PersianDateTimeService>();
1.3. آپدیت صفحات فرانتاند
Dashboard.razor + Dashboard.razor.cs
تغییرات:
- Inject کردن
IPersianDateTimeService - اضافه کردن فیلد
_currentWeekNumberPersian - تبدیل شماره هفته در
OnInitializedAsyncوOnWeekChanged - نمایش تاریخ محاسبه Pool به شمسی
نمونه کد:
[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"
}
<MudText Typo="Typo.body2">هفته @(_currentWeekNumberPersian)</MudText>
@if (_poolData?.CalculatedAt != null)
{
var persianDate = PersianDateTime.ConvertToPersianDateTime(calculatedDate);
@($"در تاریخ {persianDate}")
}
UserPayouts.razor + UserPayouts.razor.cs
تغییرات:
- Inject کردن
IPersianDateTimeService - تبدیل شماره هفته در ستون جدول
- تبدیل تاریخ ایجاد Payout
نمونه کد:
<PropertyColumn Property="x => x.WeekNumber" Title="هفته">
<CellTemplate>
@{
var persianWeek = PersianDateTime.ConvertWeekNumberToPersian(context.Item.WeekNumber);
}
<MudText Typo="Typo.body2">@persianWeek</MudText>
</CellTemplate>
</PropertyColumn>
WorkerControl.razor
تغییرات:
- Inject کردن
IPersianDateTimeService - تبدیل تاریخ آخرین اجرا و اجرای بعدی Worker
- تبدیل شماره هفته و تاریخ در لاگ اجرا
- نمایش پیام تایید با هفته شمسی
نمونه کد:
<tr>
<td><strong>آخرین اجرا:</strong></td>
<td>@PersianDateTime.ConvertToPersianDateTime(_lastRunTime)</td>
</tr>
<MudTd DataLabel="هفته">@PersianDateTime.ConvertWeekNumberToPersian(context.WeekNumber)</MudTd>
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+ فیلد جدید):
اطلاعات شخصی کاربر
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; }
اطلاعات والد (تکمیل شده)
public string? ParentFullName { get; set; }
اطلاعات فرزندان مستقیم (جزئیات کامل)
// فرزند چپ
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; }
آمار کامل شبکه
public int TotalLeftLegMembers { get; set; } // کل اعضای شاخه چپ (همه سطوح)
public int TotalRightLegMembers { get; set; } // کل اعضای شاخه راست (همه سطوح)
public int TotalNetworkSize { get; set; } // کل اعضای شبکه
public int MaxNetworkDepth { get; set; } // حداکثر عمق شبکه
اطلاعات پکیج و دایا
public bool HasReceivedDayaCredit { get; set; }
public DateTime? DayaCreditReceivedAt { get; set; }
public PackagePurchaseMethod PackagePurchaseMethod { get; set; }
public bool HasPurchasedGoldenPackage { get; set; }
آمار مالی (کمیسیون)
public decimal TotalEarnedCommission { get; set; } // کل کمیسیون کسب شده
public decimal TotalPaidCommission { get; set; } // کمیسیون پرداخت شده
public decimal PendingCommission { get; set; } // کمیسیون در انتظار
public int TotalBalancesEarned { get; set; } // تعداد بالانسهای کسب شده
آمار فعالیت
public int ActiveMembersInNetwork { get; set; } // اعضای فعال (پکیج خریده)
public int InactiveMembersInNetwork { get; set; } // اعضای غیرفعال
2.2. آپدیت GetUserNetworkPositionQueryHandler
فایل: /CMS/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/GetUserNetworkPositionQueryHandler.cs
متدهای کمکی جدید:
/// <summary>
/// محاسبه تعداد اعضای یک شاخه (چپ یا راست) به صورت بازگشتی
/// </summary>
private async Task<int> GetLegMemberCountAsync(long userId, NetworkLeg leg, CancellationToken cancellationToken)
/// <summary>
/// محاسبه حداکثر عمق شبکه
/// </summary>
private async Task<int> GetMaxNetworkDepthAsync(long userId, CancellationToken cancellationToken)
/// <summary>
/// دریافت تمام ID های زیرمجموعه یک کاربر
/// </summary>
private async Task<List<long>> 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 فیلد
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 برای تمام فیلدهای جدید
config.NewConfig<UserNetworkPositionDto, GetUserNetworkResponse>()
.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
config.NewConfig<GetUserNetworkInfoResponseDto, GetUserNetworkResponse>()
.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 آیتم با آیکون)
<MudGrid>
<MudItem xs="12" sm="6" md="3">
<!-- کل اعضای شبکه -->
<MudIcon Icon="@Icons.Material.Filled.AccountTree" />
<MudText Typo="Typo.h4">@_userInfo.TotalNetworkSize</MudText>
</MudItem>
<!-- اعضای شاخه چپ -->
<!-- اعضای شاخه راست -->
<!-- حداکثر عمق شبکه -->
<!-- اعضای فعال -->
<!-- اعضای غیرفعال -->
</MudGrid>
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 (پنجشنبه):
- اولین روز سال: 2025-01-01 = چهارشنبه
- اولین شنبه سال: 2025-01-04
- شنبه این هفته: 2025-12-07
- فاصله: 337 روز
- شماره هفته: 337 ÷ 7 = 48.14 → هفته 49 ✅
3.3. آپدیت GetAvailableWeeksQueryHandler
فایل: /CMS/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAvailableWeeks/GetAvailableWeeksQueryHandler.cs
قبل:
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}";
}
بعد:
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:
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 ✅
خلاصه فایلهای تغییر یافته
فایلهای جدید
/BackOffice/src/BackOffice/Services/PersianDateTimeService.cs⭐ جدید
فایلهای CMS
/CMS/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/UserNetworkPositionDto.cs/CMS/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetUserNetworkPosition/GetUserNetworkPositionQueryHandler.cs/CMS/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto/CMS/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs/CMS/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAvailableWeeks/GetAvailableWeeksQueryHandler.cs/CMS/src/CMSMicroservice.Application/CommissionCQ/Commands/CalculateWeeklyBalances/CalculateWeeklyBalancesCommandHandler.cs
فایلهای BackOffice.BFF
/BackOffice.BFF/src/Protobufs/BackOffice.BFF.NetworkMembership.Protobuf/Protos/networkmembership.proto/BackOffice.BFF/src/BackOffice.BFF.Application/NetworkMembershipCQ/Queries/GetUserNetworkInfo/GetUserNetworkInfoResponseDto.cs/BackOffice.BFF/src/BackOffice.BFF.WebApi/Common/Mappings/NetworkMembershipProfile.cs
فایلهای BackOffice (Frontend)
/BackOffice/src/BackOffice/ConfigureService.cs/BackOffice/src/BackOffice/Pages/Commission/Dashboard.razor/BackOffice/src/BackOffice/Pages/Commission/Dashboard.razor.cs/BackOffice/src/BackOffice/Pages/Commission/UserPayouts.razor/BackOffice/src/BackOffice/Pages/Commission/UserPayouts.razor.cs/BackOffice/src/BackOffice/Pages/SystemManagement/WorkerControl.razor/BackOffice/src/BackOffice/Pages/Network/UserNetworkInfo.razor
فایلهای SQL
/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
توصیههای آینده
کارهای تکمیلی پیشنهادی
-
Component Reusability: ایجاد Blazor Components مشترک برای نمایش تاریخ شمسی
<PersianDateDisplay DateTime="@dateTime" ShowTime="true" /> <PersianWeekDisplay WeekNumber="@weekNumber" ShowRange="true" /> -
Caching: اضافه کردن Cache برای محاسبات تبدیل هفته (اگر Performance مشکل شد)
-
Testing: نوشتن Unit Test برای
GetWeekNumberدر C# و SQL -
Documentation: اضافه کردن XML Comments بیشتر برای API Documentation
-
صفحات باقیمانده: اگر صفحات دیگری تاریخ نمایش میدهند، آنها را هم تبدیل کنید
نکات فنی مهم
Saturday-based Week Calculation
هفته از شنبه شروع میشود:
- شنبه: روز اول هفته
- جمعه: روز آخر هفته
- Week 1: اولین شنبه سال
DateTime to Timestamp Conversion
// در Protobuf mapping همیشه UTC specify کنید
Timestamp.FromDateTime(DateTime.SpecifyKind(dateTime, DateTimeKind.Utc))
Persian Calendar in C#
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