PHPackages                             jeanmarcos/inventory - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. jeanmarcos/inventory

ActiveLibrary

jeanmarcos/inventory
====================

Community distribution of Magento Multi-Source Inventory (MSI) with curated fixes. Derived from magento/inventory (Copyright Adobe), redistributed under AFL-3.0. Not affiliated with or endorsed by Adobe.

2.4.9.20(2w ago)67AFL-3.0PHP ~8.3.0||~8.4.0||~8.5.0

Since Jul 11Pushed 2w agoCompare

[ Source](https://github.com/jeanmarcos-dev/inventory)[ Packagist](https://packagist.org/packages/jeanmarcos/inventory)[ RSS](/packages/jeanmarcos-inventory/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (61)Versions (84)Used By (0)

jeanmarcos/inventory
====================

[](#jeanmarcosinventory)

Community distribution of Magento **Multi-Source Inventory (MSI)** with curated community fixes shipped ahead of Adobe's upstream release cadence, redistributed under **AFL-3.0**.

It is a **drop-in replacement** for the open-source MSI modules: it keeps the original `Magento_Inventory*` module names and `Magento\Inventory*` namespaces, so no application code changes are required.

> Derived from [magento/inventory](https://github.com/magento/inventory)(Copyright Adobe). Not affiliated with or endorsed by Adobe.

> **Looking for the line that removes the Default Stock?** That work moved to a separate package, [`jeanmarcos/msi-core`](https://github.com/jeanmarcos-dev/msi-core). This package stays a drop-in replacement and keeps receiving fixes; see [A second package](#a-second-package-msi-core) below for which one you want.

Compatibility
-------------

[](#compatibility)

**Magento Open Source**

LineConstraintInventory packages replaced2.4.9`2.4.9.*``magento/module-inventory-*`2.4.8`2.4.8.*``magento/module-inventory-*`2.4.7`2.4.7.*``magento/module-inventory-*`**Mage-OS**

LineConstraintInventory packages replacedMage-OS 3 (2.4.9 base)`2.4.9.*` (≥ `2.4.9.12`)`magento/module-inventory-*` **and** `mage-os/module-inventory-*`On **Mage-OS 3** the same `2.4.9.*` build is used with **no project-level changes**. Mage-OS republishes the inventory modules under the `mage-os/` vendor, so this package replaces both the `magento/module-inventory-*` and the `mage-os/module-inventory-*` names — whichever the platform installs, this distribution provides the code. The `magento/framework` requirement resolves because `mage-os/framework` declares `replace: {"magento/framework": "103.0.9"}`, so the framework gate still selects the 2.4.9 build.

Exclusive features
------------------

[](#exclusive-features)

Capabilities layered on top of upstream MSI. Some are **always-on** integrity guarantees, enforced once installed; the **opt-in** ones are off by default and configured under *Stores &gt; Configuration &gt; Catalog &gt; Inventory*.

FeatureActivation2.4.92.4.82.4.7Source-level reservationsopt-in`2.4.9.6``2.4.8.8``2.4.7.7`Source-level concurrency lockalways on`2.4.9.7``2.4.8.9``2.4.7.8`Reservation integrity guardsalways on`2.4.9.10``2.4.8.12``2.4.7.11`Reservation reconciliationopt-in`2.4.9.10``2.4.8.12``2.4.7.11`Supply-side oversell detectionopt-in`2.4.9.11``2.4.8.13``2.4.7.12`Storefront stock visualizeropt-in`2.4.9.13``2.4.8.14``2.4.7.13`Admin salable quantity by sourcealways on`2.4.9.17``2.4.8.18`—### Source-level reservations

[](#source-level-reservations)

Splits each sales reservation into one row **per source**, allocated across the stock's enabled sources in priority order — so salable quantity stays correct across every stock that shares a source.

 ```
flowchart LR
  R["Sales reservation(order line, −5)"] --> SP{"Split by source(priority order)"}
  SP --> A["Source A−3"]
  SP --> B["Source B−2"]
  A --> S1["Stock 1(A + B)"]
  B --> S1
  A --> S2["Stock 2(A + C)"]
  S1 --> Q["Salable qty updated onevery stock sharing the source"]
  S2 --> Q
```

      Loading - **Cross-stock accuracy** — salable quantity is updated on *every* stock that shares a source, closing the cross-stock oversell gap.
- **Compensations follow the demand** — shipment, cancellation and credit-memo compensations land on the sources the demand was originally allocated to, even when the shipment ships from a different source.
- **Disabled sources stay accounted** — a disabled source keeps its pending reservations in the salable index, so disabling a source no longer hides committed demand and silently inflates salable quantity.
- **Toggling** the setting requires a full inventory reindex.
- **2.4.7 caveat** — the SKU-list reservations reader does not exist upstream on the 2.4.7 line, so the feature covers the single-SKU read path only.

SettingDefault`cataloginventory/source_reservations/enabled`off### Source-level concurrency lock

[](#source-level-concurrency-lock)

Concurrent orders that draw from the same physical source must be serialized on that source, or they read the same availability and oversell it. Upstream locks place-order per `(sku, stock)`, which only serializes orders on the *same* stock; once reservations are allocated per source, two orders on *different* stocks that share a source still race on it.

 ```
flowchart TD
  A["Order A — Stock 1sources A, B"] --> L["Acquire (sku, source) locksin one global order"]
  B["Order B — Stock 2sources A, C"] --> L
  L --> Q{"Share a source?"}
  Q -->|"yes — source A"| SER["Serialized on the shared lock→ no cross-stock oversell"]
  Q -->|"no — disjoint sources"| PAR["Placed in parallel"]
```

      Loading - **Per source, not per stock** — locks are taken per `(sku, enabled source)`, so orders on different stocks that share a source contend on that source and can never oversell it.
- **Deadlock-free** — every order requests its locks in a single global total order, independent of the allocation algorithm, so a shared lock is always requested in the same position. Orders with disjoint source sets still place fully in parallel; acquisition retries a bounded number of times on timeout.
- **Every placement path** — the lock wraps order *submission*, so front-end and API checkout **and** admin order creation are all covered.
- **2.4.7 / 2.4.8** — these lines ship no place-order oversell lock upstream at all, so this also backports the base protection they were missing.

### Reservation integrity guards &amp; reconciliation

[](#reservation-integrity-guards--reconciliation)

The reservation ledger enforces its own invariants **at write time**, and an opt-in reconciliation pass heals orders whose release was never written.

 ```
flowchart TD
  W["Reservation write"] --> G1{"Would oversell?"}
  G1 -->|yes| REJ["Rejected(delegates to salability check —backorders / min-qty honoured)"]
  G1 -->|no| G2{"Release &gt; order'soutstanding balance?"}
  G2 -->|yes| CL["Clamped(no over-refund /positive residue)"]
  G2 -->|no| OK["Accepted"]
```

      Loading **Write-time guards** (always enforced once installed):

- **No over-release** — a compensation can never release more than the order's outstanding balance, so over-refunds and positive residue can't inflate salable quantity.
- **No oversell** — a reservation that would oversell is rejected, delegating the decision to the standard salability check so backorders and min-qty are honoured.
- **Atomic under concurrency** — the guards evaluate inside the source-level lock above, so the check and the write stay atomic even when orders are placed concurrently.

**Reconciliation** (opt-in) brings a terminal order's reservations back to zero without ever over-releasing — recovering from a failed or bypassed observer, a third-party state change, or a direct database edit.

 ```
flowchart LR
  H["Sync hook oncancel / refund"] --> Z["Terminal order'sreservations → 0"]
  S["Scheduled sweep(cron)"] --> Z
  C["CLI:inventory:reservation:reconcile"] --> Z
```

      Loading SettingDefault`cataloginventory/source_reservations/reconcile_cancel_refund`off`cataloginventory/source_reservations/reconcile_sweep_enabled`off`cataloginventory/source_reservations/reconcile_sweep_cron``0 * * * *`### Supply-side oversell detection

[](#supply-side-oversell-detection)

The write-time guards defend the demand side; this defends the supply side. When physical stock is lowered below the reservations already committed against a source — an admin edit, an ERP push, a bulk transfer, or a direct database edit — the position becomes silently oversold. Detection **never blocks the change**(physical stock stays authoritative); it surfaces the oversold position for reconciliation.

 ```
flowchart LR
  WR["Physical stock write(admin / ERP / import / transfer)"] --> CHK{"physical &lt; committedreservations?"}
  DB["Direct database edit"] --> SW["Scheduled sweep(cron / CLI)"]
  SW --> CHK
  CHK -->|yes| AL["Alert: structured log+ admin inbox notice"]
  CHK -->|no| OK["No action"]
```

      Loading - **Real-time** — every physical-quantity write path is checked as it happens; the write always succeeds.
- **Vector-agnostic sweep** — a CLI (`inventory:reservation:detect-oversell`) and an opt-in cron scan all sources regardless of how the quantity dropped, so even direct database edits are caught.

SettingDefault`cataloginventory/source_reservations/oversell_detection_enabled`off`cataloginventory/source_reservations/oversell_sweep_enabled`off`cataloginventory/source_reservations/oversell_sweep_cron``0 * * * *`### Storefront stock visualizer

[](#storefront-stock-visualizer)

Upstream shows only a coarse in-stock / out-of-stock badge on the product page. This surfaces how much is actually salable — as a traffic-light level or an exact quantity, for the whole product or broken down per source — while keeping the page cacheable. It ships as the additive `Magento_InventoryStockVisualizer`module and replaces no core module.

 ```
flowchart LR
  D["DemandAppendReservations"] --> DEC["ResolveSkusToPurge(shared decider)"]
  S["SupplySourceItemsSave / Delete"] --> DEC
  X["Salability flip(inventory reindex)"] --> DEC
  DEC --> DP{"Purge strategy"}
  DP -->|sync| T["Flush dedicatedinv_stockviz tag"]
  DP -->|async| Q["Coalescing queue"]
  Q --> T
```

      Loading - **Level or quantity** — the level (semaphore) display is rendered server-side in the cached page, with no quantity exposed and no AJAX; the quantity display fetches the exact salable number over a cacheable AJAX fragment.
- **Aggregate or per source** — per-source availability is source-reservation aware (physical quantity netted against the source's reservation balance) and degrades to the physical quantity when source-level reservations are off.
- **Composite products by type** — configurable, bundle and grouped products resolve their availability by type (the selected variant, the sellable bundle count, a per-component breakdown, or an aggregate in-stock status), each selectable in the admin, instead of the false out-of-stock the type-blind salable-quantity API would otherwise report.
- **Out of stock costs nothing** — when the server has already resolved the product to zero, the panel renders the status alone: no client component is mounted, so no call to action is offered and no fragment is requested for an answer that is already known.
- **Website-correct** — availability is resolved against the current website's stock server-side, so one website's cached fragment never stands in for another's.
- **Dedicated-tag invalidation** — a small-blast-radius cache tag is purged on both demand (reservation) and supply (source-item) changes through a shared decider, so the panel stays fresh without over-purging the product page.
- **Sync or queued purge** — the purge runs inline or, when inventory indexing runs on schedule, offloads to a database-backed queue that coalesces a burst of writes for the same SKU into a single purge.

SettingDefault`cataloginventory/stock_visualizer/enabled`off`cataloginventory/stock_visualizer/display_type``level``cataloginventory/stock_visualizer/scope``aggregate`On deploy the module registers per-product override attributes and a message-queue topology, so run `bin/magento setup:upgrade` (and `setup:di:compile` for production). When the purge resolves to the queue, run the consumer `bin/magento queue:consumers:start inventory.stockvisualizer.purge`.

### Admin salable quantity by source

[](#admin-salable-quantity-by-source)

Upstream shows the quantity a source physically holds and the salable quantity a stock aggregates, but nothing in between: a *Sources* section reporting 10 on hand next to a *Product Salable Quantity* of 7 leaves no way to tell which source holds the missing 3.

The *Product Salable Quantity* section of the product form and the *Salable Quantity* column of the product grid now report, for every source of a stock, the quantity on hand, that source's reservation balance and the salable quantity they add up to. The aggregate Magento already showed becomes the total of that breakdown.

SourceOn handReservationsSalableSLR Source A5−23SLR Source B10010**Salable quantity****13**Sources that contribute nothing are still listed and labelled — a disabled source, or a source item set out of stock — so a zero reads as a reason rather than a defect. Nothing is configurable and nothing is hidden behind a flag; with a single source the breakdown would only repeat the aggregate, so the section renders exactly as upstream.

The breakdown is exact only with [source-level reservations](#source-level-reservations)on. Without them it degrades to the quantity on hand, because reservations are then held per stock and cannot be attributed to a source — the gap source-level reservations exist to close. A grid page resolves in one pair of queries, and the breakdown stays collapsed until expanded, which costs no further request.

Installation
------------

[](#installation)

```
composer require "jeanmarcos/inventory:2.4.9.*"
```

Pick the constraint that matches your Magento line:

Magento lineConstraint`magento/framework`2.4.7`2.4.7.*``>=103.0.7 =103.0.8 =103.0.9
