Add postal code to match account tax rule


Some countries (e.g. Switzerland and the United States) also require postal code for correct application of taxes.


This proposal idea extends an earlier one to add subdivisions to account_tax_rule_country, adding two additional criteria that match postal codes.

This change is aligned with how it is done in the carrier_subdivision module:

  • From postal code: the postal code of origin
  • To postal code: the postal code of destination

I’m wondering if it will not be better to just create new subdivision for those specific case which seems to be only existing in Switzerland.

That might be a possibility for some limited use cases.

In the United States, however, boundaries for tax jurisdictions are usually aligned with cities not subdivisions (subdivisions are normally a state for US addresses, which are each independent tax agencies). Here’s an example for Utah. My hesitation with creating new subdivisions for tax matching in the US is that we would be mostly duplicating the value for the city.

Postal codes are the de facto method used here to match up a customer’s address within a particular tax jurisdiction since sometimes boundaries don’t line up exactly the way you think they should (see Streamlined Sales Tax rate and boundary files).

But it is not related to the city of the customer but to the location of the company. So in such case the system will be configured with the proper taxes that applies to the company. I doubt it will make sense to create all the possible taxes for every cities in the US.

Actually in the US it depends on the business model and type of transaction. If you have a fixed place of business, then yes, it is sourced to the company’s location. But certain types of transactions (internet purchases, mail orders, taxable services performed at customer’s location, etc.) are sourced to the tax jurisdiction of the customer’s location, increasingly so since about 2018. It’s complicated. :sweat_smile:

The approach I have taken is to create tax module as needed (i.e. for any state that my company has a presence or substantial sales in).

As I’m thinking about this more (and since only about half the states participate in the Streamlined Sales tax group mentioned above and provide zip-code lookup data), a more general solution for the US might be to provide the ability to match on the city. This would work for most cases. Any customer that had a unusual tax jurisdiction could have a more specific tax rule applied manually on the party level.

But since Tryton provides the ability to look up a city by the postal code, either approach (by postal code or by city) would be helpful.

Reading Sales taxes in the United States - Wikipedia, at first I though that it will not be practical to have about 3K rules (number of county). But it seems there are thresholds in place to avoid the local taxes. So probably as you said creating the rule when needed may be the way to go.

Yes, any location in the US is part of a component tax structure and will have an aggregate tax that is the sum of the local, county, and state (and other levels if applicable) component taxes. As a business owner, I only have to worry about collecting the aggregate tax for a specific location (the sourcing location), that is, I don’t need to worry about creating rules for each level of the component tax heirarchy, just one rule for the aggregate tax for that location. Does that agree with what you’re saying?

I guess you need to distinct the part of the collected taxes for each level so you will have to define tax rule for each level.
I see this implemented in Tryton as having 3 tax groups: state, county and local. On the products the taxes for the company local place are setup. Then the tax rules will replace each level by the proper tax (or no tax if there are no taxes for the target place).
Then you have the tax code which uses a tree structure from state to county and locale as I guess you need to report them to the state per county and local.

That is a really interesting suggestion and helps me see more possibilities of how to structure things. It seems that if I did it the way you’re suggesting, it would simplify tax templates and likely involve fewer tax rules. But I’m wondering how much of a difference it would make versus the conceptually simpler approach of using one rule with a combined tax rate per local jurisdiction.

Tax rule approaches

To get a better sense of the actual differences between the two approaches, I used this source to create a quick analysis of the breakdown of taxes into rules for the entire state’s tax system. I believe Utah’s sales tax structure is representative of most, if not all, other US states.

Approach 1: one rule per tax locality, using the combined rate

With 299 effective tax localities and 346 postal codes, I guess that could be implemented in Tryton most simply as 299 tax templates, 299 tax rules, and 346 tax rule lines, assuming the postal code is used in the rule matching. 944 records total. :sweat_smile:

Approach 2: one rule per tax component

Using @ced’s recommended approach and the account_tax_rule_country module to match on the subdivision level, we would end up with something much more manageable.

tax component state county local
ST 1
LS 1
CO 1
MT 4 11
MA 4 3
MF 1
CT 7
HT 29
HH 1
AT 19
CP 1
SM 2
RH 4 1
CZ 7 51
TO 1
TN 6
RR 24
CF 2
# of rule lines 3 50 128

This analysis for this second approach suggests at least 22 tax templates, 18 tax rules, and 181 tax rule lines. 241 records total, which is quite a bit less than the first approach.

But we need a mechanism to identify the county and “local” tax district of a customer’s address but I’m not sure of an easy way to get either without some customization. Tryton’s import_postal_codes script imports the geonames data dump, which includes the county, but does not currently import it into the company database. And the Address model in Tryton currently only relates to one (Many2One) subdivision (which in the US is the State).

Identifying the tax district

One nice side effect of matching on the postal code is that there are some that are assigned primarily to universities (e.g. 84322) so a tax rule could replace invoices with that postal code with a tax exempt 0% tax by default, whereas other postal codes for the same city would replace it with a normal tax.


  • Out of the 346 5-digit zip codes in Utah, 228 overlap with more than one tax district and 17 overlap more than one county.
  • Out of the 299 location-based tax districts in Utah, 125 overlap with more than one 5-digit zip code but none overlap more than one county.

What I get from this is that the 5-digit zip code is not enough. Only through a combination of street address, city, and postal code that I can find the proper tax district for a customer in the US. In other words, the ZIP+4 is needed to correctly identify the tax district.

