Skip to content

Items & Inventory

Backend domains:

RBAC Paths: ITEMS, INVENTORIES.

Items (product catalog)

An Item is a sellable / purchasable / inventoriable thing. The model supports:

  • Item type — product, service, or kit.
  • Default UoM plus a list of unit_of_measures the item can be transacted in.
  • Default tax rate — points at itbms_tax_rates (e.g. 0%, 7%, 10%, 15%).
  • Category — a tree of ItemCategory rows for reporting and price-rule scoping.
  • Default accounts — sales account, COGS account, inventory account (overrides the tenant defaults).
  • Alternative unit prices — per-UoM, per-currency, or per-quantity-tier pricing (alternative_unit_price_*).
  • Client-specific discountsitems_client_discount_* lets a tenant lock a price for a particular customer.

Key mutations:

  • Items: createItem, updateItem, deleteItem, deleteItems, getOrCreateItem.
  • Categories: createItemCategory, updateItemCategory, deleteItemCategory.
  • Alt prices: addAlternativeUnitPrice, updateAlternativeUnitPrice, deleteAlternativeUnitPrice.
  • Client discounts: addClientDiscount, updateClientDiscount, deleteClientDiscount.
  • UoMs: updateUnitOfMeasuresVisibility (per-tenant UoM enable/disable).

Key queries: itemSearch, getItemById, getItemByItemNumber, findAlternativeUnitPriceByItemId, getItemClientDiscountsByItemId, itbmsTaxRates, unitOfMeasures, getUnitOfMeasureByName, getDefaultUom, itemCategorySearch, getItemCategoryById.

Items are bulk-imported via app/graphql/items/tabular/ (CSV/XLSX).

Inventories

Inventory is event-sourced from invoices, supplier-invoice receivings, manual adjustments, and (planned) inter-location transfers. Stock per item is derivable; the inventory listeners maintain a cached current quantity for fast queries.

Key mutations:

  • updateInventory — direct override (admin only).
  • createManualInventoryAdjustment — recommended path: writes a labelled adjustment with a reason and posts the inventory-adjustment JE.

Key queries:

  • getItemStockOverTime — time-series stock graph.
  • getInventoryLogsByItemId — full movement history.

Stock posting goes through LedgerService for inventory accounts only when items have is_inventoriable=true. Services post to revenue/COGS without involving inventory.

Inventory dimensions (locations, lots, costing methods)

See the full feature doc at Inventory dimensions. At a glance:

  • Multi-locationInventoryLocation rows are tenant-scoped. Every Inventory, InventoryLog, InventoryLot, and InventoryCostLayer row carries a location_id. Exactly one location is marked is_default (enforced by a partial unique index). Receipts, adjustments, and queries that don't specify a location fall back to the default.
  • Lot / serial trackingItem.tracking_mode is one of NONE (default), LOT, or SERIAL. When enabled, every inflow/outflow must carry a lot_number. SERIAL items use one InventoryLot row per unit with quantity_received = 1.
  • Costing methodsItem.costing_method is one of AVERAGE (default — current behaviour), FIFO, or LIFO. FIFO/LIFO items maintain an InventoryCostLayer per receipt; outflow consumes layers oldest-first (FIFO) or newest-first (LIFO) and returns the COGS for the line. AVERAGE items continue to use the running Item.average_cost.

Switching costing_method only affects future receipts (no retroactive recompute). Switching tracking_mode from NONE requires the item to have zero stock to keep lot history clean.

What it does NOT have yet

  • Inter-location transfers (in-transit balance) — transferStock is planned in a follow-up.
  • Bill-of-materials for kits / manufacturing.
  • Barcode / scanner input on the API surface (frontend-only today).
  • Stocktake / cycle-count workflow.
  • Per-invoice-line location/lot picking on outflow (today, invoices draw from the default location).

These are documented as roadmap candidates in the PYMES feature ideas section.