Merge pull request 'Added the filter and pagenation in get task list API' (#136) from Ashutosh_Enhancement_#1293 into Organization_Management

Reviewed-on: #136
This commit is contained in:
ashutosh.nehete 2025-09-23 06:15:47 +00:00
commit 0c6f5e0df0
2 changed files with 127 additions and 39 deletions

View File

@ -0,0 +1,10 @@
namespace Marco.Pms.Model.Filters
{
public class TaskFilter
{
public List<Guid>? BuildingIds { get; set; }
public List<Guid>? FloorIds { get; set; }
public List<Guid>? ActivityIds { get; set; }
public List<Guid>? ServiceIds { get; set; }
}
}

View File

@ -2,6 +2,7 @@
using Marco.Pms.Model.Activities;
using Marco.Pms.Model.Dtos.Activities;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Filters;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
@ -17,6 +18,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using Document = Marco.Pms.Model.DocumentManager.Document;
namespace MarcoBMS.Services.Controllers
@ -428,7 +430,8 @@ namespace MarcoBMS.Services.Controllers
}
[HttpGet("list")]
public async Task<IActionResult> GetTasksList([FromQuery] Guid projectId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
public async Task<IActionResult> GetTasksList([FromQuery] Guid projectId, [FromQuery] string? filter, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20,
[FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
{
_logger.LogInfo("GetTasksList called for projectId: {ProjectId}, dateFrom: {DateFrom}, dateTo: {DateTo}", projectId, dateFrom ?? "", dateTo ?? "");
@ -436,76 +439,103 @@ namespace MarcoBMS.Services.Controllers
DateTime fromDate = new DateTime();
DateTime toDate = new DateTime();
// Parse and validate dateFrom
// 1. Parse and validate dateFrom
if (dateFrom != null && !DateTime.TryParse(dateFrom, out fromDate))
{
_logger.LogWarning("Invalid starting date provided: {DateFrom}", dateFrom);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid starting date.", "Invalid starting date.", 400));
}
// Parse and validate dateTo
// 2. Parse and validate dateTo
if (dateTo != null && !DateTime.TryParse(dateTo, out toDate))
{
_logger.LogWarning("Invalid ending date provided: {DateTo}", dateTo);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid ending date.", "Invalid ending date.", 400));
}
// Set default date range if not provided
// 3. Set default date range if not provided
fromDate = dateFrom == null ? DateTime.UtcNow.Date : fromDate;
toDate = dateTo == null ? fromDate.AddDays(1) : toDate;
// 1. Get all buildings under this project
_logger.LogInfo("Fetching buildings for projectId: {ProjectId}", projectId);
var buildings = await _context.Buildings
.Where(b => b.ProjectId == projectId && b.TenantId == tenantId)
.ToListAsync();
var buildingIds = buildings.Select(b => b.Id).ToList();
// 2. Get floors under the buildings
var floors = await _context.Floor
.Where(f => buildingIds.Contains(f.BuildingId) && f.TenantId == tenantId)
.ToListAsync();
var floorIds = floors.Select(f => f.Id).ToList();
// 3. Get work areas under the floors
var workAreas = await _context.WorkAreas
.Where(a => floorIds.Contains(a.FloorId) && a.TenantId == tenantId)
.ToListAsync();
var workAreaIds = workAreas.Select(a => a.Id).ToList();
// 4. Get work items under the work areas
var workItems = await _context.WorkItems
.Where(i => workAreaIds.Contains(i.WorkAreaId) && i.TenantId == tenantId)
.Include(i => i.ActivityMaster)
.ToListAsync();
var workItemIds = workItems.Select(i => i.Id).ToList();
_logger.LogInfo("Fetching task allocations between {FromDate} and {ToDate}", fromDate, toDate);
// 5. Get task allocations in the specified date range
var taskAllocations = await _context.TaskAllocations
// 4. Get task allocations in the specified date range
var taskAllocationQuery = _context.TaskAllocations
.Include(t => t.Employee)
.Include(t => t.ReportedBy)
.Include(t => t.ApprovedBy)
.Include(t => t.WorkStatus)
.Include(t => t.WorkItem)
.Where(t => workItemIds.Contains(t.WorkItemId) &&
.ThenInclude(wi => wi!.ActivityMaster)
.ThenInclude(a => a!.ActivityGroup)
.ThenInclude(ag => ag!.Service)
.Include(t => t.WorkItem)
.ThenInclude(wi => wi!.WorkCategoryMaster)
.Include(t => t.WorkItem)
.ThenInclude(wi => wi!.WorkArea)
.ThenInclude(wa => wa!.Floor)
.ThenInclude(f => f!.Building)
.Where(t => t.WorkItem != null &&
t.WorkItem.WorkArea != null &&
t.WorkItem.WorkArea.Floor != null &&
t.WorkItem.WorkArea.Floor.Building != null &&
t.WorkItem.WorkArea.Floor.Building.ProjectId != projectId &&
t.AssignmentDate.Date >= fromDate.Date &&
t.AssignmentDate.Date <= toDate.Date &&
t.TenantId == tenantId)
t.TenantId == tenantId);
var taskFilter = TryDeserializeFilter(filter);
if (taskFilter != null)
{
if (taskFilter.BuildingIds?.Any() ?? false)
{
taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
t.WorkItem.WorkArea != null &&
t.WorkItem.WorkArea.Floor != null &&
taskFilter.BuildingIds.Contains(t.WorkItem.WorkArea.Floor.BuildingId));
}
if (taskFilter.FloorIds?.Any() ?? false)
{
taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
t.WorkItem.WorkArea != null &&
taskFilter.FloorIds.Contains(t.WorkItem.WorkArea.FloorId));
}
if (taskFilter.ActivityIds?.Any() ?? false)
{
taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
taskFilter.ActivityIds.Contains(t.WorkItem.ActivityId));
}
if (taskFilter.ServiceIds?.Any() ?? false)
{
taskAllocationQuery = taskAllocationQuery.Where(t => t.WorkItem != null &&
t.WorkItem.ActivityMaster != null &&
t.WorkItem.ActivityMaster.ActivityGroup != null &&
taskFilter.ServiceIds.Contains(t.WorkItem.ActivityMaster.ActivityGroup.ServiceId));
}
}
int totalRecords = await taskAllocationQuery.CountAsync();
int totalPages = (int)Math.Ceiling((double)totalRecords / pageSize);
var taskAllocations = await taskAllocationQuery
.OrderByDescending(t => t.AssignmentDate)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
var taskIds = taskAllocations.Select(t => t.Id).ToList();
// 6. Load team members
// 5. Load team members
_logger.LogInfo("Loading task members and related employee data.");
var teamMembers = await _context.TaskMembers
.Include(t => t.Employee)
.Where(t => taskIds.Contains(t.TaskAllocationId))
.ToListAsync();
// 7. Load task comments
// 6. Load task comments
_logger.LogInfo("Fetching comments and attachments.");
var allComments = await _context.TaskComments
.Include(c => c.Employee)
@ -513,14 +543,14 @@ namespace MarcoBMS.Services.Controllers
.ToListAsync();
var commentIds = allComments.Select(c => c.Id).ToList();
// 8. Load all attachments (task and comment)
// 7. Load all attachments (task and comment)
var attachments = await _context.TaskAttachments
.Where(t => taskIds.Contains(t.ReferenceId) || commentIds.Contains(t.ReferenceId))
.ToListAsync();
var documentIds = attachments.Select(t => t.DocumentId).ToList();
// 9. Load actual documents from attachment references
// 8. Load actual documents from attachment references
var documents = await _context.Documents
.Where(d => documentIds.Contains(d.Id))
.ToListAsync();
@ -606,9 +636,18 @@ namespace MarcoBMS.Services.Controllers
tasks.Add(response);
}
var VM = new
{
TotalCount = totalRecords,
TotalPages = totalPages,
CurrentPage = pageNumber,
PageSize = pageSize,
Data = tasks
};
_logger.LogInfo("Task list constructed successfully. Returning {Count} tasks.", tasks.Count);
return Ok(ApiResponse<object>.SuccessResponse(tasks, "Success", 200));
return Ok(ApiResponse<object>.SuccessResponse(VM, "Success", 200));
}
[HttpGet("get/{taskId}")]
@ -869,5 +908,44 @@ namespace MarcoBMS.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse("Task has been approved", "Task has been approved", 200));
}
private TaskFilter? TryDeserializeFilter(string? filter)
{
if (string.IsNullOrWhiteSpace(filter))
{
return null;
}
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
TaskFilter? expenseFilter = null;
try
{
// First, try to deserialize directly. This is the expected case (e.g., from a web client).
expenseFilter = JsonSerializer.Deserialize<TaskFilter>(filter, options);
}
catch (JsonException ex)
{
_logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeFilter), filter);
// If direct deserialization fails, it might be an escaped string (common with tools like Postman or some mobile clients).
try
{
// Unescape the string first, then deserialize the result.
string unescapedJsonString = JsonSerializer.Deserialize<string>(filter, options) ?? "";
if (!string.IsNullOrWhiteSpace(unescapedJsonString))
{
expenseFilter = JsonSerializer.Deserialize<TaskFilter>(unescapedJsonString, options);
}
}
catch (JsonException ex1)
{
// If both attempts fail, log the final error and return null.
_logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializeFilter), filter);
return null;
}
}
return expenseFilter;
}
}
}