Skip to content

Inventory Context

The Inventory context manages stock levels, receiving, consumption tracking, manual adjustments, and reorder thresholds. It integrates with the Kitchen context to automatically track consumption as items are prepared.

Purpose

Every restaurant location tracks hundreds of ingredients and supplies. The Inventory context provides the operational foundation: receiving shipments, tracking consumption, triggering reorder alerts, and creating purchase orders when stock runs low.

Types

type InventoryItemId is Id(Inventory.InventoryItem) with {
  briefly "Inventory item identifier"
  described by "Unique identifier for an inventory item."
}

type UnitOfMeasure is any of {
  Each,
  Pound,
  Ounce,
  Gallon,
  Liter,
  Case,
  Box
} with {
  briefly "Unit of measure"
  described by "Measurement unit for inventory quantities."
}

type InventoryItemStatus is any of {
  InStock,
  LowStock,
  OutOfStock,
  Discontinued
} with {
  briefly "Inventory item status"
  described by "Current stock status of an inventory item."
}

type StockAdjustmentReason is any of {
  Spoilage,
  Breakage,
  Theft,
  CountCorrection,
  Donation,
  OtherAdjustment
} with {
  briefly "Stock adjustment reason"
  described by "Reason for a manual stock adjustment."
}

The StockAdjustmentReason enumeration captures why stock was manually adjusted — essential for loss tracking and audit compliance.

Entity: InventoryItem

The InventoryItem entity has a 5-command lifecycle:

entity InventoryItem is {

  command ReceiveStock is {
    inventoryItemId is InventoryItemId
    receivedQuantity is Decimal(10, 2)
    receivedUnit is UnitOfMeasure
    supplierRef is optional String(1, 100)
    stockReceivedAt is TimeStamp
  }

  command ConsumeStock is {
    inventoryItemId is InventoryItemId
    consumedQuantity is Decimal(10, 2)
    consumedUnit is UnitOfMeasure
    consumptionRef is optional String(1, 100)
  }

  command AdjustStock is {
    inventoryItemId is InventoryItemId
    adjustmentQuantity is Decimal(10, 2)
    adjustmentUnit is UnitOfMeasure
    adjustmentReason is StockAdjustmentReason
    adjustmentNotes is optional String(1, 500)
  }

  command SetReorderThreshold is {
    inventoryItemId is InventoryItemId
    reorderThreshold is Decimal(10, 2)
    reorderUnit is UnitOfMeasure
  }

  command CreatePurchaseOrder is {
    inventoryItemId is InventoryItemId
    orderQuantity is Decimal(10, 2)
    orderUnit is UnitOfMeasure
    preferredSupplier is optional String(1, 100)
  }

  // Events: StockReceived, StockConsumed, StockAdjusted,
  //         ReorderThresholdSet, PurchaseOrderCreated

  state TrackedItem of InventoryItem.InventoryItemStateData

  handler InventoryItemHandler is {
    on command ReceiveStock {
      morph entity Inventory.InventoryItem to state
        Inventory.InventoryItem.TrackedItem
        with command ReceiveStock
      tell event StockReceived to
        entity Inventory.InventoryItem
    }
    on command ConsumeStock {
      tell event StockConsumed to
        entity Inventory.InventoryItem
    }
    on command AdjustStock {
      tell event StockAdjusted to
        entity Inventory.InventoryItem
    }
    on command SetReorderThreshold {
      tell event ReorderThresholdSet to
        entity Inventory.InventoryItem
    }
    on command CreatePurchaseOrder {
      tell event PurchaseOrderCreated to
        entity Inventory.InventoryItem
    }
  }
}

Note that ReceiveStock uses morph — this is where an inventory item first enters the system. Subsequent commands use the standard tell pattern.

The StockConsumed event includes a remainingStockLevel field, enabling downstream systems (like the Reporting context's InventoryReport projector) to track stock levels without querying the entity directly.

Repository

repository InventoryItemRepository is {
  schema InventoryItemData is relational
    of items as InventoryItem
    index on field InventoryItem.inventoryItemId
    index on field InventoryItem.inventoryItemStatus
}

The index on inventoryItemStatus enables quick queries for low-stock and out-of-stock items.

Adaptor: FromKitchen

The most interesting part of the Inventory context is its cross-context integration with the Kitchen:

adaptor FromKitchen from context Restaurant.Kitchen is {
  handler KitchenConsumptionIntake is {
    on event Restaurant.Kitchen.KitchenTicket.PreparationStarted {
      prompt "Consume stock for items being prepared"
    }
  }
} with {
  briefly "Kitchen adaptor"
  described by {
    | Receives preparation events from the kitchen to
    | automatically track stock consumption.
  }
}

When the Kitchen starts preparing a ticket (PreparationStarted event), the Inventory context automatically issues ConsumeStock commands for the ingredients required. No manual tracking needed — stock consumption follows directly from kitchen activity.

This is a powerful example of cross-context integration via events. The Kitchen doesn't know about inventory. It just prepares food and emits events. Inventory reacts to those events to keep stock levels accurate.

Design Decisions

Why automatic consumption from Kitchen events? Manual stock tracking is error-prone and labor-intensive. By listening to PreparationStarted events and looking up the recipe's ingredient list, the system can automatically deduct the right quantities. Discrepancies are handled through the AdjustStock command with explicit reasons.

Why Decimal(10, 2) for quantities? Inventory items are measured in fractional quantities (2.5 pounds of brisket, 0.75 gallons of sauce). Using Decimal instead of Natural supports precise tracking with proper unit handling.

Cross-domain relationship: This context is in the BackOffice domain but listens to events from the Restaurant domain's Kitchen context. This cross-domain integration is exactly what adaptors are designed for — they bridge context boundaries cleanly.

Source