c18b580ec9
- Add /SmsAgreements SuperAdmin page listing per-company SMS terms acceptance status, with stats cards, filter/search, and a full acceptance history modal (terms version, accepted by, timestamp, IP, user agent) - Add SMS Agreements nav link under Tenants & Billing in the platform sidebar - Update HelpKnowledgeBase and Help docs (Quotes, Settings) to document quote approval via SMS and the reuse of existing approval tokens Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
100 lines
4.1 KiB
C#
100 lines
4.1 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using PowderCoating.Core.Interfaces;
|
|
using PowderCoating.Shared.Constants;
|
|
|
|
namespace PowderCoating.Web.Controllers;
|
|
|
|
/// <summary>
|
|
/// SuperAdmin view of per-company SMS terms agreement history.
|
|
/// Shows which companies have accepted the current SMS terms, who accepted them,
|
|
/// and the full acceptance log for each company.
|
|
/// </summary>
|
|
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
|
|
public class SmsAgreementsController : Controller
|
|
{
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
private readonly ILogger<SmsAgreementsController> _logger;
|
|
|
|
public SmsAgreementsController(IUnitOfWork unitOfWork, ILogger<SmsAgreementsController> logger)
|
|
{
|
|
_unitOfWork = unitOfWork;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lists every company with its current SMS agreement status and full acceptance history.
|
|
/// Uses IgnoreQueryFilters on both queries so deleted/inactive companies and all historical
|
|
/// agreement records are included in the audit view.
|
|
/// </summary>
|
|
public async Task<IActionResult> Index(string? search = null, string? filter = null)
|
|
{
|
|
var companies = await _unitOfWork.Companies.GetAllAsync(ignoreQueryFilters: true);
|
|
var allAgreements = await _unitOfWork.CompanySmsAgreements.GetAllAsync(ignoreQueryFilters: true);
|
|
|
|
var agreementsByCompany = allAgreements
|
|
.GroupBy(a => a.CompanyId)
|
|
.ToDictionary(g => g.Key, g => g.OrderByDescending(a => a.AgreedAt).ToList());
|
|
|
|
var rows = companies
|
|
.OrderBy(c => c.CompanyName)
|
|
.Select(c =>
|
|
{
|
|
var agreements = agreementsByCompany.TryGetValue(c.Id, out var list) ? list : [];
|
|
var current = agreements.FirstOrDefault(a => a.TermsVersion == AppConstants.SmsTermsVersion);
|
|
return new CompanySmsRow
|
|
{
|
|
CompanyId = c.Id,
|
|
CompanyName = c.CompanyName ?? "(unnamed)",
|
|
SmsEnabled = c.SmsEnabled,
|
|
SmsDisabledByAdmin = c.SmsDisabledByAdmin,
|
|
CurrentAgreement = current,
|
|
LatestAgreement = agreements.FirstOrDefault(),
|
|
AllAgreements = agreements,
|
|
IsDeleted = c.IsDeleted
|
|
};
|
|
})
|
|
.ToList();
|
|
|
|
// Filter
|
|
rows = filter switch
|
|
{
|
|
"accepted" => rows.Where(r => r.CurrentAgreement != null).ToList(),
|
|
"pending" => rows.Where(r => r.CurrentAgreement == null).ToList(),
|
|
"enabled" => rows.Where(r => r.SmsEnabled).ToList(),
|
|
"disabled" => rows.Where(r => r.SmsDisabledByAdmin).ToList(),
|
|
_ => rows
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
rows = rows.Where(r => r.CompanyName.Contains(search, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
ViewBag.Search = search;
|
|
ViewBag.Filter = filter ?? "all";
|
|
ViewBag.CurrentTermsVersion = AppConstants.SmsTermsVersion;
|
|
|
|
// Stats (pre-filter totals)
|
|
var all = companies.ToList();
|
|
ViewBag.TotalCompanies = all.Count(c => !c.IsDeleted);
|
|
ViewBag.AcceptedCount = agreementsByCompany.Count(kvp =>
|
|
kvp.Value.Any(a => a.TermsVersion == AppConstants.SmsTermsVersion) &&
|
|
!all.FirstOrDefault(c => c.Id == kvp.Key)?.IsDeleted == true);
|
|
ViewBag.SmsEnabledCount = all.Count(c => !c.IsDeleted && c.SmsEnabled);
|
|
|
|
return View(rows);
|
|
}
|
|
}
|
|
|
|
/// <summary>View model for one company row on the SMS agreements page.</summary>
|
|
public class CompanySmsRow
|
|
{
|
|
public int CompanyId { get; set; }
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
public bool SmsEnabled { get; set; }
|
|
public bool SmsDisabledByAdmin { get; set; }
|
|
public bool IsDeleted { get; set; }
|
|
public PowderCoating.Core.Entities.CompanySmsAgreement? CurrentAgreement { get; set; }
|
|
public PowderCoating.Core.Entities.CompanySmsAgreement? LatestAgreement { get; set; }
|
|
public List<PowderCoating.Core.Entities.CompanySmsAgreement> AllAgreements { get; set; } = [];
|
|
}
|