From ef84ba34da9354602d1291e0571572c5837a8e84 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Sun, 21 Sep 2025 18:10:25 +0530 Subject: [PATCH] Addedd the tenant ID in employee profile cache --- .../CacheHelper/EmployeeCache.cs | 48 +++- .../Employees/EmployeePermissionMongoDB.cs | 1 + .../Controllers/MarketController.cs | 61 +++++ .../Controllers/RolesController.cs | 12 +- .../Controllers/TenantController.cs | 2 +- .../Controllers/UserController.cs | 8 +- .../Helpers/CacheUpdateHelper.cs | 36 +-- Marco.Pms.Services/Helpers/ReportHelper.cs | 256 ++++++++++++++++++ Marco.Pms.Services/Helpers/RolesHelper.cs | 4 +- .../Service/PermissionServices.cs | 17 +- Marco.Pms.Services/Service/ProjectServices.cs | 18 +- 11 files changed, 407 insertions(+), 56 deletions(-) diff --git a/Marco.Pms.Helpers/CacheHelper/EmployeeCache.cs b/Marco.Pms.Helpers/CacheHelper/EmployeeCache.cs index 0afbb67..a93eaf3 100644 --- a/Marco.Pms.Helpers/CacheHelper/EmployeeCache.cs +++ b/Marco.Pms.Helpers/CacheHelper/EmployeeCache.cs @@ -20,18 +20,21 @@ namespace Marco.Pms.Helpers.CacheHelper var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name _collection = mongoDB.GetCollection("EmployeeProfile"); } - public async Task AddApplicationRoleToCache(Guid employeeId, List newRoleIds, List newPermissionIds) + public async Task AddApplicationRoleToCache(Guid employeeId, List newRoleIds, List newPermissionIds, Guid tenantId) { // 2. Perform database queries concurrently for better performance. var employeeIdString = employeeId.ToString(); + var tenantIdString = tenantId.ToString(); // 5. Build a single, efficient update operation. var filter = Builders.Filter.Eq(e => e.Id, employeeIdString); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); var update = Builders.Update .AddToSetEach(e => e.ApplicationRoleIds, newRoleIds) .Set(r => r.ExpireAt, DateTime.UtcNow.Date.AddDays(1)) + .Set(r => r.TenantId, tenantIdString) .AddToSetEach(e => e.PermissionIds, newPermissionIds); var options = new UpdateOptions { IsUpsert = true }; @@ -44,14 +47,17 @@ namespace Marco.Pms.Helpers.CacheHelper // 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) + public async Task AddProjectsToCache(Guid employeeId, List projectIds, Guid tenantId) { var newprojectIds = projectIds.Select(p => p.ToString()).ToList(); + var tenantIdString = tenantId.ToString(); var filter = Builders.Filter.Eq(e => e.Id, employeeId.ToString()); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); var update = Builders.Update .Set(r => r.ExpireAt, DateTime.UtcNow.Date.AddDays(1)) + .Set(r => r.TenantId, tenantIdString) .AddToSetEach(e => e.ProjectIds, newprojectIds); var result = await _collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); @@ -62,10 +68,12 @@ namespace Marco.Pms.Helpers.CacheHelper await InitializeCollectionAsync(); return true; } - public async Task> GetProjectsFromCache(Guid employeeId) + public async Task> GetProjectsFromCache(Guid employeeId, Guid tenantId) { - var filter = Builders.Filter.Eq(e => e.Id, employeeId.ToString()); + var tenantIdString = tenantId.ToString(); + var filter = Builders.Filter.Eq(e => e.Id, employeeId.ToString()); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); var result = await _collection .Find(filter) @@ -79,10 +87,12 @@ namespace Marco.Pms.Helpers.CacheHelper return projectIds; } - public async Task> GetPermissionsFromCache(Guid employeeId) + public async Task> GetPermissionsFromCache(Guid employeeId, Guid tenantId) { - var filter = Builders.Filter.Eq(e => e.Id, employeeId.ToString()); + var tenantIdString = tenantId.ToString(); + var filter = Builders.Filter.Eq(e => e.Id, employeeId.ToString()); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); var result = await _collection .Find(filter) @@ -96,10 +106,13 @@ namespace Marco.Pms.Helpers.CacheHelper return permissionIds; } - public async Task ClearAllProjectIdsFromCache(Guid employeeId) + public async Task ClearAllProjectIdsFromCache(Guid employeeId, Guid tenantId) { + var tenantIdString = tenantId.ToString(); + var filter = Builders.Filter .Eq(e => e.Id, employeeId.ToString()); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); var update = Builders.Update .Set(e => e.ProjectIds, new List()); @@ -125,18 +138,25 @@ namespace Marco.Pms.Helpers.CacheHelper return true; } - public async Task ClearAllProjectIdsByPermissionIdFromCache(Guid permissionId) + public async Task ClearAllProjectIdsByPermissionIdFromCache(Guid permissionId, Guid tenantId) { + var tenantIdString = tenantId.ToString(); + var filter = Builders.Filter.AnyEq(e => e.PermissionIds, permissionId.ToString()); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); + var update = Builders.Update.Set(e => e.ProjectIds, new List()); var result = await _collection.UpdateManyAsync(filter, update).ConfigureAwait(false); return result.IsAcknowledged && result.ModifiedCount > 0; } - public async Task RemoveRoleIdFromCache(Guid employeeId, Guid roleId) + public async Task RemoveRoleIdFromCache(Guid employeeId, Guid roleId, Guid tenantId) { + var tenantIdString = tenantId.ToString(); + var filter = Builders.Filter .Eq(e => e.Id, employeeId.ToString()); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); var update = Builders.Update .Pull(e => e.ApplicationRoleIds, roleId.ToString()); @@ -151,10 +171,13 @@ namespace Marco.Pms.Helpers.CacheHelper return true; } - public async Task ClearAllPermissionIdsByEmployeeIDFromCache(Guid employeeId) + public async Task ClearAllPermissionIdsByEmployeeIDFromCache(Guid employeeId, Guid tenantId) { + var tenantIdString = tenantId.ToString(); + var filter = Builders.Filter .Eq(e => e.Id, employeeId.ToString()); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); var update = Builders.Update .Set(e => e.PermissionIds, new List()); @@ -189,11 +212,14 @@ namespace Marco.Pms.Helpers.CacheHelper return true; } - public async Task ClearAllEmployeesFromCacheByEmployeeIds(List employeeIds) + public async Task ClearAllEmployeesFromCacheByEmployeeIds(List employeeIds, Guid tenantId) { + var tenantIdString = tenantId.ToString(); + try { var filter = Builders.Filter.In(x => x.Id, employeeIds); + filter &= Builders.Filter.Eq(e => e.TenantId, tenantIdString); var result = await _collection.DeleteManyAsync(filter); diff --git a/Marco.Pms.Model/MongoDBModels/Employees/EmployeePermissionMongoDB.cs b/Marco.Pms.Model/MongoDBModels/Employees/EmployeePermissionMongoDB.cs index 07da2ef..9ff8faa 100644 --- a/Marco.Pms.Model/MongoDBModels/Employees/EmployeePermissionMongoDB.cs +++ b/Marco.Pms.Model/MongoDBModels/Employees/EmployeePermissionMongoDB.cs @@ -10,5 +10,6 @@ namespace Marco.Pms.Model.MongoDBModels.Employees public List PermissionIds { get; set; } = new List(); public List ProjectIds { get; set; } = new List(); public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1); + public string TenantId { get; set; } = string.Empty; // Tenant ID } } diff --git a/Marco.Pms.Services/Controllers/MarketController.cs b/Marco.Pms.Services/Controllers/MarketController.cs index c9e1923..484039a 100644 --- a/Marco.Pms.Services/Controllers/MarketController.cs +++ b/Marco.Pms.Services/Controllers/MarketController.cs @@ -7,6 +7,7 @@ using Marco.Pms.Model.Master; using Marco.Pms.Model.TenantModels; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Tenant; +using Marco.Pms.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -130,5 +131,65 @@ namespace Marco.Pms.Services.Controllers return StatusCode(500, ApiResponse.ErrorResponse("An error occurred while fetching subscription plans.")); } } + + [HttpGet("get/project/report/{projectId}")] + public async Task GetProjectReport(Guid projectId) + { + using var scope = _serviceScopeFactory.CreateScope(); + var _reportHelper = scope.ServiceProvider.GetRequiredService(); + var _logger = scope.ServiceProvider.GetRequiredService(); + + var resonse = await _reportHelper.GetDailyProjectReportWithOutTenant(projectId); + + if (resonse == null) + { + _logger.LogWarning("Project report not found"); + return NotFound(ApiResponse.ErrorResponse("Project report not found", "Project report not found", 404)); + } + _logger.LogInfo("Report for the project fetched successfully"); + return Ok(ApiResponse.SuccessResponse(resonse, "Report for the project fetched successfully", 200)); + } + + /// + /// Retrieves a daily project report by its unique identifier. + /// + /// The GUID of the project for which to generate the report. + /// An IActionResult containing the project report or an appropriate error response. + [HttpGet("{projectId}/report")] + public async Task GetProjectReportAsync(Guid projectId) + { + using var scope = _serviceScopeFactory.CreateScope(); + var _reportHelper = scope.ServiceProvider.GetRequiredService(); + var _logger = scope.ServiceProvider.GetRequiredService(); + + // Use structured logging to include the projectId for better traceability. + _logger.LogInfo("Attempting to fetch report for ProjectId: {ProjectId}", projectId); + + try + { + // Call the helper service, which is now available as a class member. + var response = await _reportHelper.GetDailyProjectReportWithOutTenant(projectId); + + // Check if the report data was found. + if (response == null) + { + _logger.LogWarning("Project report not found for ProjectId: {ProjectId}", projectId); + return NotFound(ApiResponse.ErrorResponse("Project report not found.", 404)); + } + + // Log success and return the report. + _logger.LogInfo("Successfully fetched report for ProjectId: {ProjectId}", projectId); + return Ok(ApiResponse.SuccessResponse(response, "Report for the project fetched successfully.", 200)); + } + catch (Exception ex) + { + // Log the full exception if anything goes wrong during the process. + _logger.LogError(ex, "An error occurred while generating the report for ProjectId: {ProjectId}", projectId); + + // Return a standardized 500 Internal Server Error response to the client. + return StatusCode(500, ApiResponse.ErrorResponse("An internal server error occurred while processing the report.", 500)); + } + } + } } diff --git a/Marco.Pms.Services/Controllers/RolesController.cs b/Marco.Pms.Services/Controllers/RolesController.cs index 54b826a..b0db8ab 100644 --- a/Marco.Pms.Services/Controllers/RolesController.cs +++ b/Marco.Pms.Services/Controllers/RolesController.cs @@ -424,7 +424,7 @@ namespace MarcoBMS.Services.Controllers var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync(); var employees = await _context.Employees.Where(e => employeesIds.Contains(e.Id)).ToListAsync(); - Guid TenantId = GetTenantId(); + Guid tenantId = GetTenantId(); try { foreach (EmployeeRoleDot role in employeeRoleDots) @@ -435,7 +435,7 @@ namespace MarcoBMS.Services.Controllers _logger.LogWarning("Employee with ID {LoggedEmployeeId} tries to assign or remove the application role to System-defined employee with ID {EmployeeId}", LoggedEmployee.Id, employee.Id); return BadRequest(ApiResponse.ErrorResponse("System-defined employee cannot have application roles assigned or removed.", "System-defined employee cannot have application roles assigned or removed.", 400)); } - EmployeeRoleMapping mapping = role.ToEmployeeRoleMappingFromEmployeeRoleDot(TenantId); + EmployeeRoleMapping mapping = role.ToEmployeeRoleMappingFromEmployeeRoleDot(tenantId); var existingItem = await _context.EmployeeRoleMappings.AsNoTracking().SingleOrDefaultAsync(c => c.Id == mapping.Id); @@ -444,16 +444,16 @@ namespace MarcoBMS.Services.Controllers if (role.IsEnabled == true) { _context.EmployeeRoleMappings.Add(mapping); - await _cache.AddApplicationRole(role.EmployeeId, [mapping.RoleId]); + await _cache.AddApplicationRole(role.EmployeeId, [mapping.RoleId], tenantId); } } else if (role.IsEnabled == false) { _context.EmployeeRoleMappings.Remove(existingItem); - await _cache.RemoveRoleId(existingItem.EmployeeId, existingItem.RoleId); - await _cache.ClearAllPermissionIdsByEmployeeID(existingItem.EmployeeId); + await _cache.RemoveRoleId(existingItem.EmployeeId, existingItem.RoleId, tenantId); + await _cache.ClearAllPermissionIdsByEmployeeID(existingItem.EmployeeId, tenantId); } - await _cache.ClearAllProjectIds(role.EmployeeId); + await _cache.ClearAllProjectIds(role.EmployeeId, tenantId); } await _context.SaveChangesAsync(); diff --git a/Marco.Pms.Services/Controllers/TenantController.cs b/Marco.Pms.Services/Controllers/TenantController.cs index 6b7bfcb..aca5fd5 100644 --- a/Marco.Pms.Services/Controllers/TenantController.cs +++ b/Marco.Pms.Services/Controllers/TenantController.cs @@ -1776,7 +1776,7 @@ namespace Marco.Pms.Services.Controllers var _cacheLogger = scope.ServiceProvider.GetRequiredService(); var employeeIds = await _context.Employees.Where(e => e.TenantId == tenantId).Select(e => e.Id).ToListAsync(); - await _cache.ClearAllEmployeesFromCacheByEmployeeIds(employeeIds); + await _cache.ClearAllEmployeesFromCacheByEmployeeIds(employeeIds, tenantId); _cacheLogger.LogInfo("{EmployeeCount} number of employee deleted", employeeIds.Count); } diff --git a/Marco.Pms.Services/Controllers/UserController.cs b/Marco.Pms.Services/Controllers/UserController.cs index 9afb000..8db84a6 100644 --- a/Marco.Pms.Services/Controllers/UserController.cs +++ b/Marco.Pms.Services/Controllers/UserController.cs @@ -26,6 +26,7 @@ namespace MarcoBMS.Services.Controllers private readonly ILoggingService _logger; private readonly IProjectServices _projectServices; private readonly RolesHelper _rolesHelper; + private readonly Guid tenantId; public UserController(EmployeeHelper employeeHelper, UserManager userManager, ILoggingService logger, IProjectServices projectServices, UserHelper userHelper, RolesHelper rolesHelper) { @@ -35,8 +36,9 @@ namespace MarcoBMS.Services.Controllers _employeeHelper = employeeHelper; _projectServices = projectServices; _rolesHelper = rolesHelper; - + tenantId = userHelper.GetTenantId(); } + [HttpGet("profile")] public async Task GetUserProfileFromJwt() { @@ -57,7 +59,7 @@ namespace MarcoBMS.Services.Controllers emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id); } - List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(emp.Id); + List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(emp.Id, tenantId); string[] projectsId = []; /* User with permission manage project can see all projects */ @@ -75,6 +77,8 @@ namespace MarcoBMS.Services.Controllers if (featurePermission != null) { EmployeeVM employeeVM = EmployeeMapper.ToEmployeeVMFromEmployee(emp); + employeeVM.TenantId = tenantId; + profile = new EmployeeProfile() { EmployeeInfo = employeeVM, diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 3584c8e..8048742 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -708,7 +708,7 @@ namespace Marco.Pms.Services.Helpers #region ======================================================= Employee Profile Cache ======================================================= - public async Task AddApplicationRole(Guid employeeId, List roleIds) + public async Task AddApplicationRole(Guid employeeId, List roleIds, Guid tenantId) { // 1. Guard Clause: Avoid unnecessary database work if there are no roles to add. if (roleIds == null || !roleIds.Any()) @@ -733,18 +733,18 @@ namespace Marco.Pms.Services.Helpers var newPermissionIds = await getPermissionIdsTask; try { - var response = await _employeeCache.AddApplicationRoleToCache(employeeId, newRoleIds, newPermissionIds); + var response = await _employeeCache.AddApplicationRoleToCache(employeeId, newRoleIds, newPermissionIds, tenantId); } 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) + public async Task AddProjects(Guid employeeId, List projectIds, Guid tenantId) { try { - var response = await _employeeCache.AddProjectsToCache(employeeId, projectIds); + var response = await _employeeCache.AddProjectsToCache(employeeId, projectIds, tenantId); return response; } catch (Exception ex) @@ -753,11 +753,11 @@ namespace Marco.Pms.Services.Helpers return false; } } - public async Task?> GetProjects(Guid employeeId) + public async Task?> GetProjects(Guid employeeId, Guid tenantId) { try { - var response = await _employeeCache.GetProjectsFromCache(employeeId); + var response = await _employeeCache.GetProjectsFromCache(employeeId, tenantId); if (response.Count > 0) { return response; @@ -770,11 +770,11 @@ namespace Marco.Pms.Services.Helpers return null; } } - public async Task?> GetPermissions(Guid employeeId) + public async Task?> GetPermissions(Guid employeeId, Guid tenantId) { try { - var response = await _employeeCache.GetPermissionsFromCache(employeeId); + var response = await _employeeCache.GetPermissionsFromCache(employeeId, tenantId); if (response.Count > 0) { return response; @@ -787,11 +787,11 @@ namespace Marco.Pms.Services.Helpers return null; } } - public async Task ClearAllProjectIds(Guid employeeId) + public async Task ClearAllProjectIds(Guid employeeId, Guid tenantId) { try { - var response = await _employeeCache.ClearAllProjectIdsFromCache(employeeId); + var response = await _employeeCache.ClearAllProjectIdsFromCache(employeeId, tenantId); } catch (Exception ex) { @@ -809,22 +809,22 @@ 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) + public async Task ClearAllProjectIdsByPermissionId(Guid permissionId, Guid tenantId) { try { - await _employeeCache.ClearAllProjectIdsByPermissionIdFromCache(permissionId); + await _employeeCache.ClearAllProjectIdsByPermissionIdFromCache(permissionId, tenantId); } 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) + public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId, Guid tenantId) { try { - var response = await _employeeCache.ClearAllPermissionIdsByEmployeeIDFromCache(employeeId); + var response = await _employeeCache.ClearAllPermissionIdsByEmployeeIDFromCache(employeeId, tenantId); } catch (Exception ex) { @@ -842,23 +842,23 @@ namespace Marco.Pms.Services.Helpers _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) + public async Task RemoveRoleId(Guid employeeId, Guid roleId, Guid tenantId) { try { - var response = await _employeeCache.RemoveRoleIdFromCache(employeeId, roleId); + var response = await _employeeCache.RemoveRoleIdFromCache(employeeId, roleId, tenantId); } 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 ClearAllEmployeesFromCacheByEmployeeIds(List employeeIds) + public async Task ClearAllEmployeesFromCacheByEmployeeIds(List employeeIds, Guid tenantId) { try { var stringEmployeeIds = employeeIds.Select(e => e.ToString()).ToList(); - var response = await _employeeCache.ClearAllEmployeesFromCacheByEmployeeIds(stringEmployeeIds); + var response = await _employeeCache.ClearAllEmployeesFromCacheByEmployeeIds(stringEmployeeIds, tenantId); } catch (Exception ex) { diff --git a/Marco.Pms.Services/Helpers/ReportHelper.cs b/Marco.Pms.Services/Helpers/ReportHelper.cs index 49c3000..f36b84f 100644 --- a/Marco.Pms.Services/Helpers/ReportHelper.cs +++ b/Marco.Pms.Services/Helpers/ReportHelper.cs @@ -24,6 +24,262 @@ namespace Marco.Pms.Services.Helpers _logger = logger; _cache = cache; } + + public async Task GetDailyProjectReportWithOutTenant(Guid projectId) + { + // await _cache.GetBuildingAndFloorByWorkAreaId(); + DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date; + var project = await _cache.GetProjectDetailsWithBuildings(projectId); + if (project == null) + { + var projectSQL = await _context.Projects + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == projectId); + 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.Any()) + { + 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.Any()) + { + 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.Any()) + { + 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, new List()); + if (workItems == null || !workItems.Any()) + { + 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 => 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; + } + public async Task GetDailyProjectReport(Guid projectId, Guid tenantId) { // await _cache.GetBuildingAndFloorByWorkAreaId(); diff --git a/Marco.Pms.Services/Helpers/RolesHelper.cs b/Marco.Pms.Services/Helpers/RolesHelper.cs index 4fb1c49..c9f40f1 100644 --- a/Marco.Pms.Services/Helpers/RolesHelper.cs +++ b/Marco.Pms.Services/Helpers/RolesHelper.cs @@ -28,7 +28,7 @@ namespace MarcoBMS.Services.Helpers /// /// The ID of the employee. /// A distinct list of FeaturePermission objects the employee is granted. - public async Task> GetFeaturePermissionByEmployeeId(Guid EmployeeId) + public async Task> GetFeaturePermissionByEmployeeId(Guid EmployeeId, Guid tenantId) { _logger.LogInfo("Fetching feature permissions for EmployeeId: {EmployeeId}", EmployeeId); @@ -58,7 +58,7 @@ namespace MarcoBMS.Services.Helpers { // The cache service might also need its own context, or you can pass the data directly. // Assuming AddApplicationRole takes the data, not a context. - await _cache.AddApplicationRole(EmployeeId, roleIds); + await _cache.AddApplicationRole(EmployeeId, roleIds, tenantId); _logger.LogInfo("Successfully queued cache update for EmployeeId: {EmployeeId}", EmployeeId); } } diff --git a/Marco.Pms.Services/Service/PermissionServices.cs b/Marco.Pms.Services/Service/PermissionServices.cs index 979c06d..79c3cd6 100644 --- a/Marco.Pms.Services/Service/PermissionServices.cs +++ b/Marco.Pms.Services/Service/PermissionServices.cs @@ -12,11 +12,14 @@ namespace Marco.Pms.Services.Service private readonly ApplicationDbContext _context; private readonly RolesHelper _rolesHelper; private readonly CacheUpdateHelper _cache; - public PermissionServices(ApplicationDbContext context, RolesHelper rolesHelper, CacheUpdateHelper cache) + private readonly Guid tenantId; + + public PermissionServices(ApplicationDbContext context, RolesHelper rolesHelper, CacheUpdateHelper cache, UserHelper userHelper) { _context = context; _rolesHelper = rolesHelper; _cache = cache; + tenantId = userHelper.GetTenantId(); } //public async Task HasPermission(Guid featurePermissionId, Guid employeeId, Guid? projectId = null) @@ -62,12 +65,12 @@ namespace Marco.Pms.Services.Service public async Task HasPermission(Guid featurePermissionId, Guid employeeId, Guid? projectId = null) { // 1. Try fetching permissions from cache (fast-path lookup). - var featurePermissionIds = await _cache.GetPermissions(employeeId); + var featurePermissionIds = await _cache.GetPermissions(employeeId, tenantId); // If not found in cache, fallback to database (slower). if (featurePermissionIds == null) { - var featurePermissions = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId); + var featurePermissions = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId, tenantId); featurePermissionIds = featurePermissions.Select(fp => fp.Id).ToList(); } @@ -115,10 +118,10 @@ namespace Marco.Pms.Services.Service } public async Task HasPermissionAny(List featurePermissionIds, Guid employeeId) { - var allFeaturePermissionIds = await _cache.GetPermissions(employeeId); + var allFeaturePermissionIds = await _cache.GetPermissions(employeeId, tenantId); if (allFeaturePermissionIds == null) { - List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId); + List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeId(employeeId, tenantId); allFeaturePermissionIds = featurePermission.Select(fp => fp.Id).ToList(); } var hasPermission = featurePermissionIds.Any(f => allFeaturePermissionIds.Contains(f)); @@ -128,7 +131,7 @@ namespace Marco.Pms.Services.Service public async Task HasProjectPermission(Employee LoggedInEmployee, Guid projectId) { var employeeId = LoggedInEmployee.Id; - var projectIds = await _cache.GetProjects(employeeId); + var projectIds = await _cache.GetProjects(employeeId, tenantId); if (projectIds == null) { @@ -147,7 +150,7 @@ namespace Marco.Pms.Services.Service } projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList(); } - await _cache.AddProjects(LoggedInEmployee.Id, projectIds); + await _cache.AddProjects(LoggedInEmployee.Id, projectIds, tenantId); } return projectIds.Contains(projectId); } diff --git a/Marco.Pms.Services/Service/ProjectServices.cs b/Marco.Pms.Services/Service/ProjectServices.cs index e4ed444..1c3fc53 100644 --- a/Marco.Pms.Services/Service/ProjectServices.cs +++ b/Marco.Pms.Services/Service/ProjectServices.cs @@ -380,7 +380,7 @@ namespace Marco.Pms.Services.Service { // These operations do not depend on each other, so they can run in parallel. Task cacheAddDetailsTask = _cache.AddProjectDetails(project); - Task cacheClearListTask = _cache.ClearAllProjectIdsByPermissionId(PermissionsMaster.ManageProject); + Task cacheClearListTask = _cache.ClearAllProjectIdsByPermissionId(PermissionsMaster.ManageProject, tenantId); // Await all side-effect tasks to complete in parallel await Task.WhenAll(cacheAddDetailsTask, cacheClearListTask); @@ -767,7 +767,7 @@ namespace Marco.Pms.Services.Service } try { - await _cache.ClearAllProjectIds(dto.EmployeeId); + await _cache.ClearAllProjectIds(dto.EmployeeId, tenantId); _logger.LogInfo("Successfully completed cache invalidation for employee {EmployeeId}.", dto.EmployeeId); } catch (Exception ex) @@ -986,7 +986,7 @@ namespace Marco.Pms.Services.Service // --- Step 5: Invalidate Cache ONCE after successful save --- try { - await _cache.ClearAllProjectIds(employeeId); + await _cache.ClearAllProjectIds(employeeId, tenantId); _logger.LogInfo("Successfully queued cache invalidation for employee {EmployeeId}.", employeeId); } catch (Exception ex) @@ -2320,7 +2320,7 @@ namespace Marco.Pms.Services.Service using var scope = _serviceScopeFactory.CreateScope(); var _permission = scope.ServiceProvider.GetRequiredService(); - var projectIds = await _cache.GetProjects(LoggedInEmployee.Id); + var projectIds = await _cache.GetProjects(LoggedInEmployee.Id, tenantId); if (projectIds == null) { @@ -2339,7 +2339,7 @@ namespace Marco.Pms.Services.Service } projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList(); } - await _cache.AddProjects(LoggedInEmployee.Id, projectIds); + await _cache.AddProjects(LoggedInEmployee.Id, projectIds, tenantId); } return projectIds; } @@ -2350,7 +2350,7 @@ namespace Marco.Pms.Services.Service // 1. Attempt to retrieve the list of project IDs from the cache first. // This is the "happy path" and should be as fast as possible. - List? projectIds = await _cache.GetProjects(loggedInEmployee.Id); + List? projectIds = await _cache.GetProjects(loggedInEmployee.Id, tenantId); if (projectIds != null) { @@ -2389,7 +2389,7 @@ namespace Marco.Pms.Services.Service // 4. Populate the cache with the newly fetched list (even if it's empty). // This prevents repeated database queries for employees with no projects. - await _cache.AddProjects(loggedInEmployee.Id, newProjectIds); + await _cache.AddProjects(loggedInEmployee.Id, newProjectIds, tenantId); return newProjectIds; } @@ -2757,12 +2757,12 @@ namespace Marco.Pms.Services.Service var _rolesHelper = scope.ServiceProvider.GetRequiredService(); // 1. Try fetching permissions from cache (fast-path lookup). - var featurePermissionIds = await _cache.GetPermissions(EmployeeId); + var featurePermissionIds = await _cache.GetPermissions(EmployeeId, tenantId); // If not found in cache, fallback to database (slower). if (featurePermissionIds == null) { - var featurePermissions = await _rolesHelper.GetFeaturePermissionByEmployeeId(EmployeeId); + var featurePermissions = await _rolesHelper.GetFeaturePermissionByEmployeeId(EmployeeId, tenantId); featurePermissionIds = featurePermissions.Select(fp => fp.Id).ToList(); }