From 8f10f7057cc82b9353614da7007225543674fd86 Mon Sep 17 00:00:00 2001 From: mahan Date: Sun, 4 Jan 2026 17:54:59 +0330 Subject: [PATCH 1/3] fix: handle null workshop details in ticket display logic --- CompanyManagment.Application/WorkshopAppliction.cs | 4 ++++ .../Areas/AdminNew/Pages/Company/Ticket/Index.cshtml.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CompanyManagment.Application/WorkshopAppliction.cs b/CompanyManagment.Application/WorkshopAppliction.cs index a6efd5de..c2110263 100644 --- a/CompanyManagment.Application/WorkshopAppliction.cs +++ b/CompanyManagment.Application/WorkshopAppliction.cs @@ -407,6 +407,10 @@ public class WorkshopAppliction : IWorkshopApplication public EditWorkshop GetDetails(long id) { var workshop = _workshopRepository.GetDetails(id); + if (workshop == null) + { + return null; + } if (workshop.IsClassified) { workshop.CreatePlan = _workshopPlanApplication.GetWorkshopPlanByWorkshopId(id); diff --git a/ServiceHost/Areas/AdminNew/Pages/Company/Ticket/Index.cshtml.cs b/ServiceHost/Areas/AdminNew/Pages/Company/Ticket/Index.cshtml.cs index bf33a3ed..11b18fd1 100644 --- a/ServiceHost/Areas/AdminNew/Pages/Company/Ticket/Index.cshtml.cs +++ b/ServiceHost/Areas/AdminNew/Pages/Company/Ticket/Index.cshtml.cs @@ -80,7 +80,7 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.Ticket public IActionResult OnGetShowDetailTicketByAdmin(long ticketID) { var res = _ticketApplication.GetDetails(ticketID); - res.WorkshopName = _workshopApplication.GetDetails(res.WorkshopId).WorkshopFullName; + res.WorkshopName = _workshopApplication.GetDetails(res.WorkshopId)?.WorkshopFullName??""; return Partial("DetailTicketModal", res); } From 8d93fa4fc6d43c85ee5fe8e5bc146105268571bf Mon Sep 17 00:00:00 2001 From: mahan Date: Sun, 4 Jan 2026 20:39:20 +0330 Subject: [PATCH 2/3] Add approval workflow for task section completion - Updated status transitions to include PendingForCompletion before Completed - Added API endpoint and command/handler for approving/rejecting section completion - Fixed additional time calculation to include minutes - Refactored project board query logic and improved user ordering - Updated launch settings with new HTTPS endpoint - Documented progress percentage feature for TaskSection --- .../ApproveTaskSectionCompletionCommand.cs | 57 ++++++++ ...veTaskSectionCompletionCommandValidator.cs | 14 ++ .../ChangeStatusSectionCommandHandler.cs | 6 +- .../SetTimeProjectCommandHandler.cs | 2 +- .../ProjectBoardListQueryHandler.cs | 126 ++++++++++-------- .../ProgramManager/ProjectController.cs | 10 +- 6 files changed, 154 insertions(+), 61 deletions(-) create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ApproveTaskSectionCompletion/ApproveTaskSectionCompletionCommand.cs create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ApproveTaskSectionCompletion/ApproveTaskSectionCompletionCommandValidator.cs diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ApproveTaskSectionCompletion/ApproveTaskSectionCompletionCommand.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ApproveTaskSectionCompletion/ApproveTaskSectionCompletionCommand.cs new file mode 100644 index 00000000..4b21408b --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ApproveTaskSectionCompletion/ApproveTaskSectionCompletionCommand.cs @@ -0,0 +1,57 @@ +using GozareshgirProgramManager.Application._Common.Interfaces; +using GozareshgirProgramManager.Application._Common.Models; +using GozareshgirProgramManager.Domain._Common; +using GozareshgirProgramManager.Domain._Common.Exceptions; +using GozareshgirProgramManager.Domain.ProjectAgg.Enums; +using GozareshgirProgramManager.Domain.ProjectAgg.Repositories; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ApproveTaskSectionCompletion; + +public record ApproveTaskSectionCompletionCommand(Guid TaskSectionId, bool IsApproved) : IBaseCommand; + +public class ApproveTaskSectionCompletionCommandHandler : IBaseCommandHandler +{ + private readonly ITaskSectionRepository _taskSectionRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IAuthHelper _authHelper; + + public ApproveTaskSectionCompletionCommandHandler( + ITaskSectionRepository taskSectionRepository, + IUnitOfWork unitOfWork, + IAuthHelper authHelper) + { + _taskSectionRepository = taskSectionRepository; + _unitOfWork = unitOfWork; + _authHelper = authHelper; + } + + public async Task Handle(ApproveTaskSectionCompletionCommand request, CancellationToken cancellationToken) + { + var currentUserId = _authHelper.GetCurrentUserId() + ?? throw new UnAuthorizedException(" ? "); + + var section = await _taskSectionRepository.GetByIdAsync(request.TaskSectionId, cancellationToken); + if (section == null) + { + return OperationResult.NotFound(" ? "); + } + + if (section.Status != TaskSectionStatus.PendingForCompletion) + { + return OperationResult.Failure(" ԝ?? ʘ? ?? ? "); + } + + if (request.IsApproved) + { + section.UpdateStatus(TaskSectionStatus.Completed); + } + else + { + section.UpdateStatus(TaskSectionStatus.Incomplete); + } + + await _unitOfWork.SaveChangesAsync(cancellationToken); + + return OperationResult.Success(); + } +} diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ApproveTaskSectionCompletion/ApproveTaskSectionCompletionCommandValidator.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ApproveTaskSectionCompletion/ApproveTaskSectionCompletionCommandValidator.cs new file mode 100644 index 00000000..c12a43c3 --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ApproveTaskSectionCompletion/ApproveTaskSectionCompletionCommandValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ApproveTaskSectionCompletion; + +public class ApproveTaskSectionCompletionCommandValidator : AbstractValidator +{ + public ApproveTaskSectionCompletionCommandValidator() + { + RuleFor(c => c.TaskSectionId) + .NotEmpty() + .NotNull() + .WithMessage(" ? ? "); + } +} 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 d5d0cd62..178a0257 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 @@ -89,9 +89,9 @@ public class ChangeStatusSectionCommandHandler : IBaseCommandHandler> { { TaskSectionStatus.ReadyToStart, [TaskSectionStatus.InProgress] }, - { TaskSectionStatus.InProgress, [TaskSectionStatus.Incomplete, TaskSectionStatus.Completed] }, - { TaskSectionStatus.Incomplete, [TaskSectionStatus.InProgress, TaskSectionStatus.Completed] }, - { TaskSectionStatus.Completed, [TaskSectionStatus.InProgress, TaskSectionStatus.Incomplete] }, // Can return to InProgress or Incomplete + { TaskSectionStatus.InProgress, [TaskSectionStatus.Incomplete, TaskSectionStatus.PendingForCompletion] }, + { TaskSectionStatus.Incomplete, [TaskSectionStatus.InProgress, TaskSectionStatus.PendingForCompletion] }, + { TaskSectionStatus.PendingForCompletion, [TaskSectionStatus.InProgress, TaskSectionStatus.Incomplete] }, // Can return to InProgress or Incomplete { TaskSectionStatus.NotAssigned, [TaskSectionStatus.InProgress, TaskSectionStatus.ReadyToStart] } }; 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 89ae80a1..2ee2ea7d 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 @@ -366,7 +366,7 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler x.Id, x => x.FullName, cancellationToken); - var result = data.Select(x => - { - // محاسبه یکبار برای هر Activity و Cache کردن نتیجه - var activityTimeData = x.Activities.Select(a => + var result = data + .Select(x => { - var timeSpent = a.GetTimeSpent(); - return new + // محاسبه یکبار برای هر Activity و Cache کردن نتیجه + var activityTimeData = x.Activities.Select(a => { - Activity = a, - TimeSpent = timeSpent, - TotalSeconds = timeSpent.TotalSeconds, - FormattedTime = timeSpent.ToString(@"hh\:mm") - }; - }).ToList(); - - // ادغام پشت سر هم فعالیت‌های یک کاربر - var mergedHistories = new List(); - foreach (var activityData in activityTimeData) - { - var lastHistory = mergedHistories.LastOrDefault(); - - // اگر آخرین history برای همین کاربر باشد، زمان‌ها را جمع می‌کنیم - if (lastHistory != null && lastHistory.UserId == activityData.Activity.UserId) - { - var totalTimeSpan = lastHistory.WorkedTimeSpan + activityData.TimeSpent; - lastHistory.WorkedTimeSpan = totalTimeSpan; - lastHistory.WorkedTime = totalTimeSpan.ToString(@"hh\:mm"); - } - else - { - // در غیر این صورت، یک history جدید اضافه می‌کنیم - mergedHistories.Add(new ProjectProgressHistoryDto() + var timeSpent = a.GetTimeSpent(); + return new { - UserId = activityData.Activity.UserId, - IsCurrentUser = activityData.Activity.UserId == currentUserId, - Name = users.GetValueOrDefault(activityData.Activity.UserId, "ناشناس"), - WorkedTime = activityData.FormattedTime, - WorkedTimeSpan = activityData.TimeSpent, - }); - } - } + Activity = a, + TimeSpent = timeSpent, + TotalSeconds = timeSpent.TotalSeconds, + FormattedTime = timeSpent.ToString(@"hh\:mm") + }; + }).ToList(); - return new ProjectBoardListResponse() - { - Id = x.Id, - PhaseName = x.Task.Phase.Name, - ProjectName = x.Task.Phase.Project.Name, - TaskName = x.Task.Name, - SectionStatus = x.Status, - Progress = new ProjectProgressDto() + // ادغام پشت سر هم فعالیت‌های یک کاربر + var mergedHistories = new List(); + foreach (var activityData in activityTimeData) { - CompleteSecond = x.FinalEstimatedHours.TotalSeconds, - CurrentSecond = activityTimeData.Sum(a => a.TotalSeconds), - Histories = mergedHistories - }, - OriginalUser = users.GetValueOrDefault(x.OriginalAssignedUserId, "ناشناس"), - AssignedUser = x.CurrentAssignedUserId == x.OriginalAssignedUserId ? null - : users.GetValueOrDefault(x.CurrentAssignedUserId, "ناشناس"), - SkillName = x.Skill?.Name??"-", - }; - }).ToList(); + var lastHistory = mergedHistories.LastOrDefault(); + + // اگر آخرین history برای همین کاربر باشد، زمان‌ها را جمع می‌کنیم + if (lastHistory != null && lastHistory.UserId == activityData.Activity.UserId) + { + var totalTimeSpan = lastHistory.WorkedTimeSpan + activityData.TimeSpent; + lastHistory.WorkedTimeSpan = totalTimeSpan; + lastHistory.WorkedTime = totalTimeSpan.ToString(@"hh\:mm"); + } + else + { + // در غیر این صورت، یک history جدید اضافه می‌کنیم + mergedHistories.Add(new ProjectProgressHistoryDto() + { + UserId = activityData.Activity.UserId, + IsCurrentUser = activityData.Activity.UserId == currentUserId, + Name = users.GetValueOrDefault(activityData.Activity.UserId, "ناشناس"), + WorkedTime = activityData.FormattedTime, + WorkedTimeSpan = activityData.TimeSpent, + }); + } + } + + mergedHistories = mergedHistories.OrderByDescending(h => h.IsCurrentUser).ToList(); + + return new ProjectBoardListResponse() + { + Id = x.Id, + PhaseName = x.Task.Phase.Name, + ProjectName = x.Task.Phase.Project.Name, + TaskName = x.Task.Name, + SectionStatus = x.Status, + Progress = new ProjectProgressDto() + { + CompleteSecond = x.FinalEstimatedHours.TotalSeconds, + CurrentSecond = activityTimeData.Sum(a => a.TotalSeconds), + Histories = mergedHistories + }, + OriginalUser = users.GetValueOrDefault(x.OriginalAssignedUserId, "ناشناس"), + AssignedUser = x.CurrentAssignedUserId == x.OriginalAssignedUserId ? null + : users.GetValueOrDefault(x.CurrentAssignedUserId, "ناشناس"), + SkillName = x.Skill?.Name??"-", + }; + }) + .OrderByDescending(r => + { + // اگر AssignedUser null نباشد، بررسی کن که برابر current user هست یا نه + if (r.AssignedUser != null) + { + return users.FirstOrDefault(u => u.Value == r.AssignedUser).Key == currentUserId; + } + // اگر AssignedUser null بود، از OriginalUser بررسی کن + return users.FirstOrDefault(u => u.Value == r.OriginalUser).Key == currentUserId; + }) + .ToList(); return OperationResult>.Success(result); } diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index bf9617a5..153ca3cd 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -1,5 +1,6 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using GozareshgirProgramManager.Application._Common.Models; +using GozareshgirProgramManager.Application.Modules.Projects.Commands.ApproveTaskSectionCompletion; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoStopOverTimeTaskSections; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoUpdateDeployStatus; @@ -157,4 +158,11 @@ public class ProjectController : ProgramManagerBaseController var res = await _mediator.Send(command); return res; } + + [HttpPost("approve-completion")] + public async Task> ApproveTaskSectionCompletion([FromBody] ApproveTaskSectionCompletionCommand command) + { + var res = await _mediator.Send(command); + return res; + } } \ No newline at end of file From 340685a06cb7ed461eb9297faf6da2ed46f00ccc Mon Sep 17 00:00:00 2001 From: mahan Date: Mon, 5 Jan 2026 09:49:31 +0330 Subject: [PATCH 3/3] feat: enhance project retrieval by including additional times and update search parameter naming --- .../Queries/GetProjectsList/GetProjectsListQueryHandler.cs | 1 + .../Admin/Controllers/ProgramManager/ProjectController.cs | 4 ++-- ServiceHost/Properties/launchSettings.json | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs index e79b7fe4..ceaf2e19 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs @@ -272,6 +272,7 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler s.Activities) + .Include(x=>x.AdditionalTimes) .Where(s => s.TaskId == task.Id) .ToListAsync(cancellationToken); diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index 153ca3cd..95d6da94 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -44,9 +44,9 @@ public class ProjectController : ProgramManagerBaseController [HttpGet("search")] public async Task>> Search( - [FromQuery] string query) + [FromQuery] string search) { - var searchQuery = new GetProjectSearchQuery(query); + var searchQuery = new GetProjectSearchQuery(search); var res = await _mediator.Send(searchQuery); return res; } diff --git a/ServiceHost/Properties/launchSettings.json b/ServiceHost/Properties/launchSettings.json index 788962e4..f5214c2b 100644 --- a/ServiceHost/Properties/launchSettings.json +++ b/ServiceHost/Properties/launchSettings.json @@ -19,7 +19,7 @@ "sqlDebugging": true, "dotnetRunMessages": "true", "nativeDebugging": true, - "applicationUrl": "https://localhost:5004;http://localhost:5003;", + "applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:5006", "jsWebView2Debugging": false, "hotReloadEnabled": true }, @@ -44,7 +44,7 @@ "sqlDebugging": true, "dotnetRunMessages": "true", "nativeDebugging": true, - "applicationUrl": "https://localhost:5004;http://localhost:5003;", + "applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:5006;", "jsWebView2Debugging": false, "hotReloadEnabled": true }