Add invoice SMS notifications and customer intake kiosk
Invoice SMS:
- Send Invoice modal now prompts Email/SMS/Both based on customer contact data
- New /invoice/{token} customer-facing view page with full line items and pay button
- PublicViewToken (permanent) added to Invoice; separate from expiring PaymentLinkToken
- InvoiceSent SMS default template added; customizable via Notification Templates settings
- {{viewUrl}} placeholder documented in template editor
Customer Intake Kiosk:
- Tablet kiosk flow: Contact → Job → Terms/Signature → Confirmation
- Remote link mode for off-site customers (lighter form, no signature)
- KioskHub (AllowAnonymous SignalR) for staff-to-tablet push without login
- Staff activates tablet via cookie; sends remote link manually
- Submitted sessions create Customer + Job automatically; fires in-app notification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,10 @@
|
||||
var canResend = !isDraft && !isVoided && Model.Status != InvoiceStatus.Paid;
|
||||
var hasEmail = !string.IsNullOrWhiteSpace(Model.CustomerEmail);
|
||||
var emailOptedOut = hasEmail && !Model.CustomerNotifyByEmail;
|
||||
var smsPhone = !string.IsNullOrWhiteSpace(Model.CustomerMobilePhone) ? Model.CustomerMobilePhone : Model.CustomerPhone;
|
||||
var hasSms = !string.IsNullOrWhiteSpace(smsPhone) && Model.CustomerNotifyBySms;
|
||||
var showSendModal = hasEmail && !emailOptedOut && hasSms; // both channels — show choice modal
|
||||
var directSendSms = !hasEmail && hasSms; // SMS only — skip modal
|
||||
var hasAvailableCredits = ViewBag.AvailableCreditMemos != null && ((IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.AvailableCreditMemos).Any();
|
||||
var canIssueRefund = !isDraft && !isVoided && Model.AmountPaid > 0;
|
||||
var canApplyCredit = !isVoided && Model.BalanceDue > 0 && hasAvailableCredits;
|
||||
@@ -579,14 +583,32 @@
|
||||
<form id="sendInvoiceForm" asp-action="Send" asp-route-id="@Model.Id" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="overrideEmail" id="sendInvoiceOverrideEmail" value="" />
|
||||
@if (emailOptedOut)
|
||||
<input type="hidden" name="sendEmail" id="sendInvoiceSendEmail" value="true" />
|
||||
<input type="hidden" name="sendSms" id="sendInvoiceSendSms" value="false" />
|
||||
@if (emailOptedOut && !hasSms)
|
||||
{
|
||||
<button type="button" class="btn btn-primary w-100" disabled
|
||||
title="Email notifications are turned off for this customer">
|
||||
title="No delivery channel available for this customer">
|
||||
<i class="bi bi-send me-2"></i>Send Invoice
|
||||
</button>
|
||||
}
|
||||
else if (hasEmail)
|
||||
else if (showSendModal)
|
||||
{
|
||||
@* Both email + SMS available — let staff choose *@
|
||||
<button type="button" class="btn btn-primary w-100"
|
||||
data-bs-toggle="modal" data-bs-target="#sendChannelModal">
|
||||
<i class="bi bi-send me-2"></i>Send Invoice
|
||||
</button>
|
||||
}
|
||||
else if (directSendSms)
|
||||
{
|
||||
@* SMS only — send directly *@
|
||||
<button type="button" class="btn btn-primary w-100"
|
||||
onclick="submitSendInvoice(false, true)">
|
||||
<i class="bi bi-send me-2"></i>Send Invoice via SMS
|
||||
</button>
|
||||
}
|
||||
else if (hasEmail && !emailOptedOut)
|
||||
{
|
||||
<button type="button" class="btn btn-primary w-100"
|
||||
data-bs-toggle="modal" data-bs-target="#sendInvoiceModal">
|
||||
@@ -839,13 +861,50 @@
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-0">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="document.getElementById('sendInvoiceForm').submit()">
|
||||
<button type="button" class="btn btn-primary" onclick="submitSendInvoice(true, false)">
|
||||
<i class="bi bi-send me-1"></i>Yes, Send Invoice
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (showSendModal)
|
||||
{
|
||||
<!-- Send Channel Choice Modal (shown when customer has both email + SMS) -->
|
||||
<div class="modal fade" id="sendChannelModal" tabindex="-1" aria-labelledby="sendChannelModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title" id="sendChannelModalLabel">
|
||||
<i class="bi bi-send text-primary me-2"></i>Send Invoice
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body pt-2">
|
||||
<p class="mb-3">How would you like to send <strong>@Model.InvoiceNumber</strong> to <strong>@Model.CustomerName</strong>?</p>
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" class="btn btn-outline-primary text-start" onclick="submitSendInvoice(true, false)" data-bs-dismiss="modal">
|
||||
<i class="bi bi-envelope me-2"></i>Email only
|
||||
<small class="d-block text-muted ms-4">PDF attached · @Model.CustomerEmail</small>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary text-start" onclick="submitSendInvoice(false, true)" data-bs-dismiss="modal">
|
||||
<i class="bi bi-phone me-2"></i>SMS only
|
||||
<small class="d-block text-muted ms-4">View link · @smsPhone</small>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary text-start" onclick="submitSendInvoice(true, true)" data-bs-dismiss="modal">
|
||||
<i class="bi bi-send me-2"></i>Both Email & SMS
|
||||
<small class="d-block text-muted ms-4">PDF via email + view link via SMS</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-0">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (canPay)
|
||||
@@ -1381,6 +1440,12 @@
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function submitSendInvoice(sendEmail, sendSms) {
|
||||
document.getElementById('sendInvoiceSendEmail').value = sendEmail ? 'true' : 'false';
|
||||
document.getElementById('sendInvoiceSendSms').value = sendSms ? 'true' : 'false';
|
||||
document.getElementById('sendInvoiceForm').submit();
|
||||
}
|
||||
|
||||
function openEditPaymentModal(paymentId, invoiceId, paymentDate, paymentMethod, reference, notes, depositAccountId) {
|
||||
document.getElementById('editPaymentId').value = paymentId;
|
||||
document.getElementById('editPaymentDate').value = paymentDate;
|
||||
|
||||
Reference in New Issue
Block a user