// Sample inventory data window.SAMPLE_DATA = (function() { const TODAY = "2026-04-25"; const daysAgo = (n) => { const d = new Date(TODAY); d.setDate(d.getDate() - n); return d.toISOString().slice(0, 10); }; // Shops are objects now const SHOPS = [ { id: "shp-01", name: "Greenleaf Co-op", location: "Capitol Hill" }, { id: "shp-02", name: "Verdant Apothecary", location: "Ballard" }, { id: "shp-03", name: "The Field House", location: "Fremont" }, { id: "shp-04", name: "Northstar Dispensary",location: "Roosevelt" }, { id: "shp-05", name: "Wildwood Provisions", location: "West Seattle" } ]; // Brands are objects too const BRANDS = [ { id: "brd-01", name: "Foxglove Farms" }, { id: "brd-02", name: "Slow Burn" }, { id: "brd-03", name: "Heirloom Botanicals" }, { id: "brd-04", name: "Terra Vera" }, { id: "brd-05", name: "North Field" }, { id: "brd-06", name: "Cinder & Sage" }, { id: "brd-07", name: "Old Forest" }, { id: "brd-08", name: "Marigold Ext." } ]; // Type config — kind + audit cadence // bulk: track weight (g) — audit re-weighs (or estimates for concentrate) // discrete: track count (units) — audit confirms presence by SKU/asset const TYPES = [ { id: "Flower", kind: "bulk", auditMode: "weigh", cadenceDays: 14, unit: "g", weighable: true }, { id: "Concentrate", kind: "bulk", auditMode: "estimate", cadenceDays: 21, unit: "g", weighable: true }, { id: "Tincture", kind: "bulk", auditMode: "estimate", cadenceDays: 30, unit: "ml", weighable: false }, { id: "Pre-roll", kind: "discrete", auditMode: "presence", cadenceDays: 30, unit: "ct", weighable: false }, { id: "Edible", kind: "discrete", auditMode: "presence", cadenceDays: 60, unit: "ct", weighable: false }, { id: "Vaporizer", kind: "discrete", auditMode: "presence", cadenceDays: 30, unit: "ct", weighable: false } ]; const BINS = [ { id: "bin-01", name: "Top Drawer", location: "Bedroom", capacity: 14 }, { id: "bin-02", name: "Apothecary Box", location: "Office shelf", capacity: 10 }, { id: "bin-03", name: "The Safe", location: "Closet", capacity: 8 }, { id: "bin-04", name: "Travel Tin", location: "Backpack", capacity: 4 }, { id: "bin-05", name: "Cold Storage", location: "Fridge — back", capacity: 6 } ]; let n = 1; const mk = (o) => { const id = "prd-" + String(n).padStart(4, "0"); n++; const sku = o.sku || ("SKU-" + Math.random().toString(36).slice(2, 8).toUpperCase()); return { id, sku, assetTag: null, name: "", brandId: null, shopId: null, type: "Flower", kind: "bulk", // "bulk" or "discrete" // Bulk fields weight: 0, // total at purchase (g/ml) lastAuditWeight: null, // last measured weight // Discrete fields countOriginal: 0, // units at purchase countLastAudit: null, // units confirmed at last audit unitWeight: 0, // bulk-equivalent grams per unit (for grams stats) // Pricing price: 0, thc: 0, cbd: 0, totalCannabinoids: 0, purchaseDate: TODAY, binId: "bin-01", // Lifecycle status: "active", // "active" | "consumed" | "gone" consumedDate: null, goneDate: null, rating: null, notes: null, // Audits — newest last audits: [], ...o }; }; const products = []; // ─── BULK: FLOWER ──────────────────────────────────────────────── products.push(mk({ name: "Garden Ghost", brandId: "brd-01", shopId: "shp-01", type: "Flower", kind: "bulk", weight: 3.5, lastAuditWeight: 2.4, price: 48, thc: 24.6, cbd: 0.3, totalCannabinoids: 28.4, purchaseDate: daysAgo(17), binId: "bin-01", audits: [ { date: daysAgo(10), mode: "weigh", value: 3.0, prev: 3.5 }, { date: daysAgo(3), mode: "weigh", value: 2.4, prev: 3.0 } ] })); products.push(mk({ name: "Honeydew Pine", brandId: "brd-02", shopId: "shp-02", type: "Flower", kind: "bulk", weight: 7, lastAuditWeight: 5.6, price: 85, thc: 21.0, cbd: 0.5, totalCannabinoids: 25.1, purchaseDate: daysAgo(11), binId: "bin-01", audits: [ { date: daysAgo(2), mode: "weigh", value: 5.6, prev: 7.0 } ] })); products.push(mk({ name: "Late Pear", brandId: "brd-01", shopId: "shp-01", type: "Flower", kind: "bulk", weight: 3.5, lastAuditWeight: 3.5, price: 50, thc: 25.2, cbd: 0.2, totalCannabinoids: 28.9, purchaseDate: daysAgo(6), binId: "bin-05" // recently bought, no audit yet })); products.push(mk({ name: "Copper Fennel", brandId: "brd-02", shopId: "shp-02", type: "Flower", kind: "bulk", weight: 3.5, lastAuditWeight: 0.5, price: 42, thc: 20.4, cbd: 0.5, totalCannabinoids: 24.0, purchaseDate: daysAgo(26), binId: "bin-01", audits: [ { date: daysAgo(12), mode: "weigh", value: 1.6, prev: 3.5 }, { date: daysAgo(2), mode: "weigh", value: 0.5, prev: 1.6 } ] })); // Overdue audit — Flower past 14d cadence products.push(mk({ name: "Slate Cherry", brandId: "brd-06", shopId: "shp-04", type: "Flower", kind: "bulk", weight: 3.5, lastAuditWeight: 3.5, price: 46, thc: 22.8, cbd: 0.4, totalCannabinoids: 26.5, purchaseDate: daysAgo(22), binId: "bin-01" // No audit since purchase, 22d > 14d cadence → overdue })); // ─── BULK: CONCENTRATE ────────────────────────────────────────── products.push(mk({ name: "Indigo Cellar Live Rosin", brandId: "brd-03", shopId: "shp-03", type: "Concentrate", kind: "bulk", weight: 1, lastAuditWeight: 0.6, price: 65, thc: 78.4, cbd: 0.2, totalCannabinoids: 84.0, purchaseDate: daysAgo(28), binId: "bin-03", assetTag: "AT-0042", audits: [ { date: daysAgo(15), mode: "estimate", value: 0.8, prev: 1.0 }, { date: daysAgo(2), mode: "estimate", value: 0.6, prev: 0.8 } ] })); products.push(mk({ name: "Slate Apricot Hash", brandId: "brd-04", shopId: "shp-04", type: "Concentrate", kind: "bulk", weight: 2, lastAuditWeight: 1.4, price: 80, thc: 62.0, cbd: 1.1, totalCannabinoids: 70.5, purchaseDate: daysAgo(23), binId: "bin-03", audits: [ { date: daysAgo(8), mode: "estimate", value: 1.4, prev: 2.0 } ] })); products.push(mk({ name: "Birchwater Live Resin", brandId: "brd-03", shopId: "shp-04", type: "Concentrate", kind: "bulk", weight: 1, lastAuditWeight: 1.0, price: 70, thc: 76.0, cbd: 0.3, totalCannabinoids: 82.1, purchaseDate: daysAgo(4), binId: "bin-03" })); // ─── BULK: TINCTURE ───────────────────────────────────────────── products.push(mk({ name: "Nightjar Tincture 30ml", brandId: "brd-08", shopId: "shp-02", type: "Tincture", kind: "bulk", weight: 30, lastAuditWeight: 22, price: 60, thc: 0.5, cbd: 18.0, totalCannabinoids: 19.2, purchaseDate: daysAgo(62), binId: "bin-02", audits: [ { date: daysAgo(31), mode: "estimate", value: 26, prev: 30 }, { date: daysAgo(5), mode: "estimate", value: 22, prev: 26 } ] })); // ─── DISCRETE: PRE-ROLLS ─────────────────────────────────────── products.push(mk({ name: "Mossback Pre-rolls", brandId: "brd-07", shopId: "shp-03", type: "Pre-roll", kind: "discrete", countOriginal: 5, countLastAudit: 3, unitWeight: 0.7, price: 38, thc: 19.8, cbd: 0.4, totalCannabinoids: 23.0, purchaseDate: daysAgo(9), binId: "bin-04", audits: [ { date: daysAgo(2), mode: "presence", value: 3, prev: 5, confirmedBy: "SKU" } ] })); products.push(mk({ name: "Mossback Pre-rolls", brandId: "brd-07", shopId: "shp-03", type: "Pre-roll", kind: "discrete", countOriginal: 5, countLastAudit: 5, unitWeight: 0.7, price: 38, thc: 19.8, cbd: 0.4, totalCannabinoids: 23.0, purchaseDate: daysAgo(3), binId: "bin-04" })); products.push(mk({ name: "Quiet Meadow Singles", brandId: "brd-05", shopId: "shp-05", type: "Pre-roll", kind: "discrete", countOriginal: 3, countLastAudit: 1, unitWeight: 1.0, price: 24, thc: 22.0, cbd: 0.3, totalCannabinoids: 25.0, purchaseDate: daysAgo(34), binId: "bin-04", audits: [ { date: daysAgo(20), mode: "presence", value: 2, prev: 3, confirmedBy: "SKU" }, { date: daysAgo(2), mode: "presence", value: 1, prev: 2, confirmedBy: "SKU" } ] })); // ─── DISCRETE: EDIBLES ───────────────────────────────────────── products.push(mk({ name: "Ember Lily Gummies (10 ct)", brandId: "brd-06", shopId: "shp-01", type: "Edible", kind: "discrete", countOriginal: 10, countLastAudit: 6, unitWeight: 0, price: 22, thc: 5.0, cbd: 1.0, totalCannabinoids: 6.4, purchaseDate: daysAgo(20), binId: "bin-02", audits: [ { date: daysAgo(5), mode: "presence", value: 6, prev: 10, confirmedBy: "SKU" } ] })); products.push(mk({ name: "Marigold Mints (20 ct)", brandId: "brd-08", shopId: "shp-02", type: "Edible", kind: "discrete", countOriginal: 20, countLastAudit: 14, unitWeight: 0, price: 28, thc: 2.5, cbd: 0, totalCannabinoids: 3.0, purchaseDate: daysAgo(48), binId: "bin-02", audits: [ { date: daysAgo(7), mode: "presence", value: 14, prev: 20, confirmedBy: "SKU" } ] })); // ─── DISCRETE: VAPORIZER ─────────────────────────────────────── products.push(mk({ name: "Quiet Meadow Disposable", brandId: "brd-05", shopId: "shp-05", type: "Vaporizer", kind: "discrete", countOriginal: 1, countLastAudit: 1, unitWeight: 1.0, price: 55, thc: 84.0, cbd: 0.1, totalCannabinoids: 88.2, purchaseDate: daysAgo(14), binId: "bin-04", audits: [ { date: daysAgo(1), mode: "presence", value: 1, prev: 1, confirmedBy: "SKU" } ] })); // ─── CONSUMED (used up — counts as consumption) ───────────────── products.push(mk({ name: "Oolong 19", brandId: "brd-04", shopId: "shp-01", type: "Flower", kind: "bulk", weight: 3.5, lastAuditWeight: 0, price: 46, thc: 23.0, cbd: 0.4, totalCannabinoids: 27.0, purchaseDate: daysAgo(66), binId: null, status: "consumed", consumedDate: daysAgo(31), rating: 4, notes: "Smooth, citrusy. Daytime favorite." })); products.push(mk({ name: "Stonefruit OG", brandId: "brd-07", shopId: "shp-03", type: "Flower", kind: "bulk", weight: 7, lastAuditWeight: 0, price: 78, thc: 22.5, cbd: 0.6, totalCannabinoids: 26.4, purchaseDate: daysAgo(103), binId: null, status: "consumed", consumedDate: daysAgo(56), rating: 5, notes: "Best of the season. Rebuy." })); products.push(mk({ name: "Lavender Coast", brandId: "brd-01", shopId: "shp-05", type: "Flower", kind: "bulk", weight: 3.5, lastAuditWeight: 0, price: 44, thc: 19.0, cbd: 0.8, totalCannabinoids: 22.2, purchaseDate: daysAgo(80), binId: null, status: "consumed", consumedDate: daysAgo(47), rating: 3, notes: "Mellow but underwhelming." })); products.push(mk({ name: "Violet Tea", brandId: "brd-06", shopId: "shp-04", type: "Flower", kind: "bulk", weight: 3.5, lastAuditWeight: 0, price: 50, thc: 24.1, cbd: 0.3, totalCannabinoids: 28.0, purchaseDate: daysAgo(55), binId: null, status: "consumed", consumedDate: daysAgo(21), rating: 4, notes: "Floral nose, nice evenings." })); // ─── GONE (lost / damaged — counts as $ spent, NOT consumption) ─ products.push(mk({ name: "Quiet Meadow Singles", brandId: "brd-05", shopId: "shp-05", type: "Pre-roll", kind: "discrete", countOriginal: 3, countLastAudit: 0, unitWeight: 1.0, price: 24, thc: 22.0, cbd: 0.3, totalCannabinoids: 25.0, purchaseDate: daysAgo(72), binId: null, status: "gone", goneDate: daysAgo(40), notes: "Pack went through the wash. Lesson learned.", audits: [ { date: daysAgo(40), mode: "presence", value: 0, prev: 3, confirmedBy: "lost" } ] })); products.push(mk({ name: "Ember Lily Gummies (10 ct)", brandId: "brd-06", shopId: "shp-01", type: "Edible", kind: "discrete", countOriginal: 10, countLastAudit: 4, unitWeight: 0, price: 22, thc: 5.0, cbd: 1.0, totalCannabinoids: 6.4, purchaseDate: daysAgo(95), binId: null, status: "gone", goneDate: daysAgo(15), notes: "Expired. Tossed the rest." })); return { products, bins: BINS, shops: SHOPS, brands: BRANDS, types: TYPES, today: TODAY }; })(); // Helpers — exported so screens can use consistent logic window.DATA_HELPERS = { shopName: (data, id) => data.shops.find(s => s.id === id)?.name || "—", brandName: (data, id) => data.brands.find(b => b.id === id)?.name || "—", typeConfig: (data, id) => data.types.find(t => t.id === id) || data.types[0], // Days since a given ISO date string daysSince: (iso, today = "2026-04-25") => { if (!iso) return Infinity; return Math.floor((new Date(today) - new Date(iso)) / 86400000); }, // Last audit record (newest) lastAudit: (p) => p.audits && p.audits.length ? p.audits[p.audits.length - 1] : null, // Days since last audit (or since purchase if none) daysSinceCheck: (p, today = "2026-04-25") => { const last = p.audits && p.audits.length ? p.audits[p.audits.length - 1].date : p.purchaseDate; return Math.floor((new Date(today) - new Date(last)) / 86400000); }, // Is audit overdue based on per-type cadence auditOverdue: (data, p, today = "2026-04-25") => { if (p.status !== "active") return false; const cfg = data.types.find(t => t.id === p.type); if (!cfg) return false; return window.DATA_HELPERS.daysSinceCheck(p, today) >= cfg.cadenceDays; }, // Estimated remaining (bulk: decay from last audit; discrete: countLastAudit) estimatedRemaining: (p, today = "2026-04-25") => { if (p.status !== "active") return 0; if (p.kind === "discrete") { return p.countLastAudit != null ? p.countLastAudit : p.countOriginal; } // Bulk: linear decay between last audit and average lifespan estimate const last = window.DATA_HELPERS.lastAudit(p); const baseDate = last ? last.date : p.purchaseDate; const baseValue = last ? last.value : p.weight; const daysSinceBase = Math.max(0, Math.floor((new Date(today) - new Date(baseDate)) / 86400000)); // Decay rate: assume original weight is consumed over (purchase → expected lifespan ~ 35d for flower, 40 concentrate, 90 tincture) const expectedLifespan = p.type === "Flower" ? 35 : p.type === "Concentrate" ? 40 : 90; const dailyBurn = p.weight / expectedLifespan; const est = Math.max(0, baseValue - dailyBurn * daysSinceBase); return est; }, // Original-percent remaining (for low-stock on bulk) pctRemaining: (p, today = "2026-04-25") => { if (p.kind === "discrete") { const cur = p.countLastAudit != null ? p.countLastAudit : p.countOriginal; return p.countOriginal > 0 ? cur / p.countOriginal : 0; } const est = window.DATA_HELPERS.estimatedRemaining(p, today); return p.weight > 0 ? est / p.weight : 0; } };