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
@@ -28,13 +28,10 @@
// Each key must exactly match a property returned by the server's PatchItem action.
function updateTotals(cfg, data) {
const t = cfg.totals || {};
console.debug('[inline-edit] updateTotals data:', data, 'totals cfg:', t);
Object.entries(t).forEach(([key, selector]) => {
const val = data[key];
const els = document.querySelectorAll(selector);
console.debug(`[inline-edit] ${key} → "${selector}": val=${val}, elements=${els.length}`);
if (selector && val !== undefined && val !== null) {
els.forEach(el => { el.textContent = fmt(val); });
document.querySelectorAll(selector).forEach(el => { el.textContent = fmt(val); });
}
});
}
@@ -143,6 +140,9 @@
// Update document-level totals
updateTotals(cfg, data);
// Optional post-save hook (e.g. refresh a dependent card)
if (typeof cfg.afterSave === 'function') cfg.afterSave(data);
// Re-attach click listener for next edit
attachListeners(cfg, span);