Add three-layer feature gating for AI Catalog Price Check

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>
This commit is contained in:
2026-04-26 08:29:51 -04:00
parent fa9fa76231
commit cb7bbc37bd
20 changed files with 9517 additions and 6 deletions
@@ -41,6 +41,17 @@ public class PlatformSettingsService : IPlatformSettingsService
return setting?.Value;
}
/// <summary>
/// Reads a platform setting as a boolean. Returns <paramref name="defaultValue"/> when
/// the key is missing or the stored value cannot be parsed as a boolean.
/// </summary>
public async Task<bool> GetBoolAsync(string key, bool defaultValue = false)
{
var value = await GetAsync(key);
if (value == null) return defaultValue;
return bool.TryParse(value, out var result) ? result : defaultValue;
}
/// <summary>
/// Creates or updates the platform setting identified by <paramref name="key"/>.
/// Records <paramref name="updatedBy"/> (typically the SuperAdmin's username) and
@@ -323,6 +323,19 @@ public class SubscriptionService : ISubscriptionService
/// (3) monthly usage quota from <see cref="GetAiPhotoQuoteUsageAsync"/>.
/// Comped companies bypass all three gates.
/// </summary>
public async Task<bool> CanUseAiCatalogPriceCheckAsync(int companyId)
{
var (company, config) = await GetCompanyAndConfigAsync(companyId);
if (company == null) return false;
if (company.IsComped) return true;
// Plan-level gate: Enterprise plan must have this feature enabled
if (config != null && !config.AllowAiCatalogPriceCheck) return false;
// Company-level toggle: SuperAdmin can disable per company
return company.AiCatalogPriceCheckEnabled;
}
public async Task<bool> CanUseAiPhotoQuoteAsync(int companyId)
{
var (company, config) = await GetCompanyAndConfigAsync(companyId);