From cbcae9fb5780e45cc5757432edfed7894f81094e Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 6 Aug 2025 11:08:24 +0530 Subject: [PATCH] Optimized the CreateSubscriptionPlan API --- .../TenantModels/SubscriptionPlan.cs | 2 +- .../Controllers/TenantController.cs | 279 ++++++++---------- 2 files changed, 121 insertions(+), 160 deletions(-) diff --git a/Marco.Pms.Model/TenantModels/SubscriptionPlan.cs b/Marco.Pms.Model/TenantModels/SubscriptionPlan.cs index e4c1f37..4bdf838 100644 --- a/Marco.Pms.Model/TenantModels/SubscriptionPlan.cs +++ b/Marco.Pms.Model/TenantModels/SubscriptionPlan.cs @@ -10,6 +10,6 @@ public enum PLAN_FREQUENCY { - MONTHLY = 0, QUARTERLY = 1, HALF_MONTHLY = 2, YEARLY = 3 + MONTHLY = 0, QUARTERLY = 1, HALF_YEARLY = 2, YEARLY = 3 } } diff --git a/Marco.Pms.Services/Controllers/TenantController.cs b/Marco.Pms.Services/Controllers/TenantController.cs index 21fa0c8..c8e712b 100644 --- a/Marco.Pms.Services/Controllers/TenantController.cs +++ b/Marco.Pms.Services/Controllers/TenantController.cs @@ -69,6 +69,7 @@ namespace Marco.Pms.Services.Controllers /// The number of records to return per page. /// The page number to retrieve. /// A paginated list of tenants matching the criteria. + [HttpGet("list")] public async Task GetTenantList([FromQuery] string? searchString, string? filter, int pageSize = 20, int pageNumber = 1) { @@ -617,7 +618,7 @@ namespace Marco.Pms.Services.Controllers { PLAN_FREQUENCY.MONTHLY => utcNow.AddMonths(1), PLAN_FREQUENCY.QUARTERLY => utcNow.AddMonths(3), - PLAN_FREQUENCY.HALF_MONTHLY => utcNow.AddMonths(6), + PLAN_FREQUENCY.HALF_YEARLY => utcNow.AddMonths(6), PLAN_FREQUENCY.YEARLY => utcNow.AddMonths(12), _ => utcNow // default if unknown }; @@ -845,7 +846,7 @@ namespace Marco.Pms.Services.Controllers { PLAN_FREQUENCY.MONTHLY => currentSubscription.EndDate.AddMonths(1), PLAN_FREQUENCY.QUARTERLY => currentSubscription.EndDate.AddMonths(3), - PLAN_FREQUENCY.HALF_MONTHLY => currentSubscription.EndDate.AddMonths(6), + PLAN_FREQUENCY.HALF_YEARLY => currentSubscription.EndDate.AddMonths(6), PLAN_FREQUENCY.YEARLY => currentSubscription.EndDate.AddMonths(12), _ => currentSubscription.EndDate }; @@ -856,7 +857,7 @@ namespace Marco.Pms.Services.Controllers { PLAN_FREQUENCY.MONTHLY => utcNow.AddMonths(1), PLAN_FREQUENCY.QUARTERLY => utcNow.AddMonths(3), - PLAN_FREQUENCY.HALF_MONTHLY => utcNow.AddMonths(6), + PLAN_FREQUENCY.HALF_YEARLY => utcNow.AddMonths(6), PLAN_FREQUENCY.YEARLY => utcNow.AddMonths(12), _ => utcNow }; @@ -891,7 +892,7 @@ namespace Marco.Pms.Services.Controllers { PLAN_FREQUENCY.MONTHLY => utcNow.AddMonths(1), PLAN_FREQUENCY.QUARTERLY => utcNow.AddMonths(3), - PLAN_FREQUENCY.HALF_MONTHLY => utcNow.AddMonths(6), + PLAN_FREQUENCY.HALF_YEARLY => utcNow.AddMonths(6), PLAN_FREQUENCY.YEARLY => utcNow.AddMonths(12), _ => utcNow }; @@ -1047,8 +1048,6 @@ namespace Marco.Pms.Services.Controllers } } - - #endregion #region =================================================================== Subscription Plan APIs =================================================================== @@ -1110,183 +1109,81 @@ namespace Marco.Pms.Services.Controllers } } - - + /// + /// Creates a new subscription plan with details for each frequency (monthly, quarterly, half-yearly, yearly). + /// Only employees with root status and 'ManageTenants' permission can create plans. + /// [HttpPost("create/subscription-plan")] - public async Task CreateSubscriptionPlan1([FromBody] SubscriptionPlanDto model) + public async Task CreateSubscriptionPlan([FromBody] SubscriptionPlanDto model) { + // Step 1: Authenticate and check permissions var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); await using var _context = await _dbContextFactory.CreateDbContextAsync(); using var scope = _serviceScopeFactory.CreateScope(); - var _permissionService = scope.ServiceProvider.GetRequiredService(); - // A root user should have access regardless of the specific permission. + // Permission check: root user or explicit ManageTenants permission var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false; var hasPermission = await _permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id); - - if (!hasPermission || !isRootUser) + if (!(hasPermission && isRootUser)) { - _logger.LogWarning("Permission denied: User {EmployeeId} attempted to list tenants without 'ManageTenants' permission or root access.", loggedInEmployee.Id); + _logger.LogWarning("Permission denied: User {EmployeeId} attempted to create a subscription plan.", loggedInEmployee.Id); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "User does not have the required permissions for this action.", 403)); } + _logger.LogInfo("User {EmployeeId} authorized to create subscription plan.", loggedInEmployee.Id); + + // Step 2: Map DTO to entity and persist the base SubscriptionPlan var plan = _mapper.Map(model); _context.SubscriptionPlans.Add(plan); - List response = new List(); - - if (model.MonthlyPlan != null) - { - var currencyMaster = await _context.CurrencyMaster.AsNoTracking().FirstOrDefaultAsync(c => c.Id == model.MonthlyPlan.CurrencyId); - if (currencyMaster == null) - { - return NotFound(ApiResponse.ErrorResponse("Currency not found", "Currency not found", 404)); - } - - var monthlyPlan = _mapper.Map(model.MonthlyPlan); - var features = _mapper.Map(model.MonthlyPlan.Features); - - try - { - await _featureDetailsHelper.AddFeatureDetails(features); - } - catch (Exception ex) - { - _logger.LogError(ex, "Exception occured while saving feature in mongoDB"); - return StatusCode(500, ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500)); - } - - monthlyPlan.PlanId = plan.Id; - monthlyPlan.Frequency = PLAN_FREQUENCY.MONTHLY; - monthlyPlan.FeaturesId = features.Id; - monthlyPlan.CreatedById = loggedInEmployee.Id; - monthlyPlan.CreateAt = DateTime.UtcNow; - - _context.SubscriptionPlanDetails.Add(monthlyPlan); - var VM = _mapper.Map(monthlyPlan); - VM.PlanName = plan.PlanName; - VM.Description = plan.Description; - VM.Currency = currencyMaster; - response.Add(VM); - } - - if (model.QuarterlyPlan != null) - { - var currencyMaster = await _context.CurrencyMaster.AsNoTracking().FirstOrDefaultAsync(c => c.Id == model.QuarterlyPlan.CurrencyId); - if (currencyMaster == null) - { - return NotFound(ApiResponse.ErrorResponse("Currency not found", "Currency not found", 404)); - } - - var quarterlyPlan = _mapper.Map(model.QuarterlyPlan); - var features = _mapper.Map(model.QuarterlyPlan.Features); - - try - { - await _featureDetailsHelper.AddFeatureDetails(features); - } - catch (Exception ex) - { - _logger.LogError(ex, "Exception occured while saving feature in mongoDB"); - return StatusCode(500, ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500)); - } - - quarterlyPlan.PlanId = plan.Id; - quarterlyPlan.Frequency = PLAN_FREQUENCY.QUARTERLY; - quarterlyPlan.FeaturesId = features.Id; - quarterlyPlan.CreatedById = loggedInEmployee.Id; - quarterlyPlan.CreateAt = DateTime.UtcNow; - - _context.SubscriptionPlanDetails.Add(quarterlyPlan); - var VM = _mapper.Map(quarterlyPlan); - VM.PlanName = plan.PlanName; - VM.Description = plan.Description; - VM.Currency = currencyMaster; - response.Add(VM); - } - if (model.HalfYearlyPlan != null) - { - var currencyMaster = await _context.CurrencyMaster.AsNoTracking().FirstOrDefaultAsync(c => c.Id == model.HalfYearlyPlan.CurrencyId); - if (currencyMaster == null) - { - return NotFound(ApiResponse.ErrorResponse("Currency not found", "Currency not found", 404)); - } - - var halfYearlyPlan = _mapper.Map(model.HalfYearlyPlan); - var features = _mapper.Map(model.HalfYearlyPlan.Features); - - try - { - await _featureDetailsHelper.AddFeatureDetails(features); - } - catch (Exception ex) - { - _logger.LogError(ex, "Exception occured while saving feature in mongoDB"); - return StatusCode(500, ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500)); - } - - halfYearlyPlan.PlanId = plan.Id; - halfYearlyPlan.Frequency = PLAN_FREQUENCY.HALF_MONTHLY; - halfYearlyPlan.FeaturesId = features.Id; - halfYearlyPlan.CreatedById = loggedInEmployee.Id; - halfYearlyPlan.CreateAt = DateTime.UtcNow; - - _context.SubscriptionPlanDetails.Add(halfYearlyPlan); - var VM = _mapper.Map(halfYearlyPlan); - VM.PlanName = plan.PlanName; - VM.Description = plan.Description; - VM.Currency = currencyMaster; - response.Add(VM); - } - if (model.YearlyPlan != null) - { - var currencyMaster = await _context.CurrencyMaster.AsNoTracking().FirstOrDefaultAsync(c => c.Id == model.YearlyPlan.CurrencyId); - if (currencyMaster == null) - { - return NotFound(ApiResponse.ErrorResponse("Currency not found", "Currency not found", 404)); - } - - var yearlyPlan = _mapper.Map(model.YearlyPlan); - var features = _mapper.Map(model.YearlyPlan.Features); - - try - { - await _featureDetailsHelper.AddFeatureDetails(features); - } - catch (Exception ex) - { - _logger.LogError(ex, "Exception occured while saving feature in mongoDB"); - return StatusCode(500, ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500)); - } - - yearlyPlan.PlanId = plan.Id; - yearlyPlan.Frequency = PLAN_FREQUENCY.YEARLY; - yearlyPlan.FeaturesId = features.Id; - yearlyPlan.CreatedById = loggedInEmployee.Id; - yearlyPlan.CreateAt = DateTime.UtcNow; - - _context.SubscriptionPlanDetails.Add(yearlyPlan); - var VM = _mapper.Map(yearlyPlan); - VM.PlanName = plan.PlanName; - VM.Description = plan.Description; - VM.Currency = currencyMaster; - response.Add(VM); - } - try { await _context.SaveChangesAsync(); + _logger.LogInfo("Base subscription plan {PlanId} saved by user {EmployeeId}.", plan.Id, loggedInEmployee.Id); } catch (DbUpdateException dbEx) { - _logger.LogError(dbEx, "Database Exception occured while saving subscription plan"); - return StatusCode(500, ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500)); + _logger.LogError(dbEx, "Database exception occurred while saving the base subscription plan."); + return StatusCode(500, ApiResponse.ErrorResponse("Internal error occurred", ExceptionMapper(dbEx), 500)); } - return StatusCode(201, ApiResponse.SuccessResponse(response, "Plan Created Successfully", 201)); - } + // Step 3: Prepare tasks for each plan frequency + var frequencies = new[] + { + (model.MonthlyPlan, PLAN_FREQUENCY.MONTHLY), + (model.QuarterlyPlan, PLAN_FREQUENCY.QUARTERLY), + (model.HalfYearlyPlan, PLAN_FREQUENCY.HALF_YEARLY), + (model.YearlyPlan, PLAN_FREQUENCY.YEARLY) + }; + var tasks = frequencies + .Select(f => CreateSubscriptionPlanDetails(f.Item1, plan, loggedInEmployee, f.Item2)) + .ToArray(); + + // Await all frequency tasks + await Task.WhenAll(tasks); + + // Step 4: Collect successful plan details or return errors if any + var responseList = new List(); + for (int i = 0; i < tasks.Length; i++) + { + var result = tasks[i].Result; + if (result.StatusCode == 200 && result.Data != null) + { + responseList.Add(result.Data); + } + // status code 400 - skip, e.g. missing data for this frequency + else if (result.StatusCode != 400) + { + _logger.LogWarning("Failed to create plan details for {Frequency}: {Error}", frequencies[i].Item2, result.Message); + return StatusCode(result.StatusCode, result); + } + } + + _logger.LogInfo("Subscription plan {PlanId} created successfully with {DetailCount} details.", plan.Id, responseList.Count); + return StatusCode(201, ApiResponse.SuccessResponse(responseList, "Plan Created Successfully", 201)); + } #endregion @@ -1347,7 +1244,6 @@ namespace Marco.Pms.Services.Controllers return false; } } - private TenantFilter? TryDeserializeFilter(string? filter) { if (string.IsNullOrWhiteSpace(filter)) @@ -1387,6 +1283,71 @@ namespace Marco.Pms.Services.Controllers return expenseFilter; } + /// + /// Handles the creation and persistence of SubscriptionPlanDetails for a particular frequency. + /// + private async Task> CreateSubscriptionPlanDetails(SubscriptionPlanDetailsDto? model, SubscriptionPlan plan, Employee loggedInEmployee, PLAN_FREQUENCY frequency) + { + if (model == null) + { + _logger.LogInfo("No plan detail provided for {Frequency} - skipping.", frequency); + return ApiResponse.ErrorResponse("Invalid", "No data provided for this frequency", 400); + } + + await using var _dbContext = await _dbContextFactory.CreateDbContextAsync(); + + // Fetch currency master record + var currencyMaster = await _dbContext.CurrencyMaster.AsNoTracking().FirstOrDefaultAsync(c => c.Id == model.CurrencyId); + if (currencyMaster == null) + { + _logger.LogWarning("Currency with Id {CurrencyId} not found for plan {PlanId}/{Frequency}.", model.CurrencyId, plan.Id, frequency); + return ApiResponse.ErrorResponse("Currency not found", "Specified currency not found", 404); + } + + // Map to entity and create related feature details + var planDetails = _mapper.Map(model); + var features = _mapper.Map(model.Features); + + try + { + await _featureDetailsHelper.AddFeatureDetails(features); + _logger.LogInfo("FeatureDetails for plan {PlanId}/{Frequency} saved in MongoDB.", plan.Id, frequency); + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occurred while saving features in MongoDB for {PlanId}/{Frequency}.", plan.Id, frequency); + return ApiResponse.ErrorResponse("Internal error occurred", ExceptionMapper(ex), 500); + } + + planDetails.PlanId = plan.Id; + planDetails.Frequency = frequency; + planDetails.FeaturesId = features.Id; + planDetails.CreatedById = loggedInEmployee.Id; + planDetails.CreateAt = DateTime.UtcNow; + + _dbContext.SubscriptionPlanDetails.Add(planDetails); + + // Prepare view model + var VM = _mapper.Map(planDetails); + VM.PlanName = plan.PlanName; + VM.Description = plan.Description; + VM.Features = features; + VM.Currency = currencyMaster; + + try + { + await _dbContext.SaveChangesAsync(); + _logger.LogInfo("Subscription plan details for {PlanId}/{Frequency} saved to SQL.", plan.Id, frequency); + } + catch (DbUpdateException dbEx) + { + _logger.LogError(dbEx, "Database exception occurred while saving plan details for {PlanId}/{Frequency}.", plan.Id, frequency); + return ApiResponse.ErrorResponse("Internal error occurred", ExceptionMapper(dbEx), 500); + } + + return ApiResponse.SuccessResponse(VM, "Success", 200); + } + #endregion } }