Odoo 19 · verified meaning
Why does my AI get Odoo revenue wrong?
Short answer: because “revenue” is not a column in Odoo, and the field whose name sounds closest means something else. Your AI is not being unintelligent. It is reading field names built for documents, not analytics, with nothing to tell it which one carries the meaning you asked for. Here is exactly where the number goes wrong, and why a smarter model does not fix it.
Written by nanti.ai. The Odoo field facts below are verified against Odoo 19. We build the Context Layer for Odoo, the verified meaning layer this page argues for, so read us knowing that. The sample numbers are a demonstration, not a customer’s data.
The trap
One table, several meanings.
In Odoo, customer invoices, vendor bills, and credit notes all live in one table,
account.move, told apart by a move_type field. The amount that reads
like the answer, amount_total, is the document total including tax, and it exists
on every one of those. Ask an AI to “sum the invoices” and this is what it sums.
| move_type | state | amount_total | counts as revenue? |
|---|---|---|---|
out_invoice Customer invoice | posted | $2,140 | yes the tax is not revenue |
out_invoice Customer invoice | draft | $900 | no not posted |
in_invoice Vendor bill | posted | $1,300 | no money out, not in |
out_refund Credit note | posted | $180 | subtract reverses revenue |
SUM(amount_total) = $4,520. It has added a vendor bill and an
unposted draft to sales, counted tax as income, and left the credit note in. Illustrative
sample.
The verified reading
What “revenue” actually is in Odoo.
Revenue for a period is the posted customer invoices, net of credit notes, measured before tax. Every clause of that sentence maps to a specific field, and getting any one wrong makes the whole number wrong with total confidence.
-
move_type = out_invoiceCustomer invoices only. Not
in_invoice(vendor bills, money out) and notout_refundmixed in. -
state = postedPosted entries only. Drafts are proposals, not revenue, and they sit in the same table.
-
net of out_refundSubtract posted customer credit notes. A refund reverses revenue; ignoring it overstates the total.
-
amount_untaxed, not amount_totalThe tax you collect and remit is not income. The untaxed figure is the revenue measure, not the document total.
And who gets the credit
The salesperson is not the field it looks like.
The same trap runs through attribution. Ask “how did each rep do this quarter” and an AI reaches for the field that reads like an owner, and picks the wrong one.
invoice_user_id The salesperson The rep credited with the invoice. This is the field the question meant.
partner_id The customer Who was billed, not who sold. Group by this and you have a customer list, not a leaderboard.
create_uid Who keyed it in Often an accountant or an integration. It records data entry, not the sale.
The real problem
A knowledge problem, not an intelligence one.
Here is the part that surprises people: a more capable model does not fix this. It reads the same ambiguous field names and makes the same reasonable, wrong guess, only more fluently and more convincingly. The missing piece is not intelligence. It is verified knowledge of what your business data means.
That is what a context layer supplies: a checked mapping from what you asked, revenue, the salesperson, quantity on hand, to the exact fields, move types, and states that carry that meaning in Odoo 19, so the model stops guessing and starts reading the right column. The founder wrote about the distinction in his own words: intelligence isn’t knowledge. The product that supplies the knowledge is the Context Layer for Odoo.
Plain answers
Asked, answered.
Why does my AI get Odoo revenue wrong?
Because "revenue" is not a column in Odoo, and the field whose name sounds closest, amount_total on account.move, means something else. account.move is the shared table for customer invoices, vendor bills, and credit notes, and amount_total is the document total including tax, in draft as well as posted. An AI that sums amount_total across that table adds vendor bills and unposted drafts to sales, counts tax as income, and never subtracts credit notes. It is not the model being unintelligent; it is that the field names are built for documents, not analytics, and nothing tells the model which is which. Verified revenue is posted customer invoices (move_type = out_invoice, state = posted), net of credit notes (out_refund), measured before tax.
Isn’t amount_total the invoice amount?
It is the document total, including tax, which is not the same as revenue. amount_total on a customer invoice includes the tax you collect and remit, so it is not money you earned. The untaxed amount is closer to revenue for a single invoice. And amount_total exists on vendor bills and credit notes too, because they all live in account.move, so summing it across the table mixes money coming in with money going out.
How is customer revenue actually stored in Odoo?
Across account.move, distinguished by move_type: out_invoice is a customer invoice, in_invoice is a vendor bill, out_refund is a customer credit note, in_refund is a vendor refund. Only posted entries (state = posted) are real; drafts are not. So revenue for a period is the posted out_invoice entries, net of posted out_refund credit notes, measured before tax. Get any one of those wrong, the edition, the move type, the state, or the tax, and the number is wrong with total confidence.
My AI attributes sales to the wrong person. Why?
Because it reaches for the field that reads like an owner and picks the wrong one. On account.move, the salesperson is invoice_user_id. partner_id is the customer, not the seller, and create_uid is whoever keyed the record in, often an accountant or an integration. An AI asked "how did each rep do" will happily group by partner_id or create_uid and hand you a confident, wrong leaderboard. The right field exists; the model just has no way to know which one carries the meaning you asked for.
Won’t a smarter model fix this?
No, and this is the important part. A more capable model reads the same ambiguous field names and makes the same reasonable, wrong guess, only more fluently. Intelligence is not the missing piece; verified knowledge of your business is. That is what a context layer provides: a checked mapping from what you asked ("revenue", "the salesperson", "on hand") to the exact fields, move types, and states that carry that meaning in Odoo 19. The founder wrote about the distinction directly: intelligence isn’t knowledge (nanti.ai/notes/intelligence-isnt-knowledge). The product that supplies the knowledge is the Context Layer for Odoo (nanti.ai/context-layer).
Verified meaning
Stop the confident wrong answer.
The Context Layer for Odoo is the verified meaning layer between AI and Odoo: it hands the model the exact fields that carry “revenue”, “the salesperson”, and the rest, checked against Odoo 19. See also intelligence isn’t knowledge and can AI write to Odoo safely.
One list for the Context Layer. No newsletters, no spam.
