OdooWiki OCA HUB

US Sales Tax Engine

l10n_us_sales_tax_engine folder_open OCA/l10n-usa open_in_new
Accounting/Localizations License: LGPL-3

Hybrid US Sales Tax engine for Odoo: resolves the combined state + county + city + district rate for a ZIP code, applies it to sale orders and invoices, and keeps a full audit trail of every calculation.

Resolution order: local rate database first, external API providers as fallback (configured independently in their own l10n_us_sales_tax_provider_* modules — this module never imports their code directly). Tracks nexus per state, taxability per product category, and caches external API responses to control call volume.

auto_awesome What this module does

AI-generated from the code · Odoo 18.0

Calculates US sales tax (state, county, city and district) for sale orders and invoices based on the customer's ZIP code, using a local rate database first and external providers as fallback, with a full audit trail.

This module adds a hybrid US sales tax engine to Odoo. When enabled in Accounting settings, it automatically calculates the combined state, county, city and district rate for a customer's ZIP code and applies it to sale orders and customer invoices/refunds.

  • On sale orders, tax is recalculated automatically when the order is confirmed, and there is a manual "Calculate US Tax" action. A checkbox decides whether the shipping or billing address is used.
  • On invoices, tax is recalculated automatically when the invoice is posted, plus a manual action.
  • Customers can be flagged as US tax exempt with an exemption code and certificate number; products get a US tax category (e.g. TANGIBLE, SERVICE, FOOD).
  • Configuration screens let you enable the engine, choose the mode (local only, API only, hybrid), set the API failure policy, cache duration and ZIP confidence threshold.
  • Master data covers jurisdictions, versioned rates, ZIP-to-jurisdiction mappings, state/category taxability rules, nexus per state and external providers with monthly call limits and warnings.
  • Every calculation is written to an immutable audit log; expired API responses are purged by a scheduled job, and provider call counters are reset monthly.
  • A wizard imports rate files (Florida DOR, Texas Open Data, generic CSV) with preview and rollback.

Depends on

accountsale_managementmail

Menus it adds

