Skip to main content

Inventory

Stock is an operations concern. Other modules are supposed to mutate it through StockOperationsPort, not by injecting concrete stock services. yarn architecture:check guards that boundary.

Responsibilities​

  • On-hand quantity by product and warehouse, including lots and a movement ledger.
  • Reservations and picks (InventoryReservation, InventoryPick). A PENDING order that allocates owned stock reserves through reconcileOrderReservations inside allocateOrder. A pick writes InventoryPick against the scanned bin. See What an order status does to fulfillment and Pick and pack. Release on cancel is in that status page. The stock-command method that creates a reservation outside this path was not the one read.
  • Publish sellable quantity to channels.
  • Bulk upload on stock_upload_queue.
  • Stock counts.

Architecture​

stock.received is described in code as the choke point that releases waiting demand. StockAllocationListener consumes it.

Code map​

PiecePath
Schema31-inventory.prisma
Publish listenerproduct-stock-sync.listener.ts
Allocation listenerstock-allocation.listener.ts
Feature foldersrc/modules/operations/stock

Also: stock-count, stock-transfer.

Data model​

ProductWarehouseSetting, StockPosition, Stock, StockLot, StockMovement, InventoryReservation, InventoryPick, StockCount, StockCountItem.

StockMovement unique key is (tenantId, idempotencyKey, createdAt). Including createdAt weakens idempotency if callers mint a new timestamp for the same key. Whether they reuse one timestamp was not verified.

Channel publish state is ChannelInventoryState and ChannelInventoryFeedSubmission in the channel schema, not in the inventory file.

Business rules​

Implementation behavior

Mutations that affect sellable quantity are expected to emit stock.sync.requested or stock.received through the outbox. Producers found: stock command, goods receipt, transfer, count, fulfillment, returns, order command, allocation.

Status

Confirmed by the outbox producer scan. A mutation that forgets the enqueue will not publish.

Implementation behavior

Channel.allocationMode = UNMANAGED is documented in the schema comment as the inventory off switch. manageState is a separate flag for pushing order status.

Status

Schema comment. Treat it as the written rule for that column.

The quantity formula (allocation percent, fixed quantity, buffer, min, max, zero behaviour) lives in stock-sync.service.ts and was not derived here. Do not invent it. Policy fields exist on Channel as defaults and can exist per listing.

API surface​

Stock HTTP controllers are in the stock feature. OpenAPI is the field reference.

Events and queues​

NameKind
stock.sync.requestedOutbox → publish buffer
stock.receivedOutbox → allocation listener
product.channel-allocation.changedIn-process, not an outbox type
inventory_publish_queueFlush, feed poll, reconcile batch
stock_upload_queuestock-bulk-upload

Publish detail: Publish path.

Integrations​

Shopify, WooCommerce, Flipkart, and Amazon have named flush jobs. BigCommerce and other connectors fall through flush-channel:DEFAULT. Amazon-style connectors submit a feed and do not update ChannelInventoryState until the poll result is read (comment on IMarketplaceInventoryService).

Failure scenarios​

Bulk publish returns per-item BulkPublishResult. The HTTP job can complete while some SKUs fail. A processor that swallows an error will not use Bull retries.

Reconciliation scheduler runs hourly and every 2 minutes. Feed poll reaper runs every 10 minutes.

Development​

Outside the stock module, call the port. Inside a transaction that changes sellable quantity, enqueue the outbox event with the same tx.

Troubleshooting​

If on-hand changed and the channel did not, check the outbox row, then the publish queue, then allocationMode. See Sync and inventory.