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). APENDINGorder that allocates owned stock reserves throughreconcileOrderReservationsinsideallocateOrder. A pick writesInventoryPickagainst 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
| Piece | Path |
|---|---|
| Schema | 31-inventory.prisma |
| Publish listener | product-stock-sync.listener.ts |
| Allocation listener | stock-allocation.listener.ts |
| Feature folder | src/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.requestedorstock.receivedthrough 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 = UNMANAGEDis documented in the schema comment as the inventory off switch.manageStateis 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
| Name | Kind |
|---|---|
stock.sync.requested | Outbox → publish buffer |
stock.received | Outbox → allocation listener |
product.channel-allocation.changed | In-process, not an outbox type |
inventory_publish_queue | Flush, feed poll, reconcile batch |
stock_upload_queue | stock-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.