---
name: itemmodify_bom_editor_audit
description: "BOM editor (itemModify) audit — living doc, critical fixes done, high/structural open"
metadata: 
  node_type: memory
  type: project
  originSessionId: c2f3060e-767a-426b-97b9-25b40a1d7cc6
---

Living audit of the shared BOM editor `itemModify` (embedded in both `quotedit` and
`jobeditinv`/`jobedit`): `evolution/audits/ITEMMODIFY_BOM_EDITOR_AUDIT.md`. Started 2026-07-16.

Files: `itemModify.php` (AJAX backend, dispatch on `$_REQUEST['call']`), `itemModify.inc`,
`assets/js/modules/itemModify.js` (1,241 lines), resolvers `library/inventory.php::getBOM/getChildren`
+ `library/quote.php::showQuoteItemBom`. Two near-duplicate write paths: `updateQuoteItem`
(quoteitems*) and `addJobInventoryItem` (jobInventory*). See [[evolution_bom_hierarchy]].

**CRITICAL fixes DONE (working tree, not committed, not exercised live):**
- Auth gates added (`imHasAny`/`imDenyAccess`): reads = admin∪mod_sales∪mod_ar; writes = admin∪mod_sales.
  Shane: NO mod_jc on itemModify — sales perms only. (`checkSession` is login-only, see [[evolution_ajax_save_auth_gate]])
- Killed `foreach($_REQUEST as $k=>$v) $$k=$v` in getJobInvBOM + addJobInventoryItem → explicit typed reads.
- SQLi: `showQuoteItemBom` now casts $quoteitemid/$invid/$jobinvid to int at entry (were interpolated in SELECT).
- `date("Y-m-d h:i:s")`→`H`: 12h timestamp corrupted `MAX(jibdateadded)` BOM-revision selection. Historical rows may need repair.
- Client-reported EDIT-path price double-qty (§2.5): in modifyQuoteLineItem edit branch `iprice` was `icost*iqty` (line-extended) but used as unit sell → adjPrice/total inflated by qty; first-entry unaffected. Convention: quoteitems.adjPrice/price/taxAmount PER-UNIT, quoteitems.total = LINE (adjPrice*qty). Fix: iprice=icost, total=adjPrice*iqty, per-unit taxAmount from row #itaxValue. Evolution itemModify.js has NO min twin. Needs live verify (costplus + discount).

**HIGH fixes DONE (2026-07-16, working tree, not committed/live):** #6/#7 labour-var clobber → `$labourCost`/`$labourQty`, `$cost` stays BOM+labour total (job DB cost stays `$ncost`, by design); #8 routename `(...[0]->name ?? '')` kills 2 warnings/no-route save; #9 jobedit override `e.value`→`quoteItemId` (was ReferenceError aborting job save — base L628 `e.value` is CORRECT, inside forEach, untouched) + `adjPrice` decl moved above use (was NaN); #10 `markkup`→`markup` AND L759 `+ bomraw +`→`+ JSON.stringify(bomraw) +` (both needed — object stringified to "[object Object]" failed reader `isJson`, so BOM edit round-trip never restored process/labour/markup); #11 restore-prompt missing quote (`modInvRestoreStep0` is dead code). Verify facts: NO `json.*` response field consumed client-side; `php -l`+`node --check` clean.
**OPEN (structural):** setTimeout choreography (editQuoteBOM2 polls 500×); `eval()` step-0 snapshot; duplicated write handlers + duplicated modifyQuoteLineItem; implicit globals; referer-derived $quoteid.

No-build repo: JS fixes need min+nomin twins ([[portal_minified_no_build]] discipline).
