cb7bbc37bd
Adds platform-level, plan-level (Enterprise only), and per-company toggles for the AI Catalog Price Check feature. Includes: - Company.AiCatalogPriceCheckEnabled per-company flag - SubscriptionPlanConfig.AllowAiCatalogPriceCheck plan-level flag - PlatformSetting 'AiCatalogPriceCheckEnabled' global kill switch - IPlatformSettingsService.GetBoolAsync helper - ISubscriptionService.CanUseAiCatalogPriceCheckAsync - UI controls in Companies/Edit, PlatformSubscription/Edit+Index, and SubscriptionManagement/Manage - Migration AddAiCatalogPriceCheckGating applied Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
199 lines
9.8 KiB
Plaintext
199 lines
9.8 KiB
Plaintext
@using PowderCoating.Application.DTOs.Subscription
|
|
@model IEnumerable<SubscriptionPlanConfigDto>
|
|
@{
|
|
ViewData["Title"] = "Subscription Plan Configuration";
|
|
ViewData["PageIcon"] = "bi-layers";
|
|
// Badge color by position in the sorted list (SortOrder already applied in controller)
|
|
var planList = Model.ToList();
|
|
string PlanBadge(int index) => index switch {
|
|
0 => "bg-secondary",
|
|
1 => "bg-primary",
|
|
2 => "bg-info",
|
|
_ => "bg-success"
|
|
};
|
|
}
|
|
|
|
@section Styles {
|
|
<style>
|
|
/* Dark mode: table-light section headers inside plan cards */
|
|
[data-bs-theme="dark"] .card .table .table-light td {
|
|
--bs-table-bg: var(--bs-tertiary-bg);
|
|
color: var(--bs-secondary-color);
|
|
}
|
|
/* Dark mode: card headers */
|
|
[data-bs-theme="dark"] .card .card-header {
|
|
background-color: var(--bs-tertiary-bg);
|
|
border-color: var(--bs-border-color);
|
|
}
|
|
</style>
|
|
}
|
|
|
|
<div class="mb-4"></div>
|
|
|
|
@if (TempData["Success"] != null)
|
|
{
|
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
|
<i class="bi bi-check-circle me-2"></i>@TempData["Success"]
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
}
|
|
|
|
<div class="row g-4">
|
|
@for (int i = 0; i < planList.Count; i++)
|
|
{
|
|
var plan = planList[i];
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span class="badge @PlanBadge(i) fs-6">@plan.DisplayName</span>
|
|
@if (!plan.IsActive)
|
|
{
|
|
<span class="badge bg-danger">Inactive</span>
|
|
}
|
|
</div>
|
|
<div class="card-body">
|
|
@if (!string.IsNullOrEmpty(plan.Description))
|
|
{
|
|
<p class="text-muted small mb-3">@plan.Description</p>
|
|
}
|
|
<table class="table table-sm">
|
|
<tbody>
|
|
<tr class="table-light">
|
|
<td colspan="2" class="fw-semibold small text-uppercase text-muted py-1">Limits</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Users</td>
|
|
<td class="fw-semibold">@(plan.MaxUsers == -1 ? "Unlimited" : plan.MaxUsers.ToString())</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Active Jobs</td>
|
|
<td class="fw-semibold">@(plan.MaxActiveJobs == -1 ? "Unlimited" : plan.MaxActiveJobs.ToString())</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Customers</td>
|
|
<td class="fw-semibold">@(plan.MaxCustomers == -1 ? "Unlimited" : plan.MaxCustomers.ToString())</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Quotes / Month</td>
|
|
<td class="fw-semibold">@(plan.MaxQuotes == -1 ? "Unlimited" : plan.MaxQuotes.ToString())</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Catalog Items</td>
|
|
<td class="fw-semibold">@(plan.MaxCatalogItems == -1 ? "Unlimited" : plan.MaxCatalogItems.ToString())</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Photos / Job</td>
|
|
<td class="fw-semibold">@(plan.MaxJobPhotos == -1 ? "Unlimited" : plan.MaxJobPhotos == 0 ? "None" : plan.MaxJobPhotos.ToString())</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Photos / Quote</td>
|
|
<td class="fw-semibold">@(plan.MaxQuotePhotos == -1 ? "Unlimited" : plan.MaxQuotePhotos == 0 ? "None" : plan.MaxQuotePhotos.ToString())</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">AI Photo Quotes / Month</td>
|
|
<td class="fw-semibold">@(plan.MaxAiPhotoQuotesPerMonth == -1 ? "Unlimited" : plan.MaxAiPhotoQuotesPerMonth == 0 ? "Disabled" : plan.MaxAiPhotoQuotesPerMonth.ToString())</td>
|
|
</tr>
|
|
<tr class="table-light">
|
|
<td colspan="2" class="fw-semibold small text-uppercase text-muted py-1">Pricing</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Monthly</td>
|
|
<td class="fw-semibold">$@plan.MonthlyPrice.ToString("F2")</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Annual</td>
|
|
<td class="fw-semibold">$@plan.AnnualPrice.ToString("F2")</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Online Payments</td>
|
|
<td>
|
|
@if (plan.AllowOnlinePayments)
|
|
{
|
|
<span class="badge bg-success">Enabled</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Disabled</span>
|
|
}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Accounting</td>
|
|
<td>
|
|
@if (plan.AllowAccounting)
|
|
{
|
|
<span class="badge bg-success">Enabled</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Disabled</span>
|
|
}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">AI Photo Quotes</td>
|
|
<td>
|
|
@if (plan.AllowAiPhotoQuotes)
|
|
{
|
|
<span class="badge bg-success">Enabled</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Disabled</span>
|
|
}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">AI Inventory Assist</td>
|
|
<td>
|
|
@if (plan.AllowAiInventoryAssist)
|
|
{
|
|
<span class="badge bg-success">Enabled</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Disabled</span>
|
|
}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">AI Price Check</td>
|
|
<td>
|
|
@if (plan.AllowAiCatalogPriceCheck)
|
|
{
|
|
<span class="badge bg-success">Enabled</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Disabled</span>
|
|
}
|
|
</td>
|
|
</tr>
|
|
<tr class="table-light">
|
|
<td colspan="2" class="fw-semibold small text-uppercase text-muted py-1">Stripe</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Monthly ID</td>
|
|
<td class="font-monospace small text-truncate" style="max-width: 120px;" title="@plan.StripePriceIdMonthly">
|
|
@(string.IsNullOrEmpty(plan.StripePriceIdMonthly) ? "—" : plan.StripePriceIdMonthly)
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Annual ID</td>
|
|
<td class="font-monospace small text-truncate" style="max-width: 120px;" title="@plan.StripePriceIdAnnual">
|
|
@(string.IsNullOrEmpty(plan.StripePriceIdAnnual) ? "—" : plan.StripePriceIdAnnual)
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="card-footer bg-transparent">
|
|
<a asp-action="Edit" asp-route-id="@plan.Id" class="btn btn-outline-primary btn-sm w-100">
|
|
<i class="bi bi-pencil me-1"></i>Edit Configuration
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|