Stuff that runs out.
Toner. Spare drum kits. UPS batteries. Replacement keyboards. The stuff Inventory shouldn't track per-serial because it's interchangeable. Distinct table, distinct nav entry — but the same audit-trail muscle Resolvd uses everywhere. Every quantity change carries actor, reason, timestamp, and optional ticket link.
Per-SKU, per vendor.
A consumable row is one part-number you keep on hand. Linked to the vendor company that supplies it so reorder context is one click away. Stock never gets edited directly — direct PATCH /current_stock is rejected with 400 "use /:id/move". The audit trail can't be bypassed.
Unique vendor SKU — HP-58A-CF258A, APC-RBC110. Becomes the lead line on the printed label.
FK to the Resolvd company row that supplies the part. Surfaces the vendor name in the list + on labels.
Computed running total. Never mutated directly — every change goes through the ledger.
Trip-point for the red LOW chip on the list. Informational; no notification fires yet.
Free-text — "For HP LJ Pro M404n at Reception", "Datacenter A, UPS row 3".
Soft-delete flag. Archived rows hidden from the default list, kept in storage so the ledger never orphans.
Every delta. Who ran it. When.
Every stock change runs UPDATE consumables SET current_stock = current_stock + $delta + INSERT consumable_movements … in the same transaction. Inserts never get edited or deleted. A bad count? Log a count_correction row with the delta — the discrepancy stays on the timeline.
Signed int. +5 on receive, -1 on issuance, -2 on disposal. Sum across all rows equals current_stock.
Free-text but the seeded list is received | issued | returned | disposed | count_correction | loss.
Optional — link an issuance back to the ticket that consumed it. -1 Toner for ACME-0042.
Actor. Every adjustment carries who ran it.
Timestamp, server clock. Ledger is append-only — corrections happen via a new count_correction row, not by editing history.
Free-text — Newegg PO #4521, recall — supplier swap, inventory recount mismatch.
Five buttons. All audited.
Stock adjustment is one endpoint — POST /api/consumables/:id/move — with a delta, a reason, and optional context. The UI surfaces five common shapes; the API accepts arbitrary reasons so you can grow the vocabulary without a schema migration.
Detail → + N with reason received. Note the PO.
Detail → - 1 with reason issued, link the ticket id. Trail flows back to the consuming ticket.
- 1 reason issued on issue, + 1 reason returned on the way back. Sum nets zero on net-zero loans.
- N reason disposed. Keeps the deduction on the audit trail; the absence of a received counterpart explains the spend.
When a manual recount disagrees with on-screen — log the delta as count_correction. Discrepancy stays visible rather than hiding behind an edit.
A label, a QR, a scan back to the row.
Consumable detail carries a Print label button alongside its asset cousin. Same Zebra-backed pipeline, same raw TCP :9100 path. Lead line is the part_no, vendor on the property line, QR encodes FRONTEND_URL/consumables/<id>. Stick it on the shelf; scan it from a phone; you're at the right Resolvd row to log an issuance.
Stop the spreadsheet.
Define the SKUs that matter, set a reorder threshold, log a movement every time you touch the shelf. Stock numbers stop drifting because every change has an audit row.