update
This commit is contained in:
@@ -0,0 +1,642 @@
|
||||
# Network Tree - Activation Week Feature
|
||||
|
||||
## نمای کلی (Overview)
|
||||
|
||||
این سند تغییرات مربوط به افزودن قابلیت فیلتر و نمایش هفته فعالسازی در درخت شبکه را توضیح میدهد.
|
||||
|
||||
**تاریخ پیادهسازی:** دسامبر 2025
|
||||
|
||||
**تغییرات کلیدی:**
|
||||
- اضافه شدن فیلد `IsActivatedInTargetWeek` برای flagging (به جای filtering)
|
||||
- حذف فیلتر سمت Backend و انتقال به UI
|
||||
- نمایش بصری وضعیت فعالسازی در درخت
|
||||
|
||||
---
|
||||
|
||||
## منطق کسبوکار (Business Logic)
|
||||
|
||||
### رویکرد قبلی (❌ Removed)
|
||||
- فیلتر میکرد و فقط نودهایی که در هفته هدف فعال شدهاند نمایش داده میشدند
|
||||
- مشکل: کاربران نمیتوانستند کل ساختار شبکه را ببینند
|
||||
|
||||
### رویکرد جدید (✅ Current)
|
||||
- **همه نودها نمایش داده میشوند** (بدون فیلتر در دیتابیس)
|
||||
- هر نود یک flag دارد: `IsActivatedInTargetWeek`
|
||||
- UI از این flag برای نمایش بصری استفاده میکند
|
||||
|
||||
### محاسبه هفته فعالسازی
|
||||
|
||||
```csharp
|
||||
private static int CalculateWeekNumber(DateTimeOffset date)
|
||||
{
|
||||
var persianCalendar = new PersianCalendar();
|
||||
int year = persianCalendar.GetYear(date.DateTime);
|
||||
int dayOfYear = persianCalendar.GetDayOfYear(date.DateTime);
|
||||
int weekNumber = (dayOfYear - 1) / 7 + 1;
|
||||
|
||||
return int.Parse($"{year}{weekNumber:D2}");
|
||||
// مثال: 140352 = سال 1403، هفته 52
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## تغییرات Backend
|
||||
|
||||
### 1. DTO Changes
|
||||
|
||||
**فایل:** `CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/NetworkTreeDto.cs`
|
||||
|
||||
```csharp
|
||||
public class NetworkTreeDto
|
||||
{
|
||||
// ... existing fields
|
||||
public string? ActivationWeekNumber { get; set; }
|
||||
public bool IsActivatedInTargetWeek { get; set; } // ✅ NEW
|
||||
public DateTimeOffset UserCreated { get; set; }
|
||||
public NetworkTreeDto? LeftChild { get; set; }
|
||||
public NetworkTreeDto? RightChild { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Query Handler Changes
|
||||
|
||||
**فایل:** `CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs`
|
||||
|
||||
#### تغییر در BuildTree Method
|
||||
|
||||
```csharp
|
||||
private NetworkTreeDto BuildTree(
|
||||
User user,
|
||||
int currentDepth,
|
||||
int maxDepth,
|
||||
string? requestActivationWeekNumber) // ✅ پارامتر اضافه شد
|
||||
{
|
||||
// محاسبه هفته فعالسازی
|
||||
string? activationWeekNumber = null;
|
||||
bool isActivatedInTargetWeek = false;
|
||||
|
||||
if (user.ClubMembership?.ActivatedAt != null)
|
||||
{
|
||||
activationWeekNumber = CalculateWeekNumber(user.ClubMembership.ActivatedAt.Value)
|
||||
.ToString();
|
||||
|
||||
// چک کردن اینکه آیا در هفته هدف فعال شده
|
||||
if (!string.IsNullOrEmpty(requestActivationWeekNumber))
|
||||
{
|
||||
isActivatedInTargetWeek = activationWeekNumber == requestActivationWeekNumber;
|
||||
}
|
||||
}
|
||||
|
||||
var node = new NetworkTreeDto
|
||||
{
|
||||
// ... existing fields
|
||||
ActivationWeekNumber = activationWeekNumber,
|
||||
IsActivatedInTargetWeek = isActivatedInTargetWeek, // ✅ تنظیم flag
|
||||
};
|
||||
|
||||
// ... recursive calls
|
||||
}
|
||||
```
|
||||
|
||||
#### حذف فیلتر از GetFilteredChildren
|
||||
|
||||
**قبل (❌):**
|
||||
```csharp
|
||||
private IEnumerable<User> GetFilteredChildren(
|
||||
IEnumerable<User> children,
|
||||
bool? isClubActive,
|
||||
string? activationWeekNumber)
|
||||
{
|
||||
var query = children.AsQueryable();
|
||||
|
||||
if (isClubActive.HasValue)
|
||||
{
|
||||
query = query.Where(u => u.ClubMembership != null &&
|
||||
u.ClubMembership.IsActive == isClubActive.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(activationWeekNumber))
|
||||
{
|
||||
// ❌ فیلتر میکرد
|
||||
query = query.Where(u => /* filter logic */);
|
||||
}
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
**بعد (✅):**
|
||||
```csharp
|
||||
private IEnumerable<User> GetFilteredChildren(
|
||||
IEnumerable<User> children,
|
||||
bool? isClubActive)
|
||||
{
|
||||
var query = children.AsQueryable();
|
||||
|
||||
// فقط فیلتر IsClubActive باقی ماند
|
||||
if (isClubActive.HasValue)
|
||||
{
|
||||
query = query.Where(u => u.ClubMembership != null &&
|
||||
u.ClubMembership.IsActive == isClubActive.Value);
|
||||
}
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Proto Definition
|
||||
|
||||
**فایل:** `CMSMicroservice.Protobuf/Protos/networkmembership.proto`
|
||||
|
||||
```protobuf
|
||||
message NetworkTreeNodeModel {
|
||||
int64 user_id = 1;
|
||||
string user_name = 2;
|
||||
optional int64 parent_id = 3;
|
||||
optional int32 network_leg = 4;
|
||||
optional int32 network_level = 5;
|
||||
optional bool is_active = 6;
|
||||
optional google.protobuf.Timestamp joined_at = 7;
|
||||
optional google.protobuf.Timestamp club_activated_at = 8;
|
||||
bool is_club_active = 9;
|
||||
string activation_week_number = 10;
|
||||
bool is_activated_in_target_week = 11; // ✅ NEW
|
||||
google.protobuf.Timestamp user_created = 12;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Mapping
|
||||
|
||||
**فایل:** `CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs`
|
||||
|
||||
```csharp
|
||||
var protoNode = new NetworkTreeNodeModel
|
||||
{
|
||||
UserId = node.UserId,
|
||||
UserName = node.UserName,
|
||||
ParentId = node.ParentId,
|
||||
NetworkLeg = node.NetworkLeg,
|
||||
NetworkLevel = node.NetworkLevel,
|
||||
IsActive = node.IsActive,
|
||||
JoinedAt = node.JoinedAt.HasValue
|
||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(node.JoinedAt.Value, DateTimeKind.Utc))
|
||||
: null,
|
||||
ClubActivatedAt = node.ClubActivatedAt.HasValue
|
||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(node.ClubActivatedAt.Value, DateTimeKind.Utc))
|
||||
: null,
|
||||
IsClubActive = node.IsClubActive,
|
||||
ActivationWeekNumber = node.ActivationWeekNumber ?? string.Empty,
|
||||
IsActivatedInTargetWeek = node.IsActivatedInTargetWeek, // ✅ NEW
|
||||
UserCreated = Timestamp.FromDateTime(DateTime.SpecifyKind(node.UserCreated, DateTimeKind.Utc))
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## تغییرات BFF
|
||||
|
||||
### Proto & Mapping
|
||||
|
||||
همان تغییرات در CMS در BFF هم اعمال شد:
|
||||
|
||||
**فایلها:**
|
||||
- `BackOffice.BFF.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeResponseDto.cs`
|
||||
- `BackOffice.BFF.WebApi/Common/Mappings/NetworkMembershipProfile.cs`
|
||||
- `Protobufs/networkmembership.proto`
|
||||
|
||||
```csharp
|
||||
public class NetworkTreeNodeDto
|
||||
{
|
||||
// ... existing properties
|
||||
public bool IsActivatedInTargetWeek { get; set; } // ✅ NEW
|
||||
public string ActivationWeekNumber { get; set; } = string.Empty;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## تغییرات Frontend
|
||||
|
||||
### 1. Razor Component
|
||||
|
||||
**فایل:** `BackOffice/Pages/Network/NetworkTreeViewer.razor`
|
||||
|
||||
#### تغییر در ستون "وضعیت"
|
||||
|
||||
**قبل (❌):**
|
||||
```razor
|
||||
<PropertyColumn Property="x => x.IsActive" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
@if (context.Item.IsActive!=null) {
|
||||
<MudChip Color="@((bool)context.Item.IsActive ? Color.Success : Color.Error)">
|
||||
@((bool)context.Item.IsActive ? "فعال" : "غیرفعال")
|
||||
</MudChip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
```
|
||||
|
||||
**بعد (✅):**
|
||||
```razor
|
||||
<PropertyColumn Property="x => x.IsClubActive" Title="وضعیت">
|
||||
<CellTemplate>
|
||||
<MudChip T="string"
|
||||
Color="@(context.Item.IsClubActive ? Color.Success : Color.Error)"
|
||||
Size="Size.Small">
|
||||
@(context.Item.IsClubActive ? "فعال" : "غیرفعال")
|
||||
</MudChip>
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
```
|
||||
|
||||
#### ارسال داده به JavaScript
|
||||
|
||||
```csharp
|
||||
private async Task RenderTree()
|
||||
{
|
||||
if (_treeData == null || !_treeData.Nodes.Any()) return;
|
||||
|
||||
var jsNodes = _treeData.Nodes.Select(n => new
|
||||
{
|
||||
userId = n.UserId,
|
||||
userName = n.UserName,
|
||||
parentId = n.ParentId,
|
||||
networkLevel = n.NetworkLevel,
|
||||
networkLeg = n.NetworkLeg,
|
||||
isActive = n.IsClubActive, // ✅ تغییر به IsClubActive
|
||||
isClubActive = n.IsClubActive,
|
||||
isActivatedInTargetWeek = n.IsActivatedInTargetWeek, // ✅ NEW
|
||||
activationWeekNumber = _activationWeekFilter ?? "", // ✅ فیلتر UI
|
||||
clubActivatedAt = n.ClubActivatedAt?.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd") ?? "",
|
||||
userCreated = n.UserCreated?.ToDateTime().ToLocalTime().ToString("yyyy/MM/dd") ?? ""
|
||||
}).ToArray();
|
||||
|
||||
await JS.InvokeVoidAsync("NetworkTreeViewer.initialize", "network-tree-container", jsNodes);
|
||||
}
|
||||
```
|
||||
|
||||
**نکته مهم:** `activationWeekNumber` از فیلتر UI گرفته میشود (`_activationWeekFilter`) نه از Backend.
|
||||
|
||||
### 2. JavaScript Visualization
|
||||
|
||||
**فایل:** `BackOffice/wwwroot/js/network-tree.js`
|
||||
|
||||
#### منطق رنگ نود (دایره)
|
||||
|
||||
```javascript
|
||||
node.append('circle')
|
||||
.attr('r', 8)
|
||||
.style('fill', d => {
|
||||
// اگر هفتهای انتخاب نشده، همه سبز
|
||||
if (!d.data.activationWeekNumber || d.data.activationWeekNumber === '') {
|
||||
return '#4caf50';
|
||||
}
|
||||
// اگر در هفته هدف فعال شده، سبز، وگرنه قرمز
|
||||
return d.data.isActivatedInTargetWeek ? '#4caf50' : '#f44336';
|
||||
})
|
||||
.style('stroke', '#fff')
|
||||
.style('stroke-width', 2)
|
||||
.style('cursor', 'pointer');
|
||||
```
|
||||
|
||||
#### منطق رنگ تایتل (نام کاربر)
|
||||
|
||||
```javascript
|
||||
node.append('text')
|
||||
.attr('dy', -15)
|
||||
.attr('text-anchor', 'middle')
|
||||
.style('font-size', '12px')
|
||||
.style('font-weight', 'bold')
|
||||
.style('fill', d => d.data.isClubActive ? '#424242' : '#9e9e9e')
|
||||
.text(d => d.data.userName || `User ${d.data.userId}`);
|
||||
```
|
||||
|
||||
#### اضافه کردن فیلدها به buildHierarchy
|
||||
|
||||
```javascript
|
||||
buildHierarchy: function(nodes) {
|
||||
// ...
|
||||
const nodeMap = new Map();
|
||||
nodes.forEach(node => {
|
||||
nodeMap.set(node.userId, {
|
||||
userId: node.userId,
|
||||
userName: node.userName,
|
||||
parentId: node.parentId,
|
||||
level: node.networkLevel,
|
||||
networkLeg: node.networkLeg,
|
||||
isActive: node.isActive,
|
||||
isClubActive: node.isClubActive, // ✅ NEW
|
||||
isActivatedInTargetWeek: node.isActivatedInTargetWeek, // ✅ NEW
|
||||
activationWeekNumber: node.activationWeekNumber, // ✅ NEW
|
||||
clubActivatedAt: node.clubActivatedAt,
|
||||
userCreated: node.userCreated,
|
||||
children: []
|
||||
});
|
||||
});
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Legend (راهنمای رنگها)
|
||||
|
||||
```javascript
|
||||
// Legend for title colors (club status)
|
||||
legend.append('text')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.style('font-size', '12px')
|
||||
.style('font-weight', 'bold')
|
||||
.style('fill', '#424242')
|
||||
.text('باشگاه فعال');
|
||||
|
||||
legend.append('text')
|
||||
.attr('x', 0)
|
||||
.attr('y', 20)
|
||||
.style('font-size', '12px')
|
||||
.style('font-weight', 'bold')
|
||||
.style('fill', '#9e9e9e')
|
||||
.text('باشگاه غیرفعال');
|
||||
|
||||
// Legend for circles (week status)
|
||||
legend.append('circle')
|
||||
.attr('cx', 0)
|
||||
.attr('cy', 50)
|
||||
.attr('r', 6)
|
||||
.style('fill', '#4caf50');
|
||||
|
||||
legend.append('text')
|
||||
.attr('x', 12)
|
||||
.attr('y', 54)
|
||||
.style('font-size', '12px')
|
||||
.text('فعال در هفته هدف');
|
||||
|
||||
legend.append('circle')
|
||||
.attr('cx', 0)
|
||||
.attr('cy', 75)
|
||||
.attr('r', 6)
|
||||
.style('fill', '#f44336');
|
||||
|
||||
legend.append('text')
|
||||
.attr('x', 12)
|
||||
.attr('y', 79)
|
||||
.style('font-size', '12px')
|
||||
.text('خارج از هفته هدف');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## رفتار UI
|
||||
|
||||
### حالت 1: بدون فیلتر هفته
|
||||
|
||||
**وضعیت:** `_activationWeekFilter` خالی است
|
||||
|
||||
**رفتار:**
|
||||
- **دایرهها:** همه سبز (#4caf50)
|
||||
- **تایتل:** مشکی (#424242) برای باشگاه فعال، خاکستری (#9e9e9e) برای باشگاه غیرفعال
|
||||
|
||||
### حالت 2: با فیلتر هفته
|
||||
|
||||
**وضعیت:** مثلاً `_activationWeekFilter = "140352"`
|
||||
|
||||
**رفتار:**
|
||||
- **دایرهها:**
|
||||
- سبز (#4caf50) → کاربران فعال شده در هفته 52 سال 1403
|
||||
- قرمز (#f44336) → کاربران فعال شده در هفتههای دیگر
|
||||
- **تایتل:** همچنان بر اساس `isClubActive`
|
||||
|
||||
### حالت 3: فیلتر IsClubActive
|
||||
|
||||
این فیلتر در سمت Backend اعمال میشود و نودهای غیرفعال را حذف میکند.
|
||||
|
||||
---
|
||||
|
||||
## Flow Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ User Interface │
|
||||
│ ┌────────────────┐ ┌──────────────────┐ │
|
||||
│ │ IsClubActive │ │ActivationWeek │ │
|
||||
│ │ Filter │ │ Filter │ │
|
||||
│ └────────┬───────┘ └────────┬─────────┘ │
|
||||
└───────────┼──────────────────┼────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Backend (CMS) │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ GetNetworkTreeQueryHandler │ │
|
||||
│ │ │ │
|
||||
│ │ 1. GetFilteredChildren (IsClubActive filter only) │ │
|
||||
│ │ 2. BuildTree (calculate IsActivatedInTargetWeek) │ │
|
||||
│ │ 3. Return ALL nodes with flags │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ BFF Layer │
|
||||
│ - Proto mapping │
|
||||
│ - Pass-through to Frontend │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Frontend (Blazor) │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ NetworkTreeViewer.razor │ │
|
||||
│ │ │ │
|
||||
│ │ - Prepare data with UI filter (_activationWeekFilter)│ │
|
||||
│ │ - Send to JavaScript │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ JavaScript (D3.js) │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ network-tree.js │ │
|
||||
│ │ │ │
|
||||
│ │ - Apply visual logic: │ │
|
||||
│ │ * Circle color by activationWeekNumber + flag │ │
|
||||
│ │ * Title color by isClubActive │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Model
|
||||
|
||||
### Request
|
||||
|
||||
```csharp
|
||||
public class GetNetworkTreeRequest
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public int? MaxDepth { get; set; }
|
||||
public bool? IsClubActive { get; set; } // Backend filter
|
||||
public string? ActivationWeekNumber { get; set; } // For flag calculation only
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```csharp
|
||||
public class NetworkTreeDto
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public long? ParentId { get; set; }
|
||||
public int? NetworkLeg { get; set; }
|
||||
public int? NetworkLevel { get; set; }
|
||||
public bool? IsActive { get; set; } // Deprecated
|
||||
public DateTime? JoinedAt { get; set; }
|
||||
public DateTime? ClubActivatedAt { get; set; }
|
||||
public bool IsClubActive { get; set; } // ✅ Use this
|
||||
public string? ActivationWeekNumber { get; set; }
|
||||
public bool IsActivatedInTargetWeek { get; set; } // ✅ NEW
|
||||
public DateTimeOffset UserCreated { get; set; }
|
||||
public NetworkTreeDto? LeftChild { get; set; }
|
||||
public NetworkTreeDto? RightChild { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Scenarios
|
||||
|
||||
### Test 1: بدون فیلتر
|
||||
**Input:**
|
||||
- `IsClubActive`: null
|
||||
- `ActivationWeekNumber`: null
|
||||
|
||||
**Expected:**
|
||||
- همه نودها نمایش داده شوند
|
||||
- همه دایرهها سبز
|
||||
- تایتلها بر اساس IsClubActive
|
||||
|
||||
### Test 2: فیلتر باشگاه فعال
|
||||
**Input:**
|
||||
- `IsClubActive`: true
|
||||
- `ActivationWeekNumber`: null
|
||||
|
||||
**Expected:**
|
||||
- فقط نودهای با باشگاه فعال
|
||||
- همه دایرهها سبز
|
||||
- همه تایتلها مشکی
|
||||
|
||||
### Test 3: فیلتر هفته
|
||||
**Input:**
|
||||
- `IsClubActive`: null
|
||||
- `ActivationWeekNumber`: "140352"
|
||||
|
||||
**Expected:**
|
||||
- همه نودها نمایش داده شوند
|
||||
- دایره سبز: فعال شده در هفته 52
|
||||
- دایره قرمز: فعال شده در هفتههای دیگر
|
||||
- تایتلها بر اساس IsClubActive
|
||||
|
||||
### Test 4: ترکیب فیلترها
|
||||
**Input:**
|
||||
- `IsClubActive`: true
|
||||
- `ActivationWeekNumber`: "140352"
|
||||
|
||||
**Expected:**
|
||||
- فقط نودهای با باشگاه فعال
|
||||
- دایره سبز: فعال شده در هفته 52
|
||||
- دایره قرمز: فعال شده در هفتههای دیگر
|
||||
- همه تایتلها مشکی (چون همه باشگاه فعال دارند)
|
||||
|
||||
---
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Database Query
|
||||
- ✅ فیلتر `ActivationWeekNumber` از Query حذف شد
|
||||
- ✅ فقط فیلتر `IsClubActive` در سمت دیتابیس
|
||||
- ⚠️ ممکن است تعداد نودهای بیشتری بازگردانده شود
|
||||
|
||||
### Memory
|
||||
- Backend همه نودها را میفرستد
|
||||
- Frontend/JavaScript فیلتر بصری اعمال میکند
|
||||
- برای درختهای بسیار بزرگ (>1000 نود) ممکن است نیاز به pagination باشد
|
||||
|
||||
### UI Rendering
|
||||
- D3.js برای درختهای متوسط (<500 نود) عملکرد خوبی دارد
|
||||
- برای بهبود عملکرد میتوان از virtualization استفاده کرد
|
||||
|
||||
---
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### Breaking Changes
|
||||
- ❌ `IsActive` deprecated است → استفاده از `IsClubActive`
|
||||
- ✅ فیلد جدید `IsActivatedInTargetWeek` اضافه شد
|
||||
|
||||
### Backward Compatibility
|
||||
- Proto field numbers حفظ شدهاند
|
||||
- Response structure تغییر نکرده (فقط فیلد جدید اضافه شده)
|
||||
|
||||
### Deployment Steps
|
||||
1. Deploy Backend (CMS) با Proto جدید
|
||||
2. Deploy BFF با Proto جدید
|
||||
3. Deploy Frontend با visualization جدید
|
||||
4. تست تمام scenarios
|
||||
|
||||
---
|
||||
|
||||
## نکات مهم (Key Points)
|
||||
|
||||
### ✅ Do's
|
||||
- از `IsClubActive` برای وضعیت باشگاه استفاده کنید
|
||||
- `IsActivatedInTargetWeek` فقط برای نمایش بصری است
|
||||
- فیلتر UI را از Razor به JS بفرستید (`_activationWeekFilter`)
|
||||
|
||||
### ❌ Don'ts
|
||||
- از `IsActive` استفاده نکنید (deprecated)
|
||||
- `ActivationWeekNumber` را از Backend برای UI filtering استفاده نکنید
|
||||
- فیلتر `ActivationWeekNumber` را در Query اعمال نکنید
|
||||
|
||||
### 💡 Best Practices
|
||||
- همیشه فیلتر UI و Backend flag را sync نگه دارید
|
||||
- برای درختهای بزرگ از lazy loading استفاده کنید
|
||||
- Legend را همیشه با منطق UI sync کنید
|
||||
|
||||
---
|
||||
|
||||
## فایلهای تغییر یافته
|
||||
|
||||
### Backend (CMS)
|
||||
- ✅ `NetworkTreeDto.cs` - اضافه `IsActivatedInTargetWeek`
|
||||
- ✅ `GetNetworkTreeQueryHandler.cs` - محاسبه flag + حذف فیلتر
|
||||
- ✅ `networkmembership.proto` - اضافه field 11
|
||||
- ✅ `NetworkMembershipProfile.cs` - mapping فیلد جدید
|
||||
|
||||
### BFF
|
||||
- ✅ `GetNetworkTreeResponseDto.cs` - اضافه property
|
||||
- ✅ `NetworkMembershipProfile.cs` - mapping
|
||||
- ✅ `networkmembership.proto` - sync با CMS
|
||||
|
||||
### Frontend
|
||||
- ✅ `NetworkTreeViewer.razor` - تغییر `IsActive` → `IsClubActive`
|
||||
- ✅ `NetworkTreeViewer.razor` - اضافه `isActivatedInTargetWeek` به jsNodes
|
||||
- ✅ `network-tree.js` - منطق رنگ نود بر اساس flag
|
||||
- ✅ `network-tree.js` - منطق رنگ تایتل بر اساس `isClubActive`
|
||||
- ✅ `network-tree.js` - Legend جدید
|
||||
|
||||
---
|
||||
|
||||
## مراجع (References)
|
||||
|
||||
- [Binary Tree Guide](../../01-BUSINESS/binary-tree-guide.md)
|
||||
- [Network Commission System](../../01-BUSINESS/network-commission-system.md)
|
||||
- [CMS API Coverage](./api-coverage.md)
|
||||
|
||||
---
|
||||
|
||||
**تاریخ ایجاد:** 14 دسامبر 2025
|
||||
**آخرین بهروزرسانی:** 14 دسامبر 2025
|
||||
**نویسنده:** Development Team
|
||||
Reference in New Issue
Block a user