Hide email controls when no email on file; show SMS hint for quote/job events
- Quotes Create/Edit: hide 'Send via email' checkbox when customer has no email; show badge 'send via SMS from details' or 'SMS consent required' when customer has a mobile number. JS responds to customer dropdown change. - Quotes Details: hide 'Send Quote via Email' button and approval email checkbox; hide SMS button when no mobile; show consent-required note. - Jobs Details (Mark Complete modal): hide email checkbox; show 'SMS notification will be sent' badge or consent-required note. - Jobs Index (status modal): hide email row when customer has no email. - Jobs Edit: hide 'Notify customer if status changes' when no email. - Invoices Details: hide Send/Re-send buttons when no email (vs. disabled). DTOs: added CustomerEmail + CustomerNotifyByEmail to JobDto/JobListDto; added CustomerNotifyByEmail/CustomerMobilePhone/CustomerNotifyBySms to QuoteDto. Mapped in JobProfile and QuotesController customer blocks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -403,12 +403,37 @@
|
||||
<!-- Form Actions -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body d-flex align-items-center justify-content-end flex-wrap gap-3">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
id="SendEmailToCustomer" name="SendEmailToCustomer" value="true" />
|
||||
<label class="form-check-label fw-semibold" for="SendEmailToCustomer">
|
||||
<i class="bi bi-envelope me-1"></i>Send updated quote via email
|
||||
</label>
|
||||
@{
|
||||
var editHasEmail = !string.IsNullOrWhiteSpace(ViewBag.CustomerEmail as string);
|
||||
var editHasSms = (bool)(ViewBag.CustomerNotifyBySms ?? false) && !string.IsNullOrWhiteSpace(ViewBag.CustomerMobilePhone as string);
|
||||
}
|
||||
<div class="d-flex align-items-center gap-1" id="notifyCustomerSection">
|
||||
<div id="emailNotifySection" style="@(editHasEmail ? "" : "display:none;")">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
id="SendEmailToCustomer" name="SendEmailToCustomer" value="true" />
|
||||
<label class="form-check-label fw-semibold" for="SendEmailToCustomer">
|
||||
<i class="bi bi-envelope me-1"></i>Send updated quote via email
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@if (!editHasEmail)
|
||||
{
|
||||
<span id="smsNotifyNote" class="badge @(editHasSms ? "bg-info text-white" : "bg-warning text-dark")">
|
||||
@if (editHasSms)
|
||||
{
|
||||
<i class="bi bi-phone me-1"></i><text>No email — send via SMS from quote details</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bi bi-phone-slash me-1"></i><text>No email — SMS consent required</text>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span id="smsNotifyNote" style="display:none;"></span>
|
||||
}
|
||||
</div>
|
||||
<a asp-action="Details" asp-controller="Quotes" asp-route-id="@Model.Id" class="btn btn-outline-secondary btn-lg">
|
||||
<i class="bi bi-x-circle me-1"></i>Cancel
|
||||
@@ -601,7 +626,10 @@
|
||||
"aiAnalyzeUrl": "@Url.Action("AiAnalyzeItem", "Quotes")",
|
||||
"aiPhotoQuotesEnabled": @Json.Serialize((bool)(ViewBag.AiPhotoQuotesEnabled ?? true)),
|
||||
"itemsFieldPrefix": "QuoteItems",
|
||||
"aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")"
|
||||
"aiRecalcUrl": "@Url.Action("AiRecalcPrice", "Quotes")",
|
||||
"emailOptOutCustomerIds": @Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.CustomerEmailOptOutIds ?? new System.Collections.Generic.HashSet<int>())),
|
||||
"noEmailCustomerIds": @Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.CustomerNoEmailIds ?? new System.Collections.Generic.HashSet<int>())),
|
||||
"smsConsentCustomerIds": @Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.CustomerSmsConsentIds ?? new System.Collections.Generic.HashSet<int>()))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -652,9 +680,49 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
initTagInput('quoteTags', 'quoteTagsContainer');
|
||||
var custEl = document.getElementById('customerSelect');
|
||||
if (custEl) new TomSelect(custEl, { placeholder: '-- Select Customer --', openOnFocus: true, maxOptions: false });
|
||||
if (custEl) new TomSelect(custEl, { placeholder: '-- Select Customer --', openOnFocus: true, maxOptions: false,
|
||||
onChange: function(value) { onQuoteCustomerChanged({ value: value }); }
|
||||
});
|
||||
});
|
||||
|
||||
function onQuoteCustomerChanged(select) {
|
||||
const meta = JSON.parse(document.getElementById('quoteMetaData').textContent);
|
||||
const optOutIds = new Set(meta.emailOptOutCustomerIds || []);
|
||||
const noEmailIds = new Set(meta.noEmailCustomerIds || []);
|
||||
const smsConsentIds = new Set(meta.smsConsentCustomerIds || []);
|
||||
const customerId = parseInt(select.value) || 0;
|
||||
|
||||
const noEmail = customerId > 0 && noEmailIds.has(customerId);
|
||||
const emailSection = document.getElementById('emailNotifySection');
|
||||
const smsNote = document.getElementById('smsNotifyNote');
|
||||
|
||||
if (emailSection) emailSection.style.display = noEmail ? 'none' : '';
|
||||
|
||||
if (!noEmail) {
|
||||
const emailCheckbox = document.getElementById('SendEmailToCustomer');
|
||||
const emailNote = document.getElementById('emailOptOutNote');
|
||||
if (emailCheckbox) {
|
||||
const optedOut = optOutIds.has(customerId);
|
||||
emailCheckbox.disabled = optedOut;
|
||||
if (optedOut) emailCheckbox.checked = false;
|
||||
if (emailNote) emailNote.style.display = optedOut ? 'inline' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (smsNote) {
|
||||
if (noEmail && customerId > 0) {
|
||||
const hasSms = smsConsentIds.has(customerId);
|
||||
smsNote.style.display = 'inline';
|
||||
smsNote.className = hasSms ? 'badge bg-info text-white' : 'badge bg-warning text-dark';
|
||||
smsNote.innerHTML = hasSms
|
||||
? '<i class="bi bi-phone me-1"></i>No email — send via SMS from quote details'
|
||||
: '<i class="bi bi-phone-slash me-1"></i>No email — SMS consent required';
|
||||
} else {
|
||||
smsNote.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Discount type toggle
|
||||
function onDiscountTypeChange() {
|
||||
const type = document.getElementById('discountTypeSelect').value;
|
||||
|
||||
Reference in New Issue
Block a user