From 4193f77f5d31c108d7ca9972ad96a7ca8b714130 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Fri, 7 Nov 2025 13:03:16 -0600 Subject: [PATCH 01/12] SubTaskStatusChange --- Web.Api/Controllers/TaskController.cs | 173 +++++++++++++++--- Web.Api/Dto/Request/TaskCreateDto.cs | 2 + Web.Api/Dto/Response/TaskDto.cs | 5 +- Web.Api/Persistence/Models/TaskItem.cs | 2 - .../Persistence/Repositories/TaskItemRepo.cs | 6 + 5 files changed, 163 insertions(+), 25 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index 43f341b..fe99989 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -25,13 +25,15 @@ public class TaskController : ControllerBase [HttpGet("{taskId}", Name = "GetTaskById")] public async Task> GetTaskById([FromHeader] Guid userId, Guid taskId) { - if(!await _unitOfWork.User.IsUserInDbAsync(userId)) { + if (!await _unitOfWork.User.IsUserInDbAsync(userId)) + { return StatusCode(403); } - TaskItem? taskItem = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskId, userId); - if (taskItem is null) { - return NotFound(taskId); + TaskItem? taskItem = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskId, userId); + if (taskItem is null) + { + return NotFound(taskId); } @@ -41,8 +43,10 @@ public async Task> GetTaskById([FromHeader] Guid userId, G Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, + CreatedDate = taskItem.CreatedDate, CreatedUserId = taskItem.CreatedUserId, + Notes = taskItem.TaskItemNotes.Select //within the TaskDto create a new List of Notes that grabs TaskItemNotes and set their properties (note => new NoteDto //create new instance of NoteDto { @@ -68,9 +72,20 @@ public async Task> GetTaskById([FromHeader] Guid userId, G [HttpPost(Name = "CreateTask")] public async Task> CreateTask([FromHeader] Guid userId, TaskCreateDto taskCreatedDto) { - if(!await _unitOfWork.User.IsUserInDbAsync(userId)) { + if (!await _unitOfWork.User.IsUserInDbAsync(userId)) + { return StatusCode(403); - } + } + + //TaskItem? parentTask = null; + if (taskCreatedDto.ParentTaskId != Guid.Empty) + { + TaskItem? parentTask = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskCreatedDto.ParentTaskId, userId); + if (parentTask is null) + { + return NotFound(taskCreatedDto.ParentTaskId); + } + } //calls the TaskItem prop and set the task created dto to its prop //Request DTO @@ -80,15 +95,17 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta { Title = taskCreatedDto.Title, Priority = taskCreatedDto.Priority, + CreatedDate = DateTime.Now, CreatedUserId = userId, //set the UserId which is given by the user from the header TaskItemStatusHistories = [ - new TaskItemStatusHistory() { - StatusId = _statusChange.PendingId, - CreatedDate = DateTime.Now, - CreatedUserId = userId + new TaskItemStatusHistory() { + StatusId = _statusChange.PendingId, + CreatedDate = DateTime.Now, + CreatedUserId = userId } ] + }; if (taskCreatedDto.DueDate == null) @@ -99,10 +116,26 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta taskCreation.DueDate = taskCreatedDto.DueDate.Value; //enetered value } + await _unitOfWork.TaskItem.CreateTaskAsync(taskCreation); //UofW takes the TaskItem class and calls the CreateTask method from the TaskItemRepo await _unitOfWork.SaveChangesAsync(); //UofW calls the SaveChanges method taskCreation = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskCreation.Id, userId); + //SubTask creation if ParentId is provided + if (taskCreatedDto.ParentTaskId != Guid.Empty) + { + SubTask subTask = new SubTask + { + TaskItemId = taskCreatedDto.ParentTaskId, + SubTaskItemId = taskCreation.Id, + CreatedDate = DateTime.Now, + CreatedUserId = userId + }; + taskCreation.SubTaskSubTaskItems.Add(subTask); + } + await _unitOfWork.SaveChangesAsync(); + + //Response DTO //create a new instance of TaskDto //calls the TaskDto prop and call the taskCreation and set the prop for user view @@ -132,6 +165,7 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta Code = history.Status.Code, }).FirstOrDefault(), + CreatedDate = taskCreation.CreatedDate, CreatedUserId = taskCreation.CreatedUserId }; @@ -143,13 +177,15 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta public async Task> CreateNote([FromHeader] Guid userId, Guid taskId, NoteCreateDto noteCreateDto) { - if(!await _unitOfWork.User.IsUserInDbAsync(userId)) { + if (!await _unitOfWork.User.IsUserInDbAsync(userId)) + { return StatusCode(403); } - TaskItem? taskItem = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskId, userId); - if (taskItem is null) { - return NotFound(taskId); + TaskItem? taskItem = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskId, userId); + if (taskItem is null) + { + return NotFound(taskId); } TaskItemNote noteCreation = new TaskItemNote @@ -186,17 +222,19 @@ public Task>> GetAllNotes([FromHeader] Guid userId, G public async Task> DeleteNote([FromHeader] Guid userId, Guid taskId, Guid noteId) { - if(!await _unitOfWork.User.IsUserInDbAsync(userId)) { + if (!await _unitOfWork.User.IsUserInDbAsync(userId)) + { return StatusCode(403); } - TaskItem? taskItem = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskId, userId); - if (taskItem is null) { - return NotFound(taskId); + TaskItem? taskItem = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskId, userId); + if (taskItem is null) + { + return NotFound(taskId); } TaskItemNote? note = taskItem.TaskItemNotes.SingleOrDefault(n => n.Id == noteId); - if(note is null) + if (note is null) { return NotFound(noteId); } @@ -219,7 +257,8 @@ public async Task> DeleteNote([FromHeader] Guid userId, Gu [HttpPost("{taskId}/status-change/complete", Name = "StatusChangeComplete")] public async Task> StatusChangeComplete([FromHeader] Guid userId, Guid taskId) { - if(!await _unitOfWork.User.IsUserInDbAsync(userId)) { + if (!await _unitOfWork.User.IsUserInDbAsync(userId)) + { return StatusCode(403); } @@ -229,6 +268,25 @@ public async Task> StatusChangeComplete([FromHeader] Guid return NotFound(taskId); } + //var incompleteSubTask = taskItem.SubTaskSubTaskItems.Any(st => st.SubTaskItem.TaskItemStatusHistories + // .OrderByDescending(th => th.CreatedDate) + // .FirstOrDefault().Status.Equals(_statusChange.CompleteId)); + + //if (!incompleteSubTask) + //{ + // return BadRequest("Cannot complete task with incomplete sub-tasks."); + //} + + + var incompleteSubTask = taskItem.SubTaskSubTaskItems.Where(st => st.SubTaskItem.TaskItemStatusHistories + .OrderByDescending(th => th.CreatedDate) + .FirstOrDefault().StatusId == _statusChange.PendingId) + .ToList(); + + if (incompleteSubTask != null) + { + return BadRequest("Cannot complete task with incomplete sub-tasks."); + } TaskItemStatusHistory newTaskStatus = new TaskItemStatusHistory { @@ -282,7 +340,8 @@ public async Task> StatusChangePending([FromHeader] Guid u [HttpPut("{taskId}", Name = "EditTask")] public async Task> EditTask([FromHeader] Guid userId, Guid taskId, TaskDto updateTaskDto) { - if(!await _unitOfWork.User.IsUserInDbAsync(userId)) { + if (!await _unitOfWork.User.IsUserInDbAsync(userId)) + { return StatusCode(403); } @@ -292,6 +351,28 @@ public async Task> EditTask([FromHeader] Guid userId, Guid return NotFound(taskId); } + //TaskItem? parentTask = await _unitOfWork.TaskItem.GetTaskByIdAsync(updateTaskDto.ParentTaskId, userId); + //if (parentTask is null) + //{ + // return NotFound(updateTaskDto.ParentTaskId); + //} + + if (updateTaskDto.ParentTaskId != Guid.Empty) + { + SubTask? subTask = new SubTask + { + TaskItemId = updateTaskDto.ParentTaskId, + SubTaskItemId = taskItem.Id, + CreatedDate = DateTime.Now, + CreatedUserId = userId + }; + + taskItem.SubTaskSubTaskItems.Add(subTask); + } + await _unitOfWork.SaveChangesAsync(); + + + //update only provided property fields if (updateTaskDto.Title != null && updateTaskDto.DueDate.HasValue && updateTaskDto.Priority != 0) @@ -300,15 +381,16 @@ public async Task> EditTask([FromHeader] Guid userId, Guid taskItem.DueDate = updateTaskDto.DueDate.Value; taskItem.Priority = updateTaskDto.Priority; } - await _unitOfWork.SaveChangesAsync(); + TaskDto editTaskResult = new TaskDto { Id = taskItem.Id, Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, + ParentTaskId = updateTaskDto.ParentTaskId, Notes = taskItem.TaskItemNotes.Select(n => new NoteDto { @@ -332,5 +414,52 @@ public async Task> EditTask([FromHeader] Guid userId, Guid }; return Ok(editTaskResult); } + + [HttpDelete("{taskId}", Name = "DeleteTaskById")] + public async Task> DeleteTaskById([FromHeader] Guid userId, Guid taskId) + { + if (!await _unitOfWork.User.IsUserInDbAsync(userId)) + { + return StatusCode(403); + } + TaskItem? taskItem = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskId, userId); + if (taskItem is null) + { + return NotFound(taskId); + } + + + await _unitOfWork.SaveChangesAsync(); + _unitOfWork.TaskItem.DeleteTask(taskItem); + + TaskDto deleteTaskResult = new TaskDto + { + Id = taskId, + Title = taskItem.Title, + DueDate = taskItem.DueDate, + Priority = taskItem.Priority, + Notes = taskItem.TaskItemNotes.Select(n => new NoteDto + { + Id = n.Id, + TaskItemId = n.TaskItemId, + Note = n.Note, + CreatedDate = n.CreatedDate, + CreatedUser = n.CreatedUserId + }).ToList(), + CurrentStatus = taskItem.TaskItemStatusHistories.OrderByDescending(rank => rank.CreatedDate) + .Select(history => new StatusDto + { + Id = history.Id, + Name = history.Status.Name, + Code = history.Status.Code, + }).FirstOrDefault(), + CreatedDate = taskItem.CreatedDate, + CreatedUserId = taskItem.CreatedUserId + }; + + return Ok(deleteTaskResult); + + } + } } diff --git a/Web.Api/Dto/Request/TaskCreateDto.cs b/Web.Api/Dto/Request/TaskCreateDto.cs index 14ddf46..46fe5ba 100644 --- a/Web.Api/Dto/Request/TaskCreateDto.cs +++ b/Web.Api/Dto/Request/TaskCreateDto.cs @@ -5,5 +5,7 @@ public class TaskCreateDto public string Title { get; set; } public DateTime? DueDate { get; set; } public int Priority { get; set; } + public Guid ParentTaskId { get; set; } + } } diff --git a/Web.Api/Dto/Response/TaskDto.cs b/Web.Api/Dto/Response/TaskDto.cs index 62561a3..4ddd865 100644 --- a/Web.Api/Dto/Response/TaskDto.cs +++ b/Web.Api/Dto/Response/TaskDto.cs @@ -1,4 +1,6 @@ -namespace Web.Api.Dto.Response +using Web.Api.Persistence.Models; + +namespace Web.Api.Dto.Response { public class TaskDto { @@ -6,6 +8,7 @@ public class TaskDto public string Title { get; set; } public DateTime? DueDate { get; set; } public int Priority { get; set; } + public Guid ParentTaskId { get; set; } public List Notes { get; set; } = []; public StatusDto? CurrentStatus { get; set; } public DateTime CreatedDate { get; set; } diff --git a/Web.Api/Persistence/Models/TaskItem.cs b/Web.Api/Persistence/Models/TaskItem.cs index a8c59e2..f3e2330 100644 --- a/Web.Api/Persistence/Models/TaskItem.cs +++ b/Web.Api/Persistence/Models/TaskItem.cs @@ -10,9 +10,7 @@ public partial class TaskItem public string Title { get; set; } = null!; public DateTime? DueDate { get; set; } - public int Priority { get; set; } - public DateTime CreatedDate { get; set; } public Guid CreatedUserId { get; set; } diff --git a/Web.Api/Persistence/Repositories/TaskItemRepo.cs b/Web.Api/Persistence/Repositories/TaskItemRepo.cs index 38199e7..29e0f97 100644 --- a/Web.Api/Persistence/Repositories/TaskItemRepo.cs +++ b/Web.Api/Persistence/Repositories/TaskItemRepo.cs @@ -55,5 +55,11 @@ public void DeleteNote(TaskItemNote taskItemNote) { _context.Remove(taskItemNote); } + + public void DeleteTask(TaskItem taskItem) + { + _context.Remove(taskItem); + } + } } From c5379c7334617c2f73220f0535b9607cedb8ae07 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Sun, 9 Nov 2025 20:14:21 -0600 Subject: [PATCH 02/12] status change hsitory --- Web.Api/Controllers/TaskController.cs | 45 +++++++++++-------- .../Persistence/Repositories/TaskItemRepo.cs | 13 ++++++ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index fe99989..a1b8a99 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -35,7 +35,8 @@ public async Task> GetTaskById([FromHeader] Guid userId, G { return NotFound(taskId); } - + + //Guid? parentId = taskItem.SubTaskTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty; TaskDto? taskDetail = new TaskDto //create a new instance of TaskDto and set their properties { @@ -43,6 +44,9 @@ public async Task> GetTaskById([FromHeader] Guid userId, G Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, + //ParentTaskId = taskItem.SubTaskTaskItems?.FirstOrDefault()?.SubTaskItemId ?? Guid.Empty, + ParentTaskId = taskItem.SubTaskTaskItems?.FirstOrDefault()?.TaskItemId?? Guid.Empty, + CreatedDate = taskItem.CreatedDate, CreatedUserId = taskItem.CreatedUserId, @@ -140,12 +144,15 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta //create a new instance of TaskDto //calls the TaskDto prop and call the taskCreation and set the prop for user view //return the result of the tasks created + Guid? parentId = taskCreation.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty; + TaskDto creationResult = new TaskDto() { Id = taskCreation.Id, Title = taskCreation.Title, DueDate = taskCreation.DueDate, Priority = taskCreation.Priority, + ParentTaskId = (Guid)parentId, Notes = taskCreation.TaskItemNotes.Select (note => new NoteDto @@ -268,27 +275,27 @@ public async Task> StatusChangeComplete([FromHeader] Guid return NotFound(taskId); } - //var incompleteSubTask = taskItem.SubTaskSubTaskItems.Any(st => st.SubTaskItem.TaskItemStatusHistories - // .OrderByDescending(th => th.CreatedDate) - // .FirstOrDefault().Status.Equals(_statusChange.CompleteId)); - - //if (!incompleteSubTask) - //{ - // return BadRequest("Cannot complete task with incomplete sub-tasks."); - //} - - - var incompleteSubTask = taskItem.SubTaskSubTaskItems.Where(st => st.SubTaskItem.TaskItemStatusHistories - .OrderByDescending(th => th.CreatedDate) - .FirstOrDefault().StatusId == _statusChange.PendingId) - .ToList(); - - if (incompleteSubTask != null) + // Prevent completing a parent task when any child sub-task is not complete. + if (taskItem.SubTaskTaskItems != null && taskItem.SubTaskTaskItems.Any()) { - return BadRequest("Cannot complete task with incomplete sub-tasks."); + bool hasIncompletedChild = taskItem.SubTaskTaskItems + .Select(st => st.SubTaskItem) + .Any(child => + { + TaskItemStatusHistory? latest = child.TaskItemStatusHistories + .OrderByDescending(s => s.CreatedDate) + .FirstOrDefault(); + return latest == null || latest.StatusId != _statusChange.CompleteId; + }); + + if (hasIncompletedChild) + { + return BadRequest("Cannot complete parent task with incomplete child sub-tasks."); + } } - TaskItemStatusHistory newTaskStatus = new TaskItemStatusHistory + + TaskItemStatusHistory newTaskStatus = new TaskItemStatusHistory { TaskItemId = taskItem.Id, StatusId = _statusChange.CompleteId, diff --git a/Web.Api/Persistence/Repositories/TaskItemRepo.cs b/Web.Api/Persistence/Repositories/TaskItemRepo.cs index 29e0f97..ee01473 100644 --- a/Web.Api/Persistence/Repositories/TaskItemRepo.cs +++ b/Web.Api/Persistence/Repositories/TaskItemRepo.cs @@ -27,8 +27,21 @@ public TaskItemRepo(TaskManagerAppDBContext context) { return await _context.TaskItems.Include(item => item.TaskItemNotes).Include(history => history.TaskItemStatusHistories) .ThenInclude(stat => stat.Status).SingleOrDefaultAsync(ti => ti.Id == taskId && ti.CreatedUserId == userId); + + return await _context.TaskItems + .Include(item => item.TaskItemNotes) + .Include(item => item.TaskItemStatusHistories) + .ThenInclude(stat => stat.Status) + .Include(t => t.SubTaskTaskItems) + .ThenInclude(st => st.SubTaskItem) + .ThenInclude(si => si.TaskItemStatusHistories) + .ThenInclude(h => h.Status) + .SingleOrDefaultAsync(ti => ti.Id == taskId && ti.CreatedUserId == userId); + } + + public async Task CreateTaskAsync(TaskItem taskItem) { From d63c4f45cd2f651c0326565c1fb5d6b314ccae37 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Sun, 9 Nov 2025 20:48:42 -0600 Subject: [PATCH 03/12] Return ParentTaskId if exists --- Web.Api/Controllers/TaskController.cs | 37 ++++++++----------- .../Persistence/Repositories/TaskItemRepo.cs | 23 ++++-------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index a1b8a99..9aa4a38 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -35,8 +35,6 @@ public async Task> GetTaskById([FromHeader] Guid userId, G { return NotFound(taskId); } - - //Guid? parentId = taskItem.SubTaskTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty; TaskDto? taskDetail = new TaskDto //create a new instance of TaskDto and set their properties { @@ -44,8 +42,7 @@ public async Task> GetTaskById([FromHeader] Guid userId, G Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, - //ParentTaskId = taskItem.SubTaskTaskItems?.FirstOrDefault()?.SubTaskItemId ?? Guid.Empty, - ParentTaskId = taskItem.SubTaskTaskItems?.FirstOrDefault()?.TaskItemId?? Guid.Empty, + ParentTaskId = taskItem.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty, CreatedDate = taskItem.CreatedDate, @@ -99,7 +96,7 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta { Title = taskCreatedDto.Title, Priority = taskCreatedDto.Priority, - + CreatedDate = DateTime.Now, CreatedUserId = userId, //set the UserId which is given by the user from the header TaskItemStatusHistories = [ @@ -144,7 +141,6 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta //create a new instance of TaskDto //calls the TaskDto prop and call the taskCreation and set the prop for user view //return the result of the tasks created - Guid? parentId = taskCreation.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty; TaskDto creationResult = new TaskDto() { @@ -152,7 +148,7 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta Title = taskCreation.Title, DueDate = taskCreation.DueDate, Priority = taskCreation.Priority, - ParentTaskId = (Guid)parentId, + ParentTaskId = taskCreation.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty, Notes = taskCreation.TaskItemNotes.Select (note => new NoteDto @@ -282,10 +278,10 @@ public async Task> StatusChangeComplete([FromHeader] Guid .Select(st => st.SubTaskItem) .Any(child => { - TaskItemStatusHistory? latest = child.TaskItemStatusHistories + TaskItemStatusHistory? latestStatus = child.TaskItemStatusHistories .OrderByDescending(s => s.CreatedDate) .FirstOrDefault(); - return latest == null || latest.StatusId != _statusChange.CompleteId; + return latestStatus == null || latestStatus.StatusId != _statusChange.CompleteId; }); if (hasIncompletedChild) @@ -294,8 +290,7 @@ public async Task> StatusChangeComplete([FromHeader] Guid } } - - TaskItemStatusHistory newTaskStatus = new TaskItemStatusHistory + TaskItemStatusHistory newTaskStatus = new TaskItemStatusHistory { TaskItemId = taskItem.Id, StatusId = _statusChange.CompleteId, @@ -303,7 +298,6 @@ public async Task> StatusChangeComplete([FromHeader] Guid CreatedUserId = userId, }; - taskItem.TaskItemStatusHistories.Add(newTaskStatus); await _unitOfWork.SaveChangesAsync(); @@ -313,6 +307,7 @@ public async Task> StatusChangeComplete([FromHeader] Guid Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, + ParentTaskId = taskItem.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty, Notes = taskItem.TaskItemNotes.Select(n => new NoteDto { @@ -358,12 +353,14 @@ public async Task> EditTask([FromHeader] Guid userId, Guid return NotFound(taskId); } - //TaskItem? parentTask = await _unitOfWork.TaskItem.GetTaskByIdAsync(updateTaskDto.ParentTaskId, userId); - //if (parentTask is null) - //{ - // return NotFound(updateTaskDto.ParentTaskId); - //} + //check if parent task exists + TaskItem? parentTask = await _unitOfWork.TaskItem.GetTaskByIdAsync(updateTaskDto.ParentTaskId, userId); + if (parentTask is null) + { + return NotFound(updateTaskDto.ParentTaskId); + } + //subtask creation if ParentId is provided if (updateTaskDto.ParentTaskId != Guid.Empty) { SubTask? subTask = new SubTask @@ -378,7 +375,6 @@ public async Task> EditTask([FromHeader] Guid userId, Guid } await _unitOfWork.SaveChangesAsync(); - //update only provided property fields if (updateTaskDto.Title != null && updateTaskDto.DueDate.HasValue && @@ -390,14 +386,13 @@ public async Task> EditTask([FromHeader] Guid userId, Guid } await _unitOfWork.SaveChangesAsync(); - TaskDto editTaskResult = new TaskDto { Id = taskItem.Id, Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, - ParentTaskId = updateTaskDto.ParentTaskId, + ParentTaskId = taskItem.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty, Notes = taskItem.TaskItemNotes.Select(n => new NoteDto { @@ -465,7 +460,7 @@ public async Task> DeleteTaskById([FromHeader] Guid userId }; return Ok(deleteTaskResult); - + } } diff --git a/Web.Api/Persistence/Repositories/TaskItemRepo.cs b/Web.Api/Persistence/Repositories/TaskItemRepo.cs index ee01473..9051f57 100644 --- a/Web.Api/Persistence/Repositories/TaskItemRepo.cs +++ b/Web.Api/Persistence/Repositories/TaskItemRepo.cs @@ -25,38 +25,31 @@ public TaskItemRepo(TaskManagerAppDBContext context) /// public async Task GetTaskByIdAsync(Guid taskId, Guid userId) { - return await _context.TaskItems.Include(item => item.TaskItemNotes).Include(history => history.TaskItemStatusHistories) - .ThenInclude(stat => stat.Status).SingleOrDefaultAsync(ti => ti.Id == taskId && ti.CreatedUserId == userId); - - return await _context.TaskItems - .Include(item => item.TaskItemNotes) - .Include(item => item.TaskItemStatusHistories) - .ThenInclude(stat => stat.Status) - .Include(t => t.SubTaskTaskItems) + return await _context.TaskItems + .Include(item => item.TaskItemNotes) + .Include(item => item.TaskItemStatusHistories) + .ThenInclude(stat => stat.Status) + .Include(t => t.SubTaskSubTaskItems) + .ThenInclude(st => st.TaskItem) + .Include(t => t.SubTaskTaskItems) .ThenInclude(st => st.SubTaskItem) .ThenInclude(si => si.TaskItemStatusHistories) .ThenInclude(h => h.Status) - .SingleOrDefaultAsync(ti => ti.Id == taskId && ti.CreatedUserId == userId); - + .SingleOrDefaultAsync(ti => ti.Id == taskId && ti.CreatedUserId == userId); } - - - public async Task CreateTaskAsync(TaskItem taskItem) { await _context.AddAsync(taskItem); } - public async Task CreateNoteAsync(TaskItemNote taskItemItemNote) { await _context.AddAsync(taskItemItemNote); } - public IEnumerable GetAllNotes(Guid taskId) { throw new NotImplementedException(); From 8bf1ba99eef91a62f377b9248fd1ce9d9c92f691 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Mon, 10 Nov 2025 08:51:15 -0600 Subject: [PATCH 04/12] Remove unecessary method --- Web.Api/Controllers/TaskController.cs | 46 ------------------- .../Persistence/Repositories/TaskItemRepo.cs | 11 ----- 2 files changed, 57 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index 9aa4a38..b33021d 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -417,51 +417,5 @@ public async Task> EditTask([FromHeader] Guid userId, Guid return Ok(editTaskResult); } - [HttpDelete("{taskId}", Name = "DeleteTaskById")] - public async Task> DeleteTaskById([FromHeader] Guid userId, Guid taskId) - { - if (!await _unitOfWork.User.IsUserInDbAsync(userId)) - { - return StatusCode(403); - } - TaskItem? taskItem = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskId, userId); - if (taskItem is null) - { - return NotFound(taskId); - } - - - await _unitOfWork.SaveChangesAsync(); - _unitOfWork.TaskItem.DeleteTask(taskItem); - - TaskDto deleteTaskResult = new TaskDto - { - Id = taskId, - Title = taskItem.Title, - DueDate = taskItem.DueDate, - Priority = taskItem.Priority, - Notes = taskItem.TaskItemNotes.Select(n => new NoteDto - { - Id = n.Id, - TaskItemId = n.TaskItemId, - Note = n.Note, - CreatedDate = n.CreatedDate, - CreatedUser = n.CreatedUserId - }).ToList(), - CurrentStatus = taskItem.TaskItemStatusHistories.OrderByDescending(rank => rank.CreatedDate) - .Select(history => new StatusDto - { - Id = history.Id, - Name = history.Status.Name, - Code = history.Status.Code, - }).FirstOrDefault(), - CreatedDate = taskItem.CreatedDate, - CreatedUserId = taskItem.CreatedUserId - }; - - return Ok(deleteTaskResult); - - } - } } diff --git a/Web.Api/Persistence/Repositories/TaskItemRepo.cs b/Web.Api/Persistence/Repositories/TaskItemRepo.cs index 9051f57..3b70d3f 100644 --- a/Web.Api/Persistence/Repositories/TaskItemRepo.cs +++ b/Web.Api/Persistence/Repositories/TaskItemRepo.cs @@ -9,14 +9,11 @@ namespace Web.Api.Persistence.Repositories public class TaskItemRepo { private readonly TaskManagerAppDBContext _context; - - public TaskItemRepo(TaskManagerAppDBContext context) { _context = context; } - /// /// Get Task by Id that only pertains to specific user. If not found, returns null. /// @@ -55,17 +52,9 @@ public IEnumerable GetAllNotes(Guid taskId) throw new NotImplementedException(); } - - public void DeleteNote(TaskItemNote taskItemNote) { _context.Remove(taskItemNote); } - - public void DeleteTask(TaskItem taskItem) - { - _context.Remove(taskItem); - } - } } From 4a2d7b1b1d09043212dedff04c61b14e3c8a07c5 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Tue, 11 Nov 2025 10:55:48 -0600 Subject: [PATCH 05/12] Added completed status Id to status change --- Web.Api/Controllers/TaskController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index b33021d..008bf6a 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -320,7 +320,7 @@ public async Task> StatusChangeComplete([FromHeader] Guid CurrentStatus = new StatusDto { - Id = taskItem.Id, + Id = _statusChange.CompleteId, Name = _statusChange.Complete, Code = _statusChange.Code2 }, From 24c7bd488f50bbd8d70edf18fac699f53828d246 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Tue, 11 Nov 2025 11:58:28 -0600 Subject: [PATCH 06/12] remove .Any from parameter --- Web.Api/Controllers/TaskController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index 008bf6a..97b3a51 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -272,7 +272,7 @@ public async Task> StatusChangeComplete([FromHeader] Guid } // Prevent completing a parent task when any child sub-task is not complete. - if (taskItem.SubTaskTaskItems != null && taskItem.SubTaskTaskItems.Any()) + if (taskItem.SubTaskTaskItems != null) { bool hasIncompletedChild = taskItem.SubTaskTaskItems .Select(st => st.SubTaskItem) From d5c652ca64ac325ca7292ad490326ca55e5df81f Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Wed, 12 Nov 2025 09:09:58 -0600 Subject: [PATCH 07/12] added comments --- Web.Api/Controllers/TaskController.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index 97b3a51..e79a58a 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -98,7 +98,7 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta Priority = taskCreatedDto.Priority, CreatedDate = DateTime.Now, - CreatedUserId = userId, //set the UserId which is given by the user from the header + CreatedUserId = userId, TaskItemStatusHistories = [ new TaskItemStatusHistory() { StatusId = _statusChange.PendingId, @@ -168,7 +168,6 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta Code = history.Status.Code, }).FirstOrDefault(), - CreatedDate = taskCreation.CreatedDate, CreatedUserId = taskCreation.CreatedUserId }; @@ -191,6 +190,7 @@ public async Task> CreateNote([FromHeader] Guid user return NotFound(taskId); } + //Request DTO TaskItemNote noteCreation = new TaskItemNote { TaskItemId = taskId, @@ -202,6 +202,7 @@ public async Task> CreateNote([FromHeader] Guid user await _unitOfWork.TaskItem.CreateNoteAsync(noteCreation); await _unitOfWork.SaveChangesAsync(); + //Response DTO var noteResult = new NoteDto { Id = noteCreation.Id, @@ -245,6 +246,7 @@ public async Task> DeleteNote([FromHeader] Guid userId, Gu _unitOfWork.TaskItem.DeleteNote(note); await _unitOfWork.SaveChangesAsync(); + //Response DTO NoteDto deleteNote = new NoteDto { Id = note.Id, @@ -280,16 +282,18 @@ public async Task> StatusChangeComplete([FromHeader] Guid { TaskItemStatusHistory? latestStatus = child.TaskItemStatusHistories .OrderByDescending(s => s.CreatedDate) - .FirstOrDefault(); - return latestStatus == null || latestStatus.StatusId != _statusChange.CompleteId; + .FirstOrDefault(); + return latestStatus == null || latestStatus.StatusId != _statusChange.CompleteId; // Check if the latest status is not "Complete" }); - if (hasIncompletedChild) + if (hasIncompletedChild) { - return BadRequest("Cannot complete parent task with incomplete child sub-tasks."); + return BadRequest("Cannot complete parent task with incomplete child sub-tasks."); } } - + + //add new status history for Complete + //Reuest DTO TaskItemStatusHistory newTaskStatus = new TaskItemStatusHistory { TaskItemId = taskItem.Id, @@ -301,6 +305,7 @@ public async Task> StatusChangeComplete([FromHeader] Guid taskItem.TaskItemStatusHistories.Add(newTaskStatus); await _unitOfWork.SaveChangesAsync(); + //Response DTO TaskDto statusResult = new TaskDto { Id = taskItem.Id, @@ -386,6 +391,7 @@ public async Task> EditTask([FromHeader] Guid userId, Guid } await _unitOfWork.SaveChangesAsync(); + //Response DTO TaskDto editTaskResult = new TaskDto { Id = taskItem.Id, From 0981ef54fab87cf1e5392b959e031dcaa6a53351 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Thu, 13 Nov 2025 10:46:55 -0600 Subject: [PATCH 08/12] Added nullable reference to ParentTaskId --- Web.Api/Controllers/TaskController.cs | 57 +++++++++---------- Web.Api/Dto/Request/TaskCreateDto.cs | 2 +- Web.Api/Dto/Response/TaskDto.cs | 2 +- .../Persistence/Repositories/TaskItemRepo.cs | 2 + 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index e79a58a..61bcac6 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -36,13 +36,13 @@ public async Task> GetTaskById([FromHeader] Guid userId, G return NotFound(taskId); } - TaskDto? taskDetail = new TaskDto //create a new instance of TaskDto and set their properties + TaskDto? taskDetail = new() //create a new instance of TaskDto and set their properties { Id = taskItem.Id, Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, - ParentTaskId = taskItem.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty, + ParentTaskId = taskItem.SubTaskSubTaskItems.FirstOrDefault()?.TaskItemId, CreatedDate = taskItem.CreatedDate, @@ -78,10 +78,9 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta return StatusCode(403); } - //TaskItem? parentTask = null; - if (taskCreatedDto.ParentTaskId != Guid.Empty) + if (taskCreatedDto.ParentTaskId.HasValue) { - TaskItem? parentTask = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskCreatedDto.ParentTaskId, userId); + TaskItem? parentTask = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskCreatedDto.ParentTaskId.Value, userId); if (parentTask is null) { return NotFound(taskCreatedDto.ParentTaskId); @@ -114,41 +113,37 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta taskCreation.DueDate = new DateTime(1900, 1, 1); //Default if null } { - taskCreation.DueDate = taskCreatedDto.DueDate.Value; //enetered value + taskCreation.DueDate = taskCreatedDto.DueDate!.Value; //enetered value } - - await _unitOfWork.TaskItem.CreateTaskAsync(taskCreation); //UofW takes the TaskItem class and calls the CreateTask method from the TaskItemRepo - await _unitOfWork.SaveChangesAsync(); //UofW calls the SaveChanges method - taskCreation = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskCreation.Id, userId); - //SubTask creation if ParentId is provided - if (taskCreatedDto.ParentTaskId != Guid.Empty) + if (taskCreatedDto.ParentTaskId.HasValue) { - SubTask subTask = new SubTask + SubTask subTask = new() { - TaskItemId = taskCreatedDto.ParentTaskId, + TaskItemId = taskCreatedDto.ParentTaskId.Value, SubTaskItemId = taskCreation.Id, CreatedDate = DateTime.Now, CreatedUserId = userId }; taskCreation.SubTaskSubTaskItems.Add(subTask); } - await _unitOfWork.SaveChangesAsync(); + await _unitOfWork.TaskItem.CreateTaskAsync(taskCreation); //UofW takes the TaskItem class and calls the CreateTask method from the TaskItemRepo + await _unitOfWork.SaveChangesAsync(); //UofW calls the SaveChanges method + taskCreation = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskCreation.Id, userId) ?? throw new Exception ("The object you tried to create does not exist in db"); //Response DTO //create a new instance of TaskDto //calls the TaskDto prop and call the taskCreation and set the prop for user view //return the result of the tasks created - - TaskDto creationResult = new TaskDto() + TaskDto creationResult = new () { Id = taskCreation.Id, Title = taskCreation.Title, DueDate = taskCreation.DueDate, Priority = taskCreation.Priority, - ParentTaskId = taskCreation.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty, + ParentTaskId = taskCreation.SubTaskSubTaskItems.FirstOrDefault()?.TaskItemId, Notes = taskCreation.TaskItemNotes.Select (note => new NoteDto @@ -274,7 +269,7 @@ public async Task> StatusChangeComplete([FromHeader] Guid } // Prevent completing a parent task when any child sub-task is not complete. - if (taskItem.SubTaskTaskItems != null) + if (taskItem.SubTaskTaskItems != null ) { bool hasIncompletedChild = taskItem.SubTaskTaskItems .Select(st => st.SubTaskItem) @@ -294,7 +289,7 @@ public async Task> StatusChangeComplete([FromHeader] Guid //add new status history for Complete //Reuest DTO - TaskItemStatusHistory newTaskStatus = new TaskItemStatusHistory + TaskItemStatusHistory newTaskStatus = new () { TaskItemId = taskItem.Id, StatusId = _statusChange.CompleteId, @@ -306,13 +301,13 @@ public async Task> StatusChangeComplete([FromHeader] Guid await _unitOfWork.SaveChangesAsync(); //Response DTO - TaskDto statusResult = new TaskDto + TaskDto statusResult = new () { Id = taskItem.Id, Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, - ParentTaskId = taskItem.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty, + ParentTaskId = taskItem.SubTaskSubTaskItems.FirstOrDefault()?.TaskItemId, Notes = taskItem.TaskItemNotes.Select(n => new NoteDto { @@ -359,23 +354,25 @@ public async Task> EditTask([FromHeader] Guid userId, Guid } //check if parent task exists - TaskItem? parentTask = await _unitOfWork.TaskItem.GetTaskByIdAsync(updateTaskDto.ParentTaskId, userId); - if (parentTask is null) + if (updateTaskDto.ParentTaskId.HasValue) { - return NotFound(updateTaskDto.ParentTaskId); + TaskItem? parentTask = await _unitOfWork.TaskItem.GetTaskByIdAsync(updateTaskDto.ParentTaskId.Value, userId); + if (parentTask is null) + { + return NotFound(updateTaskDto.ParentTaskId); + } } //subtask creation if ParentId is provided - if (updateTaskDto.ParentTaskId != Guid.Empty) + if (updateTaskDto.ParentTaskId.HasValue) { - SubTask? subTask = new SubTask + SubTask? subTask = new() { - TaskItemId = updateTaskDto.ParentTaskId, + TaskItemId = updateTaskDto.ParentTaskId.Value, SubTaskItemId = taskItem.Id, CreatedDate = DateTime.Now, CreatedUserId = userId }; - taskItem.SubTaskSubTaskItems.Add(subTask); } await _unitOfWork.SaveChangesAsync(); @@ -398,7 +395,7 @@ public async Task> EditTask([FromHeader] Guid userId, Guid Title = taskItem.Title, DueDate = taskItem.DueDate, Priority = taskItem.Priority, - ParentTaskId = taskItem.SubTaskSubTaskItems?.FirstOrDefault()?.TaskItemId ?? Guid.Empty, + ParentTaskId = taskItem.SubTaskSubTaskItems.FirstOrDefault()?.TaskItemId, Notes = taskItem.TaskItemNotes.Select(n => new NoteDto { diff --git a/Web.Api/Dto/Request/TaskCreateDto.cs b/Web.Api/Dto/Request/TaskCreateDto.cs index 46fe5ba..7bbe923 100644 --- a/Web.Api/Dto/Request/TaskCreateDto.cs +++ b/Web.Api/Dto/Request/TaskCreateDto.cs @@ -5,7 +5,7 @@ public class TaskCreateDto public string Title { get; set; } public DateTime? DueDate { get; set; } public int Priority { get; set; } - public Guid ParentTaskId { get; set; } + public Guid? ParentTaskId { get; set; } } } diff --git a/Web.Api/Dto/Response/TaskDto.cs b/Web.Api/Dto/Response/TaskDto.cs index 4ddd865..c3087f4 100644 --- a/Web.Api/Dto/Response/TaskDto.cs +++ b/Web.Api/Dto/Response/TaskDto.cs @@ -8,7 +8,7 @@ public class TaskDto public string Title { get; set; } public DateTime? DueDate { get; set; } public int Priority { get; set; } - public Guid ParentTaskId { get; set; } + public Guid? ParentTaskId { get; set; } public List Notes { get; set; } = []; public StatusDto? CurrentStatus { get; set; } public DateTime CreatedDate { get; set; } diff --git a/Web.Api/Persistence/Repositories/TaskItemRepo.cs b/Web.Api/Persistence/Repositories/TaskItemRepo.cs index 3b70d3f..97bf012 100644 --- a/Web.Api/Persistence/Repositories/TaskItemRepo.cs +++ b/Web.Api/Persistence/Repositories/TaskItemRepo.cs @@ -35,6 +35,8 @@ public TaskItemRepo(TaskManagerAppDBContext context) .SingleOrDefaultAsync(ti => ti.Id == taskId && ti.CreatedUserId == userId); } + + public async Task CreateTaskAsync(TaskItem taskItem) { await _context.AddAsync(taskItem); From 152195e832b5a2119b7f8a1b991fed348f82ce48 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Sun, 16 Nov 2025 22:27:56 -0600 Subject: [PATCH 09/12] Implemented Recursive method to check all incomplete subtasks --- Web.Api/Controllers/TaskController.cs | 67 ++++++++++++++++++--------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index 61bcac6..44fb8ae 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -97,7 +97,7 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta Priority = taskCreatedDto.Priority, CreatedDate = DateTime.Now, - CreatedUserId = userId, + CreatedUserId = userId, TaskItemStatusHistories = [ new TaskItemStatusHistory() { StatusId = _statusChange.PendingId, @@ -131,13 +131,13 @@ public async Task> CreateTask([FromHeader] Guid userId, Ta await _unitOfWork.TaskItem.CreateTaskAsync(taskCreation); //UofW takes the TaskItem class and calls the CreateTask method from the TaskItemRepo await _unitOfWork.SaveChangesAsync(); //UofW calls the SaveChanges method - taskCreation = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskCreation.Id, userId) ?? throw new Exception ("The object you tried to create does not exist in db"); + taskCreation = await _unitOfWork.TaskItem.GetTaskByIdAsync(taskCreation.Id, userId) ?? throw new Exception("The object you tried to create does not exist in db"); //Response DTO //create a new instance of TaskDto //calls the TaskDto prop and call the taskCreation and set the prop for user view //return the result of the tasks created - TaskDto creationResult = new () + TaskDto creationResult = new() { Id = taskCreation.Id, Title = taskCreation.Title, @@ -253,6 +253,42 @@ public async Task> DeleteNote([FromHeader] Guid userId, Gu return Ok(deleteNote); } + public bool HasIncompletedDescendants(TaskItem task) + { + //If task has no subtasks end the check + if (task.SubTaskTaskItems == null) + { + return false; + } + + //Loop through every single subtask + foreach (var sub in task.SubTaskTaskItems) + { + //Get the subtask under Task Item + TaskItem child = sub.SubTaskItem; + + //Get the latest status history of a subtask and sort by created date + TaskItemStatusHistory? latestStatus = child.TaskItemStatusHistories + .OrderByDescending(c => c.CreatedDate) + .FirstOrDefault(); + //Get the impcompleted task statuses Status != Complete + bool inCompletedChild = latestStatus!.StatusId != _statusChange.CompleteId; + + //If task is incomplete return true + if (inCompletedChild) + { + return true; + } + + //Recursiveley check for any SubTask that is incomplete + if (HasIncompletedDescendants(child)) + { + return true; + } + } + //If all subtasks in descedndants are complete end the check + return false; + } [HttpPost("{taskId}/status-change/complete", Name = "StatusChangeComplete")] public async Task> StatusChangeComplete([FromHeader] Guid userId, Guid taskId) @@ -268,28 +304,15 @@ public async Task> StatusChangeComplete([FromHeader] Guid return NotFound(taskId); } - // Prevent completing a parent task when any child sub-task is not complete. - if (taskItem.SubTaskTaskItems != null ) + // Prevent completing a task when any child SubTask is not complete. + if (HasIncompletedDescendants(taskItem)) { - bool hasIncompletedChild = taskItem.SubTaskTaskItems - .Select(st => st.SubTaskItem) - .Any(child => - { - TaskItemStatusHistory? latestStatus = child.TaskItemStatusHistories - .OrderByDescending(s => s.CreatedDate) - .FirstOrDefault(); - return latestStatus == null || latestStatus.StatusId != _statusChange.CompleteId; // Check if the latest status is not "Complete" - }); - - if (hasIncompletedChild) - { - return BadRequest("Cannot complete parent task with incomplete child sub-tasks."); - } + return BadRequest("Cannot complete parent task with incomplete child sub-tasks."); } - + //add new status history for Complete //Reuest DTO - TaskItemStatusHistory newTaskStatus = new () + TaskItemStatusHistory newTaskStatus = new() { TaskItemId = taskItem.Id, StatusId = _statusChange.CompleteId, @@ -301,7 +324,7 @@ public async Task> StatusChangeComplete([FromHeader] Guid await _unitOfWork.SaveChangesAsync(); //Response DTO - TaskDto statusResult = new () + TaskDto statusResult = new() { Id = taskItem.Id, Title = taskItem.Title, From 957971f7f64ff620d37a275dd86c854663c5e589 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Mon, 17 Nov 2025 11:59:45 -0600 Subject: [PATCH 10/12] Child task is being properly access from the TaskItem --- Web.Api/Controllers/TaskController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index 44fb8ae..8b5c86b 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -255,7 +255,7 @@ public async Task> DeleteNote([FromHeader] Guid userId, Gu public bool HasIncompletedDescendants(TaskItem task) { - //If task has no subtasks end the check + //If task has no subtasks end the check and return false if (task.SubTaskTaskItems == null) { return false; @@ -264,15 +264,15 @@ public bool HasIncompletedDescendants(TaskItem task) //Loop through every single subtask foreach (var sub in task.SubTaskTaskItems) { - //Get the subtask under Task Item - TaskItem child = sub.SubTaskItem; + //Get the child task from the subtask + TaskItem child = sub.TaskItem; //Get the latest status history of a subtask and sort by created date TaskItemStatusHistory? latestStatus = child.TaskItemStatusHistories .OrderByDescending(c => c.CreatedDate) .FirstOrDefault(); - //Get the impcompleted task statuses Status != Complete - bool inCompletedChild = latestStatus!.StatusId != _statusChange.CompleteId; + //Check if the latest status is not complete (StatusId != CompleteId) + bool inCompletedChild =latestStatus!.StatusId != _statusChange.CompleteId; //If task is incomplete return true if (inCompletedChild) From 6f539f7ead916666cdb6ab5b0409592006536074 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Mon, 17 Nov 2025 14:05:19 -0600 Subject: [PATCH 11/12] changed public method to private --- Web.Api/Controllers/TaskController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index 8b5c86b..ed9ab82 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -253,7 +253,7 @@ public async Task> DeleteNote([FromHeader] Guid userId, Gu return Ok(deleteNote); } - public bool HasIncompletedDescendants(TaskItem task) + private bool HasIncompletedDescendants(TaskItem task) { //If task has no subtasks end the check and return false if (task.SubTaskTaskItems == null) @@ -265,7 +265,8 @@ public bool HasIncompletedDescendants(TaskItem task) foreach (var sub in task.SubTaskTaskItems) { //Get the child task from the subtask - TaskItem child = sub.TaskItem; + //TaskItem child = sub.TaskItem; + TaskItem child = sub.SubTaskItem; //Get the latest status history of a subtask and sort by created date TaskItemStatusHistory? latestStatus = child.TaskItemStatusHistories From 7c48eddc92de0e0654d89f5188ad37bc2e8089c7 Mon Sep 17 00:00:00 2001 From: MuhammadJr Date: Mon, 17 Nov 2025 14:05:59 -0600 Subject: [PATCH 12/12] . --- Web.Api/Controllers/TaskController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Web.Api/Controllers/TaskController.cs b/Web.Api/Controllers/TaskController.cs index ed9ab82..dd6b2d9 100644 --- a/Web.Api/Controllers/TaskController.cs +++ b/Web.Api/Controllers/TaskController.cs @@ -265,8 +265,7 @@ private bool HasIncompletedDescendants(TaskItem task) foreach (var sub in task.SubTaskTaskItems) { //Get the child task from the subtask - //TaskItem child = sub.TaskItem; - TaskItem child = sub.SubTaskItem; + TaskItem child = sub.TaskItem; //Get the latest status history of a subtask and sort by created date TaskItemStatusHistory? latestStatus = child.TaskItemStatusHistories