Inventory: raw materials, batches, expiry, harvest-to-table #3

Open
opened 2026-05-10 14:48:25 +00:00 by padreug · 0 comments
Owner

Tracks the inventory layer of #2 (bistro and full tiers).

Background

DarthCoin's framing — even a simple bar wants inventory, not full
production. AIO's interest:

we run a farm so it would be nice to have the ability to track
inventory from harvest to table.

there's an accounting module too, i really want to tie it all
together and offer it as a package to similar projects. If you
purchase materials etc so you could potentially automate calculation
[of] the cost of finished products by simply specifying [a] desired
profit margin.

So inventory has two consumers from day one:

  1. The kitchen — "do we have enough flour to make the
    quesadillas tonight?"
  2. Accounting / pricing — "this batch of beans cost X; given a
    30% margin, my burritos should be priced at Y." (Tracked under
    #6.)

Probably its own extension someday

The user explicitly flagged this:

the inventory stuff might also be generally useful, so in the
future it could potentially be an inventory extension that
restaurant can plug into?

→ Build the inventory layer with extraction in mind. Concretely:

  • All inventory operations go through a small internal API surface
    that could later become REST + Nostr endpoints on a separate
    inventory extension.
  • Restaurant talks to inventory only through that surface (no
    direct DB joins from menu_items to a hypothetical inventory_*
    table).
  • The data model is keyed by stable identifiers (uuids, not menu-
    item-id implicit FKs) so a future inventory extension can host
    the same rows for non-restaurant consumers.

If we keep this discipline now, hoisting becomes a refactor, not a
rewrite.

Data model (sketch)

materials
  id, name, sku, base_unit (kg / L / piece / ...), tags

material_batches              — purchase / harvest events
  id, material_id, qty, unit_cost_msat,
  acquired_at, expires_at, source_note

material_levels               — denormalized current stock per material
  material_id, qty_on_hand,
  qty_reserved (for accepted orders),
  low_threshold

recipes                       — BOM linking menu items to materials
  id, menu_item_id,
  yield_qty (how many servings one BOM produces, default 1)

recipe_lines
  id, recipe_id, material_id, qty_per_serving

Modifiers (e.g. extra cheese, brisket add-on) get their own
recipe_lines rows so adding them to an order decrements the right
inventory.

Behavior

  • Order placement reserves materials (qty_reserved += sum).
    Order paid → reservation is consumed (qty_on_hand -= sum,
    qty_reserved -= sum).
  • Order canceled before paid → release reservation.
  • Refunded paid order → restock (qty_on_hand += sum).
  • "Sold out" cascade: a material going below threshold flips
    every menu item whose recipe references it. The CMS shows why
    ("Tacos disabled — Brisket out of stock until restock").
  • Harvest event = material_batch row with no purchase price
    (or marked as homegrown). Useful for the farm-to-table angle.
  • Expiry triggers a CMS warning + ages out of qty_on_hand
    when crossed (configurable: hard-zero vs. soft-warn).

Stretch / farm-to-table

  • Provenance trail — each material_batch can carry a Nostr
    event id (NIP-94 file metadata or a custom kind) pointing at a
    harvest record (photo, geohash, harvester pubkey).
  • Per-line provenance on receipts — printed receipt could
    optionally name the farm + harvest date for items that came from
    a tracked batch. "Your tortilla: maíz from Finca Atitlán, 5 May
    2026." Customer-facing flex.
  • Multi-restaurant inventory — if/when extracted to its own
    extension, a single farm could supply multiple restaurants and
    see combined demand.

