Order-on-demand inventory: defer the Ingram stock mirror

Decision: do NOT build runtime Ingram stock tracking before prod. Keep the online catalog always-purchasable and handle the rare unfulfillable order by hand. Revisit with real order data. Refines extended-catalog-and-preorders; relates to emporium, ingram-ipage-pipeline.

Context

The online store holds zero stock. Every online sale is order-on-demand: Carrie sources each order from Ingram (or pulls from the shop). The catalog importer currently sets manage_inventory: false, so everything is always purchasable. The open idea was to mirror Ingram’s scraped warehouse stock into Medusa inventory (quantity = min(stock, 50)) so we never sell what Ingram can’t supply.

The scraper records, per Ingram distribution center, {type: primary|secondary, onHand, onOrder}. Forthcoming titles have no stock table at all (empty {}).

Why we’re deferring it

  1. “Inventory” is the wrong frame here. We never decrement something we own. The only question stock could answer is “can Ingram supply this if someone buys it,” not “how many do we have.” Most inventory machinery (counts, scarcity, decrement-on-sale) is built for held stock we don’t have.

  2. The simple version conflicts with pre-orders. We want popular upcoming books to be pre-orderable. A forthcoming book has onHand = 0 (often no stock data at all). A “hard out-of-stock when on-hand is 0” rule would block exactly the pre-orders we care about. On-hand quantity is the wrong signal.

  3. The 50 cap is mostly meaningless for us. A cap matters when you show scarcity (“3 left”) on stock you own. We own nothing and Ingram has thousands, so the cap number changes nothing a customer sees. The cap was arbitrary.

  4. The real states are a 3-way status, not a number:

    • In stock at Ingram (on-hand > 0): sell normally.
    • Forthcoming / on the way (not yet published, or on-order): sell as pre-order.
    • Genuinely unavailable (out of print): don’t sell. And the one case where blocking a sale is actually correct (out of print) is the one the data detects least reliably. iPage has no “out of print” flag; the best heuristic is 0 on-hand + 0 on-order + a past pub date.
  5. Getting it wrong is worse than not having it. Today an unfulfillable order is a rare manual refund. A wrong inventory rule blocks real sales (including pre-orders). For a launching store, a lost sale beats a rare refund.

  6. No data yet. We don’t know how often a catalog title is actually unavailable from Ingram. Design this against real order history, not speculation. This is also a merchandising decision for Carrie (how to treat pre-orders and out-of-print titles), not a purely technical one.

What to do instead

  • Ship prod on the current always-purchasable behavior (manage_inventory: false).
  • Carrie handles the occasional unfulfillable order by hand (refund).
  • Watch how often that actually happens.

If it becomes a real problem later

Lightest first step is import-time, not runtime: flag likely out-of-print titles (0 on-hand + 0 on-order + past pub date) as draft so dead titles are not listed, with no runtime inventory tracking. Only build the full 3-way status (and any stock sync) if the data justifies it, and design it with Carrie.

Technical notes for whoever picks this up

  • Stock lives in the catalog queue book.stock as {DC: {type, onHand, onOrder}}; importer stashes it in product metadata as ingram_stock but does not use it.
  • The scraper’s content_hash() excludes stock (it is volatile), so re-import detection will not fire on stock-only changes. Any stock sync needs its own path that updates inventory levels directly, not a re-run of the content importer.
  • The importer is create-only (skips ISBNs that already exist as a variant SKU). Updating existing products (price, cover, stock) needs update-in-place, which is also still backlogged.