Commit Graph

8 Commits

Author SHA1 Message Date
josh 1abfda7989 Use the real today everywhere
Build and push image / build (push) Successful in 53s
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>
2026-05-03 22:20:24 -04:00
josh c7f3bf25d1 Bins: fixed N-column grid per letter group
Build and push image / build (push) Successful in 52s
The bins-by-letter grid used auto-fill with a 380px minimum, so a row
of A1-A6 wrapped once the viewport got tight, and rows with fewer
bins than the auto-fill column count left empty tracks on the right.

Switch to repeat(N, minmax(0, 1fr)) where N is the number of bins in
the group: cards smush to fit so a letter never wraps to a second
visual row, and minmax(0, 1fr) lets columns share whatever width is
available so a sparse row (e.g. C with 2 bins) widens to fill the
line just as a dense row does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:14:45 -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 edb8e2ac92 Bin slot count: discrete products fill by unit, not by row
Build and push image / build (push) Successful in 54s
A 3ct pre-roll was filling one slot of its bin instead of three. Bins
visualise physical capacity, so each unit of a discrete product (rolls,
edibles, vapes) should take its own slot; bulk jars still take one slot
each. The slot tally and fill bar both use the current count
(countLastAudit ?? countOriginal) so the bin frees up as units are
audited away.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:42:39 -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 8ef8859c7d Edit and delete brands and shops
Build and push image / build (push) Successful in 49s
Adds PATCH and DELETE endpoints for brands and shops that mirror the
existing bins pattern: deleting a brand or shop nullifies referencing
products (and strains, for brands) inside a transaction so nothing is
lost. Brand renames return 409 when the new name collides with the
UNIQUE constraint, surfaced inline in the edit modal.

The Brands and Shops views now show inline edit/trash icons on each
card; the trash button confirms with a preview of how many products
will be unparented.

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