Inventory Sources and Stocks
Hyvä POS is in closed beta
Hyvä POS is currently in a closed beta (pilot phase) with a small group of merchants. It is not yet generally available: the App Store release follows the pilot, and features and configuration may still change, possibly in backwards-incompatible ways, before the general release. Want to take part? Sign up at hyva.io/pos.
You have been selling from a single stock. Now there is a shop as well as a webshop, and one stock figure no longer describes both: what is on the shelf is not what is in the warehouse, and a customer can buy the last t-shirt online while a cashier is scanning it at the counter.
Magento handles that with Multi-Source Inventory. This page covers the parts that matter when a register is involved.
You Do Not Have To Use MSI
The register works on a single stock. Nothing in Hyvä POS requires Multi-Source Inventory: with one stock the app reads and writes the single Magento stock figure, sales deduct from it, and the source picker and transfer actions do not appear. Many merchants run that way, and many agencies remove MSI from the install.
The Hyvä POS module installs on a store without Magento Inventory as well. On such a store the register works on Magento's own stock figure, a returned item goes back into that figure, and the pickup queue says the store does not have Magento Inventory, since in-store pickup is one of its modules. On a store that has Magento Inventory installed but does not use it for the register, turn Use Multi-Source Inventory off: on the register under Settings → Store Configuration → POS Inventory, or on PRO under Stores → Configuration → Hyvä POS → Terminal Defaults → Stock, per location and lockable. With it off, the register reads the single stock figure and the source features stay off.
We still recommend MSI once you sell in a shop as well as online, for one reason: with a single stock, the webshop can sell the item that is in the cashier's hand. One figure covers the warehouse and the shelf, so an online order can consume the last unit while a customer is standing at the counter with it.
Giving the shop its own source fixes the reporting side straight away: you can see what is on the shelf, restock it, count a delivery into it, and the register checks stock against the shop specifically rather than an aggregate across sources.
Reserving shelf stock from the webshop
A shop source in the same stock as your warehouse gives you separate figures and everything on this page. Salable quantity online is still the sum of both, so the webshop can still sell shelf stock.
Reserving the shelf means the shop source has to sit in a stock the webshop does not sell from. That in turn means the register has to sell through a store view whose website maps to that stock, because Magento resolves fulfillment from the order's website (see below). Set up that way, online stock and shop stock stay separate. It is more moving parts, so do it when the shelf has to be protected: a small shop with fast-moving lines, or a single last unit that cannot be promised twice.
Three Things Magento Keeps Separate
| What it is | |
|---|---|
| Source | A physical place that holds stock. Your warehouse. Your shop. Each source holds a quantity per SKU. |
| Stock | A bucket of sources, plus the websites it serves. A stock is what Magento adds up to answer "can I sell this?". |
| Sales channel | The link from a stock to a website. |
The register sells from a source: you configure which one it sells from. Magento fulfills from the stock that the order's website points at. Those are two different questions, and when they disagree the shipment fails as described below.
Default Source and Custom Stocks Do Not Mix
The Default Source cannot join a custom stock, and custom sources cannot join the Default Stock. Magento rejects the link:
Can not save link related to Default Source or Default Stock
Only the original Default Source ↔ Default Stock pairing is allowed. There is no way to add default to a stock you created, and no way to add your new shop source to the Default Stock.
This rules out a gradual migration. The moment you attach a website to a custom stock, every SKU that still lives only on the Default Source becomes unfulfillable for that website. Magento does not report those as out of stock; it refuses to ship them.
Why a Wrong Setup Fails After the Sale
When a shipment is created, Magento does not deduct from the source the register named. It resolves the stock from the order's website, then checks whether the SKU is assigned to that stock. If it is not, it throws:
The requested sku is not assigned to given stock.
which the API returns as:
Could not save a shipment, see error log for details
By then the sale is done. The customer has paid, walked out, and the order is invoiced. The shipment is the step that fails, so the order sits in Processing with no shipment and stock is not deducted.
Two things follow from this:
- Changing the source at the register cannot fix it. The stock comes from the website, not from the source. If the SKU is not in the website's stock, every source fails identically.
- The cashier cannot resolve it. It surfaces in Reports → Sync Issues as a shipment failure, and the fix is in the Magento admin.
The register filters the source picker to sources that are linked to your website's stock, so a cashier cannot pick one Magento would refuse. If the list looks shorter than your source list in the admin, that is why.
Splitting One Stock Into Warehouse and Shop
Do these in order. Step 3 is the point where fulfillment switches to the new stock. Everything before it can be undone without affecting orders.
1. Create the sources. Stores → Inventory → Sources. Add warehouse and shop (use your own codes). Give the shop source its real address; source selection and distance rules use it later.
2. Move the stock off the Default Source. In the product grid, select the products, then Actions → Transfer Inventory To Source. Choose default as the origin and warehouse as the destination, and tick the option to unassign from the origin.
The transfer adds, it does not overwrite
The destination quantity becomes origin + destination. Running the same transfer twice doubles the destination, so it is not safe to repeat. Check a few SKUs before running it across the catalog.
3. Create the stock and attach the website. Stores → Inventory → Stocks. Create one, assign warehouse and shop to it, and add your website as a sales channel. This is the step that switches fulfillment over. Anything still sitting only on default breaks here, which is why step 2 comes first.
4. Reindex. Run the inventory and cataloginventory_stock indexers, then flush the cache. Salable quantities are wrong until you do.
5. Point the register at the shop. On the register, Settings → Store Configuration → POS Inventory → Shipment Source, set to shop. On PRO, set the source on the location instead and every terminal in that shop inherits it.
6. Check one sale end to end. Sell a single item at the register and confirm the order reaches Complete with a shipment, and that the quantity came off shop. If it does not, Reports → Sync Issues will say so.
Keeping the Shop Shelf Supplied
Once the split exists, the shop source needs restocking, and that happens at the register rather than in the admin.
- Move stock from the warehouse to the shop floor. Open the product's stock detail, tap Transfer, pick origin and destination, set the quantity. See stock transfers.
- Take in a delivery with the scanner. Turn on Auto-increase stock, choose the target source, and scan. Each scan adds one unit and saves. See receiving a delivery.
Both write through Magento's standard inventory API, so anything listening to MSI stock changes sees them like any other edit.
For System Integrators
The bulk operations are available over REST, which is usually how a catalog-sized migration gets done:
| Route | Purpose |
|---|---|
POST /V1/inventory/bulk-product-source-transfer |
Move all stock between two sources, with an unassign-origin flag |
POST /V1/inventory/bulk-partial-source-transfer |
Move a specified quantity, leaving the rest |
POST /V1/inventory/bulk-product-source-assign |
Assign products to a source |
POST /V1/inventory/bulk-product-source-unassign |
Remove products from a source |
Worth knowing:
- There is no CLI command for this. The only inventory console command is
inventory-geonames:import. Migrations go through the admin mass actions or the routes above. - Transfers are additive and not idempotent. The destination becomes origin + destination, and status is inherited from the origin row. A retried job double-counts.
- Bundle and configurable parents hold no stock. Only their children do, so transfers and adjustments operate one level down.
- Reindex after bulk writes, then flush the cache.
What the register sends, if you are debugging a POS order:
- Each order item carries
extension_attributes.pos_source_codewith the source that line was sold from. This is added by the Hyvä POS Magento module, so it is present on PRO and omitted on the free tier, where module-less stores reject unknown extension attributes. - The shipment carries
arguments.extension_attributes.source_code. That is native Magento MSI, not ours, so it works on both tiers. It is only sent when a source is configured. - A cart line can override its source, in which case the register groups the shipment by source and creates one shipment per source, because Magento accepts a single
source_codeper shipment.