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 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 var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name _projetCollection = mongoDB.GetCollection("ProjectDetails"); _taskCollection = mongoDB.GetCollection("WorkItemDetails"); } public async Task AddProjectDetailsToCache(Project project) { //_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 _projetCollection.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); } // 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 _projetCollection.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) { // 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 _projetCollection .Find(filter) .Project(projection) .FirstOrDefaultAsync(); if (project == null) { //_logger.LogWarning("No project found in MongoDB for ProjectId: {ProjectId}", projectId); return null; } //_logger.LogInfo("Successfully fetched project details (excluding Buildings) for ProjectId: {ProjectId}", projectId); return project; } public async Task?> GetProjectDetailsListFromCache(List projectIds) { 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(); // 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 _projetCollection.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 _projetCollection.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 _projetCollection.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(); // 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 _projetCollection.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 _projetCollection.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 _projetCollection.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) { // 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 _projetCollection .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; } // ------------------------------------------------------- WorkItem ------------------------------------------------------- } }