Merge pull request 'Added the service Id in Query parameter to get infra details API' (#139) from Ashutosh_Enhancement_#1355 into Organization_Management

Reviewed-on: #139
This commit is contained in:
ashutosh.nehete 2025-09-25 10:16:20 +00:00
commit 4c6070fee5
5 changed files with 119 additions and 20 deletions

View File

@ -32,7 +32,7 @@ namespace MarcoBMS.Services.Controllers
public class EmployeeController : ControllerBase public class EmployeeController : ControllerBase
{ {
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
@ -50,7 +50,8 @@ namespace MarcoBMS.Services.Controllers
private readonly Guid organizationId; private readonly Guid organizationId;
public EmployeeController(IServiceScopeFactory serviceScopeFactory, public EmployeeController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
IServiceScopeFactory serviceScopeFactory,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
IEmailSender emailSender, IEmailSender emailSender,
ApplicationDbContext context, ApplicationDbContext context,
@ -64,6 +65,7 @@ namespace MarcoBMS.Services.Controllers
IMapper mapper, IMapper mapper,
GeneralHelper generalHelper) GeneralHelper generalHelper)
{ {
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_context = context; _context = context;
_userManager = userManager; _userManager = userManager;
@ -120,7 +122,7 @@ namespace MarcoBMS.Services.Controllers
} }
} }
[HttpGet("list/project/{projectId}")] [HttpGet("list/organizations/{projectId}")]
public async Task<IActionResult> GetEmployeesByProjectAsync(Guid projectId, [FromQuery] string searchString) public async Task<IActionResult> GetEmployeesByProjectAsync(Guid projectId, [FromQuery] string searchString)
{ {
try try
@ -128,6 +130,28 @@ namespace MarcoBMS.Services.Controllers
// Get the currently logged-in employee information // Get the currently logged-in employee information
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var projectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Projects.FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
});
var tenantTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId);
});
await Task.WhenAll(projectTask, tenantTask);
var project = projectTask.Result;
var tenant = tenantTask.Result;
if (project == null || tenant == null)
{
_logger.LogWarning("Project {ProjectId} not found in database for tenant {TenantId}", projectId, tenantId);
return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 404));
}
// Check if the logged-in employee has permission for the requested project // Check if the logged-in employee has permission for the requested project
var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId); var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId);
if (!hasProjectPermission) if (!hasProjectPermission)
@ -136,20 +160,36 @@ namespace MarcoBMS.Services.Controllers
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access denied", "User does not have access to view the employees for this project", 403)); return StatusCode(403, ApiResponse<object>.ErrorResponse("Access denied", "User does not have access to view the employees for this project", 403));
} }
var organizationQuery = _context.ProjectOrgMappings
.Include(po => po.ProjectService)
.Where(po => po.ProjectService != null && po.ProjectService.ProjectId == projectId);
if (loggedInEmployee.OrganizationId != project.PMCId && loggedInEmployee.OrganizationId != project.PromoterId && loggedInEmployee.OrganizationId != tenant.OrganizationId)
{
organizationQuery = organizationQuery.Where(po => po.ParentOrganizationId == loggedInEmployee.OrganizationId || po.OrganizationId == loggedInEmployee.OrganizationId);
}
var organizationIds = await organizationQuery.Select(po => po.OrganizationId).ToListAsync();
if (loggedInEmployee.OrganizationId == project.PMCId || loggedInEmployee.OrganizationId == project.PromoterId || loggedInEmployee.OrganizationId == tenant.OrganizationId)
{
organizationIds.Add(project.PMCId);
organizationIds.Add(project.PromoterId);
organizationIds.Add(tenant.OrganizationId);
}
// Fetch employees allocated to the project matching the search criteria // Fetch employees allocated to the project matching the search criteria
var employees = await _context.ProjectAllocations var employees = await _context.Employees
.AsNoTracking() // Improves performance by disabling change tracking for read-only query .AsNoTracking() // Improves performance by disabling change tracking for read-only query
.Include(pa => pa.Employee) .Include(e => e.JobRole)
.ThenInclude(e => e!.JobRole) .Where(e => (e.FirstName + " " + e.LastName).Contains(searchString) && organizationIds.Contains(e.OrganizationId))
.Where(pa => pa.ProjectId == projectId && pa.Employee != null &&
(pa.Employee.FirstName + " " + pa.Employee.LastName).Contains(searchString))
.Select(pa => pa.Employee!)
.ToListAsync(); .ToListAsync();
var result = employees.Select(e => _mapper.Map<EmployeeVM>(e)).Distinct().ToList();
_logger.LogInfo("Employees fetched for project {ProjectId} by user {EmployeeId}. Count: {Count}", projectId, loggedInEmployee.Id, employees.Count); _logger.LogInfo("Employees fetched for project {ProjectId} by user {EmployeeId}. Count: {Count}", projectId, loggedInEmployee.Id, employees.Count);
// Return the employee list wrapped in a successful API response // Return the employee list wrapped in a successful API response
return Ok(ApiResponse<object>.SuccessResponse(employees, "Employee list fetched successfully", 200)); return Ok(ApiResponse<object>.SuccessResponse(result, "Employee list fetched successfully", 200));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -324,7 +324,7 @@ namespace MarcoBMS.Services.Controllers
#region =================================================================== Project InfraStructure Get APIs =================================================================== #region =================================================================== Project InfraStructure Get APIs ===================================================================
[HttpGet("infra-details/{projectId}")] [HttpGet("infra-details/{projectId}")]
public async Task<IActionResult> GetInfraDetails(Guid projectId) public async Task<IActionResult> GetInfraDetails(Guid projectId, [FromQuery] Guid? serviceId)
{ {
// --- Step 1: Input Validation --- // --- Step 1: Input Validation ---
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -336,7 +336,7 @@ namespace MarcoBMS.Services.Controllers
// --- Step 2: Prepare data without I/O --- // --- Step 2: Prepare data without I/O ---
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _projectServices.GetInfraDetailsAsync(projectId, tenantId, loggedInEmployee); var response = await _projectServices.GetInfraDetailsAsync(projectId, serviceId, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }
@ -357,6 +357,7 @@ namespace MarcoBMS.Services.Controllers
var response = await _projectServices.GetWorkItemsAsync(workAreaId, serviceId, tenantId, loggedInEmployee); var response = await _projectServices.GetWorkItemsAsync(workAreaId, serviceId, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }
[HttpGet("tasks-employee/{employeeId}")] [HttpGet("tasks-employee/{employeeId}")]
public async Task<IActionResult> GetTasksByEmployee(Guid employeeId, [FromQuery] DateTime? fromDate, DateTime? toDate) public async Task<IActionResult> GetTasksByEmployee(Guid employeeId, [FromQuery] DateTime? fromDate, DateTime? toDate)
{ {

View File

@ -177,7 +177,10 @@ namespace Marco.Pms.Services.MappingProfiles
#region ======================================================= Employee ======================================================= #region ======================================================= Employee =======================================================
CreateMap<Employee, EmployeeVM>(); CreateMap<Employee, EmployeeVM>()
.ForMember(
dest => dest.JobRole,
opt => opt.MapFrom(src => src.JobRole != null ? src.JobRole.Name : ""));
CreateMap<CreateUserDto, Employee>(); CreateMap<CreateUserDto, Employee>();
CreateMap<MobileUserManageDto, Employee>(); CreateMap<MobileUserManageDto, Employee>();

View File

@ -1176,7 +1176,7 @@ namespace Marco.Pms.Services.Service
/// Retrieves the full infrastructure hierarchy (Buildings, Floors, Work Areas) for a project, /// Retrieves the full infrastructure hierarchy (Buildings, Floors, Work Areas) for a project,
/// including aggregated work summaries. /// including aggregated work summaries.
/// </summary> /// </summary>
public async Task<ApiResponse<object>> GetInfraDetailsAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee) public async Task<ApiResponse<object>> GetInfraDetailsAsync(Guid projectId, Guid? serviceId, Guid tenantId, Employee loggedInEmployee)
{ {
_logger.LogInfo("GetInfraDetails called for ProjectId: {ProjectId}", projectId); _logger.LogInfo("GetInfraDetails called for ProjectId: {ProjectId}", projectId);
@ -1223,19 +1223,74 @@ namespace Marco.Pms.Services.Service
if (cachedResult != null) if (cachedResult != null)
{ {
_logger.LogInfo("Cache HIT for infra details for ProjectId: {ProjectId}", projectId); _logger.LogInfo("Cache HIT for infra details for ProjectId: {ProjectId}", projectId);
return ApiResponse<object>.SuccessResponse(cachedResult, "Infra details fetched successfully from cache.", 200);
} }
_logger.LogInfo("Cache MISS for infra details for ProjectId: {ProjectId}. Fetching from database.", projectId); _logger.LogInfo("Cache MISS for infra details for ProjectId: {ProjectId}. Fetching from database.", projectId);
// --- Step 3: Fetch all required data from the database --- // --- Step 3: Fetch all required data from the database ---
if (cachedResult == null)
var buildingMongoList = await _generalHelper.GetProjectInfraFromDB(projectId); {
cachedResult = await _generalHelper.GetProjectInfraFromDB(projectId);
}
// --- Step 5: Proactively update the cache --- // --- Step 5: Proactively update the cache ---
//await _cache.SetBuildingInfra(projectId, buildingMongoList); //await _cache.SetBuildingInfra(projectId, buildingMongoList);
_logger.LogInfo("Infra details fetched successfully for ProjectId: {ProjectId}, Buildings: {Count}", projectId, buildingMongoList.Count); if (serviceId.HasValue)
return ApiResponse<object>.SuccessResponse(buildingMongoList, "Infra details fetched successfully", 200); {
var workAreaIds = cachedResult
.SelectMany(b => b.Floors)
.SelectMany(f => f.WorkAreas)
.Select(w => Guid.Parse(w.Id))
.ToList();
var workItems = await _context.WorkItems.Where(wi => workAreaIds.Contains(wi.WorkAreaId)
&& wi.ActivityMaster != null
&& wi.ActivityMaster.ActivityGroup != null
&& wi.ActivityMaster.ActivityGroup.ServiceId == serviceId)
.GroupBy(wi => wi.WorkAreaId)
.Select(g => new
{
WorkAreaId = g.Key,
PlannedWork = g.Sum(wi => wi.PlannedWork),
CompletedWork = g.Sum(wi => wi.CompletedWork)
})
.ToListAsync();
cachedResult = cachedResult.Select(b =>
{
double buildingPlanned = 0, buildingCompleted = 0;
var floors = b.Floors.Select(f =>
{
double floorPlanned = 0, floorCompleted = 0;
var workArea = f.WorkAreas.Select(wa =>
{
var workItem = workItems.FirstOrDefault(wi => wi.WorkAreaId == Guid.Parse(wa.Id));
wa.PlannedWork = workItem?.PlannedWork ?? 0;
wa.CompletedWork = workItem?.CompletedWork ?? 0;
floorPlanned += workItem?.PlannedWork ?? 0;
floorCompleted += workItem?.CompletedWork ?? 0;
return wa;
}).ToList();
f.PlannedWork = floorPlanned;
f.CompletedWork = floorCompleted;
buildingPlanned += floorPlanned;
buildingCompleted += floorCompleted;
return f;
}).ToList();
b.PlannedWork = buildingPlanned;
b.CompletedWork = buildingCompleted;
return b;
}).ToList();
}
_logger.LogInfo("Infra details fetched successfully for ProjectId: {ProjectId}, Buildings: {Count}", projectId, cachedResult.Count);
return ApiResponse<object>.SuccessResponse(cachedResult, "Infra details fetched successfully", 200);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -24,7 +24,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetProjectByEmployeeBasicAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetProjectByEmployeeBasicAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetInfraDetailsAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetInfraDetailsAsync(Guid projectId, Guid? serviceId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetWorkItemsAsync(Guid workAreaId, Guid? serviceId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetWorkItemsAsync(Guid workAreaId, Guid? serviceId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetTasksByEmployeeAsync(Guid employeeId, DateTime fromDate, DateTime toDate, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetTasksByEmployeeAsync(Guid employeeId, DateTime fromDate, DateTime toDate, Guid tenantId, Employee loggedInEmployee);
Task<ServiceResponse> ManageProjectInfraAsync(List<InfraDto> infraDtos, Guid tenantId, Employee loggedInEmployee); Task<ServiceResponse> ManageProjectInfraAsync(List<InfraDto> infraDtos, Guid tenantId, Employee loggedInEmployee);