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 { 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 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 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 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 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); // } // } }