|
|
|
|
@@ -3,6 +3,7 @@ using GozareshgirProgramManager.Application._Common.Models;
|
|
|
|
|
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
|
|
|
|
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
|
|
|
|
|
|
|
|
|
|
@@ -17,47 +18,47 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
|
|
|
|
|
|
|
|
|
public async Task<OperationResult<GetProjectsListResponse>> Handle(GetProjectsListQuery request, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
List<GetProjectListDto> projects;
|
|
|
|
|
var projects = new List<GetProjectDto>();
|
|
|
|
|
var phases = new List<GetPhaseDto>();
|
|
|
|
|
var tasks = new List<GetTaskDto>();
|
|
|
|
|
|
|
|
|
|
switch (request.HierarchyLevel)
|
|
|
|
|
{
|
|
|
|
|
case ProjectHierarchyLevel.Project:
|
|
|
|
|
projects = await GetProjects(request.ParentId, cancellationToken);
|
|
|
|
|
await SetSkillFlags(projects, cancellationToken);
|
|
|
|
|
break;
|
|
|
|
|
case ProjectHierarchyLevel.Phase:
|
|
|
|
|
projects = await GetPhases(request.ParentId, cancellationToken);
|
|
|
|
|
phases = await GetPhases(request.ParentId, cancellationToken);
|
|
|
|
|
await SetSkillFlags(phases, cancellationToken);
|
|
|
|
|
break;
|
|
|
|
|
case ProjectHierarchyLevel.Task:
|
|
|
|
|
projects = await GetTasks(request.ParentId, cancellationToken);
|
|
|
|
|
tasks = await GetTasks(request.ParentId, cancellationToken);
|
|
|
|
|
// Tasks don't need SetSkillFlags because they have Sections list
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return OperationResult<GetProjectsListResponse>.Failure("سطح سلسله مراتب نامعتبر است");
|
|
|
|
|
}
|
|
|
|
|
await SetSkillFlags(projects, cancellationToken);
|
|
|
|
|
|
|
|
|
|
var response = new GetProjectsListResponse(projects);
|
|
|
|
|
var response = new GetProjectsListResponse(projects, phases, tasks);
|
|
|
|
|
return OperationResult<GetProjectsListResponse>.Success(response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<GetProjectListDto>> GetProjects(Guid? parentId, CancellationToken cancellationToken)
|
|
|
|
|
private async Task<List<GetProjectDto>> GetProjects(Guid? parentId, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var query = _context.Projects.AsQueryable();
|
|
|
|
|
|
|
|
|
|
// پروژهها سطح بالا هستند و parentId ندارند، فقط در صورت null بودن parentId نمایش داده میشوند
|
|
|
|
|
if (parentId.HasValue)
|
|
|
|
|
{
|
|
|
|
|
return new List<GetProjectListDto>(); // پروژهها parent ندارند
|
|
|
|
|
return new List<GetProjectDto>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var projects = await query
|
|
|
|
|
var entities = await query
|
|
|
|
|
.OrderByDescending(p => p.CreationDate)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var result = new List<GetProjectListDto>();
|
|
|
|
|
|
|
|
|
|
foreach (var project in projects)
|
|
|
|
|
var result = new List<GetProjectDto>();
|
|
|
|
|
foreach (var project in entities)
|
|
|
|
|
{
|
|
|
|
|
var (percentage, totalTime) = await CalculateProjectPercentage(project, cancellationToken);
|
|
|
|
|
result.Add(new GetProjectListDto
|
|
|
|
|
result.Add(new GetProjectDto
|
|
|
|
|
{
|
|
|
|
|
Id = project.Id,
|
|
|
|
|
Name = project.Name,
|
|
|
|
|
@@ -68,28 +69,24 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
|
|
|
|
Minutes = totalTime.Minutes,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<GetProjectListDto>> GetPhases(Guid? parentId, CancellationToken cancellationToken)
|
|
|
|
|
private async Task<List<GetPhaseDto>> GetPhases(Guid? parentId, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var query = _context.ProjectPhases.AsQueryable();
|
|
|
|
|
|
|
|
|
|
if (parentId.HasValue)
|
|
|
|
|
{
|
|
|
|
|
query = query.Where(x => x.ProjectId == parentId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var phases = await query
|
|
|
|
|
var entities = await query
|
|
|
|
|
.OrderByDescending(p => p.CreationDate)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var result = new List<GetProjectListDto>();
|
|
|
|
|
|
|
|
|
|
foreach (var phase in phases)
|
|
|
|
|
var result = new List<GetPhaseDto>();
|
|
|
|
|
foreach (var phase in entities)
|
|
|
|
|
{
|
|
|
|
|
var (percentage, totalTime) = await CalculatePhasePercentage(phase, cancellationToken);
|
|
|
|
|
result.Add(new GetProjectListDto
|
|
|
|
|
result.Add(new GetPhaseDto
|
|
|
|
|
{
|
|
|
|
|
Id = phase.Id,
|
|
|
|
|
Name = phase.Name,
|
|
|
|
|
@@ -100,28 +97,87 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
|
|
|
|
Minutes = totalTime.Minutes,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<GetProjectListDto>> GetTasks(Guid? parentId, CancellationToken cancellationToken)
|
|
|
|
|
private async Task<List<GetTaskDto>> GetTasks(Guid? parentId, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var query = _context.ProjectTasks.AsQueryable();
|
|
|
|
|
|
|
|
|
|
if (parentId.HasValue)
|
|
|
|
|
{
|
|
|
|
|
query = query.Where(x => x.PhaseId == parentId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tasks = await query
|
|
|
|
|
var entities = await query
|
|
|
|
|
.OrderByDescending(t => t.CreationDate)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var result = new List<GetProjectListDto>();
|
|
|
|
|
var result = new List<GetTaskDto>();
|
|
|
|
|
// دریافت تمام Skills
|
|
|
|
|
var allSkills = await _context.Skills
|
|
|
|
|
.Select(s => new { s.Id, s.Name })
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
foreach (var task in tasks)
|
|
|
|
|
foreach (var task in entities)
|
|
|
|
|
{
|
|
|
|
|
var (percentage, totalTime) = await CalculateTaskPercentage(task, cancellationToken);
|
|
|
|
|
result.Add(new GetProjectListDto
|
|
|
|
|
var sections = await _context.TaskSections
|
|
|
|
|
.Include(s => s.Activities)
|
|
|
|
|
.Include(s => s.Skill)
|
|
|
|
|
.Where(s => s.TaskId == task.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
// جمعآوری تمام userId های مورد نیاز
|
|
|
|
|
var userIds = sections
|
|
|
|
|
.Where(s => s.CurrentAssignedUserId > 0)
|
|
|
|
|
.Select(s => s.CurrentAssignedUserId)
|
|
|
|
|
.Distinct()
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
// دریافت اطلاعات کاربران
|
|
|
|
|
var users = await _context.Users
|
|
|
|
|
.Where(u => userIds.Contains(u.Id))
|
|
|
|
|
.Select(u => new { u.Id, u.FullName })
|
|
|
|
|
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
|
|
|
|
|
|
|
|
|
|
// محاسبه SpentTime و RemainingTime
|
|
|
|
|
var spentTime = TimeSpan.FromTicks(sections.Sum(s => s.Activities.Sum(a => a.GetTimeSpent().Ticks)));
|
|
|
|
|
var remainingTime = totalTime - spentTime;
|
|
|
|
|
|
|
|
|
|
// ساخت section DTOs برای تمام Skills
|
|
|
|
|
var sectionDtos = allSkills.Select(skill =>
|
|
|
|
|
{
|
|
|
|
|
var section = sections.FirstOrDefault(s => s.SkillId == skill.Id);
|
|
|
|
|
|
|
|
|
|
if (section == null)
|
|
|
|
|
{
|
|
|
|
|
// اگر section وجود نداشت، یک DTO با وضعیت Unassigned برمیگردانیم
|
|
|
|
|
return new GetTaskSectionDto
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.Empty,
|
|
|
|
|
SkillName = skill.Name ?? string.Empty,
|
|
|
|
|
SpentTime = TimeSpan.Zero,
|
|
|
|
|
TotalTime = TimeSpan.Zero,
|
|
|
|
|
Percentage = 0,
|
|
|
|
|
UserFullName = string.Empty,
|
|
|
|
|
AssignmentStatus = AssignmentStatus.Unassigned
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// اگر section وجود داشت
|
|
|
|
|
return new GetTaskSectionDto
|
|
|
|
|
{
|
|
|
|
|
Id = section.Id,
|
|
|
|
|
SkillName = skill.Name ?? string.Empty,
|
|
|
|
|
SpentTime = TimeSpan.FromTicks(section.Activities.Sum(a => a.GetTimeSpent().Ticks)),
|
|
|
|
|
TotalTime = section.FinalEstimatedHours,
|
|
|
|
|
Percentage = (int)section.GetProgressPercentage(),
|
|
|
|
|
UserFullName = section.CurrentAssignedUserId > 0 && users.ContainsKey(section.CurrentAssignedUserId)
|
|
|
|
|
? users[section.CurrentAssignedUserId]
|
|
|
|
|
: string.Empty,
|
|
|
|
|
AssignmentStatus = GetAssignmentStatus(section)
|
|
|
|
|
};
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
result.Add(new GetTaskDto
|
|
|
|
|
{
|
|
|
|
|
Id = task.Id,
|
|
|
|
|
Name = task.Name,
|
|
|
|
|
@@ -129,167 +185,144 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
|
|
|
|
ParentId = task.PhaseId,
|
|
|
|
|
Percentage = percentage,
|
|
|
|
|
TotalHours = (int)totalTime.TotalHours,
|
|
|
|
|
Minutes = totalTime.Minutes
|
|
|
|
|
Minutes = totalTime.Minutes,
|
|
|
|
|
SpentTime = spentTime,
|
|
|
|
|
RemainingTime = remainingTime,
|
|
|
|
|
Sections = sectionDtos,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SetSkillFlags(List<GetProjectListDto> projects, CancellationToken cancellationToken)
|
|
|
|
|
private async Task SetSkillFlags<TItem>(List<TItem> items, CancellationToken cancellationToken) where TItem : GetProjectItemDto
|
|
|
|
|
{
|
|
|
|
|
if (!projects.Any())
|
|
|
|
|
if (!items.Any())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var projectIds = projects.Select(x => x.Id).ToList();
|
|
|
|
|
var hierarchyLevel = projects.First().Level;
|
|
|
|
|
|
|
|
|
|
var ids = items.Select(x => x.Id).ToList();
|
|
|
|
|
var hierarchyLevel = items.First().Level;
|
|
|
|
|
switch (hierarchyLevel)
|
|
|
|
|
{
|
|
|
|
|
case ProjectHierarchyLevel.Project:
|
|
|
|
|
await SetSkillFlagsForProjects(projects, projectIds, cancellationToken);
|
|
|
|
|
await SetSkillFlagsForProjects(items, ids, cancellationToken);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ProjectHierarchyLevel.Phase:
|
|
|
|
|
await SetSkillFlagsForPhases(projects, projectIds, cancellationToken);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ProjectHierarchyLevel.Task:
|
|
|
|
|
await SetSkillFlagsForTasks(projects, projectIds, cancellationToken);
|
|
|
|
|
await SetSkillFlagsForPhases(items, ids, cancellationToken);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SetSkillFlagsForProjects(List<GetProjectListDto> projects, List<Guid> projectIds, CancellationToken cancellationToken)
|
|
|
|
|
|
|
|
|
|
private async Task SetSkillFlagsForProjects<TItem>(List<TItem> items, List<Guid> projectIds, CancellationToken cancellationToken) where TItem : GetProjectItemDto
|
|
|
|
|
{
|
|
|
|
|
var projectSections = await _context.ProjectSections
|
|
|
|
|
.Include(x => x.Skill)
|
|
|
|
|
.Where(s => projectIds.Contains(s.ProjectId))
|
|
|
|
|
// For projects: gather all phases, then tasks, then sections
|
|
|
|
|
var phases = await _context.ProjectPhases
|
|
|
|
|
.Where(ph => projectIds.Contains(ph.ProjectId))
|
|
|
|
|
.Select(ph => ph.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var tasks = await _context.ProjectTasks
|
|
|
|
|
.Where(t => phases.Contains(t.PhaseId))
|
|
|
|
|
.Select(t => t.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var sections = await _context.TaskSections
|
|
|
|
|
.Include(s => s.Skill)
|
|
|
|
|
.Where(s => tasks.Contains(s.TaskId))
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
if (!projectSections.Any())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
foreach (var project in projects)
|
|
|
|
|
foreach (var item in items)
|
|
|
|
|
{
|
|
|
|
|
var sections = projectSections.Where(s => s.ProjectId == project.Id).ToList();
|
|
|
|
|
project.HasBackend = sections.Any(x => x.Skill?.Name == "Backend");
|
|
|
|
|
project.HasFront = sections.Any(x => x.Skill?.Name == "Frontend");
|
|
|
|
|
project.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design");
|
|
|
|
|
var relatedPhases = phases; // used for filtering tasks by project
|
|
|
|
|
var relatedTasks = await _context.ProjectTasks
|
|
|
|
|
.Where(t => t.PhaseId != Guid.Empty && relatedPhases.Contains(t.PhaseId))
|
|
|
|
|
.Select(t => t.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var itemSections = sections.Where(s => relatedTasks.Contains(s.TaskId));
|
|
|
|
|
item.Backend = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Backend"));
|
|
|
|
|
item.Front = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Frontend"));
|
|
|
|
|
item.Design = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "UI/UX Design"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SetSkillFlagsForPhases(List<GetProjectListDto> projects, List<Guid> phaseIds, CancellationToken cancellationToken)
|
|
|
|
|
private async Task SetSkillFlagsForPhases<TItem>(List<TItem> items, List<Guid> phaseIds, CancellationToken cancellationToken) where TItem : GetProjectItemDto
|
|
|
|
|
{
|
|
|
|
|
var phaseSections = await _context.PhaseSections
|
|
|
|
|
.Include(x => x.Skill)
|
|
|
|
|
.Where(s => phaseIds.Contains(s.PhaseId))
|
|
|
|
|
// For phases: gather tasks, then sections
|
|
|
|
|
var tasks = await _context.ProjectTasks
|
|
|
|
|
.Where(t => phaseIds.Contains(t.PhaseId))
|
|
|
|
|
.Select(t => t.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var sections = await _context.TaskSections
|
|
|
|
|
.Include(s => s.Skill)
|
|
|
|
|
.Where(s => tasks.Contains(s.TaskId))
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
if (!phaseSections.Any())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
foreach (var phase in projects)
|
|
|
|
|
foreach (var item in items)
|
|
|
|
|
{
|
|
|
|
|
var sections = phaseSections.Where(s => s.PhaseId == phase.Id).ToList();
|
|
|
|
|
phase.HasBackend = sections.Any(x => x.Skill?.Name == "Backend");
|
|
|
|
|
phase.HasFront = sections.Any(x => x.Skill?.Name == "Frontend");
|
|
|
|
|
phase.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SetSkillFlagsForTasks(List<GetProjectListDto> projects, List<Guid> taskIds, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var taskSections = await _context.TaskSections
|
|
|
|
|
.Include(x => x.Skill)
|
|
|
|
|
.Where(s => taskIds.Contains(s.TaskId))
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
if (!taskSections.Any())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
foreach (var task in projects)
|
|
|
|
|
{
|
|
|
|
|
var sections = taskSections.Where(s => s.TaskId == task.Id).ToList();
|
|
|
|
|
task.HasBackend = sections.Any(x => x.Skill?.Name == "Backend");
|
|
|
|
|
task.HasFront = sections.Any(x => x.Skill?.Name == "Frontend");
|
|
|
|
|
task.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design");
|
|
|
|
|
// Filter tasks for this phase
|
|
|
|
|
var phaseTaskIds = await _context.ProjectTasks
|
|
|
|
|
.Where(t => t.PhaseId == item.Id)
|
|
|
|
|
.Select(t => t.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
var itemSections = sections.Where(s => phaseTaskIds.Contains(s.TaskId));
|
|
|
|
|
item.Backend = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Backend"));
|
|
|
|
|
item.Front = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Frontend"));
|
|
|
|
|
item.Design = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "UI/UX Design"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<(int Percentage, TimeSpan TotalTime)> CalculateProjectPercentage(Project project, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
// گرفتن تمام فازهای پروژه
|
|
|
|
|
var phases = await _context.ProjectPhases
|
|
|
|
|
.Where(ph => ph.ProjectId == project.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
if (!phases.Any())
|
|
|
|
|
return (0, TimeSpan.Zero);
|
|
|
|
|
|
|
|
|
|
// محاسبه درصد هر فاز و میانگینگیری
|
|
|
|
|
var phasePercentages = new List<int>();
|
|
|
|
|
var totalTime = TimeSpan.Zero;
|
|
|
|
|
|
|
|
|
|
foreach (var phase in phases)
|
|
|
|
|
{
|
|
|
|
|
var (phasePercentage, phaseTime) = await CalculatePhasePercentage(phase, cancellationToken);
|
|
|
|
|
phasePercentages.Add(phasePercentage);
|
|
|
|
|
totalTime += phaseTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var averagePercentage = phasePercentages.Any() ? (int)phasePercentages.Average() : 0;
|
|
|
|
|
return (averagePercentage, totalTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<(int Percentage, TimeSpan TotalTime)> CalculatePhasePercentage(ProjectPhase phase, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
// گرفتن تمام تسکهای فاز
|
|
|
|
|
var tasks = await _context.ProjectTasks
|
|
|
|
|
.Where(t => t.PhaseId == phase.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
if (!tasks.Any())
|
|
|
|
|
return (0, TimeSpan.Zero);
|
|
|
|
|
|
|
|
|
|
// محاسبه درصد هر تسک و میانگینگیری
|
|
|
|
|
var taskPercentages = new List<int>();
|
|
|
|
|
var totalTime = TimeSpan.Zero;
|
|
|
|
|
|
|
|
|
|
foreach (var task in tasks)
|
|
|
|
|
{
|
|
|
|
|
var (taskPercentage, taskTime) = await CalculateTaskPercentage(task, cancellationToken);
|
|
|
|
|
taskPercentages.Add(taskPercentage);
|
|
|
|
|
totalTime += taskTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var averagePercentage = taskPercentages.Any() ? (int)taskPercentages.Average() : 0;
|
|
|
|
|
return (averagePercentage, totalTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<(int Percentage, TimeSpan TotalTime)> CalculateTaskPercentage(ProjectTask task, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
// گرفتن تمام سکشنهای تسک با activities
|
|
|
|
|
var sections = await _context.TaskSections
|
|
|
|
|
.Include(s => s.Activities)
|
|
|
|
|
.Include(x=>x.AdditionalTimes)
|
|
|
|
|
.Where(s => s.TaskId == task.Id)
|
|
|
|
|
.ToListAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
if (!sections.Any())
|
|
|
|
|
return (0, TimeSpan.Zero);
|
|
|
|
|
|
|
|
|
|
// محاسبه درصد هر سکشن و میانگینگیری
|
|
|
|
|
var sectionPercentages = new List<int>();
|
|
|
|
|
var totalTime = TimeSpan.Zero;
|
|
|
|
|
|
|
|
|
|
foreach (var section in sections)
|
|
|
|
|
{
|
|
|
|
|
var (sectionPercentage, sectionTime) = CalculateSectionPercentage(section);
|
|
|
|
|
sectionPercentages.Add(sectionPercentage);
|
|
|
|
|
totalTime += sectionTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var averagePercentage = sectionPercentages.Any() ? (int)sectionPercentages.Average() : 0;
|
|
|
|
|
return (averagePercentage, totalTime);
|
|
|
|
|
}
|
|
|
|
|
@@ -298,5 +331,29 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
|
|
|
|
{
|
|
|
|
|
return ((int)section.GetProgressPercentage(),section.FinalEstimatedHours);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static AssignmentStatus GetAssignmentStatus(TaskSection? section)
|
|
|
|
|
{
|
|
|
|
|
// تعیین تکلیف نشده: section وجود ندارد
|
|
|
|
|
if (section == null)
|
|
|
|
|
return AssignmentStatus.Unassigned;
|
|
|
|
|
|
|
|
|
|
// بررسی وجود user
|
|
|
|
|
bool hasUser = section.CurrentAssignedUserId > 0;
|
|
|
|
|
|
|
|
|
|
// بررسی وجود time (InitialEstimatedHours بزرگتر از صفر باشد)
|
|
|
|
|
bool hasTime = section.InitialEstimatedHours > TimeSpan.Zero;
|
|
|
|
|
|
|
|
|
|
// تعیین تکلیف شده: هم user و هم time تعیین شده
|
|
|
|
|
if (hasUser && hasTime)
|
|
|
|
|
return AssignmentStatus.Assigned;
|
|
|
|
|
|
|
|
|
|
// فقط کاربر تعیین شده: user دارد ولی time ندارد
|
|
|
|
|
if (hasUser && !hasTime)
|
|
|
|
|
return AssignmentStatus.UserOnly;
|
|
|
|
|
|
|
|
|
|
// تعیین تکلیف نشده: نه user دارد نه time
|
|
|
|
|
return AssignmentStatus.Unassigned;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|