Physical stock, virtual stock and inventory in Dolibarr

What moves physical and virtual stock in Dolibarr 24, six tested reasons why the virtual stock looks wrong, and the built-in inventory step by step, including the closing button bug in 24.0.2.

Versions concerned
Dolibarr 24.0.2 (tested: official Docker image 24.0.1 with its code replaced by 24.0.2, which was not on Docker Hub on 14/10/2026; MariaDB 11.4)
What was checked
Tests on 14/10/2026 on a fresh Dolibarr 24.0.2 instance, actions done in the interface: stock rules (none, sales order validation, shipment validation, shipment closing, reception validation), sales orders validated, partly shipped, classified delivered or cancelled, rule change with an order in progress, purchase order and partial receptions, kit, complete inventory with opening stock and a sale during the inventory, Lots/Serial module. Dolibarr 24.0.2 code read (Product::load_virtual_stock, load_stats_*, Inventory, product/inventory/inventory.php).

Dolibarr shows two figures on the Stock tab of a product, and the second one raises the most questions:

  • Physical Stock: what is in your warehouses. It is stored in the database and only changes through stock movements (automatic or manual);
  • Virtual stock: what will be left once the orders in progress are shipped and received. It is stored nowhere: Dolibarr recalculates it on every display from the orders, shipments and receptions in progress.

A virtual stock that “does not update” is therefore never a late calculation: there is nothing to recalculate. When it looks wrong, a document in progress is being counted, or is not. This guide shows which ones, with figures taken on 14 October 2026 on Dolibarr 24.0.2.

First: when does the stock move?

Everything is set in Home › Setup › Modules/Applications, Stocks card, gear icon. The page offers two lists: one decrease rule (goods out) and one increase rule (goods in). You pick only one per list: ticking one unticks the others.

Out: decrease real stocks… In: increase real stocks…
on validation of customer invoice/credit note on validation of vendor invoice/credit note
on validation of sales order on purchase order approval
on shipping validation on validation of reception
when shipping is set to closed when reception is set to closed

On a fresh installation, no rule is ticked. Nothing moves by itself: the stock only changes through Correct stock or Transfer stock on the product, or through an inventory. And the virtual stock stays equal to the physical stock: in our test, a validated order for 3 pens left the virtual stock at 10 out of 10. This is the first thing to check when “the virtual stock does not move”.

If you use shipments, the on shipping validation rule is the easiest to read: physical stock goes down when the parcel leaves, and virtual stock anticipates the orders to deliver. With the Product Lots module, Dolibarr greys out the invoice and order rules anyway: only the shipment and reception rules remain.

The page also warns that a point of sale module may ignore these rules and decrease stock its own way: check its own setup if you use one.

What the virtual stock adds up

The calculation depends on the chosen rule. What we checked:

Rule “on shipping validation” (or “when shipping is set to closed”): virtual stock = physical stock − (quantities of validated or in process sales orders − what has already been shipped from them).

Step (starting stock: 10) Physical Virtual
Order for 3 validated 10 7
Shipment of 2 created, draft 10 7
Shipment of 2 validated 8 7
Order classified delivered (the last pen will not ship) 8 8

Draft orders do not count, nor do cancelled ones: a validated order for 4 dropped the virtual stock to 4, cancelling it brought it back to 8.

With the rule “when shipping is set to closed”, a validated but not closed shipment does not change the physical stock; the shipment’s Close button does. In our test, closing a shipment that covered the whole order also classified the order as delivered.

Rule “on validation of sales order”: physical stock goes down at validation (the confirmation box asks for the warehouse), and virtual stock equals physical stock. Shipments no longer touch the stock.

Purchasing side (rule “on validation of reception”): virtual stock adds the quantities of purchase orders that have been ordered (Make order button) or partially received, minus what has already been received from them.

Step (starting stock: 20) Physical Virtual
Purchase order for 10 validated and approved 20 20
Make order 20 30
Reception of 4 validated 24 30
Receptions of the rest validated 30 30

Ten-second diagnosis: the tooltip

On the product’s Stock tab, hover the small “i” to the right of the virtual stock figure. Dolibarr shows Explanation for difference between physical and virtual stock: quantities in open sales orders (and in drafts), already sent, in open purchase orders (and in draft or approved orders not yet ordered), already received. Almost every case below can be read there.

Six reasons why the virtual stock looks wrong

Each one was reproduced on our test instance.

1. No rule is active

See above: without a rule, the virtual stock ignores orders. Tick one decrease rule and one increase rule.

2. A validated order was never completed

An order shipped in part, whose remainder will never ship, keeps “reserving” that remainder: the virtual stock stays below the physical stock, forever. Same for a validated and forgotten order. Open the order and click Classify delivered (or Cancel order if nothing shipped): the virtual stock comes back at once.

3. The purchase order is approved but not “ordered”

A validated and approved purchase order does not count in the virtual stock. It only counts after Make order. In the tooltip, it shows in brackets: “draft or approved orders, not yet ordered”.

4. A draft reception lowers the virtual stock

This is the “wrong virtual stock after a reception” case. On a purchase order for 10, already received for 4, we created a second reception of 3 without validating it:

