diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index 403dccf..0e9bb66 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -174,7 +174,7 @@ namespace MarcoBMS.Services.Controllers var hasTeamAttendancePermission = await _permission.HasPermission(PermissionsMaster.TeamAttendance, loggedInEmployee.Id); var hasSelfAttendancePermission = await _permission.HasPermission(PermissionsMaster.SelfAttendance, loggedInEmployee.Id); - var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasProjectPermission) { @@ -353,7 +353,7 @@ namespace MarcoBMS.Services.Controllers return NotFound(ApiResponse.ErrorResponse("Project not found.")); } - if (!await _permission.HasProjectPermission(loggedInEmployee, projectId)) + if (!await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId)) { _logger.LogWarning("Unauthorized access attempt by EmployeeId: {EmployeeId} for ProjectId: {ProjectId}", loggedInEmployee.Id, projectId); return Unauthorized(ApiResponse.ErrorResponse("You do not have permission to access this project.")); @@ -399,7 +399,7 @@ namespace MarcoBMS.Services.Controllers Employee LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var result = new List(); - var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId); + var hasProjectPermission = await _permission.HasInfraProjectPermission(LoggedInEmployee.Id, projectId); if (!hasProjectPermission) { diff --git a/Marco.Pms.Services/Controllers/DashboardController.cs b/Marco.Pms.Services/Controllers/DashboardController.cs index d7294c6..905059d 100644 --- a/Marco.Pms.Services/Controllers/DashboardController.cs +++ b/Marco.Pms.Services/Controllers/DashboardController.cs @@ -269,7 +269,7 @@ namespace Marco.Pms.Services.Controllers using var scope = _serviceScopeFactory.CreateScope(); var _permission = scope.ServiceProvider.GetRequiredService(); // Security Check: Ensure the requested project is in the user's accessible list. - var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); + var hasPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId.Value); if (!hasPermission) { _logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId} (not active or not accessible).", loggedInEmployee.Id, projectId.Value); @@ -358,7 +358,7 @@ namespace Marco.Pms.Services.Controllers var _permission = scope.ServiceProvider.GetRequiredService(); // 2a. Security Check: Verify permission for the specific project. - var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); + var hasPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId.Value); if (!hasPermission) { _logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId}.", loggedInEmployee.Id, projectId.Value); @@ -689,7 +689,7 @@ namespace Marco.Pms.Services.Controllers using var scope = _serviceScopeFactory.CreateScope(); var _permission = scope.ServiceProvider.GetRequiredService(); - bool hasPermission = await _permission.HasProjectPermission(loggedInEmployee!, projectId); + bool hasPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasPermission) { _logger.LogWarning("Unauthorized access by EmployeeId: {EmployeeId} to ProjectId: {ProjectId}", loggedInEmployee.Id, projectId); diff --git a/Marco.Pms.Services/Controllers/DocumentController.cs b/Marco.Pms.Services/Controllers/DocumentController.cs index 4fdb588..7ecbc22 100644 --- a/Marco.Pms.Services/Controllers/DocumentController.cs +++ b/Marco.Pms.Services/Controllers/DocumentController.cs @@ -96,7 +96,7 @@ namespace Marco.Pms.Services.Controllers // Project permission check if (ProjectEntity == entityTypeId) { - var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, entityId); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, entityId); if (!hasProjectPermission) { _logger.LogWarning("Employee {EmployeeId} does not have project access for ProjectId {ProjectId}", loggedInEmployee.Id, entityId); @@ -1085,7 +1085,7 @@ namespace Marco.Pms.Services.Controllers entityExists = await _context.Projects.AnyAsync(p => p.Id == oldAttachment.EntityId && p.TenantId == tenantId); if (entityExists) { - entityExists = await _permission.HasProjectPermission(loggedInEmployee, oldAttachment.EntityId); + entityExists = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, oldAttachment.EntityId); } } else diff --git a/Marco.Pms.Services/Controllers/EmployeeController.cs b/Marco.Pms.Services/Controllers/EmployeeController.cs index 0555529..2496fe7 100644 --- a/Marco.Pms.Services/Controllers/EmployeeController.cs +++ b/Marco.Pms.Services/Controllers/EmployeeController.cs @@ -153,7 +153,7 @@ namespace MarcoBMS.Services.Controllers 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); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasProjectPermission) { _logger.LogWarning("User {EmployeeId} attempts to get employees for project {ProjectId} without permission", loggedInEmployee.Id, projectId); @@ -333,7 +333,7 @@ namespace MarcoBMS.Services.Controllers var employeeQuery = _context.Employees.Where(e => e.IsActive); if (projectId != null && projectId != Guid.Empty) { - var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId.Value); if (!hasProjectPermission) { _logger.LogWarning("User {EmployeeId} attempts to get employee for project {ProjectId}, but not have access to the project", loggedInEmployee.Id, projectId); @@ -401,7 +401,7 @@ namespace MarcoBMS.Services.Controllers loggedInEmployee.Id, projectId); // Validate project access permission - var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId.Value); if (!hasProjectPermission) { _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have permission for ProjectId: {ProjectId}", diff --git a/Marco.Pms.Services/Controllers/ImageController.cs b/Marco.Pms.Services/Controllers/ImageController.cs index 0cb1c95..ec6f5f8 100644 --- a/Marco.Pms.Services/Controllers/ImageController.cs +++ b/Marco.Pms.Services/Controllers/ImageController.cs @@ -63,7 +63,7 @@ namespace Marco.Pms.Services.Controllers } // Step 2: Check project access permission - var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId); + var hasPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasPermission) { _logger.LogWarning("[GetImageList] Access denied for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", loggedInEmployee.Id, projectId); diff --git a/Marco.Pms.Services/Service/ExpensesService.cs b/Marco.Pms.Services/Service/ExpensesService.cs index 6b72397..3f988f8 100644 --- a/Marco.Pms.Services/Service/ExpensesService.cs +++ b/Marco.Pms.Services/Service/ExpensesService.cs @@ -594,90 +594,6 @@ namespace Marco.Pms.Services.Service } } - #region Helper Methods (Private) - - /// - /// Fetches the list of possible next statuses based on current status. - /// - private async Task> GetNextStatusesAsync(Guid? currentStatusId, Guid tenantId) - { - if (!currentStatusId.HasValue) return new List(); - - return await _context.ExpensesStatusMapping - .AsNoTracking() - .Include(esm => esm.NextStatus).ThenInclude(s => s!.StatusPermissionMappings) - .Where(esm => esm.StatusId == currentStatusId && esm.Status != null) - .Select(esm => esm.NextStatus!) // Select only the NextStatus entity - .ToListAsync(); - } - - /// - /// Filters statuses by permission and reorders specific actions (e.g., Reject). - /// - private List ProcessNextStatuses(List nextStatuses, Guid createdById, Guid loggedInEmployeeId, List userPermissionIds) - { - if (nextStatuses == null || !nextStatuses.Any()) return new List(); - - // Business Logic: Move "Reject" to the top - var rejectStatus = nextStatuses.FirstOrDefault(ns => ns.DisplayName == "Reject"); - if (rejectStatus != null) - { - nextStatuses.Remove(rejectStatus); - nextStatuses.Insert(0, rejectStatus); - } - - var resultVMs = new List(); - - foreach (var item in nextStatuses) - { - var vm = _mapper.Map(item); - - // Case 1: If Creator is viewing and status is Review (Assuming Review is a constant GUID or Enum) - if (item.Id == Review && createdById == loggedInEmployeeId) - { - resultVMs.Add(vm); - continue; - } - - // Case 2: Standard Permission Check - bool hasPermission = vm.PermissionIds.Any(pid => userPermissionIds.Contains(pid)); - - // Exclude "Done" status (Assuming Done is a constant GUID) - if (hasPermission && item.Id != Done) - { - resultVMs.Add(vm); - } - } - - return resultVMs.Distinct().ToList(); - } - - /// - /// Attempts to fetch project details from Projects table, falling back to ServiceProjects. - /// - private async Task GetProjectDetailsAsync(Guid? projectId, Guid tenantId) - { - if (!projectId.HasValue) return null; - - // Check Infrastructure Projects - var infraProject = await _context.Projects - .AsNoTracking() - .Where(p => p.Id == projectId && p.TenantId == tenantId) - .ProjectTo(_mapper.ConfigurationProvider) // Optimized: Project directly to VM inside SQL - .FirstOrDefaultAsync(); - - if (infraProject != null) return infraProject; - - // Fallback to Service Projects - return await _context.ServiceProjects - .AsNoTracking() - .Where(sp => sp.Id == projectId && sp.TenantId == tenantId) - .ProjectTo(_mapper.ConfigurationProvider) - .FirstOrDefaultAsync(); - } - - #endregion - public async Task> GetSupplerNameListAsync(Employee loggedInEmployee, Guid tenantId) { try @@ -4367,6 +4283,85 @@ namespace Marco.Pms.Services.Service return CreateExpenseAttachmentEntities(batchId, expense.Id, employeeId, tenantId, objectKey, attachment); } + /// + /// Fetches the list of possible next statuses based on current status. + /// + private async Task> GetNextStatusesAsync(Guid? currentStatusId, Guid tenantId) + { + if (!currentStatusId.HasValue) return new List(); + + return await _context.ExpensesStatusMapping + .AsNoTracking() + .Include(esm => esm.NextStatus).ThenInclude(s => s!.StatusPermissionMappings) + .Where(esm => esm.StatusId == currentStatusId && esm.Status != null) + .Select(esm => esm.NextStatus!) // Select only the NextStatus entity + .ToListAsync(); + } + + /// + /// Filters statuses by permission and reorders specific actions (e.g., Reject). + /// + private List ProcessNextStatuses(List nextStatuses, Guid createdById, Guid loggedInEmployeeId, List userPermissionIds) + { + if (nextStatuses == null || !nextStatuses.Any()) return new List(); + + // Business Logic: Move "Reject" to the top + var rejectStatus = nextStatuses.FirstOrDefault(ns => ns.DisplayName == "Reject"); + if (rejectStatus != null) + { + nextStatuses.Remove(rejectStatus); + nextStatuses.Insert(0, rejectStatus); + } + + var resultVMs = new List(); + + foreach (var item in nextStatuses) + { + var vm = _mapper.Map(item); + + // Case 1: If Creator is viewing and status is Review (Assuming Review is a constant GUID or Enum) + if (item.Id == Review && createdById == loggedInEmployeeId) + { + resultVMs.Add(vm); + continue; + } + + // Case 2: Standard Permission Check + bool hasPermission = vm.PermissionIds.Any(pid => userPermissionIds.Contains(pid)); + + // Exclude "Done" status (Assuming Done is a constant GUID) + if (hasPermission && item.Id != Done) + { + resultVMs.Add(vm); + } + } + + return resultVMs.Distinct().ToList(); + } + + /// + /// Attempts to fetch project details from Projects table, falling back to ServiceProjects. + /// + private async Task GetProjectDetailsAsync(Guid? projectId, Guid tenantId) + { + if (!projectId.HasValue) return null; + + // Check Infrastructure Projects + var infraProject = await _context.Projects + .AsNoTracking() + .Where(p => p.Id == projectId && p.TenantId == tenantId) + .ProjectTo(_mapper.ConfigurationProvider) // Optimized: Project directly to VM inside SQL + .FirstOrDefaultAsync(); + + if (infraProject != null) return infraProject; + + // Fallback to Service Projects + return await _context.ServiceProjects + .AsNoTracking() + .Where(sp => sp.Id == projectId && sp.TenantId == tenantId) + .ProjectTo(_mapper.ConfigurationProvider) + .FirstOrDefaultAsync(); + } /// /// A private static helper method to create Document and BillAttachment entities. diff --git a/Marco.Pms.Services/Service/PermissionServices.cs b/Marco.Pms.Services/Service/PermissionServices.cs index cbe5dd2..ca58119 100644 --- a/Marco.Pms.Services/Service/PermissionServices.cs +++ b/Marco.Pms.Services/Service/PermissionServices.cs @@ -1,5 +1,4 @@ using Marco.Pms.DataAccess.Data; -using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Services.Helpers; using MarcoBMS.Services.Helpers; @@ -50,29 +49,28 @@ namespace Marco.Pms.Services.Service var hasPermission = toCheckPermissionIds.Any(f => realPermissionIds.Contains(f)); return hasPermission; } - public async Task HasProjectPermission(Employee LoggedInEmployee, Guid projectId) + public async Task HasInfraProjectPermission(Guid loggedInEmployeeId, Guid projectId) { - var employeeId = LoggedInEmployee.Id; - var projectIds = await _cache.GetProjects(employeeId, tenantId); + var projectIds = await _cache.GetProjects(loggedInEmployeeId, tenantId); if (projectIds == null) { - var hasPermission = await HasPermission(PermissionsMaster.ManageProject, employeeId); + var hasPermission = await HasPermission(PermissionsMaster.ManageProject, loggedInEmployeeId); if (hasPermission) { - var projects = await _context.Projects.AsNoTracking().Where(c => c.TenantId == LoggedInEmployee.TenantId).ToListAsync(); + var projects = await _context.Projects.AsNoTracking().Where(c => c.TenantId == tenantId).ToListAsync(); projectIds = projects.Select(p => p.Id).ToList(); } else { - var allocation = await _context.ProjectAllocations.AsNoTracking().Where(c => c.EmployeeId == employeeId && c.IsActive).ToListAsync(); + var allocation = await _context.ProjectAllocations.AsNoTracking().Where(c => c.EmployeeId == loggedInEmployeeId && c.IsActive).ToListAsync(); if (!allocation.Any()) { return false; } projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList(); } - await _cache.AddProjects(LoggedInEmployee.Id, projectIds, tenantId); + await _cache.AddProjects(loggedInEmployeeId, projectIds, tenantId); } return projectIds.Contains(projectId); } diff --git a/Marco.Pms.Services/Service/ProjectServices.cs b/Marco.Pms.Services/Service/ProjectServices.cs index e9cb9d7..c2bd405 100644 --- a/Marco.Pms.Services/Service/ProjectServices.cs +++ b/Marco.Pms.Services/Service/ProjectServices.cs @@ -54,89 +54,144 @@ namespace Marco.Pms.Services.Service #region =================================================================== Project Get APIs ===================================================================\ - /// - /// Retrieves a combined list of basic infrastructure and active service projects accessible by the logged-in employee within a tenant. - /// - /// Optional search term to filter projects by name (if implemented). - /// Authenticated employee requesting the data. - /// Tenant identifier to ensure multi-tenant data isolation. - /// Returns an ApiResponse containing the distinct combined list of basic project view models or an error response. - public async Task> GetBothProjectBasicListAsync(Guid? id, string? searchString, Employee loggedInEmployee, Guid tenantId) + public async Task>> GetBothProjectBasicListAsync(Guid? id, string? searchString, Employee loggedInEmployee, Guid tenantId) { + // 1. Validation and Context Checks if (tenantId == Guid.Empty) { - _logger.LogWarning("GetBothProjectBasicListAsync called with invalid tenant context by EmployeeId {EmployeeId}", loggedInEmployee.Id); - return ApiResponse.ErrorResponse("Access Denied", "Invalid tenant context.", 403); + _logger.LogWarning("Security Alert: GetBothProjectBasicListAsync called with empty TenantId by Employee: {EmployeeId}", loggedInEmployee.Id); + return ApiResponse>.ErrorResponse("Access Denied", "Invalid tenant context provided.", 403); } try { - // Retrieve list of project IDs accessible by the employee for tenant isolation and security - var accessibleProjectIds = await GetMyProjects(loggedInEmployee, tenantId); + _logger.LogInfo("Initiating project fetch for Tenant: {TenantId}, User: {UserId}. Search: {Search}", tenantId, loggedInEmployee.Id, searchString ?? "None"); - // Fetch infrastructure projects concurrently filtered by accessible IDs and tenant - var infraProjectTask = Task.Run(async () => + // 2. Check Permissions (Global check, do not block parallel tasks if possible, but needed for logic) + // usage of scoped service for permission check + using var scope = _serviceScopeFactory.CreateScope(); + var permissionService = scope.ServiceProvider.GetRequiredService(); + bool hasManagePermission = await permissionService.HasPermission(PermissionsMaster.ManageProject, loggedInEmployee.Id); + + // 3. Define Parallel Tasks + // We use DbContextFactory to create short-lived contexts for safe parallel execution. + + // --- TASK A: Infrastructure Projects --- + var infraTask = Task.Run(async () => { - await using var context = await _dbContextFactory.CreateDbContextAsync(); - var infraProjectsQuery = context.Projects - .Where(p => accessibleProjectIds.Contains(p.Id) && p.TenantId == tenantId); + using var context = await _dbContextFactory.CreateDbContextAsync(); + + // Base Query + var query = context.Projects.AsNoTracking() + .Where(p => p.TenantId == tenantId); + + // Apply Filters + if (id.HasValue) + { + query = query.Where(p => p.Id == id.Value); + } if (!string.IsNullOrWhiteSpace(searchString)) { - var normalized = searchString.Trim().ToLowerInvariant(); - infraProjectsQuery = infraProjectsQuery - .Where(p => p.Name.ToLower().Contains(normalized) || - (!string.IsNullOrWhiteSpace(p.ShortName) && p.ShortName.ToLower().Contains(normalized))); - } - if (id.HasValue) - { - infraProjectsQuery = infraProjectsQuery.Where(p => p.Id == id.Value); + // Normalize search term. NOTE: Check if your DB Collation is already Case Insensitive (CI). + // If DB is CI, you don't need ToLower(). Assuming standard needed: + string normalized = searchString.Trim(); + query = query.Where(p => p.Name.Contains(normalized) || + (p.ShortName != null && p.ShortName.Contains(normalized))); } - var infraProjects = await infraProjectsQuery.ToListAsync(); - return infraProjects.Select(p => _mapper.Map(p)).ToList(); + // Apply Security (Row Level Access) + if (!hasManagePermission) + { + // Optimization: Use Any() to create an EXISTS clause in SQL rather than fetching IDs to memory + query = query.Where(p => context.ProjectAllocations.Any(pa => + pa.ProjectId == p.Id && + pa.EmployeeId == loggedInEmployee.Id && + pa.IsActive && + pa.TenantId == tenantId)); + } + + // Projection: Select only what is needed before hitting DB + return await query + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(); }); - // Fetch active service projects concurrently with tenant isolation - var serviceProjectTask = Task.Run(async () => + + // --- TASK B: Service Projects --- + var serviceTask = Task.Run(async () => { - await using var context = await _dbContextFactory.CreateDbContextAsync(); - var serviceProjectsQuery = context.ServiceProjects + using var context = await _dbContextFactory.CreateDbContextAsync(); + + var query = context.ServiceProjects.AsNoTracking() .Where(sp => sp.TenantId == tenantId && sp.IsActive); - if (!string.IsNullOrWhiteSpace(searchString)) - { - var normalized = searchString.Trim().ToLowerInvariant(); - serviceProjectsQuery = serviceProjectsQuery - .Where(sp => sp.Name.ToLower().Contains(normalized) || - (!string.IsNullOrWhiteSpace(sp.ShortName) && sp.ShortName.ToLower().Contains(normalized))); - } - if (id.HasValue) { - serviceProjectsQuery = serviceProjectsQuery.Where(sp => sp.Id == id.Value); + query = query.Where(sp => sp.Id == id.Value); } - var serviceProjects = await serviceProjectsQuery.ToListAsync(); - return serviceProjects.Select(sp => _mapper.Map(sp)).ToList(); + if (!string.IsNullOrWhiteSpace(searchString)) + { + string normalized = searchString.Trim(); + query = query.Where(sp => sp.Name.Contains(normalized) || + (sp.ShortName != null && sp.ShortName.Contains(normalized))); + } + + if (!hasManagePermission) + { + // Optimization: Complex security filter pushed to DB + // User has access if: Allocated directly OR Mapped via JobTicket + query = query.Where(sp => + // Condition 1: Direct Allocation + context.ServiceProjectAllocations.Any(spa => + spa.ProjectId == sp.Id && + spa.EmployeeId == loggedInEmployee.Id && + spa.TenantId == tenantId && + spa.IsActive) + || + // Condition 2: Job Ticket Mapping + context.JobEmployeeMappings.Any(jem => + jem.JobTicket != null && + jem.JobTicket.ProjectId == sp.Id && + jem.AssigneeId == loggedInEmployee.Id && + jem.TenantId == tenantId) + ); + } + + return await query + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(); }); - // Wait for both concurrent tasks to complete - await Task.WhenAll(infraProjectTask, serviceProjectTask); + // 4. Await Completion + await Task.WhenAll(infraTask, serviceTask); - // Combine, remove duplicates, and prepare response list - var combinedProjects = infraProjectTask.Result.Concat(serviceProjectTask.Result).OrderBy(p => p.Name).Distinct().ToList(); + // 5. Aggregate Results + // DistinctBy is available in .NET 6+. If older, use GroupBy or distinct comparer. + var infraResults = await infraTask; + var serviceResults = await serviceTask; - _logger.LogInfo("GetBothProjectBasicListAsync returning {Count} projects for tenant {TenantId} by EmployeeId {EmployeeId}", - combinedProjects.Count, tenantId, loggedInEmployee.Id); + var combinedProjects = infraResults + .Concat(serviceResults) + .DistinctBy(p => p.Id) // Ensure no duplicate IDs if cross-contamination exists + .OrderBy(p => p.Name) + .ToList(); - return ApiResponse.SuccessResponse(combinedProjects, "Service and infrastructure projects fetched successfully.", 200); + _logger.LogInfo("Successfully fetched {Count} projects ({InfraCount} Infra, {ServiceCount} Service) for User {UserId}.", + combinedProjects.Count, infraResults.Count, serviceResults.Count, loggedInEmployee.Id); + + return ApiResponse>.SuccessResponse(combinedProjects, "Projects retrieved successfully.", 200); + } + catch (OperationCanceledException) + { + _logger.LogWarning("Project fetch operation was canceled by the client."); + return ApiResponse>.ErrorResponse("Request Canceled", "The operation was canceled.", 499); } catch (Exception ex) { - _logger.LogError(ex, "Unexpected error in GetBothProjectBasicListAsync for tenant {TenantId} by EmployeeId {EmployeeId}", - tenantId, loggedInEmployee.Id); - return ApiResponse.ErrorResponse("Internal Server Error", "An error occurred while fetching projects.", 500); + _logger.LogError(ex, "CRITICAL: Failed to fetch projects for Tenant {TenantId}. Error: {Message}", tenantId, ex.Message); + return ApiResponse>.ErrorResponse("Internal Server Error", "An unexpected error occurred while processing your request.", 500); } } public async Task> GetAllProjectsBasicAsync(bool provideAll, Employee loggedInEmployee, Guid tenantId) @@ -278,7 +333,7 @@ namespace Marco.Pms.Services.Service var _permission = scope.ServiceProvider.GetRequiredService(); // --- Step 1: Run independent operations in PARALLEL --- // We can check permissions and fetch data at the same time to reduce latency. - var permissionTask = _permission.HasProjectPermission(loggedInEmployee, id); + var permissionTask = _permission.HasInfraProjectPermission(loggedInEmployee.Id, id); // This helper method encapsulates the "cache-first, then database" logic. var projectDataTask = GetProjectDataAsync(id, tenantId); @@ -333,7 +388,7 @@ namespace Marco.Pms.Services.Service } // Step 2: Check permission for this specific project - var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, id); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, id); if (!hasProjectPermission) { _logger.LogWarning("Project-specific access denied. EmployeeId: {EmployeeId}, ProjectId: {ProjectId}", loggedInEmployee.Id, id); @@ -649,7 +704,7 @@ namespace Marco.Pms.Services.Service } // 1c. Security Check - var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, id); + var hasPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, id); if (!hasPermission) { _logger.LogWarning("Access DENIED for user {UserId} attempting to update project {ProjectId}.", loggedInEmployee.Id, id); @@ -743,7 +798,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); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); var hasAllEmployeePermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id); var hasviewTeamPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id, projectId); @@ -824,7 +879,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); + var hasPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasPermission) { _logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId}.", loggedInEmployee.Id, projectId); @@ -1333,7 +1388,7 @@ namespace Marco.Pms.Services.Service } // Check if the logged-in employee has permission for the requested project - var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasProjectPermission) { _logger.LogWarning("User {EmployeeId} attempts to get employees for project {ProjectId} without permission", loggedInEmployee.Id, projectId); @@ -1416,7 +1471,7 @@ namespace Marco.Pms.Services.Service } // Check permission to view project team - var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId); + var hasProjectPermission = await _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasProjectPermission) { _logger.LogWarning("Access denied: User {EmployeeId} tried to get team for Project {ProjectId}", loggedInEmployee.Id, projectId); @@ -1507,7 +1562,7 @@ namespace Marco.Pms.Services.Service { var _permission = scope.ServiceProvider.GetRequiredService(); // --- Step 1: Run independent permission checks in PARALLEL --- - var projectPermissionTask = _permission.HasProjectPermission(loggedInEmployee, projectId); + var projectPermissionTask = _permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); var viewInfraPermissionTask = Task.Run(async () => { using var newScope = _serviceScopeFactory.CreateScope(); @@ -1658,7 +1713,7 @@ namespace Marco.Pms.Services.Service { using var taskScope = _serviceScopeFactory.CreateScope(); var permission = taskScope.ServiceProvider.GetRequiredService(); - return await permission.HasProjectPermission(loggedInEmployee, projectId); + return await permission.HasInfraProjectPermission(loggedInEmployee.Id, projectId); }); var hasGenericViewInfraPermissionTask = Task.Run(async () => { @@ -2511,7 +2566,7 @@ namespace Marco.Pms.Services.Service } // Verify logged-in employee has permission on the project - var hasPermission = await permissionService.HasProjectPermission(loggedInEmployee, projectId); + var hasPermission = await permissionService.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasPermission) { _logger.LogWarning("Access DENIED for user {UserId} attempting to access project {ProjectId}.", loggedInEmployee.Id, projectId); @@ -2601,7 +2656,7 @@ namespace Marco.Pms.Services.Service } // Validate permission for logged-in employee to assign services to project - var hasPermission = await permissionService.HasProjectPermission(loggedInEmployee, model.ProjectId); + var hasPermission = await permissionService.HasInfraProjectPermission(loggedInEmployee.Id, model.ProjectId); if (!hasPermission) { _logger.LogWarning("Access DENIED for user {UserId} attempting to assign services to project {ProjectId}.", loggedInEmployee.Id, model.ProjectId); @@ -2703,7 +2758,7 @@ namespace Marco.Pms.Services.Service } // Verify permission to update project - var hasPermission = await permissionService.HasProjectPermission(loggedInEmployee, model.ProjectId); + var hasPermission = await permissionService.HasInfraProjectPermission(loggedInEmployee.Id, model.ProjectId); if (!hasPermission) { _logger.LogWarning("Access DENIED for user {UserId} trying to deassign services from project {ProjectId}.", loggedInEmployee.Id, model.ProjectId); @@ -2801,7 +2856,7 @@ namespace Marco.Pms.Services.Service } // Check if the logged in employee has permission to access the project - var hasPermission = await permissionService.HasProjectPermission(loggedInEmployee, projectId); + var hasPermission = await permissionService.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasPermission) { _logger.LogWarning("Access denied for user {UserId} on project {ProjectId}", loggedInEmployee.Id, projectId); @@ -2970,7 +3025,7 @@ namespace Marco.Pms.Services.Service } // Check if the logged in employee has permission to access the project - var hasPermission = await permissionService.HasProjectPermission(loggedInEmployee, projectId); + var hasPermission = await permissionService.HasInfraProjectPermission(loggedInEmployee.Id, projectId); if (!hasPermission) { _logger.LogWarning("Access denied for user {UserId} on project {ProjectId}", loggedInEmployee.Id, projectId); diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs index 2042987..8520d66 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs @@ -10,7 +10,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces { public interface IProjectServices { - Task> GetBothProjectBasicListAsync(Guid? id, string? searchString, Employee loggedInEmployee, Guid tenantId); + Task>> GetBothProjectBasicListAsync(Guid? id, string? searchString, Employee loggedInEmployee, Guid tenantId); Task> GetAllProjectsBasicAsync(bool provideAll, Employee loggedInEmployee, Guid tenantId); Task> GetAllProjectsAsync(string? searchString, int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId); Task> GetProjectAsync(Guid id, Employee loggedInEmployee, Guid tenantId);