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
@@ -23,8 +23,23 @@ public interface INotificationService
/// <summary>
/// Notify customer when a job is completed and ready for pickup.
/// When <paramref name="suppressSms"/> is true the SMS is skipped so an admin can review
/// the message via <see cref="RenderJobCompletedSmsAsync"/> before sending manually.
/// </summary>
Task NotifyJobCompletedAsync(Job job);
Task NotifyJobCompletedAsync(Job job, bool suppressSms = false);
/// <summary>
/// Renders the job-completed SMS text for admin preview without sending it.
/// Returns null when SMS is not allowed for the company or the customer has not opted in.
/// </summary>
Task<string?> RenderJobCompletedSmsAsync(Job job);
/// <summary>
/// Sends a manually-composed SMS for a job (Admin/Manager compose-before-send path).
/// Appends "Reply STOP to opt out." if not already present, sends, and writes a NotificationLog row.
/// Returns (success, errorMessage).
/// </summary>
Task<(bool Success, string? Error)> SendJobSmsAsync(Job job, string message);
/// <summary>
/// Sends a welcome/confirmation SMS after staff records verbal SMS consent.