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_InventorySalesApiavailable) —Model/Inventory/StockResolver.phpreads 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.qtyandis_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 path —
Model/Inventory/ReservationManager.phpcallsMagento_InventoryReservationsApi::AppendReservationsInterfacewith the pre-order's stock-id and quantity. Reservations show up ininventory_reservationexactly like a normal sales reservation, just tagged with our metadata. - Legacy path — decrements
cataloginventory_stock_item.qtydirectly 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:
- For each source item in the save, check whether it became
IN_STOCKwithqty > 0. - For each affected SKU, look up pre-order items in
pendingorawaiting_stock(PreorderItemRepository::getByProductIdAndStatuses) — first-time pre-orders sit inpendinguntil stock arrives, so filtering onawaiting_stockalone would miss them. - 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, callsmarkPreorderReady— which flags the row for thebyte8_preorder_send_ready_notificationscron drainer (email goes out within 60 seconds, see Cron jobs). - Otherwise calls
updatePreorderStatusto refresh the parent (e.g. topartial_complete).
- Flips matched items to
- The
Cron/AutoCompletePreorders.phpjob (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.
Related
- Pre-order lifecycle — how reservations fit into the broader flow.
- Cron jobs —
AutoCompletePreordersandExpirePreorders. - CLI commands —
preorder:cancelfor reservation-aware cancellation.