How to create a Taxes Module
English | Translate this article...
About this document
This document is part of the Localization Guide for Openbravo and describes the process of creating a taxes module ready to be distributed through the Central Repository.
Before reading this howto it is important to have a clear understanding about the Modularity Concepts, specially the process of creating and packing modules.
The main objective of a taxes module is to provide a dataset with the list of common taxes for a concrete country. In case you haven't created yet a dataset, it is recommended to firstly read the How to create a Dataset article to get a general idea before moving to the particular scenario about taxes.
From the functional perspective, it is mandatory to know the way taxes work in Openbravo. The User Guide includes a complete description about it. Reading the following articles is specially crucial for a successful taxes module development:
- Tax Rate describes the way taxes are defined in Openbravo.
- Tax Category as a way to group together several tax rates.
- Business Partner Tax Category for sharing a group of taxes between different business partners.
Finally, just in case your country is internally divided into regions which have special tax configurations, it is recommended to create first a regions module before developing the taxes one. Please read the How to create a regions module article for more information.
The biggest difficulty in creating a taxes module is on the functional side. A concrete list of the taxes to be included into the module and the way to properly define them in Openbravo is mandatory before creating the taxes module.
A functional expert should spend less than two working days for defining and introducing in the ERP the set of taxes that will be part of the module. Once the taxes definition is ready, exporting the dataset and packaging the module is a straightforward process that should take few hours.
Along with the translation and the chart of account modules, the taxes module is one of the most important parts in the localization for any country.
Apart from the evident benefit of marking it easier for users to configure their taxes, the taxes module has a very important objective: it allows to develop other modules on top of it, like official tax and withholding reports ready to be sent to the Tax Authorities. The taxes defined in the module are used for creating transactions (invoices, orders...). The modules that depend on the tax module know the exact list of taxes defined in the taxes module and which one of them take into account for the report (for example, only take into account the withholding taxes, or the taxes related to imports, etc.).
Please read the How to create an official Fiscal Report based on the Tax Report Launcher article for more information.
Defining the tax configuration
Setting up the environment
The main objective of the taxes module is to include all the configuration related to taxes into a dataset. Depending on the particular scenario for your country, the taxes module could store a great number of tax rates. For example, the Taxes Module for Spain includes more than 100 different tax rates! That means the dataset will have a big size that makes it difficult to manage (review the XML file, check the differences in case of an update, etc.).
When creating a dataset, we need to be sure that only the required data is inserted in the dataset. To make the development of the taxes dataset easier, it is highly recommended to create a new empty client in which only define the configuration of taxes which will later on be included in the dataset. With this simple trick we reduce the risk of inserting wrong data in the dataset.
We can create a new client at the Initial Client Setup window when logged as the System Administrator role. In the example, the new client is called TAXES to make its purpose clear:
Now we should log out and log in again inside our TAXES client.
Entering the taxes configuration
Inside our recently created empty client, we must define the tax configuration for our country, which involves several windows and tabs:
Tax Category window provides the way for grouping and managing similar tax rates, for example Sales Taxes or VAT 18%. Every tax rate and product belongs to a tax category, so when a product is selected for an order or an invoice, a tax in this tax category is automatically chosen.
Defining the set of tax categories is the first step we must complete before moving forward. Think about useful tax categories for the users of your country, like for example: Exempt, VAT, VAT for services, reduced VAT, VAT for real estate, etc. and provide clear and concrete names and descriptions for them using the default language for your country.
Business Partner Tax Category
Business Partner Tax Category is used to define tax categories to be associated to business partners and tax rates. A business partner may or may not have an associated business partner tax category, and only one business partner tax category can be associated to a business partner (one as vendor and/or one as customer).
A tax rate associated with a business partner tax category will only be applied to partners within the same category. If the tax does not have a business partner tax category defined, the tax will be applied to any partner. That is how you create special taxes that will be applied to some specific partners.
Based on that explanation, you must define the list of business partner tax categories to be included into your module. This decision highly depends on the legal requirements for your country and the set of taxes rates you will later on define. Some examples, taken from the Taxes Module for Spain, are: Retention lease, Lease-hold, Services, Reduced Services, etc.
Tax Rate window is used to define taxes which will be later on included into transactions. Openbravo allows complex tax rate configurations: from simple VAT tax rates to structures composed of various taxes (withholding and/or VAT) at the same time. Please take a look at the User's Documentation Tax Rate article that contains a detailed description about this topic, including several examples of simple and complex tax rates definition.
Tax Zone and Regions module
The tax rate may define the zones where this tax is applied, which can be countries or even regions inside countries. In the previous screenshot we have configured a Spanish tax rate called Adquisiciones a Canarias, Ceuta y Melilla to be used only for purchase transactions of goods coming from any of the following Spanish regions: Santa Cruz de Tenerife, Ceuta or Melilla. This was just an example of the Openbravo's powerful tax engine configuration that shows us another possible module to develop in our localization pack: the Regions module.
By default Openbravo includes the complete list of countries in the world, but not their regions. So if your country is internally divided into regions and, specially, when there are different tax rates configurations depending on the regions (like in Spain), it is mandatory to previously create a Regions module that will just include the list of regions for your country. In this case the Regions module will be defined as a dependency of our Taxes module (as we will see later on), that will allow us to include any region in the tax rate configuration.
In some countries with several official languages, it may be useful to include the taxes translation for all these languages. Openbravo defines a Translation tab inside the previous windows to create one translation record per language. In the following screenshot our original tax category is written in Spanish and we have declared an English translation:
Once we have created the translations for all the records involved in the taxes configuration we have two possibilities:
- To include the translations along with the rest of the taxes configuration inside the module's dataset. This is the fastest solution, and it is only possible if we are the owners of the taxes module.
- In case we want to create the translation for a third party taxes module, we must create a new module with a dataset which just includes the translated records and that will depend on the original taxes module.
In the Dataset definition chapter we will see how to export these translations (and the rest of the taxes configuration).
Creating the Taxes Module
At this point our TAXES client should have all the required taxes configuration that we would want to include into our module. It is the time to create it.
As you already know, a taxes module is like any other module that includes a dataset, so there are no special tricks for declaring it inside the Application Dictionary, but just some few considerations:
- You should follow the Naming guidelines for modules.
(Please note that this naming guidelines were established after the creation of the taxes module for Spain).
- The flag Has reference data must be set. Remember to write useful information inside the Reference Data Description field which will be read by the user when applying the dataset.
- Inside the Dependency tab, include the mandatory dependency to Core. In case your tax configuration make use of any region defined in an external region module, you must then register the dependency to that module too.
After the module's definition is ready, you should register the module in case you want to publish it in the Forge
Dataset definition is the key step in this process. A wrong dataset definition can waste all our previous work, so it is important to follow all these considerations:
- The dataset must belong to your taxes module
- Try to avoid strange characters in the dataset name. This string is used for generating the XML file name that stores the dataset.
- The Data Access Level must be set to Client/Organization, which means we allow users to apply the taxes configuration either at Client level (Organization *) or at Organization level.
- The Export allowed flag must be set.
- Inside the Table tab we must include all the tables to be included into the dataset. The list of tables that we must include is:
- C_BP_TaxCategory, which is associated with the Business Partner Tax Category window
- C_Tax, which is associated with the Tax Rate window
- C_Tax_Zone, which is associated with the Tax Zone of the Tax Rate window.
- C_TaxCategory, which is associated with the Tax Category window.
- The HQL/SQL Where clause is a very important field, because it allows us to filter the records we want to include into the dataset. If you remember, at the beginning of this document we proposed to create a new empty client called TAXES in which define our taxes configuration. In the example bellow we have used the client.name='TAXES' clause to get only the records that belong to this client, which are the only ones we want to distribute in our taxes module.
- Finally, if you want to include the translations into the taxes dataset, you should also create new records for the following tables:
- C_TaxCategory_Trl, for the translation of the Tax Category records
- C_Tax_Trl, for the translation of the Tax Rate records.
Note the way we can filter the records inside these tables by the language definition. Example: language in ('en_US', 'es_ES')
The dataset definition is ready, so we just need to export it to a file pressing the Export Reference Data button. This process queries the previous tables and gets all the records that fulfill the HQL/SQL Where clause, generating a XML file inside the module's referencedata/standard directory. As a fast check, you can open this file using any plain text editor and you can verify that it contains hundreds of lines.
In case the file is empty, you should double check the dataset definition, specially the HQL/SQL Where clause used for each table.
Testing the Dataset
The real test to ensure the taxes dataset is OK can be done inside our development instance. The test consists on creating a new client running the Initial Client Setup and selecting our taxes module dataset inside the Reference Data section.
If the data inside the dataset are consistent, the Initial Client Setup Process should be completed successfully, otherwise it will fail giving a description about the error.
After a successful Initial Client Setup, we just need to login into the new client, go to the Tax Rate, Tax Category and Business Partner Tax Category windows and check no records are missed for these windows and their tabs.
Packaging the Module
To generate the obx file we should follow the standard steps from the command line:
- Export the database:
- Package the module:
ant package.module -Dmodule=<taxes module java package>
- We can now publish the module in the Central Repository
Releasing new versions
Each time you want to release a new version of the taxes module, you must follow this steps:
- If not done before, create a new client called TAXES and apply the immediately previous taxes dataset version.
- Do the required stuff: update any record, create new ones, etc.
- Export the dataset again by pressing the Export Reference Data button inside the Dataset window.
- Increase the version of the taxes module
- Export the database running the command:
- Package the module:
ant package.module -Dmodule=<taxes module java package>
Considerations when releasing new versions of the Taxes module
During the life of the taxes module is very likely that you will need to release new versions which include some changes: bug fixing, new tax rates, etc. It is very important to ensure our users are able to successfully apply any taxes update.
Updating and Deleting records
When a user applies any dataset, the system remembers the records that belong to this dataset. In case a user applies an update for a dataset, the system will automatically inserts the new records included into the new version, and updates the existing ones in case it is necessary.
However, the system will not automatically delete a record belonging to a dataset even in the case this record is not included anymore into the new dataset version, unless the record is not reference by any other record in the system. This is the expected behaviour to avoid database inconsistencies.
Example: imagine we have included a tax rate into the first version of our dataset and in the second version we have deleted because it was wrong. If a user has previously applied the first dataset version and, for example, he has created an invoice line with this tax rate, the system will not delete the tax rate although it is not included anymore into the new dataset version.
In this kind of scenarios you should deactivate the record and inform the user about the existence of a wrong tax rate, for example through an Alert.
Each record inside a dataset has associated an unique identifier (UUID) that is used by the system to keep track of the record. The first time we apply a dataset, the UUIDs included into the dataset are directly stored into the database. However, if we apply two or more times the same dataset (for example for several clients or organizations in the same instance) the UUIDs stored into the database will be different from the ones defined in the dataset. This behaviour doesn't create any conflict for the user, but it can be a serious problem for an inexperienced developer.
Example: imagine we apply our taxes module to the TAXES client, and after that we apply it again to the TAXES2 client. If we export the dataset from the TAXES client, the dataset's and database's UUIDs are synchronized, however if we export it from the TAXES2 client the UUID synchronization will be broken and the system will store new UUIDs for the previous records inside the dataset. If a user tries to update to the new version generated from the TAXES2 client, he will end up having duplicated records instead of updating the existing ones.
So the golden rule to use when developing new versions of any dataset is to work always with the first applied data.