Remove the projectHelper and ProjetsHelper and move its bussiness logic to project services

This commit is contained in:
ashutosh.nehete 2025-07-17 10:17:57 +05:30
parent 089ae7e9e5
commit ccce0d48d5
12 changed files with 206 additions and 162 deletions

View File

@ -33,6 +33,8 @@ namespace Marco.Pms.CacheHelper
var result = await _collection.UpdateOneAsync(filter, update, options); var result = await _collection.UpdateOneAsync(filter, update, options);
await InitializeCollectionAsync();
// 6. Return a more accurate result indicating success for both updates and upserts. // 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. // 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); return result.IsAcknowledged && (result.ModifiedCount > 0 || result.UpsertedId != null);
@ -51,6 +53,7 @@ namespace Marco.Pms.CacheHelper
{ {
return false; return false;
} }
await InitializeCollectionAsync();
return true; return true;
} }
public async Task<List<Guid>> GetProjectsFromCache(Guid employeeId) public async Task<List<Guid>> GetProjectsFromCache(Guid employeeId)
@ -177,5 +180,22 @@ namespace Marco.Pms.CacheHelper
return true; return true;
} }
// A private method to handle the one-time setup of the collection's indexes.
private async Task InitializeCollectionAsync()
{
// 1. Define the TTL (Time-To-Live) index on the 'ExpireAt' field.
var indexKeys = Builders<EmployeePermissionMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
var indexOptions = new CreateIndexOptions
{
// This tells MongoDB to automatically delete documents when their 'ExpireAt' time is reached.
ExpireAfter = TimeSpan.FromSeconds(0)
};
var indexModel = new CreateIndexModel<EmployeePermissionMongoDB>(indexKeys, indexOptions);
// 2. Create the index. This is an idempotent operation if the index already exists.
// Use CreateOneAsync since we are only creating a single index.
await _collection.Indexes.CreateOneAsync(indexModel);
}
} }
} }

View File

