diff --git a/Marco.Pms.Services/Controllers/EmployeeController.cs b/Marco.Pms.Services/Controllers/EmployeeController.cs index 5448e16..8a084a5 100644 --- a/Marco.Pms.Services/Controllers/EmployeeController.cs +++ b/Marco.Pms.Services/Controllers/EmployeeController.cs @@ -32,7 +32,7 @@ namespace MarcoBMS.Services.Controllers public class EmployeeController : ControllerBase { - + private readonly IDbContextFactory _dbContextFactory; private readonly ApplicationDbContext _context; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly UserManager _userManager; @@ -50,7 +50,8 @@ namespace MarcoBMS.Services.Controllers private readonly Guid organizationId; - public EmployeeController(IServiceScopeFactory serviceScopeFactory, + public EmployeeController(IDbContextFactory dbContextFactory, + IServiceScopeFactory serviceScopeFactory, UserManager userManager, IEmailSender emailSender, ApplicationDbContext context, @@ -64,6 +65,7 @@ namespace MarcoBMS.Services.Controllers IMapper mapper, GeneralHelper generalHelper) { + _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); _serviceScopeFactory = serviceScopeFactory; _context = context; _userManager = userManager; @@ -120,7 +122,7 @@ namespace MarcoBMS.Services.Controllers } } - [HttpGet("list/project/{projectId}")] + [HttpGet("list/organizations/{projectId}")] public async Task GetEmployeesByProjectAsync(Guid projectId, [FromQuery] string searchString) { try @@ -128,6 +130,28 @@ namespace MarcoBMS.Services.Controllers // Get the currently logged-in employee information var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var projectTask = Task.Run(async () => + { + await using var context = await _dbContextFactory.CreateDbContextAsync(); + return await context.Projects.FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId); + }); + + var tenantTask = Task.Run(async () => + { + await using var context = await _dbContextFactory.CreateDbContextAsync(); + return await context.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId); + }); + + await Task.WhenAll(projectTask, tenantTask); + + var project = projectTask.Result; + var tenant = tenantTask.Result; + + if (project == null || tenant == null) + { + _logger.LogWarning("Project {ProjectId} not found in database for tenant {TenantId}", projectId, tenantId); + return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found", 404)); + } // Check if the logged-in employee has permission for the requested project var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId); if (!hasProjectPermission) @@ -136,20 +160,36 @@ namespace MarcoBMS.Services.Controllers return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "User does not have access to view the employees for this project", 403)); } + var organizationQuery = _context.ProjectOrgMappings + .Include(po => po.ProjectService) + .Where(po => po.ProjectService != null && po.ProjectService.ProjectId == projectId); + + if (loggedInEmployee.OrganizationId != project.PMCId && loggedInEmployee.OrganizationId != project.PromoterId && loggedInEmployee.OrganizationId != tenant.OrganizationId) + { + organizationQuery = organizationQuery.Where(po => po.ParentOrganizationId == loggedInEmployee.OrganizationId || po.OrganizationId == loggedInEmployee.OrganizationId); + } + + var organizationIds = await organizationQuery.Select(po => po.OrganizationId).ToListAsync(); + + if (loggedInEmployee.OrganizationId == project.PMCId || loggedInEmployee.OrganizationId == project.PromoterId || loggedInEmployee.OrganizationId == tenant.OrganizationId) + { + organizationIds.Add(project.PMCId); + organizationIds.Add(project.PromoterId); + organizationIds.Add(tenant.OrganizationId); + } + // Fetch employees allocated to the project matching the search criteria - var employees = await _context.ProjectAllocations + var employees = await _context.Employees .AsNoTracking() // Improves performance by disabling change tracking for read-only query - .Include(pa => pa.Employee) - .ThenInclude(e => e!.JobRole) - .Where(pa => pa.ProjectId == projectId && pa.Employee != null && - (pa.Employee.FirstName + " " + pa.Employee.LastName).Contains(searchString)) - .Select(pa => pa.Employee!) + .Include(e => e.JobRole) + .Where(e => (e.FirstName + " " + e.LastName).Contains(searchString) && organizationIds.Contains(e.OrganizationId)) .ToListAsync(); + var result = employees.Select(e => _mapper.Map(e)).Distinct().ToList(); _logger.LogInfo("Employees fetched for project {ProjectId} by user {EmployeeId}. Count: {Count}", projectId, loggedInEmployee.Id, employees.Count); // Return the employee list wrapped in a successful API response - return Ok(ApiResponse.SuccessResponse(employees, "Employee list fetched successfully", 200)); + return Ok(ApiResponse.SuccessResponse(result, "Employee list fetched successfully", 200)); } catch (Exception ex) { diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index c48cc5f..dfa0361 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -324,7 +324,7 @@ namespace MarcoBMS.Services.Controllers #region =================================================================== Project InfraStructure Get APIs =================================================================== [HttpGet("infra-details/{projectId}")] - public async Task GetInfraDetails(Guid projectId) + public async Task GetInfraDetails(Guid projectId, [FromQuery] Guid? serviceId) { // --- Step 1: Input Validation --- if (!ModelState.IsValid) @@ -336,7 +336,7 @@ namespace MarcoBMS.Services.Controllers // --- Step 2: Prepare data without I/O --- Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - var response = await _projectServices.GetInfraDetailsAsync(projectId, tenantId, loggedInEmployee); + var response = await _projectServices.GetInfraDetailsAsync(projectId, serviceId, tenantId, loggedInEmployee); return StatusCode(response.StatusCode, response); } @@ -357,6 +357,7 @@ namespace MarcoBMS.Services.Controllers var response = await _projectServices.GetWorkItemsAsync(workAreaId, serviceId, tenantId, loggedInEmployee); return StatusCode(response.StatusCode, response); } + [HttpGet("tasks-employee/{employeeId}")] public async Task GetTasksByEmployee(Guid employeeId, [FromQuery] DateTime? fromDate, DateTime? toDate) { diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index bb256a9..647d0bd 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -177,7 +177,10 @@ namespace Marco.Pms.Services.MappingProfiles #region ======================================================= Employee ======================================================= - CreateMap(); + CreateMap() + .ForMember( + dest => dest.JobRole, + opt => opt.MapFrom(src => src.JobRole != null ? src.JobRole.Name : "")); CreateMap(); CreateMap(); diff --git a/Marco.Pms.Services/Service/ProjectServices.cs b/Marco.Pms.Services/Service/ProjectServices.cs index d791543..4744089 100644 --- a/Marco.Pms.Services/Service/ProjectServices.cs +++ b/Marco.Pms.Services/Service/ProjectServices.cs @@ -1176,7 +1176,7 @@ namespace Marco.Pms.Services.Service /// Retrieves the full infrastructure hierarchy (Buildings, Floors, Work Areas) for a project, /// including aggregated work summaries. /// - public async Task> GetInfraDetailsAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee) + public async Task> GetInfraDetailsAsync(Guid projectId, Guid? serviceId, Guid tenantId, Employee loggedInEmployee) { _logger.LogInfo("GetInfraDetails called for ProjectId: {ProjectId}", projectId); @@ -1223,19 +1223,74 @@ namespace Marco.Pms.Services.Service if (cachedResult != null) { _logger.LogInfo("Cache HIT for infra details for ProjectId: {ProjectId}", projectId); - return ApiResponse.SuccessResponse(cachedResult, "Infra details fetched successfully from cache.", 200); } _logger.LogInfo("Cache MISS for infra details for ProjectId: {ProjectId}. Fetching from database.", projectId); // --- Step 3: Fetch all required data from the database --- - - var buildingMongoList = await _generalHelper.GetProjectInfraFromDB(projectId); + if (cachedResult == null) + { + cachedResult = await _generalHelper.GetProjectInfraFromDB(projectId); + } // --- Step 5: Proactively update the cache --- //await _cache.SetBuildingInfra(projectId, buildingMongoList); - _logger.LogInfo("Infra details fetched successfully for ProjectId: {ProjectId}, Buildings: {Count}", projectId, buildingMongoList.Count); - return ApiResponse.SuccessResponse(buildingMongoList, "Infra details fetched successfully", 200); + if (serviceId.HasValue) + { + var workAreaIds = cachedResult + .SelectMany(b => b.Floors) + .SelectMany(f => f.WorkAreas) + .Select(w => Guid.Parse(w.Id)) + .ToList(); + + var workItems = await _context.WorkItems.Where(wi => workAreaIds.Contains(wi.WorkAreaId) + && wi.ActivityMaster != null + && wi.ActivityMaster.ActivityGroup != null + && wi.ActivityMaster.ActivityGroup.ServiceId == serviceId) + .GroupBy(wi => wi.WorkAreaId) + .Select(g => new + { + WorkAreaId = g.Key, + PlannedWork = g.Sum(wi => wi.PlannedWork), + CompletedWork = g.Sum(wi => wi.CompletedWork) + }) + .ToListAsync(); + + cachedResult = cachedResult.Select(b => + { + double buildingPlanned = 0, buildingCompleted = 0; + var floors = b.Floors.Select(f => + { + double floorPlanned = 0, floorCompleted = 0; + var workArea = f.WorkAreas.Select(wa => + { + var workItem = workItems.FirstOrDefault(wi => wi.WorkAreaId == Guid.Parse(wa.Id)); + wa.PlannedWork = workItem?.PlannedWork ?? 0; + wa.CompletedWork = workItem?.CompletedWork ?? 0; + + floorPlanned += workItem?.PlannedWork ?? 0; + floorCompleted += workItem?.CompletedWork ?? 0; + + return wa; + }).ToList(); + + f.PlannedWork = floorPlanned; + f.CompletedWork = floorCompleted; + + buildingPlanned += floorPlanned; + buildingCompleted += floorCompleted; + + return f; + }).ToList(); + b.PlannedWork = buildingPlanned; + b.CompletedWork = buildingCompleted; + return b; + }).ToList(); + + } + + _logger.LogInfo("Infra details fetched successfully for ProjectId: {ProjectId}, Buildings: {Count}", projectId, cachedResult.Count); + return ApiResponse.SuccessResponse(cachedResult, "Infra details fetched successfully", 200); } catch (Exception ex) { diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs index bab113d..5c3158c 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs @@ -24,7 +24,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task>> AssigneProjectsToEmployeeAsync(List projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task> GetProjectByEmployeeBasicAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee); - Task> GetInfraDetailsAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee); + Task> GetInfraDetailsAsync(Guid projectId, Guid? serviceId, Guid tenantId, Employee loggedInEmployee); Task> GetWorkItemsAsync(Guid workAreaId, Guid? serviceId, Guid tenantId, Employee loggedInEmployee); Task> GetTasksByEmployeeAsync(Guid employeeId, DateTime fromDate, DateTime toDate, Guid tenantId, Employee loggedInEmployee); Task ManageProjectInfraAsync(List infraDtos, Guid tenantId, Employee loggedInEmployee);