Add TCPA-compliant SMS consent tracking for prospect quotes

- Quote entity: ProspectSmsConsent (bool) + ProspectSmsConsentedAt (DateTime?) fields
- QuoteDtos: consent fields on Create/Update/Convert DTOs with TCPA guidance text
- Quote Create/Edit views: SMS consent checkbox shown when mobile number is entered
- Quote ConvertToCustomer view: staff must re-confirm consent carries over to customer record
- QuoteApproval: consent state exposed in ViewModel and ApprovalPage for transparency
- Consent timestamp cleared when prospect quote is linked to an existing customer
- Migration: AddProspectSmsConsent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-08 20:47:04 -04:00
parent fb979bc88d
commit f40d58ac2e
10 changed files with 9776 additions and 3 deletions
@@ -104,6 +104,19 @@
<span asp-validation-for="ProspectPhone" class="text-danger"></span>
</div>
</div>
<div class="row mt-2" id="prospectSmsConsentRow" style="display:none;">
<div class="col-12">
<div class="form-check">
<input asp-for="ProspectSmsConsent" class="form-check-input" id="ProspectSmsConsent" />
<label class="form-check-label fw-semibold" for="ProspectSmsConsent">
Customer verbally agreed to receive SMS notifications
</label>
</div>
<small class="text-muted">
<i class="bi bi-shield-check me-1"></i>Only check if the customer explicitly consented to receive text messages (TCPA compliance).
</small>
</div>
</div>
<div class="row mt-2 quote-advanced-only">
<div class="col-md-6">
<label asp-for="ProspectAddress" class="form-label"></label>
@@ -764,8 +777,23 @@
if (el) el.removeAttribute('required');
});
}
updateProspectSmsConsentVisibility();
}
function updateProspectSmsConsentVisibility() {
const phoneEl = document.getElementById('prospectPhone');
const row = document.getElementById('prospectSmsConsentRow');
if (!phoneEl || !row) return;
const isProspect = document.getElementById('forProspect')?.checked;
row.style.display = (isProspect && phoneEl.value.trim()) ? '' : 'none';
}
document.addEventListener('DOMContentLoaded', function () {
const phoneEl = document.getElementById('prospectPhone');
if (phoneEl) phoneEl.addEventListener('input', updateProspectSmsConsentVisibility);
updateProspectSmsConsentVisibility();
});
// Discount type toggle
function onDiscountTypeChange() {
const type = document.getElementById('discountTypeSelect').value;