Inline item editing on details pages; fix Stripe receipt_email
Allow description, quantity, and price to be edited inline on Quote, Job, and Invoice details pages without re-opening the wizard. Coating and prep service rows remain read-only by design. Invoice editing is gated to Draft/Sent/Overdue statuses; totals update live in the DOM. Remove receipt_email from Stripe PaymentIntent creation so customers can use any email they choose at checkout — Stripe validates format and sends the receipt to whatever the customer enters in the Payment Element, eliminating the risk of a stored email mismatch blocking a payment from processing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -276,14 +276,14 @@
|
||||
<tbody>
|
||||
@foreach (var item in catalogItems)
|
||||
{
|
||||
<tr>
|
||||
<tr data-item-id="@item.Id">
|
||||
<td>
|
||||
@{
|
||||
var displayDescription = item.Description == "Product Item" || string.IsNullOrWhiteSpace(item.Description)
|
||||
? (item.CatalogItemName ?? "Catalog Item")
|
||||
: item.Description;
|
||||
}
|
||||
<strong>@displayDescription</strong>
|
||||
<span data-inline-field="description" data-raw-value="@displayDescription"><strong>@displayDescription</strong></span>
|
||||
@if (item.CatalogItemId.HasValue &&
|
||||
item.Description != "Product Item" &&
|
||||
!string.IsNullOrWhiteSpace(item.Description))
|
||||
@@ -354,9 +354,9 @@
|
||||
<small class="text-muted"><i class="bi bi-sticky"></i> <strong>Notes:</strong> @item.Notes</small>
|
||||
}
|
||||
</td>
|
||||
<td>@item.Quantity</td>
|
||||
<td>@item.UnitPrice.ToString("C")</td>
|
||||
<td><strong>@item.TotalPrice.ToString("C")</strong></td>
|
||||
<td><span data-inline-field="quantity" data-raw-value="@item.Quantity">@item.Quantity</span></td>
|
||||
<td><span data-inline-field="unitPrice" data-raw-value="@item.UnitPrice">@item.UnitPrice.ToString("C")</span></td>
|
||||
<td data-line-total><strong>@item.TotalPrice.ToString("C")</strong></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@@ -394,9 +394,9 @@
|
||||
var totalSqFt = item.SurfaceAreaSqFt * item.Quantity;
|
||||
var powderOnPart = totalSqFt / coverageRate;
|
||||
var totalPowderNeeded = powderOnPart / transferEff;
|
||||
<tr>
|
||||
<tr data-item-id="@item.Id">
|
||||
<td>
|
||||
<strong>@item.Description</strong>
|
||||
<span data-inline-field="description" data-raw-value="@item.Description"><strong>@item.Description</strong></span>
|
||||
|
||||
@* Display coating layers *@
|
||||
@if (item.Coats != null && item.Coats.Any())
|
||||
@@ -474,7 +474,7 @@
|
||||
<small class="text-muted"><i class="bi bi-sticky"></i> <strong>Notes:</strong> @item.Notes</small>
|
||||
}
|
||||
</td>
|
||||
<td>@item.Quantity</td>
|
||||
<td><span data-inline-field="quantity" data-raw-value="@item.Quantity">@item.Quantity</span></td>
|
||||
<td>
|
||||
@item.SurfaceAreaSqFt.ToString("F2") @ViewBag.AreaUnit
|
||||
<br /><small class="text-muted">per item</small>
|
||||
@@ -487,8 +487,8 @@
|
||||
<strong class="text-success">@totalPowderNeeded.ToString("F2") lbs</strong>
|
||||
<br /><small class="text-muted">total batch</small>
|
||||
</td>
|
||||
<td>@item.UnitPrice.ToString("C")</td>
|
||||
<td><strong>@item.TotalPrice.ToString("C")</strong></td>
|
||||
<td><span data-inline-field="unitPrice" data-raw-value="@item.UnitPrice">@item.UnitPrice.ToString("C")</span></td>
|
||||
<td data-line-total><strong>@item.TotalPrice.ToString("C")</strong></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@@ -1023,7 +1023,7 @@
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Subtotal:</span>
|
||||
<strong>@Model.PricingBreakdown.SubtotalBeforeDiscount.ToString("C")</strong>
|
||||
<strong id="quote-subtotal">@Model.PricingBreakdown.SubtotalBeforeDiscount.ToString("C")</strong>
|
||||
</div>
|
||||
|
||||
@if (Model.PricingBreakdown.DiscountAmount > 0)
|
||||
@@ -1075,7 +1075,7 @@
|
||||
{
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Tax (@Model.PricingBreakdown.TaxPercent.ToString("G29")%):</span>
|
||||
<strong>@Model.PricingBreakdown.TaxAmount.ToString("C")</strong>
|
||||
<strong id="quote-tax">@Model.PricingBreakdown.TaxAmount.ToString("C")</strong>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1083,7 +1083,7 @@
|
||||
|
||||
<div class="d-flex justify-content-between mb-0">
|
||||
<h5>Total:</h5>
|
||||
<h5 class="text-primary"><strong>@Model.Total.ToString("C")</strong></h5>
|
||||
<h5 class="text-primary"><strong id="quote-total">@Model.Total.ToString("C")</strong></h5>
|
||||
</div>
|
||||
|
||||
@if (Model.PricingBreakdown.DiscountAmount > 0)
|
||||
@@ -2262,6 +2262,18 @@
|
||||
|
||||
@section Scripts {
|
||||
<script src="~/js/customer-change.js" asp-append-version="true"></script>
|
||||
<script src="~/js/inline-item-edit.js" asp-append-version="true"></script>
|
||||
<script>
|
||||
window.inlineItemEdit = {
|
||||
patchUrl: '@Url.Action("PatchItem", "Quotes")',
|
||||
canEdit: true,
|
||||
totals: {
|
||||
subtotal: '#quote-subtotal',
|
||||
tax: '#quote-tax',
|
||||
total: '#quote-total'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
function sendQuoteToAdHocEmail(quoteId) {
|
||||
const email = (document.getElementById('quoteAdHocEmailInput').value ?? '').trim();
|
||||
|
||||
Reference in New Issue
Block a user