Physical Virtual
After the 1st reception (4) validated 24 30
2nd reception (3) created, draft 24 27
2nd reception validated 27 30

The tooltip said “already received: 7” while only 4 had entered the stock. The reason, in the code: for an order already partially received, Dolibarr counts every reception line, drafts included. On the very first reception (order not yet partially received), the draft changed nothing. Fix: validate the reception, or delete the draft.

5. The rule was changed while orders were in progress

With the rule “on validation of sales order”, a validated order for 2 took the stock from 5 to 3. We then switched to the rule “on shipping validation”: the virtual stock dropped to 1, then shipping that same order removed 2 more. Result: 2 pens taken out twice, once at order validation, once at shipment (both movements are in the product’s movement list).

Change the rule when no order is in progress, or correct the stock afterwards. An inventory does it cleanly (see below: that is what we did).

6. Kits

With kits enabled (setup of the Products module: “Enable Kits (set of several products)”), we created a kit containing 2 boxes of staples, then validated an order for one kit. The virtual stock of the staples did not move (10 out of 10): by default, kit orders do not count for their components.

An option without a checkbox changes this: in Setup › Other Setup, add the constant PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC with the value 1. The virtual stock of the staples then went to 8. According to the code, this option also adds the kit’s documents to the component’s statistics (Related items tab).

When the kit ships, the components leave the stock (staples: 108); the kit itself has no stock of its own, and its virtual stock can show as negative.

The built-in inventory, step by step

Menu Products › Stocks › Inventories. The Stocks module is enough, there is nothing to install.

  1. New inventory: a Ref., a Label, the Warehouse (or a single product, or products by category), then Create.
  2. Validate (Start): Dolibarr fills the list with the products that already have stock recorded in the warehouse. The Expected Qty column is the current physical stock.
  3. Enter the counted Real Qty, then Save. This button only saves the quantities: no stock movement is created at this stage.
    • empty field: the product is left alone (in our test, the notebook left empty kept its stock);
    • 0: the stock is reset to zero;
    • Fill real quantity with expected quantity copies the expected quantity everywhere: handy, but a forgotten product is then confirmed as is.
  4. Generate movements and close, then Yes. Dolibarr creates one movement per difference, labelled “Inventory INV-2026-10” (code INV- followed by the reference), and the inventory becomes Closed.

Entering opening stock

A product that has never had stock in the warehouse does not appear at step 2. Add it with the top line of the table: warehouse, product, quantity, Add. That is how we entered 15 binders: at closing, a +15 movement. For a single product, Correct stock on its Stock tab does the same.

The same inventory fixed the error from reason 5: Dolibarr thought it had 1 pen, we counted 3, the +2 movement put the stock right.

The trap: sales during the inventory

At closing, Dolibarr does not replay the difference found when counting: it sets the stock to the counted quantity. The movement is “real quantity − stock at closing time”.

In our test, 8 boxes of staples were counted and saved, then one box was sold and shipped before closing (stock: 7). At closing, Dolibarr created a +1 movement and the stock went back to 8: the box sold reappeared. The help on the Expected Qty column says so: making stock movements during the inventory is not recommended.

Count, enter and close without shipping or receiving in between, or inventory one warehouse or one category at a time so each inventory stays short.

In 24.0.2, the closing button shows an error

In Dolibarr 24.0.2, clicking Generate movements and close does save the quantities entered, then shows a blank page with this message:

Access to this page this way (POST method or GET with a sensible value for 'action' parameter) is refused by CSRF protection in main.inc.php. Token not provided.

The inventory is not closed and no movement is created. The cause: the redirection made by this button does not send the security token, which Dolibarr requires by default since version 24.0. The fix has been in Dolibarr’s 24.0 branch since 10/10/2026, but not in 24.0.2, the latest published version on 14/10/2026.

The workaround we tested:

  1. Save the quantities;
  2. open the Generate movements and close button in a new tab (middle click, or right click › open in new tab): the link itself does contain the token;
  3. in that new tab, confirm with Yes.

The inventory is then closed and the movements created normally. Do not change $dolibarr_nocsrfcheck or the protection level to get around the error.

With lots and expiry dates

If you track lot or serial numbers (Product Lots module, with sell-by and eat-by dates), the inventory is done lot by lot: a Lot/Serial column is added to the table.

In short

  • “Frozen” virtual stock: check that a stock rule is ticked.
  • Virtual stock too low: a validated order never completed, or a draft reception.
  • Virtual stock too high: a purchase order placed but never fully received.
  • Kit components: constant PRODUCT_STATS_WITH_PARENT_PROD_IF_INCDEC.
  • Wrong physical stock: a rule changed along the way, or sales during an inventory. A properly closed inventory puts it right.

Before changing the stock rule or starting a large inventory, make a backup: Back up and restore Dolibarr.

If your stock still makes no sense, the €90 diagnosis is about looking at your instance with you for one hour, then giving you in writing what skews the calculation and what putting it right would cost.

Dolibarrstockvirtual stockphysical stockinventorywarehouseshipmentreceptionpurchase orderkit