feat: add club membership filters and activation details to network tree
All checks were successful
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 2m13s
All checks were successful
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 2m13s
This commit is contained in:
@@ -14,4 +14,14 @@ public record GetNetworkTreeQuery : IRequest<NetworkTreeDto?>
|
|||||||
/// تعداد سطوح (Depth) که میخواهیم نمایش دهیم (پیشفرض: 3)
|
/// تعداد سطوح (Depth) که میخواهیم نمایش دهیم (پیشفرض: 3)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxDepth { get; init; } = 3;
|
public int MaxDepth { get; init; } = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر براساس وضعیت فعال بودن باشگاه مشتریان
|
||||||
|
/// </summary>
|
||||||
|
public bool? IsClubActive { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر براساس شماره هفته فعالسازی (مثال: 2025-W05)
|
||||||
|
/// </summary>
|
||||||
|
public string? ActivationWeekNumber { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tree = await BuildTree(rootUser.Id, request.MaxDepth, 0, cancellationToken);
|
var tree = await BuildTree(rootUser.Id, request.MaxDepth, 0, cancellationToken, request);
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<NetworkTreeDto> BuildTree(long userId, int maxDepth, int currentDepth, CancellationToken cancellationToken)
|
private async Task<NetworkTreeDto> BuildTree(long userId, int maxDepth, int currentDepth, CancellationToken cancellationToken, GetNetworkTreeQuery request)
|
||||||
{
|
{
|
||||||
var user = await _context.Users
|
var user = await _context.Users
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -35,6 +35,25 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
|||||||
throw new NotFoundException(nameof(User), userId);
|
throw new NotFoundException(nameof(User), userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// دریافت اطلاعات باشگاه مشتریان
|
||||||
|
var clubMembership = await _context.ClubMemberships
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(x => x.UserId == userId && x.IsDeleted == false, cancellationToken);
|
||||||
|
|
||||||
|
// محاسبه شماره هفته فعالسازی
|
||||||
|
string? activationWeekNumber = null;
|
||||||
|
if (clubMembership?.ActivatedAt != null)
|
||||||
|
{
|
||||||
|
var activatedAt = clubMembership.ActivatedAt.Value;
|
||||||
|
var year = activatedAt.Year;
|
||||||
|
var jan1 = new DateTime(year, 1, 1);
|
||||||
|
var daysToFirstSaturday = (7 - (int)jan1.DayOfWeek + 7) % 7;
|
||||||
|
if (jan1.DayOfWeek == DayOfWeek.Saturday) daysToFirstSaturday = 0;
|
||||||
|
var firstSaturday = jan1.AddDays(daysToFirstSaturday);
|
||||||
|
var weekNum = activatedAt < firstSaturday ? 1 : ((activatedAt - firstSaturday).Days / 7) + 1;
|
||||||
|
activationWeekNumber = $"{year}-W{weekNum:D2}";
|
||||||
|
}
|
||||||
|
|
||||||
var node = new NetworkTreeDto
|
var node = new NetworkTreeDto
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
@@ -42,7 +61,11 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
|||||||
FirstName = user.FirstName,
|
FirstName = user.FirstName,
|
||||||
LastName = user.LastName,
|
LastName = user.LastName,
|
||||||
LegPosition = user.LegPosition,
|
LegPosition = user.LegPosition,
|
||||||
CurrentDepth = currentDepth
|
CurrentDepth = currentDepth,
|
||||||
|
ClubActivatedAt = clubMembership?.ActivatedAt,
|
||||||
|
IsClubActive = clubMembership?.IsActive ?? false,
|
||||||
|
ActivationWeekNumber = activationWeekNumber,
|
||||||
|
UserCreated = user.Created
|
||||||
};
|
};
|
||||||
|
|
||||||
// اگر به حداکثر عمق رسیدیم، دیگر فرزندان را نمیخوانیم
|
// اگر به حداکثر عمق رسیدیم، دیگر فرزندان را نمیخوانیم
|
||||||
@@ -52,27 +75,138 @@ public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, N
|
|||||||
}
|
}
|
||||||
|
|
||||||
// پیدا کردن فرزند چپ
|
// پیدا کردن فرزند چپ
|
||||||
var leftChild = await _context.Users
|
var leftChildQuery = _context.Users
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left,
|
.Where(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left);
|
||||||
cancellationToken);
|
|
||||||
|
// اعمال فیلتر IsClubActive
|
||||||
|
if (request.IsClubActive.HasValue)
|
||||||
|
{
|
||||||
|
var activeUserIds = await _context.ClubMemberships
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(cm => cm.IsActive == request.IsClubActive.Value && cm.IsDeleted == false)
|
||||||
|
.Select(cm => cm.UserId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
leftChildQuery = leftChildQuery.Where(u => activeUserIds.Contains(u.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// اعمال فیلتر ActivationWeekNumber
|
||||||
|
if (!string.IsNullOrEmpty(request.ActivationWeekNumber))
|
||||||
|
{
|
||||||
|
var (startDate, endDate) = ParseWeekNumber(request.ActivationWeekNumber);
|
||||||
|
if (startDate.HasValue && endDate.HasValue)
|
||||||
|
{
|
||||||
|
var weekUserIds = await _context.ClubMemberships
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(cm => cm.IsDeleted == false
|
||||||
|
&& cm.ActivatedAt != null
|
||||||
|
&& cm.ActivatedAt >= startDate.Value
|
||||||
|
&& cm.ActivatedAt < endDate.Value)
|
||||||
|
.Select(cm => cm.UserId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
leftChildQuery = leftChildQuery.Where(u => weekUserIds.Contains(u.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftChild = await leftChildQuery.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
if (leftChild != null)
|
if (leftChild != null)
|
||||||
{
|
{
|
||||||
node.LeftChild = await BuildTree(leftChild.Id, maxDepth, currentDepth + 1, cancellationToken);
|
node.LeftChild = await BuildTree(leftChild.Id, maxDepth, currentDepth + 1, cancellationToken, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
// پیدا کردن فرزند راست
|
// پیدا کردن فرزند راست
|
||||||
var rightChild = await _context.Users
|
var rightChildQuery = _context.Users
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right,
|
.Where(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right);
|
||||||
cancellationToken);
|
|
||||||
|
// اعمال فیلتر IsClubActive
|
||||||
|
if (request.IsClubActive.HasValue)
|
||||||
|
{
|
||||||
|
var activeUserIds = await _context.ClubMemberships
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(cm => cm.IsActive == request.IsClubActive.Value && cm.IsDeleted == false)
|
||||||
|
.Select(cm => cm.UserId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
rightChildQuery = rightChildQuery.Where(u => activeUserIds.Contains(u.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// اعمال فیلتر ActivationWeekNumber
|
||||||
|
if (!string.IsNullOrEmpty(request.ActivationWeekNumber))
|
||||||
|
{
|
||||||
|
var (startDate, endDate) = ParseWeekNumber(request.ActivationWeekNumber);
|
||||||
|
if (startDate.HasValue && endDate.HasValue)
|
||||||
|
{
|
||||||
|
var weekUserIds = await _context.ClubMemberships
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(cm => cm.IsDeleted == false
|
||||||
|
&& cm.ActivatedAt != null
|
||||||
|
&& cm.ActivatedAt >= startDate.Value
|
||||||
|
&& cm.ActivatedAt < endDate.Value)
|
||||||
|
.Select(cm => cm.UserId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
rightChildQuery = rightChildQuery.Where(u => weekUserIds.Contains(u.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rightChild = await rightChildQuery.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
if (rightChild != null)
|
if (rightChild != null)
|
||||||
{
|
{
|
||||||
node.RightChild = await BuildTree(rightChild.Id, maxDepth, currentDepth + 1, cancellationToken);
|
node.RightChild = await BuildTree(rightChild.Id, maxDepth, currentDepth + 1, cancellationToken, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تبدیل شماره هفته (مثلاً 2025-W05) به تاریخ شروع و پایان هفته
|
||||||
|
/// </summary>
|
||||||
|
private static (DateTime? StartDate, DateTime? EndDate) ParseWeekNumber(string weekNumber)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// فرمت: YYYY-W##
|
||||||
|
var parts = weekNumber.Split('-');
|
||||||
|
if (parts.Length != 2 || !parts[1].StartsWith("W"))
|
||||||
|
return (null, null);
|
||||||
|
|
||||||
|
if (!int.TryParse(parts[0], out var year))
|
||||||
|
return (null, null);
|
||||||
|
|
||||||
|
if (!int.TryParse(parts[1].Substring(1), out var weekNum))
|
||||||
|
return (null, null);
|
||||||
|
|
||||||
|
// محاسبه اولین شنبه سال
|
||||||
|
var jan1 = new DateTime(year, 1, 1);
|
||||||
|
var daysToFirstSaturday = (7 - (int)jan1.DayOfWeek + 7) % 7;
|
||||||
|
if (jan1.DayOfWeek == DayOfWeek.Saturday)
|
||||||
|
daysToFirstSaturday = 0;
|
||||||
|
|
||||||
|
var firstSaturday = jan1.AddDays(daysToFirstSaturday);
|
||||||
|
|
||||||
|
// محاسبه تاریخ شروع هفته مورد نظر
|
||||||
|
DateTime startDate;
|
||||||
|
if (weekNum == 1)
|
||||||
|
{
|
||||||
|
startDate = firstSaturday;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startDate = firstSaturday.AddDays((weekNum - 1) * 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
var endDate = startDate.AddDays(7);
|
||||||
|
|
||||||
|
return (startDate, endDate);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,27 @@ public class NetworkTreeDto
|
|||||||
public string? LastName { get; set; }
|
public string? LastName { get; set; }
|
||||||
public NetworkLeg? LegPosition { get; set; }
|
public NetworkLeg? LegPosition { get; set; }
|
||||||
public int CurrentDepth { get; set; }
|
public int CurrentDepth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تاریخ فعالسازی در باشگاه مشتریان
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? ClubActivatedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// وضعیت فعال بودن در باشگاه مشتریان
|
||||||
|
/// </summary>
|
||||||
|
public bool IsClubActive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شماره هفته فعالسازی
|
||||||
|
/// </summary>
|
||||||
|
public string? ActivationWeekNumber { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تاریخ ایجاد کاربر
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset UserCreated { get; set; }
|
||||||
|
|
||||||
public NetworkTreeDto? LeftChild { get; set; }
|
public NetworkTreeDto? LeftChild { get; set; }
|
||||||
public NetworkTreeDto? RightChild { get; set; }
|
public NetworkTreeDto? RightChild { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.0.152</Version>
|
<Version>0.0.154</Version>
|
||||||
<DebugType>None</DebugType>
|
<DebugType>None</DebugType>
|
||||||
<DebugSymbols>False</DebugSymbols>
|
<DebugSymbols>False</DebugSymbols>
|
||||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ message GetNetworkTreeRequest
|
|||||||
int64 user_id = 1;
|
int64 user_id = 1;
|
||||||
google.protobuf.Int32Value max_depth = 2;
|
google.protobuf.Int32Value max_depth = 2;
|
||||||
google.protobuf.BoolValue only_active = 3;
|
google.protobuf.BoolValue only_active = 3;
|
||||||
|
google.protobuf.BoolValue is_club_active = 4;
|
||||||
|
google.protobuf.StringValue activation_week_number = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetNetworkTreeResponse
|
message GetNetworkTreeResponse
|
||||||
@@ -171,6 +173,10 @@ message NetworkTreeNodeModel
|
|||||||
int32 network_level = 5;
|
int32 network_level = 5;
|
||||||
bool is_active = 6;
|
bool is_active = 6;
|
||||||
google.protobuf.Timestamp joined_at = 7;
|
google.protobuf.Timestamp joined_at = 7;
|
||||||
|
google.protobuf.Timestamp club_activated_at = 8; // تاریخ فعالسازی در باشگاه
|
||||||
|
bool is_club_active = 9; // فعال بودن در باشگاه
|
||||||
|
string activation_week_number = 10; // شماره هفته فعالسازی
|
||||||
|
google.protobuf.Timestamp user_created = 11; // تاریخ ایجاد کاربر
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHistory Query
|
// GetHistory Query
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ public class NetworkMembershipProfile : IRegister
|
|||||||
// Request mapping
|
// Request mapping
|
||||||
config.NewConfig<GetNetworkTreeRequest, GetNetworkTreeQuery>()
|
config.NewConfig<GetNetworkTreeRequest, GetNetworkTreeQuery>()
|
||||||
.Map(dest => dest.UserId, src => src.UserId)
|
.Map(dest => dest.UserId, src => src.UserId)
|
||||||
.Map(dest => dest.MaxDepth, src => src.MaxDepth != null && src.MaxDepth.Value > 0 ? src.MaxDepth.Value : 3);
|
.Map(dest => dest.MaxDepth, src => src.MaxDepth != null && src.MaxDepth.Value > 0 ? src.MaxDepth.Value : 3)
|
||||||
|
.Map(dest => dest.IsClubActive, src => src.IsClubActive)
|
||||||
|
.Map(dest => dest.ActivationWeekNumber, src => src.ActivationWeekNumber);
|
||||||
|
|
||||||
// Response mapping: تبدیل درخت به لیست مسطح
|
// Response mapping: تبدیل درخت به لیست مسطح
|
||||||
config.NewConfig<NetworkTreeDto, GetNetworkTreeResponse>()
|
config.NewConfig<NetworkTreeDto, GetNetworkTreeResponse>()
|
||||||
@@ -81,7 +83,9 @@ public class NetworkMembershipProfile : IRegister
|
|||||||
UserName = $"{node.FirstName} {node.LastName}".Trim(),
|
UserName = $"{node.FirstName} {node.LastName}".Trim(),
|
||||||
NetworkLeg = (int)(node.LegPosition ?? NetworkLeg.Left),
|
NetworkLeg = (int)(node.LegPosition ?? NetworkLeg.Left),
|
||||||
NetworkLevel = node.CurrentDepth,
|
NetworkLevel = node.CurrentDepth,
|
||||||
IsActive = true
|
IsActive = true,
|
||||||
|
IsClubActive = node.IsClubActive,
|
||||||
|
ActivationWeekNumber = node.ActivationWeekNumber ?? string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
if (parentId.HasValue)
|
if (parentId.HasValue)
|
||||||
@@ -89,6 +93,16 @@ public class NetworkMembershipProfile : IRegister
|
|||||||
protoNode.ParentId = parentId.Value;
|
protoNode.ParentId = parentId.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// تبدیل تاریخ فعالسازی باشگاه
|
||||||
|
if (node.ClubActivatedAt.HasValue)
|
||||||
|
{
|
||||||
|
protoNode.ClubActivatedAt = Timestamp.FromDateTime(
|
||||||
|
DateTime.SpecifyKind(node.ClubActivatedAt.Value, DateTimeKind.Utc));
|
||||||
|
}
|
||||||
|
|
||||||
|
// تبدیل تاریخ ایجاد کاربر
|
||||||
|
protoNode.UserCreated = Timestamp.FromDateTimeOffset(node.UserCreated);
|
||||||
|
|
||||||
nodesList.Add(protoNode);
|
nodesList.Add(protoNode);
|
||||||
|
|
||||||
// بازگشتی برای فرزندان
|
// بازگشتی برای فرزندان
|
||||||
|
|||||||
Reference in New Issue
Block a user