diff --git a/Marco.Pms.CacheHelper/EmployeeCache.cs b/Marco.Pms.CacheHelper/EmployeeCache.cs index f7b7066..0079106 100644 --- a/Marco.Pms.CacheHelper/EmployeeCache.cs +++ b/Marco.Pms.CacheHelper/EmployeeCache.cs @@ -33,6 +33,8 @@ namespace Marco.Pms.CacheHelper var result = await _collection.UpdateOneAsync(filter, update, options); + await InitializeCollectionAsync(); + // 6. Return a more accurate result indicating success for both updates and upserts. // The operation is successful if an existing document was modified OR a new one was created. return result.IsAcknowledged && (result.ModifiedCount > 0 || result.UpsertedId != null); @@ -51,6 +53,7 @@ namespace Marco.Pms.CacheHelper { return false; } + await InitializeCollectionAsync(); return true; } public async Task> GetProjectsFromCache(Guid employeeId) @@ -177,5 +180,22 @@ namespace Marco.Pms.CacheHelper return true; } + + // A private method to handle the one-time setup of the collection's indexes. + private async Task InitializeCollectionAsync() + { + // 1. Define the TTL (Time-To-Live) index on the 'ExpireAt' field. + var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); + var indexOptions = new CreateIndexOptions + { + // This tells MongoDB to automatically delete documents when their 'ExpireAt' time is reached. + ExpireAfter = TimeSpan.FromSeconds(0) + }; + var indexModel = new CreateIndexModel(indexKeys, indexOptions); + + // 2. Create the index. This is an idempotent operation if the index already exists. + // Use CreateOneAsync since we are only creating a single index. + await _collection.Indexes.CreateOneAsync(indexModel); + } } } diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index 9417724..df95419 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -11,27 +11,59 @@ namespace Marco.Pms.CacheHelper { public class ProjectCache { - private readonly ApplicationDbContext _context; - private readonly IMongoCollection _projetCollection; + private readonly IMongoCollection _projectCollection; private readonly IMongoCollection _taskCollection; public ProjectCache(ApplicationDbContext context, IConfiguration configuration) { var connectionString = configuration["MongoDB:ConnectionString"]; - _context = context; var mongoUrl = new MongoUrl(connectionString); var client = new MongoClient(mongoUrl); // Your MongoDB connection string var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name - _projetCollection = mongoDB.GetCollection("ProjectDetails"); + _projectCollection = mongoDB.GetCollection("ProjectDetails"); _taskCollection = mongoDB.GetCollection("WorkItemDetails"); } public async Task AddProjectDetailsToCache(ProjectMongoDB projectDetails) { - await _projetCollection.InsertOneAsync(projectDetails); + await _projectCollection.InsertOneAsync(projectDetails); + + var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); + var indexOptions = new CreateIndexOptions + { + ExpireAfter = TimeSpan.Zero // required for fixed expiration time + }; + var indexModel = new CreateIndexModel(indexKeys, indexOptions); + await _projectCollection.Indexes.CreateOneAsync(indexModel); + } + // The method should focus only on inserting data. public async Task AddProjectDetailsListToCache(List projectDetailsList) { - await _projetCollection.InsertManyAsync(projectDetailsList); + // 1. Add a guard clause to avoid an unnecessary database call for an empty list. + if (projectDetailsList == null || !projectDetailsList.Any()) + { + return; + } + + // 2. Perform the insert operation. This is the only responsibility of this method. + await _projectCollection.InsertManyAsync(projectDetailsList); + await InitializeCollectionAsync(); + } + // A private method to handle the one-time setup of the collection's indexes. + private async Task InitializeCollectionAsync() + { + // 1. Define the TTL (Time-To-Live) index on the 'ExpireAt' field. + var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); + var indexOptions = new CreateIndexOptions + { + // This tells MongoDB to automatically delete documents when their 'ExpireAt' time is reached. + ExpireAfter = TimeSpan.FromSeconds(0) + }; + var indexModel = new CreateIndexModel(indexKeys, indexOptions); + + // 2. Create the index. This is an idempotent operation if the index already exists. + // Use CreateOneAsync since we are only creating a single index. + await _projectCollection.Indexes.CreateOneAsync(indexModel); } public async Task UpdateProjectDetailsOnlyToCache(Project project, StatusMaster projectStatus) { @@ -51,7 +83,7 @@ namespace Marco.Pms.CacheHelper ); // Perform the update - var result = await _projetCollection.UpdateOneAsync( + var result = await _projectCollection.UpdateOneAsync( filter: r => r.Id == project.Id.ToString(), update: updates ); @@ -71,7 +103,7 @@ namespace Marco.Pms.CacheHelper var projection = Builders.Projection.Exclude(p => p.Buildings); // Perform query - var project = await _projetCollection + var project = await _projectCollection .Find(filter) .Project(projection) .FirstOrDefaultAsync(); @@ -83,7 +115,7 @@ namespace Marco.Pms.CacheHelper List stringProjectIds = projectIds.Select(p => p.ToString()).ToList(); var filter = Builders.Filter.In(p => p.Id, stringProjectIds); var projection = Builders.Projection.Exclude(p => p.Buildings); - var projects = await _projetCollection + var projects = await _projectCollection .Find(filter) .Project(projection) .ToListAsync(); @@ -92,14 +124,14 @@ namespace Marco.Pms.CacheHelper public async Task DeleteProjectByIdFromCacheAsync(Guid projectId) { var filter = Builders.Filter.Eq(e => e.Id, projectId.ToString()); - var result = await _projetCollection.DeleteOneAsync(filter); + var result = await _projectCollection.DeleteOneAsync(filter); return result.DeletedCount > 0; } public async Task RemoveProjectsFromCacheAsync(List projectIds) { var stringIds = projectIds.Select(id => id.ToString()).ToList(); var filter = Builders.Filter.In(p => p.Id, stringIds); - var result = await _projetCollection.DeleteManyAsync(filter); + var result = await _projectCollection.DeleteManyAsync(filter); return result.DeletedCount > 0; } @@ -125,7 +157,7 @@ namespace Marco.Pms.CacheHelper var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); var update = Builders.Update.Push("Buildings", buildingMongo); - var result = await _projetCollection.UpdateOneAsync(filter, update); + var result = await _projectCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { @@ -155,7 +187,7 @@ namespace Marco.Pms.CacheHelper ); var update = Builders.Update.Push("Buildings.$.Floors", floorMongo); - var result = await _projetCollection.UpdateOneAsync(filter, update); + var result = await _projectCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { @@ -189,7 +221,7 @@ namespace Marco.Pms.CacheHelper var update = Builders.Update.Push("Buildings.$[b].Floors.$[f].WorkAreas", workAreaMongo); var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; - var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); + var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { @@ -221,7 +253,7 @@ namespace Marco.Pms.CacheHelper Builders.Update.Set("Buildings.$.Description", building.Description) ); - var result = await _projetCollection.UpdateOneAsync(filter, update); + var result = await _projectCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { @@ -246,7 +278,7 @@ namespace Marco.Pms.CacheHelper var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); - var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); + var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { @@ -272,7 +304,7 @@ namespace Marco.Pms.CacheHelper var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); - var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); + var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { @@ -296,7 +328,7 @@ namespace Marco.Pms.CacheHelper var filter = Builders.Filter.Eq(p => p.Id, projectId.ToString()); // Project only the "Buildings" field from the document - var buildings = await _projetCollection + var buildings = await _projectCollection .Find(filter) .Project(p => p.Buildings) .FirstOrDefaultAsync(); @@ -315,7 +347,7 @@ namespace Marco.Pms.CacheHelper public async Task UpdatePlannedAndCompleteWorksInBuildingFromCache(Guid workAreaId, double plannedWork, double completedWork) { var filter = Builders.Filter.Eq("Buildings.Floors.WorkAreas._id", workAreaId.ToString()); - var project = await _projetCollection.Find(filter).FirstOrDefaultAsync(); + var project = await _projectCollection.Find(filter).FirstOrDefaultAsync(); string? selectedBuildingId = null; string? selectedFloorId = null; @@ -353,7 +385,7 @@ namespace Marco.Pms.CacheHelper .Inc("Buildings.$[b].CompletedWork", completedWork) .Inc("PlannedWork", plannedWork) .Inc("CompletedWork", completedWork); - var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); + var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions); } public async Task GetBuildingAndFloorByWorkAreaIdFromCache(Guid workAreaId) @@ -393,7 +425,7 @@ namespace Marco.Pms.CacheHelper { "WorkArea", "$Buildings.Floors.WorkAreas" } }) }; - var result = await _projetCollection.Aggregate(pipeline).FirstOrDefaultAsync(); + var result = await _projectCollection.Aggregate(pipeline).FirstOrDefaultAsync(); if (result == null) return null; return result; diff --git a/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs b/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs index 49c514e..fab2b84 100644 --- a/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs @@ -9,5 +9,6 @@ namespace Marco.Pms.Model.MongoDBModels public List ApplicationRoleIds { get; set; } = new List(); public List PermissionIds { get; set; } = new List(); public List ProjectIds { get; set; } = new List(); + public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1); } } diff --git a/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs b/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs index 7f3a557..aac0e2c 100644 --- a/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs @@ -14,5 +14,6 @@ public int TeamSize { get; set; } public double CompletedWork { get; set; } public double PlannedWork { get; set; } + public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1); } } diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index 1a5e4e7..7339966 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -9,6 +9,7 @@ using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.AttendanceVM; using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Service; +using Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; @@ -28,7 +29,7 @@ namespace MarcoBMS.Services.Controllers { private readonly ApplicationDbContext _context; private readonly EmployeeHelper _employeeHelper; - private readonly ProjectsHelper _projectsHelper; + private readonly IProjectServices _projectServices; private readonly UserHelper _userHelper; private readonly S3UploadService _s3Service; private readonly PermissionServices _permission; @@ -37,11 +38,11 @@ namespace MarcoBMS.Services.Controllers public AttendanceController( - ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext signalR) + ApplicationDbContext context, EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext signalR) { _context = context; _employeeHelper = employeeHelper; - _projectsHelper = projectsHelper; + _projectServices = projectServices; _userHelper = userHelper; _s3Service = s3Service; _logger = logger; @@ -188,7 +189,7 @@ namespace MarcoBMS.Services.Controllers List lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == TenantId).ToListAsync(); - List projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true); + List projectteam = await _projectServices.GetTeamByProject(TenantId, projectId, true); var jobRole = await _context.JobRoles.ToListAsync(); foreach (Attendance? attendance in lstAttendance) { @@ -295,7 +296,7 @@ namespace MarcoBMS.Services.Controllers List lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == TenantId).ToListAsync(); - List projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, IncludeInActive); + List projectteam = await _projectServices.GetTeamByProject(TenantId, projectId, IncludeInActive); var idList = projectteam.Select(p => p.EmployeeId).ToList(); //var emp = await _context.Employees.Where(e => idList.Contains(e.Id)).Include(e => e.JobRole).ToListAsync(); var jobRole = await _context.JobRoles.ToListAsync(); @@ -378,7 +379,7 @@ namespace MarcoBMS.Services.Controllers List lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.TenantId == TenantId).ToListAsync(); - List projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true); + List projectteam = await _projectServices.GetTeamByProject(TenantId, projectId, true); var idList = projectteam.Select(p => p.EmployeeId).ToList(); var jobRole = await _context.JobRoles.ToListAsync(); diff --git a/Marco.Pms.Services/Controllers/EmployeeController.cs b/Marco.Pms.Services/Controllers/EmployeeController.cs index c9e19fa..d5d7f3d 100644 --- a/Marco.Pms.Services/Controllers/EmployeeController.cs +++ b/Marco.Pms.Services/Controllers/EmployeeController.cs @@ -9,6 +9,7 @@ using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Service; +using Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; @@ -37,13 +38,13 @@ namespace MarcoBMS.Services.Controllers private readonly ILoggingService _logger; private readonly IHubContext _signalR; private readonly PermissionServices _permission; - private readonly ProjectsHelper _projectsHelper; + private readonly IProjectServices _projectServices; private readonly Guid tenantId; public EmployeeController(UserManager userManager, IEmailSender emailSender, ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger, - IHubContext signalR, PermissionServices permission, ProjectsHelper projectsHelper) + IHubContext signalR, PermissionServices permission, IProjectServices projectServices) { _context = context; _userManager = userManager; @@ -54,7 +55,7 @@ namespace MarcoBMS.Services.Controllers _logger = logger; _signalR = signalR; _permission = permission; - _projectsHelper = projectsHelper; + _projectServices = projectServices; tenantId = _userHelper.GetTenantId(); } @@ -119,7 +120,7 @@ namespace MarcoBMS.Services.Controllers loggedInEmployee.Id, projectid ?? Guid.Empty, ShowInactive); // Step 3: Fetch project access and permissions - var projectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); + var projectIds = await _projectServices.GetMyProjectIdsAsync(tenantId, loggedInEmployee); var hasViewAllEmployeesPermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id); var hasViewTeamMembersPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id); diff --git a/Marco.Pms.Services/Controllers/UserController.cs b/Marco.Pms.Services/Controllers/UserController.cs index 4bb4432..8269d3e 100644 --- a/Marco.Pms.Services/Controllers/UserController.cs +++ b/Marco.Pms.Services/Controllers/UserController.cs @@ -4,6 +4,7 @@ using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Employee; +using Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Helpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -19,14 +20,14 @@ namespace MarcoBMS.Services.Controllers private readonly UserHelper _userHelper; private readonly EmployeeHelper _employeeHelper; - private readonly ProjectsHelper _projectsHelper; + private readonly IProjectServices _projectServices; private readonly RolesHelper _rolesHelper; - public UserController(EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, RolesHelper rolesHelper) + public UserController(EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper, RolesHelper rolesHelper) { _userHelper = userHelper; _employeeHelper = employeeHelper; - _projectsHelper = projectsHelper; + _projectServices = projectServices; _rolesHelper = rolesHelper; } @@ -56,12 +57,12 @@ namespace MarcoBMS.Services.Controllers /* User with permission manage project can see all projects */ if (featurePermission != null && featurePermission.Exists(c => c.Id.ToString() == "172fc9b6-755b-4f62-ab26-55c34a330614")) { - List projects = await _projectsHelper.GetAllProjectByTanentID(emp.TenantId); + List projects = await _projectServices.GetAllProjectByTanentID(emp.TenantId); projectsId = projects.Select(c => c.Id.ToString()).ToArray(); } else { - List allocation = await _projectsHelper.GetProjectByEmployeeID(emp.Id); + List allocation = await _projectServices.GetProjectByEmployeeID(emp.Id); projectsId = allocation.Select(c => c.ProjectId.ToString()).ToArray(); } EmployeeProfile profile = new EmployeeProfile() { }; diff --git a/Marco.Pms.Services/Helpers/ProjectHelper.cs b/Marco.Pms.Services/Helpers/ProjectHelper.cs deleted file mode 100644 index f1b688e..0000000 --- a/Marco.Pms.Services/Helpers/ProjectHelper.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Marco.Pms.DataAccess.Data; -using Marco.Pms.Model.Projects; -using Microsoft.CodeAnalysis; -using Microsoft.EntityFrameworkCore; - - -namespace ModelServices.Helpers -{ - public class ProjectHelper - { - private readonly ApplicationDbContext _context; - public ProjectHelper(ApplicationDbContext context) - { - _context = context; - } - - public async Task> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive) - { - if (IncludeInactive) - { - - var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId).Include(e => e.Employee).ToListAsync(); - - return employees; - } - else - { - var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId && c.IsActive == true).Include(e => e.Employee).ToListAsync(); - - return employees; - } - } - - - - } -} diff --git a/Marco.Pms.Services/Helpers/ProjectsHelper.cs b/Marco.Pms.Services/Helpers/ProjectsHelper.cs deleted file mode 100644 index e7e1dd6..0000000 --- a/Marco.Pms.Services/Helpers/ProjectsHelper.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Marco.Pms.DataAccess.Data; -using Marco.Pms.Model.Employees; -using Marco.Pms.Model.Entitlements; -using Marco.Pms.Model.Projects; -using Marco.Pms.Services.Helpers; -using Marco.Pms.Services.Service; -using Microsoft.EntityFrameworkCore; - -namespace MarcoBMS.Services.Helpers -{ - public class ProjectsHelper - { - private readonly ApplicationDbContext _context; - private readonly CacheUpdateHelper _cache; - private readonly PermissionServices _permission; - - public ProjectsHelper(ApplicationDbContext context, CacheUpdateHelper cache, PermissionServices permission) - { - _context = context; - _cache = cache; - _permission = permission; - } - - public async Task> GetAllProjectByTanentID(Guid tanentID) - { - List alloc = await _context.Projects.Where(c => c.TenantId == tanentID).ToListAsync(); - return alloc; - } - - public async Task> GetProjectByEmployeeID(Guid employeeID) - { - List alloc = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeID && c.IsActive == true).Include(c => c.Project).ToListAsync(); - return alloc; - } - - public async Task> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive) - { - if (IncludeInactive) - { - - var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId).Include(e => e.Employee).ToListAsync(); - - return employees; - } - else - { - var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId && c.IsActive == true).Include(e => e.Employee).ToListAsync(); - - return employees; - } - } - - public async Task> GetMyProjects(Guid tenantId, Employee LoggedInEmployee) - { - var projectIds = await _cache.GetProjects(LoggedInEmployee.Id); - - if (projectIds == null) - { - var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageProject, LoggedInEmployee.Id); - if (hasPermission) - { - var projects = await _context.Projects.Where(c => c.TenantId == tenantId).ToListAsync(); - projectIds = projects.Select(p => p.Id).ToList(); - } - else - { - var allocation = await GetProjectByEmployeeID(LoggedInEmployee.Id); - if (!allocation.Any()) - { - return new List(); - } - projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList(); - } - await _cache.AddProjects(LoggedInEmployee.Id, projectIds); - } - - return projectIds; - } - - } -} \ No newline at end of file diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index 3c73416..3f012e2 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -167,7 +167,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Marco.Pms.Services/Service/ProjectServices.cs b/Marco.Pms.Services/Service/ProjectServices.cs index d7ab2ac..9406ec9 100644 --- a/Marco.Pms.Services/Service/ProjectServices.cs +++ b/Marco.Pms.Services/Service/ProjectServices.cs @@ -12,7 +12,6 @@ using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Service.ServiceInterfaces; -using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; @@ -25,7 +24,6 @@ namespace Marco.Pms.Services.Service private readonly IDbContextFactory _dbContextFactory; private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate private readonly ILoggingService _logger; - private readonly ProjectsHelper _projectsHelper; private readonly PermissionServices _permission; private readonly CacheUpdateHelper _cache; private readonly IMapper _mapper; @@ -34,7 +32,6 @@ namespace Marco.Pms.Services.Service IDbContextFactory dbContextFactory, ApplicationDbContext context, ILoggingService logger, - ProjectsHelper projectsHelper, PermissionServices permission, CacheUpdateHelper cache, IMapper mapper, @@ -43,7 +40,6 @@ namespace Marco.Pms.Services.Service _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); _context = context ?? throw new ArgumentNullException(nameof(context)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _projectsHelper = projectsHelper ?? throw new ArgumentNullException(nameof(projectsHelper)); _permission = permission ?? throw new ArgumentNullException(nameof(permission)); _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); @@ -64,7 +60,7 @@ namespace Marco.Pms.Services.Service _logger.LogInfo("Basic project list requested by EmployeeId {EmployeeId}", loggedInEmployee.Id); // Step 2: Get the list of project IDs the user has access to - List accessibleProjectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); + List accessibleProjectIds = await GetMyProjects(tenantId, loggedInEmployee); if (accessibleProjectIds == null || !accessibleProjectIds.Any()) { @@ -94,7 +90,7 @@ namespace Marco.Pms.Services.Service _logger.LogInfo("Starting GetAllProjects for TenantId: {TenantId}, User: {UserId}", tenantId, loggedInEmployee.Id); // --- Step 1: Get a list of project IDs the user can access --- - List projectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); + List projectIds = await GetMyProjects(tenantId, loggedInEmployee); if (!projectIds.Any()) { _logger.LogInfo("User has no assigned projects. Returning empty list."); @@ -743,7 +739,7 @@ namespace Marco.Pms.Services.Service // This is a placeholder for your actual, more specific permission logic. // It should also handle the case where a user is requesting their own projects (employeeId == loggedInEmployee.Id). var hasPermission = await _permission.HasPermission(PermissionsMaster.ViewProject, loggedInEmployee.Id); - var projectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); + var projectIds = await GetMyProjects(tenantId, loggedInEmployee); if (!hasPermission) { _logger.LogWarning("Access DENIED for user {UserId} trying to view projects for employee {TargetEmployeeId}.", loggedInEmployee.Id, employeeId); @@ -1329,6 +1325,110 @@ namespace Marco.Pms.Services.Service #region =================================================================== Helper Functions =================================================================== + public async Task> GetAllProjectByTanentID(Guid tanentId) + { + List alloc = await _context.Projects.Where(c => c.TenantId == tanentId).ToListAsync(); + return alloc; + } + + public async Task> GetProjectByEmployeeID(Guid employeeId) + { + List alloc = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.IsActive == true).Include(c => c.Project).ToListAsync(); + return alloc; + } + + public async Task> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive) + { + if (IncludeInactive) + { + + var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId).Include(e => e.Employee).ToListAsync(); + + return employees; + } + else + { + var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId && c.IsActive == true).Include(e => e.Employee).ToListAsync(); + + return employees; + } + } + + public async Task> GetMyProjects(Guid tenantId, Employee LoggedInEmployee) + { + var projectIds = await _cache.GetProjects(LoggedInEmployee.Id); + + if (projectIds == null) + { + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageProject, LoggedInEmployee.Id); + if (hasPermission) + { + var projects = await _context.Projects.Where(c => c.TenantId == tenantId).ToListAsync(); + projectIds = projects.Select(p => p.Id).ToList(); + } + else + { + var allocation = await GetProjectByEmployeeID(LoggedInEmployee.Id); + if (!allocation.Any()) + { + return new List(); + } + projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList(); + } + await _cache.AddProjects(LoggedInEmployee.Id, projectIds); + } + return projectIds; + } + + public async Task> GetMyProjectIdsAsync(Guid tenantId, Employee loggedInEmployee) + { + // 1. Attempt to retrieve the list of project IDs from the cache first. + // This is the "happy path" and should be as fast as possible. + List? projectIds = await _cache.GetProjects(loggedInEmployee.Id); + + if (projectIds != null) + { + // Cache Hit: Return the cached list immediately. + return projectIds; + } + + // 2. Cache Miss: The list was not in the cache, so we must fetch it from the database. + List newProjectIds; + + // Check for the specific permission. + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageProject, loggedInEmployee.Id); + + if (hasPermission) + { + // 3a. OPTIMIZATION: User has permission to see all projects. + // Fetch *only* the Ids directly from the database. This is far more efficient + // than fetching full Project objects and then selecting the Ids in memory. + newProjectIds = await _context.Projects + .Where(p => p.TenantId == tenantId) + .Select(p => p.Id) // This translates to `SELECT Id FROM Projects...` in SQL. + .ToListAsync(); + } + else + { + // 3b. OPTIMIZATION: User can only see projects they are allocated to. + // We go directly to the source (ProjectAllocations) and ask the database + // for a distinct list of ProjectIds. This is much better than calling a + // helper function that might return full allocation objects. + newProjectIds = await _context.ProjectAllocations + .Where(a => a.EmployeeId == loggedInEmployee.Id && a.ProjectId != Guid.Empty) + .Select(a => a.ProjectId) + .Distinct() // Pushes the DISTINCT operation to the database. + .ToListAsync(); + } + + // 4. Populate the cache with the newly fetched list (even if it's empty). + // This prevents repeated database queries for employees with no projects. + await _cache.AddProjects(loggedInEmployee.Id, newProjectIds); + + return newProjectIds; + } + + /// /// 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 diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs index 0c7c964..b5acccc 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs @@ -1,5 +1,6 @@ using Marco.Pms.Model.Dtos.Project; using Marco.Pms.Model.Employees; +using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Projects; @@ -25,5 +26,10 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task>> CreateProjectTaskAsync(List workItemDtos, Guid tenantId, Employee loggedInEmployee); Task DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee); + Task> GetAllProjectByTanentID(Guid tanentId); + Task> GetProjectByEmployeeID(Guid employeeId); + Task> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive); + Task> GetMyProjectIdsAsync(Guid tenantId, Employee LoggedInEmployee); + } }