From fe1dfd729336f359060cc831d96db2830af4d99a Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 24 Sep 2025 17:23:32 +0530 Subject: [PATCH] Getting the employee list of user's organization only --- .../Controllers/EmployeeController.cs | 150 ++++++++++++------ .../Controllers/OrganizationController.cs | 34 ++-- Marco.Pms.Services/Helpers/EmployeeHelper.cs | 39 ++--- 3 files changed, 143 insertions(+), 80 deletions(-) diff --git a/Marco.Pms.Services/Controllers/EmployeeController.cs b/Marco.Pms.Services/Controllers/EmployeeController.cs index 3dcdbee..f08c44c 100644 --- a/Marco.Pms.Services/Controllers/EmployeeController.cs +++ b/Marco.Pms.Services/Controllers/EmployeeController.cs @@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; +using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; using System.Data; using System.Net; @@ -33,7 +34,7 @@ namespace MarcoBMS.Services.Controllers { private readonly ApplicationDbContext _context; - private readonly IServiceScopeFactory _serviceScope; + private readonly IServiceScopeFactory _serviceScopeFactory; private readonly UserManager _userManager; private readonly IEmailSender _emailSender; private readonly EmployeeHelper _employeeHelper; @@ -49,7 +50,7 @@ namespace MarcoBMS.Services.Controllers private readonly Guid organizationId; - public EmployeeController(IServiceScopeFactory serviceScope, + public EmployeeController(IServiceScopeFactory serviceScopeFactory, UserManager userManager, IEmailSender emailSender, ApplicationDbContext context, @@ -63,7 +64,7 @@ namespace MarcoBMS.Services.Controllers IMapper mapper, GeneralHelper generalHelper) { - _serviceScope = serviceScope; + _serviceScopeFactory = serviceScopeFactory; _context = context; _userManager = userManager; _emailSender = emailSender; @@ -119,9 +120,8 @@ namespace MarcoBMS.Services.Controllers } } - [HttpGet] - [Route("list/{projectid?}")] - public async Task GetEmployeesByProject(Guid? projectid, [FromQuery] bool ShowInactive) + [HttpGet("list/{projectId?}")] + public async Task GetEmployeesByProjectAsync(Guid? projectId, [FromQuery] bool showInactive = false) { // Step 1: Validate incoming request model state if (!ModelState.IsValid) @@ -135,56 +135,112 @@ namespace MarcoBMS.Services.Controllers return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } - // Step 2: Get logged-in employee - var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - _logger.LogInfo("GetEmployeesByProject called by EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, ShowInactive: {ShowInactive}", - loggedInEmployee.Id, projectid ?? Guid.Empty, ShowInactive); - - // Step 3: Fetch project access and permissions - var projectIds = await _projectServices.GetMyProjectIdsAsync(tenantId, loggedInEmployee); - - var hasViewAllEmployeesPermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id); - var hasViewTeamMembersPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id); - - List result = new(); - - // Step 4: Determine access level and fetch employees accordingly - if (hasViewAllEmployeesPermission || projectid != null) + List result = new List(); + try { - result = await _employeeHelper.GetEmployeeByProjectId(tenantId, projectid, ShowInactive); - _logger.LogInfo("Employee list fetched using full access or specific project."); - } - else if (hasViewTeamMembersPermission && !ShowInactive) - { - var employeeIds = await _context.ProjectAllocations - .Where(pa => projectIds.Contains(pa.ProjectId) && pa.IsActive && pa.TenantId == tenantId) - .Select(pa => pa.EmployeeId) - .Distinct() - .ToListAsync(); + // Dependency injection scope for services + using var scope = _serviceScopeFactory.CreateScope(); - var employees = await _context.Employees - .Include(fp => fp.JobRole) - .Where(e => employeeIds.Contains(e.Id) && e.JobRole != null && e.IsActive && e.TenantId == tenantId) + // Step 2: Get logged-in employee details + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + _logger.LogInfo("GetEmployeesByProject called. EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, showInactive: {ShowInactive}", + loggedInEmployee.Id, projectId ?? Guid.Empty, showInactive); + + // Step 3: Fetch permissions concurrently + var viewAllTask = Task.Run(async () => + { + var _permission = scope.ServiceProvider.GetRequiredService(); + return await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id); + }); + var viewTeamTask = Task.Run(async () => + { + var _permission = scope.ServiceProvider.GetRequiredService(); + return await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id); + }); + + await Task.WhenAll(viewAllTask, viewTeamTask); + + var hasViewAllEmployeesPermission = viewAllTask.Result; + var hasViewTeamMembersPermission = viewTeamTask.Result; + + List employees = new List(); + + // Step 4: Query based on permission + if (hasViewAllEmployeesPermission && !projectId.HasValue) + { + // OrganizationId needs to be retrieved from loggedInEmployee or context based on your app's structure + var employeeQuery = _context.Employees + .AsNoTracking() // Optimize EF query for read-only operation[web:1][web:13][web:18] + .Include(e => e.JobRole) + .Where(e => e.OrganizationId == organizationId); + + employeeQuery = showInactive + ? employeeQuery.Where(e => !e.IsActive) + : employeeQuery.Where(e => e.IsActive); + + employees = await employeeQuery.ToListAsync(); + _logger.LogInfo("Employee list fetched with full access. Count: {Count}", employees.Count); + } + else if (hasViewTeamMembersPermission && !showInactive && !projectId.HasValue) + { + // Only active team members with limited permission + var projectIds = await _projectServices.GetMyProjectIdsAsync(tenantId, loggedInEmployee); + + employees = await _context.ProjectAllocations + .AsNoTracking() + .Include(pa => pa.Employee) + .ThenInclude(e => e!.JobRole) + .Where(pa => + projectIds.Contains(pa.ProjectId) + && pa.IsActive + && pa.Employee != null + && pa.Employee.IsActive + && pa.TenantId == tenantId) + .Select(pa => pa.Employee!) .Distinct() .ToListAsync(); - result = employees.Select(e => e.ToEmployeeVMFromEmployee()).ToList(); + _logger.LogInfo("Employee list fetched with limited access (active only). Count: {Count}", employees.Count); + } + + // If a specific projectId is provided, override employee fetching to ensure strict project context + if (projectId.HasValue) + { + employees = await _context.ProjectAllocations + .AsNoTracking() + .Include(pa => pa.Employee) + .ThenInclude(e => e!.JobRole) + .Where(pa => + pa.ProjectId == projectId + && pa.IsActive + && pa.Employee != null + && pa.Employee.IsActive + && pa.TenantId == tenantId) + .Select(pa => pa.Employee!) + .Distinct() + .ToListAsync(); + + _logger.LogInfo("Employee list fetched for specific project. ProjectId: {ProjectId}. Count: {Count}", + projectId, employees.Count); + } + + // Step 5: Map to view model + result = employees.Select(e => _mapper.Map(e)).Distinct().ToList(); + + _logger.LogInfo("Employees successfully fetched. EmployeeId: {EmployeeId} for ProjectId: {ProjectId}. Final Count: {Count}", + loggedInEmployee.Id, projectId ?? Guid.Empty, result.Count); - _logger.LogInfo("Employee list fetched using limited access (active only)."); - } - else - { - _logger.LogWarning("Access denied for EmployeeId: {EmployeeId} - insufficient permissions.", loggedInEmployee.Id); return Ok(ApiResponse.SuccessResponse(result, "Filter applied.", 200)); } - - // Step 5: Log and return results - _logger.LogInfo("Employees fetched successfully by EmployeeId: {EmployeeId} for ProjectId: {ProjectId}. Count: {Count}", - loggedInEmployee.Id, projectid ?? Guid.Empty, result.Count); - - return Ok(ApiResponse.SuccessResponse(result, "Filter applied.", 200)); + catch (Exception ex) + { + // Step 6: Error logging and response[web:6] + _logger.LogError(ex, "Exception occurred while getting the list of employees"); + return StatusCode(500, ApiResponse.ErrorResponse("Internal server error. Please try again later.", null, 500)); + } } + [HttpGet("basic")] public async Task GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] string? searchString) { @@ -857,7 +913,7 @@ namespace MarcoBMS.Services.Controllers [HttpDelete("{id}")] public async Task SuspendEmployee(Guid id, [FromQuery] bool active = false) { - using var scope = _serviceScope.CreateScope(); + using var scope = _serviceScopeFactory.CreateScope(); Guid tenantId = _userHelper.GetTenantId(); var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync(); diff --git a/Marco.Pms.Services/Controllers/OrganizationController.cs b/Marco.Pms.Services/Controllers/OrganizationController.cs index a9544a8..7daba1d 100644 --- a/Marco.Pms.Services/Controllers/OrganizationController.cs +++ b/Marco.Pms.Services/Controllers/OrganizationController.cs @@ -357,6 +357,7 @@ namespace Marco.Pms.Services.Controllers SPRID = organization.SPRID, AssignedDate = DateTime.UtcNow, IsActive = true, + AssignedById = loggedInEmployee.Id, TenantId = project.TenantId }; _context.TenantOrgMappings.Add(newServiceProviderTenantMapping); @@ -365,20 +366,21 @@ namespace Marco.Pms.Services.Controllers List projectOrgMappings = new List(); List projectServiceMappings = new List(); + if (isPMC && model.OrganizationTypeId != ServiceProvider && model.OrganizationTypeId != SubContractorProvider) + { + return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "You don't have access to assign this type of organization", 403)); + } + if (isServiceProvider && model.OrganizationTypeId == ServiceProvider) + { + return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "You don't have access to assign this type of organization", 403)); + } + foreach (var serviceId in model.ServiceIds) { - if (isPMC && model.OrganizationTypeId != ServiceProvider && model.OrganizationTypeId != SubContractorProvider) + var service = await _context.ServiceMasters.FirstOrDefaultAsync(s => s.Id == serviceId); + if (service == null) { - continue; - } - if (isServiceProvider && model.OrganizationTypeId == ServiceProvider) - { - continue; - } - var isServiceExist = await _context.ServiceMasters.AnyAsync(s => s.Id == serviceId); - if (!isServiceExist) - { - continue; + return NotFound(ApiResponse.ErrorResponse("Service not found", "Service not found in database", 404)); } var projectService = projectServices.FirstOrDefault(ps => ps.ServiceId == serviceId); if (projectService == null) @@ -403,14 +405,17 @@ namespace Marco.Pms.Services.Controllers OrganizationTypeId = model.OrganizationTypeId, ParentOrganizationId = model.ParentOrganizationId ?? loggedInEmployee.OrganizationId, AssignedDate = DateTime.UtcNow, + AssignedById = loggedInEmployee.Id, TenantId = project.TenantId }; var projectOrganization = projectOrganizations - .FirstOrDefault(po => po.ProjectService != null && po.ProjectService.ProjectId == project.Id && po.ProjectService.ServiceId == serviceId); - if (projectOrganization == null) + .FirstOrDefault(po => po.ProjectService != null && po.ProjectService.ProjectId == project.Id && po.ProjectService.ServiceId == serviceId + && po.OrganizationId == model.OrganizationId); + if (projectOrganization != null) { - projectOrgMappings.Add(projectOrgMapping); + return StatusCode(409, ApiResponse.ErrorResponse("Organization is already assigned to this project", "Organization is already assigned to this project", 409)); } + projectOrgMappings.Add(projectOrgMapping); } if (projectServiceMappings.Any()) @@ -495,6 +500,7 @@ namespace Marco.Pms.Services.Controllers SPRID = organization.SPRID, AssignedDate = DateTime.UtcNow, IsActive = true, + AssignedById = loggedInEmployee.Id, TenantId = tenantId }; _context.TenantOrgMappings.Add(newServiceProviderTenantMapping); diff --git a/Marco.Pms.Services/Helpers/EmployeeHelper.cs b/Marco.Pms.Services/Helpers/EmployeeHelper.cs index 6187628..717788b 100644 --- a/Marco.Pms.Services/Helpers/EmployeeHelper.cs +++ b/Marco.Pms.Services/Helpers/EmployeeHelper.cs @@ -1,4 +1,5 @@  +using AutoMapper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Mapper; @@ -13,10 +14,12 @@ namespace MarcoBMS.Services.Helpers { private readonly ApplicationDbContext _context; private readonly ILoggingService _logger; - public EmployeeHelper(ApplicationDbContext context, ILoggingService logger) + private readonly IMapper _mapper; + public EmployeeHelper(ApplicationDbContext context, ILoggingService logger, IMapper mapper) { _context = context; _logger = logger; + _mapper = mapper; } public async Task GetEmployeeByID(Guid EmployeeID) { @@ -72,38 +75,36 @@ namespace MarcoBMS.Services.Helpers } } - public async Task> GetEmployeeByProjectId(Guid tenantId, Guid? projectId, bool ShowInActive) + public async Task> GetEmployeeByProjectId(Guid organizationId, Guid tenantId, Guid? projectId, bool ShowInActive) { try { - List result = new List(); + List employees = new List(); if (projectId.HasValue) { - var employeeIds = await _context.ProjectAllocations - .Where(pa => projectId == pa.ProjectId && pa.IsActive && pa.TenantId == tenantId) - .Select(pa => pa.EmployeeId) - .Distinct() - .ToListAsync(); - - var employees = await _context.Employees - .Include(fp => fp.JobRole) - .Where(e => employeeIds.Contains(e.Id) && e.JobRole != null && e.IsActive && e.TenantId == tenantId) + employees = await _context.ProjectAllocations + .Include(pa => pa.Employee) + .ThenInclude(e => e!.JobRole) + .Where(pa => projectId == pa.ProjectId && pa.IsActive && pa.TenantId == tenantId && pa.Employee != null && pa.Employee.IsActive) + .Select(pa => pa.Employee!) .Distinct() .ToListAsync(); - result = employees.Select(e => e.ToEmployeeVMFromEmployee()).ToList(); - } else if (ShowInActive) { - result = await _context.Employees.Where(c => c.TenantId == tenantId && c.IsActive == false).Include(fp => fp.JobRole) - .Select(c => c.ToEmployeeVMFromEmployee()).ToListAsync(); + employees = await _context.Employees + .Include(fp => fp.JobRole) + .Where(c => c.OrganizationId == organizationId && c.IsActive == false) + .ToListAsync(); } else { - result = await _context.Employees.Where(c => c.TenantId == tenantId && c.IsActive == true).Include(fp => fp.JobRole) - .Select(c => c.ToEmployeeVMFromEmployee()).ToListAsync(); + employees = await _context.Employees + .Include(fp => fp.JobRole) + .Where(c => c.OrganizationId == organizationId && c.IsActive == true) + .ToListAsync(); } - + var result = employees.Select(e => _mapper.Map(e)).Distinct().ToList(); return result; } catch (Exception ex)