Replaces the unified audit system with two purpose-built flows:
- Weigh Ins: rebranded audit flow for bulk products (Flower, Concentrate,
Tincture) with scale weigh, container weigh, and estimate modes
- Bin Checks: new bin-level presence check — select a bin, scan every item,
resolve discrepancies (wrong bin, unknown, missing), auto-records presence
audits on verified items
Adds cadence_days and last_checked to bins table, with per-bin overdue
tracking on the dashboard and bins view.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comprehensive UX audit covering modals, drawers, dashboard, and inventory.
Key changes: confirmation steps before destructive actions, undo via toast
for consume/gone/checkout, back-navigation across entity drawers, optional
ratings, discrete item count field, audit progress bar, and sortable column
affordance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Record the total weight of a jar (product + container) at acquisition so
audits can be done by simply re-weighing the sealed jar. The tare is
derived (containerWeight − productWeight), and the audit flow offers a
"Weigh container" toggle that auto-calculates remaining product from the
scale reading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Items can now be checked out of their bin into "my custody" and later
checked back in or marked consumed. Adds checkout/checkin API endpoints,
a My Custody sidebar page, CheckoutFlow and CheckinFlow modals, and
updates ProductDetail, Inventory, ConsumeFlow, and MarkGoneFlow to
handle the new checked-out status. Bulk items prompt for remaining
weight on check-in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Four UX changes after using the rework for a bit:
1. Asset ids are 6-digit numbers from a roll of physical labels — server
no longer generates them. POST /api/inventory requires assetId; the
add-inventory form has a digits-only input that auto-focuses on entry.
2. Strain and product name are the same thing. Drop products.name; the
strain's name supplies the display. Product creation just asks for
"Name (strain)" and matches/creates a strain by that name.
3. Brand moves from inventory_items to products. SKUs are brand-specific,
so all instances of a product share the brand. Brand selector lives
on the product create/edit form, not the per-instance form.
4. Scanning an unknown SKU on the add-inventory step now opens the
create-product subform with the SKU prefilled — one less click.
Migration: detect prior shape (products.name column present) and rename
products/inventory_items/audits to *_v1 archives, recreate empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
The client constant TODAY_STR and the bootstrap response's today field
were both hardcoded to 2026-04-25, leaving date inputs in the New
product, Audit, and Mark consumed flows seeded with a stale date and
quietly staling out every "days since" / audit-overdue calculation
across the app.
TODAY_STR now resolves at module load from new Date() in local time,
and bootstrap returns new Date().toISOString().slice(0, 10).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>