From 2c445878d0d805c5cfc935e527373954cfcc9cc2 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 1 Jul 2025 12:39:07 +0530 Subject: [PATCH 1/6] project details API is split into three APIs. --- .../ViewModels/Projects/ProjectVM.cs | 13 +- .../Controllers/ProjectController.cs | 281 +++++++++++------- 2 files changed, 179 insertions(+), 115 deletions(-) diff --git a/Marco.Pms.Model/ViewModels/Projects/ProjectVM.cs b/Marco.Pms.Model/ViewModels/Projects/ProjectVM.cs index cd349bb..240b35f 100644 --- a/Marco.Pms.Model/ViewModels/Projects/ProjectVM.cs +++ b/Marco.Pms.Model/ViewModels/Projects/ProjectVM.cs @@ -1,10 +1,17 @@ -using Marco.Pms.Model.Dtos.Project; +using Marco.Pms.Model.Master; namespace Marco.Pms.Model.ViewModels.Projects { - public class ProjectVM : ProjectDto + public class ProjectVM { - public List? Buildings { get; set; } + public Guid Id { get; set; } + public string? Name { get; set; } + public string? ShortName { get; set; } + public string? ProjectAddress { get; set; } + public string? ContactPerson { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public StatusMaster? ProjectStatus { get; set; } } } diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 6b83a6c..6490c54 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -1,14 +1,13 @@ using Marco.Pms.DataAccess.Data; -using Marco.Pms.Model.Activities; using Marco.Pms.Model.Dtos.Project; using Marco.Pms.Model.Employees; -using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Services.Hubs; +using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; @@ -29,9 +28,16 @@ namespace MarcoBMS.Services.Controllers private readonly RolesHelper _rolesHelper; private readonly ProjectsHelper _projectsHelper; private readonly IHubContext _signalR; + private readonly PermissionServices _permission; + private readonly Guid ViewProjects; + private readonly Guid ManageProject; + private readonly Guid ViewInfra; + private readonly Guid ManageInfra; + private readonly Guid tenantId; - public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, IHubContext signalR) + public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, + IHubContext signalR, PermissionServices permission) { _context = context; _userHelper = userHelper; @@ -39,6 +45,12 @@ namespace MarcoBMS.Services.Controllers _rolesHelper = rolesHelper; _projectsHelper = projectHelper; _signalR = signalR; + _permission = permission; + ViewProjects = Guid.Parse("6ea44136-987e-44ba-9e5d-1cf8f5837ebc"); + ManageProject = Guid.Parse("172fc9b6-755b-4f62-ab26-55c34a330614"); + ViewInfra = Guid.Parse("8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"); + ManageInfra = Guid.Parse("f2aee20a-b754-4537-8166-f9507b44585b"); + tenantId = _userHelper.GetTenantId(); } @@ -177,133 +189,68 @@ namespace MarcoBMS.Services.Controllers [HttpGet("details/{id}")] public async Task Details([FromRoute] Guid id) { - // ProjectDetailsVM vm = new ProjectDetailsVM(); - + // Step 1: Validate model state 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("Invalid model state in Details endpoint. Errors: {@Errors}", errors); + return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } - var project = await _context.Projects.Where(c => c.TenantId == _userHelper.GetTenantId() && c.Id == id).Include(c => c.ProjectStatus).SingleOrDefaultAsync(); // includeProperties: "ProjectStatus,Tenant"); //_context.Stock.FindAsync(id); + // Step 2: Get logged-in employee + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + _logger.LogInfo("Details requested by EmployeeId: {EmployeeId} for ProjectId: {ProjectId}", loggedInEmployee.Id, id); + + // Step 3: Check global view project permission + var hasViewProjectPermission = await _permission.HasPermission(ViewProjects, loggedInEmployee.Id); + if (!hasViewProjectPermission) + { + _logger.LogWarning("ViewProjects permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); + return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have permission to view projects", 403)); + } + + // Step 4: Check permission for this specific project + var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, id.ToString()); + if (!hasProjectPermission) + { + _logger.LogWarning("Project-specific access denied. EmployeeId: {EmployeeId}, ProjectId: {ProjectId}", loggedInEmployee.Id, id); + return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have access to this project", 403)); + } + + // Step 5: Fetch project with status + var project = await _context.Projects + .Include(c => c.ProjectStatus) + .FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Id == id); if (project == null) { + _logger.LogWarning("Project not found. ProjectId: {ProjectId}", id); return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found", 404)); - - } - else - { - //var project = projects.Where(c => c.Id == id).SingleOrDefault(); - ProjectDetailsVM vm = await GetProjectViewModel(id, project); - - ProjectVM projectVM = new ProjectVM(); - if (vm.project != null) - { - projectVM.Id = vm.project.Id; - projectVM.Name = vm.project.Name; - projectVM.ShortName = vm.project.ShortName; - projectVM.ProjectAddress = vm.project.ProjectAddress; - projectVM.ContactPerson = vm.project.ContactPerson; - projectVM.StartDate = vm.project.StartDate; - projectVM.EndDate = vm.project.EndDate; - projectVM.ProjectStatusId = vm.project.ProjectStatusId; - } - projectVM.Buildings = new List(); - if (vm.buildings != null) - { - foreach (Building build in vm.buildings) - { - BuildingVM buildVM = new BuildingVM() { Id = build.Id, Description = build.Description, Name = build.Name }; - buildVM.Floors = new List(); - if (vm.floors != null) - { - foreach (Floor floorDto in vm.floors.Where(c => c.BuildingId == build.Id).ToList()) - { - FloorsVM floorVM = new FloorsVM() { FloorName = floorDto.FloorName, Id = floorDto.Id }; - floorVM.WorkAreas = new List(); - - if (vm.workAreas != null) - { - foreach (WorkArea workAreaDto in vm.workAreas.Where(c => c.FloorId == floorVM.Id).ToList()) - { - WorkAreaVM workAreaVM = new WorkAreaVM() { Id = workAreaDto.Id, AreaName = workAreaDto.AreaName, WorkItems = new List() }; - - if (vm.workItems != null) - { - foreach (WorkItem workItemDto in vm.workItems.Where(c => c.WorkAreaId == workAreaDto.Id).ToList()) - { - WorkItemVM workItemVM = new WorkItemVM() { WorkItemId = workItemDto.Id, WorkItem = workItemDto }; - - workItemVM.WorkItem.WorkArea = new WorkArea(); - - if (workItemVM.WorkItem.ActivityMaster != null) - { - workItemVM.WorkItem.ActivityMaster.Tenant = new Tenant(); - } - workItemVM.WorkItem.Tenant = new Tenant(); - - double todaysAssigned = 0; - if (vm.Tasks != null) - { - var tasks = vm.Tasks.Where(t => t.WorkItemId == workItemDto.Id).ToList(); - foreach (TaskAllocation task in tasks) - { - todaysAssigned += task.PlannedTask; - } - } - workItemVM.TodaysAssigned = todaysAssigned; - - workAreaVM.WorkItems.Add(workItemVM); - } - } - - floorVM.WorkAreas.Add(workAreaVM); - } - } - - buildVM.Floors.Add(floorVM); - } - } - projectVM.Buildings.Add(buildVM); - } - } - return Ok(ApiResponse.SuccessResponse(projectVM, "Success.", 200)); } - + // Step 6: Map and return result + var projectVM = GetProjectViewModel(project); + _logger.LogInfo("Project details fetched successfully. ProjectId: {ProjectId}", id); + return Ok(ApiResponse.SuccessResponse(projectVM, "Project details fetched successfully", 200)); } - private async Task GetProjectViewModel(Guid? id, Project project) + private ProjectVM GetProjectViewModel(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; + return new ProjectVM + { + Id = project.Id, + Name = project.Name, + ShortName = project.ShortName, + StartDate = project.StartDate, + EndDate = project.EndDate, + ProjectStatus = project.ProjectStatus, + ContactPerson = project.ContactPerson, + ProjectAddress = project.ProjectAddress, + }; } private Guid GetTenantId() @@ -594,6 +541,116 @@ namespace MarcoBMS.Services.Controllers } + + [HttpGet("infra-details/{projectId}")] + public async Task GetInfraDetails(Guid projectId) + { + _logger.LogInfo("GetInfraDetails called for ProjectId: {ProjectId}", projectId); + + // Step 1: Get logged-in employee + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + + // Step 2: Check project-specific permission + var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.ToString()); + if (!hasProjectPermission) + { + _logger.LogWarning("Project access denied for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", loggedInEmployee.Id, projectId); + return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have access to this project", 403)); + } + + // Step 3: Check 'ViewInfra' permission + var hasViewInfraPermission = await _permission.HasPermission(ViewInfra, loggedInEmployee.Id); + if (!hasViewInfraPermission) + { + _logger.LogWarning("ViewInfra permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); + return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have access to view infra", 403)); + } + + // Step 4: Fetch buildings for the project + var buildings = await _context.Buildings + .Where(b => b.ProjectId == projectId) + .ToListAsync(); + + var buildingIds = buildings.Select(b => b.Id).ToList(); + + // Step 5: Fetch floors associated with the buildings + var floors = await _context.Floor + .Where(f => buildingIds.Contains(f.BuildingId)) + .ToListAsync(); + + var floorIds = floors.Select(f => f.Id).ToList(); + + // Step 6: Fetch work areas associated with the floors + var workAreas = await _context.WorkAreas + .Where(wa => floorIds.Contains(wa.FloorId)) + .ToListAsync(); + + // Step 7: Build the infra hierarchy (Building > Floors > Work Areas) + var infraVM = buildings.Select(b => + { + var selectedFloors = floors + .Where(f => f.BuildingId == b.Id) + .Select(f => new + { + Id = f.Id, + FloorName = f.FloorName, + WorkAreas = workAreas + .Where(wa => wa.FloorId == f.Id) + .Select(wa => new { wa.Id, wa.AreaName }) + .ToList() + }).ToList(); + + return new + { + Id = b.Id, + BuildingName = b.Name, + Floors = selectedFloors + }; + }).ToList(); + + _logger.LogInfo("Infra details fetched successfully for ProjectId: {ProjectId}, EmployeeId: {EmployeeId}, Buildings: {Count}", + projectId, loggedInEmployee.Id, infraVM.Count); + + return Ok(ApiResponse.SuccessResponse(infraVM, "Infra details fetched successfully", 200)); + } + + [HttpGet("tasks/{workAreaId}")] + public async Task GetWorkItems(Guid workAreaId) + { + _logger.LogInfo("GetWorkItems called for WorkAreaId: {WorkAreaId}", workAreaId); + + // Step 1: Get the currently logged-in employee + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + + // Step 2: Check if the employee has ViewInfra permission + var hasViewInfraPermission = await _permission.HasPermission(ViewInfra, loggedInEmployee.Id); + if (!hasViewInfraPermission) + { + _logger.LogWarning("ViewInfra permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); + return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have permission to view infrastructure", 403)); + } + + // Step 3: Check if the specified Work Area exists + var isWorkAreaExist = await _context.WorkAreas.AnyAsync(wa => wa.Id == workAreaId); + if (!isWorkAreaExist) + { + _logger.LogWarning("Work Area not found for WorkAreaId: {WorkAreaId}", workAreaId); + return NotFound(ApiResponse.ErrorResponse("Work Area not found", "Work Area not found in database", 404)); + } + + // Step 4: Fetch WorkItems with related Activity and Work Category data + var workItems = await _context.WorkItems + .Include(wi => wi.ActivityMaster) + .Include(wi => wi.WorkCategoryMaster) + .Where(wi => wi.WorkAreaId == workAreaId) + .ToListAsync(); + + _logger.LogInfo("{Count} work items fetched successfully for WorkAreaId: {WorkAreaId}", workItems.Count, workAreaId); + + // Step 5: Return result + return Ok(ApiResponse.SuccessResponse(workItems, $"{workItems.Count} records of tasks fetched successfully", 200)); + } + [HttpPost("task")] public async Task CreateProjectTask(List workItemDtos) { From cd4ad6f4ac5002f94f628adf7d93762418c6b9f6 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 4 Jul 2025 17:49:25 +0530 Subject: [PATCH 2/6] Saving project details with infrastructure, employee permissions and assigned project for that employee in mongodb --- Marco.Pms.CacheHelper/EmployeeCache.cs | 158 +++++++ .../Marco.Pms.CacheHelper.csproj | 18 + Marco.Pms.CacheHelper/ProjectCache.cs | 434 ++++++++++++++++++ Marco.Pms.Model/Marco.Pms.Model.csproj | 1 + .../MongoDBModels/ActivityMasterMongoDB.cs | 9 + .../MongoDBModels/BuildingMongoDB.cs | 18 + .../EmployeePermissionMongoDB.cs | 13 + Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs | 17 + .../MongoDBModels/ProjectMongoDB.cs | 18 + .../MongoDBModels/StatusMasterMongoDB.cs | 8 + .../MongoDBModels/WorkAreaMongoDB.cs | 15 + .../WorkCategoryMasterMongoDB.cs | 9 + .../MongoDBModels/WorkItemMongoDB.cs | 15 + .../Controllers/ProjectController.cs | 221 +++++++-- .../Controllers/RolesController.cs | 12 +- Marco.Pms.Services/Dockerfile | 1 + .../Helpers/CacheUpdateHelper.cs | 98 ++++ Marco.Pms.Services/Helpers/ProjectsHelper.cs | 69 +-- Marco.Pms.Services/Helpers/RolesHelper.cs | 7 +- Marco.Pms.Services/Marco.Pms.Services.csproj | 1 + Marco.Pms.Services/Program.cs | 6 +- .../Service/PermissionServices.cs | 18 +- .../appsettings.Development.json | 4 +- .../appsettings.Production.json | 5 +- marco.pms.api.sln | 6 + 25 files changed, 1090 insertions(+), 91 deletions(-) create mode 100644 Marco.Pms.CacheHelper/EmployeeCache.cs create mode 100644 Marco.Pms.CacheHelper/Marco.Pms.CacheHelper.csproj create mode 100644 Marco.Pms.CacheHelper/ProjectCache.cs create mode 100644 Marco.Pms.Model/MongoDBModels/ActivityMasterMongoDB.cs create mode 100644 Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs create mode 100644 Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs create mode 100644 Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs create mode 100644 Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs create mode 100644 Marco.Pms.Model/MongoDBModels/StatusMasterMongoDB.cs create mode 100644 Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs create mode 100644 Marco.Pms.Model/MongoDBModels/WorkCategoryMasterMongoDB.cs create mode 100644 Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs create mode 100644 Marco.Pms.Services/Helpers/CacheUpdateHelper.cs diff --git a/Marco.Pms.CacheHelper/EmployeeCache.cs b/Marco.Pms.CacheHelper/EmployeeCache.cs new file mode 100644 index 0000000..7d75407 --- /dev/null +++ b/Marco.Pms.CacheHelper/EmployeeCache.cs @@ -0,0 +1,158 @@ +using Marco.Pms.DataAccess.Data; +using Marco.Pms.Model.MongoDBModels; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using MongoDB.Driver; + +namespace Marco.Pms.CacheHelper +{ + public class EmployeeCache + { + private readonly ApplicationDbContext _context; + //private readonly IMongoDatabase _mongoDB; + private readonly IMongoCollection _collection; + public EmployeeCache(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 + _collection = mongoDB.GetCollection("EmployeeProfile"); + } + public async Task AddApplicationRoleToCache(Guid employeeId, List roleIds) + { + var newRoleIds = roleIds.Select(r => r.ToString()).ToList(); + var newPermissionIds = await _context.RolePermissionMappings + .Where(rp => roleIds.Contains(rp.ApplicationRoleId)) + .Select(p => p.FeaturePermissionId.ToString()) + .Distinct() + .ToListAsync(); + + var filter = Builders.Filter.Eq(e => e.EmployeeId, employeeId.ToString()); + + var update = Builders.Update + .AddToSetEach(e => e.ApplicationRoleIds, newRoleIds) + .AddToSetEach(e => e.PermissionIds, newPermissionIds); + + var result = await _collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + if (result.MatchedCount == 0) + { + return false; + } + return true; + } + public async Task AddProjectsToCache(Guid employeeId, List projectIds) + { + var newprojectIds = projectIds.Select(p => p.ToString()).ToList(); + + var filter = Builders.Filter.Eq(e => e.EmployeeId, employeeId.ToString()); + + var update = Builders.Update + .AddToSetEach(e => e.ProjectIds, newprojectIds); + + var result = await _collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + if (result.MatchedCount == 0) + { + return false; + } + return true; + } + public async Task> GetProjectsFromCache(Guid employeeId) + { + var filter = Builders.Filter.Eq(e => e.EmployeeId, employeeId.ToString()); + + + var result = await _collection + .Find(filter) + .FirstOrDefaultAsync(); + + var projectIds = new List(); + if (result != null) + { + projectIds = result.ProjectIds.Select(Guid.Parse).ToList(); + } + + return projectIds; + } + public async Task> GetPermissionsFromCache(Guid employeeId) + { + var filter = Builders.Filter.Eq(e => e.EmployeeId, employeeId.ToString()); + + + var result = await _collection + .Find(filter) + .FirstOrDefaultAsync(); + + var permissionIds = new List(); + if (result != null) + { + permissionIds = result.PermissionIds.Select(Guid.Parse).ToList(); + } + + return permissionIds; + } + public async Task ClearAllProjectIdsFromCache(Guid employeeId) + { + var filter = Builders.Filter + .Eq(e => e.EmployeeId, employeeId.ToString()); + + var update = Builders.Update + .Set(e => e.ProjectIds, new List()); + + var result = await _collection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + return false; + + return true; + } + public async Task RemoveRoleIdFromCache(Guid employeeId, Guid roleId) + { + var filter = Builders.Filter + .Eq(e => e.EmployeeId, employeeId.ToString()); + + var update = Builders.Update + .Pull(e => e.ApplicationRoleIds, roleId.ToString()); + + var result = await _collection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + return false; + + if (result.ModifiedCount == 0) + return false; + + return true; + } + public async Task ClearAllPermissionIdsByEmployeeIDFromCache(Guid employeeId) + { + var filter = Builders.Filter + .Eq(e => e.EmployeeId, employeeId.ToString()); + + var update = Builders.Update + .Set(e => e.PermissionIds, new List()); + + var result = await _collection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + return false; + + return true; + } + public async Task ClearAllPermissionIdsByRoleIdFromCache(Guid roleId) + { + var filter = Builders.Filter.AnyEq(e => e.ApplicationRoleIds, roleId.ToString()); + + var update = Builders.Update + .Set(e => e.PermissionIds, new List()); + + var result = await _collection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + return false; + + return true; + } + } +} diff --git a/Marco.Pms.CacheHelper/Marco.Pms.CacheHelper.csproj b/Marco.Pms.CacheHelper/Marco.Pms.CacheHelper.csproj new file mode 100644 index 0000000..e12ac6c --- /dev/null +++ b/Marco.Pms.CacheHelper/Marco.Pms.CacheHelper.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs new file mode 100644 index 0000000..b667694 --- /dev/null +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -0,0 +1,434 @@ +using Marco.Pms.DataAccess.Data; +using Marco.Pms.Model.MongoDBModels; +using Marco.Pms.Model.Projects; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Marco.Pms.CacheHelper +{ + public class ProjectCache + { + private readonly ApplicationDbContext _context; + private readonly IMongoDatabase _mongoDB; + //private readonly ILoggingService _logger; + 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 + _mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name + } + public async Task AddProjectDetailsToCache(Project project) + { + var projectCollection = _mongoDB.GetCollection("ProjectDetails"); + + //_logger.LogInfo("[AddProjectDetails] Initiated for ProjectId: {ProjectId}", project.Id); + + var projectDetails = new ProjectMongoDB + { + Id = project.Id.ToString(), + Name = project.Name, + ShortName = project.ShortName, + ProjectAddress = project.ProjectAddress, + StartDate = project.StartDate, + EndDate = project.EndDate, + ContactPerson = project.ContactPerson + }; + + // Get project status + var status = await _context.StatusMasters + .AsNoTracking() + .FirstOrDefaultAsync(s => s.Id == project.ProjectStatusId); + + projectDetails.ProjectStatus = new StatusMasterMongoDB + { + Id = status?.Id.ToString(), + Status = status?.Status + }; + + // Get project team size + var teamSize = await _context.ProjectAllocations + .AsNoTracking() + .CountAsync(pa => pa.ProjectId == project.Id && pa.IsActive); + + projectDetails.TeamSize = teamSize; + + // Fetch related infrastructure in parallel + var buildings = await _context.Buildings + .AsNoTracking() + .Where(b => b.ProjectId == project.Id) + .ToListAsync(); + var buildingIds = buildings.Select(b => b.Id).ToList(); + + var floors = await _context.Floor + .AsNoTracking() + .Where(f => buildingIds.Contains(f.BuildingId)) + .ToListAsync(); + + var floorIds = floors.Select(f => f.Id).ToList(); + + var workAreas = await _context.WorkAreas + .AsNoTracking() + .Where(wa => floorIds.Contains(wa.FloorId)) + .ToListAsync(); + var workAreaIds = workAreas.Select(wa => wa.Id).ToList(); + + var workItems = await _context.WorkItems + .Where(wi => workAreaIds.Contains(wi.WorkAreaId)) + .ToListAsync(); + + double totalPlannedWork = 0, totalCompletedWork = 0; + + var buildingMongoList = new List(); + + foreach (var building in buildings) + { + double buildingPlanned = 0, buildingCompleted = 0; + var buildingFloors = floors.Where(f => f.BuildingId == building.Id).ToList(); + + var floorMongoList = new List(); + foreach (var floor in buildingFloors) + { + double floorPlanned = 0, floorCompleted = 0; + var floorWorkAreas = workAreas.Where(wa => wa.FloorId == floor.Id).ToList(); + + var workAreaMongoList = new List(); + foreach (var wa in floorWorkAreas) + { + var items = workItems.Where(wi => wi.WorkAreaId == wa.Id).ToList(); + double waPlanned = items.Sum(wi => wi.PlannedWork); + double waCompleted = items.Sum(wi => wi.CompletedWork); + + workAreaMongoList.Add(new WorkAreaMongoDB + { + Id = wa.Id.ToString(), + AreaName = wa.AreaName, + PlannedWork = waPlanned, + CompletedWork = waCompleted + }); + + floorPlanned += waPlanned; + floorCompleted += waCompleted; + } + + floorMongoList.Add(new FloorMongoDB + { + Id = floor.Id.ToString(), + FloorName = floor.FloorName, + PlannedWork = floorPlanned, + CompletedWork = floorCompleted, + WorkAreas = workAreaMongoList + }); + + buildingPlanned += floorPlanned; + buildingCompleted += floorCompleted; + } + + buildingMongoList.Add(new BuildingMongoDB + { + Id = building.Id.ToString(), + BuildingName = building.Name, + Description = building.Description, + PlannedWork = buildingPlanned, + CompletedWork = buildingCompleted, + Floors = floorMongoList + }); + + totalPlannedWork += buildingPlanned; + totalCompletedWork += buildingCompleted; + } + + projectDetails.Buildings = buildingMongoList; + projectDetails.PlannedWork = totalPlannedWork; + projectDetails.CompletedWork = totalCompletedWork; + + await projectCollection.InsertOneAsync(projectDetails); + //_logger.LogInfo("[AddProjectDetails] Project details inserted in MongoDB for ProjectId: {ProjectId}", project.Id); + } + public async Task UpdateProjectDetailsOnlyToCache(Project project) + { + //_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); + } + + var projectCollection = _mongoDB.GetCollection("ProjectDetails"); + + // Build the update definition + var updates = Builders.Update.Combine( + Builders.Update.Set(r => r.Name, project.Name), + Builders.Update.Set(r => r.ProjectAddress, project.ProjectAddress), + Builders.Update.Set(r => r.ShortName, project.ShortName), + Builders.Update.Set(r => r.ProjectStatus, new StatusMasterMongoDB + { + Id = projectStatus?.Id.ToString(), + Status = projectStatus?.Status + }), + Builders.Update.Set(r => r.StartDate, project.StartDate), + Builders.Update.Set(r => r.EndDate, project.EndDate), + Builders.Update.Set(r => r.ContactPerson, project.ContactPerson) + ); + + // Perform the update + var result = await projectCollection.UpdateOneAsync( + filter: r => r.Id == project.Id.ToString(), + update: updates + ); + + 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) + { + var projectCollection = _mongoDB.GetCollection("ProjectDetails"); + + // Build filter and projection to exclude large 'Buildings' list + 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 projectCollection + .Find(filter) + .Project(projection) + .FirstOrDefaultAsync(); + + if (project == null) + { + //_logger.LogWarning("No project found in MongoDB for ProjectId: {ProjectId}", projectId); + return null; + } + + //// Deserialize the result manually + //var project = BsonSerializer.Deserialize(result); + + //_logger.LogInfo("Successfully fetched project details (excluding Buildings) for ProjectId: {ProjectId}", projectId); + return project; + } + public async Task AddBuildngInfraToCache(Guid projectId, Building? building, Floor? floor, WorkArea? workArea, Guid? buildingId) + { + var stringProjectId = projectId.ToString(); + var projectCollection = _mongoDB.GetCollection("ProjectDetails"); + + // Add Building + if (building != null) + { + var buildingMongo = new BuildingMongoDB + { + Id = building.Id.ToString(), + BuildingName = building.Name, + Description = building.Description, + PlannedWork = 0, + CompletedWork = 0, + Floors = new List() + }; + + var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); + var update = Builders.Update.Push("Buildings", buildingMongo); + + var result = await projectCollection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + { + //_logger.LogWarning("Project not found while adding building. ProjectId: {ProjectId}", projectId); + return; + } + + //_logger.LogInfo("Building {BuildingId} added to project {ProjectId}", building.Id, projectId); + return; + } + + // Add Floor + if (floor != null) + { + var floorMongo = new FloorMongoDB + { + Id = floor.Id.ToString(), + FloorName = floor.FloorName, + PlannedWork = 0, + CompletedWork = 0, + WorkAreas = new List() + }; + + var filter = Builders.Filter.And( + Builders.Filter.Eq(p => p.Id, stringProjectId), + Builders.Filter.Eq("Buildings._id", floor.BuildingId.ToString()) + ); + + var update = Builders.Update.Push("Buildings.$.Floors", floorMongo); + var result = await projectCollection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + { + //_logger.LogWarning("Project or building not found while adding floor. ProjectId: {ProjectId}, BuildingId: {BuildingId}", projectId, floor.BuildingId); + return; + } + + //_logger.LogInfo("Floor {FloorId} added to building {BuildingId} in project {ProjectId}", floor.Id, floor.BuildingId, projectId); + return; + } + + // Add WorkArea + if (workArea != null && buildingId != null) + { + var workAreaMongo = new WorkAreaMongoDB + { + Id = workArea.Id.ToString(), + AreaName = workArea.AreaName, + PlannedWork = 0, + CompletedWork = 0 + }; + + var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); + + var arrayFilters = new List + { + new JsonArrayFilterDefinition("{ 'b._id': '" + buildingId + "' }"), + new JsonArrayFilterDefinition("{ 'f._id': '" + workArea.FloorId + "' }") + }; + + var update = Builders.Update.Push("Buildings.$[b].Floors.$[f].WorkAreas", workAreaMongo); + var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; + + var result = await projectCollection.UpdateOneAsync(filter, update, updateOptions); + + if (result.MatchedCount == 0) + { + //_logger.LogWarning("Project or nested structure not found while adding work area. ProjectId: {ProjectId}, BuildingId: {BuildingId}, FloorId: {FloorId}", projectId, buildingId, workArea.FloorId); + return; + } + + //_logger.LogInfo("WorkArea {WorkAreaId} added to floor {FloorId} in building {BuildingId}, ProjectId: {ProjectId}", workArea.Id, workArea.FloorId, buildingId, projectId); + return; + } + + // Fallback case when no valid data was passed + //_logger.LogWarning("No valid infra data provided to add for ProjectId: {ProjectId}", projectId); + } + public async Task UpdateBuildngInfraToCache(Guid projectId, Building? building, Floor? floor, WorkArea? workArea, Guid? buildingId) + { + var stringProjectId = projectId.ToString(); + var projectCollection = _mongoDB.GetCollection("ProjectDetails"); + + // Update Building + if (building != null) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(p => p.Id, stringProjectId), + Builders.Filter.Eq("Buildings._id", building.Id.ToString()) + ); + + var update = Builders.Update.Combine( + Builders.Update.Set("Buildings.$.BuildingName", building.Name), + Builders.Update.Set("Buildings.$.Description", building.Description) + ); + + var result = await projectCollection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + { + //_logger.LogWarning("Update failed: Project or Building not found. ProjectId: {ProjectId}, BuildingId: {BuildingId}", projectId, building.Id); + return false; + } + + //_logger.LogInfo("Building {BuildingId} updated successfully in project {ProjectId}", building.Id, projectId); + return true; + } + + // Update Floor + if (floor != null) + { + var arrayFilters = new List + { + new JsonArrayFilterDefinition("{ 'b._id': '" + floor.BuildingId + "' }"), + new JsonArrayFilterDefinition("{ 'f._id': '" + floor.Id + "' }") + }; + + var update = Builders.Update.Set("Buildings.$[b].Floors.$[f].FloorName", floor.FloorName); + var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; + var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); + + var result = await projectCollection.UpdateOneAsync(filter, update, updateOptions); + + if (result.MatchedCount == 0) + { + //_logger.LogWarning("Update failed: Project or Floor not found. ProjectId: {ProjectId}, BuildingId: {BuildingId}, FloorId: {FloorId}", projectId, floor.BuildingId, floor.Id); + return false; + } + + //_logger.LogInfo("Floor {FloorId} updated successfully in Building {BuildingId}, ProjectId: {ProjectId}", floor.Id, floor.BuildingId, projectId); + return true; + } + + // Update WorkArea + if (workArea != null && buildingId != null) + { + var arrayFilters = new List + { + new JsonArrayFilterDefinition("{ 'b._id': '" + buildingId + "' }"), + new JsonArrayFilterDefinition("{ 'f._id': '" + workArea.FloorId + "' }"), + new JsonArrayFilterDefinition("{ 'a._id': '" + workArea.Id + "' }") + }; + + var update = Builders.Update.Set("Buildings.$[b].Floors.$[f].WorkAreas.$[a].AreaName", workArea.AreaName); + var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; + var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); + + var result = await projectCollection.UpdateOneAsync(filter, update, updateOptions); + + if (result.MatchedCount == 0) + { + //_logger.LogWarning("Update failed: Project or WorkArea not found. ProjectId: {ProjectId}, BuildingId: {BuildingId}, FloorId: {FloorId}, WorkAreaId: {WorkAreaId}", + //projectId, buildingId, workArea.FloorId, workArea.Id); + return false; + } + + //_logger.LogInfo("WorkArea {WorkAreaId} updated successfully in Floor {FloorId}, Building {BuildingId}, ProjectId: {ProjectId}", + //workArea.Id, workArea.FloorId, buildingId, projectId); + return true; + } + + //_logger.LogWarning("No update performed. Missing or invalid data for ProjectId: {ProjectId}", projectId); + return false; + } + public async Task?> GetBuildingInfraFromCache(Guid projectId) + { + var projectCollection = _mongoDB.GetCollection("ProjectDetails"); + + // Filter by project ID + var filter = Builders.Filter.Eq(p => p.Id, projectId.ToString()); + + // Project only the "Buildings" field from the document + var buildings = await projectCollection + .Find(filter) + .Project(p => p.Buildings) + .FirstOrDefaultAsync(); + + //if (buildings == null) + //{ + // _logger.LogWarning("No building infrastructure found for ProjectId: {ProjectId}", projectId); + //} + //else + //{ + // _logger.LogInfo("Fetched {Count} buildings for ProjectId: {ProjectId}", buildings.Count, projectId); + //} + + return buildings; + } + } +} diff --git a/Marco.Pms.Model/Marco.Pms.Model.csproj b/Marco.Pms.Model/Marco.Pms.Model.csproj index d5927ce..a1a21a5 100644 --- a/Marco.Pms.Model/Marco.Pms.Model.csproj +++ b/Marco.Pms.Model/Marco.Pms.Model.csproj @@ -10,6 +10,7 @@ + diff --git a/Marco.Pms.Model/MongoDBModels/ActivityMasterMongoDB.cs b/Marco.Pms.Model/MongoDBModels/ActivityMasterMongoDB.cs new file mode 100644 index 0000000..37218b7 --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/ActivityMasterMongoDB.cs @@ -0,0 +1,9 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class ActivityMasterMongoDB + { + public string? Id { get; set; } + public string? ActivityName { get; set; } + public string? UnitOfMeasurement { get; set; } + } +} diff --git a/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs b/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs new file mode 100644 index 0000000..87ccb8d --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs @@ -0,0 +1,18 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class BuildingMongoDB + { + public string Id { get; set; } = string.Empty; + public string? BuildingName { get; set; } + public string? Description { get; set; } + public double PlannedWork { get; set; } + public double CompletedWork { get; set; } + public List? Floors { get; set; } + } + public class BuildingMongoDBVM + { + public string Id { get; set; } = string.Empty; + public string? Name { get; set; } + public string? Description { get; set; } + } +} diff --git a/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs b/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs new file mode 100644 index 0000000..f141798 --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs @@ -0,0 +1,13 @@ +using MongoDB.Bson.Serialization.Attributes; + +namespace Marco.Pms.Model.MongoDBModels +{ + [BsonIgnoreExtraElements] + public class EmployeePermissionMongoDB + { + public string EmployeeId { get; set; } = string.Empty; + public List ApplicationRoleIds { get; set; } = new List(); + public List PermissionIds { get; set; } = new List(); + public List ProjectIds { get; set; } = new List(); + } +} diff --git a/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs b/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs new file mode 100644 index 0000000..ae3975f --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs @@ -0,0 +1,17 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class FloorMongoDB + { + public string Id { get; set; } = string.Empty; + public string? FloorName { get; set; } + public double PlannedWork { get; set; } + public double CompletedWork { get; set; } + public List? WorkAreas { get; set; } + } + + public class FloorMongoDBVM + { + public string Id { get; set; } = string.Empty; + public string? FloorName { get; set; } + } +} diff --git a/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs b/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs new file mode 100644 index 0000000..8bf1c9a --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs @@ -0,0 +1,18 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class ProjectMongoDB + { + public string? Id { get; set; } + public string? Name { get; set; } + public string? ShortName { get; set; } + public string? ProjectAddress { get; set; } + public string? ContactPerson { get; set; } + public List? Buildings { get; set; } + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public StatusMasterMongoDB? ProjectStatus { get; set; } + public int TeamSize { get; set; } + public double CompletedWork { get; set; } + public double PlannedWork { get; set; } + } +} diff --git a/Marco.Pms.Model/MongoDBModels/StatusMasterMongoDB.cs b/Marco.Pms.Model/MongoDBModels/StatusMasterMongoDB.cs new file mode 100644 index 0000000..01a0552 --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/StatusMasterMongoDB.cs @@ -0,0 +1,8 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class StatusMasterMongoDB + { + public string? Id { get; set; } + public string? Status { get; set; } + } +} diff --git a/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs new file mode 100644 index 0000000..d17f52c --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs @@ -0,0 +1,15 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class WorkAreaMongoDB + { + public string Id { get; set; } = string.Empty; + public string? AreaName { get; set; } + public double PlannedWork { get; set; } + public double CompletedWork { get; set; } + } + public class WorkAreaMongoDBVM + { + public string Id { get; set; } = string.Empty; + public string? AreaName { get; set; } + } +} diff --git a/Marco.Pms.Model/MongoDBModels/WorkCategoryMasterMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkCategoryMasterMongoDB.cs new file mode 100644 index 0000000..aef0ada --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/WorkCategoryMasterMongoDB.cs @@ -0,0 +1,9 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class WorkCategoryMasterMongoDB + { + public string? Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + } +} diff --git a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs new file mode 100644 index 0000000..dc7fdb9 --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs @@ -0,0 +1,15 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class WorkItemMongoDB + { + public string? Id { get; set; } + public string? WorkAreaId { get; set; } + public ActivityMasterMongoDB? ActivityMaster { get; set; } + public WorkCategoryMasterMongoDB? WorkCategoryMaster { get; set; } + public string? ParentTaskId { get; set; } + public double PlannedWork { get; set; } + public double CompletedWork { get; set; } + public string? Description { get; set; } + public DateTime TaskDate { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 6490c54..a440c21 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -2,10 +2,13 @@ using Marco.Pms.Model.Dtos.Project; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Mapper; +using Marco.Pms.Model.Master; +using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Projects; +using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; @@ -14,6 +17,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; +using MongoDB.Driver; namespace MarcoBMS.Services.Controllers { @@ -29,6 +33,7 @@ namespace MarcoBMS.Services.Controllers private readonly ProjectsHelper _projectsHelper; private readonly IHubContext _signalR; private readonly PermissionServices _permission; + private readonly CacheUpdateHelper _cache; private readonly Guid ViewProjects; private readonly Guid ManageProject; private readonly Guid ViewInfra; @@ -37,7 +42,7 @@ namespace MarcoBMS.Services.Controllers public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, - IHubContext signalR, PermissionServices permission) + IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache) { _context = context; _userHelper = userHelper; @@ -45,13 +50,13 @@ namespace MarcoBMS.Services.Controllers _rolesHelper = rolesHelper; _projectsHelper = projectHelper; _signalR = signalR; + _cache = cache; _permission = permission; ViewProjects = Guid.Parse("6ea44136-987e-44ba-9e5d-1cf8f5837ebc"); ManageProject = Guid.Parse("172fc9b6-755b-4f62-ab26-55c34a330614"); ViewInfra = Guid.Parse("8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"); ManageInfra = Guid.Parse("f2aee20a-b754-4537-8166-f9507b44585b"); tenantId = _userHelper.GetTenantId(); - } [HttpGet("list/basic")] @@ -222,24 +227,54 @@ namespace MarcoBMS.Services.Controllers } // Step 5: Fetch project with status - var project = await _context.Projects + var projectDetails = await _cache.GetProjectDetails(id); + ProjectVM? projectVM = null; + if (projectDetails == null) + { + var project = await _context.Projects .Include(c => c.ProjectStatus) .FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Id == id); + projectVM = GetProjectViewModel(project); + } + else + { + 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 (project == null) + if (projectVM == null) { _logger.LogWarning("Project not found. ProjectId: {ProjectId}", id); return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found", 404)); } - // Step 6: Map and return result - var projectVM = GetProjectViewModel(project); + // Step 6: Return result + _logger.LogInfo("Project details fetched successfully. ProjectId: {ProjectId}", id); return Ok(ApiResponse.SuccessResponse(projectVM, "Project details fetched successfully", 200)); } - private ProjectVM GetProjectViewModel(Project project) + private ProjectVM? GetProjectViewModel(Project? project) { + if (project == null) + { + return null; + } return new ProjectVM { Id = project.Id, @@ -280,6 +315,9 @@ namespace MarcoBMS.Services.Controllers _context.Projects.Add(project); await _context.SaveChangesAsync(); + + await _cache.AddProjectDetails(project); + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Create_Project", Response = project.ToProjectDto() }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); @@ -310,6 +348,13 @@ namespace MarcoBMS.Services.Controllers await _context.SaveChangesAsync(); + // Cache functions + bool isUpdated = await _cache.UpdateProjectDetailsOnly(project); + if (!isUpdated) + { + await _cache.AddProjectDetails(project); + } + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Update_Project", Response = project.ToProjectDto() }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); @@ -524,6 +569,7 @@ namespace MarcoBMS.Services.Controllers employeeIds.Add(projectAllocation.EmployeeId); projectIds.Add(projectAllocation.ProjectId); } + await _cache.ClearAllProjectIds(item.EmpID); } catch (Exception ex) @@ -565,53 +611,102 @@ namespace MarcoBMS.Services.Controllers _logger.LogWarning("ViewInfra permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have access to view infra", 403)); } - - // Step 4: Fetch buildings for the project - var buildings = await _context.Buildings - .Where(b => b.ProjectId == projectId) - .ToListAsync(); - - var buildingIds = buildings.Select(b => b.Id).ToList(); - - // Step 5: Fetch floors associated with the buildings - var floors = await _context.Floor - .Where(f => buildingIds.Contains(f.BuildingId)) - .ToListAsync(); - - var floorIds = floors.Select(f => f.Id).ToList(); - - // Step 6: Fetch work areas associated with the floors - var workAreas = await _context.WorkAreas - .Where(wa => floorIds.Contains(wa.FloorId)) - .ToListAsync(); - - // Step 7: Build the infra hierarchy (Building > Floors > Work Areas) - var infraVM = buildings.Select(b => + var result = await _cache.GetBuildingInfra(projectId); + if (result == null) { - var selectedFloors = floors - .Where(f => f.BuildingId == b.Id) - .Select(f => new - { - Id = f.Id, - FloorName = f.FloorName, - WorkAreas = workAreas - .Where(wa => wa.FloorId == f.Id) - .Select(wa => new { wa.Id, wa.AreaName }) - .ToList() - }).ToList(); - return new + // Step 4: Fetch buildings for the project + var buildings = await _context.Buildings + .Where(b => b.ProjectId == projectId) + .ToListAsync(); + + var buildingIds = buildings.Select(b => b.Id).ToList(); + + // Step 5: Fetch floors associated with the buildings + var floors = await _context.Floor + .Where(f => buildingIds.Contains(f.BuildingId)) + .ToListAsync(); + + var floorIds = floors.Select(f => f.Id).ToList(); + + // Step 6: Fetch work areas associated with the floors + var workAreas = await _context.WorkAreas + .Where(wa => floorIds.Contains(wa.FloorId)) + .ToListAsync(); + var workAreaIds = workAreas.Select(wa => wa.Id).ToList(); + + // Step 7: Fetch work items associated with the work area + var workItems = await _context.WorkItems + .Where(wi => workAreaIds.Contains(wi.WorkAreaId)) + .ToListAsync(); + + // Step 8: Build the infra hierarchy (Building > Floors > Work Areas) + List Buildings = new List(); + foreach (var building in buildings) { - Id = b.Id, - BuildingName = b.Name, - Floors = selectedFloors - }; - }).ToList(); + double buildingPlannedWorks = 0; + double buildingCompletedWorks = 0; + + var selectedFloors = floors.Where(f => f.BuildingId == building.Id).ToList(); + List Floors = new List(); + foreach (var floor in selectedFloors) + { + double floorPlannedWorks = 0; + double floorCompletedWorks = 0; + var selectedWorkAreas = workAreas.Where(wa => wa.FloorId == floor.Id).ToList(); + List WorkAreas = new List(); + foreach (var workArea in selectedWorkAreas) + { + double workAreaPlannedWorks = 0; + double workAreaCompletedWorks = 0; + var selectedWorkItems = workItems.Where(wi => wi.WorkAreaId == workArea.Id).ToList(); + foreach (var workItem in selectedWorkItems) + { + workAreaPlannedWorks += workItem.PlannedWork; + workAreaCompletedWorks += workItem.CompletedWork; + } + WorkAreaMongoDB workAreaMongo = new WorkAreaMongoDB + { + Id = workArea.Id.ToString(), + AreaName = workArea.AreaName, + PlannedWork = workAreaPlannedWorks, + CompletedWork = workAreaCompletedWorks + }; + WorkAreas.Add(workAreaMongo); + floorPlannedWorks += workAreaPlannedWorks; + floorCompletedWorks += workAreaCompletedWorks; + } + FloorMongoDB floorMongoDB = new FloorMongoDB + { + Id = floor.Id.ToString(), + FloorName = floor.FloorName, + PlannedWork = floorPlannedWorks, + CompletedWork = floorCompletedWorks, + WorkAreas = WorkAreas + }; + Floors.Add(floorMongoDB); + buildingPlannedWorks += floorPlannedWorks; + buildingCompletedWorks += floorCompletedWorks; + } + + var buildingMongo = new BuildingMongoDB + { + Id = building.Id.ToString(), + BuildingName = building.Name, + Description = building.Description, + PlannedWork = buildingPlannedWorks, + CompletedWork = buildingCompletedWorks, + Floors = Floors + }; + Buildings.Add(buildingMongo); + } + result = Buildings; + } _logger.LogInfo("Infra details fetched successfully for ProjectId: {ProjectId}, EmployeeId: {EmployeeId}, Buildings: {Count}", - projectId, loggedInEmployee.Id, infraVM.Count); + projectId, loggedInEmployee.Id, result.Count); - return Ok(ApiResponse.SuccessResponse(infraVM, "Infra details fetched successfully", 200)); + return Ok(ApiResponse.SuccessResponse(result, "Infra details fetched successfully", 200)); } [HttpGet("tasks/{workAreaId}")] @@ -807,6 +902,7 @@ namespace MarcoBMS.Services.Controllers responseData.building = building; responseMessage = "Buliding Added Successfully"; message = "Building Added"; + await _cache.AddBuildngInfra(building.ProjectId, building); } else { @@ -816,7 +912,7 @@ namespace MarcoBMS.Services.Controllers responseData.building = building; responseMessage = "Buliding Updated Successfully"; message = "Building Updated"; - + await _cache.UpdateBuildngInfra(building.ProjectId, building); } projectIds.Add(building.ProjectId); } @@ -824,6 +920,7 @@ namespace MarcoBMS.Services.Controllers { Floor floor = item.Floor.ToFloorFromFloorDto(tenantId); floor.TenantId = GetTenantId(); + bool isCreated = false; if (item.Floor.Id == null) { @@ -833,6 +930,7 @@ namespace MarcoBMS.Services.Controllers responseData.floor = floor; responseMessage = "Floor Added Successfully"; message = "Floor Added"; + isCreated = true; } else { @@ -844,13 +942,23 @@ namespace MarcoBMS.Services.Controllers message = "Floor Updated"; } Building? building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == floor.BuildingId); - projectIds.Add(building?.ProjectId ?? Guid.Empty); + var projectId = building?.ProjectId ?? Guid.Empty; + projectIds.Add(projectId); message = $"{message} in Building: {building?.Name}"; + if (isCreated) + { + await _cache.AddBuildngInfra(projectId, floor: floor); + } + else + { + await _cache.UpdateBuildngInfra(projectId, floor: floor); + } } if (item.WorkArea != null) { WorkArea workArea = item.WorkArea.ToWorkAreaFromWorkAreaDto(tenantId); workArea.TenantId = GetTenantId(); + bool isCreated = false; if (item.WorkArea.Id == null) { @@ -860,6 +968,7 @@ namespace MarcoBMS.Services.Controllers responseData.workArea = workArea; responseMessage = "Work Area Added Successfully"; message = "Work Area Added"; + isCreated = true; } else { @@ -871,8 +980,17 @@ namespace MarcoBMS.Services.Controllers message = "Work Area Updated"; } Floor? floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == workArea.FloorId); - projectIds.Add(floor?.Building?.ProjectId ?? Guid.Empty); + var projectId = floor?.Building?.ProjectId ?? Guid.Empty; + projectIds.Add(projectId); message = $"{message} in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}"; + if (isCreated) + { + await _cache.AddBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId); + } + else + { + await _cache.UpdateBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId); + } } } message = $"{message} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}"; @@ -996,6 +1114,7 @@ namespace MarcoBMS.Services.Controllers 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); diff --git a/Marco.Pms.Services/Controllers/RolesController.cs b/Marco.Pms.Services/Controllers/RolesController.cs index 2ac2b07..4c75b3e 100644 --- a/Marco.Pms.Services/Controllers/RolesController.cs +++ b/Marco.Pms.Services/Controllers/RolesController.cs @@ -10,6 +10,7 @@ using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels; using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Model.ViewModels.Roles; +using Marco.Pms.Services.Helpers; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; @@ -29,14 +30,17 @@ namespace MarcoBMS.Services.Controllers private readonly UserHelper _userHelper; private readonly UserManager _userManager; private readonly ILoggingService _logger; + private readonly CacheUpdateHelper _cache; - public RolesController(UserManager userManager, ApplicationDbContext context, RolesHelper rolesHelper, UserHelper userHelper, ILoggingService logger) + public RolesController(UserManager userManager, ApplicationDbContext context, RolesHelper rolesHelper, UserHelper userHelper, ILoggingService logger, + CacheUpdateHelper cache) { _context = context; _userManager = userManager; _rolesHelper = rolesHelper; _userHelper = userHelper; _logger = logger; + _cache = cache; } private Guid GetTenantId() @@ -292,6 +296,8 @@ namespace MarcoBMS.Services.Controllers if (modified) await _context.SaveChangesAsync(); + await _cache.ClearAllPermissionIdsByRoleId(id); + ApplicationRolesVM response = role.ToRoleVMFromApplicationRole(); List permissions = await _rolesHelper.GetFeaturePermissionByRoleID(response.Id); response.FeaturePermission = permissions.Select(c => c.ToFeaturePermissionVMFromFeaturePermission()).ToList(); @@ -424,12 +430,16 @@ namespace MarcoBMS.Services.Controllers if (role.IsEnabled == true) { _context.EmployeeRoleMappings.Add(mapping); + await _cache.AddApplicationRole(role.EmployeeId, [mapping.RoleId]); } } else if (role.IsEnabled == false) { _context.EmployeeRoleMappings.Remove(existingItem); + await _cache.RemoveRoleId(existingItem.EmployeeId, existingItem.RoleId); + await _cache.ClearAllPermissionIdsByEmployeeID(existingItem.EmployeeId); } + await _cache.ClearAllProjectIds(role.EmployeeId); } await _context.SaveChangesAsync(); diff --git a/Marco.Pms.Services/Dockerfile b/Marco.Pms.Services/Dockerfile index 5444e56..77311ee 100644 --- a/Marco.Pms.Services/Dockerfile +++ b/Marco.Pms.Services/Dockerfile @@ -19,6 +19,7 @@ COPY ["Marco.Pms.Services/Marco.Pms.Services.csproj", "Marco.Pms.Services/"] COPY ["Marco.Pms.DataAccess/Marco.Pms.DataAccess.csproj", "Marco.Pms.DataAccess/"] COPY ["Marco.Pms.Model/Marco.Pms.Model.csproj", "Marco.Pms.Model/"] COPY ["Marco.Pms.Utility/Marco.Pms.Utility.csproj", "Marco.Pms.Utility/"] +COPY ["Marco.Pms.Utility/Marco.Pms.CacheHelper.csproj", "Marco.Pms.CacheHelper/"] RUN dotnet restore "./Marco.Pms.Services/Marco.Pms.Services.csproj" COPY . . WORKDIR "/src/Marco.Pms.Services" diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs new file mode 100644 index 0000000..1c3ee70 --- /dev/null +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -0,0 +1,98 @@ +using Marco.Pms.CacheHelper; +using Marco.Pms.Model.MongoDBModels; +using Marco.Pms.Model.Projects; +using Project = Marco.Pms.Model.Projects.Project; + +namespace Marco.Pms.Services.Helpers +{ + public class CacheUpdateHelper + { + private readonly ProjectCache _projectCache; + private readonly EmployeeCache _employeeCache; + + public CacheUpdateHelper(ProjectCache projectCache, EmployeeCache employeeCache) + { + _projectCache = projectCache; + _employeeCache = employeeCache; + } + + // ------------------------------------ Project Details and Infrastructure Cache --------------------------------------- + public async Task AddProjectDetails(Project project) + { + await _projectCache.AddProjectDetailsToCache(project); + } + public async Task UpdateProjectDetailsOnly(Project project) + { + bool response = await _projectCache.UpdateProjectDetailsOnlyToCache(project); + return response; + } + public async Task GetProjectDetails(Guid projectId) + { + var response = await _projectCache.GetProjectDetailsFromCache(projectId); + return response; + } + public async Task AddBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null) + { + await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId); + } + public async Task UpdateBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null) + { + var response = await _projectCache.UpdateBuildngInfraToCache(projectId, building, floor, workArea, buildingId); + if (!response) + { + await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId); + } + } + public async Task?> GetBuildingInfra(Guid projectId) + { + var response = await _projectCache.GetBuildingInfraFromCache(projectId); + return response; + } + + + // ------------------------------------ Employee Profile Cache --------------------------------------- + public async Task AddApplicationRole(Guid employeeId, List roleIds) + { + var response = await _employeeCache.AddApplicationRoleToCache(employeeId, roleIds); + } + public async Task AddProjects(Guid employeeId, List projectIds) + { + var response = await _employeeCache.AddProjectsToCache(employeeId, projectIds); + return response; + } + public async Task?> GetProjects(Guid employeeId) + { + var response = await _employeeCache.GetProjectsFromCache(employeeId); + if (response.Count > 0) + { + return response; + } + return null; + } + public async Task?> GetPermissions(Guid employeeId) + { + var response = await _employeeCache.GetPermissionsFromCache(employeeId); + if (response.Count > 0) + { + return response; + } + return null; + } + public async Task ClearAllProjectIds(Guid employeeId) + { + var response = await _employeeCache.ClearAllProjectIdsFromCache(employeeId); + } + public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId) + { + var response = await _employeeCache.ClearAllPermissionIdsByEmployeeIDFromCache(employeeId); + } + public async Task ClearAllPermissionIdsByRoleId(Guid roleId) + { + var response = await _employeeCache.ClearAllPermissionIdsByRoleIdFromCache(roleId); + } + public async Task RemoveRoleId(Guid employeeId, Guid roleId) + { + var response = await _employeeCache.RemoveRoleIdFromCache(employeeId, roleId); + } + } +} diff --git a/Marco.Pms.Services/Helpers/ProjectsHelper.cs b/Marco.Pms.Services/Helpers/ProjectsHelper.cs index 8ccbc85..3ccddba 100644 --- a/Marco.Pms.Services/Helpers/ProjectsHelper.cs +++ b/Marco.Pms.Services/Helpers/ProjectsHelper.cs @@ -2,11 +2,8 @@ using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Projects; -using Marco.Pms.Model.Utilities; -using Marco.Pms.Model.ViewModels.Projects; -using Microsoft.AspNetCore.Mvc; +using Marco.Pms.Services.Helpers; using Microsoft.EntityFrameworkCore; -using ModelServices.Helpers; namespace MarcoBMS.Services.Helpers { @@ -14,12 +11,14 @@ namespace MarcoBMS.Services.Helpers { private readonly ApplicationDbContext _context; private readonly RolesHelper _rolesHelper; + private readonly CacheUpdateHelper _cache; - public ProjectsHelper(ApplicationDbContext context, RolesHelper rolesHelper) + public ProjectsHelper(ApplicationDbContext context, RolesHelper rolesHelper, CacheUpdateHelper cache) { _context = context; _rolesHelper = rolesHelper; + _cache = cache; } public async Task> GetAllProjectByTanentID(Guid tanentID) @@ -53,40 +52,56 @@ namespace MarcoBMS.Services.Helpers public async Task> GetMyProjects(Guid tenantId, Employee LoggedInEmployee) { - List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(LoggedInEmployee.Id); - string[] projectsId = []; List projects = new List(); - // Define a common queryable base for projects - IQueryable projectQuery = _context.Projects.Where(c => c.TenantId == tenantId); + var projectIds = await _cache.GetProjects(LoggedInEmployee.Id); - // 2. Optimized Project Retrieval Logic - // User with permission 'manage project' can see all projects - if (featurePermission != null && featurePermission.Exists(c => c.Id.ToString() == "172fc9b6-755b-4f62-ab26-55c34a330614")) + if (projectIds != null) { - // If GetAllProjectByTanentID is already optimized and directly returns IQueryable or - // directly executes with ToListAsync(), keep it. - // If it does more complex logic or extra trips, consider inlining here. - projects = await projectQuery.ToListAsync(); // Directly query the context + projects = await _context.Projects.Where(p => projectIds.Contains(p.Id)).ToListAsync(); } else { - // 3. Efficiently get project allocations and then filter projects - // Load allocations only once - var allocation = await GetProjectByEmployeeID(LoggedInEmployee.Id); - - // If there are no allocations, return an empty list early - if (allocation == null || !allocation.Any()) + var featurePermissionIds = await _cache.GetPermissions(LoggedInEmployee.Id); + if (featurePermissionIds == null) { - return new List(); + List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(LoggedInEmployee.Id); + featurePermissionIds = featurePermission.Select(fp => fp.Id).ToList(); } + // Define a common queryable base for projects + IQueryable projectQuery = _context.Projects.Where(c => c.TenantId == tenantId); - // Use LINQ's Contains for efficient filtering by ProjectId - var projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList(); // Get distinct Guids + // 2. Optimized Project Retrieval Logic + // User with permission 'manage project' can see all projects + if (featurePermissionIds != null && featurePermissionIds.Contains(Guid.Parse("172fc9b6-755b-4f62-ab26-55c34a330614"))) + { + // If GetAllProjectByTanentID is already optimized and directly returns IQueryable or + // directly executes with ToListAsync(), keep it. + // If it does more complex logic or extra trips, consider inlining here. + projects = await projectQuery.ToListAsync(); // Directly query the context + } + else + { + // 3. Efficiently get project allocations and then filter projects + // Load allocations only once + var allocation = await GetProjectByEmployeeID(LoggedInEmployee.Id); - // Filter projects based on the retrieved ProjectIds - projects = await projectQuery.Where(c => projectIds.Contains(c.Id)).ToListAsync(); + // If there are no allocations, return an empty list early + if (allocation == null || !allocation.Any()) + { + return new List(); + } + + // Use LINQ's Contains for efficient filtering by ProjectId + projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList(); // Get distinct Guids + + // Filter projects based on the retrieved ProjectIds + projects = await projectQuery.Where(c => projectIds.Contains(c.Id)).ToListAsync(); + + } + projectIds = projects.Select(p => p.Id).ToList(); + await _cache.AddProjects(LoggedInEmployee.Id, projectIds); } return projects; diff --git a/Marco.Pms.Services/Helpers/RolesHelper.cs b/Marco.Pms.Services/Helpers/RolesHelper.cs index b571d03..15bf0b1 100644 --- a/Marco.Pms.Services/Helpers/RolesHelper.cs +++ b/Marco.Pms.Services/Helpers/RolesHelper.cs @@ -2,6 +2,7 @@ using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Entitlements; +using Marco.Pms.Services.Helpers; using Microsoft.EntityFrameworkCore; namespace MarcoBMS.Services.Helpers @@ -9,15 +10,19 @@ namespace MarcoBMS.Services.Helpers public class RolesHelper { private readonly ApplicationDbContext _context; - public RolesHelper(ApplicationDbContext context) + private readonly CacheUpdateHelper _cache; + public RolesHelper(ApplicationDbContext context, CacheUpdateHelper cache) { _context = context; + _cache = cache; } public async Task> GetFeaturePermissionByEmployeeID(Guid EmployeeID) { List roleMappings = await _context.EmployeeRoleMappings.Where(c => c.EmployeeId == EmployeeID && c.IsEnabled == true).Select(c => c.RoleId).ToListAsync(); + await _cache.AddApplicationRole(EmployeeID, roleMappings); + // _context.RolePermissionMappings var result = await (from rpm in _context.RolePermissionMappings diff --git a/Marco.Pms.Services/Marco.Pms.Services.csproj b/Marco.Pms.Services/Marco.Pms.Services.csproj index 7bef32f..a235e6a 100644 --- a/Marco.Pms.Services/Marco.Pms.Services.csproj +++ b/Marco.Pms.Services/Marco.Pms.Services.csproj @@ -44,6 +44,7 @@ + diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index 17eb5c7..1d9b4b3 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -1,4 +1,5 @@ using System.Text; +using Marco.Pms.CacheHelper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Authentication; using Marco.Pms.Model.Entitlements; @@ -136,6 +137,9 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddSingleton(); @@ -225,7 +229,7 @@ app.UseStaticFiles(); // Enables serving static files app.UseHttpsRedirection(); - +app.UseAuthentication(); app.UseAuthorization(); app.MapHub("/hubs/marco"); app.MapControllers(); diff --git a/Marco.Pms.Services/Service/PermissionServices.cs b/Marco.Pms.Services/Service/PermissionServices.cs index f3ddb58..ce7476b 100644 --- a/Marco.Pms.Services/Service/PermissionServices.cs +++ b/Marco.Pms.Services/Service/PermissionServices.cs @@ -2,6 +2,7 @@ using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Projects; +using Marco.Pms.Services.Helpers; using MarcoBMS.Services.Helpers; using Microsoft.EntityFrameworkCore; @@ -12,21 +13,24 @@ namespace Marco.Pms.Services.Service private readonly ApplicationDbContext _context; private readonly RolesHelper _rolesHelper; private readonly ProjectsHelper _projectsHelper; - public PermissionServices(ApplicationDbContext context, RolesHelper rolesHelper, ProjectsHelper projectsHelper) + private readonly CacheUpdateHelper _cache; + public PermissionServices(ApplicationDbContext context, RolesHelper rolesHelper, ProjectsHelper projectsHelper, CacheUpdateHelper cache) { _context = context; _rolesHelper = rolesHelper; _projectsHelper = projectsHelper; + _cache = cache; } public async Task HasPermission(Guid featurePermissionId, Guid employeeId) { - var hasPermission = await _context.EmployeeRoleMappings - .Where(er => er.EmployeeId == employeeId) - .Select(er => er.RoleId) - .Distinct() - .AnyAsync(roleId => _context.RolePermissionMappings - .Any(rp => rp.FeaturePermissionId == featurePermissionId && rp.ApplicationRoleId == roleId)); + var featurePermissionIds = await _cache.GetPermissions(employeeId); + if (featurePermissionIds == null) + { + List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(employeeId); + featurePermissionIds = featurePermission.Select(fp => fp.Id).ToList(); + } + var hasPermission = featurePermissionIds.Contains(featurePermissionId); return hasPermission; } public async Task HasProjectPermission(Employee emp, string projectId) diff --git a/Marco.Pms.Services/appsettings.Development.json b/Marco.Pms.Services/appsettings.Development.json index 1565018..ce80dc0 100644 --- a/Marco.Pms.Services/appsettings.Development.json +++ b/Marco.Pms.Services/appsettings.Development.json @@ -47,6 +47,8 @@ "BucketName": "testenv-marco-pms-documents" }, "MongoDB": { - "SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs" + "SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs", + "ConnectionString": "mongodb://localhost:27017/MarcoBMS_Caches" + //"DatabaseName": "" } } diff --git a/Marco.Pms.Services/appsettings.Production.json b/Marco.Pms.Services/appsettings.Production.json index 81aa998..0abe3f1 100644 --- a/Marco.Pms.Services/appsettings.Production.json +++ b/Marco.Pms.Services/appsettings.Production.json @@ -6,7 +6,7 @@ }, "Environment": { "Name": "Production", - "Title": "" + "Title": "" }, "ConnectionStrings": { "DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1" @@ -40,6 +40,7 @@ "BucketName": "testenv-marco-pms-documents" }, "MongoDB": { - "SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs" + "SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs", + "ConnectionString": "mongodb://localhost:27017/MarcoBMS_Caches" } } \ No newline at end of file diff --git a/marco.pms.api.sln b/marco.pms.api.sln index 49d3e8c..424b709 100644 --- a/marco.pms.api.sln +++ b/marco.pms.api.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marco.Pms.Utility", "Marco. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Marco.Pms.Services", "Marco.Pms.Services\Marco.Pms.Services.csproj", "{27A83653-5B7F-4135-9886-01594D54AFAE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Marco.Pms.CacheHelper", "Marco.Pms.CacheHelper\Marco.Pms.CacheHelper.csproj", "{1A105C22-4ED7-4F54-8834-6923DDD96852}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {27A83653-5B7F-4135-9886-01594D54AFAE}.Debug|Any CPU.Build.0 = Debug|Any CPU {27A83653-5B7F-4135-9886-01594D54AFAE}.Release|Any CPU.ActiveCfg = Release|Any CPU {27A83653-5B7F-4135-9886-01594D54AFAE}.Release|Any CPU.Build.0 = Release|Any CPU + {1A105C22-4ED7-4F54-8834-6923DDD96852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A105C22-4ED7-4F54-8834-6923DDD96852}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A105C22-4ED7-4F54-8834-6923DDD96852}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A105C22-4ED7-4F54-8834-6923DDD96852}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 7be7d6f13dedfa3c1cec127070a9e09c40eac60e Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 4 Jul 2025 17:50:27 +0530 Subject: [PATCH 3/6] removed comented code from appsetting file --- Marco.Pms.Services/appsettings.Development.json | 1 - 1 file changed, 1 deletion(-) diff --git a/Marco.Pms.Services/appsettings.Development.json b/Marco.Pms.Services/appsettings.Development.json index ce80dc0..5f5e19d 100644 --- a/Marco.Pms.Services/appsettings.Development.json +++ b/Marco.Pms.Services/appsettings.Development.json @@ -49,6 +49,5 @@ "MongoDB": { "SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs", "ConnectionString": "mongodb://localhost:27017/MarcoBMS_Caches" - //"DatabaseName": "" } } From 00526922831dea6d032b4d542fd81214c7352691 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Sat, 5 Jul 2025 15:25:01 +0530 Subject: [PATCH 4/6] Added error handling in cache helper --- .../Helpers/CacheUpdateHelper.cs | 170 +++++++++++++++--- 1 file changed, 143 insertions(+), 27 deletions(-) diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 1c3ee70..75b51b5 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -1,6 +1,7 @@ using Marco.Pms.CacheHelper; using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.Projects; +using MarcoBMS.Services.Service; using Project = Marco.Pms.Model.Projects.Project; namespace Marco.Pms.Services.Helpers @@ -9,90 +10,205 @@ namespace Marco.Pms.Services.Helpers { private readonly ProjectCache _projectCache; private readonly EmployeeCache _employeeCache; + private readonly ILoggingService _logger; - public CacheUpdateHelper(ProjectCache projectCache, EmployeeCache employeeCache) + public CacheUpdateHelper(ProjectCache projectCache, EmployeeCache employeeCache, ILoggingService logger) { _projectCache = projectCache; _employeeCache = employeeCache; + _logger = logger; } // ------------------------------------ Project Details and Infrastructure Cache --------------------------------------- public async Task AddProjectDetails(Project project) { - await _projectCache.AddProjectDetailsToCache(project); + try + { + await _projectCache.AddProjectDetailsToCache(project); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while adding project to Cache: {Error}", ex.Message); + } } public async Task UpdateProjectDetailsOnly(Project project) { - bool response = await _projectCache.UpdateProjectDetailsOnlyToCache(project); - return response; + try + { + bool response = await _projectCache.UpdateProjectDetailsOnlyToCache(project); + return response; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while updating project to Cache: {Error}", ex.Message); + return false; + } } public async Task GetProjectDetails(Guid projectId) { - var response = await _projectCache.GetProjectDetailsFromCache(projectId); - return response; + try + { + var response = await _projectCache.GetProjectDetailsFromCache(projectId); + return response; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while getting project to Cache: {Error}", ex.Message); + return null; + } } + //public async Task?> GetProjectDetailsList(List projectIds) + //{ + // var response = await _projectCache.GetProjectDetailsListFromCache(projectIds); + // return response; + //} public async Task AddBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null) { - await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId); + try + { + await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while adding project infra to Cache: {Error}", ex.Message); + } } public async Task UpdateBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null) { - var response = await _projectCache.UpdateBuildngInfraToCache(projectId, building, floor, workArea, buildingId); - if (!response) + try { - await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId); + var response = await _projectCache.UpdateBuildngInfraToCache(projectId, building, floor, workArea, buildingId); + if (!response) + { + await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId); + } + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while updating project infra to Cache: {Error}", ex.Message); } } public async Task?> GetBuildingInfra(Guid projectId) { - var response = await _projectCache.GetBuildingInfraFromCache(projectId); - return response; + try + { + var response = await _projectCache.GetBuildingInfraFromCache(projectId); + return response; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while getting project infra Cache: {Error}", ex.Message); + return null; + } } // ------------------------------------ Employee Profile Cache --------------------------------------- public async Task AddApplicationRole(Guid employeeId, List roleIds) { - var response = await _employeeCache.AddApplicationRoleToCache(employeeId, roleIds); + try + { + var response = await _employeeCache.AddApplicationRoleToCache(employeeId, roleIds); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while adding Application roleIds to Cache to employee {Employee}: {Error}", employeeId, ex.Message); + } } public async Task AddProjects(Guid employeeId, List projectIds) { - var response = await _employeeCache.AddProjectsToCache(employeeId, projectIds); - return response; + try + { + var response = await _employeeCache.AddProjectsToCache(employeeId, projectIds); + return response; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while adding projectIds to Cache: {Error}", ex.Message); + return false; + } } public async Task?> GetProjects(Guid employeeId) { - var response = await _employeeCache.GetProjectsFromCache(employeeId); - if (response.Count > 0) + try { - return response; + var response = await _employeeCache.GetProjectsFromCache(employeeId); + if (response.Count > 0) + { + return response; + } + return null; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while getting projectIDs to Cache: {Error}", ex.Message); + return null; } - return null; } public async Task?> GetPermissions(Guid employeeId) { - var response = await _employeeCache.GetPermissionsFromCache(employeeId); - if (response.Count > 0) + try { - return response; + var response = await _employeeCache.GetPermissionsFromCache(employeeId); + if (response.Count > 0) + { + return response; + } + return null; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while getting permissionIds to Cache: {Error}", ex.Message); + return null; } - return null; } public async Task ClearAllProjectIds(Guid employeeId) { - var response = await _employeeCache.ClearAllProjectIdsFromCache(employeeId); + try + { + var response = await _employeeCache.ClearAllProjectIdsFromCache(employeeId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting projectIds from Cache for employee {EmployeeId}: {Error}", employeeId, ex.Message); + } } + //public async Task ClearAllProjectIdsByRoleId(Guid roleId) + //{ + // await _employeeCache.ClearAllProjectIdsByRoleIdFromCache(roleId); + //} public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId) { - var response = await _employeeCache.ClearAllPermissionIdsByEmployeeIDFromCache(employeeId); + try + { + var response = await _employeeCache.ClearAllPermissionIdsByEmployeeIDFromCache(employeeId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting permissionIds from to Cache: {Error}", ex.Message); + } } public async Task ClearAllPermissionIdsByRoleId(Guid roleId) { - var response = await _employeeCache.ClearAllPermissionIdsByRoleIdFromCache(roleId); + try + { + var response = await _employeeCache.ClearAllPermissionIdsByRoleIdFromCache(roleId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting permissionIds from to Cache: {Error}", ex.Message); + } } public async Task RemoveRoleId(Guid employeeId, Guid roleId) { - var response = await _employeeCache.RemoveRoleIdFromCache(employeeId, roleId); + try + { + var response = await _employeeCache.RemoveRoleIdFromCache(employeeId, roleId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting Application roleIds from to Cache: {Error}", ex.Message); + } } } } From b8cba9f378de1610de912183552dcd75922f200d Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 10:04:11 +0530 Subject: [PATCH 5/6] Implemented the methods for deleting permission am asigned project from caches for certien employee --- Marco.Pms.CacheHelper/EmployeeCache.cs | 60 ++++++++++++++----- Marco.Pms.CacheHelper/ProjectCache.cs | 55 ++++++++++------- .../EmployeePermissionMongoDB.cs | 2 +- .../MongoDBModels/ProjectMongoDB.cs | 2 +- .../MongoDBModels/WorkItemMongoDB.cs | 9 +-- .../Controllers/RolesController.cs | 4 ++ .../Helpers/CacheUpdateHelper.cs | 57 +++++++++++------- Marco.Pms.Services/Helpers/ProjectsHelper.cs | 21 ++++++- 8 files changed, 144 insertions(+), 66 deletions(-) diff --git a/Marco.Pms.CacheHelper/EmployeeCache.cs b/Marco.Pms.CacheHelper/EmployeeCache.cs index 7d75407..5c86e6f 100644 --- a/Marco.Pms.CacheHelper/EmployeeCache.cs +++ b/Marco.Pms.CacheHelper/EmployeeCache.cs @@ -22,31 +22,47 @@ namespace Marco.Pms.CacheHelper } public async Task AddApplicationRoleToCache(Guid employeeId, List roleIds) { - var newRoleIds = roleIds.Select(r => r.ToString()).ToList(); - var newPermissionIds = await _context.RolePermissionMappings + // 1. Guard Clause: Avoid unnecessary database work if there are no roles to add. + if (roleIds == null || !roleIds.Any()) + { + return false; // Nothing to add, so the operation did not result in a change. + } + + // 2. Perform database queries concurrently for better performance. + var employeeIdString = employeeId.ToString(); + + Task> getPermissionIdsTask = _context.RolePermissionMappings .Where(rp => roleIds.Contains(rp.ApplicationRoleId)) .Select(p => p.FeaturePermissionId.ToString()) .Distinct() .ToListAsync(); - var filter = Builders.Filter.Eq(e => e.EmployeeId, employeeId.ToString()); + // 3. Prepare role IDs in parallel with the database query. + var newRoleIds = roleIds.Select(r => r.ToString()).ToList(); + + // 4. Await the database query result. + var newPermissionIds = await getPermissionIdsTask; + + // 5. Build a single, efficient update operation. + var filter = Builders.Filter.Eq(e => e.Id, employeeIdString); var update = Builders.Update .AddToSetEach(e => e.ApplicationRoleIds, newRoleIds) .AddToSetEach(e => e.PermissionIds, newPermissionIds); - var result = await _collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); - if (result.MatchedCount == 0) - { - return false; - } - return true; + var options = new UpdateOptions { IsUpsert = true }; + + var result = await _collection.UpdateOneAsync(filter, update, options); + + // 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); } public async Task AddProjectsToCache(Guid employeeId, List projectIds) { var newprojectIds = projectIds.Select(p => p.ToString()).ToList(); - var filter = Builders.Filter.Eq(e => e.EmployeeId, employeeId.ToString()); + var filter = Builders.Filter.Eq(e => e.Id, employeeId.ToString()); var update = Builders.Update .AddToSetEach(e => e.ProjectIds, newprojectIds); @@ -60,7 +76,7 @@ namespace Marco.Pms.CacheHelper } public async Task> GetProjectsFromCache(Guid employeeId) { - var filter = Builders.Filter.Eq(e => e.EmployeeId, employeeId.ToString()); + var filter = Builders.Filter.Eq(e => e.Id, employeeId.ToString()); var result = await _collection @@ -77,7 +93,7 @@ namespace Marco.Pms.CacheHelper } public async Task> GetPermissionsFromCache(Guid employeeId) { - var filter = Builders.Filter.Eq(e => e.EmployeeId, employeeId.ToString()); + var filter = Builders.Filter.Eq(e => e.Id, employeeId.ToString()); var result = await _collection @@ -95,7 +111,21 @@ namespace Marco.Pms.CacheHelper public async Task ClearAllProjectIdsFromCache(Guid employeeId) { var filter = Builders.Filter - .Eq(e => e.EmployeeId, employeeId.ToString()); + .Eq(e => e.Id, employeeId.ToString()); + + var update = Builders.Update + .Set(e => e.ProjectIds, new List()); + + var result = await _collection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + return false; + + return true; + } + public async Task ClearAllProjectIdsByRoleIdFromCache(Guid roleId) + { + var filter = Builders.Filter.AnyEq(e => e.ApplicationRoleIds, roleId.ToString()); var update = Builders.Update .Set(e => e.ProjectIds, new List()); @@ -110,7 +140,7 @@ namespace Marco.Pms.CacheHelper public async Task RemoveRoleIdFromCache(Guid employeeId, Guid roleId) { var filter = Builders.Filter - .Eq(e => e.EmployeeId, employeeId.ToString()); + .Eq(e => e.Id, employeeId.ToString()); var update = Builders.Update .Pull(e => e.ApplicationRoleIds, roleId.ToString()); @@ -128,7 +158,7 @@ namespace Marco.Pms.CacheHelper public async Task ClearAllPermissionIdsByEmployeeIDFromCache(Guid employeeId) { var filter = Builders.Filter - .Eq(e => e.EmployeeId, employeeId.ToString()); + .Eq(e => e.Id, employeeId.ToString()); var update = Builders.Update .Set(e => e.PermissionIds, new List()); diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index b667694..f60884f 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -11,19 +11,21 @@ namespace Marco.Pms.CacheHelper public class ProjectCache { private readonly ApplicationDbContext _context; - private readonly IMongoDatabase _mongoDB; - //private readonly ILoggingService _logger; + private readonly IMongoCollection _projetCollection; + 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 - _mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name + var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name + _projetCollection = mongoDB.GetCollection("ProjectDetails"); + _taskCollection = mongoDB.GetCollection("WorkItemDetails"); } public async Task AddProjectDetailsToCache(Project project) { - var projectCollection = _mongoDB.GetCollection("ProjectDetails"); + //_logger.LogInfo("[AddProjectDetails] Initiated for ProjectId: {ProjectId}", project.Id); @@ -145,7 +147,7 @@ namespace Marco.Pms.CacheHelper projectDetails.PlannedWork = totalPlannedWork; projectDetails.CompletedWork = totalCompletedWork; - await projectCollection.InsertOneAsync(projectDetails); + await _projetCollection.InsertOneAsync(projectDetails); //_logger.LogInfo("[AddProjectDetails] Project details inserted in MongoDB for ProjectId: {ProjectId}", project.Id); } public async Task UpdateProjectDetailsOnlyToCache(Project project) @@ -160,8 +162,6 @@ namespace Marco.Pms.CacheHelper //_logger.LogWarning("StatusMaster not found for ProjectStatusId: {StatusId}", project.ProjectStatusId); } - var projectCollection = _mongoDB.GetCollection("ProjectDetails"); - // Build the update definition var updates = Builders.Update.Combine( Builders.Update.Set(r => r.Name, project.Name), @@ -178,7 +178,7 @@ namespace Marco.Pms.CacheHelper ); // Perform the update - var result = await projectCollection.UpdateOneAsync( + var result = await _projetCollection.UpdateOneAsync( filter: r => r.Id == project.Id.ToString(), update: updates ); @@ -194,7 +194,6 @@ namespace Marco.Pms.CacheHelper } public async Task GetProjectDetailsFromCache(Guid projectId) { - var projectCollection = _mongoDB.GetCollection("ProjectDetails"); // Build filter and projection to exclude large 'Buildings' list var filter = Builders.Filter.Eq(p => p.Id, projectId.ToString()); @@ -203,7 +202,7 @@ namespace Marco.Pms.CacheHelper //_logger.LogInfo("Fetching project details for ProjectId: {ProjectId} from MongoDB", projectId); // Perform query - var project = await projectCollection + var project = await _projetCollection .Find(filter) .Project(projection) .FirstOrDefaultAsync(); @@ -214,16 +213,23 @@ namespace Marco.Pms.CacheHelper return null; } - //// Deserialize the result manually - //var project = BsonSerializer.Deserialize(result); - //_logger.LogInfo("Successfully fetched project details (excluding Buildings) for ProjectId: {ProjectId}", projectId); return project; } + public async Task?> GetProjectDetailsListFromCache(List projectIds) + { + 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 + .Find(filter) + .Project(projection) + .ToListAsync(); + return projects; + } public async Task AddBuildngInfraToCache(Guid projectId, Building? building, Floor? floor, WorkArea? workArea, Guid? buildingId) { var stringProjectId = projectId.ToString(); - var projectCollection = _mongoDB.GetCollection("ProjectDetails"); // Add Building if (building != null) @@ -241,7 +247,7 @@ namespace Marco.Pms.CacheHelper var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); var update = Builders.Update.Push("Buildings", buildingMongo); - var result = await projectCollection.UpdateOneAsync(filter, update); + var result = await _projetCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { @@ -271,7 +277,7 @@ namespace Marco.Pms.CacheHelper ); var update = Builders.Update.Push("Buildings.$.Floors", floorMongo); - var result = await projectCollection.UpdateOneAsync(filter, update); + var result = await _projetCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { @@ -305,7 +311,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 projectCollection.UpdateOneAsync(filter, update, updateOptions); + var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { @@ -323,7 +329,6 @@ namespace Marco.Pms.CacheHelper public async Task UpdateBuildngInfraToCache(Guid projectId, Building? building, Floor? floor, WorkArea? workArea, Guid? buildingId) { var stringProjectId = projectId.ToString(); - var projectCollection = _mongoDB.GetCollection("ProjectDetails"); // Update Building if (building != null) @@ -338,7 +343,7 @@ namespace Marco.Pms.CacheHelper Builders.Update.Set("Buildings.$.Description", building.Description) ); - var result = await projectCollection.UpdateOneAsync(filter, update); + var result = await _projetCollection.UpdateOneAsync(filter, update); if (result.MatchedCount == 0) { @@ -363,7 +368,7 @@ namespace Marco.Pms.CacheHelper var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); - var result = await projectCollection.UpdateOneAsync(filter, update, updateOptions); + var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { @@ -389,7 +394,7 @@ namespace Marco.Pms.CacheHelper var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var filter = Builders.Filter.Eq(p => p.Id, stringProjectId); - var result = await projectCollection.UpdateOneAsync(filter, update, updateOptions); + var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); if (result.MatchedCount == 0) { @@ -408,13 +413,12 @@ namespace Marco.Pms.CacheHelper } public async Task?> GetBuildingInfraFromCache(Guid projectId) { - var projectCollection = _mongoDB.GetCollection("ProjectDetails"); // Filter by project ID var filter = Builders.Filter.Eq(p => p.Id, projectId.ToString()); // Project only the "Buildings" field from the document - var buildings = await projectCollection + var buildings = await _projetCollection .Find(filter) .Project(p => p.Buildings) .FirstOrDefaultAsync(); @@ -430,5 +434,10 @@ namespace Marco.Pms.CacheHelper return buildings; } + + + // ------------------------------------------------------- WorkItem ------------------------------------------------------- + + } } diff --git a/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs b/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs index f141798..49c514e 100644 --- a/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/EmployeePermissionMongoDB.cs @@ -5,7 +5,7 @@ namespace Marco.Pms.Model.MongoDBModels [BsonIgnoreExtraElements] public class EmployeePermissionMongoDB { - public string EmployeeId { get; set; } = string.Empty; + public string Id { get; set; } = string.Empty; // Employee ID public List ApplicationRoleIds { get; set; } = new List(); public List PermissionIds { get; set; } = new List(); public List ProjectIds { get; set; } = new List(); diff --git a/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs b/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs index 8bf1c9a..8b1612c 100644 --- a/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs @@ -2,7 +2,7 @@ { public class ProjectMongoDB { - public string? Id { get; set; } + public string Id { get; set; } = string.Empty; public string? Name { get; set; } public string? ShortName { get; set; } public string? ProjectAddress { get; set; } diff --git a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs index dc7fdb9..71638a3 100644 --- a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs @@ -2,13 +2,14 @@ { public class WorkItemMongoDB { - public string? Id { get; set; } - public string? WorkAreaId { get; set; } + public string Id { get; set; } = string.Empty; + public string WorkAreaId { get; set; } = string.Empty; public ActivityMasterMongoDB? ActivityMaster { get; set; } public WorkCategoryMasterMongoDB? WorkCategoryMaster { get; set; } public string? ParentTaskId { get; set; } - public double PlannedWork { get; set; } - public double CompletedWork { get; set; } + public double PlannedWork { get; set; } = 0; + public double TodaysAssigned { get; set; } = 0; + public double CompletedWork { get; set; } = 0; public string? Description { get; set; } public DateTime TaskDate { get; set; } } diff --git a/Marco.Pms.Services/Controllers/RolesController.cs b/Marco.Pms.Services/Controllers/RolesController.cs index 4c75b3e..a67ecaf 100644 --- a/Marco.Pms.Services/Controllers/RolesController.cs +++ b/Marco.Pms.Services/Controllers/RolesController.cs @@ -292,6 +292,10 @@ namespace MarcoBMS.Services.Controllers _context.RolePermissionMappings.Add(item); modified = true; } + if (item.FeaturePermissionId == Guid.Parse("172fc9b6-755b-4f62-ab26-55c34a330614")) + { + await _cache.ClearAllProjectIdsByRoleId(id); + } } if (modified) await _context.SaveChangesAsync(); diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 75b51b5..6ff9cfe 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -28,7 +28,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while adding project to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while adding project {ProjectId} to Cache : {Error}", project.Id, ex.Message); } } public async Task UpdateProjectDetailsOnly(Project project) @@ -40,7 +40,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while updating project to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while updating project {ProjectId} to Cache: {Error}", project.Id, ex.Message); return false; } } @@ -53,15 +53,23 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while getting project to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while getting project {ProjectId} to Cache: {Error}", ex.Message); + return null; + } + } + public async Task?> GetProjectDetailsList(List projectIds) + { + try + { + var response = await _projectCache.GetProjectDetailsListFromCache(projectIds); + return response; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while getting list od project details from to Cache: {Error}", ex.Message); return null; } } - //public async Task?> GetProjectDetailsList(List projectIds) - //{ - // var response = await _projectCache.GetProjectDetailsListFromCache(projectIds); - // return response; - //} public async Task AddBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null) { try @@ -70,7 +78,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while adding project infra to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while adding project infra for project {ProjectId} to Cache: {Error}", projectId, ex.Message); } } public async Task UpdateBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null) @@ -85,7 +93,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while updating project infra to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while updating project infra for project {ProjectId} to Cache: {Error}", projectId, ex.Message); } } public async Task?> GetBuildingInfra(Guid projectId) @@ -97,7 +105,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while getting project infra Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while getting project infra for project {ProjectId} form Cache: {Error}", projectId, ex.Message); return null; } } @@ -124,7 +132,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while adding projectIds to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while adding projectIds for employee {EmployeeId} to Cache: {Error}", employeeId, ex.Message); return false; } } @@ -141,7 +149,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while getting projectIDs to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while getting projectIds for employee {EmployeeId} from Cache: {Error}", employeeId, ex.Message); return null; } } @@ -158,7 +166,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while getting permissionIds to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while getting permissionIds for employee {EmployeeId} from Cache: {Error}", employeeId, ex.Message); return null; } } @@ -173,10 +181,17 @@ namespace Marco.Pms.Services.Helpers _logger.LogWarning("Error occured while deleting projectIds from Cache for employee {EmployeeId}: {Error}", employeeId, ex.Message); } } - //public async Task ClearAllProjectIdsByRoleId(Guid roleId) - //{ - // await _employeeCache.ClearAllProjectIdsByRoleIdFromCache(roleId); - //} + public async Task ClearAllProjectIdsByRoleId(Guid roleId) + { + try + { + await _employeeCache.ClearAllProjectIdsByRoleIdFromCache(roleId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting projectIds from Cache for Application Role {RoleId}: {Error}", roleId, ex.Message); + } + } public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId) { try @@ -185,7 +200,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while deleting permissionIds from to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while deleting permissionIds from Cache for employee {EmployeeId}: {Error}", employeeId, ex.Message); } } public async Task ClearAllPermissionIdsByRoleId(Guid roleId) @@ -196,7 +211,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while deleting permissionIds from to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while deleting permissionIds from Cache for Application role {RoleId}: {Error}", roleId, ex.Message); } } public async Task RemoveRoleId(Guid employeeId, Guid roleId) @@ -207,7 +222,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occured while deleting Application roleIds from to Cache: {Error}", ex.Message); + _logger.LogWarning("Error occured while deleting Application role {RoleId} from Cache for employee {EmployeeId}: {Error}", roleId, employeeId, ex.Message); } } } diff --git a/Marco.Pms.Services/Helpers/ProjectsHelper.cs b/Marco.Pms.Services/Helpers/ProjectsHelper.cs index 3ccddba..85003ae 100644 --- a/Marco.Pms.Services/Helpers/ProjectsHelper.cs +++ b/Marco.Pms.Services/Helpers/ProjectsHelper.cs @@ -1,6 +1,7 @@ using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; +using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.Projects; using Marco.Pms.Services.Helpers; using Microsoft.EntityFrameworkCore; @@ -59,7 +60,25 @@ namespace MarcoBMS.Services.Helpers if (projectIds != null) { - projects = await _context.Projects.Where(p => projectIds.Contains(p.Id)).ToListAsync(); + + List projectdetails = await _cache.GetProjectDetailsList(projectIds) ?? new List(); + projects = projectdetails.Select(p => new Project + { + Id = Guid.Parse(p.Id), + Name = p.Name, + ShortName = p.ShortName, + ProjectAddress = p.ProjectAddress, + ProjectStatusId = Guid.Parse(p.ProjectStatus?.Id ?? ""), + ContactPerson = p.ContactPerson, + StartDate = p.StartDate, + EndDate = p.EndDate, + TenantId = tenantId + }).ToList(); + + if (projects.Count != projectIds.Count) + { + projects = await _context.Projects.Where(p => projectIds.Contains(p.Id)).ToListAsync(); + } } else { From ec1e2dc59f724940701e12e52afd2cb5882479ac Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 17:44:58 +0530 Subject: [PATCH 6/6] Storing workItem in cache and changing planned work and completed work for respective project, building, floor, and workarea --- Marco.Pms.CacheHelper/ProjectCache.cs | 120 ++++++++++++++++++ .../MongoDBModels/ActivityMasterMongoDB.cs | 2 +- .../MongoDBModels/BuildingMongoDB.cs | 2 +- Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs | 2 +- .../MongoDBModels/ProjectMongoDB.cs | 2 +- .../WorkCategoryMasterMongoDB.cs | 2 +- .../MongoDBModels/WorkItemMongoDB.cs | 2 +- .../Controllers/ProjectController.cs | 73 +++++++++-- .../Helpers/CacheUpdateHelper.cs | 65 ++++++++++ .../appsettings.Development.json | 2 +- 10 files changed, 256 insertions(+), 16 deletions(-) diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index f60884f..6f5a3d3 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -1,4 +1,5 @@ using Marco.Pms.DataAccess.Data; +using Marco.Pms.Model.Master; using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.Projects; using Microsoft.EntityFrameworkCore; @@ -434,10 +435,129 @@ namespace Marco.Pms.CacheHelper return buildings; } + 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(); + + string? selectedBuildingId = null; + string? selectedFloorId = null; + string? selectedWorkAreaId = null; + + foreach (var building in project.Buildings) + { + foreach (var floor in building.Floors) + { + foreach (var area in floor.WorkAreas) + { + if (area.Id == workAreaId.ToString()) + { + selectedWorkAreaId = area.Id; + selectedFloorId = floor.Id; + selectedBuildingId = building.Id; + } + } + } + } + + var arrayFilters = new List + { + new JsonArrayFilterDefinition("{ 'b._id': '" + selectedBuildingId + "' }"), + new JsonArrayFilterDefinition("{ 'f._id': '" + selectedFloorId + "' }"), + new JsonArrayFilterDefinition("{ 'a._id': '" + selectedWorkAreaId + "' }") + }; + var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; + var update = Builders.Update + .Inc("Buildings.$[b].Floors.$[f].WorkAreas.$[a].PlannedWork", plannedWork) + .Inc("Buildings.$[b].Floors.$[f].WorkAreas.$[a].CompletedWork", completedWork) + .Inc("Buildings.$[b].Floors.$[f].PlannedWork", plannedWork) + .Inc("Buildings.$[b].Floors.$[f].CompletedWork", completedWork) + .Inc("Buildings.$[b].PlannedWork", plannedWork) + .Inc("Buildings.$[b].CompletedWork", completedWork) + .Inc("PlannedWork", plannedWork) + .Inc("CompletedWork", completedWork); + var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); + + } // ------------------------------------------------------- WorkItem ------------------------------------------------------- + public async Task ManageWorkItemDetailsToCache(List workItems) + { + var activityIds = workItems.Select(wi => wi.ActivityId).ToList(); + var workCategoryIds = workItems.Select(wi => wi.WorkCategoryId).ToList(); + // fetching Activity master + var activities = await _context.ActivityMasters.Where(a => activityIds.Contains(a.Id)).ToListAsync() ?? new List(); + // Fetching Work Category + var workCategories = await _context.WorkCategoryMasters.Where(wc => workCategoryIds.Contains(wc.Id)).ToListAsync() ?? new List(); + + foreach (WorkItem workItem in workItems) + { + var activity = activities.FirstOrDefault(a => a.Id == workItem.ActivityId) ?? new ActivityMaster(); + var workCategory = workCategories.FirstOrDefault(a => a.Id == workItem.WorkCategoryId) ?? new WorkCategoryMaster(); + + var filter = Builders.Filter.Eq(p => p.Id, workItem.Id.ToString()); + var updates = Builders.Update.Combine( + Builders.Update.Set(r => r.WorkAreaId, workItem.WorkAreaId.ToString()), + Builders.Update.Set(r => r.ParentTaskId, (workItem.ParentTaskId != null ? workItem.ParentTaskId.ToString() : null)), + Builders.Update.Set(r => r.PlannedWork, workItem.PlannedWork), + Builders.Update.Set(r => r.TodaysAssigned, 0), + Builders.Update.Set(r => r.CompletedWork, workItem.CompletedWork), + Builders.Update.Set(r => r.Description, workItem.Description), + Builders.Update.Set(r => r.TaskDate, workItem.TaskDate), + Builders.Update.Set(r => r.ActivityMaster, new ActivityMasterMongoDB + { + Id = activity.Id.ToString(), + ActivityName = activity.ActivityName, + UnitOfMeasurement = activity.UnitOfMeasurement + }), + Builders.Update.Set(r => r.WorkCategoryMaster, new WorkCategoryMasterMongoDB + { + Id = workCategory.Id.ToString(), + Name = workCategory.Name, + Description = workCategory.Description, + }) + ); + var options = new UpdateOptions { IsUpsert = true }; + var result = await _taskCollection.UpdateOneAsync(filter, updates, options); + } + } + public async Task> GetWorkItemDetailsByWorkAreaFromCache(Guid workAreaId) + { + var filter = Builders.Filter.Eq(p => p.WorkAreaId, workAreaId.ToString()); + + var options = new UpdateOptions { IsUpsert = true }; + var workItems = await _taskCollection + .Find(filter) + .ToListAsync(); + return workItems; + } + public async Task GetWorkItemDetailsByIdFromCache(Guid id) + { + var filter = Builders.Filter.Eq(p => p.Id, id.ToString()); + + var options = new UpdateOptions { IsUpsert = true }; + var workItem = await _taskCollection + .Find(filter) + .FirstOrDefaultAsync(); + return workItem; + } + public async Task UpdatePlannedAndCompleteWorksInWorkItem(Guid id, double plannedWork = 0, double completedWork = 0, double todaysAssigned = 0) + { + var filter = Builders.Filter.Eq(p => p.Id, id.ToString()); + var updates = Builders.Update + .Inc("PlannedWork", plannedWork) + .Inc("CompletedWork", completedWork) + .Inc("TodaysAssigned", todaysAssigned); + + var result = await _taskCollection.UpdateOneAsync(filter, updates); + if (result.ModifiedCount > 0) + { + return true; + } + return false; + } } } diff --git a/Marco.Pms.Model/MongoDBModels/ActivityMasterMongoDB.cs b/Marco.Pms.Model/MongoDBModels/ActivityMasterMongoDB.cs index 37218b7..cc77d96 100644 --- a/Marco.Pms.Model/MongoDBModels/ActivityMasterMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/ActivityMasterMongoDB.cs @@ -2,7 +2,7 @@ { public class ActivityMasterMongoDB { - public string? Id { get; set; } + public string Id { get; set; } = string.Empty; public string? ActivityName { get; set; } public string? UnitOfMeasurement { get; set; } } diff --git a/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs b/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs index 87ccb8d..64ccbce 100644 --- a/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs @@ -7,7 +7,7 @@ public string? Description { get; set; } public double PlannedWork { get; set; } public double CompletedWork { get; set; } - public List? Floors { get; set; } + public List Floors { get; set; } = new List(); } public class BuildingMongoDBVM { diff --git a/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs b/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs index ae3975f..57257a4 100644 --- a/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs @@ -6,7 +6,7 @@ public string? FloorName { get; set; } public double PlannedWork { get; set; } public double CompletedWork { get; set; } - public List? WorkAreas { get; set; } + public List WorkAreas { get; set; } = new List(); } public class FloorMongoDBVM diff --git a/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs b/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs index 8b1612c..7f3a557 100644 --- a/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/ProjectMongoDB.cs @@ -7,7 +7,7 @@ public string? ShortName { get; set; } public string? ProjectAddress { get; set; } public string? ContactPerson { get; set; } - public List? Buildings { get; set; } + public List Buildings { get; set; } = new List(); public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } public StatusMasterMongoDB? ProjectStatus { get; set; } diff --git a/Marco.Pms.Model/MongoDBModels/WorkCategoryMasterMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkCategoryMasterMongoDB.cs index aef0ada..4ea4682 100644 --- a/Marco.Pms.Model/MongoDBModels/WorkCategoryMasterMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/WorkCategoryMasterMongoDB.cs @@ -2,7 +2,7 @@ { public class WorkCategoryMasterMongoDB { - public string? Id { get; set; } + public string Id { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; } diff --git a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs index 71638a3..850300d 100644 --- a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs @@ -6,7 +6,7 @@ public string WorkAreaId { get; set; } = string.Empty; public ActivityMasterMongoDB? ActivityMaster { get; set; } public WorkCategoryMasterMongoDB? WorkCategoryMaster { get; set; } - public string? ParentTaskId { get; set; } + public string? ParentTaskId { get; set; } = null; public double PlannedWork { get; set; } = 0; public double TodaysAssigned { get; set; } = 0; public double CompletedWork { get; set; } = 0; diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index a440c21..3ae76ed 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -734,16 +734,45 @@ namespace MarcoBMS.Services.Controllers } // Step 4: Fetch WorkItems with related Activity and Work Category data - var workItems = await _context.WorkItems - .Include(wi => wi.ActivityMaster) - .Include(wi => wi.WorkCategoryMaster) - .Where(wi => wi.WorkAreaId == workAreaId) - .ToListAsync(); + var workItemVMs = await _cache.GetWorkItemDetailsByWorkArea(workAreaId); + if (workItemVMs == null) + { + var workItems = await _context.WorkItems + .Include(wi => wi.ActivityMaster) + .Include(wi => wi.WorkCategoryMaster) + .Where(wi => wi.WorkAreaId == workAreaId) + .ToListAsync(); - _logger.LogInfo("{Count} work items fetched successfully for WorkAreaId: {WorkAreaId}", workItems.Count, workAreaId); + workItemVMs = workItems.Select(wi => new WorkItemMongoDB + { + Id = wi.Id.ToString(), + WorkAreaId = wi.WorkAreaId.ToString(), + ParentTaskId = wi.ParentTaskId.ToString(), + ActivityMaster = new ActivityMasterMongoDB + { + Id = wi.ActivityId.ToString(), + ActivityName = wi.ActivityMaster != null ? wi.ActivityMaster.ActivityName : null, + UnitOfMeasurement = wi.ActivityMaster != null ? wi.ActivityMaster.UnitOfMeasurement : null + }, + WorkCategoryMaster = new WorkCategoryMasterMongoDB + { + Id = wi.ActivityId.ToString(), + Name = wi.WorkCategoryMaster != null ? wi.WorkCategoryMaster.Name : "", + Description = wi.WorkCategoryMaster != null ? wi.WorkCategoryMaster.Description : "" + }, + PlannedWork = wi.PlannedWork, + CompletedWork = wi.CompletedWork, + Description = wi.Description, + TaskDate = wi.TaskDate, + }).ToList(); + + await _cache.ManageWorkItemDetails(workItems); + } + + _logger.LogInfo("{Count} work items fetched successfully for WorkAreaId: {WorkAreaId}", workItemVMs.Count, workAreaId); // Step 5: Return result - return Ok(ApiResponse.SuccessResponse(workItems, $"{workItems.Count} records of tasks fetched successfully", 200)); + return Ok(ApiResponse.SuccessResponse(workItemVMs, $"{workItemVMs.Count} records of tasks fetched successfully", 200)); } [HttpPost("task")] @@ -765,6 +794,8 @@ namespace MarcoBMS.Services.Controllers var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); string message = ""; List projectIds = new List(); + var workItemIds = workItemDtos.Where(wi => wi.Id != null && wi.Id != Guid.Empty).Select(wi => wi.Id).ToList(); + var workItems = await _context.WorkItems.AsNoTracking().Where(wi => workItemIds.Contains(wi.Id)).ToListAsync(); foreach (var itemDto in workItemDtos) { @@ -778,6 +809,28 @@ namespace MarcoBMS.Services.Controllers // Update existing workItemsToUpdate.Add(workItem); message = $"Task Updated in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}"; + var existingWorkItem = workItems.FirstOrDefault(wi => wi.Id == workItem.Id); + double plannedWork = 0; + double completedWork = 0; + if (existingWorkItem != null) + { + if (existingWorkItem.PlannedWork != workItem.PlannedWork && existingWorkItem.CompletedWork != workItem.CompletedWork) + { + plannedWork = workItem.PlannedWork - existingWorkItem.PlannedWork; + completedWork = workItem.CompletedWork - existingWorkItem.CompletedWork; + } + else if (existingWorkItem.PlannedWork == workItem.PlannedWork && existingWorkItem.CompletedWork != workItem.CompletedWork) + { + plannedWork = 0; + completedWork = workItem.CompletedWork - existingWorkItem.CompletedWork; + } + else if (existingWorkItem.PlannedWork != workItem.PlannedWork && existingWorkItem.CompletedWork == workItem.CompletedWork) + { + plannedWork = workItem.PlannedWork - existingWorkItem.PlannedWork; + completedWork = 0; + } + await _cache.UpdatePlannedAndCompleteWorksInBuilding(workArea.Id, plannedWork, completedWork); + } } else { @@ -785,6 +838,7 @@ namespace MarcoBMS.Services.Controllers workItem.Id = Guid.NewGuid(); workItemsToCreate.Add(workItem); message = $"Task Added in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}"; + await _cache.UpdatePlannedAndCompleteWorksInBuilding(workArea.Id, workItem.PlannedWork, workItem.CompletedWork); } responseList.Add(new WorkItemVM @@ -793,6 +847,7 @@ namespace MarcoBMS.Services.Controllers WorkItem = workItem }); projectIds.Add(building.ProjectId); + } string responseMessage = ""; // Apply DB changes @@ -801,7 +856,7 @@ namespace MarcoBMS.Services.Controllers _logger.LogInfo("Adding {Count} new work items", workItemsToCreate.Count); await _context.WorkItems.AddRangeAsync(workItemsToCreate); responseMessage = "Task Added Successfully"; - + await _cache.ManageWorkItemDetails(workItemsToCreate); } if (workItemsToUpdate.Any()) @@ -809,7 +864,7 @@ namespace MarcoBMS.Services.Controllers _logger.LogInfo("Updating {Count} existing work items", workItemsToUpdate.Count); _context.WorkItems.UpdateRange(workItemsToUpdate); responseMessage = "Task Updated Successfully"; - + await _cache.ManageWorkItemDetails(workItemsToUpdate); } await _context.SaveChangesAsync(); diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 6ff9cfe..ecce8ab 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -109,6 +109,71 @@ namespace Marco.Pms.Services.Helpers return null; } } + public async Task UpdatePlannedAndCompleteWorksInBuilding(Guid workAreaId, double plannedWork = 0, double completedWork = 0) + { + try + { + await _projectCache.UpdatePlannedAndCompleteWorksInBuildingFromCache(workAreaId, plannedWork, completedWork); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while updating planned work and completed work in building infra form Cache: {Error}", ex.Message); + } + } + + // ------------------------------------------------------- WorkItem ------------------------------------------------------- + + public async Task ManageWorkItemDetails(List workItems) + { + try + { + await _projectCache.ManageWorkItemDetailsToCache(workItems); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while saving workItems form Cache: {Error}", ex.Message); + } + } + public async Task?> GetWorkItemDetailsByWorkArea(Guid workAreaId) + { + try + { + var workItems = await _projectCache.GetWorkItemDetailsByWorkAreaFromCache(workAreaId); + if (workItems.Count > 0) + { + return workItems; + } + else + { + return null; + } + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while fetching list of workItems form Cache: {Error}", ex.Message); + return null; + } + } + public async Task GetWorkItemDetailsById(Guid id) + { + try + { + var workItem = await _projectCache.GetWorkItemDetailsByIdFromCache(id); + if (workItem.Id != "") + { + return workItem; + } + else + { + return null; + } + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while fetching list of workItems form Cache: {Error}", ex.Message); + return null; + } + } // ------------------------------------ Employee Profile Cache --------------------------------------- diff --git a/Marco.Pms.Services/appsettings.Development.json b/Marco.Pms.Services/appsettings.Development.json index 5f5e19d..030c450 100644 --- a/Marco.Pms.Services/appsettings.Development.json +++ b/Marco.Pms.Services/appsettings.Development.json @@ -48,6 +48,6 @@ }, "MongoDB": { "SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs", - "ConnectionString": "mongodb://localhost:27017/MarcoBMS_Caches" + "ConnectionString": "mongodb://localhost:27017/MarcoBMS_Caches?socketTimeoutMS=500&serverSelectionTimeoutMS=500&connectTimeoutMS=500" } }