Skip to main content

MSI integration

Pre-Order is MSI-aware by default with a clean fallback to legacy cataloginventory_stock_item when MSI isn't installed or is disabled. You don't need to configure anything — it auto-detects.

The two sides

Stock resolution

When deciding whether a product is "out of stock and therefore pre-orderable", we ask the resolver:

  • MSI path (Magento_InventorySalesApi available) — Model/Inventory/StockResolver.php reads salable quantity per stock-id, scoped to the current website. This means a product that's out at warehouse A but in stock at warehouse B is correctly classified per stock.
  • Legacy path — falls back to cataloginventory_stock_item.qty and is_in_stock.

Model/Service/PreorderEligibility.php calls the resolver — neither callers nor extending modules need to know which path is used.

Reservations

When a pre-order is placed, we want the warehouse team to see the right available-to-promise number. So we reserve the pre-ordered quantity:

  • MSI pathModel/Inventory/ReservationManager.php calls Magento_InventoryReservationsApi::AppendReservationsInterface with the pre-order's stock-id and quantity. Reservations show up in inventory_reservation exactly like a normal sales reservation, just tagged with our metadata.
  • Legacy path — decrements cataloginventory_stock_item.qty directly with the same idempotency the standard order-place observer uses.

On cancel, both paths revert via the same reservation manager.

Stock-arrival trigger (MSI)

MSI does not dispatch an event when source items are saved — its reindex hook is wired as a plugin on the save command. We mirror that pattern in Plugin/Inventory/SourceItemsSavePlugin.php, hooking Magento\InventoryApi\Api\SourceItemsSaveInterface::execute via afterExecute:

  1. For each source item in the save, check whether it became IN_STOCK with qty > 0.
  2. For each affected SKU, look up pre-order items in pending or awaiting_stock (PreorderItemRepository::getByProductIdAndStatuses) — first-time pre-orders sit in pending until stock arrives, so filtering on awaiting_stock alone would miss them.
  3. Call PreorderManagement::updateAvailability($productId, true). That:
    • Flips matched items to ready.
    • For each affected parent pre-order, if every sibling item is now ready / complete, calls markPreorderReady — which flags the row for the byte8_preorder_send_ready_notifications cron drainer (email goes out within 60 seconds, see Cron jobs).
    • Otherwise calls updatePreorderStatus to refresh the parent (e.g. to partial_complete).
  4. The Cron/AutoCompletePreorders.php job (every 15 min) finishes any zero-balance pre-orders.

The legacy cataloginventory_stock_item_save_after event is still observed (Observer/CheckPreorderAvailability.php) for stores without MSI — same updateAvailability path, just driven by the legacy stock-item save.

The MSI plugin path was chosen specifically because MSI doesn't fire events on source save; an earlier implementation listened for a non-existent after_source_items_save event and silently never fired. The plugin attaches to the same interface MSI's own reindex plugin uses, so it follows the actual save path through admin product edits, bulk inventory transfer, and CSV imports.

Fallback: byte8:preorder:scan-stock

If the plugin ever misses a save (bulk imports that bypass the standard interface, vendor sync writing directly to the table, etc.), the byte8:preorder:scan-stock CLI command iterates SKUs with pending pre-order items, checks salability via StockResolver, and runs the same updateAvailability path. Recommend scheduling it every 5–10 minutes as a backup.

Stock-id awareness

A pre-order knows which website it was placed from, and which stock that website is assigned to (the standard MSI website-to-stock mapping). When we resolve eligibility or place reservations, we use the right stock — so a pre-order placed on a German store reserves from the German warehouse, not the US one.

If the website-to-stock mapping changes mid-flight (rare but possible), pre-orders carry the stock-id at time of placement in their data. Reservation reverts on cancel always go back to the right stock.

Disabling MSI

If your store explicitly disables MSI (e.g. with Magento_InventoryCatalog disabled), the legacy path takes over automatically. No config flag needed. The module checks interface_exists(GetSalableQuantityInterface::class) at boot.

Multi-source under MSI

When a stock has multiple sources (e.g. main warehouse + drop-ship warehouse), MSI's salable-quantity calculation already accounts for all of them. We don't override or augment that — we ask MSI the question and trust the answer.

For pre-orders that need to reserve from a specific source, write a plugin on ReservationManager::reserveForPreorder to set the source code explicitly. The default behaviour follows MSI's source-selection algorithm.

Reservation cleanup

MSI reservations accumulate over time. Magento's standard inventory:reservation:list-inconsistencies and inventory:reservation:create-compensations commands work normally — pre-order reservations are tagged with the pre-order increment ID in the metadata column, so you can identify and audit them.

If you cancel a pre-order via SQL (don't, but if you must), you'll need to also create a compensation reservation manually. Always prefer the admin grid / CLI cancel flow, which handles the reversal cleanly.