Initial commit
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PowderCoating.Application.DTOs.Subscription;
|
||||
using PowderCoating.Core.Interfaces;
|
||||
using PowderCoating.Shared.Constants;
|
||||
|
||||
namespace PowderCoating.Web.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// SuperAdmin-only interface for managing subscription plan configurations
|
||||
/// (limits, pricing, feature flags, and Stripe price IDs). Changes here
|
||||
/// affect every new tenant assignment of the plan and any quota checks that
|
||||
/// read plan limits at runtime. Plan <c>DisplayName</c> and <c>Plan</c> enum
|
||||
/// value are intentionally not editable here to prevent breaking existing company
|
||||
/// subscription records that reference them by integer value.
|
||||
/// </summary>
|
||||
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
|
||||
public class PlatformSubscriptionController : Controller
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<PlatformSubscriptionController> _logger;
|
||||
|
||||
public PlatformSubscriptionController(IUnitOfWork unitOfWork, ILogger<PlatformSubscriptionController> logger)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists all subscription plan configurations ordered by display sort order.
|
||||
/// Uses <c>ignoreQueryFilters: true</c> because plan configs carry
|
||||
/// <c>CompanyId = 0</c> and are otherwise hidden by the multi-tenancy filter.
|
||||
/// Projects to <see cref="SubscriptionPlanConfigDto"/> to avoid exposing the
|
||||
/// full entity (including internal Stripe secrets) to the view layer.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var configs = (await _unitOfWork.SubscriptionPlanConfigs.GetAllAsync(ignoreQueryFilters: true))
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.ToList();
|
||||
|
||||
var dtos = configs.Select(c => new SubscriptionPlanConfigDto
|
||||
{
|
||||
Id = c.Id,
|
||||
Plan = c.Plan,
|
||||
DisplayName = c.DisplayName,
|
||||
Description = c.Description,
|
||||
MaxUsers = c.MaxUsers,
|
||||
MaxActiveJobs = c.MaxActiveJobs,
|
||||
MaxCustomers = c.MaxCustomers,
|
||||
MaxQuotes = c.MaxQuotes,
|
||||
MaxCatalogItems = c.MaxCatalogItems,
|
||||
MaxJobPhotos = c.MaxJobPhotos,
|
||||
MaxQuotePhotos = c.MaxQuotePhotos,
|
||||
MaxAiPhotoQuotesPerMonth = c.MaxAiPhotoQuotesPerMonth,
|
||||
MonthlyPrice = c.MonthlyPrice,
|
||||
AnnualPrice = c.AnnualPrice,
|
||||
StripePriceIdMonthly = c.StripePriceIdMonthly,
|
||||
StripePriceIdAnnual = c.StripePriceIdAnnual,
|
||||
AllowOnlinePayments = c.AllowOnlinePayments,
|
||||
AllowAccounting = c.AllowAccounting,
|
||||
AllowAiPhotoQuotes = c.AllowAiPhotoQuotes,
|
||||
AllowAiInventoryAssist = c.AllowAiInventoryAssist,
|
||||
IsActive = c.IsActive,
|
||||
SortOrder = c.SortOrder
|
||||
}).ToList();
|
||||
|
||||
return View(dtos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Edit form for a plan config, loaded into an
|
||||
/// <see cref="UpdateSubscriptionPlanConfigDto"/> to prevent over-posting of
|
||||
/// immutable fields such as <c>Plan</c>, <c>DisplayName</c>, and <c>SortOrder</c>.
|
||||
/// The plan display name is placed in ViewBag for the page heading.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Edit(int id)
|
||||
{
|
||||
var config = await _unitOfWork.SubscriptionPlanConfigs.GetByIdAsync(id, ignoreQueryFilters: true);
|
||||
if (config == null) return NotFound();
|
||||
|
||||
var dto = new UpdateSubscriptionPlanConfigDto
|
||||
{
|
||||
Id = config.Id,
|
||||
Description = config.Description,
|
||||
MaxUsers = config.MaxUsers,
|
||||
MaxActiveJobs = config.MaxActiveJobs,
|
||||
MaxCustomers = config.MaxCustomers,
|
||||
MaxQuotes = config.MaxQuotes,
|
||||
MaxCatalogItems = config.MaxCatalogItems,
|
||||
MaxJobPhotos = config.MaxJobPhotos,
|
||||
MaxQuotePhotos = config.MaxQuotePhotos,
|
||||
MaxAiPhotoQuotesPerMonth = config.MaxAiPhotoQuotesPerMonth,
|
||||
MonthlyPrice = config.MonthlyPrice,
|
||||
AnnualPrice = config.AnnualPrice,
|
||||
StripePriceIdMonthly = config.StripePriceIdMonthly,
|
||||
StripePriceIdAnnual = config.StripePriceIdAnnual,
|
||||
AllowOnlinePayments = config.AllowOnlinePayments,
|
||||
AllowAccounting = config.AllowAccounting,
|
||||
AllowAiPhotoQuotes = config.AllowAiPhotoQuotes,
|
||||
AllowAiInventoryAssist = config.AllowAiInventoryAssist,
|
||||
IsActive = config.IsActive
|
||||
};
|
||||
|
||||
ViewBag.PlanName = config.DisplayName;
|
||||
return View(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the updated plan configuration values and saves. Uses explicit field
|
||||
/// mapping from <paramref name="dto"/> to the tracked entity so that immutable
|
||||
/// identity fields (<c>Plan</c>, <c>DisplayName</c>, <c>SortOrder</c>) are never
|
||||
/// overwritten. Logs the change at Information level for the audit trail.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Edit(int id, UpdateSubscriptionPlanConfigDto dto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var configForView = await _unitOfWork.SubscriptionPlanConfigs.GetByIdAsync(id, ignoreQueryFilters: true);
|
||||
ViewBag.PlanName = configForView?.DisplayName ?? "Unknown";
|
||||
return View(dto);
|
||||
}
|
||||
|
||||
var config = await _unitOfWork.SubscriptionPlanConfigs.GetByIdAsync(id, ignoreQueryFilters: true);
|
||||
if (config == null) return NotFound();
|
||||
|
||||
config.Description = dto.Description;
|
||||
config.MaxUsers = dto.MaxUsers;
|
||||
config.MaxActiveJobs = dto.MaxActiveJobs;
|
||||
config.MaxCustomers = dto.MaxCustomers;
|
||||
config.MaxQuotes = dto.MaxQuotes;
|
||||
config.MaxCatalogItems = dto.MaxCatalogItems;
|
||||
config.MaxJobPhotos = dto.MaxJobPhotos;
|
||||
config.MaxQuotePhotos = dto.MaxQuotePhotos;
|
||||
config.MaxAiPhotoQuotesPerMonth = dto.MaxAiPhotoQuotesPerMonth;
|
||||
config.MonthlyPrice = dto.MonthlyPrice;
|
||||
config.AnnualPrice = dto.AnnualPrice;
|
||||
config.StripePriceIdMonthly = dto.StripePriceIdMonthly;
|
||||
config.StripePriceIdAnnual = dto.StripePriceIdAnnual;
|
||||
config.AllowOnlinePayments = dto.AllowOnlinePayments;
|
||||
config.AllowAccounting = dto.AllowAccounting;
|
||||
config.AllowAiPhotoQuotes = dto.AllowAiPhotoQuotes;
|
||||
config.AllowAiInventoryAssist = dto.AllowAiInventoryAssist;
|
||||
config.IsActive = dto.IsActive;
|
||||
|
||||
await _unitOfWork.SubscriptionPlanConfigs.UpdateAsync(config);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
_logger.LogInformation("SuperAdmin updated subscription plan config: {Plan}", config.DisplayName);
|
||||
TempData["Success"] = $"{config.DisplayName} plan configuration updated successfully.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user