Skip to content

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 one pile of stock. Now there is a shop as well as a webshop, and one stock figure can no longer describe both: what is on the shelf is not what is in the warehouse, and a customer should not be able to 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, including the one rule that breaks a half-finished migration.

You Do Not Have To Use MSI

The register works fine 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. Plenty of merchants run that way, and many agencies remove MSI from the install entirely.

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. You find out at the register, or the picker finds out at the warehouse. Neither is a good place to discover 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 that hides a local shortage.

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.

Genuinely 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 never touch each other. It is more moving parts, so do it when protecting the shelf actually matters - a small shop with fast-moving lines, or a single last unit you cannot afford to promise 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 tell it which shelf it is standing next to. Magento fulfills from the stock that the order's website points at. Those are two different questions, and when they disagree you get the failure further down this page.

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 outright:

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 at all.

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 the far less helpful:

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 never moves.

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. That is the right place to notice it, but the fix is in the Magento admin.

The register filters the source picker to sources that are actually 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 "just in case". 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. Stores → Configuration → Hyvä POS → 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_code with 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_code per shipment.