Add SMS gating, TCPA terms agreement, and compose-before-send modal

- Three-tier SMS gate: platform kill-switch → admin force-disable → plan AllowSms → company opt-in
- CompanySmsAgreement entity records admin acceptance of TCPA terms with IP, user agent, and terms version
- SMS terms of service modal on Company Settings with versioned re-agreement (AppConstants.SmsTermsVersion)
- Dev redirect: non-production SMS routed to Twilio:DevRedirectPhone to protect real customer numbers
- Removed redundant Ready for Pickup SMS (Job Completed covers it)
- Role-based compose modal on job completion: Admin/Manager reviews and edits before send; ShopFloor auto-sends
- Send SMS button on job details for ad-hoc messages (Admin/Manager only)
- SendJobSmsAsync auto-appends STOP opt-out language if missing
- Migrations: AddSmsGating, AddCompanySmsAgreement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 22:29:39 -04:00
parent 2b89fcf483
commit 6569d9c4ea
32 changed files with 19855 additions and 106 deletions
@@ -88,6 +88,19 @@ public class Company : BaseEntity
/// </summary>
public bool? AccountingOverride { get; set; }
/// <summary>
/// Company admin opt-in for SMS notifications. Defaults to false — company admin must
/// explicitly accept the SMS terms of service before enabling. Has no effect if the plan
/// does not allow SMS or if SmsDisabledByAdmin is true.
/// </summary>
public bool SmsEnabled { get; set; } = false;
/// <summary>
/// SuperAdmin force-disable for this company's SMS. When true, no SMS is sent regardless
/// of plan or company settings. Use when a company is abusing SMS or requests a full opt-out.
/// </summary>
public bool SmsDisabledByAdmin { get; set; } = false;
// Email marketing opt-out (CAN-SPAM compliance for platform broadcast emails)
public bool MarketingEmailOptOut { get; set; } = false;
public string MarketingUnsubscribeToken { get; set; } = Guid.NewGuid().ToString("N");