Optimized the CreateSubscriptionPlan API

This commit is contained in:
ashutosh.nehete 2025-08-06 11:08:24 +05:30
parent 640b80ea82
commit cbcae9fb57
2 changed files with 121 additions and 160 deletions

View File

@ -10,6 +10,6 @@
public enum PLAN_FREQUENCY public enum PLAN_FREQUENCY
{ {
MONTHLY = 0, QUARTERLY = 1, HALF_MONTHLY = 2, YEARLY = 3 MONTHLY = 0, QUARTERLY = 1, HALF_YEARLY = 2, YEARLY = 3
} }
} }

View File

@ -69,6 +69,7 @@ namespace Marco.Pms.Services.Controllers
/// <param name="pageSize">The number of records to return per page.</param> /// <param name="pageSize">The number of records to return per page.</param>
/// <param name="pageNumber">The page number to retrieve.</param> /// <param name="pageNumber">The page number to retrieve.</param>
/// <returns>A paginated list of tenants matching the criteria.</returns> /// <returns>A paginated list of tenants matching the criteria.</returns>
[HttpGet("list")] [HttpGet("list")]
public async Task<IActionResult> GetTenantList([FromQuery] string? searchString, string? filter, int pageSize = 20, int pageNumber = 1) public async Task<IActionResult> 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.MONTHLY => utcNow.AddMonths(1),
PLAN_FREQUENCY.QUARTERLY => utcNow.AddMonths(3), 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), PLAN_FREQUENCY.YEARLY => utcNow.AddMonths(12),
_ => utcNow // default if unknown _ => utcNow // default if unknown
}; };
@ -845,7 +846,7 @@ namespace Marco.Pms.Services.Controllers
{ {
PLAN_FREQUENCY.MONTHLY => currentSubscription.EndDate.AddMonths(1), PLAN_FREQUENCY.MONTHLY => currentSubscription.EndDate.AddMonths(1),
PLAN_FREQUENCY.QUARTERLY => currentSubscription.EndDate.AddMonths(3), 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), PLAN_FREQUENCY.YEARLY => currentSubscription.EndDate.AddMonths(12),
_ => currentSubscription.EndDate _ => currentSubscription.EndDate
}; };
@ -856,7 +857,7 @@ namespace Marco.Pms.Services.Controllers
{ {
PLAN_FREQUENCY.MONTHLY => utcNow.AddMonths(1), PLAN_FREQUENCY.MONTHLY => utcNow.AddMonths(1),
PLAN_FREQUENCY.QUARTERLY => utcNow.AddMonths(3), 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), PLAN_FREQUENCY.YEARLY => utcNow.AddMonths(12),
_ => utcNow _ => utcNow
}; };
@ -891,7 +892,7 @@ namespace Marco.Pms.Services.Controllers
{ {
PLAN_FREQUENCY.MONTHLY => utcNow.AddMonths(1), PLAN_FREQUENCY.MONTHLY => utcNow.AddMonths(1),
PLAN_FREQUENCY.QUARTERLY => utcNow.AddMonths(3), 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), PLAN_FREQUENCY.YEARLY => utcNow.AddMonths(12),
_ => utcNow _ => utcNow
}; };
@ -1047,8 +1048,6 @@ namespace Marco.Pms.Services.Controllers
} }
} }
#endregion #endregion
#region =================================================================== Subscription Plan APIs =================================================================== #region =================================================================== Subscription Plan APIs ===================================================================
@ -1110,183 +1109,81 @@ namespace Marco.Pms.Services.Controllers
} }
} }
/// <summary>
/// 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.
/// </summary>
[HttpPost("create/subscription-plan")] [HttpPost("create/subscription-plan")]
public async Task<IActionResult> CreateSubscriptionPlan1([FromBody] SubscriptionPlanDto model) public async Task<IActionResult> CreateSubscriptionPlan([FromBody] SubscriptionPlanDto model)
{ {
// Step 1: Authenticate and check permissions
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
await using var _context = await _dbContextFactory.CreateDbContextAsync(); await using var _context = await _dbContextFactory.CreateDbContextAsync();
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
var _permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>(); var _permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// 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 isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
var hasPermission = await _permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id); 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<object>.ErrorResponse("Access denied", "User does not have the required permissions for this action.", 403)); return StatusCode(403, ApiResponse<object>.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<SubscriptionPlan>(model); var plan = _mapper.Map<SubscriptionPlan>(model);
_context.SubscriptionPlans.Add(plan); _context.SubscriptionPlans.Add(plan);
List<SubscriptionPlanVM> response = new List<SubscriptionPlanVM>();
if (model.MonthlyPlan != null)
{
var currencyMaster = await _context.CurrencyMaster.AsNoTracking().FirstOrDefaultAsync(c => c.Id == model.MonthlyPlan.CurrencyId);
if (currencyMaster == null)
{
return NotFound(ApiResponse<object>.ErrorResponse("Currency not found", "Currency not found", 404));
}
var monthlyPlan = _mapper.Map<SubscriptionPlanDetails>(model.MonthlyPlan);
var features = _mapper.Map<FeatureDetails>(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<object>.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<SubscriptionPlanVM>(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<object>.ErrorResponse("Currency not found", "Currency not found", 404));
}
var quarterlyPlan = _mapper.Map<SubscriptionPlanDetails>(model.QuarterlyPlan);
var features = _mapper.Map<FeatureDetails>(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<object>.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<SubscriptionPlanVM>(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<object>.ErrorResponse("Currency not found", "Currency not found", 404));
}
var halfYearlyPlan = _mapper.Map<SubscriptionPlanDetails>(model.HalfYearlyPlan);
var features = _mapper.Map<FeatureDetails>(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<object>.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<SubscriptionPlanVM>(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<object>.ErrorResponse("Currency not found", "Currency not found", 404));
}
var yearlyPlan = _mapper.Map<SubscriptionPlanDetails>(model.YearlyPlan);
var features = _mapper.Map<FeatureDetails>(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<object>.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<SubscriptionPlanVM>(yearlyPlan);
VM.PlanName = plan.PlanName;
VM.Description = plan.Description;
VM.Currency = currencyMaster;
response.Add(VM);
}
try try
{ {
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
_logger.LogInfo("Base subscription plan {PlanId} saved by user {EmployeeId}.", plan.Id, loggedInEmployee.Id);
} }
catch (DbUpdateException dbEx) catch (DbUpdateException dbEx)
{ {
_logger.LogError(dbEx, "Database Exception occured while saving subscription plan"); _logger.LogError(dbEx, "Database exception occurred while saving the base subscription plan.");
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500)); return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error occurred", ExceptionMapper(dbEx), 500));
} }
return StatusCode(201, ApiResponse<object>.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<SubscriptionPlanVM>();
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<object>.SuccessResponse(responseList, "Plan Created Successfully", 201));
}
#endregion #endregion
@ -1347,7 +1244,6 @@ namespace Marco.Pms.Services.Controllers
return false; return false;
} }
} }
private TenantFilter? TryDeserializeFilter(string? filter) private TenantFilter? TryDeserializeFilter(string? filter)
{ {
if (string.IsNullOrWhiteSpace(filter)) if (string.IsNullOrWhiteSpace(filter))
@ -1387,6 +1283,71 @@ namespace Marco.Pms.Services.Controllers
return expenseFilter; return expenseFilter;
} }
/// <summary>
/// Handles the creation and persistence of SubscriptionPlanDetails for a particular frequency.
/// </summary>
private async Task<ApiResponse<SubscriptionPlanVM>> 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<SubscriptionPlanVM>.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<SubscriptionPlanVM>.ErrorResponse("Currency not found", "Specified currency not found", 404);
}
// Map to entity and create related feature details
var planDetails = _mapper.Map<SubscriptionPlanDetails>(model);
var features = _mapper.Map<FeatureDetails>(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<SubscriptionPlanVM>.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<SubscriptionPlanVM>(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<SubscriptionPlanVM>.ErrorResponse("Internal error occurred", ExceptionMapper(dbEx), 500);
}
return ApiResponse<SubscriptionPlanVM>.SuccessResponse(VM, "Success", 200);
}
#endregion #endregion
} }
} }