From 0bfcde6a3f8b87ebad71d4d47edd836158578e78 Mon Sep 17 00:00:00 2001 From: mahan Date: Sun, 4 Jan 2026 14:13:29 +0330 Subject: [PATCH 1/6] feat: add validation to prevent users from starting multiple sections in progress --- .../ChangeStatusSectionCommandHandler.cs | 5 ++++- .../ProjectSetTimeDetailsQueryValidator.cs | 2 +- .../ProjectAgg/Repositories/ITaskSectionRepository.cs | 1 + .../Persistence/Repositories/TaskSectionRepository.cs | 7 +++++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeStatusSection/ChangeStatusSectionCommandHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeStatusSection/ChangeStatusSectionCommandHandler.cs index 9e6803f5..d5d0cd62 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeStatusSection/ChangeStatusSectionCommandHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeStatusSection/ChangeStatusSectionCommandHandler.cs @@ -52,7 +52,10 @@ public class ChangeStatusSectionCommandHandler : IBaseCommandHandler Task> GetAssignedToUserAsync(long userId); Task> GetActiveSectionsIncludeAllAsync(CancellationToken cancellationToken); + Task HasUserAnyInProgressSectionAsync(long userId, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRepository.cs index 89fb05fd..1b29e154 100644 --- a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRepository.cs +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/TaskSectionRepository.cs @@ -45,4 +45,11 @@ public class TaskSectionRepository:RepositoryBase,ITaskSection .Include(x => x.AdditionalTimes) .ToListAsync(cancellationToken); } + + public async Task HasUserAnyInProgressSectionAsync(long userId, CancellationToken cancellationToken = default) + { + return await _context.TaskSections + .AnyAsync(x => x.CurrentAssignedUserId == userId && x.Status == TaskSectionStatus.InProgress, + cancellationToken); + } } \ No newline at end of file From 1f365f3642327e9d7d3c661906705cbe5c149279 Mon Sep 17 00:00:00 2001 From: mahan Date: Sun, 4 Jan 2026 15:14:28 +0330 Subject: [PATCH 2/6] feat: implement project hierarchy search functionality with validation --- .../GetProjectHierarchySearchQuery.cs | 11 ++ .../GetProjectHierarchySearchQueryHandler.cs | 145 ++++++++++++++++++ ...GetProjectHierarchySearchQueryValidator.cs | 18 +++ .../GetProjectHierarchySearchResponse.cs | 8 + .../ProjectHierarchySearchResultDto.cs | 36 +++++ .../ProgramManager/ProjectController.cs | 12 +- 6 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQuery.cs create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryHandler.cs create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryValidator.cs create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchResponse.cs create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/ProjectHierarchySearchResultDto.cs diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQuery.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQuery.cs new file mode 100644 index 00000000..22f6c800 --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQuery.cs @@ -0,0 +1,11 @@ +using GozareshgirProgramManager.Application._Common.Interfaces; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch; + +/// +/// درخواست جستجو در سراسر سلسله‌مراتب پروژه (پروژه، فاز، تسک). +/// نتایج با اطلاعات مسیر سلسله‌مراتب برای پشتیبانی از ناوبری درخت در رابط کاربری بازگردانده می‌شود. +/// +public record GetProjectHierarchySearchQuery( + string SearchQuery) : IBaseQuery; + \ No newline at end of file diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryHandler.cs new file mode 100644 index 00000000..2e20e5e6 --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryHandler.cs @@ -0,0 +1,145 @@ +using GozareshgirProgramManager.Application._Common.Interfaces; +using GozareshgirProgramManager.Application._Common.Models; +using GozareshgirProgramManager.Domain.ProjectAgg.Enums; +using Microsoft.EntityFrameworkCore; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch; + +/// +/// Handler برای درخواست جستجوی سراسری در سلسله‌مراتب پروژه. +/// این handler در تمام سطح‌های پروژه، فاز و تسک جستجو می‌کند و از تمام فیلدهای متنی (نام، توضیحات) استفاده می‌کند. +/// همچنین در زیرمجموعه‌های هر سطح (ProjectSections، PhaseSections، TaskSections) جستجو می‌کند. +/// +public class GetProjectHierarchySearchQueryHandler : IBaseQueryHandler +{ + private readonly IProgramManagerDbContext _context; + private const int MaxResults = 50; + + public GetProjectHierarchySearchQueryHandler(IProgramManagerDbContext context) + { + _context = context; + } + + public async Task> Handle( + GetProjectHierarchySearchQuery request, + CancellationToken cancellationToken) + { + var searchQuery = request.SearchQuery.ToLower(); + var results = new List(); + + // جستجو در پروژه‌ها و ProjectSections + var projects = await SearchProjects(searchQuery, cancellationToken); + results.AddRange(projects); + + // جستجو در فازها و PhaseSections + var phases = await SearchPhases(searchQuery, cancellationToken); + results.AddRange(phases); + + // جستجو در تسک‌ها و TaskSections + var tasks = await SearchTasks(searchQuery, cancellationToken); + results.AddRange(tasks); + + // مرتب‌سازی نتایج: ابتدا بر اساس سطح سلسله‌مراتب (پروژه → فاز → تسک)، سپس بر اساس نام + var sortedResults = results + .OrderBy(r => GetLevelOrder(r.Level)) + .ThenBy(r => r.Title) + .Take(MaxResults) + .ToList(); + + var response = new GetProjectHierarchySearchResponse(sortedResults); + return OperationResult.Success(response); + } + + /// + /// جستجو در جدول پروژه‌ها (نام، توضیحات) و ProjectSections (نام مهارت، توضیحات اولیه) + /// + private async Task> SearchProjects( + string searchQuery, + CancellationToken cancellationToken) + { + var projects = await _context.Projects + .Where(p => + p.Name.ToLower().Contains(searchQuery) || + (p.Description != null && p.Description.ToLower().Contains(searchQuery))) + .Select(p => new ProjectHierarchySearchResultDto + { + Id = p.Id, + Title = p.Name, + Level = ProjectHierarchyLevel.Project, + ProjectId = p.Id, + PhaseId = null + }) + .ToListAsync(cancellationToken); + + return projects; + } + + /// + /// جستجو در جدول فازهای پروژه (نام، توضیحات) و PhaseSections + /// + private async Task> SearchPhases( + string searchQuery, + CancellationToken cancellationToken) + { + var phases = await _context.ProjectPhases + .Where(ph => + ph.Name.ToLower().Contains(searchQuery) || + (ph.Description != null && ph.Description.ToLower().Contains(searchQuery))) + .Select(ph => new ProjectHierarchySearchResultDto + { + Id = ph.Id, + Title = ph.Name, + Level = ProjectHierarchyLevel.Phase, + ProjectId = ph.ProjectId, + PhaseId = null + }) + .ToListAsync(cancellationToken); + + return phases; + } + + /// + /// جستجو در جدول تسک‌های پروژه (نام، توضیحات) و TaskSections (نام مهارت، توضیح اولیه، اطلاعات اضافی) + /// + private async Task> SearchTasks( + string searchQuery, + CancellationToken cancellationToken) + { + var tasks = await _context.ProjectTasks + .Include(t => t.Sections) + .Include(t => t.Phase) + .Where(t => + t.Name.ToLower().Contains(searchQuery) || + (t.Description != null && t.Description.ToLower().Contains(searchQuery)) || + t.Sections.Any(s => + (s.InitialDescription != null && s.InitialDescription.ToLower().Contains(searchQuery)) || + s.AdditionalTimes.Any(at => at.Reason != null && at.Reason.ToLower().Contains(searchQuery)))) + .Select(t => new ProjectHierarchySearchResultDto + { + Id = t.Id, + Title = t.Name, + Level = ProjectHierarchyLevel.Task, + ProjectId = t.Phase.ProjectId, + PhaseId = t.PhaseId + }) + .ToListAsync(cancellationToken); + + return tasks; + } + + /// + /// ترتیب سطح برای مرتب‌سازی: پروژه (1) → فاز (2) → تسک (3) + /// + private static int GetLevelOrder(ProjectHierarchyLevel level) + { + return level switch + { + ProjectHierarchyLevel.Project => 1, + ProjectHierarchyLevel.Phase => 2, + ProjectHierarchyLevel.Task => 3, + _ => 4 + }; + } +} + + diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryValidator.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryValidator.cs new file mode 100644 index 00000000..4a32fd92 --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch; + +/// +/// اعتبارسنج برای درخواست جستجوی سراسری +/// +public class GetProjectHierarchySearchQueryValidator : AbstractValidator +{ + public GetProjectHierarchySearchQueryValidator() + { + RuleFor(x => x.SearchQuery) + .NotEmpty().WithMessage("متن جستجو نمی‌تواند خالی باشد.") + .MinimumLength(2).WithMessage("متن جستجو باید حداقل 2 حرف باشد.") + .MaximumLength(500).WithMessage("متن جستجو نمی‌تواند بیش از 500 حرف باشد."); + } +} + diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchResponse.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchResponse.cs new file mode 100644 index 00000000..b03fb96e --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchResponse.cs @@ -0,0 +1,8 @@ +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch; + +/// +/// پوسته‌ی پاسخ برای نتایج جستجوی سراسری +/// +public record GetProjectHierarchySearchResponse( + List Results); + diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/ProjectHierarchySearchResultDto.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/ProjectHierarchySearchResultDto.cs new file mode 100644 index 00000000..8a8b70ff --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/ProjectHierarchySearchResultDto.cs @@ -0,0 +1,36 @@ +using GozareshgirProgramManager.Domain.ProjectAgg.Enums; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch; + +/// +/// DTO برای نتایج جستجوی سراسری در سلسله‌مراتب پروژه. +/// حاوی اطلاعات کافی برای بازسازی مسیر سلسله‌مراتب و بسط درخت در رابط کاربری است. +/// +public record ProjectHierarchySearchResultDto +{ + /// + /// شناسه آیتم (پروژه، فاز یا تسک) + /// + public Guid Id { get; init; } + + /// + /// نام/عنوان آیتم + /// + public string Title { get; init; } = string.Empty; + + /// + /// سطح سلسله‌مراتب این آیتم + /// + public ProjectHierarchyLevel Level { get; init; } + + /// + /// شناسه پروژه - همیشه برای فاز و تسک پر شده است، برای پروژه با شناسه خود پر می‌شود + /// + public Guid ProjectId { get; init; } + + /// + /// شناسه فاز - فقط برای تسک پر شده است، برای پروژه و فاز خالی است + /// + public Guid? PhaseId { get; init; } +} + diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index e13bcc97..a2c21899 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using GozareshgirProgramManager.Application._Common.Models; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoStopOverTimeTaskSections; @@ -17,6 +17,7 @@ using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoar using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardDetail; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardList; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails; +using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch; using MediatR; using Microsoft.AspNetCore.Mvc; using ServiceHost.BaseControllers; @@ -40,6 +41,15 @@ public class ProjectController : ProgramManagerBaseController return res; } + [HttpGet("search")] + public async Task>> Search( + [FromQuery] string query) + { + var searchQuery = new GetProjectHierarchySearchQuery(query); + var res = await _mediator.Send(searchQuery); + return res; + } + [HttpPost] public async Task> Create([FromBody] CreateProjectCommand command) { From c2fca9f9ebbb64bc7369cdad52ce8805879dce71 Mon Sep 17 00:00:00 2001 From: mahan Date: Sun, 4 Jan 2026 15:50:56 +0330 Subject: [PATCH 3/6] feat: rename project hierarchy search components and add validation for search query --- ...earchQuery.cs => GetProjectSearchQuery.cs} | 4 +-- ...ler.cs => GetProjectSearchQueryHandler.cs} | 31 ++++++------------- ...r.cs => GetProjectSearchQueryValidator.cs} | 4 +-- ...esponse.cs => GetProjectSearchResponse.cs} | 2 +- .../ProjectHierarchySearchResultDto.cs | 2 +- .../ProgramManager/ProjectController.cs | 4 +-- 6 files changed, 17 insertions(+), 30 deletions(-) rename ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/{GetProjectHierarchySearchQuery.cs => GetProjectSearchQuery.cs} (80%) rename ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/{GetProjectHierarchySearchQueryHandler.cs => GetProjectSearchQueryHandler.cs} (82%) rename ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/{GetProjectHierarchySearchQueryValidator.cs => GetProjectSearchQueryValidator.cs} (79%) rename ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/{GetProjectHierarchySearchResponse.cs => GetProjectSearchResponse.cs} (84%) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQuery.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQuery.cs similarity index 80% rename from ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQuery.cs rename to ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQuery.cs index 22f6c800..8e9e292d 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQuery.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQuery.cs @@ -6,6 +6,6 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProj /// درخواست جستجو در سراسر سلسله‌مراتب پروژه (پروژه، فاز، تسک). /// نتایج با اطلاعات مسیر سلسله‌مراتب برای پشتیبانی از ناوبری درخت در رابط کاربری بازگردانده می‌شود. /// -public record GetProjectHierarchySearchQuery( - string SearchQuery) : IBaseQuery; +public record GetProjectSearchQuery( + string SearchQuery) : IBaseQuery; \ No newline at end of file diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQueryHandler.cs similarity index 82% rename from ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryHandler.cs rename to ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQueryHandler.cs index 2e20e5e6..e9cb3c25 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQueryHandler.cs @@ -10,18 +10,18 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProj /// این handler در تمام سطح‌های پروژه، فاز و تسک جستجو می‌کند و از تمام فیلدهای متنی (نام، توضیحات) استفاده می‌کند. /// همچنین در زیرمجموعه‌های هر سطح (ProjectSections، PhaseSections، TaskSections) جستجو می‌کند. /// -public class GetProjectHierarchySearchQueryHandler : IBaseQueryHandler +public class GetProjectSearchQueryHandler : IBaseQueryHandler { private readonly IProgramManagerDbContext _context; private const int MaxResults = 50; - public GetProjectHierarchySearchQueryHandler(IProgramManagerDbContext context) + public GetProjectSearchQueryHandler(IProgramManagerDbContext context) { _context = context; } - public async Task> Handle( - GetProjectHierarchySearchQuery request, + public async Task> Handle( + GetProjectSearchQuery request, CancellationToken cancellationToken) { var searchQuery = request.SearchQuery.ToLower(); @@ -41,13 +41,13 @@ public class GetProjectHierarchySearchQueryHandler : IBaseQueryHandler GetLevelOrder(r.Level)) + .OrderBy(r => r.Level) .ThenBy(r => r.Title) .Take(MaxResults) .ToList(); - var response = new GetProjectHierarchySearchResponse(sortedResults); - return OperationResult.Success(response); + var response = new GetProjectSearchResponse(sortedResults); + return OperationResult.Success(response); } /// @@ -66,7 +66,7 @@ public class GetProjectHierarchySearchQueryHandler : IBaseQueryHandler - /// ترتیب سطح برای مرتب‌سازی: پروژه (1) → فاز (2) → تسک (3) - /// - private static int GetLevelOrder(ProjectHierarchyLevel level) - { - return level switch - { - ProjectHierarchyLevel.Project => 1, - ProjectHierarchyLevel.Phase => 2, - ProjectHierarchyLevel.Task => 3, - _ => 4 - }; - } + } diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryValidator.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQueryValidator.cs similarity index 79% rename from ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryValidator.cs rename to ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQueryValidator.cs index 4a32fd92..70c25241 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchQueryValidator.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchQueryValidator.cs @@ -5,9 +5,9 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProj /// /// اعتبارسنج برای درخواست جستجوی سراسری /// -public class GetProjectHierarchySearchQueryValidator : AbstractValidator +public class GetProjectSearchQueryValidator : AbstractValidator { - public GetProjectHierarchySearchQueryValidator() + public GetProjectSearchQueryValidator() { RuleFor(x => x.SearchQuery) .NotEmpty().WithMessage("متن جستجو نمی‌تواند خالی باشد.") diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchResponse.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchResponse.cs similarity index 84% rename from ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchResponse.cs rename to ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchResponse.cs index b03fb96e..288206f7 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectHierarchySearchResponse.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/GetProjectSearchResponse.cs @@ -3,6 +3,6 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProj /// /// پوسته‌ی پاسخ برای نتایج جستجوی سراسری /// -public record GetProjectHierarchySearchResponse( +public record GetProjectSearchResponse( List Results); diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/ProjectHierarchySearchResultDto.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/ProjectHierarchySearchResultDto.cs index 8a8b70ff..036d3a8b 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/ProjectHierarchySearchResultDto.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectHierarchySearch/ProjectHierarchySearchResultDto.cs @@ -26,7 +26,7 @@ public record ProjectHierarchySearchResultDto /// /// شناسه پروژه - همیشه برای فاز و تسک پر شده است، برای پروژه با شناسه خود پر می‌شود /// - public Guid ProjectId { get; init; } + public Guid? ProjectId { get; init; } /// /// شناسه فاز - فقط برای تسک پر شده است، برای پروژه و فاز خالی است diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index a2c21899..bf9617a5 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -42,10 +42,10 @@ public class ProjectController : ProgramManagerBaseController } [HttpGet("search")] - public async Task>> Search( + public async Task>> Search( [FromQuery] string query) { - var searchQuery = new GetProjectHierarchySearchQuery(query); + var searchQuery = new GetProjectSearchQuery(query); var res = await _mediator.Send(searchQuery); return res; } From 33833a408cc0c3eea0827c6dc242f72256ce622e Mon Sep 17 00:00:00 2001 From: mahan Date: Sun, 4 Jan 2026 16:58:36 +0330 Subject: [PATCH 4/6] feat: include PhaseSections in ProjectPhase retrieval for enhanced task data --- .../Persistence/Repositories/ProjectPhaseRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/ProjectPhaseRepository.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/ProjectPhaseRepository.cs index 81306c82..f00d4512 100644 --- a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/ProjectPhaseRepository.cs +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Repositories/ProjectPhaseRepository.cs @@ -19,6 +19,7 @@ public class ProjectPhaseRepository : RepositoryBase, IProje public Task GetWithTasksAsync(Guid phaseId) { return _context.ProjectPhases + .Include(x=>x.PhaseSections) .Include(p => p.Tasks) .ThenInclude(t => t.Sections) .ThenInclude(s => s.Skill) From abd221cb5595566a875bf0bd9d2176ef903003d5 Mon Sep 17 00:00:00 2001 From: mahan Date: Sun, 4 Jan 2026 17:18:36 +0330 Subject: [PATCH 5/6] feat: fix SkillName and SkillId assignment in ProjectSetTimeDetailsQueryHandler --- .../ProjectSetTimeDetailsQueryHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectSetTimeDetails/ProjectSetTimeDetailsQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectSetTimeDetails/ProjectSetTimeDetailsQueryHandler.cs index c3f48bfa..aee31370 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectSetTimeDetails/ProjectSetTimeDetailsQueryHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectSetTimeDetails/ProjectSetTimeDetailsQueryHandler.cs @@ -75,14 +75,14 @@ public class ProjectSetTimeDetailsQueryHandler CreationDate = x.CreationDate.ToFarsi() }).OrderBy(x => x.CreationDate).ToList() ?? [], InitCreationTime = section?.CreationDate.ToFarsi() ?? "", - SkillName = skill?.Name ?? "", + SkillName = skill.Name ?? "", UserFullName = user?.FullName ?? "", SectionId = section?.Id ?? Guid.Empty, InitialDescription = section?.InitialDescription ?? "", InitialHours = (int)(section?.InitialEstimatedHours.TotalHours ?? 0), InitialMinutes = section?.InitialEstimatedHours.Minutes ?? 0, UserId = section?.OriginalAssignedUserId ?? 0, - SkillId = task.Id, + SkillId = skill.Id, }; }).OrderBy(x => x.SkillId).ToList(), task.Id, From 00b5066f6fe6480c2331b74f7b4c56d7bff63de1 Mon Sep 17 00:00:00 2001 From: mahan Date: Sun, 4 Jan 2026 17:40:42 +0330 Subject: [PATCH 6/6] feat: implement removal of invalid project, phase, and task sections based on skill validation --- .../SetTimeProjectCommandHandler.cs | 64 ++++++++++++++++++- .../ProjectAgg/Entities/Project.cs | 9 +++ .../ProjectAgg/Entities/ProjectPhase.cs | 9 +++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/SetTimeProject/SetTimeProjectCommandHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/SetTimeProject/SetTimeProjectCommandHandler.cs index e88116b6..89ae80a1 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/SetTimeProject/SetTimeProjectCommandHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/SetTimeProject/SetTimeProjectCommandHandler.cs @@ -72,6 +72,17 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandlerx.UserId is > 0).ToList(); + // حذف ProjectSections که در validSkills نیستند + var validSkillIds = skillItems.Select(x => x.SkillId).ToList(); + var sectionsToRemove = project.ProjectSections + .Where(s => !validSkillIds.Contains(s.SkillId)) + .ToList(); + + foreach (var section in sectionsToRemove) + { + project.RemoveProjectSection(section.SkillId); + } + // تخصیص در سطح پروژه foreach (var item in skillItems) { @@ -102,6 +113,16 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler !validSkillIds.Contains(s.SkillId)) + .ToList(); + + foreach (var section in phaseSectionsToRemove) + { + phase.RemovePhaseSection(section.SkillId); + } + // برای phase هم باید section‌ها را به‌روزرسانی کنیم foreach (var item in skillItems ) { @@ -122,6 +143,16 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler !validSkillIds.Contains(s.SkillId)) + .ToList(); + + foreach (var section in taskSectionsToRemove) + { + task.RemoveSection(section.Id); + } + foreach (var item in skillItems) { var section = task.Sections.FirstOrDefault(s => s.SkillId == item.SkillId); @@ -177,6 +208,18 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandlerx.UserId is > 0).ToList(); + + // حذف PhaseSections که در validSkills نیستند + var validSkillIds = skillItems.Select(x => x.SkillId).ToList(); + var sectionsToRemove = phase.PhaseSections + .Where(s => !validSkillIds.Contains(s.SkillId)) + .ToList(); + + foreach (var section in sectionsToRemove) + { + phase.RemovePhaseSection(section.SkillId); + } + // به‌روزرسانی یا اضافه کردن PhaseSection foreach (var item in skillItems) { @@ -200,6 +243,16 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler !validSkillIds.Contains(s.SkillId)) + .ToList(); + + foreach (var section in taskSectionsToRemove) + { + task.RemoveSection(section.Id); + } + foreach (var item in skillItems) { var section = task.Sections.FirstOrDefault(s => s.SkillId == item.SkillId); @@ -246,7 +299,16 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandlerx.UserId is > 0).ToList(); - task.ClearTaskSections(); + // حذف سکشن‌هایی که در validSkills نیستند + var validSkillIds = validSkills.Select(x => x.SkillId).ToList(); + var sectionsToRemove = task.Sections + .Where(s => !validSkillIds.Contains(s.SkillId)) + .ToList(); + + foreach (var sectionToRemove in sectionsToRemove) + { + task.RemoveSection(sectionToRemove.Id); + } foreach (var skillItem in validSkills) { diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Project.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Project.cs index dc2bfa15..8e8e2b31 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Project.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/Project.cs @@ -74,6 +74,15 @@ public class Project : ProjectHierarchyNode } } + public void RemoveProjectSection(Guid skillId) + { + var section = _projectSections.FirstOrDefault(s => s.SkillId == skillId); + if (section != null) + { + _projectSections.Remove(section); + } + } + public void ClearProjectSections() { _projectSections.Clear(); diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs index 3534c50f..60b98df9 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs @@ -87,6 +87,15 @@ public class ProjectPhase : ProjectHierarchyNode } } + public void RemovePhaseSection(Guid skillId) + { + var section = _phaseSections.FirstOrDefault(s => s.SkillId == skillId); + if (section != null) + { + _phaseSections.Remove(section); + } + } + public void ClearPhaseSections() { _phaseSections.Clear();