From cf75f14003425e400f1874bffe03110583f6968f Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 14 Dec 2025 02:56:04 +0330 Subject: [PATCH] feat: add club membership filters and activation details to network tree --- .../GetNetworkTree/GetNetworkTreeQuery.cs | 10 ++ .../GetNetworkTreeQueryHandler.cs | 156 ++++++++++++++++-- .../Queries/GetNetworkTree/NetworkTreeDto.cs | 21 +++ .../CMSMicroservice.Protobuf.csproj | 2 +- .../Protos/networkmembership.proto | 6 + .../Mappings/NetworkMembershipProfile.cs | 18 +- 6 files changed, 199 insertions(+), 14 deletions(-) diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQuery.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQuery.cs index 1386bb3..4c81777 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQuery.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQuery.cs @@ -14,4 +14,14 @@ public record GetNetworkTreeQuery : IRequest /// تعداد سطوح (Depth) که می‌خواهیم نمایش دهیم (پیش‌فرض: 3) /// public int MaxDepth { get; init; } = 3; + + /// + /// فیلتر براساس وضعیت فعال بودن باشگاه مشتریان + /// + public bool? IsClubActive { get; init; } + + /// + /// فیلتر براساس شماره هفته فعالسازی (مثال: 2025-W05) + /// + public string? ActivationWeekNumber { get; init; } } diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs index 067775c..bdd08e0 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs @@ -20,11 +20,11 @@ public class GetNetworkTreeQueryHandler : IRequestHandler BuildTree(long userId, int maxDepth, int currentDepth, CancellationToken cancellationToken) + private async Task BuildTree(long userId, int maxDepth, int currentDepth, CancellationToken cancellationToken, GetNetworkTreeQuery request) { var user = await _context.Users .AsNoTracking() @@ -35,6 +35,25 @@ public class GetNetworkTreeQueryHandler : IRequestHandler 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 { UserId = user.Id, @@ -42,7 +61,11 @@ public class GetNetworkTreeQueryHandler : IRequestHandler x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left, - cancellationToken); + .Where(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left); + + // اعمال فیلتر 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) { - 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() - .FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right, - cancellationToken); + .Where(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right); + + // اعمال فیلتر 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) { - node.RightChild = await BuildTree(rightChild.Id, maxDepth, currentDepth + 1, cancellationToken); + node.RightChild = await BuildTree(rightChild.Id, maxDepth, currentDepth + 1, cancellationToken, request); } return node; } + + /// + /// تبدیل شماره هفته (مثلاً 2025-W05) به تاریخ شروع و پایان هفته + /// + 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); + } + } } diff --git a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/NetworkTreeDto.cs b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/NetworkTreeDto.cs index c623693..b43e599 100644 --- a/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/NetworkTreeDto.cs +++ b/src/CMSMicroservice.Application/NetworkMembershipCQ/Queries/GetNetworkTree/NetworkTreeDto.cs @@ -11,6 +11,27 @@ public class NetworkTreeDto public string? LastName { get; set; } public NetworkLeg? LegPosition { get; set; } public int CurrentDepth { get; set; } + + /// + /// تاریخ فعال‌سازی در باشگاه مشتریان + /// + public DateTime? ClubActivatedAt { get; set; } + + /// + /// وضعیت فعال بودن در باشگاه مشتریان + /// + public bool IsClubActive { get; set; } + + /// + /// شماره هفته فعال‌سازی + /// + public string? ActivationWeekNumber { get; set; } + + /// + /// تاریخ ایجاد کاربر + /// + public DateTimeOffset UserCreated { get; set; } + public NetworkTreeDto? LeftChild { get; set; } public NetworkTreeDto? RightChild { get; set; } } diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 030e1b3..985486e 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.152 + 0.0.154 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto index 81a8c7e..ddbc842 100644 --- a/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto +++ b/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto @@ -155,6 +155,8 @@ message GetNetworkTreeRequest int64 user_id = 1; google.protobuf.Int32Value max_depth = 2; google.protobuf.BoolValue only_active = 3; + google.protobuf.BoolValue is_club_active = 4; + google.protobuf.StringValue activation_week_number = 5; } message GetNetworkTreeResponse @@ -171,6 +173,10 @@ message NetworkTreeNodeModel int32 network_level = 5; bool is_active = 6; 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 diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs index 19d13d3..e109233 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/NetworkMembershipProfile.cs @@ -12,7 +12,9 @@ public class NetworkMembershipProfile : IRegister // Request mapping config.NewConfig() .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: تبدیل درخت به لیست مسطح config.NewConfig() @@ -81,7 +83,9 @@ public class NetworkMembershipProfile : IRegister UserName = $"{node.FirstName} {node.LastName}".Trim(), NetworkLeg = (int)(node.LegPosition ?? NetworkLeg.Left), NetworkLevel = node.CurrentDepth, - IsActive = true + IsActive = true, + IsClubActive = node.IsClubActive, + ActivationWeekNumber = node.ActivationWeekNumber ?? string.Empty }; if (parentId.HasValue) @@ -89,6 +93,16 @@ public class NetworkMembershipProfile : IRegister 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); // بازگشتی برای فرزندان