From bcba454b6e55f27370e3e681ad8f207ef0f8a82c Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 25 Nov 2025 12:17:23 +0530 Subject: [PATCH] Added a function to check if have service project permission --- .../Service/PermissionServices.cs | 111 ++++++++++++------ .../Service/ServiceProjectService.cs | 10 +- 2 files changed, 82 insertions(+), 39 deletions(-) diff --git a/Marco.Pms.Services/Service/PermissionServices.cs b/Marco.Pms.Services/Service/PermissionServices.cs index e7ca947..be759dc 100644 --- a/Marco.Pms.Services/Service/PermissionServices.cs +++ b/Marco.Pms.Services/Service/PermissionServices.cs @@ -3,6 +3,7 @@ using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Services.Helpers; using MarcoBMS.Services.Helpers; +using MarcoBMS.Services.Service; using Microsoft.EntityFrameworkCore; namespace Marco.Pms.Services.Service @@ -12,49 +13,18 @@ namespace Marco.Pms.Services.Service private readonly ApplicationDbContext _context; private readonly RolesHelper _rolesHelper; private readonly CacheUpdateHelper _cache; + private readonly ILoggingService _logger; private readonly Guid tenantId; - public PermissionServices(ApplicationDbContext context, RolesHelper rolesHelper, CacheUpdateHelper cache, UserHelper userHelper) + public PermissionServices(ApplicationDbContext context, RolesHelper rolesHelper, CacheUpdateHelper cache, ILoggingService logger, UserHelper userHelper) { _context = context; _rolesHelper = rolesHelper; _cache = cache; + _logger = logger; tenantId = userHelper.GetTenantId(); } - //public async Task HasPermission(Guid featurePermissionId, Guid employeeId, Guid? projectId = null) - //{ - // var featurePermissionIds = await _cache.GetPermissions(employeeId); - // if (featurePermissionIds == null) - // { - // List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId); - // featurePermissionIds = featurePermission.Select(fp => fp.Id).ToList(); - // } - // if (projectId != null) - // { - // var projectLevelPerissionIds = await _context.ProjectLevelPermissionMappings - // .Where(pl => pl.ProjectId == projectId.Value && pl.EmployeeId == employeeId).Select(pl => pl.PermissionId).ToListAsync(); - - // var projectLevelModuleIds = new HashSet - // { - // Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"), - // Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"), - // Guid.Parse("81ab8a87-8ccd-4015-a917-0627cee6a100"), - // Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), - // Guid.Parse("a8cf4331-8f04-4961-8360-a3f7c3cc7462") - // }; - - // var allProjectLevelPermissionIds = await _context.FeaturePermissions - // .Where(fp => projectLevelModuleIds.Contains(fp.FeatureId) && !projectLevelPerissionIds.Contains(fp.Id)).Select(fp => fp.Id).ToListAsync(); - // featurePermissionIds.RemoveRange(allProjectLevelPermissionIds); - - // featurePermissionIds.AddRange(projectLevelPerissionIds); - // featurePermissionIds = featurePermissionIds.Distinct().ToList(); - // } - // var hasPermission = featurePermissionIds.Contains(featurePermissionId); - // return hasPermission; - //} - /// /// Checks whether an employee has a specific feature permission, optionally within a project context. /// @@ -156,5 +126,78 @@ namespace Marco.Pms.Services.Service } return projectIds.Contains(projectId); } + + /// + /// Determines if an employee has permission to access a specific service project. + /// Permission is granted if the user is directly allocated to the project OR + /// assigned to any active job ticket within the project. + /// + /// The ID of the user requesting access. + /// The ID of the project to access. + /// True if access is allowed, otherwise False. + public async Task HasServiceProjectPermission(Guid loggedInEmployeeId, Guid projectId) + { + Guid ReviewDoneStatus = Guid.Parse("ed10ab57-dbaa-4ca5-8ecd-56745dcbdbd7"); + Guid ClosedStatus = Guid.Parse("3ddeefb5-ae3c-4e10-a922-35e0a452bb69"); + // 1. Input Validation + if (loggedInEmployeeId == Guid.Empty || projectId == Guid.Empty) + { + _logger.LogWarning("Permission check failed: Invalid input parameters. EmployeeId: {EmployeeId}, ProjectId: {ProjectId}", loggedInEmployeeId, projectId); + return false; + } + + try + { + _logger.LogInfo("Starting permission check for Employee: {EmployeeId} on Project: {ProjectId}", loggedInEmployeeId, projectId); + + // 2. Check Level 1: Is the user a generic Team Member of the project? + // This is usually the most common case, so checking this first saves complex query execution. + bool isTeamMember = await _context.ServiceProjectAllocations + .AsNoTracking() // Optimization: Read-only query does not need tracking + .AnyAsync(spa => spa.ProjectId == projectId + && spa.EmployeeId == loggedInEmployeeId + && spa.IsActive + && spa.TenantId == tenantId); + + if (isTeamMember) + { + _logger.LogInfo("Access Granted: User {EmployeeId} is a team member of Project {ProjectId}.", loggedInEmployeeId, projectId); + return true; + } + + // 3. Check Level 2: Is the user assigned to any ACTIVE specific Job Ticket? + // Optimization: Combined the check for JobTicket and Mapping into a single Join query. + // This prevents pulling a list of JobIds into memory (fixing memory bloat) and reduces DB roundtrips. + bool hasActiveJobAssignment = await _context.JobTickets + .AsNoTracking() + .Where(jt => jt.ProjectId == projectId + && jt.StatusId != ReviewDoneStatus + && jt.StatusId != ClosedStatus + && jt.IsActive) + .Join(_context.JobEmployeeMappings, + ticket => ticket.Id, + mapping => mapping.JobTicketId, + (ticket, mapping) => mapping) + .AnyAsync(mapping => mapping.AssigneeId == loggedInEmployeeId + && mapping.TenantId == tenantId); + + if (hasActiveJobAssignment) + { + _logger.LogInfo("Access Granted: User {EmployeeId} is assigned active tickets in Project {ProjectId}.", loggedInEmployeeId, projectId); + return true; + } + + // 4. Default Deny + _logger.LogWarning("Access Denied: User {EmployeeId} has no permissions for Project {ProjectId}.", loggedInEmployeeId, projectId); + return false; + } + catch (Exception ex) + { + // 5. Robust Error Handling + // Log the full stack trace for debugging, but return false to maintain security (fail-closed). + _logger.LogError(ex, "An error occurred while checking permissions for Employee: {EmployeeId} on Project: {ProjectId}", loggedInEmployeeId, projectId); + return false; + } + } } } diff --git a/Marco.Pms.Services/Service/ServiceProjectService.cs b/Marco.Pms.Services/Service/ServiceProjectService.cs index 9fbc9f5..4fa347d 100644 --- a/Marco.Pms.Services/Service/ServiceProjectService.cs +++ b/Marco.Pms.Services/Service/ServiceProjectService.cs @@ -36,8 +36,8 @@ namespace Marco.Pms.Services.Service private readonly Guid NewStatus = Guid.Parse("32d76a02-8f44-4aa0-9b66-c3716c45a918"); private readonly Guid AssignedStatus = Guid.Parse("cfa1886d-055f-4ded-84c6-42a2a8a14a66"); private readonly Guid InProgressStatus = Guid.Parse("5a6873a5-fed7-4745-a52f-8f61bf3bd72d"); - private readonly Guid ReviewStatus = Guid.Parse("aab71020-2fb8-44d9-9430-c9a7e9bf33b0"); - private readonly Guid DoneStatus = Guid.Parse("ed10ab57-dbaa-4ca5-8ecd-56745dcbdbd7"); + private readonly Guid WorkDoneStatus = Guid.Parse("aab71020-2fb8-44d9-9430-c9a7e9bf33b0"); + private readonly Guid ReviewDoneStatus = Guid.Parse("ed10ab57-dbaa-4ca5-8ecd-56745dcbdbd7"); private readonly Guid ClosedStatus = Guid.Parse("3ddeefb5-ae3c-4e10-a922-35e0a452bb69"); private readonly Guid OnHoldStatus = Guid.Parse("75a0c8b8-9c6a-41af-80bf-b35bab722eb2"); @@ -130,8 +130,8 @@ namespace Marco.Pms.Services.Service .Select(g => new { ProjectId = g.Key, - JobsPassedDueDateCount = g.Count(jt => jt.StatusId != DoneStatus && jt.StatusId != ClosedStatus && jt.DueDate.Date < DateTime.UtcNow.Date), - ActiveJobsCount = g.Count(jt => jt.StatusId != DoneStatus && jt.StatusId != ClosedStatus && jt.StatusId != OnHoldStatus), + JobsPassedDueDateCount = g.Count(jt => jt.StatusId != ReviewDoneStatus && jt.StatusId != ClosedStatus && jt.DueDate.Date < DateTime.UtcNow.Date), + ActiveJobsCount = g.Count(jt => jt.StatusId != ReviewDoneStatus && jt.StatusId != ClosedStatus && jt.StatusId != OnHoldStatus), AssignedJobsCount = g.Count(jt => jt.StatusId == AssignedStatus), OnHoldJobsCount = g.Count(jt => jt.StatusId == OnHoldStatus) }) @@ -2164,7 +2164,7 @@ namespace Marco.Pms.Services.Service if (jobTicket.IsArchive != model.IsArchive) { // Validate if job ticket status permits archiving - if (model.IsArchive && jobTicket.StatusId != DoneStatus && jobTicket.StatusId != ClosedStatus) + if (model.IsArchive && jobTicket.StatusId != ReviewDoneStatus && jobTicket.StatusId != ClosedStatus) { _logger.LogWarning("Archiving failed: Job status not eligible. JobTicketId: {JobTicketId}, StatusId: {StatusId}", jobTicket.Id, jobTicket.StatusId); return ApiResponse.ErrorResponse(