Money & Tax Conventions (gross to net)
Odoo treats every account.move.line.price_unit as a tax-exclusive
(net) amount. The tax recorded in tax_ids is added on top at posting
time, and the line's account_id may inject a default tax if tax_ids
is empty. The bill's amount_total is always gross.
Receipts and invoices the user uploads show gross totals. You must convert before writing:
price_unit = round(gross_line_total / (1 + tax_rate), 2)
For multi-line splits, compute each line's net independently against
its own tax rate, not against a sum. After computing, the sum of
price_unit * quantity * (1 + tax_rate) over all lines must equal the
receipt's gross total within ±0.02 EUR (rounding tolerance).
If a line has no applicable VAT (e.g. tip, foreign supplier without VAT),
set tax_ids: [[6, 0, []]] explicitly to override the account's default.
Verify against the gross total
This convention is the usual cause of a post-create mismatch: tax applied on top
of an already-gross price_unit, or an account injecting an unintended default
tax. After creating the draft, read amount_total back and compare it to the
document's gross total. On a mismatch beyond the ±0.02 EUR tolerance, show the
user the diff and wait — do not silently rewrite the line.