Tiering (per #2)

  • bar — inventory off; nothing decrements.
  • bistro — inventory ON for finished items only (one row per
    menu item, no recipe expansion). Decrement = quantity ordered.
  • full — inventory ON with full BOM expansion (recipes →
    materials).

Acceptance

  • materials, material_batches, recipes, recipe_lines
    tables.
  • CMS panel under settings (visible only when mode≥bistro):
    list materials, see levels, log a batch / harvest, edit
    recipes per menu item.
  • Order paid → decrement; order canceled → release.
  • "Sold out" cascade reflected in menu_items.is_available
    surfaced via the existing API.
  • Internal API surface (Python) is consumed only through
    well-defined functions in services/inventory.py so a
    future extraction is a port, not a rewrite.
  • docs/inventory note added describing the data model
    and the extraction-friendly seam.

See also

  • #2 — tiered operator modes (parent)
  • #4 — kitchen workflow (consumes "is X available?" signals)
  • #6 — cost-of-goods + margin-driven pricing (consumes batch costs)

References

  • Telegram conversation 2026-05-09.
Tracks the inventory layer of #2 (`bistro` and `full` tiers). ## Background DarthCoin's framing — even a simple bar wants inventory, not full production. AIO's interest: > we run a farm so it would be nice to have the ability to track > inventory from harvest to table. > there's an accounting module too, i really want to tie it all > together and offer it as a package to similar projects. If you > purchase materials etc so you could potentially automate calculation > [of] the cost of finished products by simply specifying [a] desired > profit margin. So inventory has two consumers from day one: 1. **The kitchen** — "do we have enough flour to make the quesadillas tonight?" 2. **Accounting / pricing** — "this batch of beans cost X; given a 30% margin, my burritos should be priced at Y." (Tracked under #6.) ## Probably its own extension someday The user explicitly flagged this: > the inventory stuff might also be generally useful, so in the > future it could potentially be an inventory extension that > restaurant can plug into? → Build the inventory layer **with extraction in mind**. Concretely: - All inventory operations go through a small internal API surface that could later become REST + Nostr endpoints on a separate `inventory` extension. - Restaurant talks to inventory **only** through that surface (no direct DB joins from `menu_items` to a hypothetical `inventory_*` table). - The data model is keyed by stable identifiers (uuids, not menu- item-id implicit FKs) so a future `inventory` extension can host the same rows for non-restaurant consumers. If we keep this discipline now, hoisting becomes a refactor, not a rewrite. ## Data model (sketch) ``` materials id, name, sku, base_unit (kg / L / piece / ...), tags material_batches — purchase / harvest events id, material_id, qty, unit_cost_msat, acquired_at, expires_at, source_note material_levels — denormalized current stock per material material_id, qty_on_hand, qty_reserved (for accepted orders), low_threshold recipes — BOM linking menu items to materials id, menu_item_id, yield_qty (how many servings one BOM produces, default 1) recipe_lines id, recipe_id, material_id, qty_per_serving ``` Modifiers (e.g. extra cheese, brisket add-on) get their own `recipe_lines` rows so adding them to an order decrements the right inventory. ## Behavior - **Order placement** reserves materials (`qty_reserved += sum`). Order paid → reservation is consumed (`qty_on_hand -= sum`, `qty_reserved -= sum`). - **Order canceled before paid** → release reservation. - **Refunded paid order** → restock (`qty_on_hand += sum`). - **"Sold out" cascade**: a material going below threshold flips every menu item whose recipe references it. The CMS shows why ("Tacos disabled — Brisket out of stock until restock"). - **Harvest event** = `material_batch` row with no purchase price (or marked as homegrown). Useful for the farm-to-table angle. - **Expiry** triggers a CMS warning + ages out of `qty_on_hand` when crossed (configurable: hard-zero vs. soft-warn). ## Stretch / farm-to-table - **Provenance trail** — each `material_batch` can carry a Nostr event id (NIP-94 file metadata or a custom kind) pointing at a harvest record (photo, geohash, harvester pubkey). - **Per-line provenance on receipts** — printed receipt could optionally name the farm + harvest date for items that came from a tracked batch. "Your tortilla: maíz from Finca Atitlán, 5 May 2026." Customer-facing flex. - **Multi-restaurant inventory** — if/when extracted to its own extension, a single farm could supply multiple restaurants and see combined demand. ## Tiering (per #2) - **bar** — inventory off; nothing decrements. - **bistro** — inventory ON for finished items only (one row per menu item, no recipe expansion). Decrement = quantity ordered. - **full** — inventory ON with full BOM expansion (recipes → materials). ## Acceptance - [ ] `materials`, `material_batches`, `recipes`, `recipe_lines` tables. - [ ] CMS panel under settings (visible only when mode≥bistro): list materials, see levels, log a batch / harvest, edit recipes per menu item. - [ ] Order paid → decrement; order canceled → release. - [ ] "Sold out" cascade reflected in `menu_items.is_available` surfaced via the existing API. - [ ] Internal API surface (Python) is consumed *only* through well-defined functions in `services/inventory.py` so a future extraction is a port, not a rewrite. - [ ] [[docs/inventory]] note added describing the data model and the extraction-friendly seam. ## See also - #2 — tiered operator modes (parent) - #4 — kitchen workflow (consumes "is X available?" signals) - #6 — cost-of-goods + margin-driven pricing (consumes batch costs) ## References - Telegram conversation 2026-05-09.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/restaurant#3
No description provided.