Inventory module
The stock console. SKU search → variant detail → Product Card editor → stock-movement ledger. Split out of the former two-face Products module in v0.7.69; the Purchasing side became its own Purchases module.
| Tab label | Inventory |
| Tab accent | #0d9488 (teal) |
| Display order | 21 (right after Purchases at 20) |
| Folder | src/modules/inventory/ |
| Migrations shared | 018_products_catalog.sql, 019_inventory.sql, 022_products_perms_consolidate.sql, 024_rma.sql, 032_bundled_accessory_tax.sql |
What it owns
src/modules/inventory/
index.ts — registers the module with the Kernel
view.ts — markup + styles for the stock console
client.ts — browser JS (search, variant detail, Product Card editor, movements)
api.ts — server-side routes (/api/m/inventory/*)
The inventory-movement engine lives at core/movements.ts — extracted to Core in v0.7.69 so Sales and Service can decrement stock through the same code path Inventory does. Every change to inventory.quantity_on_hand writes one immutable row to inventory_movements.
What it does
SKU search. Free-text across product name, variant SKU, barcode; returns matching variants with on-hand readout per row. Picking one loads the variant detail.
Variant detail — read-only. Rebuilt to prior-implementation Inventory-tab parity in v0.7.67. Sections:
| Section | Content |
|---|---|
| Hero | Product name + variant descriptor (size / colour / year) + SKU + primary image |
| Stock | On-hand · reorder point · on-order · low-stock flag (tabular numerals so the numbers align) |
| Vendors | The variant's supplier links (supplier_products — supplier, cost, primary flag). Rich vendor record (account #, address, website / B2B portal, logo, drop-ship) opens from a row click |
| IDs | Barcode / UPC · aim_id · internal id |
| Description | Free-form product description + specs |
Product Card editor. The full editable surface for a product + its variants — one dialog covering every field on every section. Replaces the older Face-A section-edit chain that was removed in v0.7.68 (that chain was orphaned once the Product Card superseded it). Save writes to the catalog + inventory rows; the audit chain captures the diff.
The three stock actions. The only way quantity_on_hand changes from this surface:
| Action | Process | What it does |
|---|---|---|
| Adjust | BIKE.L2-0046 | Signed delta with a reason; writes a movement_type='adjust' row via core/movements.ts |
| Cycle count | BIKE.L2-0084 | Set the on-hand to the counted number; movement records the implied delta |
| Write-off | BIKE.L2-0085 | Removes units with a reason (damage, theft, demo) |
Every action goes through Core's movement engine — write the movement row, then update inventory.quantity_on_hand, then audit. The on-hand can never drift from the sum of its movements.
Stock-movement ledger — 7 analytical views (BIKE.L2-0149 + 0145). The ledger pane at the bottom carries a Tier-2 view Pill that selects one of seven views:
| View | Shape |
|---|---|
today | Movement-shaped: every movement in the last 24 hours |
sold_today | Movement-shaped: sales-type movements today |
last_received | Movement-shaped: most-recent receive movements |
top_sellers | Variant-shaped: highest sale volume in the window |
bottom_sellers | Variant-shaped: lowest sale volume |
out_of_stock | Variant-shaped: quantity_on_hand <= 0 |
old_stock | Variant-shaped: no movement in N days |
/movements?view=X is the unified endpoint. The ledger <thead> + rows adapt per shape; variant rows click through back into the variant detail. Replaces the older movement-type filter dropdown.
Recent purchasers (BIKE.L2-0148). The variant detail surfaces a Recent purchasers section — who bought this SKU, newest first, with the sale_id for each. Click a row to open the sale in a read-only viewer (GET /api/m/sales/transaction).
Reorder dashboard (BIKE.L2-0088 / 0089). A low-stock surface that lists every variant at or below its reorder_point, with a per-row Add to Bucket action pre-quantitied to reorder_qty. Reachable regardless of low-stock count (v0.7.60 fix; empty state reads "All stocked above reorder point").
Print tag (BIKE.L2-0047). Variant detail footer Print tag opens a preview of the SKU tag — product name, price, a hand-rolled CODE39 barcode of the SKU, SKU text — then Print opens a print window.
Bundled-accessory tax rule (v0.7.81, migration 032). Accessories bundled with a bike at Sales time drop PST (BC PST Bulletin 204); standalone they carry both GST + PST. The tax_categories seed carries is_context_dependent + context_rule for the bundled case; Sales computePerLineTaxes reads the context at cart time.
Vendor RMA. Vendor returns (migration 024, BIKE.L2-0091) — rma header (status: draft / sent / received / closed / cancelled) + rma_lines (per-variant qty / reason / disposition). Operator UI ships from this module's Vendors surface.
Data model
Catalog (018). tax_categories (BC two-rate model + bundled-accessory context; migration 032 seeds the context rule), tax_rates, categories + subcategories, manufacturers, products, product_variants (the canonical SKU), product_barcodes.
Inventory (019). inventory_locations (single-shop seeds "Main Shop"), inventory (per variant per location: quantity_on_hand, reorder thresholds), inventory_movements (the immutable ledger — type, signed quantity, before / after on-hand, reason, originating reference).
Vendor RMA (024). rma + rma_lines.
Permissions
| Permission | What it gates |
|---|---|
screen.inventory | The Inventory tab is visible at all |
inventory.read | Search + variant detail + movement ledger |
inventory.edit | Product Card editor + Adjust / Cycle count / Write-off |
inventory.rma | Vendor RMA lifecycle |
See also
- Three strata — Kernel · Meta · Modules — the architectural shape (
core/movements.tsextracted here) - Purchases module — the PO / Bucket side that used to be Face B
- Sales module — sells parts that decrement through the same movement engine
- Service module — parts lines decrement through the movement engine at invoice time