diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index 1fd36f4..183bbc4 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -32,20 +32,9 @@ namespace Marco.Pms.CacheHelper public async Task AddProjectDetailsListToCache(List projectDetailsList) { await _projetCollection.InsertManyAsync(projectDetailsList); - //_logger.LogInfo("[AddProjectDetails] Project details inserted in MongoDB for ProjectId: {ProjectId}", project.Id); } - public async Task UpdateProjectDetailsOnlyToCache(Project project) + public async Task UpdateProjectDetailsOnlyToCache(Project project, StatusMaster projectStatus) { - //_logger.LogInfo("Starting update for project: {ProjectId}", project.Id); - - var projectStatus = await _context.StatusMasters - .FirstOrDefaultAsync(s => s.Id == project.ProjectStatusId); - - if (projectStatus == null) - { - //_logger.LogWarning("StatusMaster not found for ProjectStatusId: {StatusId}", project.ProjectStatusId); - } - // Build the update definition var updates = Builders.Update.Combine( Builders.Update.Set(r => r.Name, project.Name), @@ -69,11 +58,9 @@ namespace Marco.Pms.CacheHelper if (result.MatchedCount == 0) { - //_logger.LogWarning("No project matched in MongoDB for update. ProjectId: {ProjectId}", project.Id); return false; } - //_logger.LogInfo("Project {ProjectId} successfully updated in MongoDB", project.Id); return true; } public async Task GetProjectDetailsFromCache(Guid projectId) @@ -83,21 +70,12 @@ namespace Marco.Pms.CacheHelper var filter = Builders.Filter.Eq(p => p.Id, projectId.ToString()); var projection = Builders.Projection.Exclude(p => p.Buildings); - //_logger.LogInfo("Fetching project details for ProjectId: {ProjectId} from MongoDB", projectId); - // Perform query var project = await _projetCollection .Find(filter) .Project(projection) .FirstOrDefaultAsync(); - if (project == null) - { - //_logger.LogWarning("No project found in MongoDB for ProjectId: {ProjectId}", projectId); - return null; - } - - //_logger.LogInfo("Successfully fetched project details (excluding Buildings) for ProjectId: {ProjectId}", projectId); return project; } public async Task> GetProjectDetailsListFromCache(List projectIds) @@ -111,6 +89,12 @@ namespace Marco.Pms.CacheHelper .ToListAsync(); return projects; } + public async Task DeleteProjectByIdFromCacheAsync(Guid projectId) + { + var filter = Builders.Filter.Eq(e => e.Id, projectId.ToString()); + var result = await _projetCollection.DeleteOneAsync(filter); + return result.DeletedCount > 0; + } // ------------------------------------------------------- Project InfraStructure ------------------------------------------------------- @@ -407,6 +391,10 @@ namespace Marco.Pms.CacheHelper return null; return result; } + + + // ------------------------------------------------------- WorkItem ------------------------------------------------------- + public async Task> GetWorkItemsByWorkAreaIdsFromCache(List workAreaIds) { var stringWorkAreaIds = workAreaIds.Select(wa => wa.ToString()).ToList(); @@ -418,9 +406,6 @@ namespace Marco.Pms.CacheHelper return workItems; } - - // ------------------------------------------------------- WorkItem ------------------------------------------------------- - public async Task ManageWorkItemDetailsToCache(List workItems) { var activityIds = workItems.Select(wi => wi.ActivityId).ToList(); @@ -510,5 +495,11 @@ namespace Marco.Pms.CacheHelper } return false; } + public async Task DeleteWorkItemByIdFromCacheAsync(Guid workItemId) + { + var filter = Builders.Filter.Eq(e => e.Id, workItemId.ToString()); + var result = await _taskCollection.DeleteOneAsync(filter); + return result.DeletedCount > 0; + } } } diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 29f9d04..adb5887 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -28,10 +28,10 @@ namespace MarcoBMS.Services.Controllers [Authorize] public class ProjectController : ControllerBase { + private readonly IDbContextFactory _dbContextFactory; private readonly ApplicationDbContext _context; private readonly UserHelper _userHelper; private readonly ILoggingService _logger; - //private readonly RolesHelper _rolesHelper; private readonly ProjectsHelper _projectsHelper; private readonly IHubContext _signalR; private readonly PermissionServices _permission; @@ -40,13 +40,13 @@ namespace MarcoBMS.Services.Controllers private readonly Guid tenantId; - public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, - IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache, IMapper mapper) + public ProjectController(IDbContextFactory dbContextFactory, ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, + ProjectsHelper projectHelper, IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache, IMapper mapper) { + _dbContextFactory = dbContextFactory; _context = context; _userHelper = userHelper; _logger = logger; - //_rolesHelper = rolesHelper; _projectsHelper = projectHelper; _signalR = signalR; _cache = cache; @@ -55,55 +55,10 @@ namespace MarcoBMS.Services.Controllers tenantId = _userHelper.GetTenantId(); } - [HttpGet("list/basic1")] - public async Task GetAllProjects1() - { - if (!ModelState.IsValid) - { - var errors = ModelState.Values - .SelectMany(v => v.Errors) - .Select(e => e.ErrorMessage) - .ToList(); - return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); - - } - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - - // Defensive check for null employee (important for robust APIs) - if (LoggedInEmployee == null) - { - return Unauthorized(ApiResponse.ErrorResponse("Employee not found.", null, 401)); - } - - List response = new List(); - List projectIds = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee); - - List? projectsDetails = await _cache.GetProjectDetailsList(projectIds); - if (projectsDetails == null) - { - List projects = await _context.Projects.Where(p => projectIds.Contains(p.Id)).ToListAsync(); - //using (var scope = _serviceScopeFactory.CreateScope()) - //{ - // var cacheHelper = scope.ServiceProvider.GetRequiredService(); - - //} - foreach (var project in projects) - { - await _cache.AddProjectDetails(project); - } - response = projects.Select(p => _mapper.Map(p)).ToList(); - } - else - { - response = projectsDetails.Select(p => _mapper.Map(p)).ToList(); - } - - return Ok(ApiResponse.SuccessResponse(response, "Success.", 200)); - } + #region =================================================================== Project Get APIs =================================================================== [HttpGet("list/basic")] - public async Task GetAllProjects() // Renamed for clarity + public async Task GetAllProjectsBasic() { // Step 1: Get the current user var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); @@ -133,146 +88,82 @@ namespace MarcoBMS.Services.Controllers } /// - /// Retrieves a list of ProjectInfoVMs by their IDs, using an efficient partial cache-hit strategy. - /// It fetches what it can from the cache (as ProjectMongoDB), gets the rest from the - /// database (as Project), updates the cache, and returns a unified list of ViewModels. + /// Retrieves a list of projects accessible to the current user, including aggregated details. + /// This method is optimized to use a cache-first approach. If data is not in the cache, + /// it fetches and aggregates data efficiently from the database in parallel. /// - /// The list of project IDs to retrieve. - /// A list of ProjectInfoVMs. - private async Task> GetProjectInfosByIdsAsync(List projectIds) - { - // --- Step 1: Fetch from Cache --- - // The cache returns a list of MongoDB documents for the projects it found. - var cachedMongoDocs = await _cache.GetProjectDetailsList(projectIds) ?? new List(); - var finalViewModels = _mapper.Map>(cachedMongoDocs); - - _logger.LogDebug("Cache hit for {CacheCount} of {TotalCount} projects.", finalViewModels.Count, projectIds.Count); - - // --- Step 2: Identify Missing Projects --- - // If we found everything in the cache, we can return early. - if (finalViewModels.Count == projectIds.Count) - { - return finalViewModels; - } - - var cachedIds = cachedMongoDocs.Select(p => p.Id).ToHashSet(); // Assuming ProjectMongoDB has an Id - var missingIds = projectIds.Where(id => !cachedIds.Contains(id.ToString())).ToList(); - - // --- Step 3: Fetch Missing from Database --- - if (missingIds.Any()) - { - _logger.LogDebug("Cache miss for {MissingCount} projects. Querying database.", missingIds.Count); - - var projectsFromDb = await _context.Projects - .Where(p => missingIds.Contains(p.Id)) - .AsNoTracking() // Use AsNoTracking for read-only query performance - .ToListAsync(); - - if (projectsFromDb.Any()) - { - // Map the newly fetched projects (from SQL) to their ViewModel - var vmsFromDb = _mapper.Map>(projectsFromDb); - finalViewModels.AddRange(vmsFromDb); - - // --- Step 4: Update Cache with Missing Items in a new scope --- - _logger.LogDebug("Updating cache with {DbCount} newly fetched projects.", projectsFromDb.Count); - await _cache.AddProjectDetailsList(projectsFromDb); - } - } - - return finalViewModels; - } + /// An ApiResponse containing a list of projects or an error. [HttpGet("list")] - public async Task GetAll() + public async Task GetAllProjects() { + // --- Step 1: Input Validation and Initial Setup --- if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); - + _logger.LogWarning("GetAllProjects called with invalid model state. Errors: {Errors}", string.Join(", ", errors)); + return BadRequest(ApiResponse.ErrorResponse("Invalid request data provided.", errors, 400)); } - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - //List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(LoggedInEmployee.Id); - //string[] projectsId = []; - //List projects = new List(); - ///* User with permission manage project can see all projects */ - //if (featurePermission != null && featurePermission.Exists(c => c.Id.ToString() == "172fc9b6-755b-4f62-ab26-55c34a330614")) - //{ - // projects = await _projectsHelper.GetAllProjectByTanentID(LoggedInEmployee.TenantId); - //} - //else - //{ - // List allocation = await _projectsHelper.GetProjectByEmployeeID(LoggedInEmployee.Id); - // projectsId = allocation.Select(c => c.ProjectId.ToString()).ToArray(); - // projects = await _context.Projects.Where(c => projectsId.Contains(c.Id.ToString()) && c.TenantId == tenantId).ToListAsync(); - //} - - //List projects = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee); - ////List projects = new List(); - /// - List response = new List(); - List projectIds = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee); - - var projectsDetails = await _cache.GetProjectDetailsList(projectIds); - if (projectsDetails == null) + try { - List projects = await _context.Projects.Where(p => projectIds.Contains(p.Id)).ToListAsync(); + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + _logger.LogInfo("Starting GetAllProjects for TenantId: {TenantId}, User: {UserId}", tenantId, loggedInEmployee.Id); - var teams = await _context.ProjectAllocations.Where(p => p.TenantId == tenantId && projectIds.Contains(p.ProjectId) && p.IsActive == true).ToListAsync(); - - - List allBuildings = await _context.Buildings.Where(b => projectIds.Contains(b.ProjectId) && b.TenantId == tenantId).ToListAsync(); - List idList = allBuildings.Select(b => b.Id).ToList(); - - List allFloors = await _context.Floor.Where(f => idList.Contains(f.BuildingId) && f.TenantId == tenantId).ToListAsync(); - idList = allFloors.Select(f => f.Id).ToList(); - - List allWorkAreas = await _context.WorkAreas.Where(a => idList.Contains(a.FloorId) && a.TenantId == tenantId).ToListAsync(); - idList = allWorkAreas.Select(a => a.Id).ToList(); - - List allWorkItems = await _context.WorkItems.Where(i => idList.Contains(i.WorkAreaId) && i.TenantId == tenantId).Include(i => i.ActivityMaster).ToListAsync(); - - foreach (var project in projects) + // --- Step 2: Get a list of project IDs the user can access --- + List projectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); + if (!projectIds.Any()) { - var result = _mapper.Map(project); - var team = teams.Where(p => p.TenantId == tenantId && p.ProjectId == project.Id && p.IsActive == true).ToList(); - - result.TeamSize = team.Count(); - - List buildings = allBuildings.Where(b => b.ProjectId == project.Id && b.TenantId == tenantId).ToList(); - idList = buildings.Select(b => b.Id).ToList(); - - List floors = allFloors.Where(f => idList.Contains(f.BuildingId) && f.TenantId == tenantId).ToList(); - idList = floors.Select(f => f.Id).ToList(); - - List workAreas = allWorkAreas.Where(a => idList.Contains(a.FloorId) && a.TenantId == tenantId).ToList(); - idList = workAreas.Select(a => a.Id).ToList(); - - List workItems = allWorkItems.Where(i => idList.Contains(i.WorkAreaId) && i.TenantId == tenantId).ToList(); - double completedTask = 0; - double plannedTask = 0; - foreach (var workItem in workItems) - { - completedTask += workItem.CompletedWork; - plannedTask += workItem.PlannedWork; - } - result.PlannedWork = plannedTask; - result.CompletedWork = completedTask; - response.Add(result); + _logger.LogInfo("User has no assigned projects. Returning empty list."); + return Ok(ApiResponse>.SuccessResponse(new List(), "No projects found for the current user.", 200)); } - } - else - { - response = projectsDetails.Select(p => _mapper.Map(p)).ToList(); - } - return Ok(ApiResponse.SuccessResponse(response, "Success.", 200)); + // --- Step 3: Efficiently handle partial cache hits --- + _logger.LogInfo("Attempting to fetch details for {ProjectCount} projects from cache.", projectIds.Count); + + // Fetch what we can from the cache. + var cachedDetails = await _cache.GetProjectDetailsList(projectIds) ?? new List(); + var cachedDictionary = cachedDetails.ToDictionary(p => Guid.Parse(p.Id)); + + // Identify which projects are missing from the cache. + var missingIds = projectIds.Where(id => !cachedDictionary.ContainsKey(id)).ToList(); + + // Start building the response with the items we found in the cache. + var responseVms = _mapper.Map>(cachedDictionary.Values); + + if (missingIds.Any()) + { + // --- Step 4: Fetch ONLY the missing items from the database --- + _logger.LogInfo("Cache partial MISS. Found {CachedCount}, fetching {MissingCount} projects from DB.", + cachedDictionary.Count, missingIds.Count); + + // Call our dedicated data-fetching method for the missing IDs. + var newMongoDetails = await FetchAndBuildProjectDetails(missingIds, tenantId); + + if (newMongoDetails.Any()) + { + // Map the newly fetched items and add them to our response list. + responseVms.AddRange(newMongoDetails); + } + } + else + { + _logger.LogInfo("Cache HIT. All {ProjectCount} projects found in cache.", projectIds.Count); + } + + // --- Step 5: Return the combined result --- + _logger.LogInfo("Successfully retrieved a total of {ProjectCount} projects.", responseVms.Count); + return Ok(ApiResponse>.SuccessResponse(responseVms, "Projects retrieved successfully.", 200)); + } + catch (Exception ex) + { + // --- Step 6: Graceful Error Handling --- + _logger.LogError("An unexpected error occurred in GetAllProjects for tenant {TenantId}. : {Error}", tenantId, ex.Message); + return StatusCode(500, ApiResponse.ErrorResponse("An internal server error occurred. Please try again later.", null, 500)); + } } [HttpGet("get/{id}")] @@ -351,23 +242,6 @@ namespace MarcoBMS.Services.Controllers { projectVM.ProjectStatus.TenantId = tenantId; } - //projectVM = new ProjectVM - //{ - // Id = projectDetails.Id != null ? Guid.Parse(projectDetails.Id) : Guid.Empty, - // Name = projectDetails.Name, - // ShortName = projectDetails.ShortName, - // ProjectAddress = projectDetails.ProjectAddress, - // StartDate = projectDetails.StartDate, - // EndDate = projectDetails.EndDate, - // ContactPerson = projectDetails.ContactPerson, - // ProjectStatus = new StatusMaster - // { - // Id = projectDetails.ProjectStatus?.Id != null ? Guid.Parse(projectDetails.ProjectStatus.Id) : Guid.Empty, - // Status = projectDetails.ProjectStatus?.Status, - // TenantId = tenantId - // } - // //ProjectStatusId = projectDetails.ProjectStatus?.Id != null ? Guid.Parse(projectDetails.ProjectStatus.Id) : Guid.Empty, - //}; } if (projectVM == null) @@ -486,40 +360,9 @@ namespace MarcoBMS.Services.Controllers } - private async Task GetProjectViewModel(Guid? id, Project project) - { - ProjectDetailsVM vm = new ProjectDetailsVM(); + #endregion - // List buildings = _unitOfWork.Building.GetAll(c => c.ProjectId == id).ToList(); - List buildings = await _context.Buildings.Where(c => c.ProjectId == id).ToListAsync(); - List idList = buildings.Select(o => o.Id).ToList(); - // List floors = _unitOfWork.Floor.GetAll(c => idList.Contains(c.Id)).ToList(); - List floors = await _context.Floor.Where(c => idList.Contains(c.BuildingId)).ToListAsync(); - idList = floors.Select(o => o.Id).ToList(); - //List workAreas = _unitOfWork.WorkArea.GetAll(c => idList.Contains(c.Id), includeProperties: "WorkItems,WorkItems.ActivityMaster").ToList(); - - List workAreas = await _context.WorkAreas.Where(c => idList.Contains(c.FloorId)).ToListAsync(); - - idList = workAreas.Select(o => o.Id).ToList(); - List workItems = await _context.WorkItems.Include(c => c.WorkCategoryMaster).Where(c => idList.Contains(c.WorkAreaId)).Include(c => c.ActivityMaster).ToListAsync(); - // List workItems = _unitOfWork.WorkItem.GetAll(c => idList.Contains(c.WorkAreaId), includeProperties: "ActivityMaster").ToList(); - idList = workItems.Select(t => t.Id).ToList(); - List tasks = await _context.TaskAllocations.Where(t => idList.Contains(t.WorkItemId) && t.AssignmentDate.Date == DateTime.UtcNow.Date).ToListAsync(); - vm.project = project; - vm.buildings = buildings; - vm.floors = floors; - vm.workAreas = workAreas; - vm.workItems = workItems; - vm.Tasks = tasks; - return vm; - } - - private Guid GetTenantId() - { - return _userHelper.GetTenantId(); - //var tenant = User.FindFirst("TenantId")?.Value; - //return (tenant != null ? Convert.ToInt32(tenant) : 1); - } + #region =================================================================== Project Manage APIs =================================================================== [HttpPost] public async Task Create([FromBody] CreateProjectDto projectDto) @@ -619,50 +462,9 @@ namespace MarcoBMS.Services.Controllers } } - //[HttpPost("assign-employee")] - //public async Task AssignEmployee(int? allocationid, int employeeId, int projectId) - //{ - // var employee = await _context.Employees.FindAsync(employeeId); - // var project = _projectrepo.Get(c => c.Id == projectId); - // if (employee == null || project == null) - // { - // return NotFound(); - // } + #endregion - // // Logic to add the product to a new table (e.g., selected products) - - // if (allocationid == null) - // { - // // Add allocation - // ProjectAllocation allocation = new ProjectAllocation() - // { - // EmployeeId = employeeId, - // ProjectId = project.Id, - // AllocationDate = DateTime.UtcNow, - // //EmployeeRole = employee.Rol - // TenantId = project.TenantId - // }; - - // _unitOfWork.ProjectAllocation.CreateAsync(allocation); - // } - // else - // { - // //remove allocation - // var allocation = await _context.ProjectAllocations.FindAsync(allocationid); - // if (allocation != null) - // { - // allocation.ReAllocationDate = DateTime.UtcNow; - - // _unitOfWork.ProjectAllocation.UpdateAsync(allocation.Id, allocation); - // } - // else - // { - // return NotFound(); - // } - // } - - // return Ok(); - //} + #region =================================================================== Project Allocation APIs =================================================================== [HttpGet] [Route("employees/get/{projectid?}/{includeInactive?}")] @@ -838,6 +640,134 @@ namespace MarcoBMS.Services.Controllers } + [HttpGet("assigned-projects/{employeeId}")] + public async Task GetProjectsByEmployee([FromRoute] Guid employeeId) + { + + Guid tenantId = _userHelper.GetTenantId(); + if (employeeId == Guid.Empty) + { + return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "Employee id not valid.", 400)); + } + + List projectList = await _context.ProjectAllocations + .Where(c => c.TenantId == tenantId && c.EmployeeId == employeeId && c.IsActive) + .Select(c => c.ProjectId).Distinct() + .ToListAsync(); + + if (!projectList.Any()) + { + return NotFound(ApiResponse.SuccessResponse(new List(), "No projects found.", 200)); + } + + + List projectlist = await _context.Projects + .Where(p => projectList.Contains(p.Id)) + .ToListAsync(); + + List projects = new List(); + + + foreach (var project in projectlist) + { + + projects.Add(project.ToProjectListVMFromProject()); + } + + + + return Ok(ApiResponse.SuccessResponse(projects, "Success.", 200)); + } + + [HttpPost("assign-projects/{employeeId}")] + public async Task AssigneProjectsToEmployee([FromBody] List projectAllocationDtos, [FromRoute] Guid employeeId) + { + if (projectAllocationDtos != null && employeeId != Guid.Empty) + { + Guid TenentID = GetTenantId(); + var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + List? result = new List(); + List projectIds = new List(); + + foreach (var projectAllocationDto in projectAllocationDtos) + { + try + { + ProjectAllocation projectAllocation = projectAllocationDto.ToProjectAllocationFromProjectsAllocationDto(TenentID, employeeId); + ProjectAllocation? projectAllocationFromDb = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.ProjectId == projectAllocationDto.ProjectId && c.ReAllocationDate == null && c.TenantId == TenentID).SingleOrDefaultAsync(); + + if (projectAllocationFromDb != null) + { + + + _context.ProjectAllocations.Attach(projectAllocationFromDb); + + if (projectAllocationDto.Status) + { + projectAllocationFromDb.JobRoleId = projectAllocation.JobRoleId; ; + projectAllocationFromDb.IsActive = true; + _context.Entry(projectAllocationFromDb).Property(e => e.JobRoleId).IsModified = true; + _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true; + } + else + { + projectAllocationFromDb.ReAllocationDate = DateTime.UtcNow; + projectAllocationFromDb.IsActive = false; + _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true; + _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true; + + projectIds.Add(projectAllocation.ProjectId); + } + await _context.SaveChangesAsync(); + var result1 = new + { + Id = projectAllocationFromDb.Id, + EmployeeId = projectAllocation.EmployeeId, + JobRoleId = projectAllocation.JobRoleId, + IsActive = projectAllocation.IsActive, + ProjectId = projectAllocation.ProjectId, + AllocationDate = projectAllocation.AllocationDate, + ReAllocationDate = projectAllocation.ReAllocationDate, + TenantId = projectAllocation.TenantId + }; + result.Add(result1); + } + else + { + projectAllocation.AllocationDate = DateTime.Now; + projectAllocation.IsActive = true; + _context.ProjectAllocations.Add(projectAllocation); + await _context.SaveChangesAsync(); + + projectIds.Add(projectAllocation.ProjectId); + + } + + + } + catch (Exception ex) + { + + return Ok(ApiResponse.ErrorResponse(ex.Message, ex, 400)); + } + } + await _cache.ClearAllProjectIds(employeeId); + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeId = employeeId }; + + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + + return Ok(ApiResponse.SuccessResponse(result, "Data saved successfully", 200)); + } + else + { + return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "All Field is required", 400)); + } + + } + + #endregion + + #region =================================================================== Project InfraStructure Get APIs =================================================================== [HttpGet("infra-details/{projectId}")] public async Task GetInfraDetails(Guid projectId) @@ -1026,6 +956,10 @@ namespace MarcoBMS.Services.Controllers return Ok(ApiResponse.SuccessResponse(workItemVMs, $"{workItemVMs.Count} records of tasks fetched successfully", 200)); } + #endregion + + #region =================================================================== Project Infrastructre Manage APIs =================================================================== + [HttpPost("task")] public async Task CreateProjectTask(List workItemDtos) { @@ -1309,131 +1243,172 @@ namespace MarcoBMS.Services.Controllers } - [HttpGet("assigned-projects/{employeeId}")] - public async Task GetProjectsByEmployee([FromRoute] Guid employeeId) + #endregion + + #region =================================================================== Helper Functions =================================================================== + + /// + /// Retrieves a list of ProjectInfoVMs by their IDs, using an efficient partial cache-hit strategy. + /// It fetches what it can from the cache (as ProjectMongoDB), gets the rest from the + /// database (as Project), updates the cache, and returns a unified list of ViewModels. + /// + /// The list of project IDs to retrieve. + /// A list of ProjectInfoVMs. + private async Task> GetProjectInfosByIdsAsync(List projectIds) { + // --- Step 1: Fetch from Cache --- + // The cache returns a list of MongoDB documents for the projects it found. + var cachedMongoDocs = await _cache.GetProjectDetailsList(projectIds) ?? new List(); + var finalViewModels = _mapper.Map>(cachedMongoDocs); - Guid tenantId = _userHelper.GetTenantId(); - if (employeeId == Guid.Empty) + _logger.LogDebug("Cache hit for {CacheCount} of {TotalCount} projects.", finalViewModels.Count, projectIds.Count); + + // --- Step 2: Identify Missing Projects --- + // If we found everything in the cache, we can return early. + if (finalViewModels.Count == projectIds.Count) { - return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "Employee id not valid.", 400)); + return finalViewModels; } - List projectList = await _context.ProjectAllocations - .Where(c => c.TenantId == tenantId && c.EmployeeId == employeeId && c.IsActive) - .Select(c => c.ProjectId).Distinct() - .ToListAsync(); + var cachedIds = cachedMongoDocs.Select(p => p.Id).ToHashSet(); // Assuming ProjectMongoDB has an Id + var missingIds = projectIds.Where(id => !cachedIds.Contains(id.ToString())).ToList(); - if (!projectList.Any()) + // --- Step 3: Fetch Missing from Database --- + if (missingIds.Any()) { - return NotFound(ApiResponse.SuccessResponse(new List(), "No projects found.", 200)); - } + _logger.LogDebug("Cache miss for {MissingCount} projects. Querying database.", missingIds.Count); + var projectsFromDb = await _context.Projects + .Where(p => missingIds.Contains(p.Id)) + .AsNoTracking() // Use AsNoTracking for read-only query performance + .ToListAsync(); - List projectlist = await _context.Projects - .Where(p => projectList.Contains(p.Id)) - .ToListAsync(); - - List projects = new List(); - - - foreach (var project in projectlist) - { - - projects.Add(project.ToProjectListVMFromProject()); - } - - - - return Ok(ApiResponse.SuccessResponse(projects, "Success.", 200)); - } - - [HttpPost("assign-projects/{employeeId}")] - public async Task AssigneProjectsToEmployee([FromBody] List projectAllocationDtos, [FromRoute] Guid employeeId) - { - if (projectAllocationDtos != null && employeeId != Guid.Empty) - { - Guid TenentID = GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - List? result = new List(); - List projectIds = new List(); - - foreach (var projectAllocationDto in projectAllocationDtos) + if (projectsFromDb.Any()) { - try - { - ProjectAllocation projectAllocation = projectAllocationDto.ToProjectAllocationFromProjectsAllocationDto(TenentID, employeeId); - ProjectAllocation? projectAllocationFromDb = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.ProjectId == projectAllocationDto.ProjectId && c.ReAllocationDate == null && c.TenantId == TenentID).SingleOrDefaultAsync(); + // Map the newly fetched projects (from SQL) to their ViewModel + var vmsFromDb = _mapper.Map>(projectsFromDb); + finalViewModels.AddRange(vmsFromDb); - if (projectAllocationFromDb != null) - { - - - _context.ProjectAllocations.Attach(projectAllocationFromDb); - - if (projectAllocationDto.Status) - { - projectAllocationFromDb.JobRoleId = projectAllocation.JobRoleId; ; - projectAllocationFromDb.IsActive = true; - _context.Entry(projectAllocationFromDb).Property(e => e.JobRoleId).IsModified = true; - _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true; - } - else - { - projectAllocationFromDb.ReAllocationDate = DateTime.UtcNow; - projectAllocationFromDb.IsActive = false; - _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true; - _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true; - - projectIds.Add(projectAllocation.ProjectId); - } - await _context.SaveChangesAsync(); - var result1 = new - { - Id = projectAllocationFromDb.Id, - EmployeeId = projectAllocation.EmployeeId, - JobRoleId = projectAllocation.JobRoleId, - IsActive = projectAllocation.IsActive, - ProjectId = projectAllocation.ProjectId, - AllocationDate = projectAllocation.AllocationDate, - ReAllocationDate = projectAllocation.ReAllocationDate, - TenantId = projectAllocation.TenantId - }; - result.Add(result1); - } - else - { - projectAllocation.AllocationDate = DateTime.Now; - projectAllocation.IsActive = true; - _context.ProjectAllocations.Add(projectAllocation); - await _context.SaveChangesAsync(); - - projectIds.Add(projectAllocation.ProjectId); - - } - - - } - catch (Exception ex) - { - - return Ok(ApiResponse.ErrorResponse(ex.Message, ex, 400)); - } + // --- Step 4: Update Cache with Missing Items in a new scope --- + _logger.LogDebug("Updating cache with {DbCount} newly fetched projects.", projectsFromDb.Count); + await _cache.AddProjectDetailsList(projectsFromDb); } - await _cache.ClearAllProjectIds(employeeId); - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeId = employeeId }; - - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); - - return Ok(ApiResponse.SuccessResponse(result, "Data saved successfully", 200)); - } - else - { - return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "All Field is required", 400)); } + return finalViewModels; + } + + private Guid GetTenantId() + { + return _userHelper.GetTenantId(); + } + + private async Task GetProjectViewModel(Guid? id, Project project) + { + ProjectDetailsVM vm = new ProjectDetailsVM(); + + // List buildings = _unitOfWork.Building.GetAll(c => c.ProjectId == id).ToList(); + List buildings = await _context.Buildings.Where(c => c.ProjectId == id).ToListAsync(); + List idList = buildings.Select(o => o.Id).ToList(); + // List floors = _unitOfWork.Floor.GetAll(c => idList.Contains(c.Id)).ToList(); + List floors = await _context.Floor.Where(c => idList.Contains(c.BuildingId)).ToListAsync(); + idList = floors.Select(o => o.Id).ToList(); + //List workAreas = _unitOfWork.WorkArea.GetAll(c => idList.Contains(c.Id), includeProperties: "WorkItems,WorkItems.ActivityMaster").ToList(); + + List workAreas = await _context.WorkAreas.Where(c => idList.Contains(c.FloorId)).ToListAsync(); + + idList = workAreas.Select(o => o.Id).ToList(); + List workItems = await _context.WorkItems.Include(c => c.WorkCategoryMaster).Where(c => idList.Contains(c.WorkAreaId)).Include(c => c.ActivityMaster).ToListAsync(); + // List workItems = _unitOfWork.WorkItem.GetAll(c => idList.Contains(c.WorkAreaId), includeProperties: "ActivityMaster").ToList(); + idList = workItems.Select(t => t.Id).ToList(); + List tasks = await _context.TaskAllocations.Where(t => idList.Contains(t.WorkItemId) && t.AssignmentDate.Date == DateTime.UtcNow.Date).ToListAsync(); + vm.project = project; + vm.buildings = buildings; + vm.floors = floors; + vm.workAreas = workAreas; + vm.workItems = workItems; + vm.Tasks = tasks; + return vm; } + /// + /// Fetches project details from the database for a given list of project IDs and assembles them into MongoDB models. + /// This method encapsulates the optimized, parallel database queries. + /// + /// The list of project IDs to fetch. + /// The current tenant ID for filtering. + /// A list of fully populated ProjectMongoDB objects. + private async Task> FetchAndBuildProjectDetails(List projectIdsToFetch, Guid tenantId) + { + // Task to get base project details for the MISSING projects + var projectsTask = Task.Run(async () => + { + using var context = _dbContextFactory.CreateDbContext(); + return await context.Projects.AsNoTracking() + .Where(p => projectIdsToFetch.Contains(p.Id) && p.TenantId == tenantId) + .ToListAsync(); + }); + + // Task to get team sizes for the MISSING projects + var teamSizesTask = Task.Run(async () => + { + using var context = _dbContextFactory.CreateDbContext(); + return await context.ProjectAllocations.AsNoTracking() + .Where(pa => pa.TenantId == tenantId && projectIdsToFetch.Contains(pa.ProjectId) && pa.IsActive) + .GroupBy(pa => pa.ProjectId) + .Select(g => new { ProjectId = g.Key, Count = g.Count() }) + .ToDictionaryAsync(x => x.ProjectId, x => x.Count); + }); + + // Task to get work summaries for the MISSING projects + var workSummariesTask = Task.Run(async () => + { + using var context = _dbContextFactory.CreateDbContext(); + return await context.WorkItems.AsNoTracking() + .Where(wi => wi.TenantId == tenantId && + wi.WorkArea != null && + wi.WorkArea.Floor != null && + wi.WorkArea.Floor.Building != null && + projectIdsToFetch.Contains(wi.WorkArea.Floor.Building.ProjectId)) + .GroupBy(wi => wi.WorkArea!.Floor!.Building!.ProjectId) + .Select(g => new { ProjectId = g.Key, PlannedWork = g.Sum(i => i.PlannedWork), CompletedWork = g.Sum(i => i.CompletedWork) }) + .ToDictionaryAsync(x => x.ProjectId); + }); + + // Await all parallel tasks to complete + await Task.WhenAll(projectsTask, teamSizesTask, workSummariesTask); + + var projects = await projectsTask; + var teamSizes = await teamSizesTask; + var workSummaries = await workSummariesTask; + + // Proactively update the cache with the items we just fetched. + _logger.LogInfo("Updating cache with {NewItemCount} newly fetched projects.", projects.Count); + await _cache.AddProjectDetailsList(projects); + + // This section would build the full ProjectMongoDB objects, similar to your AddProjectDetailsList method. + // For brevity, assuming you have a mapper or a builder for this. Here's a simplified representation: + var mongoDetailsList = new List(); + foreach (var project in projects) + { + // This is a placeholder for the full build logic from your other methods. + // In a real scenario, you would fetch all hierarchy levels (buildings, floors, etc.) + // for the `projectIdsToFetch` and build the complete MongoDB object. + var mongoDetail = _mapper.Map(project); + mongoDetail.Id = project.Id; + mongoDetail.TeamSize = teamSizes.GetValueOrDefault(project.Id, 0); + if (workSummaries.TryGetValue(project.Id, out var summary)) + { + mongoDetail.PlannedWork = summary.PlannedWork; + mongoDetail.CompletedWork = summary.CompletedWork; + } + mongoDetailsList.Add(mongoDetail); + } + + return mongoDetailsList; + } + + #endregion } } \ No newline at end of file diff --git a/Marco.Pms.Services/Controllers/UserController.cs b/Marco.Pms.Services/Controllers/UserController.cs index 2aeb208..4bb4432 100644 --- a/Marco.Pms.Services/Controllers/UserController.cs +++ b/Marco.Pms.Services/Controllers/UserController.cs @@ -50,7 +50,7 @@ namespace MarcoBMS.Services.Controllers emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id); } - List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(emp.Id); + List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(emp.Id); string[] projectsId = []; /* User with permission manage project can see all projects */ diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 589ab52..4369b5b 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -1,5 +1,6 @@ using Marco.Pms.CacheHelper; using Marco.Pms.DataAccess.Data; +using Marco.Pms.Model.Master; using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.Projects; using MarcoBMS.Services.Service; @@ -15,20 +16,20 @@ namespace Marco.Pms.Services.Helpers private readonly ReportCache _reportCache; private readonly ILoggingService _logger; private readonly IDbContextFactory _dbContextFactory; + private readonly ApplicationDbContext _context; public CacheUpdateHelper(ProjectCache projectCache, EmployeeCache employeeCache, ReportCache reportCache, ILoggingService logger, - IDbContextFactory dbContextFactory) + IDbContextFactory dbContextFactory, ApplicationDbContext context) { _projectCache = projectCache; _employeeCache = employeeCache; _reportCache = reportCache; _logger = logger; _dbContextFactory = dbContextFactory; + _context = context; } // ------------------------------------ Project Details Cache --------------------------------------- - // Assuming you have access to an IDbContextFactory as _dbContextFactory - // This is crucial for safe parallel database operations. public async Task AddProjectDetails(Project project) { @@ -417,9 +418,11 @@ namespace Marco.Pms.Services.Helpers } public async Task UpdateProjectDetailsOnly(Project project) { + StatusMaster projectStatus = await _context.StatusMasters + .FirstOrDefaultAsync(s => s.Id == project.ProjectStatusId) ?? new StatusMaster(); try { - bool response = await _projectCache.UpdateProjectDetailsOnlyToCache(project); + bool response = await _projectCache.UpdateProjectDetailsOnlyToCache(project, projectStatus); return response; } catch (Exception ex) @@ -457,10 +460,22 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while getting list od project details from to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while getting list of project details from to Cache: {Error}", ex.Message); return null; } } + public async Task DeleteProjectByIdAsync(Guid projectId) + { + try + { + var response = await _projectCache.DeleteProjectByIdFromCacheAsync(projectId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting project from to Cache: {Error}", ex.Message); + + } + } // ------------------------------------ Project Infrastructure Cache --------------------------------------- @@ -527,6 +542,9 @@ namespace Marco.Pms.Services.Helpers return null; } } + + // ------------------------------------------------------- WorkItem ------------------------------------------------------- + public async Task?> GetWorkItemsByWorkAreaIds(List workAreaIds) { try @@ -544,9 +562,6 @@ namespace Marco.Pms.Services.Helpers return null; } } - - // ------------------------------------------------------- WorkItem ------------------------------------------------------- - public async Task ManageWorkItemDetails(List workItems) { try @@ -609,6 +624,18 @@ namespace Marco.Pms.Services.Helpers _logger.LogWarning("Error occured while updating planned work, completed work, and today's assigned work in workItems in Cache: {Error}", ex.Message); } } + public async Task DeleteWorkItemByIdAsync(Guid workItemId) + { + try + { + var response = await _projectCache.DeleteWorkItemByIdFromCacheAsync(workItemId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting work item from to Cache: {Error}", ex.Message); + + } + } // ------------------------------------ Employee Profile Cache --------------------------------------- diff --git a/Marco.Pms.Services/Helpers/ProjectsHelper.cs b/Marco.Pms.Services/Helpers/ProjectsHelper.cs index fb5b6f2..6c1cab1 100644 --- a/Marco.Pms.Services/Helpers/ProjectsHelper.cs +++ b/Marco.Pms.Services/Helpers/ProjectsHelper.cs @@ -58,7 +58,7 @@ namespace MarcoBMS.Services.Helpers if (projectIds == null) { - var hasPermission = await _permission.HasPermission(LoggedInEmployee.Id, PermissionsMaster.ManageProject); + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageProject, LoggedInEmployee.Id); if (hasPermission) { var projects = await _context.Projects.Where(c => c.TenantId == tenantId).ToListAsync(); diff --git a/Marco.Pms.Services/Helpers/RolesHelper.cs b/Marco.Pms.Services/Helpers/RolesHelper.cs index 15bf0b1..1688dce 100644 --- a/Marco.Pms.Services/Helpers/RolesHelper.cs +++ b/Marco.Pms.Services/Helpers/RolesHelper.cs @@ -3,6 +3,7 @@ using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Entitlements; using Marco.Pms.Services.Helpers; +using MarcoBMS.Services.Service; using Microsoft.EntityFrameworkCore; namespace MarcoBMS.Services.Helpers @@ -11,33 +12,81 @@ namespace MarcoBMS.Services.Helpers { private readonly ApplicationDbContext _context; private readonly CacheUpdateHelper _cache; - public RolesHelper(ApplicationDbContext context, CacheUpdateHelper cache) + private readonly ILoggingService _logger; + public RolesHelper(ApplicationDbContext context, CacheUpdateHelper cache, ILoggingService logger) { _context = context; _cache = cache; + _logger = logger; } - public async Task> GetFeaturePermissionByEmployeeID(Guid EmployeeID) + /// + /// Retrieves a unique list of enabled feature permissions for a given employee. + /// This method is optimized to use a single, composed database query. + /// + /// The ID of the employee. + /// A distinct list of FeaturePermission objects the employee is granted. + public async Task> GetFeaturePermissionByEmployeeId(Guid EmployeeId) { - List roleMappings = await _context.EmployeeRoleMappings.Where(c => c.EmployeeId == EmployeeID && c.IsEnabled == true).Select(c => c.RoleId).ToListAsync(); + _logger.LogInfo("Fetching feature permissions for EmployeeId: {EmployeeId}", EmployeeId); - await _cache.AddApplicationRole(EmployeeID, roleMappings); + try + { + // --- Step 1: Define the subquery for the employee's roles --- + // This is an IQueryable, not a list. It will be composed directly into the main query + // by Entity Framework, avoiding a separate database call. + var employeeRoleIdsQuery = _context.EmployeeRoleMappings + .Where(erm => erm.EmployeeId == EmployeeId && erm.IsEnabled == true) + .Select(erm => erm.RoleId); - // _context.RolePermissionMappings + // --- Step 2: Asynchronously update the cache in the background (Fire and Forget) --- + // This task is started but not awaited. The main function continues immediately, + // reducing latency. The cache will be updated eventually without blocking the user. + _ = Task.Run(async () => + { + try + { + var roleIds = await employeeRoleIdsQuery.ToListAsync(); // Execute the query for the cache + if (roleIds.Any()) + { + await _cache.AddApplicationRole(EmployeeId, roleIds); + _logger.LogInfo("Successfully queued cache update for EmployeeId: {EmployeeId}", EmployeeId); + } + } + catch (Exception ex) + { + // Log errors from the background task so they are not lost. + _logger.LogWarning("Background cache update failed for EmployeeId {EmployeeId} : {Error}", EmployeeId, ex.Message); + } + }); - var result = await (from rpm in _context.RolePermissionMappings - join fp in _context.FeaturePermissions.Where(c => c.IsEnabled == true).Include(fp => fp.Feature) // Include Feature - on rpm.FeaturePermissionId equals fp.Id - where roleMappings.Contains(rpm.ApplicationRoleId) - select fp) - .ToListAsync(); + // --- Step 3: Execute the main query to get permissions in a single database call --- + // This single, efficient query gets all the required data at once. + var permissions = await ( + from rpm in _context.RolePermissionMappings + join fp in _context.FeaturePermissions.Include(f => f.Feature) // Include related Feature data + on rpm.FeaturePermissionId equals fp.Id + // The 'employeeRoleIdsQuery' subquery is seamlessly integrated here by EF Core, + // resulting in a SQL "IN (SELECT ...)" clause. + where employeeRoleIdsQuery.Contains(rpm.ApplicationRoleId) && fp.IsEnabled == true + select fp) + .Distinct() // Ensures each permission is returned only once + .ToListAsync(); - return result; + _logger.LogInfo("Successfully retrieved {PermissionCount} unique permissions for EmployeeId: {EmployeeId}", permissions.Count, EmployeeId); - // return null; + return permissions; + } + catch (Exception ex) + { + _logger.LogError("An error occurred while fetching permissions for EmployeeId {EmployeeId} :{Error}", EmployeeId, ex.Message); + // Depending on your application's error handling strategy, you might re-throw, + // or return an empty list to prevent downstream failures. + return new List(); + } } - public async Task> GetFeaturePermissionByRoleID(Guid roleId) + public async Task> GetFeaturePermissionByRoleID1(Guid roleId) { List roleMappings = await _context.RolePermissionMappings.Where(c => c.ApplicationRoleId == roleId).Select(c => c.ApplicationRoleId).ToListAsync(); @@ -54,5 +103,49 @@ namespace MarcoBMS.Services.Helpers // return null; } + /// + /// Retrieves a unique list of enabled feature permissions for a given role. + /// This method is optimized to fetch all data in a single, efficient database query. + /// + /// The ID of the role. + /// A distinct list of FeaturePermission objects granted to the role. + public async Task> GetFeaturePermissionByRoleID(Guid roleId) + { + _logger.LogInfo("Fetching feature permissions for RoleID: {RoleId}", roleId); + + try + { + // This single, efficient query gets all the required data at once. + // It joins the mapping table to the permissions table and filters by the given roleId. + var permissions = await ( + // 1. Start with the linking table. + from rpm in _context.RolePermissionMappings + + // 2. Join to the FeaturePermissions table on the foreign key. + join fp in _context.FeaturePermissions on rpm.FeaturePermissionId equals fp.Id + + // 3. Apply all filters in one 'where' clause for clarity and efficiency. + where + rpm.ApplicationRoleId == roleId // Filter by the specific role + && fp.IsEnabled == true // And only get enabled permissions + + // 4. Select the final FeaturePermission object. + select fp) + .Include(fp => fp.Feature) + .Distinct() + .ToListAsync(); + + _logger.LogInfo("Successfully retrieved {PermissionCount} unique permissions for RoleID: {RoleId}", permissions.Count, roleId); + + return permissions; + } + catch (Exception ex) + { + _logger.LogError("An error occurred while fetching permissions for RoleId {RoleId}: {Error}", roleId, ex.Message); + // Return an empty list as a safe default to prevent downstream failures. + return new List(); + } + } + } } diff --git a/Marco.Pms.Services/Service/PermissionServices.cs b/Marco.Pms.Services/Service/PermissionServices.cs index 7162dc5..f20a768 100644 --- a/Marco.Pms.Services/Service/PermissionServices.cs +++ b/Marco.Pms.Services/Service/PermissionServices.cs @@ -24,7 +24,7 @@ namespace Marco.Pms.Services.Service var featurePermissionIds = await _cache.GetPermissions(employeeId); if (featurePermissionIds == null) { - List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(employeeId); + List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId); featurePermissionIds = featurePermission.Select(fp => fp.Id).ToList(); } var hasPermission = featurePermissionIds.Contains(featurePermissionId);