merge from master
This commit is contained in:
@@ -406,6 +406,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);
|
||||
|
||||
@@ -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<ApproveTaskSectionCompletionCommand>
|
||||
{
|
||||
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<OperationResult> 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("ÝÞØ ÈÎÔ<C38E>åÇ?? ˜å ÏÑ ÇäÊÙÇÑ Ê˜ã?á åÓÊäÏ ÞÇÈá ÊÇ??Ï ?Ç ÑÏ åÓÊäÏ");
|
||||
}
|
||||
|
||||
if (request.IsApproved)
|
||||
{
|
||||
section.UpdateStatus(TaskSectionStatus.Completed);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.UpdateStatus(TaskSectionStatus.Incomplete);
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return OperationResult.Success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ApproveTaskSectionCompletion;
|
||||
|
||||
public class ApproveTaskSectionCompletionCommandValidator : AbstractValidator<ApproveTaskSectionCompletionCommand>
|
||||
{
|
||||
public ApproveTaskSectionCompletionCommandValidator()
|
||||
{
|
||||
RuleFor(c => c.TaskSectionId)
|
||||
.NotEmpty()
|
||||
.NotNull()
|
||||
.WithMessage("ÔäÇÓå ÈÎÔ äã?<3F>ÊæÇäÏ ÎÇá? ÈÇÔÏ");
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,10 @@ public class ChangeStatusSectionCommandHandler : IBaseCommandHandler<ChangeStatu
|
||||
// Going TO InProgress: Check if section has remaining time, then start work
|
||||
if (!section.HasRemainingTime())
|
||||
return OperationResult.ValidationError("زمان این بخش به پایان رسیده است");
|
||||
|
||||
if (await _taskSectionRepository.HasUserAnyInProgressSectionAsync(section.CurrentAssignedUserId, cancellationToken))
|
||||
{
|
||||
return OperationResult.ValidationError("کاربر مورد نظر در حال حاضر بخش دیگری را در وضعیت 'درحال انجام' دارد");
|
||||
}
|
||||
section.StartWork();
|
||||
}
|
||||
else
|
||||
@@ -86,9 +89,9 @@ public class ChangeStatusSectionCommandHandler : IBaseCommandHandler<ChangeStatu
|
||||
var validTransitions = new Dictionary<TaskSectionStatus, List<TaskSectionStatus>>
|
||||
{
|
||||
{ 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] }
|
||||
};
|
||||
|
||||
|
||||
@@ -4,10 +4,15 @@ using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
|
||||
|
||||
public record SetTimeProjectCommand(List<SetTimeProjectSectionItem> SectionItems, Guid Id, ProjectHierarchyLevel Level):IBaseCommand;
|
||||
public record SetTimeProjectCommand(
|
||||
List<SetTimeProjectSkillItem> SkillItems,
|
||||
Guid Id,
|
||||
ProjectHierarchyLevel Level,
|
||||
bool CascadeToChildren) : IBaseCommand;
|
||||
|
||||
public class SetTimeSectionTime
|
||||
{
|
||||
public string Description { get; set; }
|
||||
public int Hours { get; set; }
|
||||
public int Minutes { get; set; }
|
||||
}
|
||||
@@ -6,6 +6,8 @@ using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
|
||||
|
||||
@@ -15,21 +17,33 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
private readonly IProjectPhaseRepository _projectPhaseRepository;
|
||||
private readonly IProjectTaskRepository _projectTaskRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ISkillRepository _skillRepository;
|
||||
private readonly IPhaseSectionRepository _phaseSectionRepository;
|
||||
private readonly IProjectSectionRepository _projectSectionRepository;
|
||||
private long? _userId;
|
||||
private readonly ITaskSectionRepository _taskSectionRepository;
|
||||
|
||||
|
||||
public SetTimeProjectCommandHandler(
|
||||
IProjectRepository projectRepository,
|
||||
IProjectPhaseRepository projectPhaseRepository,
|
||||
IProjectTaskRepository projectTaskRepository,
|
||||
IUnitOfWork unitOfWork, IAuthHelper authHelper)
|
||||
IUnitOfWork unitOfWork, IAuthHelper authHelper,
|
||||
IUserRepository userRepository, ISkillRepository skillRepository,
|
||||
IPhaseSectionRepository phaseSectionRepository,
|
||||
IProjectSectionRepository projectSectionRepository,
|
||||
ITaskSectionRepository taskSectionRepository)
|
||||
{
|
||||
_projectRepository = projectRepository;
|
||||
_projectPhaseRepository = projectPhaseRepository;
|
||||
_projectTaskRepository = projectTaskRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_authHelper = authHelper;
|
||||
_userRepository = userRepository;
|
||||
_skillRepository = skillRepository;
|
||||
_phaseSectionRepository = phaseSectionRepository;
|
||||
_projectSectionRepository = projectSectionRepository;
|
||||
_taskSectionRepository = taskSectionRepository;
|
||||
_userId = authHelper.GetCurrentUserId();
|
||||
}
|
||||
|
||||
@@ -37,6 +51,10 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
{
|
||||
switch (request.Level)
|
||||
{
|
||||
case ProjectHierarchyLevel.Project:
|
||||
return await AssignProject(request);
|
||||
case ProjectHierarchyLevel.Phase:
|
||||
return await AssignProjectPhase(request);
|
||||
case ProjectHierarchyLevel.Task:
|
||||
return await SetTimeForProjectTask(request, cancellationToken);
|
||||
default:
|
||||
@@ -44,67 +62,229 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<OperationResult> SetTimeForProject(SetTimeProjectCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
private async Task<OperationResult> AssignProject(SetTimeProjectCommand request)
|
||||
{
|
||||
var project = await _projectRepository.GetWithFullHierarchyAsync(request.Id);
|
||||
if (project == null)
|
||||
if (project is null)
|
||||
{
|
||||
return OperationResult.NotFound("پروژه یافت نشد");
|
||||
return OperationResult.NotFound("<22><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>");
|
||||
}
|
||||
|
||||
long? addedByUserId = _userId;
|
||||
var skillItems = request.SkillItems.Where(x=>x.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)
|
||||
{
|
||||
var skill = await _skillRepository.GetByIdAsync(item.SkillId);
|
||||
if (skill is null)
|
||||
{
|
||||
return OperationResult.NotFound($"مهارت با شناسه {item.SkillId} یافت نشد");
|
||||
}
|
||||
|
||||
// تنظیم زمان برای تمام sections در تمام فازها و تسکهای پروژه
|
||||
// بررسی و بهروزرسانی یا اضافه کردن ProjectSection
|
||||
var existingSection = project.ProjectSections.FirstOrDefault(s => s.SkillId == item.SkillId);
|
||||
if (existingSection != null)
|
||||
{
|
||||
// اگر وجود داشت، فقط userId را بهروزرسانی کن
|
||||
existingSection.UpdateUser(item.UserId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// اگر وجود نداشت، اضافه کن
|
||||
var newSection = new ProjectSection(project.Id, item.UserId.Value, item.SkillId);
|
||||
await _projectSectionRepository.CreateAsync(newSection);
|
||||
}
|
||||
}
|
||||
|
||||
// حالا برای تمام فازها و تسکها cascade کن
|
||||
foreach (var phase in project.Phases)
|
||||
{
|
||||
foreach (var task in phase.Tasks)
|
||||
// اگر CascadeToChildren true است یا فاز override ندارد
|
||||
if (request.CascadeToChildren || !phase.HasAssignmentOverride)
|
||||
{
|
||||
foreach (var section in task.Sections)
|
||||
// حذف PhaseSections که در validSkills نیستند
|
||||
var phaseSectionsToRemove = phase.PhaseSections
|
||||
.Where(s => !validSkillIds.Contains(s.SkillId))
|
||||
.ToList();
|
||||
|
||||
foreach (var section in phaseSectionsToRemove)
|
||||
{
|
||||
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
|
||||
if (sectionItem != null)
|
||||
phase.RemovePhaseSection(section.SkillId);
|
||||
}
|
||||
|
||||
// برای phase هم باید sectionها را بهروزرسانی کنیم
|
||||
foreach (var item in skillItems )
|
||||
{
|
||||
var existingSection = phase.PhaseSections.FirstOrDefault(s => s.SkillId == item.SkillId);
|
||||
if (existingSection != null)
|
||||
{
|
||||
SetSectionTime(section, sectionItem, addedByUserId);
|
||||
existingSection.Update(item.UserId.Value, item.SkillId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newPhaseSection = new PhaseSection(phase.Id, item.UserId.Value, item.SkillId);
|
||||
await _phaseSectionRepository.CreateAsync(newPhaseSection);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var task in phase.Tasks)
|
||||
{
|
||||
// اگر CascadeToChildren true است یا تسک override ندارد
|
||||
if (request.CascadeToChildren || !task.HasAssignmentOverride)
|
||||
{
|
||||
// حذف TaskSections که در validSkills نیستند
|
||||
var taskSectionsToRemove = task.Sections
|
||||
.Where(s => !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);
|
||||
if (section != null)
|
||||
{
|
||||
// استفاده از TransferToUser
|
||||
if (section.CurrentAssignedUserId != item.UserId)
|
||||
{
|
||||
if (section.CurrentAssignedUserId > 0)
|
||||
{
|
||||
section.TransferToUser(section.CurrentAssignedUserId, item.UserId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.AssignToUser(item.UserId.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var newTaskSection = new TaskSection(task.Id, item.SkillId, item.UserId.Value);
|
||||
await _taskSectionRepository.CreateAsync(newTaskSection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
return OperationResult.Success();
|
||||
}
|
||||
|
||||
private async Task<OperationResult> SetTimeForProjectPhase(SetTimeProjectCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
private async Task<OperationResult> AssignProjectPhase(SetTimeProjectCommand request)
|
||||
{
|
||||
var phase = await _projectPhaseRepository.GetWithTasksAsync(request.Id);
|
||||
if (phase == null)
|
||||
if (phase is null)
|
||||
{
|
||||
return OperationResult.NotFound("فاز پروژه یافت نشد");
|
||||
return OperationResult.NotFound("<22><><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>");
|
||||
}
|
||||
|
||||
long? addedByUserId = _userId;
|
||||
// تخصیص در سطح فاز
|
||||
foreach (var item in request.SkillItems)
|
||||
{
|
||||
var skill = await _skillRepository.GetByIdAsync(item.SkillId);
|
||||
if (skill is null)
|
||||
{
|
||||
return OperationResult.NotFound($"مهارت با شناسه {item.SkillId} یافت نشد");
|
||||
}
|
||||
}
|
||||
|
||||
// تنظیم زمان برای تمام sections در تمام تسکهای این فاز
|
||||
// علامتگذاری که این فاز نسبت به parent متمایز است
|
||||
phase.MarkAsOverridden();
|
||||
|
||||
var skillItems = request.SkillItems.Where(x=>x.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)
|
||||
{
|
||||
var existingSection = phase.PhaseSections.FirstOrDefault(s => s.SkillId == item.SkillId);
|
||||
if (existingSection != null)
|
||||
{
|
||||
// اگر وجود داشت، فقط userId را بهروزرسانی کن
|
||||
existingSection.Update(item.UserId!.Value, item.SkillId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// اگر وجود نداشت، اضافه کن
|
||||
var newPhaseSection = new PhaseSection(phase.Id, item.UserId!.Value, item.SkillId);
|
||||
await _phaseSectionRepository.CreateAsync(newPhaseSection);
|
||||
}
|
||||
}
|
||||
|
||||
// cascade به تمام تسکها
|
||||
foreach (var task in phase.Tasks)
|
||||
{
|
||||
foreach (var section in task.Sections)
|
||||
// اگر CascadeToChildren true است یا تسک override ندارد
|
||||
if (request.CascadeToChildren || !task.HasAssignmentOverride)
|
||||
{
|
||||
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
|
||||
if (sectionItem != null)
|
||||
// حذف TaskSections که در validSkills نیستند
|
||||
var taskSectionsToRemove = task.Sections
|
||||
.Where(s => !validSkillIds.Contains(s.SkillId))
|
||||
.ToList();
|
||||
|
||||
foreach (var section in taskSectionsToRemove)
|
||||
{
|
||||
SetSectionTime(section, sectionItem, addedByUserId);
|
||||
task.RemoveSection(section.Id);
|
||||
}
|
||||
|
||||
foreach (var item in skillItems)
|
||||
{
|
||||
var section = task.Sections.FirstOrDefault(s => s.SkillId == item.SkillId);
|
||||
if (section != null)
|
||||
{
|
||||
// استفاده از TransferToUser
|
||||
if (section.CurrentAssignedUserId != item.UserId)
|
||||
{
|
||||
if (section.CurrentAssignedUserId > 0)
|
||||
{
|
||||
section.TransferToUser(section.CurrentAssignedUserId, item.UserId!.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.AssignToUser(item.UserId!.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var newTaskSection = new TaskSection(task.Id, item.SkillId, item.UserId!.Value);
|
||||
await _taskSectionRepository.CreateAsync(newTaskSection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
return OperationResult.Success();
|
||||
}
|
||||
|
||||
|
||||
private async Task<OperationResult> SetTimeForProjectTask(SetTimeProjectCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -116,21 +296,60 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
|
||||
long? addedByUserId = _userId;
|
||||
|
||||
// تنظیم زمان مستقیماً برای sections این تسک
|
||||
foreach (var section in task.Sections)
|
||||
var validSkills = request.SkillItems
|
||||
.Where(x=>x.UserId is > 0).ToList();
|
||||
|
||||
// حذف سکشنهایی که در 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)
|
||||
{
|
||||
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
|
||||
if (sectionItem != null)
|
||||
{
|
||||
SetSectionTime(section, sectionItem, addedByUserId);
|
||||
}
|
||||
task.RemoveSection(sectionToRemove.Id);
|
||||
}
|
||||
|
||||
foreach (var skillItem in validSkills)
|
||||
{
|
||||
var section = task.Sections.FirstOrDefault(s => s.SkillId == skillItem.SkillId);
|
||||
|
||||
if (!_userRepository.Exists(x=>x.Id == skillItem.UserId!.Value))
|
||||
{
|
||||
throw new BadRequestException("کاربر با شناسه یافت نشد.");
|
||||
}
|
||||
|
||||
if (section == null)
|
||||
{
|
||||
var taskSection = new TaskSection(task.Id,
|
||||
skillItem.SkillId, skillItem.UserId!.Value);
|
||||
|
||||
task.AddSection(taskSection);
|
||||
section = taskSection;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (section.CurrentAssignedUserId != skillItem.UserId)
|
||||
{
|
||||
if (section.CurrentAssignedUserId > 0)
|
||||
{
|
||||
section.TransferToUser(section.CurrentAssignedUserId, skillItem.UserId!.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.AssignToUser(skillItem.UserId!.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetSectionTime(section, skillItem, addedByUserId);
|
||||
|
||||
}
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return OperationResult.Success();
|
||||
}
|
||||
|
||||
private void SetSectionTime(TaskSection section, SetTimeProjectSectionItem sectionItem, long? addedByUserId)
|
||||
private void SetSectionTime(TaskSection section, SetTimeProjectSkillItem sectionItem, long? addedByUserId)
|
||||
{
|
||||
var initData = sectionItem.InitData;
|
||||
var initialTime = TimeSpan.FromHours(initData.Hours);
|
||||
@@ -147,7 +366,7 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
// افزودن زمانهای اضافی
|
||||
foreach (var additionalTime in sectionItem.AdditionalTime)
|
||||
{
|
||||
var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours);
|
||||
var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours).Add(TimeSpan.FromMinutes(additionalTime.Minutes));
|
||||
section.AddAdditionalTime(additionalTimeSpan, additionalTime.Description, addedByUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,15 @@ public class SetTimeProjectCommandValidator:AbstractValidator<SetTimeProjectComm
|
||||
.NotNull()
|
||||
.WithMessage("شناسه پروژه نمیتواند خالی باشد.");
|
||||
|
||||
RuleForEach(x => x.SectionItems)
|
||||
.SetValidator(command => new SetTimeProjectSectionItemValidator());
|
||||
|
||||
RuleFor(x => x.SectionItems)
|
||||
.Must(sectionItems => sectionItems.Any(si => si.InitData?.Hours > 0))
|
||||
.WithMessage("حداقل یکی از بخشها باید مقدار ساعت معتبری داشته باشد.");
|
||||
RuleForEach(x => x.SkillItems)
|
||||
.SetValidator(command => new SetTimeProjectSkillItemValidator());
|
||||
}
|
||||
}
|
||||
public class SetTimeProjectSectionItemValidator:AbstractValidator<SetTimeProjectSectionItem>
|
||||
public class SetTimeProjectSkillItemValidator:AbstractValidator<SetTimeProjectSkillItem>
|
||||
{
|
||||
public SetTimeProjectSectionItemValidator()
|
||||
public SetTimeProjectSkillItemValidator()
|
||||
{
|
||||
RuleFor(x=>x.SectionId)
|
||||
RuleFor(x=>x.SkillId)
|
||||
.NotEmpty()
|
||||
.NotNull()
|
||||
.WithMessage("شناسه بخش نمیتواند خالی باشد.");
|
||||
@@ -47,6 +43,18 @@ public class AdditionalTimeDataValidator: AbstractValidator<SetTimeSectionTime>
|
||||
.GreaterThanOrEqualTo(0)
|
||||
.WithMessage("ساعت نمیتواند منفی باشد.");
|
||||
|
||||
RuleFor(x => x.Hours)
|
||||
.LessThan(1_000)
|
||||
.WithMessage("ساعت باید کمتر از 1000 باشد.");
|
||||
|
||||
RuleFor(x => x.Minutes)
|
||||
.GreaterThanOrEqualTo(0)
|
||||
.WithMessage("دقیقه نمیتواند منفی باشد.");
|
||||
|
||||
RuleFor(x => x.Minutes)
|
||||
.LessThan(60)
|
||||
.WithMessage("دقیقه باید بین 0 تا 59 باشد.");
|
||||
|
||||
RuleFor(x=>x.Description)
|
||||
.MaximumLength(500)
|
||||
.WithMessage("توضیحات نمیتواند بیشتر از 500 کاراکتر باشد.");
|
||||
|
||||
@@ -2,9 +2,10 @@ using GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimePro
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.DTOs;
|
||||
|
||||
public class SetTimeProjectSectionItem
|
||||
public class SetTimeProjectSkillItem
|
||||
{
|
||||
public Guid SectionId { get; set; }
|
||||
public Guid SkillId { get; set; }
|
||||
public long? UserId { get; set; }
|
||||
public SetTimeSectionTime InitData { get; set; }
|
||||
public List<SetTimeSectionTime> AdditionalTime { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// درخواست جستجو در سراسر سلسلهمراتب پروژه (پروژه، فاز، تسک).
|
||||
/// نتایج با اطلاعات مسیر سلسلهمراتب برای پشتیبانی از ناوبری درخت در رابط کاربری بازگردانده میشود.
|
||||
/// </summary>
|
||||
public record GetProjectSearchQuery(
|
||||
string SearchQuery) : IBaseQuery<GetProjectSearchResponse>;
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Handler برای درخواست جستجوی سراسری در سلسلهمراتب پروژه.
|
||||
/// این handler در تمام سطحهای پروژه، فاز و تسک جستجو میکند و از تمام فیلدهای متنی (نام، توضیحات) استفاده میکند.
|
||||
/// همچنین در زیرمجموعههای هر سطح (ProjectSections، PhaseSections، TaskSections) جستجو میکند.
|
||||
/// </summary>
|
||||
public class GetProjectSearchQueryHandler : IBaseQueryHandler<GetProjectSearchQuery, GetProjectSearchResponse>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _context;
|
||||
private const int MaxResults = 50;
|
||||
|
||||
public GetProjectSearchQueryHandler(IProgramManagerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<GetProjectSearchResponse>> Handle(
|
||||
GetProjectSearchQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var searchQuery = request.SearchQuery.ToLower();
|
||||
var results = new List<ProjectHierarchySearchResultDto>();
|
||||
|
||||
// جستجو در پروژهها و 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 => r.Level)
|
||||
.ThenBy(r => r.Title)
|
||||
.Take(MaxResults)
|
||||
.ToList();
|
||||
|
||||
var response = new GetProjectSearchResponse(sortedResults);
|
||||
return OperationResult<GetProjectSearchResponse>.Success(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در جدول پروژهها (نام، توضیحات) و ProjectSections (نام مهارت، توضیحات اولیه)
|
||||
/// </summary>
|
||||
private async Task<List<ProjectHierarchySearchResultDto>> 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 = null,
|
||||
PhaseId = null
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در جدول فازهای پروژه (نام، توضیحات) و PhaseSections
|
||||
/// </summary>
|
||||
private async Task<List<ProjectHierarchySearchResultDto>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در جدول تسکهای پروژه (نام، توضیحات) و TaskSections (نام مهارت، توضیح اولیه، اطلاعات اضافی)
|
||||
/// </summary>
|
||||
private async Task<List<ProjectHierarchySearchResultDto>> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// اعتبارسنج برای درخواست جستجوی سراسری
|
||||
/// </summary>
|
||||
public class GetProjectSearchQueryValidator : AbstractValidator<GetProjectSearchQuery>
|
||||
{
|
||||
public GetProjectSearchQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.SearchQuery)
|
||||
.NotEmpty().WithMessage("متن جستجو نمیتواند خالی باشد.")
|
||||
.MinimumLength(2).WithMessage("متن جستجو باید حداقل 2 حرف باشد.")
|
||||
.MaximumLength(500).WithMessage("متن جستجو نمیتواند بیش از 500 حرف باشد.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// پوستهی پاسخ برای نتایج جستجوی سراسری
|
||||
/// </summary>
|
||||
public record GetProjectSearchResponse(
|
||||
List<ProjectHierarchySearchResultDto> Results);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// DTO برای نتایج جستجوی سراسری در سلسلهمراتب پروژه.
|
||||
/// حاوی اطلاعات کافی برای بازسازی مسیر سلسلهمراتب و بسط درخت در رابط کاربری است.
|
||||
/// </summary>
|
||||
public record ProjectHierarchySearchResultDto
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه آیتم (پروژه، فاز یا تسک)
|
||||
/// </summary>
|
||||
public Guid Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// نام/عنوان آیتم
|
||||
/// </summary>
|
||||
public string Title { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// سطح سلسلهمراتب این آیتم
|
||||
/// </summary>
|
||||
public ProjectHierarchyLevel Level { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پروژه - همیشه برای فاز و تسک پر شده است، برای پروژه با شناسه خود پر میشود
|
||||
/// </summary>
|
||||
public Guid? ProjectId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه فاز - فقط برای تسک پر شده است، برای پروژه و فاز خالی است
|
||||
/// </summary>
|
||||
public Guid? PhaseId { get; init; }
|
||||
}
|
||||
|
||||
@@ -53,67 +53,81 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
|
||||
.ToDictionaryAsync(x => 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<ProjectProgressHistoryDto>();
|
||||
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<ProjectProgressHistoryDto>();
|
||||
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<List<ProjectBoardListResponse>>.Success(result);
|
||||
}
|
||||
|
||||
@@ -3,21 +3,22 @@ using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
|
||||
|
||||
public record ProjectSetTimeDetailsQuery(Guid TaskId)
|
||||
public record ProjectSetTimeDetailsQuery(Guid Id, ProjectHierarchyLevel Level)
|
||||
: IBaseQuery<ProjectSetTimeResponse>;
|
||||
public record ProjectSetTimeResponse(
|
||||
List<ProjectSetTimeResponseSections> SectionItems,
|
||||
List<ProjectSetTimeResponseSkill> SkillItems,
|
||||
Guid Id,
|
||||
ProjectHierarchyLevel Level);
|
||||
|
||||
public record ProjectSetTimeResponseSections
|
||||
public record ProjectSetTimeResponseSkill
|
||||
{
|
||||
public Guid SkillId { get; init; }
|
||||
public string SkillName { get; init; }
|
||||
public string UserName { get; init; }
|
||||
public int InitialTime { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string UserFullName { get; init; }
|
||||
public int InitialHours { get; set; }
|
||||
public int InitialMinutes { get; set; }
|
||||
public string InitialDescription { get; set; }
|
||||
public int TotalEstimateTime { get; init; }
|
||||
public int TotalAdditionalTime { get; init; }
|
||||
public string InitCreationTime { get; init; }
|
||||
public List<ProjectSetTimeResponseSectionAdditionalTime> AdditionalTimes { get; init; }
|
||||
public Guid SectionId { get; set; }
|
||||
@@ -25,6 +26,8 @@ public record ProjectSetTimeResponseSections
|
||||
|
||||
public class ProjectSetTimeResponseSectionAdditionalTime
|
||||
{
|
||||
public int Time { get; init; }
|
||||
public int Hours { get; init; }
|
||||
public int Minutes { get; init; }
|
||||
public string Description { get; init; }
|
||||
public string CreationDate { get; set; }
|
||||
}
|
||||
|
||||
@@ -22,17 +22,30 @@ public class ProjectSetTimeDetailsQueryHandler
|
||||
|
||||
public async Task<OperationResult<ProjectSetTimeResponse>> Handle(ProjectSetTimeDetailsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return request.Level switch
|
||||
{
|
||||
ProjectHierarchyLevel.Task => await GetTaskSetTimeDetails(request.Id, request.Level, cancellationToken),
|
||||
ProjectHierarchyLevel.Phase => await GetPhaseSetTimeDetails(request.Id, request.Level, cancellationToken),
|
||||
ProjectHierarchyLevel.Project => await GetProjectSetTimeDetails(request.Id, request.Level, cancellationToken),
|
||||
_ => OperationResult<ProjectSetTimeResponse>.Failure("سطح معادل نامعتبر است")
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<OperationResult<ProjectSetTimeResponse>> GetTaskSetTimeDetails(Guid id, ProjectHierarchyLevel level,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await _context.ProjectTasks
|
||||
.Where(p => p.Id == request.TaskId)
|
||||
.Where(p => p.Id == id)
|
||||
.Include(x => x.Sections)
|
||||
.ThenInclude(x => x.AdditionalTimes).AsNoTracking()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (task == null)
|
||||
{
|
||||
return OperationResult<ProjectSetTimeResponse>.NotFound("Project not found");
|
||||
return OperationResult<ProjectSetTimeResponse>.NotFound("تسک یافت نشد");
|
||||
}
|
||||
|
||||
var userIds = task.Sections.Select(x => x.OriginalAssignedUserId)
|
||||
.Distinct().ToList();
|
||||
|
||||
@@ -40,40 +53,142 @@ public class ProjectSetTimeDetailsQueryHandler
|
||||
.Where(x => userIds.Contains(x.Id))
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
var skillIds = task.Sections.Select(x => x.SkillId)
|
||||
.Distinct().ToList();
|
||||
|
||||
var skills = await _context.Skills
|
||||
.Where(x => skillIds.Contains(x.Id))
|
||||
var skills = await _context.Skills
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var res = new ProjectSetTimeResponse(
|
||||
task.Sections.Select(ts =>
|
||||
skills.Select(skill =>
|
||||
{
|
||||
var section = task.Sections
|
||||
.FirstOrDefault(x => x.SkillId == skill.Id);
|
||||
var user = users.FirstOrDefault(x => x.Id == section?.OriginalAssignedUserId);
|
||||
return new ProjectSetTimeResponseSkill
|
||||
{
|
||||
var user = users.FirstOrDefault(x => x.Id == ts.OriginalAssignedUserId);
|
||||
var skill = skills.FirstOrDefault(x => x.Id == ts.SkillId);
|
||||
return new ProjectSetTimeResponseSections
|
||||
{
|
||||
AdditionalTimes = ts.AdditionalTimes
|
||||
.Select(x => new ProjectSetTimeResponseSectionAdditionalTime
|
||||
{
|
||||
Description = x.Reason ?? "",
|
||||
Time = (int)x.Hours.TotalHours
|
||||
}).ToList(),
|
||||
InitCreationTime = ts.CreationDate.ToFarsi(),
|
||||
SkillName = skill?.Name ?? "",
|
||||
TotalAdditionalTime = (int)ts.GetTotalAdditionalTime().TotalHours,
|
||||
TotalEstimateTime = (int)ts.FinalEstimatedHours.TotalHours,
|
||||
UserName = user?.UserName ?? "",
|
||||
SectionId = ts.Id,
|
||||
InitialDescription = ts.InitialDescription ?? "",
|
||||
InitialTime = (int)ts.InitialEstimatedHours.TotalHours
|
||||
};
|
||||
}).ToList(),
|
||||
task.Id,
|
||||
ProjectHierarchyLevel.Task);
|
||||
AdditionalTimes = section?.AdditionalTimes
|
||||
.Select(x => new ProjectSetTimeResponseSectionAdditionalTime
|
||||
{
|
||||
Description = x.Reason ?? "",
|
||||
Hours = (int)x.Hours.TotalHours,
|
||||
Minutes = x.Hours.Minutes,
|
||||
CreationDate = x.CreationDate.ToFarsi()
|
||||
}).OrderBy(x => x.CreationDate).ToList() ?? [],
|
||||
InitCreationTime = section?.CreationDate.ToFarsi() ?? "",
|
||||
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 = skill.Id,
|
||||
};
|
||||
}).OrderBy(x => x.SkillId).ToList(),
|
||||
task.Id,
|
||||
level);
|
||||
|
||||
return OperationResult<ProjectSetTimeResponse>.Success(res);
|
||||
}
|
||||
|
||||
private async Task<OperationResult<ProjectSetTimeResponse>> GetPhaseSetTimeDetails(Guid id, ProjectHierarchyLevel level,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var phase = await _context.ProjectPhases
|
||||
.Where(p => p.Id == id)
|
||||
.Include(x => x.PhaseSections).AsNoTracking()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (phase == null)
|
||||
{
|
||||
return OperationResult<ProjectSetTimeResponse>.NotFound("فاز یافت نشد");
|
||||
}
|
||||
|
||||
var userIds = phase.PhaseSections.Select(x => x.UserId)
|
||||
.Distinct().ToList();
|
||||
|
||||
var users = await _context.Users
|
||||
.Where(x => userIds.Contains(x.Id))
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var skills = await _context.Skills
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var res = new ProjectSetTimeResponse(
|
||||
skills.Select(skill =>
|
||||
{
|
||||
var section = phase.PhaseSections
|
||||
.FirstOrDefault(x => x.SkillId == skill.Id);
|
||||
var user = users.FirstOrDefault(x => x.Id == section?.UserId);
|
||||
return new ProjectSetTimeResponseSkill
|
||||
{
|
||||
AdditionalTimes = [],
|
||||
InitCreationTime = "",
|
||||
SkillName = skill.Name ?? "",
|
||||
UserFullName = user?.FullName ?? "",
|
||||
SectionId = section?.Id ?? Guid.Empty,
|
||||
InitialDescription = "",
|
||||
InitialHours = 0,
|
||||
InitialMinutes = 0,
|
||||
UserId = section?.UserId ?? 0,
|
||||
SkillId = skill.Id,
|
||||
};
|
||||
}).OrderBy(x => x.SkillId).ToList(),
|
||||
phase.Id,
|
||||
level);
|
||||
|
||||
return OperationResult<ProjectSetTimeResponse>.Success(res);
|
||||
}
|
||||
|
||||
private async Task<OperationResult<ProjectSetTimeResponse>> GetProjectSetTimeDetails(Guid id, ProjectHierarchyLevel level,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var project = await _context.Projects
|
||||
.Where(p => p.Id == id)
|
||||
.Include(x => x.ProjectSections).AsNoTracking()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (project == null)
|
||||
{
|
||||
return OperationResult<ProjectSetTimeResponse>.NotFound("پروژه یافت نشد");
|
||||
}
|
||||
|
||||
var userIds = project.ProjectSections.Select(x => x.UserId)
|
||||
.Distinct().ToList();
|
||||
|
||||
var users = await _context.Users
|
||||
.Where(x => userIds.Contains(x.Id))
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var skills = await _context.Skills
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var res = new ProjectSetTimeResponse(
|
||||
skills.Select(skill =>
|
||||
{
|
||||
var section = project.ProjectSections
|
||||
.FirstOrDefault(x => x.SkillId == skill.Id);
|
||||
var user = users.FirstOrDefault(x => x.Id == section?.UserId);
|
||||
return new ProjectSetTimeResponseSkill
|
||||
{
|
||||
AdditionalTimes = [],
|
||||
InitCreationTime = "",
|
||||
SkillName = skill.Name ?? "",
|
||||
UserFullName = user?.FullName ?? "",
|
||||
SectionId = section?.Id ?? Guid.Empty,
|
||||
InitialDescription = "",
|
||||
InitialHours = 0,
|
||||
InitialMinutes = 0,
|
||||
UserId = section?.UserId ?? 0,
|
||||
SkillId = skill.Id,
|
||||
};
|
||||
}).OrderBy(x => x.SkillId).ToList(),
|
||||
project.Id,
|
||||
level);
|
||||
|
||||
return OperationResult<ProjectSetTimeResponse>.Success(res);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
using FluentValidation;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
|
||||
|
||||
public class ProjectSetTimeDetailsQueryValidator:AbstractValidator<ProjectSetTimeDetailsQuery>
|
||||
public class ProjectSetTimeDetailsQueryValidator : AbstractValidator<ProjectSetTimeDetailsQuery>
|
||||
{
|
||||
public ProjectSetTimeDetailsQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.TaskId)
|
||||
RuleFor(x => x.Id)
|
||||
.NotEmpty()
|
||||
.WithMessage("شناسه پروژه نمیتواند خالی باشد.");
|
||||
.WithMessage("شناسه نمیتواند خالی باشد.");
|
||||
|
||||
RuleFor(x => x.Level)
|
||||
.IsInEnum()
|
||||
.WithMessage("سطح معادل نامعتبر است.");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -246,4 +246,9 @@ public class ProjectTask : ProjectHierarchyNode
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ClearTaskSections()
|
||||
{
|
||||
_sections.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ public interface ITaskSectionRepository: IRepository<Guid,TaskSection>
|
||||
|
||||
Task<List<TaskSection>> GetAssignedToUserAsync(long userId);
|
||||
Task<List<TaskSection>> GetActiveSectionsIncludeAllAsync(CancellationToken cancellationToken);
|
||||
Task<bool> HasUserAnyInProgressSectionAsync(long userId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public class ProjectPhaseRepository : RepositoryBase<Guid, ProjectPhase>, IProje
|
||||
public Task<ProjectPhase?> GetWithTasksAsync(Guid phaseId)
|
||||
{
|
||||
return _context.ProjectPhases
|
||||
.Include(x=>x.PhaseSections)
|
||||
.Include(p => p.Tasks)
|
||||
.ThenInclude(t => t.Sections)
|
||||
.ThenInclude(s => s.Skill)
|
||||
|
||||
@@ -45,4 +45,11 @@ public class TaskSectionRepository:RepositoryBase<Guid,TaskSection>,ITaskSection
|
||||
.Include(x => x.AdditionalTimes)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> HasUserAnyInProgressSectionAsync(long userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.TaskSections
|
||||
.AnyAsync(x => x.CurrentAssignedUserId == userId && x.Status == TaskSectionStatus.InProgress,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -17,6 +18,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 +42,15 @@ public class ProjectController : ProgramManagerBaseController
|
||||
return res;
|
||||
}
|
||||
|
||||
[HttpGet("search")]
|
||||
public async Task<ActionResult<OperationResult<GetProjectSearchResponse>>> Search(
|
||||
[FromQuery] string query)
|
||||
{
|
||||
var searchQuery = new GetProjectSearchQuery(query);
|
||||
var res = await _mediator.Send(searchQuery);
|
||||
return res;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<OperationResult>> Create([FromBody] CreateProjectCommand command)
|
||||
{
|
||||
@@ -147,4 +158,11 @@ public class ProjectController : ProgramManagerBaseController
|
||||
var res = await _mediator.Send(command);
|
||||
return res;
|
||||
}
|
||||
|
||||
[HttpPost("approve-completion")]
|
||||
public async Task<ActionResult<OperationResult>> ApproveTaskSectionCompletion([FromBody] ApproveTaskSectionCompletionCommand command)
|
||||
{
|
||||
var res = await _mediator.Send(command);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user