Files
Backend-Api/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/SetTimeProject/SetTimeProjectCommandHandler.cs

467 lines
20 KiB
C#
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Projects.DTOs;
using GozareshgirProgramManager.Domain._Common;
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;
public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCommand>
{
private readonly IProjectRepository _projectRepository;
private readonly IProjectPhaseRepository _projectPhaseRepository;
private readonly IProjectTaskRepository _projectTaskRepository;
private readonly IUnitOfWork _unitOfWork;
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,
IUserRepository userRepository, ISkillRepository skillRepository,
IPhaseSectionRepository phaseSectionRepository,
IProjectSectionRepository projectSectionRepository,
ITaskSectionRepository taskSectionRepository)
{
_projectRepository = projectRepository;
_projectPhaseRepository = projectPhaseRepository;
_projectTaskRepository = projectTaskRepository;
_unitOfWork = unitOfWork;
_userRepository = userRepository;
_skillRepository = skillRepository;
_phaseSectionRepository = phaseSectionRepository;
_projectSectionRepository = projectSectionRepository;
_taskSectionRepository = taskSectionRepository;
_userId = authHelper.GetCurrentUserId();
}
public async Task<OperationResult> Handle(SetTimeProjectCommand request, CancellationToken cancellationToken)
{
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:
return OperationResult.Failure("سطح پروژه نامعتبر است");
}
}
private async Task<OperationResult> AssignProject(SetTimeProjectCommand request)
{
var project = await _projectRepository.GetWithFullHierarchyAsync(request.Id);
if (project is null)
{
return OperationResult.NotFound("پروژه یافت نشد");
}
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} یافت نشد");
}
// بررسی و به‌روزرسانی یا اضافه کردن 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)
{
// اگر CascadeToChildren true است یا فاز override ندارد
if (request.CascadeToChildren || !phase.HasAssignmentOverride)
{
// حذف PhaseSections که در validSkills نیستند
var phaseSectionsToRemove = phase.PhaseSections
.Where(s => !validSkillIds.Contains(s.SkillId))
.ToList();
foreach (var section in phaseSectionsToRemove)
{
phase.RemovePhaseSection(section.SkillId);
}
// برای phase هم باید sectionها را به‌روزرسانی کنیم
foreach (var item in skillItems )
{
var existingSection = phase.PhaseSections.FirstOrDefault(s => s.SkillId == item.SkillId);
if (existingSection != null)
{
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();
return OperationResult.Success();
}
private async Task<OperationResult> AssignProjectPhase(SetTimeProjectCommand request)
{
var phase = await _projectPhaseRepository.GetWithTasksAsync(request.Id);
if (phase is null)
{
return OperationResult.NotFound("فاز پروژه یافت نشد");
}
// تخصیص در سطح فاز
foreach (var item in request.SkillItems)
{
var skill = await _skillRepository.GetByIdAsync(item.SkillId);
if (skill is null)
{
return OperationResult.NotFound($"مهارت با شناسه {item.SkillId} یافت نشد");
}
}
// علامت‌گذاری که این فاز نسبت به 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)
{
// اگر 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();
return OperationResult.Success();
}
private async Task<OperationResult> SetTimeForProjectTask(SetTimeProjectCommand request,
CancellationToken cancellationToken)
{
var task = await _projectTaskRepository.GetWithSectionsAsync(request.Id);
if (task == null)
{
return OperationResult.NotFound("تسک یافت نشد");
}
long? addedByUserId = _userId;
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)
{
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 ValidateTotalTimeNotLessThanSpent(TimeSpan newTotalTime, TimeSpan currentTotalSpent)
{
if (newTotalTime < currentTotalSpent)
{
throw new BadRequestException(
$"تایم کل سکشن نمی‌تواند کمتر از زمان مصرف شده ({currentTotalSpent.TotalHours:F2} ساعت) باشد");
}
}
private void SetSectionTime(TaskSection section, SetTimeProjectSkillItem sectionItem, long? addedByUserId)
{
var initData = sectionItem.InitData;
var initialTime = TimeSpan.FromHours(initData.Hours)
.Add(TimeSpan.FromMinutes(initData.Minutes));
if (initialTime <= TimeSpan.Zero)
{
return;
}
// تنظیم زمان اولیه
section.UpdateInitialEstimatedHours(initialTime, initData.Description);
// مدیریت هوشمند زمان‌های اضافی
var existingAdditionalTimes = section.AdditionalTimes.ToList();
var incomingAdditionalTimes = sectionItem.AdditionalTime ?? [];
var currentTotalSpent = section.GetTotalTimeSpent();
bool hasRealChange = false;
// حذف آیتم‌هایی که دیگر در لیست نیستند
foreach (var existingTime in existingAdditionalTimes)
{
var stillExists = incomingAdditionalTimes.Any(x => x.Id == existingTime.Id);
if (!stillExists)
{
section.RemoveAdditionalTime(existingTime.Id);
hasRealChange = true;
}
}
// ویرایش یا اضافه کردن آیتم‌های جدید
foreach (var additionalTime in incomingAdditionalTimes)
{
var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours)
.Add(TimeSpan.FromMinutes(additionalTime.Minutes));
if (additionalTimeSpan <= TimeSpan.Zero)
continue;
var existingAdditionalTime = existingAdditionalTimes.FirstOrDefault(x => x.Id == additionalTime.Id);
if (existingAdditionalTime != null)
{
// اگر آیتم با این ID وجود دارد، بررسی کن اگر تغییر کرده باشد
if (existingAdditionalTime.HasChanged(additionalTimeSpan, additionalTime.Description))
{
var newTotalTime = section.InitialEstimatedHours
.Add(existingAdditionalTimes
.Where(x => x.Id != existingAdditionalTime.Id)
.Aggregate(TimeSpan.Zero, (acc, x) => acc.Add(x.Hours))
.Add(additionalTimeSpan));
ValidateTotalTimeNotLessThanSpent(newTotalTime, currentTotalSpent);
// ویرایش بدون حذف و ایجاد دوباره
existingAdditionalTime.Update(additionalTimeSpan, additionalTime.Description);
hasRealChange = true;
}
}
else
{
// اگر ID نداشت یا ID جدید بود، اضافه کن
if (additionalTime.Id == null || additionalTime.Id == Guid.Empty)
{
var newTotalTime = section.FinalEstimatedHours.Add(additionalTimeSpan);
ValidateTotalTimeNotLessThanSpent(newTotalTime, currentTotalSpent);
section.AddAdditionalTime(additionalTimeSpan, additionalTime.Description, addedByUserId);
hasRealChange = true;
}
}
}
// تغییر status به Incomplete فقط اگر تغییری واقعی اعمال شده باشد و در وضعیتی غیر از ReadyToStart باشد
if (hasRealChange && section.Status != TaskSectionStatus.ReadyToStart)
{
// اگر سکشن درحال انجام است، باید متوقف شود قبل از تغییر status
if (section.Status == TaskSectionStatus.InProgress)
{
section.StopWork(TaskSectionStatus.Incomplete);
}
else
{
section.UpdateStatus(TaskSectionStatus.Incomplete);
}
}
}
// private void SetSectionTime(ProjectSection section, SetTimeProjectSectionItem sectionItem, long? addedByUserId)
// {
// var initData = sectionItem.InitData;
// var initialTime = TimeSpan.FromHours(initData.Hours);
//
// // تنظیم زمان اولیه
// section.UpdateInitialEstimatedHours(initialTime, initData.Description);
//
// section.ClearAdditionalTimes();
// // افزودن زمان‌های اضافی
// foreach (var additionalTime in sectionItem.AdditionalTime)
// {
// var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours);
// section.AddAdditionalTime(additionalTimeSpan, additionalTime.Description, addedByUserId);
// }
// }
}