Teannt porfile API Optimized

This commit is contained in:
ashutosh.nehete 2025-08-06 15:05:48 +05:30
parent c210e6e3f2
commit 9ef7946d89

View File

@ -57,13 +57,13 @@ namespace Marco.Pms.Services.Controllers
UserHelper userHelper, UserHelper userHelper,
FeatureDetailsHelper featureDetailsHelper) FeatureDetailsHelper featureDetailsHelper)
{ {
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); ;
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory)); ;
_logger = logger; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); ;
_userManager = userManager; _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); ;
_mapper = mapper; _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); ;
_userHelper = userHelper; _userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper)); ;
_featureDetailsHelper = featureDetailsHelper; _featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper)); ;
} }
#region =================================================================== Tenant APIs =================================================================== #region =================================================================== Tenant APIs ===================================================================
@ -211,7 +211,7 @@ namespace Marco.Pms.Services.Controllers
// GET api/<TenantController>/5 // GET api/<TenantController>/5
[HttpGet("details/{id}")] [HttpGet("details/{id}")]
public async Task<IActionResult> GetDetails(Guid id) private async Task<IActionResult> GetTenantDetails(Guid id)
{ {
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
@ -325,6 +325,148 @@ namespace Marco.Pms.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(response, "Tenant profile fetched successfully", 200)); return Ok(ApiResponse<object>.SuccessResponse(response, "Tenant profile fetched successfully", 200));
} }
[HttpGet("details/{id}")]
public async Task<IActionResult> GetTenantDetailsAsync(Guid id)
{
_logger.LogInfo("GetTenantDetails started for TenantId: {TenantId}", id);
// Get currently logged-in employee info
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
if (loggedInEmployee == null)
{
_logger.LogWarning("No logged-in employee found for the request.");
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", "User must be logged in.", 401));
}
// Check permissions using a single service scope to avoid overhead
bool hasManagePermission, hasModifyPermission, hasViewPermission;
using (var scope = _serviceScopeFactory.CreateScope())
{
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
var manageTask = permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id);
var modifyTask = permissionService.HasPermission(PermissionsMaster.ModifyTenant, loggedInEmployee.Id);
var viewTask = permissionService.HasPermission(PermissionsMaster.ViewTenant, loggedInEmployee.Id);
await Task.WhenAll(manageTask, modifyTask, viewTask);
hasManagePermission = manageTask.Result;
hasModifyPermission = modifyTask.Result;
hasViewPermission = viewTask.Result;
}
if (!hasManagePermission && !hasModifyPermission && !hasViewPermission)
{
_logger.LogWarning("Permission denied: User {EmployeeId} attempted to access tenant details without the required permissions.", loggedInEmployee.Id);
return StatusCode(403,
ApiResponse<object>.ErrorResponse("Access denied", "User does not have the required permissions for this action.", 403));
}
// Create a single DbContext for main tenant fetch and related data requests
await using var _context = await _dbContextFactory.CreateDbContextAsync();
// Fetch tenant details with related Industry and TenantStatus in a single query
var tenant = await _context.Tenants
.Include(t => t.Industry)
.Include(t => t.TenantStatus)
.AsNoTracking()
.FirstOrDefaultAsync(t => t.Id == id);
if (tenant == null)
{
_logger.LogWarning("Tenant {TenantId} not found in database", id);
return NotFound(ApiResponse<object>.ErrorResponse("Tenant not found", "Tenant not found", 404));
}
_logger.LogInfo("Tenant {TenantId} found.", tenant.Id);
// Fetch dependent data in parallel to improve performance
var employeesTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Employees
.Include(e => e.ApplicationUser)
.Where(e => e.TenantId == tenant.Id)
.AsNoTracking()
.ToListAsync();
});
var createdByTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Employees
.AsNoTracking()
.Where(e => e.Id == tenant.CreatedById)
.Select(e => _mapper.Map<BasicEmployeeVM>(e))
.FirstOrDefaultAsync();
});
var plansTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.TenantSubscriptions
.Include(sp => sp.CreatedBy)
.ThenInclude(e => e!.JobRole)
.Include(sp => sp.UpdatedBy)
.ThenInclude(e => e!.JobRole)
.Include(sp => sp.Currency)
.Include(ts => ts.Plan).ThenInclude(sp => sp.Plan)
.Where(ts => ts.TenantId == tenant.Id && ts.Plan != null)
.AsNoTracking()
.OrderBy(ts => ts.CreatedBy)
.ToListAsync();
});
var projectsTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.Projects
.Include(p => p.ProjectStatus)
.Where(p => p.TenantId == tenant.Id)
.AsNoTracking()
.ToListAsync();
});
await Task.WhenAll(employeesTask, createdByTask, plansTask, projectsTask);
var employees = employeesTask.Result;
var createdBy = createdByTask.Result;
var plans = plansTask.Result;
var projects = projectsTask.Result;
// Calculate active/inactive employees count
var activeEmployeesCount = employees.Count(e => e.IsActive);
var inActiveEmployeesCount = employees.Count - activeEmployeesCount;
// Filter current active (non-cancelled) subscription plan
var currentPlan = plans.FirstOrDefault(ts => !ts.IsCancelled);
var expiryDate = currentPlan?.EndDate;
var nextBillingDate = currentPlan?.NextBillingDate;
// Map Tenant entity to TenantDetailsVM response model
var response = _mapper.Map<TenantDetailsVM>(tenant);
response.ActiveEmployees = activeEmployeesCount;
response.InActiveEmployees = inActiveEmployeesCount;
// Count projects by status
response.ActiveProjects = projects.Count(p => p.ProjectStatusId == projectActiveStatus);
response.InProgressProjects = projects.Count(p => p.ProjectStatusId == projectInProgressStatus);
response.OnHoldProjects = projects.Count(p => p.ProjectStatusId == projectOnHoldStatus);
response.InActiveProjects = projects.Count(p => p.ProjectStatusId == projectInActiveStatus);
response.CompletedProjects = projects.Count(p => p.ProjectStatusId == projectCompletedStatus);
response.ExpiryDate = expiryDate;
response.NextBillingDate = nextBillingDate;
response.CreatedBy = createdBy;
// Map subscription history plans to DTO
response.SubscriptionHistery = _mapper.Map<List<SubscriptionPlanDetailsVM>>(plans);
_logger.LogInfo("Tenant details fetched successfully for TenantId: {TenantId}", tenant.Id);
return Ok(ApiResponse<object>.SuccessResponse(response, "Tenant profile fetched successfully", 200));
}
// POST api/<TenantController> // POST api/<TenantController>
[HttpPost("create")] [HttpPost("create")]
public async Task<IActionResult> CreateTenant([FromBody] CreateTenantDto model) public async Task<IActionResult> CreateTenant([FromBody] CreateTenantDto model)