Saving the tenant if payment is successfully
This commit is contained in:
parent
ac4da0c930
commit
78575337ec
@ -2,8 +2,10 @@
|
|||||||
{
|
{
|
||||||
public class PaymentVerificationRequest
|
public class PaymentVerificationRequest
|
||||||
{
|
{
|
||||||
public string? OrderId { get; set; }
|
public required Guid TenantEnquireId { get; set; }
|
||||||
public string? PaymentId { get; set; }
|
public required Guid PlanId { get; set; }
|
||||||
public string? Signature { get; set; }
|
public required string OrderId { get; set; }
|
||||||
|
public required string PaymentId { get; set; }
|
||||||
|
public required string Signature { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using Marco.Pms.Model.Employees;
|
using Marco.Pms.Model.Employees;
|
||||||
using Marco.Pms.Model.Master;
|
using Marco.Pms.Model.Master;
|
||||||
|
using Marco.Pms.Model.PaymentGetway;
|
||||||
using Marco.Pms.Model.Utilities;
|
using Marco.Pms.Model.Utilities;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
@ -32,6 +33,11 @@ namespace Marco.Pms.Model.TenantModels
|
|||||||
public DateTime? CancellationDate { get; set; }
|
public DateTime? CancellationDate { get; set; }
|
||||||
public bool AutoRenew { get; set; } = true;
|
public bool AutoRenew { get; set; } = true;
|
||||||
public bool IsCancelled { get; set; } = false;
|
public bool IsCancelled { get; set; } = false;
|
||||||
|
public Guid PaymentDetailId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey("PaymentDetailId")]
|
||||||
|
[ValidateNever]
|
||||||
|
public PaymentDetail? PaymentDetail { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
public DateTime? UpdateAt { get; set; }
|
public DateTime? UpdateAt { get; set; }
|
||||||
public Guid CreatedById { get; set; }
|
public Guid CreatedById { get; set; }
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class PaymentDetailsVM
|
public class PaymentDetailsVM
|
||||||
{
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
public RazorpayPaymentDetails? RazorpayPaymentDetails { get; set; }
|
public RazorpayPaymentDetails? RazorpayPaymentDetails { get; set; }
|
||||||
public RazorpayOrderDetails? RazorpayOrderDetails { get; set; }
|
public RazorpayOrderDetails? RazorpayOrderDetails { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,15 +14,17 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
private readonly UserHelper _userHelper;
|
private readonly UserHelper _userHelper;
|
||||||
private readonly ILoggingService _logger;
|
private readonly ILoggingService _logger;
|
||||||
private readonly IRazorpayService _razorpayService;
|
private readonly IRazorpayService _razorpayService;
|
||||||
|
private readonly ITenantService _tenantService;
|
||||||
private readonly Guid tenantId;
|
private readonly Guid tenantId;
|
||||||
private readonly Guid organizaionId;
|
private readonly Guid organizaionId;
|
||||||
public PaymentController(UserHelper userHelper, ILoggingService logger, IRazorpayService razorpayService)
|
public PaymentController(UserHelper userHelper, ILoggingService logger, IRazorpayService razorpayService, ITenantService tenantService)
|
||||||
{
|
{
|
||||||
_userHelper = userHelper;
|
_userHelper = userHelper;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_razorpayService = razorpayService;
|
_razorpayService = razorpayService;
|
||||||
tenantId = userHelper.GetTenantId();
|
tenantId = userHelper.GetTenantId();
|
||||||
organizaionId = userHelper.GetCurrentOrganizationId();
|
organizaionId = userHelper.GetCurrentOrganizationId();
|
||||||
|
_tenantService = tenantService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("create-order")]
|
[HttpPost("create-order")]
|
||||||
@ -77,6 +79,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
|
|
||||||
// Fetch complete payment details from Razorpay including card details
|
// Fetch complete payment details from Razorpay including card details
|
||||||
var response = await _razorpayService.GetPaymentDetails(request.PaymentId);
|
var response = await _razorpayService.GetPaymentDetails(request.PaymentId);
|
||||||
|
var tenant = await _tenantService.CreateTenantAsync(request.TenantEnquireId, response.Id, request.PlanId);
|
||||||
|
|
||||||
_logger.LogInfo("Invoice generated and saved for OrderId: {OrderId}", request.OrderId);
|
_logger.LogInfo("Invoice generated and saved for OrderId: {OrderId}", request.OrderId);
|
||||||
|
|
||||||
|
|||||||
@ -923,6 +923,15 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
// Create db context asynchronously for optimized resource use
|
// Create db context asynchronously for optimized resource use
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
|
// 2. --- VALIDATION ---
|
||||||
|
// Check if a user with the same email already exists.
|
||||||
|
var existingUser = await _userManager.FindByEmailAsync(model.Email);
|
||||||
|
if (existingUser != null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Tenant creation failed for email {Email}: an application user with this email already exists.", model.Email);
|
||||||
|
return StatusCode(409, ApiResponse<object>.ErrorResponse("Tenant cannot be created", "A user with the specified email already exists.", 409));
|
||||||
|
}
|
||||||
|
|
||||||
// Map DTO to domain model and assign new Guid
|
// Map DTO to domain model and assign new Guid
|
||||||
var tenantEnquire = _mapper.Map<TenantEnquire>(model);
|
var tenantEnquire = _mapper.Map<TenantEnquire>(model);
|
||||||
tenantEnquire.Id = Guid.NewGuid();
|
tenantEnquire.Id = Guid.NewGuid();
|
||||||
|
|||||||
@ -185,6 +185,7 @@ builder.Services.AddScoped<IDirectoryService, DirectoryService>();
|
|||||||
builder.Services.AddScoped<IFirebaseService, FirebaseService>();
|
builder.Services.AddScoped<IFirebaseService, FirebaseService>();
|
||||||
builder.Services.AddScoped<IRazorpayService, RazorpayService>();
|
builder.Services.AddScoped<IRazorpayService, RazorpayService>();
|
||||||
builder.Services.AddScoped<IAesEncryption, AesEncryption>();
|
builder.Services.AddScoped<IAesEncryption, AesEncryption>();
|
||||||
|
builder.Services.AddScoped<ITenantService, TenantService>();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
|||||||
@ -91,6 +91,8 @@ namespace Marco.Pms.Services.Service
|
|||||||
// Extract customer name from notes or fetch from customer API
|
// Extract customer name from notes or fetch from customer API
|
||||||
string customerName = ExtractCustomerName(payment);
|
string customerName = ExtractCustomerName(payment);
|
||||||
|
|
||||||
|
Guid paymentDetailsId = Guid.NewGuid();
|
||||||
|
|
||||||
// Map to custom model with all details
|
// Map to custom model with all details
|
||||||
var paymentDetails = new RazorpayPaymentDetails
|
var paymentDetails = new RazorpayPaymentDetails
|
||||||
{
|
{
|
||||||
@ -143,6 +145,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
var response = new PaymentDetailsVM
|
var response = new PaymentDetailsVM
|
||||||
{
|
{
|
||||||
|
Id = paymentDetailsId,
|
||||||
RazorpayPaymentDetails = paymentDetails,
|
RazorpayPaymentDetails = paymentDetails,
|
||||||
RazorpayOrderDetails = razorpayOrderDetails
|
RazorpayOrderDetails = razorpayOrderDetails
|
||||||
};
|
};
|
||||||
@ -153,7 +156,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
var paymentDetail = new PaymentDetail
|
var paymentDetail = new PaymentDetail
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Id = paymentDetailsId,
|
||||||
PaymentId = paymentDetails.PaymentId ?? "",
|
PaymentId = paymentDetails.PaymentId ?? "",
|
||||||
OrderId = paymentDetails.OrderId ?? "",
|
OrderId = paymentDetails.OrderId ?? "",
|
||||||
Status = paymentDetails.Status ?? "",
|
Status = paymentDetails.Status ?? "",
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
using Marco.Pms.Model.Utilities;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||||
|
{
|
||||||
|
public interface ITenantService
|
||||||
|
{
|
||||||
|
Task<ApiResponse<object>> CreateTenantAsync(Guid enquireId, Guid paymentDetailId, Guid planId);
|
||||||
|
}
|
||||||
|
}
|
||||||
682
Marco.Pms.Services/Service/TenantService.cs
Normal file
682
Marco.Pms.Services/Service/TenantService.cs
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Marco.Pms.DataAccess.Data;
|
||||||
|
using Marco.Pms.Helpers.Utility;
|
||||||
|
using Marco.Pms.Model.Dtos.Tenant;
|
||||||
|
using Marco.Pms.Model.Employees;
|
||||||
|
using Marco.Pms.Model.Entitlements;
|
||||||
|
using Marco.Pms.Model.OrganizationModel;
|
||||||
|
using Marco.Pms.Model.Projects;
|
||||||
|
using Marco.Pms.Model.Roles;
|
||||||
|
using Marco.Pms.Model.TenantModels;
|
||||||
|
using Marco.Pms.Model.TenantModels.MongoDBModel;
|
||||||
|
using Marco.Pms.Model.Utilities;
|
||||||
|
using Marco.Pms.Model.ViewModels.Activities;
|
||||||
|
using Marco.Pms.Model.ViewModels.Tenant;
|
||||||
|
using Marco.Pms.Services.Helpers;
|
||||||
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||||
|
using MarcoBMS.Services.Helpers;
|
||||||
|
using MarcoBMS.Services.Service;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Services.Service
|
||||||
|
{
|
||||||
|
public class TenantService : ITenantService
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly ILoggingService _logger;
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
private readonly UserHelper _userHelper;
|
||||||
|
private readonly FeatureDetailsHelper _featureDetailsHelper;
|
||||||
|
|
||||||
|
private readonly static Guid projectActiveStatus = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731");
|
||||||
|
private readonly static Guid projectInProgressStatus = Guid.Parse("cdad86aa-8a56-4ff4-b633-9c629057dfef");
|
||||||
|
private readonly static Guid projectOnHoldStatus = Guid.Parse("603e994b-a27f-4e5d-a251-f3d69b0498ba");
|
||||||
|
private readonly static Guid projectInActiveStatus = Guid.Parse("ef1c356e-0fe0-42df-a5d3-8daee355492d");
|
||||||
|
private readonly static Guid projectCompletedStatus = Guid.Parse("33deaef9-9af1-4f2a-b443-681ea0d04f81");
|
||||||
|
|
||||||
|
private readonly static Guid tenantActiveStatus = Guid.Parse("62b05792-5115-4f99-8ff5-e8374859b191");
|
||||||
|
private readonly static Guid activePlanStatus = Guid.Parse("cd3a68ea-41fd-42f0-bd0c-c871c7337727");
|
||||||
|
private readonly static Guid EmployeeFeatureId = Guid.Parse("81ab8a87-8ccd-4015-a917-0627cee6a100");
|
||||||
|
private readonly static string AdminRoleName = "Admin";
|
||||||
|
public TenantService(IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
||||||
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
ILoggingService logger,
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
IMapper 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResponse<object>> CreateTenantAsync(Guid enquireId, Guid paymentDetailId, Guid planId)
|
||||||
|
{
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
|
var _configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
|
||||||
|
var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
|
||||||
|
|
||||||
|
var tenantEnquireTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await context.TenantEnquires.FirstOrDefaultAsync(te => te.Id == enquireId);
|
||||||
|
});
|
||||||
|
var paymentDetailTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await context.PaymentDetails.FirstOrDefaultAsync(pd => pd.Id == paymentDetailId);
|
||||||
|
});
|
||||||
|
var subscriptionPlanTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await context.SubscriptionPlanDetails.Include(sp => sp.Plan).FirstOrDefaultAsync(sp => sp.Id == planId);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(tenantEnquireTask, paymentDetailTask, subscriptionPlanTask);
|
||||||
|
|
||||||
|
var tenantEnquire = tenantEnquireTask.Result;
|
||||||
|
var paymentDetail = paymentDetailTask.Result;
|
||||||
|
var subscriptionPlan = subscriptionPlanTask.Result;
|
||||||
|
|
||||||
|
if (tenantEnquire == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Tenant Enquire {TenantEnquireId} not found in database", enquireId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Tenant Enquire not found", "Tenant Enquire not found", 404);
|
||||||
|
}
|
||||||
|
if (paymentDetail == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Payment Details {PaymentDetailsId} not found in database", paymentDetailId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Payment Details not found", "Payment Details not found", 404);
|
||||||
|
}
|
||||||
|
if (subscriptionPlan == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Subscription plan {PlanId} not found in database", planId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Subscription plan not found", "Subscription plan not found", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingUser = await _userManager.FindByEmailAsync(tenantEnquire.Email);
|
||||||
|
if (existingUser != null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Tenant creation failed for email {Email}: an application user with this email already exists.", tenantEnquire.Email);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Tenant cannot be created", "A user with the specified email already exists.", 409);
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Guid employeeId = Guid.NewGuid();
|
||||||
|
DateTime onBoardingDate = DateTime.UtcNow;
|
||||||
|
Guid tenantId = Guid.NewGuid();
|
||||||
|
|
||||||
|
// Get last SPRID and increment for new organization
|
||||||
|
var lastOrganization = await _context.Organizations.OrderByDescending(sp => sp.SPRID).FirstOrDefaultAsync();
|
||||||
|
double lastSPRID = lastOrganization?.SPRID ?? 5400;
|
||||||
|
|
||||||
|
// Map DTO to entity and set defaults
|
||||||
|
Organization organization = new Organization
|
||||||
|
{
|
||||||
|
Name = tenantEnquire.OrganizationName,
|
||||||
|
Email = tenantEnquire.Email,
|
||||||
|
ContactPerson = $"{tenantEnquire.FirstName} {tenantEnquire.LastName}",
|
||||||
|
Address = tenantEnquire.BillingAddress,
|
||||||
|
ContactNumber = tenantEnquire.ContactNumber,
|
||||||
|
SPRID = lastSPRID + 1,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
CreatedById = employeeId,
|
||||||
|
IsActive = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Organizations.Add(organization);
|
||||||
|
|
||||||
|
// Create the primary Tenant entity
|
||||||
|
|
||||||
|
var tenant = new Tenant
|
||||||
|
{
|
||||||
|
Id = tenantId,
|
||||||
|
Name = tenantEnquire.OrganizationName,
|
||||||
|
Email = tenantEnquire.Email,
|
||||||
|
ContactName = $"{tenantEnquire.FirstName} {tenantEnquire.LastName}",
|
||||||
|
ContactNumber = tenantEnquire.ContactNumber,
|
||||||
|
OrganizationSize = tenantEnquire.OrganizationSize,
|
||||||
|
BillingAddress = tenantEnquire.BillingAddress,
|
||||||
|
IndustryId = tenantEnquire.IndustryId,
|
||||||
|
Reference = tenantEnquire.Reference,
|
||||||
|
OnBoardingDate = onBoardingDate,
|
||||||
|
TenantStatusId = tenantActiveStatus,
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
CreatedById = employeeId,
|
||||||
|
IsActive = true,
|
||||||
|
IsSuperTenant = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Tenants.Add(tenant);
|
||||||
|
|
||||||
|
// Create the root ApplicationUser for the new tenant
|
||||||
|
var applicationUser = new ApplicationUser
|
||||||
|
{
|
||||||
|
Email = tenantEnquire.Email,
|
||||||
|
UserName = tenantEnquire.Email, // Best practice to use email as username for simplicity
|
||||||
|
IsRootUser = true,
|
||||||
|
EmailConfirmed = true // Auto-confirming email as it's part of a trusted setup process
|
||||||
|
};
|
||||||
|
|
||||||
|
// SECURITY WARNING: Hardcoded passwords are a major vulnerability.
|
||||||
|
// Replace "User@123" with a securely generated random password.
|
||||||
|
var initialPassword = "User@123"; // TODO: Replace with password generation service.
|
||||||
|
var result = await _userManager.CreateAsync(applicationUser, initialPassword);
|
||||||
|
|
||||||
|
if (!result.Succeeded)
|
||||||
|
{
|
||||||
|
// If user creation fails, roll back the transaction immediately and return the errors.
|
||||||
|
await transaction.RollbackAsync();
|
||||||
|
var errors = result.Errors.Select(e => e.Description).ToList();
|
||||||
|
_logger.LogWarning("Failed to create ApplicationUser for tenant {TenantName}. Errors: {Errors}", tenantEnquire.OrganizationName, string.Join(", ", errors));
|
||||||
|
return ApiResponse<object>.ErrorResponse("Failed to create user", errors, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the default "Admin" Job Role for the tenant
|
||||||
|
var adminJobRole = new JobRole
|
||||||
|
{
|
||||||
|
Name = AdminRoleName,
|
||||||
|
Description = "Default administrator role for the tenant.",
|
||||||
|
TenantId = tenantId
|
||||||
|
};
|
||||||
|
_context.JobRoles.Add(adminJobRole);
|
||||||
|
|
||||||
|
// Create the primary Employee record and link it to the ApplicationUser and JobRole
|
||||||
|
var employeeUser = new Employee
|
||||||
|
{
|
||||||
|
Id = employeeId,
|
||||||
|
FirstName = tenantEnquire.FirstName,
|
||||||
|
LastName = tenantEnquire.LastName,
|
||||||
|
Email = tenantEnquire.Email,
|
||||||
|
PhoneNumber = tenantEnquire.ContactNumber,
|
||||||
|
JoiningDate = onBoardingDate,
|
||||||
|
ApplicationUserId = applicationUser.Id,
|
||||||
|
JobRole = adminJobRole, // Link to the newly created role
|
||||||
|
CurrentAddress = tenantEnquire.BillingAddress,
|
||||||
|
IsActive = true,
|
||||||
|
IsSystem = false,
|
||||||
|
IsPrimary = true,
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
HasApplicationAccess = true
|
||||||
|
};
|
||||||
|
_context.Employees.Add(employeeUser);
|
||||||
|
|
||||||
|
var applicationRole = new ApplicationRole
|
||||||
|
{
|
||||||
|
Role = "Super User",
|
||||||
|
Description = "Super User",
|
||||||
|
IsSystem = true,
|
||||||
|
TenantId = tenantId
|
||||||
|
};
|
||||||
|
_context.ApplicationRoles.Add(applicationRole);
|
||||||
|
|
||||||
|
var rolePermissionMappigs = new List<RolePermissionMappings> {
|
||||||
|
new RolePermissionMappings
|
||||||
|
{
|
||||||
|
ApplicationRoleId = applicationRole.Id,
|
||||||
|
FeaturePermissionId = PermissionsMaster.ModifyTenant
|
||||||
|
},
|
||||||
|
new RolePermissionMappings
|
||||||
|
{
|
||||||
|
ApplicationRoleId = applicationRole.Id,
|
||||||
|
FeaturePermissionId = PermissionsMaster.ViewTenant
|
||||||
|
},
|
||||||
|
new RolePermissionMappings
|
||||||
|
{
|
||||||
|
ApplicationRoleId = applicationRole.Id,
|
||||||
|
FeaturePermissionId = PermissionsMaster.ManageMasters
|
||||||
|
},
|
||||||
|
new RolePermissionMappings
|
||||||
|
{
|
||||||
|
ApplicationRoleId = applicationRole.Id,
|
||||||
|
FeaturePermissionId = PermissionsMaster.ViewMasters
|
||||||
|
},
|
||||||
|
new RolePermissionMappings
|
||||||
|
{
|
||||||
|
ApplicationRoleId = applicationRole.Id,
|
||||||
|
FeaturePermissionId = PermissionsMaster.ViewOrganization
|
||||||
|
},
|
||||||
|
new RolePermissionMappings
|
||||||
|
{
|
||||||
|
ApplicationRoleId = applicationRole.Id,
|
||||||
|
FeaturePermissionId = PermissionsMaster.AddOrganization
|
||||||
|
},
|
||||||
|
new RolePermissionMappings
|
||||||
|
{
|
||||||
|
ApplicationRoleId = applicationRole.Id,
|
||||||
|
FeaturePermissionId = PermissionsMaster.EditOrganization
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_context.RolePermissionMappings.AddRange(rolePermissionMappigs);
|
||||||
|
|
||||||
|
_context.EmployeeRoleMappings.Add(new EmployeeRoleMapping
|
||||||
|
{
|
||||||
|
EmployeeId = employeeUser.Id,
|
||||||
|
RoleId = applicationRole.Id,
|
||||||
|
IsEnabled = true,
|
||||||
|
TenantId = tenantId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a default project for the new tenant
|
||||||
|
var project = new Project
|
||||||
|
{
|
||||||
|
Name = "Default Project",
|
||||||
|
ProjectStatusId = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731"), // Consider using a constant for this GUID
|
||||||
|
ProjectAddress = tenantEnquire.BillingAddress,
|
||||||
|
StartDate = onBoardingDate,
|
||||||
|
EndDate = DateTime.MaxValue,
|
||||||
|
PromoterId = organization.Id,
|
||||||
|
PMCId = organization.Id,
|
||||||
|
ContactPerson = tenant.ContactName,
|
||||||
|
TenantId = tenantId
|
||||||
|
};
|
||||||
|
_context.Projects.Add(project);
|
||||||
|
|
||||||
|
var projectAllocation = new ProjectAllocation
|
||||||
|
{
|
||||||
|
ProjectId = project.Id,
|
||||||
|
EmployeeId = employeeUser.Id,
|
||||||
|
AllocationDate = onBoardingDate,
|
||||||
|
IsActive = true,
|
||||||
|
JobRoleId = adminJobRole.Id,
|
||||||
|
TenantId = tenantId
|
||||||
|
};
|
||||||
|
_context.ProjectAllocations.Add(projectAllocation);
|
||||||
|
|
||||||
|
// All entities are now added to the context. Save them all in a single database operation.
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// 4. --- POST-CREATION ACTIONS ---
|
||||||
|
// Generate a password reset token so the new user can set their own password.
|
||||||
|
_logger.LogInfo("User {Email} created. Sending password setup email.", applicationUser.Email);
|
||||||
|
var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
|
||||||
|
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}&email={WebUtility.UrlEncode(applicationUser.Email)}";
|
||||||
|
await _emailSender.SendResetPasswordEmailOnRegister(applicationUser.Email, employeeUser.FirstName, resetLink);
|
||||||
|
|
||||||
|
// Map the result to a ViewModel for the API response.
|
||||||
|
var tenantVM = _mapper.Map<TenantVM>(tenant);
|
||||||
|
tenantVM.CreatedBy = _mapper.Map<BasicEmployeeVM>(employeeUser);
|
||||||
|
|
||||||
|
await AddSubscriptionAsync(tenantId, employeeId, paymentDetailId, planId);
|
||||||
|
|
||||||
|
// Commit the transaction as all operations were successful.
|
||||||
|
await transaction.CommitAsync();
|
||||||
|
|
||||||
|
_logger.LogInfo("Successfully created tenant {TenantId} for organization {OrganizationName}.", tenant.Id, tenant.Name);
|
||||||
|
return ApiResponse<object>.SuccessResponse(tenantVM, "Tenant created successfully.", 201);
|
||||||
|
}
|
||||||
|
catch (DbUpdateException dbEx)
|
||||||
|
{
|
||||||
|
await transaction.RollbackAsync();
|
||||||
|
// Log the detailed database exception, including the inner exception if available.
|
||||||
|
_logger.LogError(dbEx, "A database update exception occurred while creating tenant for email {Email}. Inner Exception: {InnerException}",
|
||||||
|
tenantEnquire.Email, dbEx.InnerException?.Message ?? string.Empty);
|
||||||
|
return ApiResponse<object>.ErrorResponse("An internal database error occurred.", ExceptionMapper(dbEx), 500);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log the general exception.
|
||||||
|
_logger.LogError(ex, "An unexpected exception occurred while creating tenant for email {Email}.", tenantEnquire.Email);
|
||||||
|
return ApiResponse<object>.ErrorResponse("An unexpected internal error occurred.", ExceptionMapper(ex), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResponse<object>> AddSubscriptionAsync(Guid tenantId, Guid employeeId, Guid paymentDetailId, Guid planId)
|
||||||
|
{
|
||||||
|
|
||||||
|
_logger.LogInfo("AddSubscription called for Tenant {TenantId} and Plan {PlanId}", tenantId, planId);
|
||||||
|
|
||||||
|
await using var _context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
||||||
|
var subscriptionPlan = await _context.SubscriptionPlanDetails.Include(sp => sp.Plan).FirstOrDefaultAsync(sp => sp.Id == planId);
|
||||||
|
|
||||||
|
var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId);
|
||||||
|
if (tenant == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Tenant {TenantId} not found in database", tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Tenant not found", "Tenant not found", 404);
|
||||||
|
}
|
||||||
|
if (subscriptionPlan == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Subscription plan {PlanId} not found in database", planId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Subscription plan not found", "Subscription plan not found", 404);
|
||||||
|
}
|
||||||
|
var activeUsers = await _context.Employees.CountAsync(e => e.Email != null && e.ApplicationUserId != null && e.TenantId == tenant.Id && e.IsActive);
|
||||||
|
if (activeUsers > subscriptionPlan.MaxUser)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Add less max user than the active user in the tenant {TenantId}", tenant.Id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Invalid Max user count", "Max User count must be higher than active user count", 400);
|
||||||
|
}
|
||||||
|
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||||
|
var utcNow = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// Prepare subscription dates based on frequency
|
||||||
|
var endDate = subscriptionPlan.Frequency switch
|
||||||
|
{
|
||||||
|
PLAN_FREQUENCY.MONTHLY => utcNow.AddDays(30),
|
||||||
|
PLAN_FREQUENCY.QUARTERLY => utcNow.AddDays(90),
|
||||||
|
PLAN_FREQUENCY.HALF_YEARLY => utcNow.AddDays(120),
|
||||||
|
PLAN_FREQUENCY.YEARLY => utcNow.AddDays(360),
|
||||||
|
_ => utcNow // default if unknown
|
||||||
|
};
|
||||||
|
|
||||||
|
var tenantSubscription = new TenantSubscriptions
|
||||||
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
|
PlanId = planId,
|
||||||
|
StatusId = activePlanStatus,
|
||||||
|
CreatedAt = utcNow,
|
||||||
|
MaxUsers = subscriptionPlan.MaxUser,
|
||||||
|
CreatedById = employeeId,
|
||||||
|
CurrencyId = subscriptionPlan.CurrencyId,
|
||||||
|
PaymentDetailId = paymentDetailId,
|
||||||
|
IsTrial = false,
|
||||||
|
StartDate = utcNow,
|
||||||
|
EndDate = endDate,
|
||||||
|
NextBillingDate = endDate,
|
||||||
|
AutoRenew = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.TenantSubscriptions.Add(tenantSubscription);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInfo("Tenant subscription added successfully for Tenant {TenantId}, Plan {PlanId}",
|
||||||
|
tenantId, planId);
|
||||||
|
}
|
||||||
|
catch (DbUpdateException dbEx)
|
||||||
|
{
|
||||||
|
_logger.LogError(dbEx, "Database exception while adding subscription plan to tenant {TenantId}", tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Internal error occured", ExceptionMapper(dbEx), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var features = await _featureDetailsHelper.GetFeatureDetails(subscriptionPlan.FeaturesId);
|
||||||
|
if (features == null)
|
||||||
|
{
|
||||||
|
_logger.LogInfo("No features found for subscription plan {PlanId}", planId);
|
||||||
|
await transaction.CommitAsync();
|
||||||
|
return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to get permissions for a module asynchronously
|
||||||
|
async Task<List<Guid>> GetPermissionsForModuleAsync(List<Guid>? featureIds)
|
||||||
|
{
|
||||||
|
if (featureIds == null || featureIds.Count == 0) return new List<Guid>();
|
||||||
|
|
||||||
|
await using var ctx = await _dbContextFactory.CreateDbContextAsync();
|
||||||
|
return await ctx.FeaturePermissions.AsNoTracking()
|
||||||
|
.Where(fp => featureIds.Contains(fp.FeatureId))
|
||||||
|
.Select(fp => fp.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch permission tasks for all modules in parallel
|
||||||
|
var projectPermissionTask = GetPermissionsForModuleAsync(features.Modules?.ProjectManagement?.FeatureId);
|
||||||
|
var attendancePermissionTask = GetPermissionsForModuleAsync(features.Modules?.Attendance?.FeatureId);
|
||||||
|
var directoryPermissionTask = GetPermissionsForModuleAsync(features.Modules?.Directory?.FeatureId);
|
||||||
|
var expensePermissionTask = GetPermissionsForModuleAsync(features.Modules?.Expense?.FeatureId);
|
||||||
|
var employeePermissionTask = GetPermissionsForModuleAsync(new List<Guid> { EmployeeFeatureId });
|
||||||
|
|
||||||
|
await Task.WhenAll(projectPermissionTask, attendancePermissionTask, directoryPermissionTask, expensePermissionTask, employeePermissionTask);
|
||||||
|
|
||||||
|
var newPermissionIds = new List<Guid>();
|
||||||
|
var deletePermissionIds = new List<Guid>();
|
||||||
|
|
||||||
|
// Add or remove permissions based on modules enabled status
|
||||||
|
void ProcessPermissions(bool? enabled, List<Guid> permissions)
|
||||||
|
{
|
||||||
|
if (enabled == true)
|
||||||
|
newPermissionIds.AddRange(permissions);
|
||||||
|
else
|
||||||
|
deletePermissionIds.AddRange(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessPermissions(features.Modules?.ProjectManagement?.Enabled, projectPermissionTask.Result);
|
||||||
|
ProcessPermissions(features.Modules?.Attendance?.Enabled, attendancePermissionTask.Result);
|
||||||
|
ProcessPermissions(features.Modules?.Directory?.Enabled, directoryPermissionTask.Result);
|
||||||
|
ProcessPermissions(features.Modules?.Expense?.Enabled, expensePermissionTask.Result);
|
||||||
|
|
||||||
|
newPermissionIds = newPermissionIds.Distinct().ToList();
|
||||||
|
deletePermissionIds = deletePermissionIds.Distinct().ToList();
|
||||||
|
|
||||||
|
// Get root employee and role for this tenant
|
||||||
|
var rootEmployee = await _context.Employees
|
||||||
|
.Include(e => e.ApplicationUser)
|
||||||
|
.FirstOrDefaultAsync(e => e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false) && e.OrganizationId == tenant.OrganizationId);
|
||||||
|
|
||||||
|
if (rootEmployee == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Root employee not found for tenant {TenantId}", tenantId);
|
||||||
|
await transaction.CommitAsync();
|
||||||
|
return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
var roleId = await _context.EmployeeRoleMappings
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(er => er.EmployeeId == rootEmployee.Id && er.TenantId == tenantId)
|
||||||
|
.Select(er => er.RoleId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (roleId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("RoleId for root employee {EmployeeId} in tenant {TenantId} not found", rootEmployee.Id, tenantId);
|
||||||
|
await transaction.CommitAsync();
|
||||||
|
return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant subscription successfully added", 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldRolePermissionMappings = await _context.RolePermissionMappings
|
||||||
|
.Where(rp => rp.ApplicationRoleId == roleId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var oldPermissionIds = oldRolePermissionMappings.Select(rp => rp.FeaturePermissionId).ToList();
|
||||||
|
|
||||||
|
// Prevent accidentally deleting essential employee permissions
|
||||||
|
var permissionIdCount = oldPermissionIds.Count - deletePermissionIds.Count;
|
||||||
|
if (permissionIdCount <= 4 && deletePermissionIds.Any())
|
||||||
|
{
|
||||||
|
var employeePermissionIds = employeePermissionTask.Result;
|
||||||
|
deletePermissionIds = deletePermissionIds.Where(p => !employeePermissionIds.Contains(p)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare mappings to delete and add
|
||||||
|
var deleteMappings = oldRolePermissionMappings.Where(rp => deletePermissionIds.Contains(rp.FeaturePermissionId)).ToList();
|
||||||
|
var addRolePermissionMappings = newPermissionIds
|
||||||
|
.Where(p => !oldPermissionIds.Contains(p))
|
||||||
|
.Select(p => new RolePermissionMappings
|
||||||
|
{
|
||||||
|
ApplicationRoleId = roleId,
|
||||||
|
FeaturePermissionId = p
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (addRolePermissionMappings.Any())
|
||||||
|
{
|
||||||
|
_context.RolePermissionMappings.AddRange(addRolePermissionMappings);
|
||||||
|
_logger.LogInfo("Added {Count} new role permission mappings for role {RoleId}", addRolePermissionMappings.Count, roleId);
|
||||||
|
}
|
||||||
|
if (deleteMappings.Any())
|
||||||
|
{
|
||||||
|
_context.RolePermissionMappings.RemoveRange(deleteMappings);
|
||||||
|
_logger.LogInfo("Removed {Count} role permission mappings for role {RoleId}", deleteMappings.Count, roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var _cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>();
|
||||||
|
await _cache.ClearAllEmployeesFromCacheByTenantId(tenant.Id);
|
||||||
|
|
||||||
|
var _masteData = scope.ServiceProvider.GetRequiredService<MasterDataService>();
|
||||||
|
|
||||||
|
if (features.Modules?.ProjectManagement?.Enabled ?? false)
|
||||||
|
{
|
||||||
|
var workCategoryMaster = _masteData.GetWorkCategoriesData(tenant.Id);
|
||||||
|
var workStatusMaster = _masteData.GetWorkStatusesData(tenant.Id);
|
||||||
|
|
||||||
|
_context.WorkCategoryMasters.AddRange(workCategoryMaster);
|
||||||
|
_context.WorkStatusMasters.AddRange(workStatusMaster);
|
||||||
|
}
|
||||||
|
if (features.Modules?.Expense?.Enabled ?? false)
|
||||||
|
{
|
||||||
|
var expensesTypeMaster = _masteData.GetExpensesTypeesData(tenant.Id);
|
||||||
|
var paymentModeMatser = _masteData.GetPaymentModesData(tenant.Id);
|
||||||
|
|
||||||
|
_context.ExpensesTypeMaster.AddRange(expensesTypeMaster);
|
||||||
|
_context.PaymentModeMatser.AddRange(paymentModeMatser);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
await transaction.CommitAsync();
|
||||||
|
|
||||||
|
_logger.LogInfo("Permissions updated successfully for tenant {TenantId} subscription", tenantId);
|
||||||
|
|
||||||
|
return ApiResponse<object>.SuccessResponse(tenantSubscription, "Tenant Subscription Successfully", 200);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await transaction.RollbackAsync();
|
||||||
|
_logger.LogError(ex, "Exception occurred while updating permissions for tenant {TenantId}", tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Internal error occured", ExceptionMapper(ex), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region =================================================================== Helper Functions ===================================================================
|
||||||
|
private static object ExceptionMapper(Exception ex)
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Message = ex.Message,
|
||||||
|
StackTrace = ex.StackTrace,
|
||||||
|
Source = ex.Source,
|
||||||
|
InnerException = new
|
||||||
|
{
|
||||||
|
Message = ex.InnerException?.Message,
|
||||||
|
StackTrace = ex.InnerException?.StackTrace,
|
||||||
|
Source = ex.InnerException?.Source,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private bool IsBase64String(string? input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string base64Data = input;
|
||||||
|
const string dataUriMarker = "base64,";
|
||||||
|
int markerIndex = input.IndexOf(dataUriMarker, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
// If the marker is found, extract the actual Base64 data
|
||||||
|
if (markerIndex >= 0)
|
||||||
|
{
|
||||||
|
base64Data = input.Substring(markerIndex + dataUriMarker.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, validate the extracted payload
|
||||||
|
base64Data = base64Data.Trim();
|
||||||
|
|
||||||
|
// Check for valid length (must be a multiple of 4) and non-empty
|
||||||
|
if (base64Data.Length == 0 || base64Data.Length % 4 != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The most reliable test is to simply try to convert it.
|
||||||
|
// The .NET converter is strict and will throw a FormatException
|
||||||
|
// for invalid characters or incorrect padding.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Convert.FromBase64String(base64Data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
// The string is not a valid Base64 payload.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user