Getting the rate onto the line is the easy half, and most systems do it. The hard half arrives later, from a direction nobody plans for: a filing period has to be reconstructed, and the question is not what the rate was but why it was that rate, on that line, for that customer, on that date, and whether anything has moved since. If the answer lives in a spreadsheet built from an export, the reconstruction is an archaeology project. If the rate was applied by a service that has since updated itself, the record of the decision may not exist at all. Tax is not really a calculation problem. It is an evidence problem that presents as a calculation problem.
A tax engine on the ledger, not beside it. Taxes are records with computation and repartition lines, grouped into tax groups, and applied through fiscal positions that map a customer's situation onto the right treatment, including by ZIP range where you configure it that way. Every line carries its own tax and its own legal note, so the invoice states its own rationale.
And the evidence stays put. Posted entries carry an inalterable hash and a secure sequence number, journals can be locked into hash-chained mode, and changes to posted entries are logged immutably against the move, the account, the tax, the partner and the company. A posted entry is corrected by a reversal or a controlled resequence rather than by being edited out of existence, which is what makes a filing period reconstructable years later rather than merely archived.
Country coverage is code, not a licence tier: 232 country localisations, and the e-invoicing and e-filing modules for the countries that mandate them, are installable Community modules on the deployment you own.
Three places, and the difference matters more here than on any other page.
The engine, the fiscal positions, the per-line legal notes, the audit trail and the localisations are Odoo itself. No module to add, no tier to reach, no extra fee per user, and it runs on infrastructure you own. NetSuite bills its tax engine inside a suite you license per seat; in Odoo the engine and the audit trail are already included, and that difference is the product.
The tax report is not. Community defines the generic tax report but ships no
screen that renders it, and the dynamic tax report is in the paid edition,
which is licensed per user for the whole system, never per app. The free
replacement is account_tax_balance, published by the Odoo Community
Association on the 18.0 branch, which computes tax balances over a date range
from the same ledger entries. We install it. The tax position underneath it was
already yours, and it stays extractable from the entries whatever renders it.
Rate determination at Avalara's grade is a third-party product, and we will not pretend otherwise. There is no rate service inside either Odoo edition and none inside ours, the connector is sold by the tax vendor, it runs on Community or Enterprise alike, and you buy it from them. What Odoo gives you natively is a configurable engine and fiscal positions you maintain; what a rate service gives you is somebody else maintaining thousands of jurisdictions. Those are different products and the honest answer at the call is which one your footprint needs.
What our agent adds, it audits, and it does not fix
Nobody opens migrated invoices one at a time hunting for the ones that lost their tax in the move. The agent reads every migrated draft customer invoice, pulls out the lines that came across carrying no tax at all, and describes per invoice what is missing and what the correction should be, then it stops. On your approval it writes that finding onto the invoice as a note, so it lives on the record and in the chatter rather than in a message someone has to keep. It never sets a tax on a line: the correct tax is specific to your tenant, and an agent guessing it would put a wrong tax on a customer invoice, which is worse than the blank it found. The invoices are still wrong after the run, what you have is the whole list, with reasons, attached to the documents themselves, instead of a hunt.
That block is our migration tax audit. It runs in the migration window, on draft invoices, and produces a fix list, it does not apply the fix, does not post anything, does not calculate your ongoing tax and does not file. Nothing it writes moves money. If this scoping line is dropped in build, the agent block must be dropped with it.
A tax engine decides which tax applies to a given line and then records the decision as part of the accounting entry rather than as a calculated total. Two mechanisms do most of the work. The first is the tax record itself, which holds the rate, whether it is computed as a percentage or a fixed amount or on top of another tax, and, importantly, how the resulting amount is split across accounts and reporting tags, which is what allows one payment to be reported in several boxes of a return. The second is a mapping layer, often called a fiscal position, which selects the right tax for a given customer or supplier based on their situation: where they are, whether they are registered, whether the goods crossed a border. What an engine does not do by itself is know the rate. Rates are configuration or they come from a rate service, and the difference between the two is the main thing to establish about any system before you rely on it.
Because the two taxes are shaped differently. A VAT or GST regime typically has a small number of national rates and a registration that determines which apply, so the rates are configuration a finance team can hold and revise a few times a year. US sales tax is destination-based and levied by states, counties, cities and special districts that overlap, change on their own schedules, and disagree about whether a particular product is taxable at all, which produces thousands of jurisdictions and a rate table that is out of date almost immediately. Software can apply a rate correctly without knowing what the rate should be, and those are separate jobs. That is why US sales tax is generally answered by a subscription rate-determination service that maintains the jurisdictions on your behalf, sitting alongside whatever ledger you run, and why a system claiming to "handle US sales tax" is worth one follow-up question: does it apply rates, or does it maintain them?
It should describe the defect precisely and leave the correction to a person, rather than choose a tax and write it. The tempting design is the opposite, resolve the exception automatically so the migration finishes clean, and it is wrong for a specific reason rather than a cautious one: the correct tax record is particular to the destination system's own chart of accounts and tax configuration, so an agent that infers it is guessing, and a wrong tax on a customer invoice is a worse defect than the blank it replaced. It is also a quieter one, because a blank field gets found and a plausible wrong number does not. The useful output is an audit: which invoices, which lines, what is missing, what the correction ought to be, recorded against the documents themselves so the list cannot drift away from the records it describes. Oracle states the opposite posture on NetSuite's own tax page, under the heading No More Guessing: "Know that the right rate is applied to every transaction, every time. Accounting, purchasing and sales teams can confidently process taxes in accordance with country-specific laws." That is a promise about a steady state, and it is a reasonable one to make about a system that has been configured. It is not a promise anything can honestly make about records arriving from a system that was configured differently, which is exactly the moment a migration consists of.
Broadly, three things: that the rate applied to a transaction was the rate in force for that customer at that date, that the total reported on the return agrees with the underlying transactions, and that neither has been altered since. The first two are usually available in some form. The third is where systems differ most, and it is decided by design rather than by policy: a ledger that lets a posted entry be edited can only offer you a promise that it was not, whereas a ledger that seals posted entries, with a hash chain, a secure sequence, and corrections made as reversals rather than edits, can offer evidence. The practical implication is that tax evidence should live with the entries and not in a reporting layer built on top of them, because a report can be regenerated from changed data and look identical. Ask where a system stores the tax decision, not where it displays it.