Case study · Anonymized · Odoo.sh

What an Odoo.sh accounting audit usually finds

The profit and loss is wrong before anyone has “a reporting bug.” On a multi-company retailer we audited, three mechanisms were enough to make a normal month look disastrous.

In plain terms: if Odoo.sh profit does not match the accountant’s spreadsheet, check stock-closing entries posted on top of real-time cost of goods, cash journals that are not the bank the money actually hit, and stock or cash sitting on the wrong company. Do not post a correcting journal on production until a staging copy reproduces the gap.

The month that looked unprofitable

Operations said the month was ordinary. The profit and loss said cost of sales had swallowed the revenue. Physical stock in the shops was fine. Point-of-sale orders were fine. The general ledger was not.

The database was Odoo.sh, several companies under one instance: shops plus a central warehouse company. Accounting was Anglo-Saxon. Inventory valuation was real time. The inventory period was daily. That combination is the first thing we look for.

Stock closing posted a second cost of goods

With Anglo-Saxon accounting and real-time valuation, the customer invoice already records cost of goods sold. A daily inventory period then lets the scheduler post another document, often referenced as a stock closing. Both amounts land in cost of sales. A stock-variation account that should net out stays open. The stock asset on the balance sheet moves even though nobody received or sold anything extra.

What we do not do: delete stock moves, point-of-sale orders, or the valuation layers that came from real receipts and real sales. Those are the warehouse. The extra cost is the closing document.

The repair, once staging proves it, is:

  • List every stock-closing entry in the month, company by company.
  • Reverse only those entries, so cost of sales falls back to the invoice cost and the variation account returns toward zero.
  • Turn the inventory period from daily automatic closing to manual, and leave Anglo-Saxon and real-time valuation on. Shops keep costing on the invoice. Finance can still run a closing later if they truly want one.
  • Leave the warehouse company alone if its closings were not part of the error. One company’s profit is not fixed by editing another.

After the reversal, the stock general-ledger balance will not match a “periodic closing” view of inventory. That is intended. Invoice cost of goods is the source of truth. The shelf quantity does not change.

Three documents for one cash amount

A shop day produces a session close, a mobile-money statement, and a bank statement. Only journals plus the bank statement are the general ledger. A parallel operations file of till transactions is useful for the supervisor. It is not the books.

The failures we keep seeing:

  • Session close posts collections into a generic bank journal. The accountant cannot tell which account the money hit, so the statement import matches the wrong line or matches nothing.
  • The shop statement is reconciled, and the mobile-money journal still shows yesterday’s collections because the transfer out of that journal was never posted, or was posted on the wrong company.
  • A fund transfer must decrease the source journal and increase the destination. If it is posted once, on one company, both dashboards lie in opposite directions.
  • Counted cash at session close is typed to equal “expected” without looking at the agent statement. The session looks closed. The bank does not.

The fix is named journals for each real account, an import map for each statement format, and a session-close method that lands on those journals. Transfers are documents with a source, a destination, and a company. The accounting dashboard card for that line should rise when the session closes and fall when the transfer is posted.

The balance sheet only works because the map is wrong

Renaming an account does not change what Odoo does with it. Account type does. We have opened charts where an asset account carried a balance that belonged on the profit and loss, and income accounts that still had a balance-sheet type. The trial balance “balanced.” The statements did not mean what the labels said.

Reclass is a reviewed list: account, current type, type it must have, balance that will move. It is applied on staging against the accountant’s export. Production waits until the difference is inside a tolerance finance has agreed in writing, often a small residual rather than a promise of zero cents.

Inter-company stock

Goods that leave the warehouse company and arrive at a shop have to exist on both sides: issue on the sender, receipt on the shop, stock asset moving with them. A transfer that updates the quant and not the receiving company’s ledger is how a shop sells stock the balance sheet still holds at the centre. We trace one document, one product, both companies, before we talk about margins.

Vendor bills and the payment that already left

Approval that lives in chat is not an audit trail. A posted bill is not the same as money that left the bank. On retail groups we put the approval on the bill: who may approve, what was approved, and a clear state for payments that are proposed versus payments that have hit the statement. Finance stops reconstructing the week from messages.

How the audit is allowed to run

Odoo.sh production is not where this is discovered. The sequence we insist on:

  • A staging build with a copy of production data, on its own branch.
  • A read-only script that compares the profit and loss, company by company, to the spreadsheet finance already signs.
  • A dry run that lists every journal it would reverse or reclass, and posts nothing.
  • Finance reads that list. Only then is an apply flag turned on, on staging first.
  • Production is a separate, later step. Merging the tools to the production branch does not itself post a single entry.

If the read-only pass does not explain the gap, we do not “adjust” the month to make the total match. The unexplained amount is the finding.

Books that do not match the spreadsheet?

Send the month, the companies, and the export finance already trusts. We start read-only.