using AutoMapper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Helpers; using Marco.Pms.Helpers.CacheHelper; using Marco.Pms.Model.Expenses; using Marco.Pms.Model.Master; using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.MongoDBModels.Expenses; using Marco.Pms.Model.MongoDBModels.Masters; using Marco.Pms.Model.MongoDBModels.Project; using Marco.Pms.Model.MongoDBModels.Utility; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using MarcoBMS.Services.Service; using Microsoft.EntityFrameworkCore; using MongoDB.Driver; using Project = Marco.Pms.Model.Projects.Project; namespace Marco.Pms.Services.Helpers { public class CacheUpdateHelper { private readonly IDbContextFactory _dbContextFactory; private readonly ApplicationDbContext _context; private readonly IMapper _mapper; private readonly ProjectCache _projectCache; private readonly EmployeeCache _employeeCache; private readonly ReportCache _reportCache; private readonly ExpenseCache _expenseCache; private readonly ILoggingService _logger; private readonly GeneralHelper _generalHelper; private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8"); private static readonly Guid Rejected = Guid.Parse("d1ee5eec-24b6-4364-8673-a8f859c60729"); public CacheUpdateHelper( IMapper mapper, ProjectCache projectCache, EmployeeCache employeeCache, ReportCache reportCache, ExpenseCache expenseCache, ILoggingService logger, IDbContextFactory dbContextFactory, ApplicationDbContext context, GeneralHelper generalHelper) { _mapper = mapper; _projectCache = projectCache; _employeeCache = employeeCache; _reportCache = reportCache; _expenseCache = expenseCache; _logger = logger; _dbContextFactory = dbContextFactory; _context = context; _generalHelper = generalHelper; } #region ======================================================= Project Details Cache ======================================================= public async Task AddProjectDetails(Project project) { // --- Step 1: Fetch all required data from the database in parallel --- // Each task uses its own DbContext instance to avoid concurrency issues. var statusTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); return await context.StatusMasters .AsNoTracking() .Where(s => s.Id == project.ProjectStatusId) .Select(s => new { s.Id, s.Status }) // Projection .FirstOrDefaultAsync(); }); var teamSizeTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); return await context.ProjectAllocations .AsNoTracking() .CountAsync(pa => pa.ProjectId == project.Id && pa.IsActive); // Server-side count is efficient }); // This task fetches the entire infrastructure hierarchy and performs aggregations in the database. var infrastructureTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); // 1. Fetch all hierarchical data using projections. // This is still a chain, but it's inside one task and much faster due to projections. var buildings = await context.Buildings.AsNoTracking() .Where(b => b.ProjectId == project.Id) .Select(b => new { b.Id, b.ProjectId, b.Name, b.Description }) .ToListAsync(); var buildingIds = buildings.Select(b => b.Id).ToList(); var floors = await context.Floor.AsNoTracking() .Where(f => buildingIds.Contains(f.BuildingId)) .Select(f => new { f.Id, f.BuildingId, f.FloorName }) .ToListAsync(); var floorIds = floors.Select(f => f.Id).ToList(); var workAreas = await context.WorkAreas.AsNoTracking() .Where(wa => floorIds.Contains(wa.FloorId)) .Select(wa => new { wa.Id, wa.FloorId, wa.AreaName }) .ToListAsync(); var workAreaIds = workAreas.Select(wa => wa.Id).ToList(); // 2. THE KEY OPTIMIZATION: Aggregate work items in the database. var workSummaries = await context.WorkItems.AsNoTracking() .Where(wi => workAreaIds.Contains(wi.WorkAreaId)) .GroupBy(wi => wi.WorkAreaId) // Group by parent on the DB server .Select(g => new // Let the DB do the SUM { WorkAreaId = g.Key, PlannedWork = g.Sum(i => i.PlannedWork), CompletedWork = g.Sum(i => i.CompletedWork) }) .ToDictionaryAsync(x => x.WorkAreaId); // Return a ready-to-use dictionary return (buildings, floors, workAreas, workSummaries); }); // Wait for all parallel database operations to complete. await Task.WhenAll(statusTask, teamSizeTask, infrastructureTask); // Get the results from the completed tasks. var status = await statusTask; var teamSize = await teamSizeTask; var (allBuildings, allFloors, allWorkAreas, workSummariesByWorkAreaId) = await infrastructureTask; // --- Step 2: Process the fetched data and build the MongoDB model --- 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, TeamSize = teamSize }; projectDetails.ProjectStatus = new StatusMasterMongoDB { Id = status!.Id.ToString(), Status = status.Status }; // Use fast in-memory lookups instead of .Where() in loops. var floorsByBuildingId = allFloors.ToLookup(f => f.BuildingId); var workAreasByFloorId = allWorkAreas.ToLookup(wa => wa.FloorId); double totalPlannedWork = 0, totalCompletedWork = 0; var buildingMongoList = new List(); foreach (var building in allBuildings) { double buildingPlanned = 0, buildingCompleted = 0; var floorMongoList = new List(); foreach (var floor in floorsByBuildingId[building.Id]) // Fast lookup { double floorPlanned = 0, floorCompleted = 0; var workAreaMongoList = new List(); foreach (var wa in workAreasByFloorId[floor.Id]) // Fast lookup { // Get the pre-calculated summary from the dictionary. O(1) operation. workSummariesByWorkAreaId.TryGetValue(wa.Id, out var summary); var waPlanned = summary?.PlannedWork ?? 0; var waCompleted = summary?.CompletedWork ?? 0; workAreaMongoList.Add(new WorkAreaMongoDB { Id = wa.Id.ToString(), FloorId = wa.FloorId.ToString(), AreaName = wa.AreaName, PlannedWork = waPlanned, CompletedWork = waCompleted }); floorPlanned += waPlanned; floorCompleted += waCompleted; } floorMongoList.Add(new FloorMongoDB { Id = floor.Id.ToString(), BuildingId = floor.BuildingId.ToString(), FloorName = floor.FloorName, PlannedWork = floorPlanned, CompletedWork = floorCompleted, WorkAreas = workAreaMongoList }); buildingPlanned += floorPlanned; buildingCompleted += floorCompleted; } buildingMongoList.Add(new BuildingMongoDB { Id = building.Id.ToString(), ProjectId = building.ProjectId.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; try { await _projectCache.AddProjectDetailsToCache(projectDetails); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while adding project {ProjectId} to Cache", project.Id); } } public async Task AddProjectDetailsList(List projects) { var projectIds = projects.Select(p => p.Id).ToList(); if (!projectIds.Any()) { return; // Nothing to do } var projectStatusIds = projects.Select(p => p.ProjectStatusId).Distinct().ToList(); // --- Step 1: Fetch all required data in maximum parallel --- // Each task uses its own DbContext and selects only the required columns (projection). var statusTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); return await context.StatusMasters .AsNoTracking() .Where(s => projectStatusIds.Contains(s.Id)) .Select(s => new { s.Id, s.Status }) // Projection .ToDictionaryAsync(s => s.Id); }); var teamSizeTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); // Server-side aggregation and projection into a dictionary return await context.ProjectAllocations .AsNoTracking() .Where(pa => projectIds.Contains(pa.ProjectId) && pa.IsActive) .GroupBy(pa => pa.ProjectId) .Select(g => new { ProjectId = g.Key, Count = g.Count() }) .ToDictionaryAsync(x => x.ProjectId, x => x.Count); }); var buildingsTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); return await context.Buildings .AsNoTracking() .Where(b => projectIds.Contains(b.ProjectId)) .Select(b => new { b.Id, b.ProjectId, b.Name, b.Description }) // Projection .ToListAsync(); }); // We need the building IDs for the next level, so we must await this one first. var allBuildings = await buildingsTask; var buildingIds = allBuildings.Select(b => b.Id).ToList(); var floorsTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); return await context.Floor .AsNoTracking() .Where(f => buildingIds.Contains(f.BuildingId)) .Select(f => new { f.Id, f.BuildingId, f.FloorName }) // Projection .ToListAsync(); }); // We need floor IDs for the next level. var allFloors = await floorsTask; var floorIds = allFloors.Select(f => f.Id).ToList(); var workAreasTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); return await context.WorkAreas .AsNoTracking() .Where(wa => floorIds.Contains(wa.FloorId)) .Select(wa => new { wa.Id, wa.FloorId, wa.AreaName }) // Projection .ToListAsync(); }); // The most powerful optimization: Aggregate work items in the database. var workSummaryTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); var workAreaIds = await context.WorkAreas .Where(wa => floorIds.Contains(wa.FloorId)) .Select(wa => wa.Id) .ToListAsync(); // Let the DB do the SUM. This is much faster and transfers less data. return await context.WorkItems .AsNoTracking() .Where(wi => workAreaIds.Contains(wi.WorkAreaId)) .GroupBy(wi => wi.WorkAreaId) .Select(g => new { WorkAreaId = g.Key, PlannedWork = g.Sum(wi => wi.PlannedWork), CompletedWork = g.Sum(wi => wi.CompletedWork) }) .ToDictionaryAsync(x => x.WorkAreaId); }); // Await the remaining parallel tasks. await Task.WhenAll(statusTask, teamSizeTask, workAreasTask, workSummaryTask); // --- Step 2: Process the fetched data and build the MongoDB models --- var allStatuses = await statusTask; var teamSizesByProjectId = await teamSizeTask; var allWorkAreas = await workAreasTask; var workSummariesByWorkAreaId = await workSummaryTask; // Create fast in-memory lookups for hierarchical data var buildingsByProjectId = allBuildings.ToLookup(b => b.ProjectId); var floorsByBuildingId = allFloors.ToLookup(f => f.BuildingId); var workAreasByFloorId = allWorkAreas.ToLookup(wa => wa.FloorId); var projectDetailsList = new List(projects.Count); foreach (var project in projects) { 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, TeamSize = teamSizesByProjectId.GetValueOrDefault(project.Id, 0) }; if (allStatuses.TryGetValue(project.ProjectStatusId, out var status)) { projectDetails.ProjectStatus = new StatusMasterMongoDB { Id = status.Id.ToString(), Status = status.Status }; } double totalPlannedWork = 0, totalCompletedWork = 0; var buildingMongoList = new List(); foreach (var building in buildingsByProjectId[project.Id]) { double buildingPlanned = 0, buildingCompleted = 0; var floorMongoList = new List(); foreach (var floor in floorsByBuildingId[building.Id]) { double floorPlanned = 0, floorCompleted = 0; var workAreaMongoList = new List(); foreach (var wa in workAreasByFloorId[floor.Id]) { double waPlanned = 0, waCompleted = 0; if (workSummariesByWorkAreaId.TryGetValue(wa.Id, out var summary)) { waPlanned = summary.PlannedWork; waCompleted = summary.CompletedWork; } workAreaMongoList.Add(new WorkAreaMongoDB { Id = wa.Id.ToString(), FloorId = wa.FloorId.ToString(), AreaName = wa.AreaName, PlannedWork = waPlanned, CompletedWork = waCompleted }); floorPlanned += waPlanned; floorCompleted += waCompleted; } floorMongoList.Add(new FloorMongoDB { Id = floor.Id.ToString(), BuildingId = floor.BuildingId.ToString(), FloorName = floor.FloorName, PlannedWork = floorPlanned, CompletedWork = floorCompleted, WorkAreas = workAreaMongoList }); buildingPlanned += floorPlanned; buildingCompleted += floorCompleted; } buildingMongoList.Add(new BuildingMongoDB { Id = building.Id.ToString(), ProjectId = building.ProjectId.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; projectDetailsList.Add(projectDetails); } // --- Step 3: Update the cache --- try { await _projectCache.AddProjectDetailsListToCache(projectDetailsList); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while adding project list to Cache"); } } public async Task UpdateProjectDetailsOnly(Project project) { StatusMaster projectStatus = await _context.StatusMasters .FirstOrDefaultAsync(s => s.Id == project.ProjectStatusId) ?? new StatusMaster(); try { bool response = await _projectCache.UpdateProjectDetailsOnlyToCache(project, projectStatus); return response; } catch (Exception ex) { _logger.LogError(ex, "Error occured while updating project {ProjectId} to Cache", project.Id); return false; } } public async Task GetProjectDetails(Guid projectId) { try { var response = await _projectCache.GetProjectDetailsFromCache(projectId); return response; } catch (Exception ex) { _logger.LogError(ex, "Error occured while getting project {ProjectId} to Cache"); return null; } } public async Task GetProjectDetailsWithBuildings(Guid projectId) { try { var response = await _projectCache.GetProjectDetailsWithBuildingsFromCache(projectId); return response; } catch (Exception ex) { _logger.LogError(ex, "Error occured while getting project {ProjectId} to Cache"); return null; } } public async Task?> GetProjectDetailsList(List projectIds) { try { var response = await _projectCache.GetProjectDetailsListFromCache(projectIds); if (response.Any()) { return response; } else { return null; } } catch (Exception ex) { _logger.LogError(ex, "Error occured while getting list of project details from to Cache"); return null; } } public async Task DeleteProjectByIdAsync(Guid projectId) { try { var response = await _projectCache.DeleteProjectByIdFromCacheAsync(projectId); } catch (Exception ex) { _logger.LogError(ex, "Error occured while deleting project from to Cache"); } } public async Task RemoveProjectsAsync(List projectIds) { try { var response = await _projectCache.RemoveProjectsFromCacheAsync(projectIds); } catch (Exception ex) { _logger.LogError(ex, "Error occured while deleting project list from to Cache"); } } #endregion #region ======================================================= Project Infrastructure Cache ======================================================= public async Task AddBuildngInfra(Guid projectId, Building? building = null, Floor? floor = null, WorkArea? workArea = null, Guid? buildingId = null) { try { await _projectCache.AddBuildngInfraToCache(projectId, building, floor, workArea, buildingId); } catch (Exception ex) { _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) { try { 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 for project {ProjectId} to Cache: {Error}", projectId, ex.Message); } } public async Task?> GetBuildingInfra(Guid projectId) { try { var response = await _projectCache.GetBuildingInfraFromCache(projectId); return response; } catch (Exception ex) { _logger.LogWarning("Error occured while getting project infra for project {ProjectId} form Cache: {Error}", projectId, ex.Message); 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); } } 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; } } #endregion #region ======================================================= WorkItem Cache ======================================================= 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; } } public async Task ManageWorkItemDetails(List workItems) { try { var workAreaId = workItems.First().WorkAreaId; var workItemDB = await _generalHelper.GetWorkItemsListFromDB(workAreaId); await _projectCache.ManageWorkItemDetailsToCache(workItemDB); } catch (Exception ex) { _logger.LogWarning("Error occured while saving workItems form Cache: {Error}", ex.Message); } } public async Task ManageWorkItemDetailsByVM(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; } } 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); } } public async Task DeleteWorkItemByIdAsync(Guid workItemId) { try { var response = await _projectCache.DeleteWorkItemByIdFromCacheAsync(workItemId); } catch (Exception ex) { _logger.LogWarning("Error occured while deleting work item from to Cache: {Error}", ex.Message); } } #endregion #region ======================================================= Employee Profile Cache ======================================================= public async Task AddApplicationRole(Guid employeeId, List roleIds) { // 1. Guard Clause: Avoid unnecessary database work if there are no roles to add. if (roleIds == null || !roleIds.Any()) { return; // Nothing to add, so the operation did not result in a change. } Task> getPermissionIdsTask = Task.Run(async () => { using var context = _dbContextFactory.CreateDbContext(); return await context.RolePermissionMappings .Where(rp => roleIds.Contains(rp.ApplicationRoleId)) .Select(p => p.FeaturePermissionId.ToString()) .Distinct() .ToListAsync(); }); // 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; try { var response = await _employeeCache.AddApplicationRoleToCache(employeeId, newRoleIds, newPermissionIds); } 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) { try { var response = await _employeeCache.AddProjectsToCache(employeeId, projectIds); return response; } catch (Exception ex) { _logger.LogWarning("Error occured while adding projectIds for employee {EmployeeId} to Cache: {Error}", employeeId, ex.Message); return false; } } public async Task?> GetProjects(Guid employeeId) { try { var response = await _employeeCache.GetProjectsFromCache(employeeId); if (response.Count > 0) { return response; } return null; } catch (Exception ex) { _logger.LogWarning("Error occured while getting projectIds for employee {EmployeeId} from Cache: {Error}", employeeId, ex.Message); return null; } } public async Task?> GetPermissions(Guid employeeId) { try { var response = await _employeeCache.GetPermissionsFromCache(employeeId); if (response.Count > 0) { return response; } return null; } catch (Exception ex) { _logger.LogWarning("Error occured while getting permissionIds for employee {EmployeeId} from Cache: {Error}", employeeId, ex.Message); return null; } } public async Task ClearAllProjectIds(Guid 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) { 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 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 { var response = await _employeeCache.ClearAllPermissionIdsByEmployeeIDFromCache(employeeId); } catch (Exception ex) { _logger.LogWarning("Error occured while deleting permissionIds from Cache for employee {EmployeeId}: {Error}", employeeId, ex.Message); } } public async Task ClearAllPermissionIdsByRoleId(Guid roleId) { try { var response = await _employeeCache.ClearAllPermissionIdsByRoleIdFromCache(roleId); } catch (Exception ex) { _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) { try { var response = await _employeeCache.RemoveRoleIdFromCache(employeeId, roleId); } catch (Exception ex) { _logger.LogWarning("Error occured while deleting Application role {RoleId} from Cache for employee {EmployeeId}: {Error}", roleId, employeeId, ex.Message); } } public async Task ClearAllEmployees() { try { var response = await _employeeCache.ClearAllEmployeesFromCache(); } catch (Exception ex) { _logger.LogError(ex, "Error occured while deleting all employees from Cache"); } } #endregion #region ======================================================= Expenses Cache ======================================================= public async Task AddExpenseByObjectAsync(Expenses expense) { var expenseCache = _mapper.Map(expense); try { var billAttachment = await _context.BillAttachments .Include(ba => ba.Document) .AsNoTracking() .Where(ba => ba.ExpensesId == expense.Id && ba.Document != null) .GroupBy(ba => ba.ExpensesId) .Select(g => new { Documents = g.Select(ba => new DocumentMongoDB { DocumentId = ba.Document!.Id.ToString(), FileName = ba.Document.FileName, ContentType = ba.Document.ContentType, S3Key = ba.Document.S3Key, ThumbS3Key = ba.Document.ThumbS3Key ?? ba.Document.S3Key }).ToList() }) .FirstOrDefaultAsync(); ; if (billAttachment != null) { expenseCache.Documents = billAttachment.Documents; } } catch (Exception ex) { _logger.LogError(ex, "Error occurd while fetched expense related tables to save in cahce"); } try { await _expenseCache.AddExpenseToCacheAsync(expenseCache); } catch (Exception ex) { _logger.LogError(ex, "Error occurd while storing expense related table in cahce"); return; } return; } public async Task AddExpenseByIdAsync(Guid Id, Guid tenantId) { var expense = await _context.Expenses.AsNoTracking().FirstOrDefaultAsync(e => e.Id == Id && e.TenantId == tenantId); var expenseCache = _mapper.Map(expense); if (expense == null) { return null; } try { var billAttachments = await _context.BillAttachments .Include(ba => ba.Document) .AsNoTracking() .Where(ba => ba.ExpensesId == expense.Id && ba.Document != null) .GroupBy(ba => ba.ExpensesId) .Select(g => new { Documents = g.Select(ba => new DocumentMongoDB { DocumentId = ba.Document!.Id.ToString(), FileName = ba.Document.FileName, ContentType = ba.Document.ContentType, S3Key = ba.Document.S3Key, ThumbS3Key = ba.Document.ThumbS3Key ?? ba.Document.S3Key }).ToList() }) .FirstOrDefaultAsync(); if (billAttachments != null) { expenseCache.Documents = billAttachments.Documents; } } catch (Exception ex) { _logger.LogError(ex, "Error occurd while fetched expense related tables to save in cahce"); return null; } try { await _expenseCache.AddExpenseToCacheAsync(expenseCache); } catch (Exception ex) { _logger.LogError(ex, "Error occurd while storing expense related table in cahce"); return null; } return expenseCache; } public async Task AddExpensesListToCache(List expenses) { var expensesCache = _mapper.Map>(expenses); var expenseIds = expenses.Select(e => e.Id).ToList(); try { var billAttachments = await _context.BillAttachments .Include(ba => ba.Document) .AsNoTracking() .Where(ba => expenseIds.Contains(ba.ExpensesId) && ba.Document != null) .GroupBy(ba => ba.ExpensesId) .Select(g => new { ExpensesId = g.Key, Documents = g.Select(ba => new DocumentMongoDB { DocumentId = ba.Document!.Id.ToString(), FileName = ba.Document.FileName, ContentType = ba.Document.ContentType, S3Key = ba.Document.S3Key, ThumbS3Key = ba.Document.ThumbS3Key ?? ba.Document.S3Key }).ToList() }) .ToListAsync(); foreach (var expenseCache in expensesCache) { expenseCache.Documents = billAttachments.Where(ba => ba.ExpensesId == Guid.Parse(expenseCache.Id)).Select(ba => ba.Documents).FirstOrDefault() ?? new List(); } } catch (Exception ex) { _logger.LogError(ex, "Error occurd while fetched expense related tables to save in cahce"); } try { await _expenseCache.AddExpensesListToCacheAsync(expensesCache); } catch (Exception ex) { _logger.LogError(ex, "Error occured while saving the list of expenses to Cache"); } } public async Task<(int totalPages, long totalCount, List? expenseList)> GetExpenseListAsync(Guid tenantId, Guid loggedInEmployeeId, bool viewAll, bool viewSelf, int pageNumber, int pageSize, ExpensesFilter? filter) { try { var (totalPages, totalCount, expenseList) = await _expenseCache.GetExpenseListFromCacheAsync(tenantId, loggedInEmployeeId, viewAll, viewSelf, pageNumber, pageSize, filter); if (expenseList.Any()) { return (totalPages, totalCount, expenseList); } } catch (Exception ex) { _logger.LogError(ex, "Error occured while fetching the list of expenses to Cache"); } return (0, 0, null); } public async Task GetExpenseDetailsById(Guid id, Guid tenantId) { try { var response = await _expenseCache.GetExpenseDetailsByIdAsync(id, tenantId); if (response != null && response.Id != string.Empty) { return response; } } catch (Exception ex) { _logger.LogError(ex, "Error occured while fetching expense details from cache"); } return null; } public async Task ReplaceExpenseAsync(Expenses expense) { bool response = false; try { response = await _expenseCache.DeleteExpenseFromCacheAsync(expense.Id, expense.TenantId); } catch (Exception ex) { _logger.LogError(ex, "Error occured while deleting expense from cache"); } if (response) { await AddExpenseByObjectAsync(expense); } } public async Task DeleteExpenseAsync(Guid id, Guid tenantId) { try { var response = await _expenseCache.DeleteExpenseFromCacheAsync(id, tenantId); } catch (Exception ex) { _logger.LogError(ex, "Error occured while deleting expense from cache"); } } #endregion #region ======================================================= Report Cache ======================================================= public async Task?> GetProjectReportMail(bool IsSend) { try { var response = await _reportCache.GetProjectReportMailFromCache(IsSend); return response; } catch (Exception ex) { _logger.LogError(ex, "Error occured while fetching project report mail bodys"); return null; } } public async Task AddProjectReportMail(ProjectReportEmailMongoDB report) { try { await _reportCache.AddProjectReportMailToCache(report); } catch (Exception ex) { _logger.LogError(ex, "Error occured while adding project report mail bodys"); } } #endregion } }