The Stock Registration Module
Hyvä POS is on the App Store
The free app is out for iPhone, iPad and Mac: install it from the App Store, connect your Magento store and sell. PRO, the Magento module with a license per terminal, follows shortly. News and pricing are at hyva.io/pos.
The register places its orders through Magento's order API, POST /V1/orders. Magento treats an order written that way as an import: it saves the order without the stock side a checkout order gets. With Magento Inventory, no order_placed reservation is written, so the website's salable quantity does not move when the register sells, and after the register's shipment the website offers goods that have left the shop. Without Magento Inventory, the legacy stock deduction does not run either. Magento has had this on record since 2020 as magento/inventory#3185.
Two packages close that gap. Both are Magento modules from Hyvä under open-source licenses (OSL 3.0 and AFL 3.0) with no dependency on Hyvä POS, so any integration that creates orders through the order API can use them. Both are published on Packagist and GitHub: hyva-themes/magento2-inventory-order-reservation and hyva-themes/magento2-inventory-order-reservation-msi.
| Package | Module | Install it on |
|---|---|---|
hyva-themes/magento2-inventory-order-reservation |
Hyva_InventoryOrderReservation |
Every store |
hyva-themes/magento2-inventory-order-reservation-msi |
Hyva_InventoryOrderReservationMsi |
A store with Magento Inventory (MSI) |
The first package records what each order owes in stock and makes sure it is written exactly once. On a store without Magento Inventory it registers the sale on the legacy stock item itself, the way the checkout does. On a store with Magento Inventory the second package does the writing: it places the order_placed reservation and teaches Magento's reservation repair commands about orders the first package is still working on. A store with Magento Inventory needs both. The first package alone accepts the register's orders and keeps them owed until the second one is installed.
Installing
The packages come from the public Packagist, so any Magento project installs them with composer require and nothing added: no repository, no key. The source is on GitHub under hyva-themes.
composer require hyva-themes/magento2-inventory-order-reservation-msi
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush
The MSI package brings the core package hyva-themes/magento2-inventory-order-reservation in with it.
If you don't use MSI, you only need the core package:
A store that removed Magento Inventory with yireo/magento2-replace-inventory gets a refusal from Composer for the MSI package, on purpose: install the core package only.
Hyvä POS PRO (module 1.6.0 or later) brings the core package in on its own; with MSI, add the MSI package. Update the PRO module and the packages together, see Updating with Hyvä POS.
What the Register Does
The register asks the store for the module when it connects, on every sync and on Check for Magento Updates, and shows the answer as a row under Settings → Inventory:
| The row reads | What the store returned | What a sale does |
|---|---|---|
| Ready | The module is installed and can register an order's stock on this store view's website. | Every order carries reserve_stock and the store writes its stock side. |
| Installed, not ready | The module is installed and reports what is missing: the companion package on a store with Magento Inventory, a disabled Inventory module, or a website that sells from no stock. The sentence is the module's own. | The order carries reserve_stock; the store records what it owes and completes it with the first registration after the fix. The sale needs the acknowledgement below. |
| Installed, not verified | The module is there, but the last check did not reach it (no connection, a timed-out request) or the register's connection may not read it (the permission Order stock registration: read capabilities, under Sales). | The order carries reserve_stock. The sale needs the acknowledgement below, after Check the store again. |
| Not installed | The store has no module. | The order carries nothing and the stock side is not written. The sale needs the acknowledgement below. |
| Not checked | The store has not been asked yet. | As not verified. |
Readiness is read per store view, because the module reports for the view's website.
Selling without stock registration
On a store that does not read Ready, the register asks the merchant once, before any money moves: at the first sale (the checkout stops with Sell without stock registration?, stating the store's condition, with Check the store again and Sell without stock registration), on the setup checklist, and under Settings → Inventory. The acknowledgement is recorded for that condition on the device with its date; the same condition is not asked again, a store that becomes ready sells without asking, and a store that was ready and fails anew asks again. The self-checkout kiosk cannot take the acknowledgement: it shows Please pay at the counter until the merchant has given it.
The warning reads the same everywhere: a headline stating the store's condition, Stock registration module missing (or not ready, not checked, not verified), and one line under it: Install the stock registration module before selling to keep Magento's stock figures correct (make it ready, or check it, for the other conditions). What happens to the stock without the module is under More info and in Products and stock. Installing the module later registers nothing for the sales taken before it; what those sales moved on the shelf stays as the shipment left it.
The counter shipment
Automatically fulfil take-now items (Settings → Inventory, on by default; on PRO also set in Magento under Terminal Defaults → Stock and per location) is what ships the goods the customer takes at the counter right after the order, so the order completes and the shelf moves. It follows that switch alone, never readiness: on a store without the module the register still ships at the counter. With Magento Inventory the shipment takes the source down the way an admin shipment does, and appends its shipment_created compensation without a matching placement, so the salable quantity reads one unit higher than the shelf for every unit sold; that is the figure the acknowledgement is about. On a store without Magento Inventory a shipment moves nothing: the legacy stock item moves at placement only, which is what the module's legacy mode does for a register order, so without the module the quantity does not follow the sale at all. A shipment the store refuses, a line whose source is empty for instance, is filed under Reports → Sync Issues as Shipment failed for the order; the order is placed and invoiced either way.
When the module is removed
If the module is removed or disabled between two sales, the register still asks the next order for reserve_stock, and Magento refuses the field it no longer recognises. That one refusal the register recovers by itself: it asks the store again, and only where the store now reports that the module is removed does it post the same sale once more without the attribute. A store that still reports the module holds the sale for a retry. Either way Reports → Sync Issues carries Stock registration lost on this store with the date. Any other refusal of an order is left for the cashier's retry, as before.
Reservations switched off
With Stores → Configuration → Catalog → Inventory → Stock Options → Decrease Stock When Order is Placed off, Magento Inventory appends no reservation at all, for the web shop and the register alike, and the salable quantity follows the shelf alone: the register's shipment moves it, and the module has nothing to register. A web shop order then holds no stock between the order and its shipment, which is the trade-off that setting has always been.
What the Store Does
The order's save records one row per product: the quantity ordered, the product type, the website and the stock it sells from. The registration runs after the order's transaction has committed, under Magento's own order lock, and lands in one transaction with the state of each row. If anything fails, all of it rolls back and the order stays owed. A row ends as done when the stock was written or the rows already recorded what the order owes, or as refused when something a placement cannot fix stands in the way, such as more reserved than the order owes. The reason is stored on the row, and a manual compensation repairs it.
If a registration fails, three things try again:
- the order's next save, which an invoice or a shipment brings within seconds for most integrations;
- a cron job every five minutes, for orders owed longer than two minutes;
bin/magento inventory:order-reservation:run, by hand.
bin/magento inventory:order-reservation:list shows what is owed and why the last attempt failed. Orders owed longer than an hour raise an admin system message. A missing package or a disabled Inventory module is an installation problem, not the order's fault: the admin message and the listing name it at once, and no attempt is counted against the order.
Integrations other than the register can read the store before they send the attribute: GET /V1/inventory/order-reservation returns the module's version, the mode (inventory or legacy), the attribute, and whether registration runs for the website asked on. The route needs the ACL resource Hyva_InventoryOrderReservation::capabilities.
Magento's Repair Tools
With Magento Inventory, Magento ships inventory:reservation:list-inconsistencies and inventory:reservation:create-compensations. The second package hooks into both.
The listing leaves out skus the module still has to register. They are not inconsistencies for a merchant to fix, they are work the module has not done yet. Other skus of the same order stay listed as normal.
A manual compensation for an order the module has recorded is checked against what the order still owes on the stock it names. Already balanced: nothing is written and the command reports an error. The right amount, or part of it: written. The wrong direction, too much, or a sku the order does not have: refused with the current numbers. A refused row becomes done once nothing is missing on any stock. Refusals reach the command as errors, so it prints them and exits non-zero; for rows that were written, the package prints how much is still owed after a partial compensation under Magento's output.
Updating with Hyvä POS
Hyvä POS PRO plugs into the second package to assign the register's sources before a placement. Update the PRO module and the two packages together:
- An older PRO module (1.5.0) with the 0.1 packages leaves the register's source assignment before the placement unplugged, because the step it plugs into moved to the second package.
- The 0.1 packages with PRO 1.6.0 but without the second package leave a Magento Inventory store's orders owed until it is installed. Admin messages will warn about this.
Without Magento Inventory
On a store without Magento Inventory the first package registers the sale on the legacy stock item, the way the checkout does. From there on everything is Magento's own legacy behaviour: a cancellation returns the unit only when Set Items' Status to be In Stock When Order is Cancelled is on, a credit memo returns it only when asked to, and a shipment moves nothing. A line the stock item cannot cover, with backorders off, is deducted past zero the way an admin order's is, since the goods have left the shop, and the item reads out of stock.