Files
docs/archive/SESSION-2025-12-12-PERSIAN-DATE-AND-NETWORK-INFO-IMPROVEMENTS.md
T
masoodafar-web 5965b98728 update
2026-01-03 18:27:49 +03:30

664 lines
23 KiB
Markdown

# گزارش تغییرات - 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<BackOffice.Services.IPersianDateTimeService,
BackOffice.Services.PersianDateTimeService>();
```
### 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
<MudText Typo="Typo.body2">هفته @(_currentWeekNumberPersian)</MudText>
@if (_poolData?.CalculatedAt != null)
{
var persianDate = PersianDateTime.ConvertToPersianDateTime(calculatedDate);
@($"در تاریخ {persianDate}")
}
```
#### UserPayouts.razor + UserPayouts.razor.cs
**تغییرات:**
- Inject کردن `IPersianDateTimeService`
- تبدیل شماره هفته در ستون جدول
- تبدیل تاریخ ایجاد Payout
**نمونه کد:**
```razor
<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
- تبدیل شماره هفته و تاریخ در لاگ اجرا
- نمایش پیام تایید با هفته شمسی
**نمونه کد:**
```razor
<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+ فیلد جدید):**
#### اطلاعات شخصی کاربر
```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
/// <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 فیلد
```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<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
```csharp
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 آیتم با آیکون)
```razor
<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 (پنجشنبه):**
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
<PersianDateDisplay DateTime="@dateTime" ShowTime="true" />
<PersianWeekDisplay WeekNumber="@weekNumber" ShowRange="true" />
```
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