From 9ef7946d8978d43575e25164bcc46b4778ff689b Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 6 Aug 2025 15:05:48 +0530 Subject: [PATCH] Teannt porfile API Optimized --- .../Controllers/TenantController.cs | 158 +++++++++++++++++- 1 file changed, 150 insertions(+), 8 deletions(-) diff --git a/Marco.Pms.Services/Controllers/TenantController.cs b/Marco.Pms.Services/Controllers/TenantController.cs index fa48dce..5a7b390 100644 --- a/Marco.Pms.Services/Controllers/TenantController.cs +++ b/Marco.Pms.Services/Controllers/TenantController.cs @@ -57,13 +57,13 @@ namespace Marco.Pms.Services.Controllers UserHelper userHelper, FeatureDetailsHelper featureDetailsHelper) { - _dbContextFactory = dbContextFactory; - _serviceScopeFactory = serviceScopeFactory; - _logger = logger; - _userManager = userManager; - _mapper = mapper; - _userHelper = userHelper; - _featureDetailsHelper = featureDetailsHelper; + _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); ; + _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory)); ; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); ; + _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); ; + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); ; + _userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper)); ; + _featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper)); ; } #region =================================================================== Tenant APIs =================================================================== @@ -211,7 +211,7 @@ namespace Marco.Pms.Services.Controllers // GET api//5 [HttpGet("details/{id}")] - public async Task GetDetails(Guid id) + private async Task GetTenantDetails(Guid id) { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); @@ -325,6 +325,148 @@ namespace Marco.Pms.Services.Controllers return Ok(ApiResponse.SuccessResponse(response, "Tenant profile fetched successfully", 200)); } + [HttpGet("details/{id}")] + public async Task 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.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(); + + 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.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.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(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(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>(plans); + + _logger.LogInfo("Tenant details fetched successfully for TenantId: {TenantId}", tenant.Id); + + return Ok(ApiResponse.SuccessResponse(response, "Tenant profile fetched successfully", 200)); + } + + // POST api/ [HttpPost("create")] public async Task CreateTenant([FromBody] CreateTenantDto model)