Core
Products
Inventory

Inventory

Stock tracking for products and variants. Single service, JSON column, decremented atomically during order creation, restored only on cancel. Refund does NOT restore stock.

1. Overview

Stock is a JSON blob on both Product.inventory and ProductVariant.inventory, shape { trackQuantity, quantity, lowStockThreshold? }. All mutations go through backend/lib/inventory-service.ts — no direct writes elsewhere. Decrement runs inside the order-create transaction; restore runs on cancel or hard delete. Refund does not restore.

2. Architecture

3. Data model

{ "trackQuantity": true, "quantity": 42, "lowStockThreshold": 5 }

Product.inventory at schema.prisma:L329; ProductVariant.inventory at L366. No dedicated table, no ledger.

4. Key flows

4.1 Decrement on order create (atomic)

4.2 Restore on cancel (NOT atomic with status)

Server crash between tx1 and tx2 → order cancelled, stock not restored. Errors log console.warn only.

5. Lifecycle

6. Key files

7. Env vars & config

None. Inherits DB. lowStockThreshold is per-product in the JSON.

8. Gotchas & troubleshooting

  • Refund does NOT restore stock → Only cancelled and delete do. Refund without return = stock silently lost.
  • Cancel restore is not atomic with status flip → Two separate transactions. Real inconsistency risk under failure.
  • Variant-bearing products ignore Product.inventory.quantity → If a line item has variantId, only the variant counter moves. The product-level count is dead weight.
  • reduceInventory is read-modify-write → Not an atomic SQL decrement. Safety relies on the caller's transaction. Calling it outside a tx = data race.
  • Oversell is silently clamped at 0Math.max(0, quantity - reduce). Order succeeds even if stock insufficient. checkAndReduceInventory guards this; reduceInventory alone does not.
  • No audit trail → Debugging drift means correlating against Order history manually.
  • Low-stock threshold is UI-only metadata → No alert system.

9. Extension points

  • Add a ledger → insert InventoryAdjustment rows (new model) from inside the service. Non-breaking.
  • Restore on refund → one-line at the refund handler; watch for double-restore if refund follows cancel.
  • Atomic SQL decrement → migrate to quantity INT column, use UPDATE ... WHERE qty >= :n; removes tx dependency.

10. Related docs