Commit Graph

5 Commits

Author SHA1 Message Date
josh 02dc6e523f Track inventory at the instance level, not by product
Build and push image / build (push) Successful in 46s
The products table conflated catalog ("kind of thing you scan") with
instance ("this jar I bought") — splitting it lets us record every
purchase as its own asset and autofill brand/shop/price/THC from the
last instance when scanning a known SKU.

- products: sku + strain + name + type + kind (catalog only)
- inventory_items: physical jars with short-UUID asset ids, per-batch
  brand/shop/bin/price/cannabinoids/weight, audits, lifecycle
- audits now key on inventory_id; strains lose brand_id and type
- migration: rename existing products/audits/strains to *_legacy on
  first boot so users keep historical reference, fresh start otherwise
- two-step add flow: scan SKU → select/create product → instance
  details (autofilled from last instance) → generated asset id shown
- ScanField matches asset id first, falls back to SKU
- inventory list defaults flat, "By product" toggle groups instances

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 05:59:46 -04:00
josh d335525073 Group bins by letter, sort by number, drop location
Build and push image / build (push) Successful in 46s
Bins follow an A1/A2/B1 naming convention, so the Bins page now parses
the leading letter prefix as a row group and the trailing number as the
within-row order. Each letter starts a fresh grid section; bins whose
names don't match the pattern fall into a trailing "Other" bucket
sorted alphabetically.

Removes the optional location field from bins end to end: the API
client signatures, server POST/PATCH routes, both product-flow inline
creates, the dropdown labels, the ProductDetail bin row, and the
BinsView header line. The bootstrap query explicitly projects only
id/name/capacity so the dead column doesn't leak through.

The location column stays in the bins table on disk to avoid a
migration on existing deployments — it just isn't read or written.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:07:12 -04:00
josh cd7aeb9d09 Per-unit pricing for discrete products
Build and push image / build (push) Successful in 51s
Pre-rolls, edibles, and vaporizers are sold and reasoned about per unit
("$10 each") rather than as a bag total. The Add and Edit forms now ask
for "Price per unit" when the kind is discrete, and the product drawer
displays the per-unit number with the total as a small subline. Bulk
products (flower, concentrate, tincture) still take and show a total.

The stored price column remains the total, so existing data, spend
totals, sort-by-price, and bin value calculations all keep working
unchanged. Conversion (pricePerUnit × countOriginal) happens at the
form boundary on save and the inverse on edit-modal load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:59:35 -04:00
josh 592bb28740 Edit existing products
Adds PATCH /products/:id and an EditProductFlow modal opened from the
product drawer. Editable fields cover name, brand, shop, bin, asset tag,
price, purchase date, size (weight or count + unit weight), and the
cannabinoid profile. SKU, type, kind, and status-derived dates stay
locked because changing them would invalidate audit history math; type
changes are surfaced as "mark gone, add new" in the modal.

The strain row is re-resolved on name or brand change so analytics stay
aligned, and the last-audit mirror (last_audit_weight / count_last_audit)
only syncs with the original size when there are no audits yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:42:33 -04:00
josh 027cf032be Initial commit: Apothecary v0.4.0 2026-05-03 20:19:26 -04:00