From 2c445878d0d805c5cfc935e527373954cfcc9cc2 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 1 Jul 2025 12:39:07 +0530 Subject: [PATCH 01/42] 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 8055c04e4b85e54415cf10f5b5bfa5e62d5bae7a Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 1 Jul 2025 12:39:07 +0530 Subject: [PATCH 02/42] 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 03/42] 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 04/42] 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 05/42] 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 06/42] 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 07/42] 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" } } From c2354033b735c50237d3e0594d73308e9660e29f Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 4 Jul 2025 17:49:25 +0530 Subject: [PATCH 08/42] 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 8e67e801a302579b9062f725a0837defae84700b Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 4 Jul 2025 17:50:27 +0530 Subject: [PATCH 09/42] 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 0654bca655fed5b84e10fda64893c8bee1f7a06d Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Sat, 5 Jul 2025 15:25:01 +0530 Subject: [PATCH 10/42] 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 11b54debc6eaa55b302ab39578ca03dbf97db228 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 10:04:11 +0530 Subject: [PATCH 11/42] 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 80a197b408ffeb3f5c1a55c8416b8b06e728f40a Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 17:44:58 +0530 Subject: [PATCH 12/42] 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" } } From 67c8bee2c2624414ebf285d85786bc198501cbaa Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 8 Jul 2025 12:20:54 +0530 Subject: [PATCH 13/42] Implemented the cache in task allocation --- Marco.Pms.CacheHelper/ProjectCache.cs | 4 +--- Marco.Pms.Services/Controllers/ProjectController.cs | 4 ++-- Marco.Pms.Services/Controllers/TaskController.cs | 12 +++++++++++- Marco.Pms.Services/Helpers/CacheUpdateHelper.cs | 11 +++++++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index 6f5a3d3..23df64c 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -26,8 +26,6 @@ namespace Marco.Pms.CacheHelper } public async Task AddProjectDetailsToCache(Project project) { - - //_logger.LogInfo("[AddProjectDetails] Initiated for ProjectId: {ProjectId}", project.Id); var projectDetails = new ProjectMongoDB @@ -544,7 +542,7 @@ namespace Marco.Pms.CacheHelper .FirstOrDefaultAsync(); return workItem; } - public async Task UpdatePlannedAndCompleteWorksInWorkItem(Guid id, double plannedWork = 0, double completedWork = 0, double todaysAssigned = 0) + public async Task UpdatePlannedAndCompleteWorksInWorkItemToCache(Guid id, double plannedWork, double completedWork, double todaysAssigned) { var filter = Builders.Filter.Eq(p => p.Id, id.ToString()); var updates = Builders.Update diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 3ae76ed..e12d2ad 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -29,7 +29,7 @@ namespace MarcoBMS.Services.Controllers private readonly ApplicationDbContext _context; private readonly UserHelper _userHelper; private readonly ILoggingService _logger; - private readonly RolesHelper _rolesHelper; + //private readonly RolesHelper _rolesHelper; private readonly ProjectsHelper _projectsHelper; private readonly IHubContext _signalR; private readonly PermissionServices _permission; @@ -47,7 +47,7 @@ namespace MarcoBMS.Services.Controllers _context = context; _userHelper = userHelper; _logger = logger; - _rolesHelper = rolesHelper; + //_rolesHelper = rolesHelper; _projectsHelper = projectHelper; _signalR = signalR; _cache = cache; diff --git a/Marco.Pms.Services/Controllers/TaskController.cs b/Marco.Pms.Services/Controllers/TaskController.cs index 6b55c3f..4a89e19 100644 --- a/Marco.Pms.Services/Controllers/TaskController.cs +++ b/Marco.Pms.Services/Controllers/TaskController.cs @@ -6,6 +6,7 @@ using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Activities; +using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; @@ -28,16 +29,18 @@ namespace MarcoBMS.Services.Controllers private readonly S3UploadService _s3Service; private readonly ILoggingService _logger; private readonly PermissionServices _permissionServices; + private readonly CacheUpdateHelper _cache; private readonly Guid Approve_Task; private readonly Guid Assign_Report_Task; - public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices) + public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices, CacheUpdateHelper cache) { _context = context; _userHelper = userHelper; _s3Service = s3Service; _logger = logger; _permissionServices = permissionServices; + _cache = cache; Approve_Task = Guid.Parse("db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c"); Assign_Report_Task = Guid.Parse("6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"); } @@ -81,6 +84,8 @@ namespace MarcoBMS.Services.Controllers _context.TaskAllocations.Add(taskAllocation); await _context.SaveChangesAsync(); + await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, todaysAssigned: taskAllocation.PlannedTask); + _logger.LogInfo("Task {TaskId} assigned by Employee {EmployeeId}", taskAllocation.Id, employee.Id); var response = taskAllocation.ToAssignTaskVMFromTaskAllocation(); @@ -245,6 +250,10 @@ namespace MarcoBMS.Services.Controllers } await _context.SaveChangesAsync(); + var selectedWorkAreaId = taskAllocation.WorkItem?.WorkAreaId ?? Guid.Empty; + + await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, completedWork: taskAllocation.CompletedTask); + await _cache.UpdatePlannedAndCompleteWorksInBuilding(selectedWorkAreaId, completedWork: taskAllocation.CompletedTask); var response = taskAllocation.ToReportTaskVMFromTaskAllocation(); var comments = await _context.TaskComments @@ -653,6 +662,7 @@ namespace MarcoBMS.Services.Controllers /// /// DTO containing task approval details. /// IActionResult indicating success or failure. + [HttpPost("approve")] public async Task ApproveTask(ApproveTaskDto approveTask) { diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index ecce8ab..03fd397 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -174,6 +174,17 @@ namespace Marco.Pms.Services.Helpers return null; } } + public async Task UpdatePlannedAndCompleteWorksInWorkItem(Guid id, double plannedWork = 0, double completedWork = 0, double todaysAssigned = 0) + { + try + { + var response = await _projectCache.UpdatePlannedAndCompleteWorksInWorkItemToCache(id, plannedWork, completedWork, todaysAssigned); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while updating planned work, completed work, and today's assigned work in workItems in Cache: {Error}", ex.Message); + } + } // ------------------------------------ Employee Profile Cache --------------------------------------- From 3dd5e7f626bcad948e868ac35e3e546435e61275 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 1 Jul 2025 12:39:07 +0530 Subject: [PATCH 14/42] 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 3ce9851a7f3914722d7b26863464a87bf9e6c98e Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 4 Jul 2025 17:49:25 +0530 Subject: [PATCH 15/42] 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 8c85d92ba6a8845e6ffabaef44d02bd166417e13 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 4 Jul 2025 17:50:27 +0530 Subject: [PATCH 16/42] 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 8521a68c3e356118ac7a3e82eee350bc0659ef5d Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Sat, 5 Jul 2025 15:25:01 +0530 Subject: [PATCH 17/42] 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 1d318c75d83ca57e179deb07bbee602dca0e83d1 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 10:04:11 +0530 Subject: [PATCH 18/42] 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 56aca323e5d2407a4454758cc888f779808a31cb Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 17:44:58 +0530 Subject: [PATCH 19/42] 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" } } From aebb344a5ac78a63ee98cd661ed99342cd4b6731 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 8 Jul 2025 12:20:54 +0530 Subject: [PATCH 20/42] Implemented the cache in task allocation --- Marco.Pms.CacheHelper/ProjectCache.cs | 4 +--- Marco.Pms.Services/Controllers/ProjectController.cs | 4 ++-- Marco.Pms.Services/Controllers/TaskController.cs | 12 +++++++++++- Marco.Pms.Services/Helpers/CacheUpdateHelper.cs | 11 +++++++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index 6f5a3d3..23df64c 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -26,8 +26,6 @@ namespace Marco.Pms.CacheHelper } public async Task AddProjectDetailsToCache(Project project) { - - //_logger.LogInfo("[AddProjectDetails] Initiated for ProjectId: {ProjectId}", project.Id); var projectDetails = new ProjectMongoDB @@ -544,7 +542,7 @@ namespace Marco.Pms.CacheHelper .FirstOrDefaultAsync(); return workItem; } - public async Task UpdatePlannedAndCompleteWorksInWorkItem(Guid id, double plannedWork = 0, double completedWork = 0, double todaysAssigned = 0) + public async Task UpdatePlannedAndCompleteWorksInWorkItemToCache(Guid id, double plannedWork, double completedWork, double todaysAssigned) { var filter = Builders.Filter.Eq(p => p.Id, id.ToString()); var updates = Builders.Update diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 3ae76ed..e12d2ad 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -29,7 +29,7 @@ namespace MarcoBMS.Services.Controllers private readonly ApplicationDbContext _context; private readonly UserHelper _userHelper; private readonly ILoggingService _logger; - private readonly RolesHelper _rolesHelper; + //private readonly RolesHelper _rolesHelper; private readonly ProjectsHelper _projectsHelper; private readonly IHubContext _signalR; private readonly PermissionServices _permission; @@ -47,7 +47,7 @@ namespace MarcoBMS.Services.Controllers _context = context; _userHelper = userHelper; _logger = logger; - _rolesHelper = rolesHelper; + //_rolesHelper = rolesHelper; _projectsHelper = projectHelper; _signalR = signalR; _cache = cache; diff --git a/Marco.Pms.Services/Controllers/TaskController.cs b/Marco.Pms.Services/Controllers/TaskController.cs index 4ad1f85..68a7132 100644 --- a/Marco.Pms.Services/Controllers/TaskController.cs +++ b/Marco.Pms.Services/Controllers/TaskController.cs @@ -6,6 +6,7 @@ using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Activities; +using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; @@ -31,11 +32,12 @@ namespace MarcoBMS.Services.Controllers private readonly ILoggingService _logger; private readonly IHubContext _signalR; private readonly PermissionServices _permissionServices; + private readonly CacheUpdateHelper _cache; private readonly Guid Approve_Task; private readonly Guid Assign_Report_Task; public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices, - IHubContext signalR) + IHubContext signalR, CacheUpdateHelper cache) { _context = context; _userHelper = userHelper; @@ -43,6 +45,7 @@ namespace MarcoBMS.Services.Controllers _logger = logger; _signalR = signalR; _permissionServices = permissionServices; + _cache = cache; Approve_Task = Guid.Parse("db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c"); Assign_Report_Task = Guid.Parse("6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"); } @@ -86,6 +89,8 @@ namespace MarcoBMS.Services.Controllers _context.TaskAllocations.Add(taskAllocation); await _context.SaveChangesAsync(); + await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, todaysAssigned: taskAllocation.PlannedTask); + _logger.LogInfo("Task {TaskId} assigned by Employee {EmployeeId}", taskAllocation.Id, employee.Id); var response = taskAllocation.ToAssignTaskVMFromTaskAllocation(); @@ -259,6 +264,10 @@ namespace MarcoBMS.Services.Controllers } await _context.SaveChangesAsync(); + var selectedWorkAreaId = taskAllocation.WorkItem?.WorkAreaId ?? Guid.Empty; + + await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, completedWork: taskAllocation.CompletedTask); + await _cache.UpdatePlannedAndCompleteWorksInBuilding(selectedWorkAreaId, completedWork: taskAllocation.CompletedTask); var response = taskAllocation.ToReportTaskVMFromTaskAllocation(); var comments = await _context.TaskComments @@ -679,6 +688,7 @@ namespace MarcoBMS.Services.Controllers /// /// DTO containing task approval details. /// IActionResult indicating success or failure. + [HttpPost("approve")] public async Task ApproveTask(ApproveTaskDto approveTask) { diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index ecce8ab..03fd397 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -174,6 +174,17 @@ namespace Marco.Pms.Services.Helpers return null; } } + public async Task UpdatePlannedAndCompleteWorksInWorkItem(Guid id, double plannedWork = 0, double completedWork = 0, double todaysAssigned = 0) + { + try + { + var response = await _projectCache.UpdatePlannedAndCompleteWorksInWorkItemToCache(id, plannedWork, completedWork, todaysAssigned); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while updating planned work, completed work, and today's assigned work in workItems in Cache: {Error}", ex.Message); + } + } // ------------------------------------ Employee Profile Cache --------------------------------------- From 856510baff8b8b224dd2c06e021af24dec197f4f Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 8 Jul 2025 12:48:13 +0530 Subject: [PATCH 21/42] In Project Report Email only sending data of job role assigned to that project --- Marco.Pms.Services/Controllers/ReportController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marco.Pms.Services/Controllers/ReportController.cs b/Marco.Pms.Services/Controllers/ReportController.cs index 893c16b..8f8a790 100644 --- a/Marco.Pms.Services/Controllers/ReportController.cs +++ b/Marco.Pms.Services/Controllers/ReportController.cs @@ -232,9 +232,9 @@ namespace Marco.Pms.Services.Controllers double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask); double totalCompletedTask = todayAssignedTasks.Sum(t => t.CompletedTask); - + var jobRoleIds = projectAllocations.Select(pa => pa.JobRoleId).ToList(); var jobRoles = await _context.JobRoles - .Where(j => j.TenantId == project.TenantId) + .Where(j => j.TenantId == project.TenantId && jobRoleIds.Contains(j.Id)) .ToListAsync(); // Team on site From de5485b8f61ef5e3f7457accea41e2c2039424c6 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 9 Jul 2025 10:35:35 +0530 Subject: [PATCH 22/42] Changed the signalR keyword for work item --- Marco.Pms.Services/Controllers/ProjectController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index e12d2ad..022729d 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -793,7 +793,7 @@ namespace MarcoBMS.Services.Controllers var responseList = new List(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); string message = ""; - List projectIds = new List(); + List workAreaIds = 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(); @@ -846,7 +846,7 @@ namespace MarcoBMS.Services.Controllers WorkItemId = workItem.Id, WorkItem = workItem }); - projectIds.Add(building.ProjectId); + workAreaIds.Add(workItem.WorkAreaId); } string responseMessage = ""; @@ -873,7 +873,7 @@ namespace MarcoBMS.Services.Controllers - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message }; + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "WorkItem", WorkAreaIds = workAreaIds, Message = message }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); @@ -885,7 +885,7 @@ namespace MarcoBMS.Services.Controllers { Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - List projectIds = new List(); + List workAreaIds = new List(); WorkItem? task = await _context.WorkItems.AsNoTracking().Include(t => t.WorkArea).FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId); if (task != null) { @@ -902,9 +902,9 @@ namespace MarcoBMS.Services.Controllers var floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == floorId); - projectIds.Add(floor?.Building?.ProjectId ?? Guid.Empty); + workAreaIds.Add(task.WorkAreaId); - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = $"Task Deleted in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}, in Area: {task.WorkArea?.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}" }; + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "WorkItem", WorkAreaIds = workAreaIds, Message = $"Task Deleted in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}, in Area: {task.WorkArea?.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}" }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); } else From c4ad3fdad5eb3ee4bb0cee27dcb6d56bbb1d1c13 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 9 Jul 2025 12:39:27 +0530 Subject: [PATCH 23/42] Added the caching project report API and added expiry in workItems in cache --- Marco.Pms.CacheHelper/ProjectCache.cs | 72 ++++- .../MongoDBModels/BuildingMongoDB.cs | 6 +- Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs | 4 + .../MongoDBModels/WorkAreaInfoMongoDB.cs | 13 + .../MongoDBModels/WorkAreaMongoDB.cs | 1 + .../MongoDBModels/WorkItemMongoDB.cs | 1 + .../Controllers/ProjectController.cs | 4 + .../Controllers/ReportController.cs | 161 +--------- Marco.Pms.Services/Dockerfile | 2 +- .../Helpers/CacheUpdateHelper.cs | 30 ++ Marco.Pms.Services/Helpers/ReportHelper.cs | 274 ++++++++++++++++++ Marco.Pms.Services/Program.cs | 1 + 12 files changed, 411 insertions(+), 158 deletions(-) create mode 100644 Marco.Pms.Model/MongoDBModels/WorkAreaInfoMongoDB.cs create mode 100644 Marco.Pms.Services/Helpers/ReportHelper.cs diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index 23df64c..9b2036d 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -106,6 +106,7 @@ namespace Marco.Pms.CacheHelper workAreaMongoList.Add(new WorkAreaMongoDB { Id = wa.Id.ToString(), + FloorId = wa.FloorId.ToString(), AreaName = wa.AreaName, PlannedWork = waPlanned, CompletedWork = waCompleted @@ -118,6 +119,7 @@ namespace Marco.Pms.CacheHelper floorMongoList.Add(new FloorMongoDB { Id = floor.Id.ToString(), + BuildingId = floor.BuildingId.ToString(), FloorName = floor.FloorName, PlannedWork = floorPlanned, CompletedWork = floorCompleted, @@ -131,6 +133,7 @@ namespace Marco.Pms.CacheHelper buildingMongoList.Add(new BuildingMongoDB { Id = building.Id.ToString(), + ProjectId = building.ProjectId.ToString(), BuildingName = building.Name, Description = building.Description, PlannedWork = buildingPlanned, @@ -477,7 +480,59 @@ namespace Marco.Pms.CacheHelper var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); } + public async Task GetBuildingAndFloorByWorkAreaIdFromCache(Guid workAreaId) + { + var pipeline = new[] + { + new BsonDocument("$unwind", "$Buildings"), + new BsonDocument("$unwind", "$Buildings.Floors"), + new BsonDocument("$unwind", "$Buildings.Floors.WorkAreas"), + new BsonDocument("$match", new BsonDocument("Buildings.Floors.WorkAreas._id", workAreaId.ToString())), + new BsonDocument("$project", new BsonDocument + { + { "_id", 0 }, + { "ProjectId", "$_id" }, + { "ProjectName", "$Name" }, + { "PlannedWork", "$PlannedWork" }, + { "CompletedWork", "$CompletedWork" }, + { + "Building", new BsonDocument + { + { "_id", "$Buildings._id" }, + { "BuildingName", "$Buildings.BuildingName" }, + { "Description", "$Buildings.Description" }, + { "PlannedWork", "$Buildings.PlannedWork" }, + { "CompletedWork", "$Buildings.CompletedWork" } + } + }, + { + "Floor", new BsonDocument + { + { "_id", "$Buildings.Floors._id" }, + { "FloorName", "$Buildings.Floors.FloorName" }, + { "PlannedWork", "$Buildings.Floors.PlannedWork" }, + { "CompletedWork", "$Buildings.Floors.CompletedWork" } + } + }, + { "WorkArea", "$Buildings.Floors.WorkAreas" } + }) + }; + var result = await _projetCollection.Aggregate(pipeline).FirstOrDefaultAsync(); + if (result == null) + return null; + return result; + } + public async Task> GetWorkItemsByWorkAreaIdsFromCache(List workAreaIds) + { + var stringWorkAreaIds = workAreaIds.Select(wa => wa.ToString()).ToList(); + var filter = Builders.Filter.In(w => w.WorkAreaId, stringWorkAreaIds); + var workItems = await _taskCollection // replace with your actual collection name + .Find(filter) + .ToListAsync(); + + return workItems; + } // ------------------------------------------------------- WorkItem ------------------------------------------------------- @@ -485,12 +540,14 @@ namespace Marco.Pms.CacheHelper { var activityIds = workItems.Select(wi => wi.ActivityId).ToList(); var workCategoryIds = workItems.Select(wi => wi.WorkCategoryId).ToList(); + var workItemIds = workItems.Select(wi => wi.Id).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(); - + var task = await _context.TaskAllocations.Where(t => workItemIds.Contains(t.WorkItemId) && t.AssignmentDate == DateTime.UtcNow).ToListAsync(); + var todaysAssign = task.Sum(t => t.PlannedTask); foreach (WorkItem workItem in workItems) { var activity = activities.FirstOrDefault(a => a.Id == workItem.ActivityId) ?? new ActivityMaster(); @@ -501,10 +558,11 @@ namespace Marco.Pms.CacheHelper 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.TodaysAssigned, todaysAssign), 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.ExpireAt, DateTime.UtcNow.Date.AddDays(1)), Builders.Update.Set(r => r.ActivityMaster, new ActivityMasterMongoDB { Id = activity.Id.ToString(), @@ -520,6 +578,16 @@ namespace Marco.Pms.CacheHelper ); var options = new UpdateOptions { IsUpsert = true }; var result = await _taskCollection.UpdateOneAsync(filter, updates, options); + if (result.UpsertedId != null) + { + var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); + var indexOptions = new CreateIndexOptions + { + ExpireAfter = TimeSpan.Zero // required for fixed expiration time + }; + var indexModel = new CreateIndexModel(indexKeys, indexOptions); + await _taskCollection.Indexes.CreateOneAsync(indexModel); + } } } public async Task> GetWorkItemDetailsByWorkAreaFromCache(Guid workAreaId) diff --git a/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs b/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs index 64ccbce..786ceb5 100644 --- a/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs @@ -7,12 +7,16 @@ public string? Description { get; set; } public double PlannedWork { get; set; } public double CompletedWork { get; set; } + public string ProjectId { get; set; } = string.Empty; public List Floors { get; set; } = new List(); } public class BuildingMongoDBVM { public string Id { get; set; } = string.Empty; - public string? Name { get; set; } + public string? BuildingName { get; set; } public string? Description { get; set; } + public double PlannedWork { get; set; } + public double CompletedWork { get; set; } + public string ProjectId { get; set; } = string.Empty; } } diff --git a/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs b/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs index 57257a4..15d3060 100644 --- a/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs @@ -3,6 +3,7 @@ public class FloorMongoDB { public string Id { get; set; } = string.Empty; + public string BuildingId { get; set; } = string.Empty; public string? FloorName { get; set; } public double PlannedWork { get; set; } public double CompletedWork { get; set; } @@ -12,6 +13,9 @@ public class FloorMongoDBVM { public string Id { get; set; } = string.Empty; + public string BuildingId { get; set; } = string.Empty; public string? FloorName { get; set; } + public double PlannedWork { get; set; } + public double CompletedWork { get; set; } } } diff --git a/Marco.Pms.Model/MongoDBModels/WorkAreaInfoMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkAreaInfoMongoDB.cs new file mode 100644 index 0000000..da1001b --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/WorkAreaInfoMongoDB.cs @@ -0,0 +1,13 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class WorkAreaInfoMongoDB + { + public string ProjectId { get; set; } = string.Empty; + public string? ProjectName { get; set; } + public BuildingMongoDBVM? Building { get; set; } + public FloorMongoDBVM? Floor { get; set; } + public WorkAreaMongoDB? WorkArea { get; set; } + public double CompletedWork { get; set; } + public double PlannedWork { get; set; } + } +} diff --git a/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs index d17f52c..412c940 100644 --- a/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs @@ -3,6 +3,7 @@ public class WorkAreaMongoDB { public string Id { get; set; } = string.Empty; + public string FloorId { get; set; } = string.Empty; public string? AreaName { get; set; } public double PlannedWork { get; set; } public double CompletedWork { get; set; } diff --git a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs index 850300d..cf798f3 100644 --- a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs @@ -12,5 +12,6 @@ public double CompletedWork { get; set; } = 0; public string? Description { get; set; } public DateTime TaskDate { get; set; } + public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1); } } diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 022729d..620ae75 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -235,6 +235,10 @@ namespace MarcoBMS.Services.Controllers .Include(c => c.ProjectStatus) .FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Id == id); projectVM = GetProjectViewModel(project); + if (project != null) + { + await _cache.AddProjectDetails(project); + } } else { diff --git a/Marco.Pms.Services/Controllers/ReportController.cs b/Marco.Pms.Services/Controllers/ReportController.cs index 8f8a790..11dec58 100644 --- a/Marco.Pms.Services/Controllers/ReportController.cs +++ b/Marco.Pms.Services/Controllers/ReportController.cs @@ -1,12 +1,10 @@ using System.Data; -using System.Globalization; using Marco.Pms.DataAccess.Data; -using Marco.Pms.Model.Dtos.Attendance; using Marco.Pms.Model.Dtos.Mail; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Mail; using Marco.Pms.Model.Utilities; -using Marco.Pms.Model.ViewModels.Report; +using Marco.Pms.Services.Helpers; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; @@ -26,13 +24,15 @@ namespace Marco.Pms.Services.Controllers private readonly ILoggingService _logger; private readonly UserHelper _userHelper; private readonly IWebHostEnvironment _env; - public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper, IWebHostEnvironment env) + private readonly ReportHelper _reportHelper; + public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper, IWebHostEnvironment env, ReportHelper reportHelper) { _context = context; _emailSender = emailSender; _logger = logger; _userHelper = userHelper; _env = env; + _reportHelper = reportHelper; } [HttpPost("set-mail")] @@ -151,7 +151,6 @@ namespace Marco.Pms.Services.Controllers /// An ApiResponse indicating the success or failure of retrieving statistics and sending the email. private async Task> GetProjectStatistics(Guid projectId, List recipientEmails, string body, string subject, Guid tenantId) { - DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date; if (projectId == Guid.Empty) { @@ -159,161 +158,15 @@ namespace Marco.Pms.Services.Controllers return ApiResponse.ErrorResponse("Provided empty Project ID.", "Provided empty Project ID.", 400); } - var project = await _context.Projects - .AsNoTracking() - .FirstOrDefaultAsync(p => p.Id == projectId); - if (project == null) + var statisticReport = await _reportHelper.GetDailyProjectReport(projectId, tenantId); + + if (statisticReport == null) { _logger.LogWarning("User attempted to fetch project progress for project ID {ProjectId} but not found.", projectId); return ApiResponse.ErrorResponse("Project not found.", "Project not found.", 404); } - var statisticReport = new ProjectStatisticReport - { - Date = reportDate, - ProjectName = project.Name ?? "", - TimeStamp = DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture) - }; - - // Preload relevant data - var projectAllocations = await _context.ProjectAllocations - .Include(p => p.Employee) - .Where(p => p.ProjectId == project.Id && p.IsActive) - .ToListAsync(); - - var assignedEmployeeIds = projectAllocations.Select(p => p.EmployeeId).ToHashSet(); - - var attendances = await _context.Attendes - .AsNoTracking() - .Where(a => a.ProjectID == project.Id && a.InTime != null && a.InTime.Value.Date == reportDate) - .ToListAsync(); - - var checkedInEmployeeIds = attendances.Select(a => a.EmployeeID).Distinct().ToHashSet(); - var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeID).Distinct().ToHashSet(); - var regularizationIds = attendances - .Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE) - .Select(a => a.EmployeeID).Distinct().ToHashSet(); - - // Preload buildings, floors, areas - var buildings = await _context.Buildings.Where(b => b.ProjectId == project.Id).ToListAsync(); - var buildingIds = buildings.Select(b => b.Id).ToList(); - - var floors = await _context.Floor.Where(f => buildingIds.Contains(f.BuildingId)).ToListAsync(); - var floorIds = floors.Select(f => f.Id).ToList(); - - var areas = await _context.WorkAreas.Where(a => floorIds.Contains(a.FloorId)).ToListAsync(); - var areaIds = areas.Select(a => a.Id).ToList(); - - var workItems = await _context.WorkItems - .Include(w => w.ActivityMaster) - .Where(w => areaIds.Contains(w.WorkAreaId)) - .ToListAsync(); - - var itemIds = workItems.Select(i => i.Id).ToList(); - - var tasks = await _context.TaskAllocations - .Where(t => itemIds.Contains(t.WorkItemId)) - .ToListAsync(); - - var taskIds = tasks.Select(t => t.Id).ToList(); - - var taskMembers = await _context.TaskMembers - .Include(m => m.Employee) - .Where(m => taskIds.Contains(m.TaskAllocationId)) - .ToListAsync(); - - // Aggregate data - double totalPlannedWork = workItems.Sum(w => w.PlannedWork); - double totalCompletedWork = workItems.Sum(w => w.CompletedWork); - - var todayAssignedTasks = tasks.Where(t => t.AssignmentDate.Date == reportDate).ToList(); - var reportPending = tasks.Where(t => t.ReportedDate == null).ToList(); - - double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask); - double totalCompletedTask = todayAssignedTasks.Sum(t => t.CompletedTask); - var jobRoleIds = projectAllocations.Select(pa => pa.JobRoleId).ToList(); - var jobRoles = await _context.JobRoles - .Where(j => j.TenantId == project.TenantId && jobRoleIds.Contains(j.Id)) - .ToListAsync(); - - // Team on site - var teamOnSite = jobRoles - .Select(role => - { - var count = projectAllocations.Count(p => p.JobRoleId == role.Id && checkedInEmployeeIds.Contains(p.EmployeeId)); - return new TeamOnSite { RoleName = role.Name, NumberofEmployees = count }; - }) - .OrderByDescending(t => t.NumberofEmployees) - .ToList(); - - // Task details - var performedTasks = todayAssignedTasks.Select(task => - { - var workItem = workItems.FirstOrDefault(w => w.Id == task.WorkItemId); - var area = areas.FirstOrDefault(a => a.Id == workItem?.WorkAreaId); - var floor = floors.FirstOrDefault(f => f.Id == area?.FloorId); - var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId); - - string activityName = workItem?.ActivityMaster?.ActivityName ?? ""; - string location = $"{building?.Name} > {floor?.FloorName}
{floor?.FloorName}-{area?.AreaName}"; - double pending = (workItem?.PlannedWork ?? 0) - (workItem?.CompletedWork ?? 0); - - var taskTeam = taskMembers - .Where(m => m.TaskAllocationId == task.Id) - .Select(m => - { - string name = $"{m.Employee?.FirstName ?? ""} {m.Employee?.LastName ?? ""}"; - var role = jobRoles.FirstOrDefault(r => r.Id == m.Employee?.JobRoleId); - return new TaskTeam { Name = name, RoleName = role?.Name ?? "" }; - }) - .ToList(); - - return new PerformedTask - { - Activity = activityName, - Location = location, - AssignedToday = task.PlannedTask, - CompletedToday = task.CompletedTask, - Pending = pending, - Comment = task.Description, - Team = taskTeam - }; - }).ToList(); - - // Attendance details - var performedAttendance = attendances.Select(att => - { - var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeID); - var role = jobRoles.FirstOrDefault(r => r.Id == alloc?.JobRoleId); - string name = $"{alloc?.Employee?.FirstName ?? ""} {alloc?.Employee?.LastName ?? ""}"; - - return new PerformedAttendance - { - Name = name, - RoleName = role?.Name ?? "", - InTime = att.InTime ?? DateTime.UtcNow, - OutTime = att.OutTime, - Comment = att.Comment - }; - }).ToList(); - - // Fill report - statisticReport.TodaysAttendances = checkedInEmployeeIds.Count; - statisticReport.TotalEmployees = assignedEmployeeIds.Count; - statisticReport.RegularizationPending = regularizationIds.Count; - statisticReport.CheckoutPending = checkoutPendingIds.Count; - statisticReport.TotalPlannedWork = totalPlannedWork; - statisticReport.TotalCompletedWork = totalCompletedWork; - statisticReport.TotalPlannedTask = totalPlannedTask; - statisticReport.TotalCompletedTask = totalCompletedTask; - statisticReport.CompletionStatus = totalPlannedWork > 0 ? totalCompletedWork / totalPlannedWork : 0; - statisticReport.TodaysAssignTasks = todayAssignedTasks.Count; - statisticReport.ReportPending = reportPending.Count; - statisticReport.TeamOnSite = teamOnSite; - statisticReport.PerformedTasks = performedTasks; - statisticReport.PerformedAttendance = performedAttendance; - // Send Email var emailBody = await _emailSender.SendProjectStatisticsEmail(recipientEmails, body, subject, statisticReport); var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Email != null && recipientEmails.Contains(e.Email)) ?? new Employee(); diff --git a/Marco.Pms.Services/Dockerfile b/Marco.Pms.Services/Dockerfile index 77311ee..2aa24ea 100644 --- a/Marco.Pms.Services/Dockerfile +++ b/Marco.Pms.Services/Dockerfile @@ -19,7 +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/"] +COPY ["Marco.Pms.CacheHelper/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 index 03fd397..216ec6e 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -120,6 +120,36 @@ namespace Marco.Pms.Services.Helpers _logger.LogWarning("Error occured while updating planned work and completed work in building infra form Cache: {Error}", ex.Message); } } + public async Task GetBuildingAndFloorByWorkAreaId(Guid workAreaId) + { + try + { + var response = await _projectCache.GetBuildingAndFloorByWorkAreaIdFromCache(workAreaId); + return response; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while fetching workArea Details using its ID form Cache: {Error}", ex.Message); + return null; + } + } + public async Task?> GetWorkItemsByWorkAreaIds(List workAreaIds) + { + try + { + var response = await _projectCache.GetWorkItemsByWorkAreaIdsFromCache(workAreaIds); + if (response.Count > 0) + { + return response; + } + return null; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while fetching workItems list using workArea IDs list form Cache: {Error}", ex.Message); + return null; + } + } // ------------------------------------------------------- WorkItem ------------------------------------------------------- diff --git a/Marco.Pms.Services/Helpers/ReportHelper.cs b/Marco.Pms.Services/Helpers/ReportHelper.cs new file mode 100644 index 0000000..e7632fd --- /dev/null +++ b/Marco.Pms.Services/Helpers/ReportHelper.cs @@ -0,0 +1,274 @@ +using System.Globalization; +using Marco.Pms.DataAccess.Data; +using Marco.Pms.Model.Dtos.Attendance; +using Marco.Pms.Model.MongoDBModels; +using Marco.Pms.Model.ViewModels.Report; +using Microsoft.EntityFrameworkCore; + +namespace Marco.Pms.Services.Helpers +{ + public class ReportHelper + { + private readonly ApplicationDbContext _context; + private readonly CacheUpdateHelper _cache; + public ReportHelper(CacheUpdateHelper cache, ApplicationDbContext context) + { + _cache = cache; + _context = context; + } + public async Task GetDailyProjectReport(Guid projectId, Guid tenantId) + { + // await _cache.GetBuildingAndFloorByWorkAreaId(); + DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date; + var project = await _cache.GetProjectDetails(projectId); + if (project == null) + { + var projectSQL = await _context.Projects + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId); + if (projectSQL != null) + { + project = new ProjectMongoDB + { + Id = projectSQL.Id.ToString(), + Name = projectSQL.Name, + ShortName = projectSQL.ShortName, + ProjectAddress = projectSQL.ProjectAddress, + ContactPerson = projectSQL.ContactPerson + }; + await _cache.AddProjectDetails(projectSQL); + } + } + if (project != null) + { + + var statisticReport = new ProjectStatisticReport + { + Date = reportDate, + ProjectName = project.Name ?? "", + TimeStamp = DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture) + }; + + // Preload relevant data + var projectAllocations = await _context.ProjectAllocations + .Include(p => p.Employee) + .Where(p => p.ProjectId == projectId && p.IsActive) + .ToListAsync(); + + var assignedEmployeeIds = projectAllocations.Select(p => p.EmployeeId).ToHashSet(); + + var attendances = await _context.Attendes + .AsNoTracking() + .Where(a => a.ProjectID == projectId && a.InTime != null && a.InTime.Value.Date == reportDate) + .ToListAsync(); + + var checkedInEmployeeIds = attendances.Select(a => a.EmployeeID).Distinct().ToHashSet(); + var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeID).Distinct().ToHashSet(); + var regularizationIds = attendances + .Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE) + .Select(a => a.EmployeeID).Distinct().ToHashSet(); + + // Preload buildings, floors, areas + List? buildings = null; + List? floors = null; + List? areas = null; + List? workItems = null; + + // Fetch Buildings + buildings = project.Buildings + .Select(b => new BuildingMongoDBVM + { + Id = b.Id, + ProjectId = b.ProjectId, + BuildingName = b.BuildingName, + Description = b.Description + }).ToList(); + if (buildings == null) + { + buildings = await _context.Buildings + .Where(b => b.ProjectId == projectId) + .Select(b => new BuildingMongoDBVM + { + Id = b.Id.ToString(), + ProjectId = b.ProjectId.ToString(), + BuildingName = b.Name, + Description = b.Description + }) + .ToListAsync(); + } + + // fetch Floors + floors = project.Buildings + .SelectMany(b => b.Floors.Select(f => new FloorMongoDBVM + { + Id = f.Id.ToString(), + BuildingId = f.BuildingId, + FloorName = f.FloorName + })).ToList(); + if (floors == null) + { + var buildingIds = buildings.Select(b => Guid.Parse(b.Id)).ToList(); + floors = await _context.Floor + .Where(f => buildingIds.Contains(f.BuildingId)) + .Select(f => new FloorMongoDBVM + { + Id = f.Id.ToString(), + BuildingId = f.BuildingId.ToString(), + FloorName = f.FloorName + }) + .ToListAsync(); + } + + // fetch Work Areas + areas = project.Buildings + .SelectMany(b => b.Floors) + .SelectMany(f => f.WorkAreas).ToList(); + if (areas == null) + { + var floorIds = floors.Select(f => Guid.Parse(f.Id)).ToList(); + areas = await _context.WorkAreas + .Where(a => floorIds.Contains(a.FloorId)) + .Select(wa => new WorkAreaMongoDB + { + Id = wa.Id.ToString(), + FloorId = wa.FloorId.ToString(), + AreaName = wa.AreaName, + }) + .ToListAsync(); + } + + var areaIds = areas.Select(a => Guid.Parse(a.Id)).ToList(); + + // fetch Work Items + workItems = await _cache.GetWorkItemsByWorkAreaIds(areaIds); + if (workItems == null) + { + workItems = await _context.WorkItems + .Include(w => w.ActivityMaster) + .Where(w => areaIds.Contains(w.WorkAreaId)) + .Select(wi => new WorkItemMongoDB + { + Id = wi.Id.ToString(), + WorkAreaId = wi.WorkAreaId.ToString(), + PlannedWork = wi.PlannedWork, + CompletedWork = wi.CompletedWork, + Description = wi.Description, + TaskDate = wi.TaskDate, + ActivityMaster = new ActivityMasterMongoDB + { + ActivityName = wi.ActivityMaster != null ? wi.ActivityMaster.ActivityName : null, + UnitOfMeasurement = wi.ActivityMaster != null ? wi.ActivityMaster.UnitOfMeasurement : null + } + }) + .ToListAsync(); + } + + var itemIds = workItems.Select(i => Guid.Parse(i.Id)).ToList(); + + var tasks = await _context.TaskAllocations + .Where(t => itemIds.Contains(t.WorkItemId)) + .ToListAsync(); + + var taskIds = tasks.Select(t => t.Id).ToList(); + + var taskMembers = await _context.TaskMembers + .Include(m => m.Employee) + .Where(m => taskIds.Contains(m.TaskAllocationId)) + .ToListAsync(); + + // Aggregate data + double totalPlannedWork = workItems.Sum(w => w.PlannedWork); + double totalCompletedWork = workItems.Sum(w => w.CompletedWork); + + var todayAssignedTasks = tasks.Where(t => t.AssignmentDate.Date == reportDate).ToList(); + var reportPending = tasks.Where(t => t.ReportedDate == null).ToList(); + + double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask); + double totalCompletedTask = todayAssignedTasks.Sum(t => t.CompletedTask); + var jobRoleIds = projectAllocations.Select(pa => pa.JobRoleId).ToList(); + var jobRoles = await _context.JobRoles + .Where(j => j.TenantId == tenantId && jobRoleIds.Contains(j.Id)) + .ToListAsync(); + + // Team on site + var teamOnSite = jobRoles + .Select(role => + { + var count = projectAllocations.Count(p => p.JobRoleId == role.Id && checkedInEmployeeIds.Contains(p.EmployeeId)); + return new TeamOnSite { RoleName = role.Name, NumberofEmployees = count }; + }) + .OrderByDescending(t => t.NumberofEmployees) + .ToList(); + + // Task details + var performedTasks = todayAssignedTasks.Select(task => + { + var workItem = workItems.FirstOrDefault(w => w.Id == task.WorkItemId.ToString()); + var area = areas.FirstOrDefault(a => a.Id == workItem?.WorkAreaId); + var floor = floors.FirstOrDefault(f => f.Id == area?.FloorId); + var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId); + + string activityName = workItem?.ActivityMaster?.ActivityName ?? ""; + string location = $"{building?.BuildingName} > {floor?.FloorName}
{floor?.FloorName}-{area?.AreaName}"; + double pending = (workItem?.PlannedWork ?? 0) - (workItem?.CompletedWork ?? 0); + + var taskTeam = taskMembers + .Where(m => m.TaskAllocationId == task.Id) + .Select(m => + { + string name = $"{m.Employee?.FirstName ?? ""} {m.Employee?.LastName ?? ""}"; + var role = jobRoles.FirstOrDefault(r => r.Id == m.Employee?.JobRoleId); + return new TaskTeam { Name = name, RoleName = role?.Name ?? "" }; + }) + .ToList(); + + return new PerformedTask + { + Activity = activityName, + Location = location, + AssignedToday = task.PlannedTask, + CompletedToday = task.CompletedTask, + Pending = pending, + Comment = task.Description, + Team = taskTeam + }; + }).ToList(); + + // Attendance details + var performedAttendance = attendances.Select(att => + { + var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeID); + var role = jobRoles.FirstOrDefault(r => r.Id == alloc?.JobRoleId); + string name = $"{alloc?.Employee?.FirstName ?? ""} {alloc?.Employee?.LastName ?? ""}"; + + return new PerformedAttendance + { + Name = name, + RoleName = role?.Name ?? "", + InTime = att.InTime ?? DateTime.UtcNow, + OutTime = att.OutTime, + Comment = att.Comment + }; + }).ToList(); + + // Fill report + statisticReport.TodaysAttendances = checkedInEmployeeIds.Count; + statisticReport.TotalEmployees = assignedEmployeeIds.Count; + statisticReport.RegularizationPending = regularizationIds.Count; + statisticReport.CheckoutPending = checkoutPendingIds.Count; + statisticReport.TotalPlannedWork = totalPlannedWork; + statisticReport.TotalCompletedWork = totalCompletedWork; + statisticReport.TotalPlannedTask = totalPlannedTask; + statisticReport.TotalCompletedTask = totalCompletedTask; + statisticReport.CompletionStatus = totalPlannedWork > 0 ? totalCompletedWork / totalPlannedWork : 0; + statisticReport.TodaysAssignTasks = todayAssignedTasks.Count; + statisticReport.ReportPending = reportPending.Count; + statisticReport.TeamOnSite = teamOnSite; + statisticReport.PerformedTasks = performedTasks; + statisticReport.PerformedAttendance = performedAttendance; + return statisticReport; + } + return null; + } + } +} diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index 1d9b4b3..30831c6 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -137,6 +137,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); From 17c56be712eb644b1e0c1b3a12318bf340603dca Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 9 Jul 2025 15:11:08 +0530 Subject: [PATCH 24/42] Added new parameter in log "Origin" --- Marco.Pms.Services/Middleware/LoggingMiddleware.cs | 4 +++- Marco.Pms.Services/Service/RefreshTokenService.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Marco.Pms.Services/Middleware/LoggingMiddleware.cs b/Marco.Pms.Services/Middleware/LoggingMiddleware.cs index dd10d7d..c57f05c 100644 --- a/Marco.Pms.Services/Middleware/LoggingMiddleware.cs +++ b/Marco.Pms.Services/Middleware/LoggingMiddleware.cs @@ -24,7 +24,7 @@ namespace MarcoBMS.Services.Middleware var response = context.Response; var request = context.Request; var tenantId = context.User.FindFirst("TenantId")?.Value; - + string origin = request.Headers["Origin"].FirstOrDefault() ?? ""; using (LogContext.PushProperty("TenantId", tenantId)) using (LogContext.PushProperty("TraceId", context.TraceIdentifier)) @@ -33,6 +33,8 @@ namespace MarcoBMS.Services.Middleware using (LogContext.PushProperty("Timestamp", DateTime.UtcNow)) using (LogContext.PushProperty("IpAddress", context.Connection.RemoteIpAddress?.ToString())) using (LogContext.PushProperty("RequestPath", request.Path)) + using (LogContext.PushProperty("Origin", origin)) + try diff --git a/Marco.Pms.Services/Service/RefreshTokenService.cs b/Marco.Pms.Services/Service/RefreshTokenService.cs index 018de68..231e27c 100644 --- a/Marco.Pms.Services/Service/RefreshTokenService.cs +++ b/Marco.Pms.Services/Service/RefreshTokenService.cs @@ -218,7 +218,7 @@ namespace MarcoBMS.Services.Service catch (Exception ex) { // Token is invalid - Console.WriteLine($"Token validation failed: {ex.Message}"); + _logger.LogError($"Token validation failed: {ex.Message}"); return null; } } From c2a9a42af55982de4b7012fa8f9ce9c230abf198 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 10 Jul 2025 09:52:18 +0530 Subject: [PATCH 25/42] Added old project Details API --- .../ViewModels/Projects/OldProjectVM.cs | 10 ++ .../Controllers/ProjectController.cs | 134 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 Marco.Pms.Model/ViewModels/Projects/OldProjectVM.cs diff --git a/Marco.Pms.Model/ViewModels/Projects/OldProjectVM.cs b/Marco.Pms.Model/ViewModels/Projects/OldProjectVM.cs new file mode 100644 index 0000000..cb38dfc --- /dev/null +++ b/Marco.Pms.Model/ViewModels/Projects/OldProjectVM.cs @@ -0,0 +1,10 @@ +using Marco.Pms.Model.Dtos.Project; + +namespace Marco.Pms.Model.ViewModels.Projects +{ + public class OldProjectVM : ProjectDto + { + public List? Buildings { get; set; } + + } +} diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index e12d2ad..fbc9bf6 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -1,6 +1,8 @@ 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.Master; using Marco.Pms.Model.MongoDBModels; @@ -288,6 +290,138 @@ namespace MarcoBMS.Services.Controllers }; } + [HttpGet("details-old/{id}")] + public async Task DetailsOld([FromRoute] Guid id) + { + // ProjectDetailsVM vm = new ProjectDetailsVM(); + + if (!ModelState.IsValid) + { + var errors = ModelState.Values + .SelectMany(v => v.Errors) + .Select(e => e.ErrorMessage) + .ToList(); + 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); + + if (project == null) + { + 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); + + OldProjectVM projectVM = new OldProjectVM(); + 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)); + } + + + } + + private async Task GetProjectViewModel(Guid? id, Project project) + { + ProjectDetailsVM vm = new ProjectDetailsVM(); + + // List buildings = _unitOfWork.Building.GetAll(c => c.ProjectId == id).ToList(); + List buildings = await _context.Buildings.Where(c => c.ProjectId == id).ToListAsync(); + List idList = buildings.Select(o => o.Id).ToList(); + // List floors = _unitOfWork.Floor.GetAll(c => idList.Contains(c.Id)).ToList(); + List floors = await _context.Floor.Where(c => idList.Contains(c.BuildingId)).ToListAsync(); + idList = floors.Select(o => o.Id).ToList(); + //List workAreas = _unitOfWork.WorkArea.GetAll(c => idList.Contains(c.Id), includeProperties: "WorkItems,WorkItems.ActivityMaster").ToList(); + + List workAreas = await _context.WorkAreas.Where(c => idList.Contains(c.FloorId)).ToListAsync(); + + idList = workAreas.Select(o => o.Id).ToList(); + List workItems = await _context.WorkItems.Include(c => c.WorkCategoryMaster).Where(c => idList.Contains(c.WorkAreaId)).Include(c => c.ActivityMaster).ToListAsync(); + // List workItems = _unitOfWork.WorkItem.GetAll(c => idList.Contains(c.WorkAreaId), includeProperties: "ActivityMaster").ToList(); + idList = workItems.Select(t => t.Id).ToList(); + List tasks = await _context.TaskAllocations.Where(t => idList.Contains(t.WorkItemId) && t.AssignmentDate.Date == DateTime.UtcNow.Date).ToListAsync(); + vm.project = project; + vm.buildings = buildings; + vm.floors = floors; + vm.workAreas = workAreas; + vm.workItems = workItems; + vm.Tasks = tasks; + return vm; + } + private Guid GetTenantId() { return _userHelper.GetTenantId(); From 40ce4ced42bc67a7e79829077bbb31449f10937f Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 10 Jul 2025 14:59:28 +0530 Subject: [PATCH 26/42] Added the workcategory in WorkItem --- Marco.Pms.Services/Controllers/ProjectController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index fde715f..09858d5 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -894,7 +894,7 @@ namespace MarcoBMS.Services.Controllers }, WorkCategoryMaster = new WorkCategoryMasterMongoDB { - Id = wi.ActivityId.ToString(), + Id = wi.WorkCategoryId.ToString() ?? "", Name = wi.WorkCategoryMaster != null ? wi.WorkCategoryMaster.Name : "", Description = wi.WorkCategoryMaster != null ? wi.WorkCategoryMaster.Description : "" }, From bb76c45195aee9d63c6412224055f0e1709a34c9 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 10 Jul 2025 15:57:08 +0530 Subject: [PATCH 27/42] Removing the project stored in cache for employee who have the project manage permission --- Marco.Pms.CacheHelper/EmployeeCache.cs | 14 +++++ .../Controllers/ProjectController.cs | 59 ++++++++++++++----- .../Helpers/CacheUpdateHelper.cs | 11 ++++ 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/Marco.Pms.CacheHelper/EmployeeCache.cs b/Marco.Pms.CacheHelper/EmployeeCache.cs index 5c86e6f..c2a1f7b 100644 --- a/Marco.Pms.CacheHelper/EmployeeCache.cs +++ b/Marco.Pms.CacheHelper/EmployeeCache.cs @@ -137,6 +137,20 @@ namespace Marco.Pms.CacheHelper return true; } + public async Task ClearAllProjectIdsByPermissionIdFromCache(Guid permissionId) + { + var filter = Builders.Filter.AnyEq(e => e.PermissionIds, permissionId.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 diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 09858d5..07ddbfd 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -36,6 +36,7 @@ namespace MarcoBMS.Services.Controllers private readonly IHubContext _signalR; private readonly PermissionServices _permission; private readonly CacheUpdateHelper _cache; + private readonly IServiceScopeFactory _serviceScopeFactory; private readonly Guid ViewProjects; private readonly Guid ManageProject; private readonly Guid ViewInfra; @@ -44,7 +45,7 @@ namespace MarcoBMS.Services.Controllers public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, - IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache) + IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache, IServiceScopeFactory serviceScopeFactory) { _context = context; _userHelper = userHelper; @@ -59,6 +60,7 @@ namespace MarcoBMS.Services.Controllers ViewInfra = Guid.Parse("8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"); ManageInfra = Guid.Parse("f2aee20a-b754-4537-8166-f9507b44585b"); tenantId = _userHelper.GetTenantId(); + _serviceScopeFactory = serviceScopeFactory; } [HttpGet("list/basic")] @@ -436,31 +438,56 @@ namespace MarcoBMS.Services.Controllers [HttpPost] public async Task Create([FromBody] CreateProjectDto projectDto) { - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + // 1. Validate input first (early exit) if (!ModelState.IsValid) { - var errors = ModelState.Values - .SelectMany(v => v.Errors) - .Select(e => e.ErrorMessage) - .ToList(); + var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); - } - Guid TenantId = GetTenantId(); - var project = projectDto.ToProjectFromCreateProjectDto(TenantId); + // 2. Prepare data without I/O + Guid tenantId = _userHelper.GetTenantId(); // Assuming this is fast and from claims + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var loggedInUserId = loggedInEmployee.Id; + var project = projectDto.ToProjectFromCreateProjectDto(tenantId); - _context.Projects.Add(project); + // 3. Store it to database + try + { + _context.Projects.Add(project); + await _context.SaveChangesAsync(); + } + catch (Exception ex) + { + // Log the detailed exception + _logger.LogError("Failed to create project in database. Rolling back transaction. : {Error}", ex.Message); + // Return a server error as the primary operation failed + return StatusCode(500, ApiResponse.ErrorResponse("An error occurred while saving the project.", ex.Message, 500)); + } - await _context.SaveChangesAsync(); + // 4. Perform non-critical side-effects (caching, notifications) concurrently + try + { + // These operations do not depend on each other, so they can run in parallel. + Task cacheAddDetailsTask = _cache.AddProjectDetails(project); + Task cacheClearListTask = _cache.ClearAllProjectIdsByPermissionId(ManageProject); - await _cache.AddProjectDetails(project); + var notification = new { LoggedInUserId = loggedInUserId, Keyword = "Create_Project", Response = project.ToProjectDto() }; + // Send notification only to the relevant group (e.g., users in the same tenant) + Task notificationTask = _signalR.Clients.Group(tenantId.ToString()).SendAsync("NotificationEventHandler", notification); - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Create_Project", Response = project.ToProjectDto() }; + // Await all side-effect tasks to complete in parallel + await Task.WhenAll(cacheAddDetailsTask, cacheClearListTask, notificationTask); + } + catch (Exception ex) + { + // The project was created successfully, but a side-effect failed. + // Log this as a warning, as the primary operation succeeded. Don't return an error to the user. + _logger.LogWarning("Project {ProjectId} was created, but a post-creation side-effect (caching/notification) failed. : {Error}", project.Id, ex.Message); + } - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); - - return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Success.", 200)); + // 5. Return a success response to the user as soon as the critical data is saved. + return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Project created successfully.", 200)); } [HttpPut] diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 216ec6e..ae6264e 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -298,6 +298,17 @@ namespace Marco.Pms.Services.Helpers _logger.LogWarning("Error occured while deleting projectIds from Cache for Application Role {RoleId}: {Error}", roleId, ex.Message); } } + public async Task ClearAllProjectIdsByPermissionId(Guid permissionId) + { + try + { + await _employeeCache.ClearAllProjectIdsByPermissionIdFromCache(permissionId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting projectIds from Cache for Permission {PermissionId}: {Error}", permissionId, ex.Message); + } + } public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId) { try From de3fa6b929617c9f49e22272e98476c985db9a08 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 1 Jul 2025 12:39:07 +0530 Subject: [PATCH 28/42] 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 3e8ef856d4dd9e91b78ec5858939f2f5e0f34472 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 4 Jul 2025 17:49:25 +0530 Subject: [PATCH 29/42] 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 | 65 ++- 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(+), 87 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 50bb9a0..3ccddba 100644 --- a/Marco.Pms.Services/Helpers/ProjectsHelper.cs +++ b/Marco.Pms.Services/Helpers/ProjectsHelper.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 Microsoft.EntityFrameworkCore; namespace MarcoBMS.Services.Helpers @@ -10,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) @@ -49,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 129ccf7faef77e2e971d66d153376bfa0e7eb231 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 4 Jul 2025 17:50:27 +0530 Subject: [PATCH 30/42] 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 3d8e91d58d0d1d7039bc6eb36a60c6e37b8d0b43 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Sat, 5 Jul 2025 15:25:01 +0530 Subject: [PATCH 31/42] 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 d8cf87aee4dd55fc0e417a60bb439ae3dc5e27fb Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 10:04:11 +0530 Subject: [PATCH 32/42] 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 cbcc3398c31396533e2f897d98e0a14cc5d5827e Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 7 Jul 2025 17:44:58 +0530 Subject: [PATCH 33/42] 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" } } From 364616359336fbda224d109b8373ab05256ca30d Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 8 Jul 2025 12:20:54 +0530 Subject: [PATCH 34/42] Implemented the cache in task allocation --- Marco.Pms.CacheHelper/ProjectCache.cs | 4 +--- Marco.Pms.Services/Controllers/ProjectController.cs | 4 ++-- Marco.Pms.Services/Controllers/TaskController.cs | 10 +++++++++- Marco.Pms.Services/Helpers/CacheUpdateHelper.cs | 11 +++++++++++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index 6f5a3d3..23df64c 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -26,8 +26,6 @@ namespace Marco.Pms.CacheHelper } public async Task AddProjectDetailsToCache(Project project) { - - //_logger.LogInfo("[AddProjectDetails] Initiated for ProjectId: {ProjectId}", project.Id); var projectDetails = new ProjectMongoDB @@ -544,7 +542,7 @@ namespace Marco.Pms.CacheHelper .FirstOrDefaultAsync(); return workItem; } - public async Task UpdatePlannedAndCompleteWorksInWorkItem(Guid id, double plannedWork = 0, double completedWork = 0, double todaysAssigned = 0) + public async Task UpdatePlannedAndCompleteWorksInWorkItemToCache(Guid id, double plannedWork, double completedWork, double todaysAssigned) { var filter = Builders.Filter.Eq(p => p.Id, id.ToString()); var updates = Builders.Update diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 3ae76ed..e12d2ad 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -29,7 +29,7 @@ namespace MarcoBMS.Services.Controllers private readonly ApplicationDbContext _context; private readonly UserHelper _userHelper; private readonly ILoggingService _logger; - private readonly RolesHelper _rolesHelper; + //private readonly RolesHelper _rolesHelper; private readonly ProjectsHelper _projectsHelper; private readonly IHubContext _signalR; private readonly PermissionServices _permission; @@ -47,7 +47,7 @@ namespace MarcoBMS.Services.Controllers _context = context; _userHelper = userHelper; _logger = logger; - _rolesHelper = rolesHelper; + //_rolesHelper = rolesHelper; _projectsHelper = projectHelper; _signalR = signalR; _cache = cache; diff --git a/Marco.Pms.Services/Controllers/TaskController.cs b/Marco.Pms.Services/Controllers/TaskController.cs index ca24f1a..40d31f8 100644 --- a/Marco.Pms.Services/Controllers/TaskController.cs +++ b/Marco.Pms.Services/Controllers/TaskController.cs @@ -6,6 +6,7 @@ using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Activities; +using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; @@ -33,7 +34,7 @@ namespace MarcoBMS.Services.Controllers private readonly PermissionServices _permissionServices; public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices, - IHubContext signalR) + IHubContext signalR, CacheUpdateHelper cache) { _context = context; _userHelper = userHelper; @@ -82,6 +83,8 @@ namespace MarcoBMS.Services.Controllers _context.TaskAllocations.Add(taskAllocation); await _context.SaveChangesAsync(); + await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, todaysAssigned: taskAllocation.PlannedTask); + _logger.LogInfo("Task {TaskId} assigned by Employee {EmployeeId}", taskAllocation.Id, employee.Id); var response = taskAllocation.ToAssignTaskVMFromTaskAllocation(); @@ -255,6 +258,10 @@ namespace MarcoBMS.Services.Controllers } await _context.SaveChangesAsync(); + var selectedWorkAreaId = taskAllocation.WorkItem?.WorkAreaId ?? Guid.Empty; + + await _cache.UpdatePlannedAndCompleteWorksInWorkItem(taskAllocation.WorkItemId, completedWork: taskAllocation.CompletedTask); + await _cache.UpdatePlannedAndCompleteWorksInBuilding(selectedWorkAreaId, completedWork: taskAllocation.CompletedTask); var response = taskAllocation.ToReportTaskVMFromTaskAllocation(); var comments = await _context.TaskComments @@ -675,6 +682,7 @@ namespace MarcoBMS.Services.Controllers /// /// DTO containing task approval details. /// IActionResult indicating success or failure. + [HttpPost("approve")] public async Task ApproveTask(ApproveTaskDto approveTask) { diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index ecce8ab..03fd397 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -174,6 +174,17 @@ namespace Marco.Pms.Services.Helpers return null; } } + public async Task UpdatePlannedAndCompleteWorksInWorkItem(Guid id, double plannedWork = 0, double completedWork = 0, double todaysAssigned = 0) + { + try + { + var response = await _projectCache.UpdatePlannedAndCompleteWorksInWorkItemToCache(id, plannedWork, completedWork, todaysAssigned); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while updating planned work, completed work, and today's assigned work in workItems in Cache: {Error}", ex.Message); + } + } // ------------------------------------ Employee Profile Cache --------------------------------------- From 0be200e77aa2bd3efd06d510ab2eef929a93e6d9 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 8 Jul 2025 12:48:13 +0530 Subject: [PATCH 35/42] In Project Report Email only sending data of job role assigned to that project --- Marco.Pms.Services/Controllers/ReportController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marco.Pms.Services/Controllers/ReportController.cs b/Marco.Pms.Services/Controllers/ReportController.cs index 893c16b..8f8a790 100644 --- a/Marco.Pms.Services/Controllers/ReportController.cs +++ b/Marco.Pms.Services/Controllers/ReportController.cs @@ -232,9 +232,9 @@ namespace Marco.Pms.Services.Controllers double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask); double totalCompletedTask = todayAssignedTasks.Sum(t => t.CompletedTask); - + var jobRoleIds = projectAllocations.Select(pa => pa.JobRoleId).ToList(); var jobRoles = await _context.JobRoles - .Where(j => j.TenantId == project.TenantId) + .Where(j => j.TenantId == project.TenantId && jobRoleIds.Contains(j.Id)) .ToListAsync(); // Team on site From 3ec4bd762f574e37c02dbcba154c595bf92656da Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 10 Jul 2025 09:52:18 +0530 Subject: [PATCH 36/42] Added old project Details API --- .../ViewModels/Projects/OldProjectVM.cs | 10 ++ .../Controllers/ProjectController.cs | 134 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 Marco.Pms.Model/ViewModels/Projects/OldProjectVM.cs diff --git a/Marco.Pms.Model/ViewModels/Projects/OldProjectVM.cs b/Marco.Pms.Model/ViewModels/Projects/OldProjectVM.cs new file mode 100644 index 0000000..cb38dfc --- /dev/null +++ b/Marco.Pms.Model/ViewModels/Projects/OldProjectVM.cs @@ -0,0 +1,10 @@ +using Marco.Pms.Model.Dtos.Project; + +namespace Marco.Pms.Model.ViewModels.Projects +{ + public class OldProjectVM : ProjectDto + { + public List? Buildings { get; set; } + + } +} diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index e12d2ad..fbc9bf6 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -1,6 +1,8 @@ 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.Master; using Marco.Pms.Model.MongoDBModels; @@ -288,6 +290,138 @@ namespace MarcoBMS.Services.Controllers }; } + [HttpGet("details-old/{id}")] + public async Task DetailsOld([FromRoute] Guid id) + { + // ProjectDetailsVM vm = new ProjectDetailsVM(); + + if (!ModelState.IsValid) + { + var errors = ModelState.Values + .SelectMany(v => v.Errors) + .Select(e => e.ErrorMessage) + .ToList(); + 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); + + if (project == null) + { + 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); + + OldProjectVM projectVM = new OldProjectVM(); + 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)); + } + + + } + + private async Task GetProjectViewModel(Guid? id, Project project) + { + ProjectDetailsVM vm = new ProjectDetailsVM(); + + // List buildings = _unitOfWork.Building.GetAll(c => c.ProjectId == id).ToList(); + List buildings = await _context.Buildings.Where(c => c.ProjectId == id).ToListAsync(); + List idList = buildings.Select(o => o.Id).ToList(); + // List floors = _unitOfWork.Floor.GetAll(c => idList.Contains(c.Id)).ToList(); + List floors = await _context.Floor.Where(c => idList.Contains(c.BuildingId)).ToListAsync(); + idList = floors.Select(o => o.Id).ToList(); + //List workAreas = _unitOfWork.WorkArea.GetAll(c => idList.Contains(c.Id), includeProperties: "WorkItems,WorkItems.ActivityMaster").ToList(); + + List workAreas = await _context.WorkAreas.Where(c => idList.Contains(c.FloorId)).ToListAsync(); + + idList = workAreas.Select(o => o.Id).ToList(); + List workItems = await _context.WorkItems.Include(c => c.WorkCategoryMaster).Where(c => idList.Contains(c.WorkAreaId)).Include(c => c.ActivityMaster).ToListAsync(); + // List workItems = _unitOfWork.WorkItem.GetAll(c => idList.Contains(c.WorkAreaId), includeProperties: "ActivityMaster").ToList(); + idList = workItems.Select(t => t.Id).ToList(); + List tasks = await _context.TaskAllocations.Where(t => idList.Contains(t.WorkItemId) && t.AssignmentDate.Date == DateTime.UtcNow.Date).ToListAsync(); + vm.project = project; + vm.buildings = buildings; + vm.floors = floors; + vm.workAreas = workAreas; + vm.workItems = workItems; + vm.Tasks = tasks; + return vm; + } + private Guid GetTenantId() { return _userHelper.GetTenantId(); From 3e316ef388afe1254933ad98260780be85d4ba20 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 9 Jul 2025 10:35:35 +0530 Subject: [PATCH 37/42] Changed the signalR keyword for work item --- Marco.Pms.Services/Controllers/ProjectController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index fbc9bf6..8453db2 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -927,7 +927,7 @@ namespace MarcoBMS.Services.Controllers var responseList = new List(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); string message = ""; - List projectIds = new List(); + List workAreaIds = 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(); @@ -980,7 +980,7 @@ namespace MarcoBMS.Services.Controllers WorkItemId = workItem.Id, WorkItem = workItem }); - projectIds.Add(building.ProjectId); + workAreaIds.Add(workItem.WorkAreaId); } string responseMessage = ""; @@ -1007,7 +1007,7 @@ namespace MarcoBMS.Services.Controllers - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message }; + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "WorkItem", WorkAreaIds = workAreaIds, Message = message }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); @@ -1019,7 +1019,7 @@ namespace MarcoBMS.Services.Controllers { Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - List projectIds = new List(); + List workAreaIds = new List(); WorkItem? task = await _context.WorkItems.AsNoTracking().Include(t => t.WorkArea).FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId); if (task != null) { @@ -1036,9 +1036,9 @@ namespace MarcoBMS.Services.Controllers var floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == floorId); - projectIds.Add(floor?.Building?.ProjectId ?? Guid.Empty); + workAreaIds.Add(task.WorkAreaId); - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = $"Task Deleted in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}, in Area: {task.WorkArea?.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}" }; + var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "WorkItem", WorkAreaIds = workAreaIds, Message = $"Task Deleted in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}, in Area: {task.WorkArea?.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}" }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); } else From 669500e57ec5fe35f10a653e331b51df976bfda7 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 9 Jul 2025 12:39:27 +0530 Subject: [PATCH 38/42] Added the caching project report API and added expiry in workItems in cache --- Marco.Pms.CacheHelper/ProjectCache.cs | 72 ++++- .../MongoDBModels/BuildingMongoDB.cs | 6 +- Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs | 4 + .../MongoDBModels/WorkAreaInfoMongoDB.cs | 13 + .../MongoDBModels/WorkAreaMongoDB.cs | 1 + .../MongoDBModels/WorkItemMongoDB.cs | 1 + .../Controllers/ProjectController.cs | 4 + .../Controllers/ReportController.cs | 161 +--------- Marco.Pms.Services/Dockerfile | 2 +- .../Helpers/CacheUpdateHelper.cs | 30 ++ Marco.Pms.Services/Helpers/ReportHelper.cs | 274 ++++++++++++++++++ Marco.Pms.Services/Program.cs | 1 + 12 files changed, 411 insertions(+), 158 deletions(-) create mode 100644 Marco.Pms.Model/MongoDBModels/WorkAreaInfoMongoDB.cs create mode 100644 Marco.Pms.Services/Helpers/ReportHelper.cs diff --git a/Marco.Pms.CacheHelper/ProjectCache.cs b/Marco.Pms.CacheHelper/ProjectCache.cs index 23df64c..9b2036d 100644 --- a/Marco.Pms.CacheHelper/ProjectCache.cs +++ b/Marco.Pms.CacheHelper/ProjectCache.cs @@ -106,6 +106,7 @@ namespace Marco.Pms.CacheHelper workAreaMongoList.Add(new WorkAreaMongoDB { Id = wa.Id.ToString(), + FloorId = wa.FloorId.ToString(), AreaName = wa.AreaName, PlannedWork = waPlanned, CompletedWork = waCompleted @@ -118,6 +119,7 @@ namespace Marco.Pms.CacheHelper floorMongoList.Add(new FloorMongoDB { Id = floor.Id.ToString(), + BuildingId = floor.BuildingId.ToString(), FloorName = floor.FloorName, PlannedWork = floorPlanned, CompletedWork = floorCompleted, @@ -131,6 +133,7 @@ namespace Marco.Pms.CacheHelper buildingMongoList.Add(new BuildingMongoDB { Id = building.Id.ToString(), + ProjectId = building.ProjectId.ToString(), BuildingName = building.Name, Description = building.Description, PlannedWork = buildingPlanned, @@ -477,7 +480,59 @@ namespace Marco.Pms.CacheHelper var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); } + public async Task GetBuildingAndFloorByWorkAreaIdFromCache(Guid workAreaId) + { + var pipeline = new[] + { + new BsonDocument("$unwind", "$Buildings"), + new BsonDocument("$unwind", "$Buildings.Floors"), + new BsonDocument("$unwind", "$Buildings.Floors.WorkAreas"), + new BsonDocument("$match", new BsonDocument("Buildings.Floors.WorkAreas._id", workAreaId.ToString())), + new BsonDocument("$project", new BsonDocument + { + { "_id", 0 }, + { "ProjectId", "$_id" }, + { "ProjectName", "$Name" }, + { "PlannedWork", "$PlannedWork" }, + { "CompletedWork", "$CompletedWork" }, + { + "Building", new BsonDocument + { + { "_id", "$Buildings._id" }, + { "BuildingName", "$Buildings.BuildingName" }, + { "Description", "$Buildings.Description" }, + { "PlannedWork", "$Buildings.PlannedWork" }, + { "CompletedWork", "$Buildings.CompletedWork" } + } + }, + { + "Floor", new BsonDocument + { + { "_id", "$Buildings.Floors._id" }, + { "FloorName", "$Buildings.Floors.FloorName" }, + { "PlannedWork", "$Buildings.Floors.PlannedWork" }, + { "CompletedWork", "$Buildings.Floors.CompletedWork" } + } + }, + { "WorkArea", "$Buildings.Floors.WorkAreas" } + }) + }; + var result = await _projetCollection.Aggregate(pipeline).FirstOrDefaultAsync(); + if (result == null) + return null; + return result; + } + public async Task> GetWorkItemsByWorkAreaIdsFromCache(List workAreaIds) + { + var stringWorkAreaIds = workAreaIds.Select(wa => wa.ToString()).ToList(); + var filter = Builders.Filter.In(w => w.WorkAreaId, stringWorkAreaIds); + var workItems = await _taskCollection // replace with your actual collection name + .Find(filter) + .ToListAsync(); + + return workItems; + } // ------------------------------------------------------- WorkItem ------------------------------------------------------- @@ -485,12 +540,14 @@ namespace Marco.Pms.CacheHelper { var activityIds = workItems.Select(wi => wi.ActivityId).ToList(); var workCategoryIds = workItems.Select(wi => wi.WorkCategoryId).ToList(); + var workItemIds = workItems.Select(wi => wi.Id).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(); - + var task = await _context.TaskAllocations.Where(t => workItemIds.Contains(t.WorkItemId) && t.AssignmentDate == DateTime.UtcNow).ToListAsync(); + var todaysAssign = task.Sum(t => t.PlannedTask); foreach (WorkItem workItem in workItems) { var activity = activities.FirstOrDefault(a => a.Id == workItem.ActivityId) ?? new ActivityMaster(); @@ -501,10 +558,11 @@ namespace Marco.Pms.CacheHelper 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.TodaysAssigned, todaysAssign), 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.ExpireAt, DateTime.UtcNow.Date.AddDays(1)), Builders.Update.Set(r => r.ActivityMaster, new ActivityMasterMongoDB { Id = activity.Id.ToString(), @@ -520,6 +578,16 @@ namespace Marco.Pms.CacheHelper ); var options = new UpdateOptions { IsUpsert = true }; var result = await _taskCollection.UpdateOneAsync(filter, updates, options); + if (result.UpsertedId != null) + { + var indexKeys = Builders.IndexKeys.Ascending(x => x.ExpireAt); + var indexOptions = new CreateIndexOptions + { + ExpireAfter = TimeSpan.Zero // required for fixed expiration time + }; + var indexModel = new CreateIndexModel(indexKeys, indexOptions); + await _taskCollection.Indexes.CreateOneAsync(indexModel); + } } } public async Task> GetWorkItemDetailsByWorkAreaFromCache(Guid workAreaId) diff --git a/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs b/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs index 64ccbce..786ceb5 100644 --- a/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/BuildingMongoDB.cs @@ -7,12 +7,16 @@ public string? Description { get; set; } public double PlannedWork { get; set; } public double CompletedWork { get; set; } + public string ProjectId { get; set; } = string.Empty; public List Floors { get; set; } = new List(); } public class BuildingMongoDBVM { public string Id { get; set; } = string.Empty; - public string? Name { get; set; } + public string? BuildingName { get; set; } public string? Description { get; set; } + public double PlannedWork { get; set; } + public double CompletedWork { get; set; } + public string ProjectId { get; set; } = string.Empty; } } diff --git a/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs b/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs index 57257a4..15d3060 100644 --- a/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/FloorMongoDB.cs @@ -3,6 +3,7 @@ public class FloorMongoDB { public string Id { get; set; } = string.Empty; + public string BuildingId { get; set; } = string.Empty; public string? FloorName { get; set; } public double PlannedWork { get; set; } public double CompletedWork { get; set; } @@ -12,6 +13,9 @@ public class FloorMongoDBVM { public string Id { get; set; } = string.Empty; + public string BuildingId { get; set; } = string.Empty; public string? FloorName { get; set; } + public double PlannedWork { get; set; } + public double CompletedWork { get; set; } } } diff --git a/Marco.Pms.Model/MongoDBModels/WorkAreaInfoMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkAreaInfoMongoDB.cs new file mode 100644 index 0000000..da1001b --- /dev/null +++ b/Marco.Pms.Model/MongoDBModels/WorkAreaInfoMongoDB.cs @@ -0,0 +1,13 @@ +namespace Marco.Pms.Model.MongoDBModels +{ + public class WorkAreaInfoMongoDB + { + public string ProjectId { get; set; } = string.Empty; + public string? ProjectName { get; set; } + public BuildingMongoDBVM? Building { get; set; } + public FloorMongoDBVM? Floor { get; set; } + public WorkAreaMongoDB? WorkArea { get; set; } + public double CompletedWork { get; set; } + public double PlannedWork { get; set; } + } +} diff --git a/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs index d17f52c..412c940 100644 --- a/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/WorkAreaMongoDB.cs @@ -3,6 +3,7 @@ public class WorkAreaMongoDB { public string Id { get; set; } = string.Empty; + public string FloorId { get; set; } = string.Empty; public string? AreaName { get; set; } public double PlannedWork { get; set; } public double CompletedWork { get; set; } diff --git a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs index 850300d..cf798f3 100644 --- a/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/WorkItemMongoDB.cs @@ -12,5 +12,6 @@ public double CompletedWork { get; set; } = 0; public string? Description { get; set; } public DateTime TaskDate { get; set; } + public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1); } } diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 8453db2..fde715f 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -237,6 +237,10 @@ namespace MarcoBMS.Services.Controllers .Include(c => c.ProjectStatus) .FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Id == id); projectVM = GetProjectViewModel(project); + if (project != null) + { + await _cache.AddProjectDetails(project); + } } else { diff --git a/Marco.Pms.Services/Controllers/ReportController.cs b/Marco.Pms.Services/Controllers/ReportController.cs index 8f8a790..11dec58 100644 --- a/Marco.Pms.Services/Controllers/ReportController.cs +++ b/Marco.Pms.Services/Controllers/ReportController.cs @@ -1,12 +1,10 @@ using System.Data; -using System.Globalization; using Marco.Pms.DataAccess.Data; -using Marco.Pms.Model.Dtos.Attendance; using Marco.Pms.Model.Dtos.Mail; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Mail; using Marco.Pms.Model.Utilities; -using Marco.Pms.Model.ViewModels.Report; +using Marco.Pms.Services.Helpers; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; @@ -26,13 +24,15 @@ namespace Marco.Pms.Services.Controllers private readonly ILoggingService _logger; private readonly UserHelper _userHelper; private readonly IWebHostEnvironment _env; - public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper, IWebHostEnvironment env) + private readonly ReportHelper _reportHelper; + public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper, IWebHostEnvironment env, ReportHelper reportHelper) { _context = context; _emailSender = emailSender; _logger = logger; _userHelper = userHelper; _env = env; + _reportHelper = reportHelper; } [HttpPost("set-mail")] @@ -151,7 +151,6 @@ namespace Marco.Pms.Services.Controllers /// An ApiResponse indicating the success or failure of retrieving statistics and sending the email. private async Task> GetProjectStatistics(Guid projectId, List recipientEmails, string body, string subject, Guid tenantId) { - DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date; if (projectId == Guid.Empty) { @@ -159,161 +158,15 @@ namespace Marco.Pms.Services.Controllers return ApiResponse.ErrorResponse("Provided empty Project ID.", "Provided empty Project ID.", 400); } - var project = await _context.Projects - .AsNoTracking() - .FirstOrDefaultAsync(p => p.Id == projectId); - if (project == null) + var statisticReport = await _reportHelper.GetDailyProjectReport(projectId, tenantId); + + if (statisticReport == null) { _logger.LogWarning("User attempted to fetch project progress for project ID {ProjectId} but not found.", projectId); return ApiResponse.ErrorResponse("Project not found.", "Project not found.", 404); } - var statisticReport = new ProjectStatisticReport - { - Date = reportDate, - ProjectName = project.Name ?? "", - TimeStamp = DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture) - }; - - // Preload relevant data - var projectAllocations = await _context.ProjectAllocations - .Include(p => p.Employee) - .Where(p => p.ProjectId == project.Id && p.IsActive) - .ToListAsync(); - - var assignedEmployeeIds = projectAllocations.Select(p => p.EmployeeId).ToHashSet(); - - var attendances = await _context.Attendes - .AsNoTracking() - .Where(a => a.ProjectID == project.Id && a.InTime != null && a.InTime.Value.Date == reportDate) - .ToListAsync(); - - var checkedInEmployeeIds = attendances.Select(a => a.EmployeeID).Distinct().ToHashSet(); - var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeID).Distinct().ToHashSet(); - var regularizationIds = attendances - .Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE) - .Select(a => a.EmployeeID).Distinct().ToHashSet(); - - // Preload buildings, floors, areas - var buildings = await _context.Buildings.Where(b => b.ProjectId == project.Id).ToListAsync(); - var buildingIds = buildings.Select(b => b.Id).ToList(); - - var floors = await _context.Floor.Where(f => buildingIds.Contains(f.BuildingId)).ToListAsync(); - var floorIds = floors.Select(f => f.Id).ToList(); - - var areas = await _context.WorkAreas.Where(a => floorIds.Contains(a.FloorId)).ToListAsync(); - var areaIds = areas.Select(a => a.Id).ToList(); - - var workItems = await _context.WorkItems - .Include(w => w.ActivityMaster) - .Where(w => areaIds.Contains(w.WorkAreaId)) - .ToListAsync(); - - var itemIds = workItems.Select(i => i.Id).ToList(); - - var tasks = await _context.TaskAllocations - .Where(t => itemIds.Contains(t.WorkItemId)) - .ToListAsync(); - - var taskIds = tasks.Select(t => t.Id).ToList(); - - var taskMembers = await _context.TaskMembers - .Include(m => m.Employee) - .Where(m => taskIds.Contains(m.TaskAllocationId)) - .ToListAsync(); - - // Aggregate data - double totalPlannedWork = workItems.Sum(w => w.PlannedWork); - double totalCompletedWork = workItems.Sum(w => w.CompletedWork); - - var todayAssignedTasks = tasks.Where(t => t.AssignmentDate.Date == reportDate).ToList(); - var reportPending = tasks.Where(t => t.ReportedDate == null).ToList(); - - double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask); - double totalCompletedTask = todayAssignedTasks.Sum(t => t.CompletedTask); - var jobRoleIds = projectAllocations.Select(pa => pa.JobRoleId).ToList(); - var jobRoles = await _context.JobRoles - .Where(j => j.TenantId == project.TenantId && jobRoleIds.Contains(j.Id)) - .ToListAsync(); - - // Team on site - var teamOnSite = jobRoles - .Select(role => - { - var count = projectAllocations.Count(p => p.JobRoleId == role.Id && checkedInEmployeeIds.Contains(p.EmployeeId)); - return new TeamOnSite { RoleName = role.Name, NumberofEmployees = count }; - }) - .OrderByDescending(t => t.NumberofEmployees) - .ToList(); - - // Task details - var performedTasks = todayAssignedTasks.Select(task => - { - var workItem = workItems.FirstOrDefault(w => w.Id == task.WorkItemId); - var area = areas.FirstOrDefault(a => a.Id == workItem?.WorkAreaId); - var floor = floors.FirstOrDefault(f => f.Id == area?.FloorId); - var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId); - - string activityName = workItem?.ActivityMaster?.ActivityName ?? ""; - string location = $"{building?.Name} > {floor?.FloorName}
{floor?.FloorName}-{area?.AreaName}"; - double pending = (workItem?.PlannedWork ?? 0) - (workItem?.CompletedWork ?? 0); - - var taskTeam = taskMembers - .Where(m => m.TaskAllocationId == task.Id) - .Select(m => - { - string name = $"{m.Employee?.FirstName ?? ""} {m.Employee?.LastName ?? ""}"; - var role = jobRoles.FirstOrDefault(r => r.Id == m.Employee?.JobRoleId); - return new TaskTeam { Name = name, RoleName = role?.Name ?? "" }; - }) - .ToList(); - - return new PerformedTask - { - Activity = activityName, - Location = location, - AssignedToday = task.PlannedTask, - CompletedToday = task.CompletedTask, - Pending = pending, - Comment = task.Description, - Team = taskTeam - }; - }).ToList(); - - // Attendance details - var performedAttendance = attendances.Select(att => - { - var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeID); - var role = jobRoles.FirstOrDefault(r => r.Id == alloc?.JobRoleId); - string name = $"{alloc?.Employee?.FirstName ?? ""} {alloc?.Employee?.LastName ?? ""}"; - - return new PerformedAttendance - { - Name = name, - RoleName = role?.Name ?? "", - InTime = att.InTime ?? DateTime.UtcNow, - OutTime = att.OutTime, - Comment = att.Comment - }; - }).ToList(); - - // Fill report - statisticReport.TodaysAttendances = checkedInEmployeeIds.Count; - statisticReport.TotalEmployees = assignedEmployeeIds.Count; - statisticReport.RegularizationPending = regularizationIds.Count; - statisticReport.CheckoutPending = checkoutPendingIds.Count; - statisticReport.TotalPlannedWork = totalPlannedWork; - statisticReport.TotalCompletedWork = totalCompletedWork; - statisticReport.TotalPlannedTask = totalPlannedTask; - statisticReport.TotalCompletedTask = totalCompletedTask; - statisticReport.CompletionStatus = totalPlannedWork > 0 ? totalCompletedWork / totalPlannedWork : 0; - statisticReport.TodaysAssignTasks = todayAssignedTasks.Count; - statisticReport.ReportPending = reportPending.Count; - statisticReport.TeamOnSite = teamOnSite; - statisticReport.PerformedTasks = performedTasks; - statisticReport.PerformedAttendance = performedAttendance; - // Send Email var emailBody = await _emailSender.SendProjectStatisticsEmail(recipientEmails, body, subject, statisticReport); var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Email != null && recipientEmails.Contains(e.Email)) ?? new Employee(); diff --git a/Marco.Pms.Services/Dockerfile b/Marco.Pms.Services/Dockerfile index 77311ee..2aa24ea 100644 --- a/Marco.Pms.Services/Dockerfile +++ b/Marco.Pms.Services/Dockerfile @@ -19,7 +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/"] +COPY ["Marco.Pms.CacheHelper/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 index 03fd397..216ec6e 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -120,6 +120,36 @@ namespace Marco.Pms.Services.Helpers _logger.LogWarning("Error occured while updating planned work and completed work in building infra form Cache: {Error}", ex.Message); } } + public async Task GetBuildingAndFloorByWorkAreaId(Guid workAreaId) + { + try + { + var response = await _projectCache.GetBuildingAndFloorByWorkAreaIdFromCache(workAreaId); + return response; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while fetching workArea Details using its ID form Cache: {Error}", ex.Message); + return null; + } + } + public async Task?> GetWorkItemsByWorkAreaIds(List workAreaIds) + { + try + { + var response = await _projectCache.GetWorkItemsByWorkAreaIdsFromCache(workAreaIds); + if (response.Count > 0) + { + return response; + } + return null; + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while fetching workItems list using workArea IDs list form Cache: {Error}", ex.Message); + return null; + } + } // ------------------------------------------------------- WorkItem ------------------------------------------------------- diff --git a/Marco.Pms.Services/Helpers/ReportHelper.cs b/Marco.Pms.Services/Helpers/ReportHelper.cs new file mode 100644 index 0000000..e7632fd --- /dev/null +++ b/Marco.Pms.Services/Helpers/ReportHelper.cs @@ -0,0 +1,274 @@ +using System.Globalization; +using Marco.Pms.DataAccess.Data; +using Marco.Pms.Model.Dtos.Attendance; +using Marco.Pms.Model.MongoDBModels; +using Marco.Pms.Model.ViewModels.Report; +using Microsoft.EntityFrameworkCore; + +namespace Marco.Pms.Services.Helpers +{ + public class ReportHelper + { + private readonly ApplicationDbContext _context; + private readonly CacheUpdateHelper _cache; + public ReportHelper(CacheUpdateHelper cache, ApplicationDbContext context) + { + _cache = cache; + _context = context; + } + public async Task GetDailyProjectReport(Guid projectId, Guid tenantId) + { + // await _cache.GetBuildingAndFloorByWorkAreaId(); + DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date; + var project = await _cache.GetProjectDetails(projectId); + if (project == null) + { + var projectSQL = await _context.Projects + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId); + if (projectSQL != null) + { + project = new ProjectMongoDB + { + Id = projectSQL.Id.ToString(), + Name = projectSQL.Name, + ShortName = projectSQL.ShortName, + ProjectAddress = projectSQL.ProjectAddress, + ContactPerson = projectSQL.ContactPerson + }; + await _cache.AddProjectDetails(projectSQL); + } + } + if (project != null) + { + + var statisticReport = new ProjectStatisticReport + { + Date = reportDate, + ProjectName = project.Name ?? "", + TimeStamp = DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture) + }; + + // Preload relevant data + var projectAllocations = await _context.ProjectAllocations + .Include(p => p.Employee) + .Where(p => p.ProjectId == projectId && p.IsActive) + .ToListAsync(); + + var assignedEmployeeIds = projectAllocations.Select(p => p.EmployeeId).ToHashSet(); + + var attendances = await _context.Attendes + .AsNoTracking() + .Where(a => a.ProjectID == projectId && a.InTime != null && a.InTime.Value.Date == reportDate) + .ToListAsync(); + + var checkedInEmployeeIds = attendances.Select(a => a.EmployeeID).Distinct().ToHashSet(); + var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeID).Distinct().ToHashSet(); + var regularizationIds = attendances + .Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE) + .Select(a => a.EmployeeID).Distinct().ToHashSet(); + + // Preload buildings, floors, areas + List? buildings = null; + List? floors = null; + List? areas = null; + List? workItems = null; + + // Fetch Buildings + buildings = project.Buildings + .Select(b => new BuildingMongoDBVM + { + Id = b.Id, + ProjectId = b.ProjectId, + BuildingName = b.BuildingName, + Description = b.Description + }).ToList(); + if (buildings == null) + { + buildings = await _context.Buildings + .Where(b => b.ProjectId == projectId) + .Select(b => new BuildingMongoDBVM + { + Id = b.Id.ToString(), + ProjectId = b.ProjectId.ToString(), + BuildingName = b.Name, + Description = b.Description + }) + .ToListAsync(); + } + + // fetch Floors + floors = project.Buildings + .SelectMany(b => b.Floors.Select(f => new FloorMongoDBVM + { + Id = f.Id.ToString(), + BuildingId = f.BuildingId, + FloorName = f.FloorName + })).ToList(); + if (floors == null) + { + var buildingIds = buildings.Select(b => Guid.Parse(b.Id)).ToList(); + floors = await _context.Floor + .Where(f => buildingIds.Contains(f.BuildingId)) + .Select(f => new FloorMongoDBVM + { + Id = f.Id.ToString(), + BuildingId = f.BuildingId.ToString(), + FloorName = f.FloorName + }) + .ToListAsync(); + } + + // fetch Work Areas + areas = project.Buildings + .SelectMany(b => b.Floors) + .SelectMany(f => f.WorkAreas).ToList(); + if (areas == null) + { + var floorIds = floors.Select(f => Guid.Parse(f.Id)).ToList(); + areas = await _context.WorkAreas + .Where(a => floorIds.Contains(a.FloorId)) + .Select(wa => new WorkAreaMongoDB + { + Id = wa.Id.ToString(), + FloorId = wa.FloorId.ToString(), + AreaName = wa.AreaName, + }) + .ToListAsync(); + } + + var areaIds = areas.Select(a => Guid.Parse(a.Id)).ToList(); + + // fetch Work Items + workItems = await _cache.GetWorkItemsByWorkAreaIds(areaIds); + if (workItems == null) + { + workItems = await _context.WorkItems + .Include(w => w.ActivityMaster) + .Where(w => areaIds.Contains(w.WorkAreaId)) + .Select(wi => new WorkItemMongoDB + { + Id = wi.Id.ToString(), + WorkAreaId = wi.WorkAreaId.ToString(), + PlannedWork = wi.PlannedWork, + CompletedWork = wi.CompletedWork, + Description = wi.Description, + TaskDate = wi.TaskDate, + ActivityMaster = new ActivityMasterMongoDB + { + ActivityName = wi.ActivityMaster != null ? wi.ActivityMaster.ActivityName : null, + UnitOfMeasurement = wi.ActivityMaster != null ? wi.ActivityMaster.UnitOfMeasurement : null + } + }) + .ToListAsync(); + } + + var itemIds = workItems.Select(i => Guid.Parse(i.Id)).ToList(); + + var tasks = await _context.TaskAllocations + .Where(t => itemIds.Contains(t.WorkItemId)) + .ToListAsync(); + + var taskIds = tasks.Select(t => t.Id).ToList(); + + var taskMembers = await _context.TaskMembers + .Include(m => m.Employee) + .Where(m => taskIds.Contains(m.TaskAllocationId)) + .ToListAsync(); + + // Aggregate data + double totalPlannedWork = workItems.Sum(w => w.PlannedWork); + double totalCompletedWork = workItems.Sum(w => w.CompletedWork); + + var todayAssignedTasks = tasks.Where(t => t.AssignmentDate.Date == reportDate).ToList(); + var reportPending = tasks.Where(t => t.ReportedDate == null).ToList(); + + double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask); + double totalCompletedTask = todayAssignedTasks.Sum(t => t.CompletedTask); + var jobRoleIds = projectAllocations.Select(pa => pa.JobRoleId).ToList(); + var jobRoles = await _context.JobRoles + .Where(j => j.TenantId == tenantId && jobRoleIds.Contains(j.Id)) + .ToListAsync(); + + // Team on site + var teamOnSite = jobRoles + .Select(role => + { + var count = projectAllocations.Count(p => p.JobRoleId == role.Id && checkedInEmployeeIds.Contains(p.EmployeeId)); + return new TeamOnSite { RoleName = role.Name, NumberofEmployees = count }; + }) + .OrderByDescending(t => t.NumberofEmployees) + .ToList(); + + // Task details + var performedTasks = todayAssignedTasks.Select(task => + { + var workItem = workItems.FirstOrDefault(w => w.Id == task.WorkItemId.ToString()); + var area = areas.FirstOrDefault(a => a.Id == workItem?.WorkAreaId); + var floor = floors.FirstOrDefault(f => f.Id == area?.FloorId); + var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId); + + string activityName = workItem?.ActivityMaster?.ActivityName ?? ""; + string location = $"{building?.BuildingName} > {floor?.FloorName}
{floor?.FloorName}-{area?.AreaName}"; + double pending = (workItem?.PlannedWork ?? 0) - (workItem?.CompletedWork ?? 0); + + var taskTeam = taskMembers + .Where(m => m.TaskAllocationId == task.Id) + .Select(m => + { + string name = $"{m.Employee?.FirstName ?? ""} {m.Employee?.LastName ?? ""}"; + var role = jobRoles.FirstOrDefault(r => r.Id == m.Employee?.JobRoleId); + return new TaskTeam { Name = name, RoleName = role?.Name ?? "" }; + }) + .ToList(); + + return new PerformedTask + { + Activity = activityName, + Location = location, + AssignedToday = task.PlannedTask, + CompletedToday = task.CompletedTask, + Pending = pending, + Comment = task.Description, + Team = taskTeam + }; + }).ToList(); + + // Attendance details + var performedAttendance = attendances.Select(att => + { + var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeID); + var role = jobRoles.FirstOrDefault(r => r.Id == alloc?.JobRoleId); + string name = $"{alloc?.Employee?.FirstName ?? ""} {alloc?.Employee?.LastName ?? ""}"; + + return new PerformedAttendance + { + Name = name, + RoleName = role?.Name ?? "", + InTime = att.InTime ?? DateTime.UtcNow, + OutTime = att.OutTime, + Comment = att.Comment + }; + }).ToList(); + + // Fill report + statisticReport.TodaysAttendances = checkedInEmployeeIds.Count; + statisticReport.TotalEmployees = assignedEmployeeIds.Count; + statisticReport.RegularizationPending = regularizationIds.Count; + statisticReport.CheckoutPending = checkoutPendingIds.Count; + statisticReport.TotalPlannedWork = totalPlannedWork; + statisticReport.TotalCompletedWork = totalCompletedWork; + statisticReport.TotalPlannedTask = totalPlannedTask; + statisticReport.TotalCompletedTask = totalCompletedTask; + statisticReport.CompletionStatus = totalPlannedWork > 0 ? totalCompletedWork / totalPlannedWork : 0; + statisticReport.TodaysAssignTasks = todayAssignedTasks.Count; + statisticReport.ReportPending = reportPending.Count; + statisticReport.TeamOnSite = teamOnSite; + statisticReport.PerformedTasks = performedTasks; + statisticReport.PerformedAttendance = performedAttendance; + return statisticReport; + } + return null; + } + } +} diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index 1d9b4b3..30831c6 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -137,6 +137,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); From ff722503d508599acd701a36594e9cf3699c0ff7 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 9 Jul 2025 15:11:08 +0530 Subject: [PATCH 39/42] Added new parameter in log "Origin" --- Marco.Pms.Services/Middleware/LoggingMiddleware.cs | 4 +++- Marco.Pms.Services/Service/RefreshTokenService.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Marco.Pms.Services/Middleware/LoggingMiddleware.cs b/Marco.Pms.Services/Middleware/LoggingMiddleware.cs index dd10d7d..c57f05c 100644 --- a/Marco.Pms.Services/Middleware/LoggingMiddleware.cs +++ b/Marco.Pms.Services/Middleware/LoggingMiddleware.cs @@ -24,7 +24,7 @@ namespace MarcoBMS.Services.Middleware var response = context.Response; var request = context.Request; var tenantId = context.User.FindFirst("TenantId")?.Value; - + string origin = request.Headers["Origin"].FirstOrDefault() ?? ""; using (LogContext.PushProperty("TenantId", tenantId)) using (LogContext.PushProperty("TraceId", context.TraceIdentifier)) @@ -33,6 +33,8 @@ namespace MarcoBMS.Services.Middleware using (LogContext.PushProperty("Timestamp", DateTime.UtcNow)) using (LogContext.PushProperty("IpAddress", context.Connection.RemoteIpAddress?.ToString())) using (LogContext.PushProperty("RequestPath", request.Path)) + using (LogContext.PushProperty("Origin", origin)) + try diff --git a/Marco.Pms.Services/Service/RefreshTokenService.cs b/Marco.Pms.Services/Service/RefreshTokenService.cs index 018de68..231e27c 100644 --- a/Marco.Pms.Services/Service/RefreshTokenService.cs +++ b/Marco.Pms.Services/Service/RefreshTokenService.cs @@ -218,7 +218,7 @@ namespace MarcoBMS.Services.Service catch (Exception ex) { // Token is invalid - Console.WriteLine($"Token validation failed: {ex.Message}"); + _logger.LogError($"Token validation failed: {ex.Message}"); return null; } } From 3c8a044d6682b4af8aba6585f6c50f964c7a959d Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 10 Jul 2025 14:59:28 +0530 Subject: [PATCH 40/42] Added the workcategory in WorkItem --- Marco.Pms.Services/Controllers/ProjectController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index fde715f..09858d5 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -894,7 +894,7 @@ namespace MarcoBMS.Services.Controllers }, WorkCategoryMaster = new WorkCategoryMasterMongoDB { - Id = wi.ActivityId.ToString(), + Id = wi.WorkCategoryId.ToString() ?? "", Name = wi.WorkCategoryMaster != null ? wi.WorkCategoryMaster.Name : "", Description = wi.WorkCategoryMaster != null ? wi.WorkCategoryMaster.Description : "" }, From 8e3eedbfa7317ac44729d1960718d033b188d92e Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 10 Jul 2025 15:57:08 +0530 Subject: [PATCH 41/42] Removing the project stored in cache for employee who have the project manage permission --- Marco.Pms.CacheHelper/EmployeeCache.cs | 14 +++++ .../Controllers/ProjectController.cs | 59 ++++++++++++++----- .../Helpers/CacheUpdateHelper.cs | 11 ++++ 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/Marco.Pms.CacheHelper/EmployeeCache.cs b/Marco.Pms.CacheHelper/EmployeeCache.cs index 5c86e6f..c2a1f7b 100644 --- a/Marco.Pms.CacheHelper/EmployeeCache.cs +++ b/Marco.Pms.CacheHelper/EmployeeCache.cs @@ -137,6 +137,20 @@ namespace Marco.Pms.CacheHelper return true; } + public async Task ClearAllProjectIdsByPermissionIdFromCache(Guid permissionId) + { + var filter = Builders.Filter.AnyEq(e => e.PermissionIds, permissionId.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 diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 09858d5..07ddbfd 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -36,6 +36,7 @@ namespace MarcoBMS.Services.Controllers private readonly IHubContext _signalR; private readonly PermissionServices _permission; private readonly CacheUpdateHelper _cache; + private readonly IServiceScopeFactory _serviceScopeFactory; private readonly Guid ViewProjects; private readonly Guid ManageProject; private readonly Guid ViewInfra; @@ -44,7 +45,7 @@ namespace MarcoBMS.Services.Controllers public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, - IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache) + IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache, IServiceScopeFactory serviceScopeFactory) { _context = context; _userHelper = userHelper; @@ -59,6 +60,7 @@ namespace MarcoBMS.Services.Controllers ViewInfra = Guid.Parse("8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"); ManageInfra = Guid.Parse("f2aee20a-b754-4537-8166-f9507b44585b"); tenantId = _userHelper.GetTenantId(); + _serviceScopeFactory = serviceScopeFactory; } [HttpGet("list/basic")] @@ -436,31 +438,56 @@ namespace MarcoBMS.Services.Controllers [HttpPost] public async Task Create([FromBody] CreateProjectDto projectDto) { - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + // 1. Validate input first (early exit) if (!ModelState.IsValid) { - var errors = ModelState.Values - .SelectMany(v => v.Errors) - .Select(e => e.ErrorMessage) - .ToList(); + var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); - } - Guid TenantId = GetTenantId(); - var project = projectDto.ToProjectFromCreateProjectDto(TenantId); + // 2. Prepare data without I/O + Guid tenantId = _userHelper.GetTenantId(); // Assuming this is fast and from claims + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var loggedInUserId = loggedInEmployee.Id; + var project = projectDto.ToProjectFromCreateProjectDto(tenantId); - _context.Projects.Add(project); + // 3. Store it to database + try + { + _context.Projects.Add(project); + await _context.SaveChangesAsync(); + } + catch (Exception ex) + { + // Log the detailed exception + _logger.LogError("Failed to create project in database. Rolling back transaction. : {Error}", ex.Message); + // Return a server error as the primary operation failed + return StatusCode(500, ApiResponse.ErrorResponse("An error occurred while saving the project.", ex.Message, 500)); + } - await _context.SaveChangesAsync(); + // 4. Perform non-critical side-effects (caching, notifications) concurrently + try + { + // These operations do not depend on each other, so they can run in parallel. + Task cacheAddDetailsTask = _cache.AddProjectDetails(project); + Task cacheClearListTask = _cache.ClearAllProjectIdsByPermissionId(ManageProject); - await _cache.AddProjectDetails(project); + var notification = new { LoggedInUserId = loggedInUserId, Keyword = "Create_Project", Response = project.ToProjectDto() }; + // Send notification only to the relevant group (e.g., users in the same tenant) + Task notificationTask = _signalR.Clients.Group(tenantId.ToString()).SendAsync("NotificationEventHandler", notification); - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Create_Project", Response = project.ToProjectDto() }; + // Await all side-effect tasks to complete in parallel + await Task.WhenAll(cacheAddDetailsTask, cacheClearListTask, notificationTask); + } + catch (Exception ex) + { + // The project was created successfully, but a side-effect failed. + // Log this as a warning, as the primary operation succeeded. Don't return an error to the user. + _logger.LogWarning("Project {ProjectId} was created, but a post-creation side-effect (caching/notification) failed. : {Error}", project.Id, ex.Message); + } - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); - - return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Success.", 200)); + // 5. Return a success response to the user as soon as the critical data is saved. + return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Project created successfully.", 200)); } [HttpPut] diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 216ec6e..ae6264e 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -298,6 +298,17 @@ namespace Marco.Pms.Services.Helpers _logger.LogWarning("Error occured while deleting projectIds from Cache for Application Role {RoleId}: {Error}", roleId, ex.Message); } } + public async Task ClearAllProjectIdsByPermissionId(Guid permissionId) + { + try + { + await _employeeCache.ClearAllProjectIdsByPermissionIdFromCache(permissionId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting projectIds from Cache for Permission {PermissionId}: {Error}", permissionId, ex.Message); + } + } public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId) { try From 5cb56b7a10e04f4cf51992817b0f50f708f2945c Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 11 Jul 2025 11:06:29 +0530 Subject: [PATCH 42/42] Sovled the rebase code errors --- Marco.Pms.Services/Controllers/TaskController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Marco.Pms.Services/Controllers/TaskController.cs b/Marco.Pms.Services/Controllers/TaskController.cs index 40d31f8..b764f00 100644 --- a/Marco.Pms.Services/Controllers/TaskController.cs +++ b/Marco.Pms.Services/Controllers/TaskController.cs @@ -31,6 +31,7 @@ namespace MarcoBMS.Services.Controllers private readonly S3UploadService _s3Service; private readonly ILoggingService _logger; private readonly IHubContext _signalR; + private readonly CacheUpdateHelper _cache; private readonly PermissionServices _permissionServices; public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices, @@ -41,6 +42,7 @@ namespace MarcoBMS.Services.Controllers _s3Service = s3Service; _logger = logger; _signalR = signalR; + _cache = cache; _permissionServices = permissionServices; }