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 c8ca2d5c49
commit 8735de3d93
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);
await InitializeCollectionAsync();
// 6. Return a more accurate result indicating success for both updates and upserts.
// The operation is successful if an existing document was modified OR a new one was created.
return result.IsAcknowledged && (result.ModifiedCount > 0 || result.UpsertedId != null);
@ -51,6 +53,7 @@ namespace Marco.Pms.CacheHelper
{
return false;
}
await InitializeCollectionAsync();
return true;
}
public async Task<List<Guid>> GetProjectsFromCache(Guid employeeId)
@ -177,5 +180,22 @@ namespace Marco.Pms.CacheHelper
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
{
private readonly ApplicationDbContext _context;
private readonly IMongoCollection<ProjectMongoDB> _projetCollection;
private readonly IMongoCollection<ProjectMongoDB> _projectCollection;
private readonly IMongoCollection<WorkItemMongoDB> _taskCollection;
public ProjectCache(ApplicationDbContext context, IConfiguration configuration)
{
var connectionString = configuration["MongoDB:ConnectionString"];
_context = context;
var mongoUrl = new MongoUrl(connectionString);
var client = new MongoClient(mongoUrl); // Your MongoDB connection string
var mongoDB = client.GetDatabase(mongoUrl.DatabaseName); // Your MongoDB Database name
_projetCollection = mongoDB.GetCollection<ProjectMongoDB>("ProjectDetails");
_projectCollection = mongoDB.GetCollection<ProjectMongoDB>("ProjectDetails");
_taskCollection = mongoDB.GetCollection<WorkItemMongoDB>("WorkItemDetails");
}
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)
{
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)
{
@ -51,7 +83,7 @@ namespace Marco.Pms.CacheHelper
);
// Perform the update
var result = await _projetCollection.UpdateOneAsync(
var result = await _projectCollection.UpdateOneAsync(
filter: r => r.Id == project.Id.ToString(),
update: updates
);
@ -71,7 +103,7 @@ namespace Marco.Pms.CacheHelper
var projection = Builders<ProjectMongoDB>.Projection.Exclude(p => p.Buildings);
// Perform query
var project = await _projetCollection
var project = await _projectCollection
.Find(filter)
.Project<ProjectMongoDB>(projection)
.FirstOrDefaultAsync();
@ -83,7 +115,7 @@ namespace Marco.Pms.CacheHelper
List<string> stringProjectIds = projectIds.Select(p => p.ToString()).ToList();
var filter = Builders<ProjectMongoDB>.Filter.In(p => p.Id, stringProjectIds);
var projection = Builders<ProjectMongoDB>.Projection.Exclude(p => p.Buildings);
var projects = await _projetCollection
var projects = await _projectCollection
.Find(filter)
.Project<ProjectMongoDB>(projection)
.ToListAsync();
@ -92,14 +124,14 @@ namespace Marco.Pms.CacheHelper
public async Task<bool> DeleteProjectByIdFromCacheAsync(Guid projectId)
{
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;
}
public async Task<bool> RemoveProjectsFromCacheAsync(List<Guid> projectIds)
{
var stringIds = projectIds.Select(id => id.ToString()).ToList();
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;
}
@ -125,7 +157,7 @@ namespace Marco.Pms.CacheHelper
var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, stringProjectId);
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)
{
@ -155,7 +187,7 @@ namespace Marco.Pms.CacheHelper
);
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)
{
@ -189,7 +221,7 @@ namespace Marco.Pms.CacheHelper
var update = Builders<ProjectMongoDB>.Update.Push("Buildings.$[b].Floors.$[f].WorkAreas", workAreaMongo);
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)
{
@ -221,7 +253,7 @@ namespace Marco.Pms.CacheHelper
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)
{
@ -246,7 +278,7 @@ namespace Marco.Pms.CacheHelper
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
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)
{
@ -272,7 +304,7 @@ namespace Marco.Pms.CacheHelper
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
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)
{
@ -296,7 +328,7 @@ namespace Marco.Pms.CacheHelper
var filter = Builders<ProjectMongoDB>.Filter.Eq(p => p.Id, projectId.ToString());
// Project only the "Buildings" field from the document
var buildings = await _projetCollection
var buildings = await _projectCollection
.Find(filter)
.Project(p => p.Buildings)
.FirstOrDefaultAsync();
@ -315,7 +347,7 @@ namespace Marco.Pms.CacheHelper
public async Task UpdatePlannedAndCompleteWorksInBuildingFromCache(Guid workAreaId, double plannedWork, double completedWork)
{
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? selectedFloorId = null;
@ -353,7 +385,7 @@ namespace Marco.Pms.CacheHelper
.Inc("Buildings.$[b].CompletedWork", completedWork)
.Inc("PlannedWork", plannedWork)
.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)
@ -393,7 +425,7 @@ namespace Marco.Pms.CacheHelper
{ "WorkArea", "$Buildings.Floors.WorkAreas" }
})
};
var result = await _projetCollection.Aggregate<WorkAreaInfoMongoDB>(pipeline).FirstOrDefaultAsync();
var result = await _projectCollection.Aggregate<WorkAreaInfoMongoDB>(pipeline).FirstOrDefaultAsync();
if (result == null)
return null;
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> PermissionIds { 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 double CompletedWork { 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.Services.Hubs;
using Marco.Pms.Services.Service;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
@ -28,7 +29,7 @@ namespace MarcoBMS.Services.Controllers
{
private readonly ApplicationDbContext _context;
private readonly EmployeeHelper _employeeHelper;
private readonly ProjectsHelper _projectsHelper;
private readonly IProjectServices _projectServices;
private readonly UserHelper _userHelper;
private readonly S3UploadService _s3Service;
private readonly PermissionServices _permission;
@ -37,11 +38,11 @@ namespace MarcoBMS.Services.Controllers
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;
_employeeHelper = employeeHelper;
_projectsHelper = projectsHelper;
_projectServices = projectServices;
_userHelper = userHelper;
_s3Service = s3Service;
_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<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true);
List<ProjectAllocation> projectteam = await _projectServices.GetTeamByProject(TenantId, projectId, true);
var jobRole = await _context.JobRoles.ToListAsync();
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<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 emp = await _context.Employees.Where(e => idList.Contains(e.Id)).Include(e => e.JobRole).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<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 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.Services.Hubs;
using Marco.Pms.Services.Service;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
@ -37,13 +38,13 @@ namespace MarcoBMS.Services.Controllers
private readonly ILoggingService _logger;
private readonly IHubContext<MarcoHub> _signalR;
private readonly PermissionServices _permission;
private readonly ProjectsHelper _projectsHelper;
private readonly IProjectServices _projectServices;
private readonly Guid tenantId;
public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
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;
_userManager = userManager;
@ -54,7 +55,7 @@ namespace MarcoBMS.Services.Controllers
_logger = logger;
_signalR = signalR;
_permission = permission;
_projectsHelper = projectsHelper;
_projectServices = projectServices;
tenantId = _userHelper.GetTenantId();
}
@ -119,7 +120,7 @@ namespace MarcoBMS.Services.Controllers
loggedInEmployee.Id, projectid ?? Guid.Empty, ShowInactive);
// 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 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.Utilities;
using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -19,14 +20,14 @@ namespace MarcoBMS.Services.Controllers
private readonly UserHelper _userHelper;
private readonly EmployeeHelper _employeeHelper;
private readonly ProjectsHelper _projectsHelper;
private readonly IProjectServices _projectServices;
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;
_employeeHelper = employeeHelper;
_projectsHelper = projectsHelper;
_projectServices = projectServices;
_rolesHelper = rolesHelper;
}
@ -56,12 +57,12 @@ namespace MarcoBMS.Services.Controllers
/* User with permission manage project can see all projects */
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();
}
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();
}
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<RolesHelper>();
builder.Services.AddScoped<EmployeeHelper>();
builder.Services.AddScoped<ProjectsHelper>();
builder.Services.AddScoped<DirectoryHelper>();
builder.Services.AddScoped<MasterHelper>();
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.Services.Helpers;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
@ -25,7 +24,6 @@ namespace Marco.Pms.Services.Service
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
private readonly ILoggingService _logger;
private readonly ProjectsHelper _projectsHelper;
private readonly PermissionServices _permission;
private readonly CacheUpdateHelper _cache;
private readonly IMapper _mapper;
@ -34,7 +32,6 @@ namespace Marco.Pms.Services.Service
IDbContextFactory<ApplicationDbContext> dbContextFactory,
ApplicationDbContext context,
ILoggingService logger,
ProjectsHelper projectsHelper,
PermissionServices permission,
CacheUpdateHelper cache,
IMapper mapper,
@ -43,7 +40,6 @@ namespace Marco.Pms.Services.Service
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_projectsHelper = projectsHelper ?? throw new ArgumentNullException(nameof(projectsHelper));
_permission = permission ?? throw new ArgumentNullException(nameof(permission));
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_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);
// 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())
{
@ -94,7 +90,7 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("Starting GetAllProjects for TenantId: {TenantId}, User: {UserId}", tenantId, loggedInEmployee.Id);
// --- 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())
{
_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.
// 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 projectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee);
var projectIds = await GetMyProjects(tenantId, loggedInEmployee);
if (!hasPermission)
{
_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 ===================================================================
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>
/// 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

View File

@ -1,5 +1,6 @@
using Marco.Pms.Model.Dtos.Project;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
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<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);
}
}