A boundary file provided by the tax agency allows me to find the ZIP+4 for a given address. It seems like I would need to import the ~600K rows into a table that could be used to do a lookup of the tax district and county for a given address, using a model and schema that is generic enough to use for other states’ boundary files as well. Once the tax district and county has been identified, I would suppose that they would be saved to a party as additional Subdivision fields of type tax district and county respectively, so that rules could be matched against them as needed. I’m covering a lot of ground here at a high level and am open to suggestions.

Reporting per tax district @ the combined rate

For reporting non-fixed location sales at least in Utah, I report the base sales amount and tax at the combined rate for each tax district and the state then divides up and delivers each component tax to the right agency. I don’t distinguish the tax at each component level.

tax district net base sales combined tax rate sales tax
City 1 $100 7.5% $7.50
City 2 $100 8.1% $8.10

So using a tree structure for tax codes from state to county to local, as you’re suggesting, wouldn’t roll up into the right structure I need for reporting.

Any idea how to make this part work?

For me we may reach a limit of tax rule system. The lookup of tax rule is performed by checking each line one by one if it matches. So having so much rules will slow down the system (probably too much to be usable).
I’m wondering if it will not be better to connect to service like
Or at least manage to have the tax rate to be dynamically compute on a single tax sale (or one tax sale per state report).

Yes, that’s a question I’m trying to explore. From a architectural perspective, it doesn’t make sense to me to depend on a third-party service in order to make any taxable sale, especially when all the data to figure things out is publicly available. Perhaps for a national company that has sales in all 50 states that might make sense, but I’m wondering whether there might be a feasible solution for smaller companies that might only operate in one or a small number of states.

This is good to know. I haven’t looked deeply into the implementation of the rule engine yet, though I think there may be opportunity to group some of the tax components into one tax to need fewer rules. Also, we could update the tax district and combined (if applicable) tax rate dynamically for an address one time whenever it changes.

To sum up a relevant resolution to this topic, I concur that it doesn’t make sense to have rules based on postal codes, since just in Utah there are currently almost 600K ZIP+4 postal codes. It might make sense to have rules that match ranges of postal codes, but if you go that far, you might as well match on subdivisions, i.e. tax jurisdictions (as @ced suggested earlier).

As far as using Tryton’s tax rule system for US sales taxes, I have three ideas that I will share in separate posts below. Feel free to split them into new topics if you think they are worth pursuing.

Create a module for US address verification using the free USPS Address API. This would validate an address and provide the ZIP+4 code needed to identify the tax jurisdiction of a customer (for destination-based sourcing). I imagine this would only need to be done when the address is changed, but could be manually triggered like the Check VIES wizard.

The Streamlined Sales Tax Project (mentioned earlier) provides standardized boundary and summarized rate data, which are updated by member states quarterly. After digging deeper into the project, I realized this is not a currently a solution for the entire US. Since the project has been around since 2000, it doesn’t seem very likely to me that additional states (including some of the most populous) will join the project anytime soon (some reasons offered by New York, most of which could be traced back to the data format not being flexible enough), but it does seem to be a complete solution for those states that are part of the project (all 22 of them). At any rate, the project seems to have awakened the need to have a more streamlined process across all states to make it easier to collect sales tax from out-of-state businesses.

Looking at the SST rate data for all member states, only one state currently has five rate components, all others have less than that.

Looking at the actual types (i.e. Jurisdiction Types or what would likely be Tryton tax groups) of active component rates for all member states (combined):

Jurisdiction Type Component Rates
State 22
County 1093
City 4229
Town 19
Special District 2324
Other Combination Collection 5
Other Special Applications 4

If we look at tax components per state, we get something like this:

At this point, I’m not sure whether it would be worth developing a module for this when it is not a complete solution for the US. But it may be a complete solution for businesses that operate only in some states. Downsides are that the database would be bigger (due to the boundary and rate data for the states that the company operates in) and the module’s data would need to be updated once a quarter (either with Tryton’s scheduler or by manually running a script).

As far as tax rules go, I believe the basic tax computation could be done dynamically, without Tryton’s tax rule system, due to a boundary record containing all of the relevant jurisdictions (i.e. tax component references) for a given ZIP+4 postal code. The module would be dependent on the USPS address verification module (described above) to obtain the ZIP+4. We would need some way to trigger the SSTP tax calculation when the address is in a supported State (subdivision for US addresses). I wonder whether tax rules could be used for this?

Openlabs created a Tryton (3.2.1) module for Avatax that creates taxes in Tryton just in time. Their reported reason for doing this was not primarily due to Tryton’s rule system performance, but because “the US supposedly has 11,000 tax jurisdictions and keeping the system updated with all the rules could easily end up being a full time job and a pain in the rear!” While a SSTP module would help with the maintenance problem for a select number of states, comparing performance between these two approaches is something that I’ve not yet done.

Extend TaxLine with an additional optional Many2One('', ...) field that could be used for tax reporting, similar in some respects to how analytic accounts work with financial accounts. This additional tax would have the same amount as each component tax. Some US states require taxes to be reported using a composite reporting tax code (usually at the level of the most local tax jurisdiction) rather than reporting the individual components that were used to figure the composite tax.

This functionality could be packaged as part of the above module or a separate module if this functionality is more broadly useful.

I do not think it is needed, you can just add more TaxLine. Or having the tax line for all the composite taxes link to the same tax which is the proper level tax.
But if you use the Avatax service, I do not know how you can know that when creating the tax.

Yes, I could see that or calculating each component tax individually and creating one TaxLine for the composite tax as possibilities.

Yes, I would imagine that Avatax, TaxCloud, or any other third-party service would figure out the tax components internally and provide the composite or individual taxes via the api, whichever the state requires for reporting (looking at the Openlabs code, that seems to be what is happening).