@ -11,27 +11,59 @@ namespace Marco.Pms.CacheHelper
{ {
public class ProjectCache public class ProjectCache
{ {
private readonly ApplicationDbContext _context; private readonly IMongoCollection<ProjectMongoDB> _projectCollection;
private readonly IMongoCollection<ProjectMongoDB> _projetCollection;
private readonly IMongoCollection<WorkItemMongoDB> _taskCollection; private readonly IMongoCollection<WorkItemMongoDB> _taskCollection;
public ProjectCache(ApplicationDbContext context, IConfiguration configuration) public ProjectCache(ApplicationDbContext context, IConfiguration configuration)
{ {
var connectionString = configuration["MongoDB:ConnectionString"]; var connectionString = configuration["MongoDB:ConnectionString"];
_context = context;
var mongoUrl = new MongoUrl(connectionString); var mongoUrl = new MongoUrl(connectionString);
var client = new MongoClient(mongoUrl); // Your MongoDB connection string var client = new MongoClient(mongoUrl); // Your MongoDB connection string
var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name
_projetCollection = mongoDB.GetCollection<ProjectMongoDB>("ProjectDetails"); _projectCollection = mongoDB.GetCollection<ProjectMongoDB>("ProjectDetails");
_taskCollection = mongoDB.GetCollection<WorkItemMongoDB>("WorkItemDetails"); _taskCollection = mongoDB.GetCollection<WorkItemMongoDB>("WorkItemDetails");
} }
public async Task AddProjectDetailsToCache(ProjectMongoDB projectDetails) public async Task AddProjectDetailsToCache(ProjectMongoDB projectDetails)
{ {
await _projetCollection.InsertOneAsync(projectDetails); await _projectCollection.InsertOneAsync(projectDetails);
var indexKeys = Builders<ProjectMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
var indexOptions = new CreateIndexOptions
{
ExpireAfter = TimeSpan.Zero // required for fixed expiration time
};
var indexModel = new CreateIndexModel<ProjectMongoDB>(indexKeys, indexOptions);
await _projectCollection.Indexes.CreateOneAsync(indexModel);
} }
// The method should focus only on inserting data.
public async Task AddProjectDetailsListToCache(List<ProjectMongoDB> projectDetailsList) public async Task AddProjectDetailsListToCache(List<ProjectMongoDB> projectDetailsList)
{ {
await _projetCollection.InsertManyAsync(projectDetailsList); // 1. Add a guard clause to avoid an unnecessary database call for an empty list.
if (projectDetailsList == null || !projectDetailsList.Any())
{
return;
}
// 2. Perform the insert operation. This is the only responsibility of this method.
await _projectCollection.InsertManyAsync(projectDetailsList);
await InitializeCollectionAsync();
}
// A private method to handle the one-time setup of the collection's indexes.
private async Task InitializeCollectionAsync()
{
// 1. Define the TTL (Time-To-Live) index on the 'ExpireAt' field.
var indexKeys = Builders<ProjectMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
var indexOptions = new CreateIndexOptions
{
// This tells MongoDB to automatically delete documents when their 'ExpireAt' time is reached.
ExpireAfter = TimeSpan.FromSeconds(0)
};
var indexModel = new CreateIndexModel<ProjectMongoDB>(indexKeys, indexOptions);
// 2. Create the index. This is an idempotent operation if the index already exists.
// Use CreateOneAsync since we are only creating a single index.
await _projectCollection.Indexes.CreateOneAsync(indexModel);
} }
public async Task<bool> UpdateProjectDetailsOnlyToCache(Project project, StatusMaster projectStatus) public async Task<bool> UpdateProjectDetailsOnlyToCache(Project project, StatusMaster projectStatus)
{ {
@ -51,7 +83,7 @@ namespace Marco.Pms.CacheHelper
); );
// Perform the update // Perform the update
var result = await _projetCollection.UpdateOneAsync( var result = await _projectCollection.UpdateOneAsync(
filter: r => r.Id == project.Id.ToString(), filter: r => r.Id == project.Id.ToString(),
update: updates update: updates
); );
@ -71,7 +103,7 @@ namespace Marco.Pms.CacheHelper
var projection = Builders<ProjectMongoDB>.Projection.Exclude(p => p.Buildings); var projection = Builders<ProjectMongoDB>.Projection.Exclude(p => p.Buildings);
// Perform query // Perform query
var project = await _projetCollection var project = await _projectCollection
.Find(filter) .Find(filter)
.Project<ProjectMongoDB>(projection) .Project<ProjectMongoDB>(projection)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
@ -83,7 +115,7 @@ namespace Marco.Pms.CacheHelper
List<string> stringProjectIds = projectIds.Select(p => p.ToString()).ToList(); List<string> stringProjectIds = projectIds.Select(p => p.ToString()).ToList();
var filter = Builders<ProjectMongoDB>.Filter.In(p => p.Id, stringProjectIds); var filter = Builders<ProjectMongoDB>.Filter.In(p => p.Id, stringProjectIds);
var projection = Builders<ProjectMongoDB>.Projection.Exclude(p => p.Buildings); var projection = Builders<ProjectMongoDB>.Projection.Exclude(p => p.Buildings);
var projects = await _projetCollection var projects = await _projectCollection
.Find(filter) .Find(filter)
.Project<ProjectMongoDB>(projection) .Project<ProjectMongoDB>(projection)
.ToListAsync(); .ToListAsync();
@ -92,14 +124,14 @@ namespace Marco.Pms.CacheHelper
public async Task<bool> DeleteProjectByIdFromCacheAsync(Guid projectId) public async Task<bool> DeleteProjectByIdFromCacheAsync(Guid projectId)
{ {
var filter = Builders<ProjectMongoDB>.Filter.Eq(e => e.Id, projectId.ToString()); var filter = Builders<ProjectMongoDB>.Filter.Eq(e => e.Id, projectId.ToString());
var result = await _projetCollection.DeleteOneAsync(filter); var result = await _projectCollection.DeleteOneAsync(filter);
return result.DeletedCount > 0; return result.DeletedCount > 0;
} }
public async Task<bool> RemoveProjectsFromCacheAsync(List<Guid> projectIds) public async Task<bool> RemoveProjectsFromCacheAsync(List<Guid> projectIds)
{ {
var stringIds = projectIds.Select(id => id.ToString()).ToList(); var stringIds = projectIds.Select(id => id.ToString()).ToList();
var filter = Builders<ProjectMongoDB>.Filter.In(p => p.Id, stringIds); var filter = Builders<ProjectMongoDB>.Filter.In(p => p.Id, stringIds);
var result = await _projetCollection.DeleteManyAsync(filter); var result = await _projectCollection.DeleteManyAsync(filter);
return result.DeletedCount > 0; return result.DeletedCount > 0;
} }
@ -125,7 +157,7 @@ namespace Marco.Pms.CacheHelper
var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId); var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId);
var update = Builders<ProjectMongoDB>.Update.Push("Buildings", buildingMongo); var update = Builders<ProjectMongoDB>.Update.Push("Buildings", buildingMongo);
var result = await _projetCollection.UpdateOneAsync(filter, update); var result = await _projectCollection.UpdateOneAsync(filter, update);
if (result.MatchedCount == 0) if (result.MatchedCount == 0)
{ {
@ -155,7 +187,7 @@ namespace Marco.Pms.CacheHelper
); );
var update = Builders<ProjectMongoDB>.Update.Push("Buildings.$.Floors", floorMongo); var update = Builders<ProjectMongoDB>.Update.Push("Buildings.$.Floors", floorMongo);
var result = await _projetCollection.UpdateOneAsync(filter, update); var result = await _projectCollection.UpdateOneAsync(filter, update);
if (result.MatchedCount == 0) if (result.MatchedCount == 0)
{ {
@ -189,7 +221,7 @@ namespace Marco.Pms.CacheHelper
var update = Builders<ProjectMongoDB>.Update.Push("Buildings.$[b].Floors.$[f].WorkAreas", workAreaMongo); var update = Builders<ProjectMongoDB>.Update.Push("Buildings.$[b].Floors.$[f].WorkAreas", workAreaMongo);
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions);
if (result.MatchedCount == 0) if (result.MatchedCount == 0)
{ {
@ -221,7 +253,7 @@ namespace Marco.Pms.CacheHelper
Builders<ProjectMongoDB>.Update.Set("Buildings.$.Description", building.Description) Builders<ProjectMongoDB>.Update.Set("Buildings.$.Description", building.Description)
); );
var result = await _projetCollection.UpdateOneAsync(filter, update); var result = await _projectCollection.UpdateOneAsync(filter, update);
if (result.MatchedCount == 0) if (result.MatchedCount == 0)
{ {
@ -246,7 +278,7 @@ namespace Marco.Pms.CacheHelper
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId); var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId);
var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions);
if (result.MatchedCount == 0) if (result.MatchedCount == 0)
{ {
@ -272,7 +304,7 @@ namespace Marco.Pms.CacheHelper
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters }; var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId); var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId);
var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions);
if (result.MatchedCount == 0) if (result.MatchedCount == 0)
{ {
@ -296,7 +328,7 @@ namespace Marco.Pms.CacheHelper
var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, projectId.ToString()); var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, projectId.ToString());
// Project only the "Buildings" field from the document // Project only the "Buildings" field from the document
var buildings = await _projetCollection var buildings = await _projectCollection
.Find(filter) .Find(filter)
.Project(p => p.Buildings) .Project(p => p.Buildings)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
@ -315,7 +347,7 @@ namespace Marco.Pms.CacheHelper
public async Task UpdatePlannedAndCompleteWorksInBuildingFromCache(Guid workAreaId, double plannedWork, double completedWork) public async Task UpdatePlannedAndCompleteWorksInBuildingFromCache(Guid workAreaId, double plannedWork, double completedWork)
{ {
var filter = Builders<ProjectMongoDB>.Filter.Eq("Buildings.Floors.WorkAreas._id", workAreaId.ToString()); var filter = Builders<ProjectMongoDB>.Filter.Eq("Buildings.Floors.WorkAreas._id", workAreaId.ToString());
var project = await _projetCollection.Find(filter).FirstOrDefaultAsync(); var project = await _projectCollection.Find(filter).FirstOrDefaultAsync();
string? selectedBuildingId = null; string? selectedBuildingId = null;
string? selectedFloorId = null; string? selectedFloorId = null;
@ -353,7 +385,7 @@ namespace Marco.Pms.CacheHelper
.Inc("Buildings.$[b].CompletedWork", completedWork) .Inc("Buildings.$[b].CompletedWork", completedWork)
.Inc("PlannedWork", plannedWork) .Inc("PlannedWork", plannedWork)
.Inc("CompletedWork", completedWork); .Inc("CompletedWork", completedWork);
var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); var result = await _projectCollection.UpdateOneAsync(filter, update, updateOptions);
} }
public async Task<WorkAreaInfoMongoDB?> GetBuildingAndFloorByWorkAreaIdFromCache(Guid workAreaId) public async Task<WorkAreaInfoMongoDB?> GetBuildingAndFloorByWorkAreaIdFromCache(Guid workAreaId)
@ -393,7 +425,7 @@ namespace Marco.Pms.CacheHelper
{ "WorkArea", "$Buildings.Floors.WorkAreas" } { "WorkArea", "$Buildings.Floors.WorkAreas" }
}) })
}; };
var result = await _projetCollection.Aggregate<WorkAreaInfoMongoDB>(pipeline).FirstOrDefaultAsync(); var result = await _projectCollection.Aggregate<WorkAreaInfoMongoDB>(pipeline).FirstOrDefaultAsync();
if (result == null) if (result == null)
return null; return null;
return result; return result;

View File

@ -9,5 +9,6 @@ namespace Marco.Pms.Model.MongoDBModels
public List<string> ApplicationRoleIds { get; set; } = new List<string>(); public List<string> ApplicationRoleIds { get; set; } = new List<string>();
public List<string> PermissionIds { get; set; } = new List<string>(); public List<string> PermissionIds { get; set; } = new List<string>();
public List<string> ProjectIds { get; set; } = new List<string>(); public List<string> ProjectIds { get; set; } = new List<string>();
public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1);
} }
} }

View File

@ -14,5 +14,6 @@
public int TeamSize { get; set; } public int TeamSize { get; set; }
public double CompletedWork { get; set; } public double CompletedWork { get; set; }
public double PlannedWork { get; set; } public double PlannedWork { get; set; }
public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1);
} }
} }

View File

@ -9,6 +9,7 @@ using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.AttendanceVM; using Marco.Pms.Model.ViewModels.AttendanceVM;
using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Hubs;
using Marco.Pms.Services.Service; using Marco.Pms.Services.Service;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -28,7 +29,7 @@ namespace MarcoBMS.Services.Controllers
{ {
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;
private readonly EmployeeHelper _employeeHelper; private readonly EmployeeHelper _employeeHelper;
private readonly ProjectsHelper _projectsHelper; private readonly IProjectServices _projectServices;
private readonly UserHelper _userHelper; private readonly UserHelper _userHelper;
private readonly S3UploadService _s3Service; private readonly S3UploadService _s3Service;
private readonly PermissionServices _permission; private readonly PermissionServices _permission;
@ -37,11 +38,11 @@ namespace MarcoBMS.Services.Controllers
public AttendanceController( public AttendanceController(
ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext<MarcoHub> signalR) ApplicationDbContext context, EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext<MarcoHub> signalR)
{ {
_context = context; _context = context;
_employeeHelper = employeeHelper; _employeeHelper = employeeHelper;
_projectsHelper = projectsHelper; _projectServices = projectServices;
_userHelper = userHelper; _userHelper = userHelper;
_s3Service = s3Service; _s3Service = s3Service;
_logger = logger; _logger = logger;
@ -188,7 +189,7 @@ namespace MarcoBMS.Services.Controllers
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == TenantId).ToListAsync(); List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == TenantId).ToListAsync();
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true); List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(TenantId, projectId, true);
var jobRole = await _context.JobRoles.ToListAsync(); var jobRole = await _context.JobRoles.ToListAsync();
foreach (Attendance? attendance in lstAttendance) foreach (Attendance? attendance in lstAttendance)
{ {
@ -295,7 +296,7 @@ namespace MarcoBMS.Services.Controllers
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == TenantId).ToListAsync(); List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == TenantId).ToListAsync();
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, IncludeInActive); List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(TenantId, projectId, IncludeInActive);
var idList = projectteam.Select(p => p.EmployeeId).ToList(); var idList = projectteam.Select(p => p.EmployeeId).ToList();
//var emp = await _context.Employees.Where(e => idList.Contains(e.Id)).Include(e => e.JobRole).ToListAsync(); //var emp = await _context.Employees.Where(e => idList.Contains(e.Id)).Include(e => e.JobRole).ToListAsync();
var jobRole = await _context.JobRoles.ToListAsync(); var jobRole = await _context.JobRoles.ToListAsync();
@ -378,7 +379,7 @@ namespace MarcoBMS.Services.Controllers
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.TenantId == TenantId).ToListAsync(); List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.TenantId == TenantId).ToListAsync();
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true); List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(TenantId, projectId, true);
var idList = projectteam.Select(p => p.EmployeeId).ToList(); var idList = projectteam.Select(p => p.EmployeeId).ToList();
var jobRole = await _context.JobRoles.ToListAsync(); var jobRole = await _context.JobRoles.ToListAsync();

View File

@ -9,6 +9,7 @@ using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Hubs;
using Marco.Pms.Services.Service; using Marco.Pms.Services.Service;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -37,13 +38,13 @@ namespace MarcoBMS.Services.Controllers
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly IHubContext<MarcoHub> _signalR; private readonly IHubContext<MarcoHub> _signalR;
private readonly PermissionServices _permission; private readonly PermissionServices _permission;
private readonly ProjectsHelper _projectsHelper; private readonly IProjectServices _projectServices;
private readonly Guid tenantId; private readonly Guid tenantId;
public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender, public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger, ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger,
IHubContext<MarcoHub> signalR, PermissionServices permission, ProjectsHelper projectsHelper) IHubContext<MarcoHub> signalR, PermissionServices permission, IProjectServices projectServices)
{ {
_context = context; _context = context;
_userManager = userManager; _userManager = userManager;
@ -54,7 +55,7 @@ namespace MarcoBMS.Services.Controllers
_logger = logger; _logger = logger;
_signalR = signalR; _signalR = signalR;
_permission = permission; _permission = permission;
_projectsHelper = projectsHelper; _projectServices = projectServices;
tenantId = _userHelper.GetTenantId(); tenantId = _userHelper.GetTenantId();
} }
@ -119,7 +120,7 @@ namespace MarcoBMS.Services.Controllers
loggedInEmployee.Id, projectid ?? Guid.Empty, ShowInactive); loggedInEmployee.Id, projectid ?? Guid.Empty, ShowInactive);
// Step 3: Fetch project access and permissions // Step 3: Fetch project access and permissions
var projectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); var projectIds = await _projectServices.GetMyProjectIdsAsync(tenantId, loggedInEmployee);
var hasViewAllEmployeesPermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id); var hasViewAllEmployeesPermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id);
var hasViewTeamMembersPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id); var hasViewTeamMembersPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id);

View File

@ -4,6 +4,7 @@ using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects; using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -19,14 +20,14 @@ namespace MarcoBMS.Services.Controllers
private readonly UserHelper _userHelper; private readonly UserHelper _userHelper;
private readonly EmployeeHelper _employeeHelper; private readonly EmployeeHelper _employeeHelper;
private readonly ProjectsHelper _projectsHelper; private readonly IProjectServices _projectServices;
private readonly RolesHelper _rolesHelper; private readonly RolesHelper _rolesHelper;
public UserController(EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, RolesHelper rolesHelper) public UserController(EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper, RolesHelper rolesHelper)
{ {
_userHelper = userHelper; _userHelper = userHelper;
_employeeHelper = employeeHelper; _employeeHelper = employeeHelper;
_projectsHelper = projectsHelper; _projectServices = projectServices;
_rolesHelper = rolesHelper; _rolesHelper = rolesHelper;
} }
@ -56,12 +57,12 @@ namespace MarcoBMS.Services.Controllers
/* User with permission manage project can see all projects */ /* User with permission manage project can see all projects */
if (featurePermission != null && featurePermission.Exists(c => c.Id.ToString() == "172fc9b6-755b-4f62-ab26-55c34a330614")) if (featurePermission != null && featurePermission.Exists(c => c.Id.ToString() == "172fc9b6-755b-4f62-ab26-55c34a330614"))
{ {
List<Project> projects = await _projectsHelper.GetAllProjectByTanentID(emp.TenantId); List<Project> projects = await _projectServices.GetAllProjectByTanentID(emp.TenantId);
projectsId = projects.Select(c => c.Id.ToString()).ToArray(); projectsId = projects.Select(c => c.Id.ToString()).ToArray();
} }
else else
{ {
List<ProjectAllocation> allocation = await _projectsHelper.GetProjectByEmployeeID(emp.Id); List<ProjectAllocation> allocation = await _projectServices.GetProjectByEmployeeID(emp.Id);
projectsId = allocation.Select(c => c.ProjectId.ToString()).ToArray(); projectsId = allocation.Select(c => c.ProjectId.ToString()).ToArray();
} }
EmployeeProfile profile = new EmployeeProfile() { }; EmployeeProfile profile = new EmployeeProfile() { };

View File

@ -1,37 +0,0 @@
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Projects;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
namespace ModelServices.Helpers
{
public class ProjectHelper
{
private readonly ApplicationDbContext _context;
public ProjectHelper(ApplicationDbContext context)
{
_context = context;
}
public async Task<List<ProjectAllocation>> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive)
{
if (IncludeInactive)
{
var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId).Include(e => e.Employee).ToListAsync();
return employees;
}
else
{
var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId && c.IsActive == true).Include(e => e.Employee).ToListAsync();
return employees;
}
}
}
}

View File

@ -1,81 +0,0 @@
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Projects;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service;
using Microsoft.EntityFrameworkCore;
namespace MarcoBMS.Services.Helpers
{
public class ProjectsHelper
{
private readonly ApplicationDbContext _context;
private readonly CacheUpdateHelper _cache;
private readonly PermissionServices _permission;
public ProjectsHelper(ApplicationDbContext context, CacheUpdateHelper cache, PermissionServices permission)
{
_context = context;
_cache = cache;
_permission = permission;
}
public async Task<List<Project>> GetAllProjectByTanentID(Guid tanentID)
{
List<Project> alloc = await _context.Projects.Where(c => c.TenantId == tanentID).ToListAsync();
return alloc;
}
public async Task<List<ProjectAllocation>> GetProjectByEmployeeID(Guid employeeID)
{
List<ProjectAllocation> alloc = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeID && c.IsActive == true).Include(c => c.Project).ToListAsync();
return alloc;
}
public async Task<List<ProjectAllocation>> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive)
{
if (IncludeInactive)
{
var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId).Include(e => e.Employee).ToListAsync();
return employees;
}
else
{
var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId && c.IsActive == true).Include(e => e.Employee).ToListAsync();
return employees;
}
}
public async Task<List<Guid>> GetMyProjects(Guid tenantId, Employee LoggedInEmployee)
{
var projectIds = await _cache.GetProjects(LoggedInEmployee.Id);
if (projectIds == null)
{
var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageProject, LoggedInEmployee.Id);
if (hasPermission)
{
var projects = await _context.Projects.Where(c => c.TenantId == tenantId).ToListAsync();
projectIds = projects.Select(p => p.Id).ToList();
}
else
{
var allocation = await GetProjectByEmployeeID(LoggedInEmployee.Id);
if (!allocation.Any())
{
return new List<Guid>();
}
projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList();
}
await _cache.AddProjects(LoggedInEmployee.Id, projectIds);
}
return projectIds;
}
}
}

View File

@ -167,7 +167,6 @@ builder.Services.AddScoped<GeneralHelper>();
builder.Services.AddScoped<UserHelper>(); builder.Services.AddScoped<UserHelper>();
builder.Services.AddScoped<RolesHelper>(); builder.Services.AddScoped<RolesHelper>();
builder.Services.AddScoped<EmployeeHelper>(); builder.Services.AddScoped<EmployeeHelper>();
builder.Services.AddScoped<ProjectsHelper>();
builder.Services.AddScoped<DirectoryHelper>(); builder.Services.AddScoped<DirectoryHelper>();
builder.Services.AddScoped<MasterHelper>(); builder.Services.AddScoped<MasterHelper>();
builder.Services.AddScoped<ReportHelper>(); builder.Services.AddScoped<ReportHelper>();

View File

@ -12,7 +12,6 @@ using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Model.ViewModels.Projects;
using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service.ServiceInterfaces; using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -25,7 +24,6 @@ namespace Marco.Pms.Services.Service
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory; private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly ProjectsHelper _projectsHelper;
private readonly PermissionServices _permission; private readonly PermissionServices _permission;
private readonly CacheUpdateHelper _cache; private readonly CacheUpdateHelper _cache;
private readonly IMapper _mapper; private readonly IMapper _mapper;
@ -34,7 +32,6 @@ namespace Marco.Pms.Services.Service
IDbContextFactory<ApplicationDbContext> dbContextFactory, IDbContextFactory<ApplicationDbContext> dbContextFactory,
ApplicationDbContext context, ApplicationDbContext context,
ILoggingService logger, ILoggingService logger,
ProjectsHelper projectsHelper,
PermissionServices permission, PermissionServices permission,
CacheUpdateHelper cache, CacheUpdateHelper cache,
IMapper mapper, IMapper mapper,
@ -43,7 +40,6 @@ namespace Marco.Pms.Services.Service
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
_context = context ?? throw new ArgumentNullException(nameof(context)); _context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_projectsHelper = projectsHelper ?? throw new ArgumentNullException(nameof(projectsHelper));
_permission = permission ?? throw new ArgumentNullException(nameof(permission)); _permission = permission ?? throw new ArgumentNullException(nameof(permission));
_cache = cache ?? throw new ArgumentNullException(nameof(cache)); _cache = cache ?? throw new ArgumentNullException(nameof(cache));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
@ -64,7 +60,7 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("Basic project list requested by EmployeeId {EmployeeId}", loggedInEmployee.Id); _logger.LogInfo("Basic project list requested by EmployeeId {EmployeeId}", loggedInEmployee.Id);
// Step 2: Get the list of project IDs the user has access to // Step 2: Get the list of project IDs the user has access to
List<Guid> accessibleProjectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); List<Guid> accessibleProjectIds = await GetMyProjects(tenantId, loggedInEmployee);
if (accessibleProjectIds == null || !accessibleProjectIds.Any()) if (accessibleProjectIds == null || !accessibleProjectIds.Any())
{ {
@ -94,7 +90,7 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("Starting GetAllProjects for TenantId: {TenantId}, User: {UserId}", tenantId, loggedInEmployee.Id); _logger.LogInfo("Starting GetAllProjects for TenantId: {TenantId}, User: {UserId}", tenantId, loggedInEmployee.Id);
// --- Step 1: Get a list of project IDs the user can access --- // --- Step 1: Get a list of project IDs the user can access ---
List<Guid> projectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); List<Guid> projectIds = await GetMyProjects(tenantId, loggedInEmployee);
if (!projectIds.Any()) if (!projectIds.Any())
{ {
_logger.LogInfo("User has no assigned projects. Returning empty list."); _logger.LogInfo("User has no assigned projects. Returning empty list.");
@ -743,7 +739,7 @@ namespace Marco.Pms.Services.Service
// This is a placeholder for your actual, more specific permission logic. // This is a placeholder for your actual, more specific permission logic.
// It should also handle the case where a user is requesting their own projects (employeeId == loggedInEmployee.Id). // It should also handle the case where a user is requesting their own projects (employeeId == loggedInEmployee.Id).
var hasPermission = await _permission.HasPermission(PermissionsMaster.ViewProject, loggedInEmployee.Id); var hasPermission = await _permission.HasPermission(PermissionsMaster.ViewProject, loggedInEmployee.Id);
var projectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); var projectIds = await GetMyProjects(tenantId, loggedInEmployee);
if (!hasPermission) if (!hasPermission)
{ {
_logger.LogWarning("Access DENIED for user {UserId} trying to view projects for employee {TargetEmployeeId}.", loggedInEmployee.Id, employeeId); _logger.LogWarning("Access DENIED for user {UserId} trying to view projects for employee {TargetEmployeeId}.", loggedInEmployee.Id, employeeId);
@ -1329,6 +1325,110 @@ namespace Marco.Pms.Services.Service
#region =================================================================== Helper Functions =================================================================== #region =================================================================== Helper Functions ===================================================================
public async Task<List<Project>> GetAllProjectByTanentID(Guid tanentId)
{
List<Project> alloc = await _context.Projects.Where(c => c.TenantId == tanentId).ToListAsync();
return alloc;
}
public async Task<List<ProjectAllocation>> GetProjectByEmployeeID(Guid employeeId)
{
List<ProjectAllocation> alloc = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.IsActive == true).Include(c => c.Project).ToListAsync();
return alloc;
}
public async Task<List<ProjectAllocation>> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive)
{
if (IncludeInactive)
{
var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId).Include(e => e.Employee).ToListAsync();
return employees;
}
else
{
var employees = await _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == ProjectId && c.IsActive == true).Include(e => e.Employee).ToListAsync();
return employees;
}
}
public async Task<List<Guid>> GetMyProjects(Guid tenantId, Employee LoggedInEmployee)
{
var projectIds = await _cache.GetProjects(LoggedInEmployee.Id);
if (projectIds == null)
{
var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageProject, LoggedInEmployee.Id);
if (hasPermission)
{
var projects = await _context.Projects.Where(c => c.TenantId == tenantId).ToListAsync();
projectIds = projects.Select(p => p.Id).ToList();
}
else
{
var allocation = await GetProjectByEmployeeID(LoggedInEmployee.Id);
if (!allocation.Any())
{
return new List<Guid>();
}
projectIds = allocation.Select(c => c.ProjectId).Distinct().ToList();
}
await _cache.AddProjects(LoggedInEmployee.Id, projectIds);
}
return projectIds;
}
public async Task<List<Guid>> GetMyProjectIdsAsync(Guid tenantId, Employee loggedInEmployee)
{
// 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<Guid>? projectIds = await _cache.GetProjects(loggedInEmployee.Id);
if (projectIds != null)
{
// Cache Hit: Return the cached list immediately.
return projectIds;
}
// 2. Cache Miss: The list was not in the cache, so we must fetch it from the database.
List<Guid> newProjectIds;
// Check for the specific permission.
var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageProject, loggedInEmployee.Id);
if (hasPermission)
{
// 3a. OPTIMIZATION: User has permission to see all projects.
// Fetch *only* the Ids directly from the database. This is far more efficient
// than fetching full Project objects and then selecting the Ids in memory.
newProjectIds = await _context.Projects
.Where(p => p.TenantId == tenantId)
.Select(p => p.Id) // This translates to `SELECT Id FROM Projects...` in SQL.
.ToListAsync();
}
else
{
// 3b. OPTIMIZATION: User can only see projects they are allocated to.
// We go directly to the source (ProjectAllocations) and ask the database
// for a distinct list of ProjectIds. This is much better than calling a
// helper function that might return full allocation objects.
newProjectIds = await _context.ProjectAllocations
.Where(a => a.EmployeeId == loggedInEmployee.Id && a.ProjectId != Guid.Empty)
.Select(a => a.ProjectId)
.Distinct() // Pushes the DISTINCT operation to the database.
.ToListAsync();
}
// 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);
return newProjectIds;
}
/// <summary> /// <summary>
/// Retrieves a list of ProjectInfoVMs by their IDs, using an efficient partial cache-hit strategy. /// Retrieves a list of ProjectInfoVMs by their IDs, using an efficient partial cache-hit strategy.
/// It fetches what it can from the cache (as ProjectMongoDB), gets the rest from the /// It fetches what it can from the cache (as ProjectMongoDB), gets the rest from the

View File

@ -1,5 +1,6 @@
using Marco.Pms.Model.Dtos.Project; using Marco.Pms.Model.Dtos.Project;
using Marco.Pms.Model.Employees; using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Model.ViewModels.Projects;
@ -25,5 +26,10 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<List<WorkItemVM>>> CreateProjectTaskAsync(List<WorkItemDto> workItemDtos, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<List<WorkItemVM>>> CreateProjectTaskAsync(List<WorkItemDto> workItemDtos, Guid tenantId, Employee loggedInEmployee);
Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee); Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee);
Task<List<Project>> GetAllProjectByTanentID(Guid tanentId);
Task<List<ProjectAllocation>> GetProjectByEmployeeID(Guid employeeId);
Task<List<ProjectAllocation>> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive);
Task<List<Guid>> GetMyProjectIdsAsync(Guid tenantId, Employee LoggedInEmployee);
} }
} }