Added the organization filter in get project allocation API

This commit is contained in:
ashutosh.nehete 2025-09-20 14:58:32 +05:30
parent 05bfa48115
commit 9b59a4d6b6
3 changed files with 47 additions and 21 deletions

View File

@ -201,8 +201,8 @@ namespace MarcoBMS.Services.Controllers
#region =================================================================== Project Allocation APIs =================================================================== #region =================================================================== Project Allocation APIs ===================================================================
[HttpGet("employees/get/{projectid?}/{includeInactive?}")] [HttpGet("employees/get/{projectId}")]
public async Task<IActionResult> GetEmployeeByProjectId(Guid? projectId, bool includeInactive = false) public async Task<IActionResult> GetEmployeeByProjectId(Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] bool includeInactive = false)
{ {
// --- Step 1: Input Validation --- // --- Step 1: Input Validation ---
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -214,12 +214,12 @@ namespace MarcoBMS.Services.Controllers
// --- Step 2: Prepare data without I/O --- // --- Step 2: Prepare data without I/O ---
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _projectServices.GetEmployeeByProjectIdAsync(projectId, includeInactive, tenantId, loggedInEmployee); var response = await _projectServices.GetEmployeeByProjectIdAsync(projectId, organizationId, includeInactive, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }
[HttpGet("allocation/{projectId}")] [HttpGet("allocation/{projectId}")]
public async Task<IActionResult> GetProjectAllocation(Guid? projectId) public async Task<IActionResult> GetProjectAllocation(Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] bool includeInactive = false)
{ {
// --- Step 1: Input Validation --- // --- Step 1: Input Validation ---
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -231,7 +231,7 @@ namespace MarcoBMS.Services.Controllers
// --- Step 2: Prepare data without I/O --- // --- Step 2: Prepare data without I/O ---
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _projectServices.GetProjectAllocationAsync(projectId, tenantId, loggedInEmployee); var response = await _projectServices.GetProjectAllocationAsync(projectId, organizationId, includeInactive, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }

View File

@ -480,10 +480,10 @@ namespace Marco.Pms.Services.Service
/// <param name="tenantId">The ID of the current tenant.</param> /// <param name="tenantId">The ID of the current tenant.</param>
/// <param name="loggedInEmployee">The current authenticated employee (used for permission checks).</param> /// <param name="loggedInEmployee">The current authenticated employee (used for permission checks).</param>
/// <returns>An ApiResponse containing a list of employees or an error.</returns> /// <returns>An ApiResponse containing a list of employees or an error.</returns>
public async Task<ApiResponse<object>> GetEmployeeByProjectIdAsync(Guid? projectId, bool includeInactive, Guid tenantId, Employee loggedInEmployee) public async Task<ApiResponse<object>> GetEmployeeByProjectIdAsync(Guid projectId, Guid? organizationId, bool includeInactive, Guid tenantId, Employee loggedInEmployee)
{ {
// --- Step 1: Input Validation --- // --- Step 1: Input Validation ---
if (projectId == null) if (projectId == Guid.Empty)
{ {
_logger.LogWarning("GetEmployeeByProjectID called with a null projectId."); _logger.LogWarning("GetEmployeeByProjectID called with a null projectId.");
// 400 Bad Request is more appropriate for invalid input than 404 Not Found. // 400 Bad Request is more appropriate for invalid input than 404 Not Found.
@ -500,7 +500,7 @@ namespace Marco.Pms.Services.Service
// --- CRITICAL: Security Check --- // --- CRITICAL: Security Check ---
// Before fetching data, you MUST verify the user has permission to see it. // Before fetching data, you MUST verify the user has permission to see it.
// This is a placeholder for your actual permission logic. // This is a placeholder for your actual permission logic.
var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId);
var hasAllEmployeePermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id); var hasAllEmployeePermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id);
var hasviewTeamPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id, projectId); var hasviewTeamPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id, projectId);
@ -514,6 +514,7 @@ namespace Marco.Pms.Services.Service
// We start with the base query and conditionally add filters before executing it. // We start with the base query and conditionally add filters before executing it.
// This avoids code duplication and is highly performant. // This avoids code duplication and is highly performant.
var employeeQuery = _context.ProjectAllocations var employeeQuery = _context.ProjectAllocations
.Include(pa => pa.Employee)
.Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId); .Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId);
// Conditionally apply the filter for active allocations. // Conditionally apply the filter for active allocations.
@ -522,16 +523,23 @@ namespace Marco.Pms.Services.Service
employeeQuery = employeeQuery.Where(pa => pa.IsActive); employeeQuery = employeeQuery.Where(pa => pa.IsActive);
} }
// Conditionally apply the filter for organization ID.
if (organizationId.HasValue)
{
employeeQuery = employeeQuery.Where(pa => pa.Employee != null && pa.Employee.OrganizationId == organizationId.Value);
}
// --- Step 3: Project Directly to the ViewModel on the Database Server --- // --- Step 3: Project Directly to the ViewModel on the Database Server ---
// This is the most significant performance optimization. // This is the most significant performance optimization.
// Instead of fetching full Employee entities, we select only the data needed for the EmployeeVM. // Instead of fetching full Employee entities, we select only the data needed for the EmployeeVM.
// AutoMapper's ProjectTo is perfect for this, as it translates the mapping configuration into an efficient SQL SELECT statement. // AutoMapper's ProjectTo is perfect for this, as it translates the mapping configuration into an efficient SQL SELECT statement.
var resultVM = await employeeQuery
var projectAllocations = await employeeQuery
.Where(pa => pa.Employee != null) // Safety check for data integrity .Where(pa => pa.Employee != null) // Safety check for data integrity
.Select(pa => pa.Employee) // Navigate to the Employee entity
.ProjectTo<EmployeeVM>(_mapper.ConfigurationProvider) // Let AutoMapper generate the SELECT
.ToListAsync(); .ToListAsync();
var resultVM = projectAllocations.Select(pa => _mapper.Map<EmployeeVM>(pa.Employee)).ToList();
_logger.LogInfo("Successfully fetched {EmployeeCount} employees for project {ProjectId}.", resultVM.Count, projectId); _logger.LogInfo("Successfully fetched {EmployeeCount} employees for project {ProjectId}.", resultVM.Count, projectId);
// Note: The original mapping loop is now completely gone, replaced by the single efficient query above. // Note: The original mapping loop is now completely gone, replaced by the single efficient query above.
@ -554,10 +562,10 @@ namespace Marco.Pms.Services.Service
/// <param name="tenantId">The ID of the current tenant.</param> /// <param name="tenantId">The ID of the current tenant.</param>
/// <param name="loggedInEmployee">The current authenticated employee for permission checks.</param> /// <param name="loggedInEmployee">The current authenticated employee for permission checks.</param>
/// <returns>An ApiResponse containing allocation details or an appropriate error.</returns> /// <returns>An ApiResponse containing allocation details or an appropriate error.</returns>
public async Task<ApiResponse<object>> GetProjectAllocationAsync(Guid? projectId, Guid tenantId, Employee loggedInEmployee) public async Task<ApiResponse<object>> GetProjectAllocationAsync(Guid projectId, Guid? organizationId, bool includeInactive, Guid tenantId, Employee loggedInEmployee)
{ {
// --- Step 1: Input Validation --- // --- Step 1: Input Validation ---
if (projectId == null) if (projectId == Guid.Empty)
{ {
_logger.LogWarning("GetProjectAllocation called with a null projectId."); _logger.LogWarning("GetProjectAllocation called with a null projectId.");
return ApiResponse<object>.ErrorResponse("Project ID is required.", "Invalid Input Parameter", 400); return ApiResponse<object>.ErrorResponse("Project ID is required.", "Invalid Input Parameter", 400);
@ -573,7 +581,7 @@ namespace Marco.Pms.Services.Service
// --- Step 2: Security and Existence Checks --- // --- Step 2: Security and Existence Checks ---
// Before fetching data, you MUST verify the user has permission to see it. // Before fetching data, you MUST verify the user has permission to see it.
// This is a placeholder for your actual permission logic. // This is a placeholder for your actual permission logic.
var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId);
if (!hasPermission) if (!hasPermission)
{ {
_logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId}.", loggedInEmployee.Id, projectId); _logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId}.", loggedInEmployee.Id, projectId);
@ -582,11 +590,27 @@ namespace Marco.Pms.Services.Service
// --- Step 3: Execute a Single, Optimized Database Query --- // --- Step 3: Execute a Single, Optimized Database Query ---
// This query projects directly to a new object on the database server, which is highly efficient. // This query projects directly to a new object on the database server, which is highly efficient.
var allocations = await _context.ProjectAllocations var projectAllocationQuery = _context.ProjectAllocations
// Filter down to the relevant records first. .Include(pa => pa.Employee)
.Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId && pa.Employee != null) .ThenInclude(e => e!.JobRole)
// Project directly to the final shape. This tells EF Core which columns to select. .Include(pa => pa.Employee)
// The redundant .Include() is removed as EF Core infers the JOIN from this Select. .ThenInclude(e => e!.Organization)
.Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId);
// Conditionally apply the filter for active allocations.
if (!includeInactive)
{
projectAllocationQuery = projectAllocationQuery.Where(pa => pa.IsActive);
}
// Conditionally apply the filter for organization ID.
if (organizationId.HasValue)
{
projectAllocationQuery = projectAllocationQuery
.Where(pa => pa.Employee != null && pa.Employee.OrganizationId == organizationId.Value && pa.Employee.Organization != null);
}
var allocations = await projectAllocationQuery
.Select(pa => new .Select(pa => new
{ {
// Fields from ProjectAllocation // Fields from ProjectAllocation
@ -602,6 +626,8 @@ namespace Marco.Pms.Services.Service
LastName = pa.Employee.LastName, LastName = pa.Employee.LastName,
MiddleName = pa.Employee.MiddleName, MiddleName = pa.Employee.MiddleName,
OrganizationName = pa.Employee.Organization!.Name,
// Simplified JobRoleId logic: Use the allocation's role if it exists, otherwise fall back to the employee's default role. // Simplified JobRoleId logic: Use the allocation's role if it exists, otherwise fall back to the employee's default role.
JobRoleId = pa.JobRoleId ?? pa.Employee.JobRoleId JobRoleId = pa.JobRoleId ?? pa.Employee.JobRoleId
}) })

View File

@ -17,8 +17,8 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<object>> GetProjectDetailsOldAsync(Guid id, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetProjectDetailsOldAsync(Guid id, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> CreateProjectAsync(CreateProjectDto projectDto, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> CreateProjectAsync(CreateProjectDto projectDto, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetEmployeeByProjectIdAsync(Guid? projectId, bool includeInactive, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetEmployeeByProjectIdAsync(Guid projectId, Guid? organizationId, bool includeInactive, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetProjectAllocationAsync(Guid? projectId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetProjectAllocationAsync(Guid projectId, Guid? organizationId, bool includeInactive, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> projectAllocationDots, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> projectAllocationDots, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetProjectsByEmployeeAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetProjectsByEmployeeAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee);