@using PowderCoating.Application.DTOs.Invoice @using PowderCoating.Core.Enums @using PowderCoating.Web.Controllers @model InvoiceDto @{ ViewData["Title"] = $"Invoice {Model.InvoiceNumber}"; ViewData["PageIcon"] = "bi-receipt"; var statusColor = InvoicesController.GetStatusColorClass(Model.Status); var statusDisplay = InvoicesController.GetStatusDisplay(Model.Status); var isDraft = Model.Status == InvoiceStatus.Draft; var isVoided = Model.Status == InvoiceStatus.Voided || Model.Status == InvoiceStatus.WrittenOff; var canEdit = Model.Status is InvoiceStatus.Draft or InvoiceStatus.Sent or InvoiceStatus.Overdue; var canPay = !isVoided && Model.BalanceDue > 0; 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)ViewBag.AvailableCreditMemos).Any(); var canIssueRefund = !isDraft && !isVoided && Model.AmountPaid > 0; var canApplyCredit = !isVoided && Model.BalanceDue > 0 && hasAvailableCredits; string? paymentUrl = ViewBag.PaymentUrl as string; var linkExpired = !string.IsNullOrEmpty(Model.PaymentLinkToken) && (Model.PaymentLinkExpiresAt == null || Model.PaymentLinkExpiresAt <= DateTime.UtcNow); var onlinePaymentsEnabled = ViewBag.OnlinePaymentsEnabled == true; var showOnlinePaymentCard = !isDraft && !isVoided && Model.BalanceDue > 0 && onlinePaymentsEnabled; var guidedActivationCallout = ViewBag.GuidedActivationCallout as PowderCoating.Web.ViewModels.GuidedActivation.GuidedActivationCalloutViewModel; var nonDepositPaymentTotal = Model.Payments .Where(p => !(p.Reference ?? "").StartsWith("Deposit ")) .Sum(p => p.Amount); }
@if (TempData["Success"] != null) { } @if (TempData["Error"] != null) { } @if (!hasEmail) {
@Model.CustomerName has no email address on file — you'll be prompted to enter one when sending. Add one in customer settings.
} else if (emailOptedOut) {
@Model.CustomerName has email notifications turned off. Email buttons are disabled. You can change this in the customer settings.
} @if (guidedActivationCallout?.Show == true) {
@guidedActivationCallout.Title
@guidedActivationCallout.Message
}
Status: @statusDisplay @if (Model.BalanceDue > 0 && !isVoided) { Balance Due: @Model.BalanceDue.ToString("C") }
Invoice Information

@Model.InvoiceNumber

@statusDisplay

@Model.CustomerName

@if (!string.IsNullOrWhiteSpace(Model.CustomerEmail)) {

@Model.CustomerEmail

}

@if (Model.JobId.HasValue) { @Model.JobNumber } else { Merchandise Sale }

@Model.InvoiceDate.ToString("MMMM d, yyyy")

@(Model.DueDate.HasValue ? Model.DueDate.Value.ToString("MMMM d, yyyy") : "—")

@(Model.SentDate.HasValue ? Model.SentDate.Value.ToString("MMMM d, yyyy") : "—")

@if (!string.IsNullOrWhiteSpace(Model.CustomerPO)) {

@Model.CustomerPO

} @if (!string.IsNullOrWhiteSpace(Model.ExternalReference)) {

@Model.ExternalReference

} @if (!string.IsNullOrWhiteSpace(Model.Terms)) {

@Model.Terms

} @if (!string.IsNullOrWhiteSpace(Model.PreparedByName)) {

@Model.PreparedByName

}
Line Items
@foreach (var item in Model.InvoiceItems) { }
Description Qty Unit Price Total
@item.Description @if (!string.IsNullOrWhiteSpace(item.ColorName)) { @item.ColorName } @if (!string.IsNullOrWhiteSpace(item.Notes)) { @item.Notes } @item.Quantity.ToString("G") @item.UnitPrice.ToString("C") @item.TotalPrice.ToString("C")
Subtotal @Model.SubTotal.ToString("C")
@if (Model.DiscountAmount > 0) {
Discount (@Model.DiscountAmount.ToString("C"))
} @if (Model.TaxPercent > 0) {
Tax (@Model.TaxPercent.ToString("G")%) @if (!string.IsNullOrEmpty(Model.SalesTaxAccountName)) { @Model.SalesTaxAccountName } @Model.TaxAmount.ToString("C")
}
Total @Model.Total.ToString("C")
@if (Model.AmountPaid > 0) {
Amount Paid @Model.AmountPaid.ToString("C")
Balance Due @Model.BalanceDue.ToString("C")
}
@if (!string.IsNullOrWhiteSpace(Model.Notes)) {
Notes

@Model.Notes

} @{ var issuedGcs = Model.InvoiceItems.Where(i => i.IsGiftCertificate && i.GeneratedGiftCertificateId.HasValue).ToList(); } @if (issuedGcs.Any()) {
Gift Certificates Issued
@foreach (var gcItem in issuedGcs) { }
Certificate Code Recipient Face Value
@gcItem.GeneratedGiftCertificateCode @(gcItem.Description.Contains("for ") ? gcItem.Description.Substring(gcItem.Description.IndexOf("for ") + 4).TrimEnd(')') : "—") @gcItem.TotalPrice.ToString("C")
} @if (Model.Payments.Any()) {
Payment History
@foreach (var p in Model.Payments) { }
Date Method Reference Deposited To Recorded By Amount
@p.PaymentDate.ToString("MM/dd/yyyy") @p.PaymentMethodDisplay @(p.Reference ?? "—") @if (!string.IsNullOrEmpty(p.DepositAccountName)) { @p.DepositAccountName } else { } @(p.RecordedByName ?? "—") @p.Amount.ToString("C") @if (!isVoided) {
@Html.AntiForgeryToken()
}
} @if (Model.Refunds.Any()) {
Refunds Issued
@foreach (var r in Model.Refunds) { var refundStatusColor = r.Status == RefundStatus.Issued ? "success" : r.Status == RefundStatus.Cancelled ? "secondary" : "warning"; }
Date Method Reason Reference Status Amount
@r.RefundDate.ToString("MM/dd/yyyy") @r.RefundMethodDisplay @r.Reason @(r.Reference ?? "—") @r.Status (@r.Amount.ToString("C")) @if (r.Status == RefundStatus.Pending) {
@Html.AntiForgeryToken()
@Html.AntiForgeryToken()
}
} @if (Model.GiftCertificateRedemptions.Any()) {
Gift Certificates Applied
@foreach (var gr in Model.GiftCertificateRedemptions) { }
Date Certificate Amount Applied
@gr.RedeemedDate.ToString("MM/dd/yyyy") GC-@gr.GiftCertificateId.ToString("D4") (@gr.AmountRedeemed.ToString("C"))
} @if (Model.CreditApplications.Any()) {
Credits Applied
@foreach (var ca in Model.CreditApplications) { }
Date Credit Memo Amount Applied
@ca.AppliedDate.ToString("MM/dd/yyyy") @ca.MemoNumber (@ca.AmountApplied.ToString("C"))
}
Actions
@if (canEdit) { Edit Invoice
@Html.AntiForgeryToken() @if (emailOptedOut && !hasSms) { } else if (showSendModal) { @* Both email + SMS available — let staff choose *@ } else if (directSendSms) { @* SMS only — send directly *@ } else if (hasEmail && !emailOptedOut) { } else { }
} @if (canPay) { } Print Download PDF @if (canResend) { @if (!hasEmail) { } else if (emailOptedOut) { } else { } } @if (canIssueRefund) { } @if (canApplyCredit) { } @if (canPay) { } @if (Model.JobId.HasValue) { View Job } @if (!isVoided && Model.Status != InvoiceStatus.Paid) {
@Html.AntiForgeryToken()
} @if (!isVoided && Model.BalanceDue > 0) { } @if (isDraft) {
@Html.AntiForgeryToken()
}
Summary
Invoice Total @Model.Total.ToString("C")
Amount Paid @Model.AmountPaid.ToString("C")
@if (Model.CreditApplied > 0) {
Credits Applied (@Model.CreditApplied.ToString("C"))
} @if (Model.GiftCertificateRedeemed > 0) {
Gift Certificate (@Model.GiftCertificateRedeemed.ToString("C"))
}
Balance Due @Model.BalanceDue.ToString("C")
@if (Model.PaidDate.HasValue) {
Paid in full on @Model.PaidDate.Value.ToString("MMMM d, yyyy")
}
@if (showOnlinePaymentCard) {
Online Payment Link
@if (Model.OnlineAmountPaid > 0) {
Collected online @Model.OnlineAmountPaid.ToString("C")
} @if (!string.IsNullOrEmpty(paymentUrl)) {
Active — expires @Model.PaymentLinkExpiresAt!.Value.ToString("MMM d")
Preview Payment Page
} else if (linkExpired) {
Payment link expired.
} else {

No payment link yet. A link is auto-generated when you send the invoice, or you can create one now.

}
}
@* Hidden anti-forgery token for AJAX calls *@ @Html.AntiForgeryToken() @if (isDraft) { } @if (showSendModal) { } @if (canPay) { } @if (!isVoided) { } @if (canApplyCredit) { } @if (canPay) { } @if (!isVoided && Model.BalanceDue > 0) { } @section Scripts { }