Inline item editing on Job Details with live pricing and costing updates

- PatchItem: add case-insensitive JSON deserialization; add legacy fallback
  that computes a live breakdown from job items when PricingBreakdownJson is null
- PatchItem: return itemsSubtotal, subtotalBeforeDiscount, subtotalAfterDiscount,
  taxAmount in JSON response for immediate DOM updates
- GetCostingBreakdown: use job.FinalPrice as revenue (not invoice total) so
  costing figures reflect inline edits before an invoice exists
- Details.cshtml: add data-pb attributes to visible pricing rows; add
  job-final-price-display class to visible Total element
- Details.cshtml: wire afterSave callback to call costing.load() after each edit
- inline-item-edit.js: add afterSave hook in commit(); clean up debug logging
- Help docs: add Inline Price Editing sections to Jobs, Quotes, and Invoices
  help articles; add inline editing + job costing revenue notes to AI knowledge base

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 23:56:36 -04:00
parent ec925f9e08
commit 1bb07162cd
7 changed files with 122 additions and 24 deletions
@@ -1479,7 +1479,7 @@
{
<div class="d-flex justify-content-between mb-2">
<span>Items Subtotal:</span>
<strong>@jobPb.ItemsSubtotal.ToString("C")</strong>
<strong data-pb="itemsSubtotal">@jobPb.ItemsSubtotal.ToString("C")</strong>
</div>
@if (jobPb.OvenBatchCost > 0)
@@ -1508,7 +1508,7 @@
<div class="d-flex justify-content-between mb-2">
<span>Subtotal:</span>
<strong>@jobPb.SubtotalBeforeDiscount.ToString("C")</strong>
<strong data-pb="subtotalBeforeDiscount">@jobPb.SubtotalBeforeDiscount.ToString("C")</strong>
</div>
@if (jobPb.DiscountAmount > 0)
@@ -1552,14 +1552,14 @@
{
<div class="d-flex justify-content-between mb-2">
<span>Tax (@jobPb.TaxPercent.ToString("G29")%):</span>
<strong>@jobPb.TaxAmount.ToString("C")</strong>
<strong data-pb="taxAmount">@jobPb.TaxAmount.ToString("C")</strong>
</div>
}
<hr />
<div class="d-flex justify-content-between mb-3">
<h5>Total:</h5>
<h5 class="text-primary"><strong>@jobPb.Total.ToString("C")</strong></h5>
<h5 class="text-primary"><strong class="job-final-price-display">@jobPb.Total.ToString("C")</strong></h5>
</div>
@* Collapsible detail breakdown *@
@@ -2427,7 +2427,8 @@
subtotalAfterDiscount: '[data-pb="subtotalAfterDiscount"]',
taxAmount: '[data-pb="taxAmount"]',
finalPrice: '.job-final-price-display'
}
},
afterSave: () => { if (typeof costing !== 'undefined') costing.load(); }
};
</script>
<script>