---
name: evolution_invbomrep_rebuild
description: Bill of Material Detail report (invbomrep) rebuilt as a modern read-only BOM cost audit
metadata: 
  node_type: memory
  type: project
  originSessionId: 6421cda0-57c1-4756-aef1-ef9ecb58bfe2
  modified: 2026-07-20T03:15:43.661Z
---

`invbomrep` (Reports → Inventory → Manufacturing → "Bill of Material Detail") rebuilt 2026-07-20 to the modern report standard, extending [[evolution_inventory_analytics_suite]]. Files: `reports/invbomrep.inc` (inline filter bar, not the old cloned filter modal), `reports/invbomrep.js` (prefix `bd`, live sort/search + expandable component breakdown + CSV), `reports/invbomrepSave.php` (JSON endpoint, capital-S per house convention). Deleted the old `reports/invbomrepsave.php` (lowercase) + a committed `.swp`.

**Purpose:** catalogue-wide audit of single-level MASTER BOMs (not recursive, not job/quote overrides — that's BOM Restocking). For each assembly: its own cost/price vs the rolled-up component cost (SUM(qty × component current master cost)) and the variance, so stale/mis-priced BOMs stand out. Read-only.

**Key model facts:**
- Assemblies = `inventory` rows that appear in `bom.parent`. Driven off `INNER JOIN (SELECT DISTINCT parent FROM bom) b ON b.parent = p.id`. `bom.parent` is varchar storing the parent `inventory.id`; `bom.inventoryId` is the child item. Both MyISAM, no useful indexes — bounded with `LIMIT 2000` parents (dev tenant has ~1913 assemblies).
- No N+1: old report ran one `bom` query PER inventory row + interpolated `$_REQUEST` raw (SQLi) + had NO auth gate. New = 2 bounded parameterised queries (parents, then one child fetch with a flat `IN (?,?,…)`), gated on mod_rep+mod_inv or admin.
- Component unit cost = `COALESCE(c.cost, b.cost)` (live master cost, fall back to stored bom.cost if item deleted).

**Dropped:** the documented "Synchronize Pricing to Xero" headline action — it was already fully broken in this report (hidden button never un-hidden, form posted to `pricingsyncsave.php` which needs `update[]`/`itemID[]`/`description[]` fields the preview never rendered). User chose to drop it, not re-fix. `pricingsyncsave.php` (shared by `page=pricingsync`) left untouched.

**Gotcha — orphaned `topselling` report:** `reports/topselling.js` fetched the old `invbomrepsave.php`, but `topselling` has NO `.inc` and is unregistered (not in invreports/reportsdashboard/reportdb) → unreachable/dead. Deleting invbomrepsave.php broke nothing live. `mf{Demand,Load}ByInvCat.inc` reference it only as a cosmetic `name=` attribute (action=pricingsyncsave.php).

**Docs + release notes:** docs section rewritten in `docs/reporting/inventory.php` (BOM Detail heading). Dashboard desc in `reportsdashboard.inc` corrected. Release note **id 131** injected to prod `releasenotes` (status=0 unapproved, public=1, module Inventory) per [[live_inject_release_notes]]. OPEN: `docs/reporting/images/inventory/bom-detail.png` is stale (shows old UI + Xero sync) — regenerate via [[browser_screenshot_tooling]] and re-embed.