US Sales TaxConfigurationNexusProduct Tax CategoriesTax RulesProvidersRate DatabaseJurisdictionsTax RatesZIP MappingsImport RatesImport HistoryAuditCalculation LogsCache
Models and fields (17)
  • sale.order · extends an existing modelus_tax_source (Tax Source), us_tax_calculated_at (Tax Calculated At), us_tax_based_on_shipping (Tax Based on Shipping Address)
  • res.partner · extends an existing modelus_tax_exempt (US Tax Exempt), us_tax_exemption_code (Exemption Code), us_tax_exemption_number (Exemption Certificate #)
  • us.tax.rate · new modelnotes, active, source, end_date, state_id, city_rate, source_url (Source URL), state_rate, total_rate, county_rate, district_rate, effective_date, import_batch_id, jurisdiction_id, product_tax_category_id
  • us.tax.rule · new modelactive, taxable, end_date, state_id, rate_override, effective_date, exemption_reason, product_tax_category_id
  • account.move · extends an existing modelus_tax_source (Tax Source), us_tax_calculated_at (Tax Calculated At)
  • us.tax.nexus · new modelnotes, active, state_id, company_id, start_date, threshold_amount, threshold_transactions
  • us.tax.provider · new modelcode, name, active, is_paid, timeout, priority, description, limit_status, sandbox_mode, api_key_param, limit_usage_pct, account_id_param, calls_this_month, limit_alert_sent, monthly_call_limit, supports_zip_level, supports_address_level
  • product.template · extends an existing modelus_tax_category_id (US Tax Category)
  • us.tax.api.cache · new modelzip, active, state_id, cached_at, city_rate, expires_at, state_rate, total_rate, county_rate, provider_id, source_date, address_hash, district_rate, request_payload, response_payload
  • us.tax.zip.mapping · new modelzip (ZIP Code), city, zip4 (ZIP+4), county, source, state_id, confidence, imported_at, import_batch_id, jurisdiction_id
  • res.config.settings · extends an existing modelus_tax_engine_mode (Engine Mode), us_tax_fail_policy (API Failure Policy), us_tax_engine_active (Enable US Sales Tax Engine), us_tax_cache_ttl_hours (Cache TTL (hours)), us_tax_confidence_threshold (ZIP Confidence Threshold)
  • us.tax.import.batch · new modelname, source, status, state_id (State Covered), error_log, file_name, started_at, finished_at, imported_by, can_rollback, effective_date, records_created, records_skipped, records_updated
  • us.tax.jurisdiction · new modelcity, name, type, active, county, rate_ids, state_id, child_ids, parent_id (Parent Jurisdiction), parent_path, complete_name, district_code, zip_mapping_ids
  • us.tax.import.wizard · new modelsource, state_id, delimiter, file_data (File), file_name, preview_html, effective_date
  • us.tax.engine.service · new model
  • us.tax.calculation.log · new modelres_id, source, status, city_rate, res_model, partner_id, state_rate, tax_amount, total_rate, county_rate, currency_id, provider_id, document_ref, full_address, shipping_zip, calculated_at, calculated_by, district_rate, error_message, nexus_applied, shipping_city, taxable_amount, request_payload, shipping_county, response_payload, shipping_state_id, product_category_id
  • us.tax.product.category · new modelcode, name, active, description, default_taxable

Text generated by AI (DeepSeek) from reading the Odoo 18.0 code on GitHub; it may contain mistakes, so check the code before deciding. The module is very large and only part of the code was read. View the code on GitHub

Availability by version

Reviewed: 2026-11-03
Confirmed (verified in repository and branch) Estimated (unverified) No information

Confirmation status: 1 confirmed, 8 with no information of 9 versions.

terminal Installation and usage

OCA documentation · Odoo 18.0

Loading local rate data

The local provider needs three things before it can resolve a tax rate for a ZIP code: a Jurisdiction (county/city), a ZIP Mapping pointing that ZIP to the jurisdiction, and a Tax Rate on that jurisdiction. None of these ship pre-loaded — only product categories and the provider registry are seeded on install.

Import Tax Rates wizard

US Sales Tax → Rate Database → Import Rates always requires a file upload — there is no "instant, no file" option in the wizard.

  • Florida DOR: upload the official Master Address List CSV from https://pointmatch.floridarevenue.com/General/AddressFiles.aspx (select a county and effective date, then download). This creates the jurisdiction, the rate, and the ZIP mapping for every row — full ZIP-level resolution.
  • Generic CSV (any state): any file with ZIP, COUNTY/CITY, and a rate column works the same way.

Manual setup (quick test, single ZIP)

For testing a single ZIP without downloading a file, create the three records by hand under US Sales Tax → Rate Database:

  • Jurisdictions — name, type (county/city), state.
  • ZIP Mappings — the ZIP code, pointing to that jurisdiction.
  • Tax Rates — the actual rate values on that jurisdiction.

Nexus is independent of rate data

Creating a Nexus record (US Sales Tax → Configuration → Nexus) only marks that the company has a legal obligation to collect tax in that state — it does not create or require any jurisdiction, ZIP mapping, or rate. Both are needed independently before a sale order calculates a non-zero local tax.

Zero-tax lines always carry an explicit tax, never an empty tax field

When a line ends up with 0% tax, the engine always assigns a real account.tax record rather than leaving the line untaxed:

  • Exempt product category or no nexus in that state — assigned the shared US Sales Tax - Exempt (0%) tax (one tax for both reasons, across all states).
  • A state with a genuine 0% combined rate (e.g. Oregon, Montana — states with no sales tax at all) — assigned US Sales Tax {state} 0%, the same per-state-and-rate tax used for any other rate.

This is deliberate: an empty tax_id is indistinguishable from "tax was never calculated" on an invoice or in a tax report filtered/grouped by account.tax/account.tax.group. An explicit 0% tax record shows that the line was evaluated and a deliberate "no tax due" determination was made — the kind of audit trail a sales-tax-exempt line should leave behind.

The specific reason for a 0% line (which product category, which state, nexus or no nexus) is not encoded in the tax's name — that level of detail already lives on us.tax.calculation.log, one record per calculation. The tax itself only needs to answer "was this evaluated, and is it taxable" at a glance.

Migration note: if you had ad-hoc reports filtering for lines with no tax_id/tax_ids at all to flag "missing tax" cases, those lines will stop matching once they carry the explicit 0% tax — this is the intended effect of this change, not a regression.

No verified information in OCA: Installation, Configuration.

info

Data imported from GitHub: this sheet reflects the latest catalog review, not a live lookup. Always check the module documentation and staging before installing in production.