# Stock Valuation History & Ledger Reconciliation

> Opening, in, out and closing stock value per account for a period, next to the ledger - read on the accounting date, so both answer one question.

- **What it is:** Opening, in, out and closing stock value per account for a period, next to the ledger - read on the accounting date, so both answer one question.
- **For:** Accounting
- **Odoo versions:** 16.0, 17.0, 18.0 - Community and Enterprise
- **Runs on:** Odoo.sh or Odoo you host yourself - not Odoo Online, which accepts no third-party apps
- **Price:** 199 EUR, one-time, on the Odoo Apps Store
- **Technical name:** bambooforge_valuation_history
- **Manual:** https://docs.bambooforge.dev/bambooforge_valuation_history/
- **Live demo:** https://demo18.bambooforge.dev/odoo/action-bambooforge_valuation_history.action_bf_stock_value_run
- **Store page:** https://apps.odoo.com/apps/modules/18.0/bambooforge_valuation_history
- **Manual last updated:** 2026-09-06

---
# Stock Valuation History & Ledger Reconciliation

At every closing somebody asks the same question: *the stock account
says one number, inventory says another, where is the difference*. Odoo
Community can list the valuation layers and filter them to a date, and
from there the answer gets assembled by hand in a spreadsheet.

This is that answer as a report — and it is built on one correction that
matters more than the layout.

## Both sides on the accounting date

A valuation layer has no date of its own. Odoo's layer list groups by
**Date**, and that field is `create_date` — the moment the row was
written to the database. The journal entry the layer produced carries an
*accounting* date, and that is what the trial balance is built from.

Most of the time they are the same day and nobody notices. Then somebody
receives goods on the 2nd of January against December, or an entry is
reset to draft and re-posted into the prior period, and the two dates
part company. From then on the layer report and the balance sheet
disagree, by exactly that amount, and neither of them says why.

This report places every layer on the date of the journal entry it
produced. A layer that never produced one is placed on the day it was
written — and still counted, because a value that reached no entry is
precisely the sort of difference the report exists to surface.

## Opening, in, out, closing

Pick a period. For every stock valuation account you get:

| Opening | everything valued before the start of the period |
|---|---|
| In | positive value in the period — receipts, revaluations up |
| Out | negative value in the period — deliveries, scrap, write-downs |
| Closing | opening + in − out |

Closing is never stored. It is its own parts added together, every time
the report runs, because a stored total is one more number that can end
up disagreeing with the numbers under it.

So a change in the stock value is explained rather than only observed:
the balance moved by €40,000, of which €52,000 came in and €12,000 went
out.

## Against the ledger

Next to Closing sits **In the Ledger** — the posted balance of that same
account on the same date — and the difference between them.

Every account a category values stock on appears, whether or not
anything moved through it, and so does any account that stock entries
once reached but no category names today — the balance left behind when
a chart is renumbered or a category is pointed at a new account.
Comparing only the accounts inventory reached this period is how a
report announces that the books agree while a whole balance sits
unexplained beside it.

Two buttons on every line open what is behind each figure:

- the **valuation layers** that were added up to make the stock column,
  filtered by the same dates and the same accounting-date rule;
- the **journal items** that were added up to make the ledger column,
  posted only, on or before the closing date.

The filters are the report's own, not an approximation of them, so the
two lists always add up to the two numbers they came from. A difference
becomes something to click on rather than something to go hunting for.

## What does not fit is named, not hidden

Two kinds of value cannot belong to a reconciled line, and both are
reported as their own explanation rather than quietly dropped:

- **Manually valued stock.** A product category on manual (periodic)
  valuation still produces valuation layers, and Odoo posts no journal
  entries for them — core actually clears the valuation account of any
  category that is not automated. There is nothing to reconcile that
  value against, so it is reported as its own total and left out of the
  comparison. It is a supported way to run Odoo, not a fault, and this
  report does not tell you to change your accounting policy to suit it.
- **Value with no account.** A category set to *automated* valuation
  that names no valuation account still produces layers, and that value
  belongs to no account at all. The fix is the category.
- **Value with no posted entry.** A layer whose journal entry is still
  in draft — or was never created — counts as stock and counts for
  nobody in the ledger. Whenever there is a difference, this is usually
  most of it.

Dropping either would make the report balance while the stock did not,
which is worse than having no report.

## Filtering, and what a filtered run may claim

You can narrow the run to a few product categories. That narrows the
**stock** side only — the ledger side is whole accounts, and there is no
honest way to take a slice of an account balance by product category.

So a filtered run is a movement report, not a reconciliation, and it
says so: the difference column is left out rather than filled with the
difference between part of the inventory and all of an account.

## Who can run it

The module carries its own group, **Stock Valuation History**, in the
Accounting category, and it implies Odoo's read-only accounting group.

This is deliberate. `account.group_account_readonly` lives in Odoo's
hidden module category, so on a Community database running Invoicing
only it cannot be ticked for anybody. Had the menu been anchored to it,
the only way an administrator could let an accountant *read* this report
would be to grant them write access to something else. Now it is a
tick-box, and the report writes nothing anywhere.

## Series

Community 16.0, 17.0 and 18.0.

There is no 19.0 version, on purpose. Odoo 18.0 added this to Community
itself — `stock_value()` next to `stock_accounting_value()`, grouped
by account, with a date — and shipping a paid module that duplicates it
would mean selling something given away, and disagreeing with the core
report the first time the two rounded differently. On 19.0 the right
answer is the one already in the box.

## How we make sure it works

Before every release, **28 automated checks** run against this app on Odoo 16, 17 and 18.
Among them:

- A reader cannot report on another company.
- Running twice does not double the lines.
- The notes stay shut when there is nothing to note.
- Two wrong accounts that cancel out are not agreement.
- Manual valuation is not called a misconfiguration.

The complete list is in the source you receive.

## Install

1. Copy `bambooforge_valuation_history` into your addons path and update the app list, or install the ZIP from the Odoo Apps Store.
2. Install **Stock Valuation History & Ledger Reconciliation**.
3. Open **Stock Valuation History**.

## Questions people ask

****Which Odoo versions does it run on?****

Odoo 16.0, 17.0, 18.0, on the series you buy it for; each series is its own listing.

****Community or Enterprise?****

Both. It needs no Enterprise app.

****What does it need?****

The standard Inventory Valuation app. No extra Python package.

****Where does it show up?****

Under the **Stock Valuation History** menu.

****Does anything leave my server?****

No. It makes no call outside your Odoo server: no telemetry, no licence check, no third-party service.

****What happens if I uninstall it?****

Odoo drops the app's own tables and fields; the standard records it worked on (contacts, orders, products) stay as they are.
