Changed the business logic of teams and tasks API in DashboardController to accept project ID and provide data according to project ID or project IDs assigned to logged in user

This commit is contained in:
ashutosh.nehete 2025-07-16 12:39:16 +05:30
parent 852b079428
commit 08e8e8d75f

View File

@ -21,12 +21,15 @@ namespace Marco.Pms.Services.Controllers
{
private readonly ApplicationDbContext _context;
private readonly UserHelper _userHelper;
private readonly ProjectsHelper _projectsHelper;
private readonly ILoggingService _logger;
private readonly PermissionServices _permissionServices;
public DashboardController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, PermissionServices permissionServices)
public static readonly Guid ActiveId = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731");
public DashboardController(ApplicationDbContext context, UserHelper userHelper, ProjectsHelper projectsHelper, ILoggingService logger, PermissionServices permissionServices)
{
_context = context;
_userHelper = userHelper;
_projectsHelper = projectsHelper;
_logger = logger;
_permissionServices = permissionServices;
}
@ -162,46 +165,185 @@ namespace Marco.Pms.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(projectDashboardVM, "Success", 200));
}
/// <summary>
/// Retrieves a dashboard summary of total employees and today's attendance.
/// If a projectId is provided, it returns totals for that project; otherwise, for all accessible active projects.
/// </summary>
/// <param name="projectId">Optional. The ID of a specific project to get totals for.</param>
[HttpGet("teams")]
public async Task<IActionResult> GetTotalEmployees()
public async Task<IActionResult> GetTotalEmployees([FromQuery] Guid? projectId)
{
var tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var date = DateTime.UtcNow.Date;
var Employees = await _context.Employees.Where(e => e.TenantId == tenantId && e.IsActive == true).Select(e => e.Id).ToListAsync();
var checkedInEmployee = await _context.Attendes.Where(e => e.InTime != null ? e.InTime.Value.Date == date : false).Select(e => e.EmployeeID).ToListAsync();
TeamDashboardVM teamDashboardVM = new TeamDashboardVM
try
{
TotalEmployees = Employees.Count(),
InToday = checkedInEmployee.Distinct().Count()
};
_logger.LogInfo("Today's total checked in employees fetched by employee {EmployeeId}", LoggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(teamDashboardVM, "Success", 200));
}
var tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
[HttpGet("tasks")]
public async Task<IActionResult> GetTotalTasks()
{
var tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var Tasks = await _context.WorkItems.Where(t => t.TenantId == tenantId).Select(t => new { PlannedWork = t.PlannedWork, CompletedWork = t.CompletedWork }).ToListAsync();
TasksDashboardVM tasksDashboardVM = new TasksDashboardVM
{
TotalTasks = 0,
CompletedTasks = 0
};
foreach (var task in Tasks)
{
tasksDashboardVM.TotalTasks += task.PlannedWork;
tasksDashboardVM.CompletedTasks += task.CompletedWork;
_logger.LogInfo("GetTotalEmployees called by user {UserId} for ProjectId: {ProjectId}", loggedInEmployee.Id, projectId ?? Guid.Empty);
// --- Step 1: Get the list of projects the user can access ---
// This query is more efficient as it only selects the IDs needed.
var projects = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee);
var accessibleActiveProjectIds = projects
.Where(p => p.ProjectStatusId == ActiveId)
.Select(p => p.Id)
.ToList();
if (!accessibleActiveProjectIds.Any())
{
_logger.LogInfo("User {UserId} has no accessible active projects.", loggedInEmployee.Id);
return Ok(ApiResponse<TeamDashboardVM>.SuccessResponse(new TeamDashboardVM(), "No accessible active projects found.", 200));
}
// --- Step 2: Build the list of project IDs to query against ---
List<Guid> finalProjectIds;
if (projectId.HasValue)
{
// Security Check: Ensure the requested project is in the user's accessible list.
if (!accessibleActiveProjectIds.Contains(projectId.Value))
{
_logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId} (not active or not accessible).", loggedInEmployee.Id, projectId.Value);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to view this project, or it is not active.", 403));
}
finalProjectIds = new List<Guid> { projectId.Value };
}
else
{
finalProjectIds = accessibleActiveProjectIds;
}
// --- Step 3: Run efficient aggregation queries SEQUENTIALLY ---
// Since we only have one DbContext instance, we await each query one by one.
// Query 1: Count total distinct employees allocated to the final project list
int totalEmployees = await _context.ProjectAllocations
.Where(pa => pa.TenantId == tenantId &&
finalProjectIds.Contains(pa.ProjectId) &&
pa.IsActive)
.Select(pa => pa.EmployeeId)
.Distinct()
.CountAsync();
// Query 2: Count total distinct employees who checked in today
// Use an efficient date range check
var today = DateTime.UtcNow.Date;
var tomorrow = today.AddDays(1);
int inTodays = await _context.Attendes
.Where(a => a.InTime >= today && a.InTime < tomorrow &&
finalProjectIds.Contains(a.ProjectID))
.Select(a => a.EmployeeID)
.Distinct()
.CountAsync();
// --- Step 4: Assemble the response ---
var teamDashboardVM = new TeamDashboardVM
{
TotalEmployees = totalEmployees,
InToday = inTodays
};
_logger.LogInfo("Successfully fetched team dashboard for user {UserId}. Total: {TotalEmployees}, InToday: {InToday}",
loggedInEmployee.Id, teamDashboardVM.TotalEmployees, teamDashboardVM.InToday);
return Ok(ApiResponse<TeamDashboardVM>.SuccessResponse(teamDashboardVM, "Dashboard data retrieved successfully.", 200));
}
catch (Exception ex)
{
_logger.LogError("An unexpected error occurred in GetTotalEmployees for projectId {ProjectId} \n {Error}", projectId ?? Guid.Empty, ex.Message);
return StatusCode(500, ApiResponse<object>.ErrorResponse("An internal server error occurred.", null, 500));
}
_logger.LogInfo("Total targeted tasks and total completed tasks fetched by employee {EmployeeId}", LoggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(tasksDashboardVM, "Success", 200));
}
/// <summary>
/// Retrieves a dashboard summary of total planned and completed tasks.
/// If a projectId is provided, it returns totals for that project; otherwise, for all accessible projects.
/// </summary>
/// <param name="projectId">Optional. The ID of a specific project to get totals for.</param>
/// <returns>An ApiResponse containing the task dashboard summary.</returns>
[HttpGet("tasks")] // Example route
public async Task<IActionResult> GetTotalTasks1([FromQuery] Guid? projectId) // Changed to FromQuery as it's optional
{
try
{
var tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
_logger.LogInfo("GetTotalTasks called by user {UserId} for ProjectId: {ProjectId}", loggedInEmployee.Id, projectId ?? Guid.Empty);
// --- Step 1: Build the base IQueryable for WorkItems ---
// This query is NOT executed yet. We will add more filters to it.
var baseWorkItemQuery = _context.WorkItems.Where(t => t.TenantId == tenantId);
// --- Step 2: Apply Filters based on the request (Project or All Accessible) ---
if (projectId.HasValue)
{
// --- Logic for a SINGLE Project ---
// 2a. Security Check: Verify permission for the specific project.
var hasPermission = await _permissionServices.HasProjectPermission(loggedInEmployee, projectId.Value.ToString());
if (!hasPermission)
{
_logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId}.", loggedInEmployee.Id, projectId.Value);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied.", "You do not have permission to view this project.", 403));
}
// 2b. Add project-specific filter to the base query.
// This is more efficient than fetching workAreaIds separately.
baseWorkItemQuery = baseWorkItemQuery
.Where(wi => wi.WorkArea != null &&
wi.WorkArea.Floor != null &&
wi.WorkArea.Floor.Building != null &&
wi.WorkArea.Floor.Building.ProjectId == projectId.Value);
}
else
{
// --- Logic for ALL Accessible Projects ---
// 2c. Get a list of all projects the user is allowed to see.
var accessibleProject = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee);
var accessibleProjectIds = accessibleProject.Select(p => p.Id).ToList();
if (!accessibleProjectIds.Any())
{
_logger.LogInfo("User {UserId} has no accessible projects.", loggedInEmployee.Id);
// Return a zeroed-out dashboard if the user has no projects.
return Ok(ApiResponse<TasksDashboardVM>.SuccessResponse(new TasksDashboardVM(), "No accessible projects found.", 200));
}
// 2d. Add a filter to include all work items from all accessible projects.
baseWorkItemQuery = baseWorkItemQuery
.Where(wi => wi.WorkArea != null &&
wi.WorkArea.Floor != null &&
wi.WorkArea.Floor.Building != null &&
accessibleProjectIds.Contains(wi.WorkArea.Floor.Building.ProjectId));
}
// --- Step 3: Execute the Aggregation Query ON THE DATABASE SERVER ---
// This is the most powerful optimization. The database does all the summing.
// EF Core translates this into a single, efficient SQL query like:
// SELECT SUM(PlannedWork), SUM(CompletedWork) FROM WorkItems WHERE ...
var tasksDashboardVM = await baseWorkItemQuery
.GroupBy(x => 1) // Group by a constant to aggregate all rows into one result.
.Select(g => new TasksDashboardVM
{
TotalTasks = g.Sum(wi => wi.PlannedWork),
CompletedTasks = g.Sum(wi => wi.CompletedWork)
})
.FirstOrDefaultAsync(); // Use FirstOrDefaultAsync as GroupBy might return no rows.
// If the query returned no work items, the result will be null. Default to a zeroed object.
tasksDashboardVM ??= new TasksDashboardVM();
_logger.LogInfo("Successfully fetched task dashboard for user {UserId}. Total: {TotalTasks}, Completed: {CompletedTasks}",
loggedInEmployee.Id, tasksDashboardVM.TotalTasks, tasksDashboardVM.CompletedTasks);
return Ok(ApiResponse<TasksDashboardVM>.SuccessResponse(tasksDashboardVM, "Dashboard data retrieved successfully.", 200));
}
catch (Exception ex)
{
_logger.LogError("An unexpected error occurred in GetTotalTasks for projectId {ProjectId} \n {Error}", projectId ?? Guid.Empty, ex.Message);
return StatusCode(500, ApiResponse<object>.ErrorResponse("An internal server error occurred.", null, 500));
}
}
[HttpGet("pending-attendance")]
public async Task<IActionResult> GetPendingAttendance()
{