Here at Hibou, we love Payroll in Odoo. We loved it when we gave our first talk on it back at Odoo Experience 2018 (https://hibou.io/watch-our-talks) and demonstrated our US localization. We love it now in 2023 as we go to close the year out with a talk on Peru payroll at Odoo Experience and US payroll at Odoo Day.
Odoo loves Payroll, so much so that as of Odoo 13, payroll is not in the core offering and is part of Enterprise. For years we continued to support Odoo payroll, improving features and developing mechanisms to deliver payroll rate updates without code updates. The ability to do 'over the air' updates of payroll rates means that we can continue to support many versions of Odoo while only maintaining one 'source of truth' as far as the data is concerned.
The following is summary of the components of payroll that are interesting from a localization and implementation point of view.
What is Localization
In a nutshell, localization is the code and data that tailors a piece of software for a specific region or market. This pops up in obvious places like language translation, and is seen in the names of buttons for example. But it also has broad implications when it comes to things like taxes applying on sales orders, or what accounts are needed in a country to make it straightforward to actually pay the tax authorities.
For payroll, that means we need rules that calculate and segment things like what part of the wage is taxable, which amounts to withhold, and (importantly) how much the employee actually takes home.
Sometimes there are special pay periods or payslip runs like holiday or periodic bonuses that need to be considered for these rules. .
Categories
Categories are the underpinnings of calculations. Some rules, like GROSS and NET, just evaluate to sums of categories. In general, you will want to use the basic categories and only add categories when the sum of that category over a period of time is important to a computation.
Categories tend to be pretty static and not change over time, provided you can predict which categories you need from the start.
Salary rules ultimately have a category and contribute to the category's totals during the payslip calculation, that is the model:`hr.salary.rule.category`
Salary Rules
Salary Rules are the core of interacting with payroll, and thus are the main starting model for practically every amount you need to add onto a payslip. In fact, rules are evaluated and turn into payslip computation lines coded by the original rule.
You should think of them as a configurable function. You configure what order they run, what identifying code is used, and ultimately you configure how the calculation is made. There is in fact two 'calculations' going on, one for if the rule applies at all (and thus if you get a line on the payslip or not), and the calculation for the amount of the line itself.
You should think about if it makes sense for the line to be there even if the amount is zero. For example, if you have hundreds of rules for 50 different US states, you do NOT want hundreds of 'zero' lines on every payslip.
Calculations can be very simple like fixed amounts but, in general when localizing, you will likely need to use the dynamic evaluated python conditions and amounts.
The evaluated python code will have variables already available to you for doing things like summing amounts or categories from prior pay slips. Localizations practically always need to look at 'the current year' when computing amounts.
Salary Rules are mostly the same between community and enterprise.
Model:
`hr.salary.rule`
Rule Parameters
The salary rules above often need access to some 'external' rate or tables. You can think of this as the tax rate that may change from year to year. These parameters are essentially dated, meaning that the parameter has a specific value at a specific time. That time is usually the payslip's starting date, not the date/time you run the payslip, but the date ON the payslip.
Rule parameters are extremely important as they change over time!
Enterprise Model:
`hr.rule.parameter`
`hr.rule.parameter.value`
Community Model:
`base.time.parameter`
`base.time.parameter.version`
Salary Structures
Rules go into structures, and every payslip has a specific structure. This structure defines which rules will be used during the computation of the payslip's lines.
Structures can be used to provide for those special bonus or liquidation payslips, bringing their own set of rules and calculations along.
Model:
`hr.payroll.structure`
An interesting thing to note is that both Community and Enterprise have a model `hr.payroll.structure.type`, this model is not used by Community ... yet. This model is visible on employee contracts. In Enterprise, this filters which structures are allowed to be on a contract.
Contribution Register or Partner
One of the larger, though not all that large, differences between community and enterprise is how they deal with WHO should receive the amount the rule calculates.
Community has an object kind of "Contribution Register" which has a partner_id field that points to good old `res.partner`.
In general, in community you will make a contribution register and a partner. In enterprise, you're creating a partner.
Enterprise Model:
`res.partner`
Community Model:
`hr.contribution.register` + `res.partner`
Learnings
Is a single module possible? No. You will need to maintain two versions for each localization. (I tried!)
There are subtle differences in the variables available during rule execution, e.g. `payslip` vs `payslips` that must be understood to really support both. In general, you can accomplish the same results, but you may need to make some differences.