Track inventory at the instance level, not by product
Build and push image / build (push) Successful in 46s
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>
This commit is contained in:
@@ -6,36 +6,40 @@ export const bootstrapRouter: Router = Router();
|
||||
type ProductRow = {
|
||||
id: string;
|
||||
sku: string;
|
||||
asset_tag: string | null;
|
||||
strain_id: string | null;
|
||||
name: string;
|
||||
type: string;
|
||||
kind: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
type InventoryRow = {
|
||||
id: string;
|
||||
asset_id: string;
|
||||
product_id: string;
|
||||
brand_id: string | null;
|
||||
shop_id: string | null;
|
||||
bin_id: string | null;
|
||||
type: string;
|
||||
kind: string;
|
||||
price: number;
|
||||
thc: number;
|
||||
cbd: number;
|
||||
total_cannabinoids: number;
|
||||
weight: number;
|
||||
last_audit_weight: number | null;
|
||||
count_original: number;
|
||||
count_last_audit: number | null;
|
||||
unit_weight: number;
|
||||
price: number;
|
||||
thc: number;
|
||||
cbd: number;
|
||||
total_cannabinoids: number;
|
||||
purchase_date: string;
|
||||
status: string;
|
||||
consumed_date: string | null;
|
||||
gone_date: string | null;
|
||||
rating: number | null;
|
||||
notes: string | null;
|
||||
strain_id: string | null;
|
||||
};
|
||||
|
||||
type StrainRow = {
|
||||
id: string;
|
||||
name: string;
|
||||
brand_id: string | null;
|
||||
type: string;
|
||||
default_thc: number | null;
|
||||
default_cbd: number | null;
|
||||
default_total_cannabinoids: number | null;
|
||||
@@ -44,7 +48,7 @@ type StrainRow = {
|
||||
|
||||
type AuditRow = {
|
||||
id: number;
|
||||
product_id: string;
|
||||
inventory_id: string;
|
||||
date: string;
|
||||
mode: string;
|
||||
value: number;
|
||||
@@ -53,9 +57,14 @@ type AuditRow = {
|
||||
};
|
||||
|
||||
bootstrapRouter.get("/bootstrap", (_req, res) => {
|
||||
const products = db.prepare<[], ProductRow>("SELECT * FROM products ORDER BY id").all();
|
||||
const products = db
|
||||
.prepare<[], ProductRow>("SELECT * FROM products ORDER BY id")
|
||||
.all();
|
||||
const inventory = db
|
||||
.prepare<[], InventoryRow>("SELECT * FROM inventory_items ORDER BY id")
|
||||
.all();
|
||||
const audits = db
|
||||
.prepare<[], AuditRow>("SELECT * FROM audits ORDER BY product_id, date")
|
||||
.prepare<[], AuditRow>("SELECT * FROM audits ORDER BY inventory_id, date")
|
||||
.all();
|
||||
const shops = db.prepare("SELECT * FROM shops ORDER BY id").all();
|
||||
const brands = db.prepare("SELECT * FROM brands ORDER BY id").all();
|
||||
@@ -64,40 +73,46 @@ bootstrapRouter.get("/bootstrap", (_req, res) => {
|
||||
.prepare<[], StrainRow>("SELECT * FROM strains ORDER BY name COLLATE NOCASE")
|
||||
.all();
|
||||
|
||||
const auditsByProduct = new Map<string, AuditRow[]>();
|
||||
const auditsByInventory = new Map<string, AuditRow[]>();
|
||||
for (const a of audits) {
|
||||
const arr = auditsByProduct.get(a.product_id) ?? [];
|
||||
const arr = auditsByInventory.get(a.inventory_id) ?? [];
|
||||
arr.push(a);
|
||||
auditsByProduct.set(a.product_id, arr);
|
||||
auditsByInventory.set(a.inventory_id, arr);
|
||||
}
|
||||
|
||||
const productsOut = products.map((p) => ({
|
||||
id: p.id,
|
||||
sku: p.sku,
|
||||
assetTag: p.asset_tag,
|
||||
strainId: p.strain_id,
|
||||
name: p.name,
|
||||
brandId: p.brand_id,
|
||||
shopId: p.shop_id,
|
||||
binId: p.bin_id,
|
||||
type: p.type,
|
||||
kind: p.kind,
|
||||
weight: p.weight,
|
||||
lastAuditWeight: p.last_audit_weight,
|
||||
countOriginal: p.count_original,
|
||||
countLastAudit: p.count_last_audit,
|
||||
unitWeight: p.unit_weight,
|
||||
price: p.price,
|
||||
thc: p.thc,
|
||||
cbd: p.cbd,
|
||||
totalCannabinoids: p.total_cannabinoids,
|
||||
purchaseDate: p.purchase_date,
|
||||
status: p.status,
|
||||
consumedDate: p.consumed_date,
|
||||
goneDate: p.gone_date,
|
||||
rating: p.rating,
|
||||
notes: p.notes,
|
||||
strainId: p.strain_id,
|
||||
audits: (auditsByProduct.get(p.id) ?? []).map((a) => ({
|
||||
createdAt: p.created_at,
|
||||
}));
|
||||
|
||||
const inventoryOut = inventory.map((i) => ({
|
||||
id: i.id,
|
||||
assetId: i.asset_id,
|
||||
productId: i.product_id,
|
||||
brandId: i.brand_id,
|
||||
shopId: i.shop_id,
|
||||
binId: i.bin_id,
|
||||
price: i.price,
|
||||
thc: i.thc,
|
||||
cbd: i.cbd,
|
||||
totalCannabinoids: i.total_cannabinoids,
|
||||
weight: i.weight,
|
||||
lastAuditWeight: i.last_audit_weight,
|
||||
countOriginal: i.count_original,
|
||||
countLastAudit: i.count_last_audit,
|
||||
unitWeight: i.unit_weight,
|
||||
purchaseDate: i.purchase_date,
|
||||
status: i.status,
|
||||
consumedDate: i.consumed_date,
|
||||
goneDate: i.gone_date,
|
||||
rating: i.rating,
|
||||
notes: i.notes,
|
||||
audits: (auditsByInventory.get(i.id) ?? []).map((a) => ({
|
||||
date: a.date,
|
||||
mode: a.mode,
|
||||
value: a.value,
|
||||
@@ -109,8 +124,6 @@ bootstrapRouter.get("/bootstrap", (_req, res) => {
|
||||
const strainsOut = strains.map((s) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
brandId: s.brand_id,
|
||||
type: s.type,
|
||||
defaultThc: s.default_thc,
|
||||
defaultCbd: s.default_cbd,
|
||||
defaultTotalCannabinoids: s.default_total_cannabinoids,
|
||||
@@ -119,6 +132,7 @@ bootstrapRouter.get("/bootstrap", (_req, res) => {
|
||||
|
||||
res.json({
|
||||
products: productsOut,
|
||||
inventoryItems: inventoryOut,
|
||||
shops,
|
||||
brands,
|
||||
bins,
|
||||
|
||||
Reference in New Issue
Block a user