diff --git a/Marco.Pms.Services/Controllers/EmployeeController.cs b/Marco.Pms.Services/Controllers/EmployeeController.cs index cdc28ed..3f39412 100644 --- a/Marco.Pms.Services/Controllers/EmployeeController.cs +++ b/Marco.Pms.Services/Controllers/EmployeeController.cs @@ -191,24 +191,89 @@ namespace MarcoBMS.Services.Controllers var response = await employeeQuery.Take(10).Select(e => _mapper.Map(e)).ToListAsync(); return Ok(ApiResponse.SuccessResponse(response, $"{response.Count} records of employees fetched successfully", 200)); } - [HttpGet] - [Route("search/{name}/{projectid?}")] - public async Task SearchEmployee(string name, Guid? projectid) + + /// + /// Retrieves a paginated list of employees assigned to a specified project (if provided), + /// with optional search functionality. + /// Ensures that the logged-in user has necessary permissions before accessing project employees. + /// + /// Optional project identifier to filter employees by project. + /// Optional search string to filter employees by name. + /// Page number for pagination (default = 1). + /// Paginated list of employees in BasicEmployeeVM format wrapped in ApiResponse. + + [HttpGet("search")] + public async Task GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] string? searchString, [FromQuery] int pageNumber = 1) { - if (!ModelState.IsValid) + const int pageSize = 10; // Fixed page size for pagination + + // Log API entry with context + _logger.LogInfo("Fetching employees. ProjectId: {ProjectId}, SearchString: {SearchString}, PageNumber: {PageNumber}", + projectId ?? Guid.Empty, searchString ?? "", pageNumber); + + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + _logger.LogDebug("Logged-in EmployeeId: {EmployeeId}", loggedInEmployee.Id); + + // Initialize query scoped by tenant + var employeeQuery = _context.Employees.Where(e => e.TenantId == tenantId); + + // Filter by project if projectId is supplied + if (projectId.HasValue && projectId.Value != Guid.Empty) { - var errors = ModelState.Values - .SelectMany(v => v.Errors) - .Select(e => e.ErrorMessage) - .ToList(); - return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); + _logger.LogDebug("Project filter applied. Checking permission for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", + loggedInEmployee.Id, projectId); + // Validate project access permission + var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); + if (!hasProjectPermission) + { + _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have permission for ProjectId: {ProjectId}", + loggedInEmployee.Id, projectId); + + return StatusCode(403, ApiResponse.ErrorResponse( + "Access denied", + "User does not have access to view employees for this project", + 403)); + } + + // Employees allocated to the project + var employeeIds = await _context.ProjectAllocations + .Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.TenantId == tenantId) + .Select(pa => pa.EmployeeId) + .ToListAsync(); + + _logger.LogDebug("Project employees retrieved. Total linked employees found: {Count}", employeeIds.Count); + + // Apply project allocation filter + employeeQuery = employeeQuery.Where(e => employeeIds.Contains(e.Id)); } - var result = await _employeeHelper.SearchEmployeeByProjectId(GetTenantId(), name.ToLower(), projectid); - return Ok(ApiResponse.SuccessResponse(result, "Filter applied.", 200)); + // Apply search filter if provided + if (!string.IsNullOrWhiteSpace(searchString)) + { + var searchStringLower = searchString.ToLower(); + _logger.LogDebug("Search filter applied. Search term: {SearchTerm}", searchStringLower); + + employeeQuery = employeeQuery.Where(e => + (e.FirstName + " " + e.LastName).ToLower().Contains(searchStringLower)); + } + + // Pagination and Projection (executed in DB) + var employees = await employeeQuery + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(e => _mapper.Map(e)) + .ToListAsync(); + + _logger.LogInfo("Employees fetched successfully. Records returned: {Count}", employees.Count); + + return Ok(ApiResponse.SuccessResponse( + employees, + $"{employees.Count} employee records fetched successfully", + 200)); } + [HttpGet] [Route("profile/get/{employeeId}")] public async Task GetEmployeeProfileById(Guid employeeId)