From 9b59a4d6b66b505bccecbf8958694cfaac838f4f Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Sat, 20 Sep 2025 14:58:32 +0530 Subject: [PATCH] Added the organization filter in get project allocation API --- .../Controllers/ProjectController.cs | 10 ++-- Marco.Pms.Services/Service/ProjectServices.cs | 54 ++++++++++++++----- .../ServiceInterfaces/IProjectServices.cs | 4 +- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index f8a8275..f087d61 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -201,8 +201,8 @@ namespace MarcoBMS.Services.Controllers #region =================================================================== Project Allocation APIs =================================================================== - [HttpGet("employees/get/{projectid?}/{includeInactive?}")] - public async Task GetEmployeeByProjectId(Guid? projectId, bool includeInactive = false) + [HttpGet("employees/get/{projectId}")] + public async Task GetEmployeeByProjectId(Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] bool includeInactive = false) { // --- Step 1: Input Validation --- if (!ModelState.IsValid) @@ -214,12 +214,12 @@ namespace MarcoBMS.Services.Controllers // --- Step 2: Prepare data without I/O --- 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); } [HttpGet("allocation/{projectId}")] - public async Task GetProjectAllocation(Guid? projectId) + public async Task GetProjectAllocation(Guid projectId, [FromQuery] Guid? organizationId, [FromQuery] bool includeInactive = false) { // --- Step 1: Input Validation --- if (!ModelState.IsValid) @@ -231,7 +231,7 @@ namespace MarcoBMS.Services.Controllers // --- Step 2: Prepare data without I/O --- 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); } diff --git a/Marco.Pms.Services/Service/ProjectServices.cs b/Marco.Pms.Services/Service/ProjectServices.cs index 37c3a6b..31d69ce 100644 --- a/Marco.Pms.Services/Service/ProjectServices.cs +++ b/Marco.Pms.Services/Service/ProjectServices.cs @@ -480,10 +480,10 @@ namespace Marco.Pms.Services.Service /// The ID of the current tenant. /// The current authenticated employee (used for permission checks). /// An ApiResponse containing a list of employees or an error. - public async Task> GetEmployeeByProjectIdAsync(Guid? projectId, bool includeInactive, Guid tenantId, Employee loggedInEmployee) + public async Task> GetEmployeeByProjectIdAsync(Guid projectId, Guid? organizationId, bool includeInactive, Guid tenantId, Employee loggedInEmployee) { // --- Step 1: Input Validation --- - if (projectId == null) + if (projectId == Guid.Empty) { _logger.LogWarning("GetEmployeeByProjectID called with a null projectId."); // 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 --- // Before fetching data, you MUST verify the user has permission to see it. // 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 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. // This avoids code duplication and is highly performant. var employeeQuery = _context.ProjectAllocations + .Include(pa => pa.Employee) .Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId); // Conditionally apply the filter for active allocations. @@ -522,16 +523,23 @@ namespace Marco.Pms.Services.Service 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 --- // This is the most significant performance optimization. // 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. - var resultVM = await employeeQuery + + var projectAllocations = await employeeQuery .Where(pa => pa.Employee != null) // Safety check for data integrity - .Select(pa => pa.Employee) // Navigate to the Employee entity - .ProjectTo(_mapper.ConfigurationProvider) // Let AutoMapper generate the SELECT .ToListAsync(); + var resultVM = projectAllocations.Select(pa => _mapper.Map(pa.Employee)).ToList(); + _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. @@ -554,10 +562,10 @@ namespace Marco.Pms.Services.Service /// The ID of the current tenant. /// The current authenticated employee for permission checks. /// An ApiResponse containing allocation details or an appropriate error. - public async Task> GetProjectAllocationAsync(Guid? projectId, Guid tenantId, Employee loggedInEmployee) + public async Task> GetProjectAllocationAsync(Guid projectId, Guid? organizationId, bool includeInactive, Guid tenantId, Employee loggedInEmployee) { // --- Step 1: Input Validation --- - if (projectId == null) + if (projectId == Guid.Empty) { _logger.LogWarning("GetProjectAllocation called with a null projectId."); return ApiResponse.ErrorResponse("Project ID is required.", "Invalid Input Parameter", 400); @@ -573,7 +581,7 @@ namespace Marco.Pms.Services.Service // --- Step 2: Security and Existence Checks --- // Before fetching data, you MUST verify the user has permission to see it. // 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) { _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 --- // This query projects directly to a new object on the database server, which is highly efficient. - var allocations = await _context.ProjectAllocations - // Filter down to the relevant records first. - .Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId && pa.Employee != null) - // Project directly to the final shape. This tells EF Core which columns to select. - // The redundant .Include() is removed as EF Core infers the JOIN from this Select. + var projectAllocationQuery = _context.ProjectAllocations + .Include(pa => pa.Employee) + .ThenInclude(e => e!.JobRole) + .Include(pa => pa.Employee) + .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 { // Fields from ProjectAllocation @@ -602,6 +626,8 @@ namespace Marco.Pms.Services.Service LastName = pa.Employee.LastName, 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. JobRoleId = pa.JobRoleId ?? pa.Employee.JobRoleId }) diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs index 57bbfe4..36f5142 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs @@ -17,8 +17,8 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task> GetProjectDetailsOldAsync(Guid id, Guid tenantId, Employee loggedInEmployee); Task> CreateProjectAsync(CreateProjectDto projectDto, Guid tenantId, Employee loggedInEmployee); Task> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee); - Task> GetEmployeeByProjectIdAsync(Guid? projectId, bool includeInactive, Guid tenantId, Employee loggedInEmployee); - Task> GetProjectAllocationAsync(Guid? projectId, Guid tenantId, Employee loggedInEmployee); + Task> GetEmployeeByProjectIdAsync(Guid projectId, Guid? organizationId, bool includeInactive, Guid tenantId, Employee loggedInEmployee); + Task> GetProjectAllocationAsync(Guid projectId, Guid? organizationId, bool includeInactive, Guid tenantId, Employee loggedInEmployee); Task>> ManageAllocationAsync(List projectAllocationDots, Guid tenantId, Employee loggedInEmployee); Task> GetProjectsByEmployeeAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task>> AssigneProjectsToEmployeeAsync(List projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee);