SlideShare a Scribd company logo
• Software Engineer, RD4HR Team Leader
EXPERIENCE
2019
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
● It has been largely improved
○ Functionally:
■ Easier structures definition
■ New dashboards
■ Less complexity (removed parent/children hierarchy)
■ New employee scheduler (Work Entries)
■ More tools for payroll management
■ New versioning mechanism
● It has been largely improved
○ Technically: The models have been refactored to:
■ Ease the accounting entries generation
■ Batch all the payslip computation and validation
■ Ease the structures definition
■ Allow easy methods inheritance for both rules
computation methods and evaluation context
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
● Attachment of salary
● Meal Vouchers Management
● Expense Reimbursement
● SEPA payment generation
● 281.10, 245.10, DMFA export
● Paid Time Off Allocation
● Company cars management
● Salary configurator
● Credit Time Management
● ...
l10n_be only
How to Create a l10n Payroll Structure
● Define the global parameters for the structures:
○ The scheduled pay (daily, monthly, …)
○ The default working hours (38h/week, …)
○ The wage type (Hours/month-based).
○ The default structure to use as regular pay
How to Create a l10n Payroll Structure
● Define some parameters to compute a payslip:
○ The rules to be computed on the payslip lines
○ The payslip name
○ Take the worked days into account or not
○ The unpaid work entry types
○ Other specific inputs
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
How to Create a l10n Payroll Structure
● Define some parameters to compute the payslip line:
○ The code
○ The name
○ The category
○ Displayed on the payslip or not
○ The applicability/amount rules
○ The journal on which to post the accounting entries
○ The contributor (ONSS, Fédéral State, …)
● Define the way a payslip line is computed:
○ A condition to apply it or not:
■ Always
■ Based on a field and a particular range (min/max)
■ Based on a python code
NB: Bold means that the element is evaluated in python (using
safe_eval). This will be explained later.
● Define the way a payslip line in computed:
○ The amount, based on:
■ A fixed amount
■ A fixed percentage based on a field
■ On a python code
○ The quantity
NB: Bold means that the element is evaluated in python (using
safe_eval). This will be explained later.
How to Create a l10n Payroll Structure
● Some parameters are evaluated with safe_eval
● The evaluation context ?
○ localdict
try:
safe_eval(
self.amount_python_compute or 0.0,
localdict, mode='exec', nocopy=True)
return float(
localdict['result']),
localdict.get('result_qty', 1.0),
localdict.get('result_rate', 100.0)
except Exception as e:
raise UserError(_('Wrong python code defined for salary rule %s (%s).nError: %s') %
(self.name, self.code, e))
localdict = {
**self._get_base_local_dict(),
**{
'categories': BrowsableObject(employee.id, {}, self.env),
'rules': BrowsableObject(employee.id, {}, self.env),
'payslip': Payslips(employee.id, self, self.env),
'worked_days': WorkedDays(employee.id, worked_days_dict, self.env),
'inputs': InputLine(employee.id, inputs_dict, self.env),
'employee': employee,
'contract': contract
},
**{
'result': None,
'result_qty': 1.0,
'result_rate': 100
}
}
1
3
2
def _get_base_local_dict(self):
return {
'float_round': float_round
}
● The first block is quite simple…
● It only contains a pointer to a classic Odoo method
● But can be overridden in your own module!
def _get_base_local_dict(self):
res = super()._get_base_local_dict()
res.update({
'compute_withholding_taxes': compute_withholding_taxes,
})
return res
def compute_withholding_taxes(payslip, categories, worked_days, inputs):
# Do smart stuff
return smart_amount
localdict = {
**self._get_base_local_dict(),
**{
'categories': BrowsableObject(employee.id, {}, self.env),
'rules': BrowsableObject(employee.id, {}, self.env),
'payslip': Payslips(employee.id, self, self.env),
'worked_days': WorkedDays(employee.id, worked_days_dict, self.env),
'inputs': InputLine(employee.id, inputs_dict, self.env),
'employee': employee,
'contract': contract
},
**{
'result': None,
'result_qty': 1.0,
'result_rate': 100
}
}
1
3
2
● BrowsableObject ? Vasistasse ?
● So ? It’s mostly the same thing than a browsed record or a
dictionary ?
class BrowsableObject(object):
def __init__(self, employee_id, dict, env):
self.employee_id = employee_id
self.dict = dict
self.env = env
def __getattr__(self, attr):
return attr in self.dict and self.dict.__getitem__(attr) or 0.0
● So ? It’s mostly the same thing than a browsed record ?
○ NO ! Because we can define more local information on it !
○ Example: categories is initially empty.
'categories': BrowsableObject(employee.id, {}, self.env)
● So ? It’s mostly the same thing than a browsed record ?
○ But on each computed payslip line, the sum for each rule
category is recomputed
def _sum_salary_rule_category(localdict, category, amount):
if category.parent_id:
localdict = _sum_salary_rule_category(
localdict,
category.parent_id,
amount)
old_value = localdict['categories'].dict.get(category.code, 0)
localdict['categories'].dict[category.code] = old_value + amount
return localdict
● So ? It’s mostly the same thing than a browsed record ?
○ Then, in a salary rule, you can use a shortcut like:
○ In that case, you get the sum of all the payslip lines amount
that belong to the categories ‘BASIC’ and ‘ALW’.
result = categories.BASIC + categories.ALW
● Same for rules, which is also initially empty
● But, is updated each time a payslip line is computed
● You can then access to all the evaluated rules
'rules': BrowsableObject(employee.id, rules_dict, self.env)
rules_dict[rule.code] = rule
result = 20 if 'CHILD' in rules else 10
● For payslip, it’s a little more complicated
● You could do something like:
class Payslips(BrowsableObject):
def sum(self, code, from_date, to_date=None):
self.env.cr.execute("""SMART SQL QUERY""")
res = self.env.cr.fetchone()
return res and res[0] or 0.0
def sum_category(self, code, from_date, to_date=None):
self.env.cr.execute("""SMART SQL QUERY""")
res = self.env.cr.fetchone()
return res and res[0] or 0.0
result = payslip.sum('NET', '2018-01-01', '2018-12-31')
● For payslip, it’s a little more complicated
class Payslips(BrowsableObject):
def rule_parameter(self, code):
return self.env['hr.rule.parameter']._get_parameter_from_code(
code, self.dict.date_to)
● What is a hr.rule.parameter ?
○ It’s a constant used in a salary rule, that may have different
values over the time. (Example: the Child Allowances)
result = categories.BRUT <= payslip.rule_parameter('work_bonus_reference_wage_high')
● What is a hr.rule.parameter ?
○ So, we could retrieve the current value for a given parameter,
for example in a applicability condition like the following:
● For payslip, it’s a little more complicated
● By default, we retrieve the basic salary (wage - unpaid amount) like
this:
class Payslips(BrowsableObject):
@property
def paid_amount(self):
return self.dict._get_paid_amount()
result = payslip.paid_amount
● worked_days and inputs also are BrowsableObjects, with sum and
sum_hours methods, as for payslip
● They are defined as:
'worked_days': WorkedDays(employee.id, worked_days_dict, self.env),
'inputs': InputLine(employee.id, inputs_dict, self.env),
worked_days_dict = {line.code: line for line in self.worked_days_line_ids if line.code}
inputs_dict = {line.code: line for line in self.input_line_ids if line.code}
● It is thus possible to retrieve the number of worked days on a
payslip like:
● Or retrieve the value for an additional payslip line from an input like:
worked_days.WORK100.number_of_days
result = inputs.YEAREND_BONUS.amount if inputs.YEAREND_BONUS else 0
● employee and contract are simple browsed records, and can be
used in a classical way
result = 100 if employee.children == 1 else 200
result = contract.car_atn
localdict = {
**self._get_base_local_dict(),
**{
'categories': BrowsableObject(employee.id, {}, self.env),
'rules': BrowsableObject(employee.id, {}, self.env),
'payslip': Payslips(employee.id, self, self.env),
'worked_days': WorkedDays(employee.id, worked_days_dict, self.env),
'inputs': InputLine(employee.id, inputs_dict, self.env),
'employee': employee,
'contract': contract
},
**{
'result': None,
'result_qty': 1.0,
'result_rate': 100
}
}
1
3
2
● You probably noticed, that we often set a variable result on the
rules computation formulae
● In fact, for each payslip line, the default value is:
● The python rule can set all those 3 variables for a payslip line:
{
'result': None,
'result_qty': 1.0,
'result_rate': 100
}
result = categories.TERMINAISON_DOUBLE
result_rate = -13.07
result_qty = 6.8/7.67
How to Create a l10n Payroll Structure
<record id="structure_type_employee_cp200" model="hr.payroll.structure.type">
<field name="name">CP200: Belgian Employee</field>
<field name="default_resource_calendar_id" ref="resource.resource_calendar_std_38h"/>
<field name="country_id" ref="base.be"/>
</record>
<record id="hr_payroll_structure_cp200_employee_salary" model="hr.payroll.structure">
<field name="name">CP200: Employees Monthly Pay</field>
<field name="country_id" ref="base.be"/>
<field name="type_id" ref="l10n_be_hr_payroll.structure_type_employee_cp200"/>
<field name="regular_pay" eval="True"/>
<field name="unpaid_work_entry_type_ids" eval="[
(4, ref('hr_payroll.work_entry_type_unpaid_leave')),
(4, ref('l10n_be_hr_payroll.work_entry_type_unpredictable')),
(4, ref('l10n_be_hr_payroll.work_entry_type_long_sick')),
(4, ref('l10n_be_hr_payroll.work_entry_type_part_sick')),
(4, ref('l10n_be_hr_payroll.work_entry_type_maternity')),
(4, ref('l10n_be_hr_payroll.work_entry_type_breast_feeding')),
]"/>
</record>
<record id="cp200_employees_salary_atn_internet" model="hr.salary.rule">
<field name="category_id" ref="hr_payroll_head_div_impos"/>
<field name="name">ATN Internet</field>
<field name="code">ATN.INT</field>
<field name="sequence">16</field>
<field name="condition_select">python</field>
<field name="condition_python">result = bool(contract.internet)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = 5.0</field>
<field name="struct_id" ref="hr_payroll_structure_cp200_employee_salary"/>
</record>
<record id="cp200_employees_salary_mis_ex_onss" model="hr.salary.rule">
<field name="category_id" ref="hr_salary_rule_category_spec_soc_contribution"/>
<field name="name">Special social cotisation</field>
<field name="code">M.ONSS</field>
<field name="amount_select">fix</field>
<field name="sequence">165</field>
<field name="appears_on_payslip" eval="False"/>
<field name="amount_select">code</field>
<field name="amount_python_compute">result =
compute_special_social_cotisations(payslip, categories, worked_days, inputs)</field>
<field name="struct_id" ref="hr_payroll_structure_cp200_employee_salary"/>
</record>
● Be careful! By default, a structure has already 3 default rules.
○ BASIC
{
'name': 'Basic Salary',
'sequence': 1,
'code': 'BASIC',
'category_id': self.env.ref('hr_payroll.BASIC').id,
'condition_select': 'none',
'amount_select': 'code',
'amount_python_compute': 'result = payslip.paid_amount',
}
● Be careful! By default, a structure has already 3 default rules.
○ GROSS
{
'name': 'Gross',
'sequence': 100,
'code': 'GROSS',
'category_id': self.env.ref('hr_payroll.GROSS').id,
'condition_select': 'none',
'amount_select': 'code',
'amount_python_compute': 'result = categories.BASIC + categories.ALW',
}
● Be careful! By default, a structure has already 3 default rules.
○ NET
{
'name': 'Net Salary',
'sequence': 200,
'code': 'NET',
'category_id': self.env.ref('hr_payroll.NET').id,
'condition_select': 'none',
'amount_select': 'code',
'amount_python_compute': 'result = categories.BASIC + categories.ALW + categories.DED',
}
● Don’t recreate those lines, override them ;)
<function model="hr.salary.rule" name="write">
<value model="hr.salary.rule" search="[
('struct_id', '=', ref('l10n_be_hr_payroll.hr_payroll_structure_cp200_double_holiday')),
('code', '=', 'NET')]"/>
<value eval="{'amount_python_compute': 'result = categories.DP + categories.ALW +
categories.DED', 'sequence': '201'}"/>
</function>
How to Create a l10n Payroll Structure
● Benefits : Your module is under our responsibility concerning the
maintenance + less customizations for your customers.
● Contact yti@odoo.com, and open a PR with your modifications.
● It should be included in a l10_CODE_hr_payroll module.
● Try to be generic (Only data for the salary structures, or new
computation methods)
● You may propose new features as well (reports, ...), that will be
validated internally with the product owner.
⇒ THANK YOU FOR YOUR HELP !
#odooexperience
https://github.com/tivisse/
EXPERIENCE
2019

More Related Content

PPTX
Building l10n Payroll Structures from the Ground up
PDF
결제를 구현하고 싶은 개발팀을 위한 안내서
PPTX
Empower your App by Inheriting from Odoo Mixins
PDF
Impact of the New ORM on Your Modules
PPTX
Odoo ORM Methods | Object Relational Mapping in Odoo15
PDF
Ky thuat Lap trinh voi Python - Tran Duy Thanh
PDF
Odoo - Smart buttons
PDF
Tools for Solving Performance Issues
Building l10n Payroll Structures from the Ground up
결제를 구현하고 싶은 개발팀을 위한 안내서
Empower your App by Inheriting from Odoo Mixins
Impact of the New ORM on Your Modules
Odoo ORM Methods | Object Relational Mapping in Odoo15
Ky thuat Lap trinh voi Python - Tran Duy Thanh
Odoo - Smart buttons
Tools for Solving Performance Issues

What's hot (20)

PDF
Odoo - CMS dynamic widgets
PPTX
Lot or Serial Number Configuration in Odoo 15
PPTX
What is Computed Fields and @api Depends in Odoo 15
DOC
Oracle ebs otl setup document
PPTX
How to Build Custom Module in Odoo 15
PPTX
HOW TO CREATE A MODULE IN ODOO
DOCX
Personalization Validate Po Quantity With PR
PPTX
What is Delegation Inheritance in Odoo 15
PPTX
Basic Views in Odoo 16
PPTX
Tutorial: Develop an App with the Odoo Framework
DOCX
Oracle EBS Tracking items costing return from customer
PPTX
How to Define One2Many Field in Odoo 15
PDF
애플리케이션 아키텍처와 객체지향
PDF
Odoo Functional Training
PPTX
Common Performance Pitfalls in Odoo apps
PDF
Query Pre Payment details Oracle Fusion Cloud
PDF
Oracle Cloud SQL FTE Positions Query
PDF
Asynchronous JS in Odoo
PPTX
Functional i store overview knoworacle
PDF
New Framework - ORM
Odoo - CMS dynamic widgets
Lot or Serial Number Configuration in Odoo 15
What is Computed Fields and @api Depends in Odoo 15
Oracle ebs otl setup document
How to Build Custom Module in Odoo 15
HOW TO CREATE A MODULE IN ODOO
Personalization Validate Po Quantity With PR
What is Delegation Inheritance in Odoo 15
Basic Views in Odoo 16
Tutorial: Develop an App with the Odoo Framework
Oracle EBS Tracking items costing return from customer
How to Define One2Many Field in Odoo 15
애플리케이션 아키텍처와 객체지향
Odoo Functional Training
Common Performance Pitfalls in Odoo apps
Query Pre Payment details Oracle Fusion Cloud
Oracle Cloud SQL FTE Positions Query
Asynchronous JS in Odoo
Functional i store overview knoworacle
New Framework - ORM
Ad

Similar to How to Create a l10n Payroll Structure (20)

DOCX
MSCD650 Final Exam feedback FormMSCD650 Final Exam Grading For.docx
PDF
I need help to modify my code according to the instructions- Modify th.pdf
PPT
lec_4_data_structures_and_algorithm_analysis.ppt
PPT
lec_4_data_structures_and_algorithm_analysis.ppt
PDF
ADBMS ASSIGNMENT
PPT
Java: Class Design Examples
PPTX
Workforce Plus: Tips and Tricks to Give Workforce an Extra Kick!
ODP
Hybrid rule engines (rulesfest 2010)
PPTX
Exploring Advanced SQL Techniques Using Analytic Functions
PPTX
Exploring Advanced SQL Techniques Using Analytic Functions
PDF
Accelerating OpenERP accounting: Precalculated period sums
PPT
Payroll process oracle hrms
PDF
Refactoring
PPTX
Custom Star Creation for Ellucain's Enterprise Data Warehouse
PDF
In memory OLAP engine
PPTX
Intro To C++ - Cass 11 - Converting between types, formatting floating point,...
PPTX
Intro To C++ - Class 11 - Converting between types, formatting floating point...
DOCX
Accrual plan set up in oracle hrms
PPTX
POLITEKNIK MALAYSIA
DOCX
33.docxSTEP 1 Understand the UML Diagram Analyze and under.docx
MSCD650 Final Exam feedback FormMSCD650 Final Exam Grading For.docx
I need help to modify my code according to the instructions- Modify th.pdf
lec_4_data_structures_and_algorithm_analysis.ppt
lec_4_data_structures_and_algorithm_analysis.ppt
ADBMS ASSIGNMENT
Java: Class Design Examples
Workforce Plus: Tips and Tricks to Give Workforce an Extra Kick!
Hybrid rule engines (rulesfest 2010)
Exploring Advanced SQL Techniques Using Analytic Functions
Exploring Advanced SQL Techniques Using Analytic Functions
Accelerating OpenERP accounting: Precalculated period sums
Payroll process oracle hrms
Refactoring
Custom Star Creation for Ellucain's Enterprise Data Warehouse
In memory OLAP engine
Intro To C++ - Cass 11 - Converting between types, formatting floating point,...
Intro To C++ - Class 11 - Converting between types, formatting floating point...
Accrual plan set up in oracle hrms
POLITEKNIK MALAYSIA
33.docxSTEP 1 Understand the UML Diagram Analyze and under.docx
Ad

More from Odoo (20)

PPTX
Timesheet Workshop: The Timesheet App People Love!
PPTX
Odoo 3D Product View with Google Model-Viewer
PPTX
Keynote - Vision & Strategy
PPTX
Opening Keynote - Unveilling Odoo 14
PDF
Extending Odoo with a Comprehensive Budgeting and Forecasting Capability
PDF
Managing Multi-channel Selling with Odoo
PPTX
Product Configurator: Advanced Use Case
PDF
Accounting Automation: How Much Money We Saved and How?
PPTX
Rock Your Logistics with Advanced Operations
PPTX
Transition from a cost to a flow-centric organization
PDF
Synchronization: The Supply Chain Response to Overcome the Crisis
PPTX
Running a University with Odoo
PPTX
Down Payments on Purchase Orders in Odoo
PPTX
Odoo Implementation in Phases - Success Story of a Retail Chain 3Sach food
PPTX
Migration from Salesforce to Odoo
PPTX
Preventing User Mistakes by Using Machine Learning
PPTX
Becoming an Odoo Expert: How to Prepare for the Certification
PPTX
Instant Printing of any Odoo Report or Shipping Label
PPTX
How Odoo helped an Organization Grow 3 Fold
PPTX
From Shopify to Odoo
Timesheet Workshop: The Timesheet App People Love!
Odoo 3D Product View with Google Model-Viewer
Keynote - Vision & Strategy
Opening Keynote - Unveilling Odoo 14
Extending Odoo with a Comprehensive Budgeting and Forecasting Capability
Managing Multi-channel Selling with Odoo
Product Configurator: Advanced Use Case
Accounting Automation: How Much Money We Saved and How?
Rock Your Logistics with Advanced Operations
Transition from a cost to a flow-centric organization
Synchronization: The Supply Chain Response to Overcome the Crisis
Running a University with Odoo
Down Payments on Purchase Orders in Odoo
Odoo Implementation in Phases - Success Story of a Retail Chain 3Sach food
Migration from Salesforce to Odoo
Preventing User Mistakes by Using Machine Learning
Becoming an Odoo Expert: How to Prepare for the Certification
Instant Printing of any Odoo Report or Shipping Label
How Odoo helped an Organization Grow 3 Fold
From Shopify to Odoo

Recently uploaded (20)

DOCX
Business Management - unit 1 and 2
PPT
Chapter four Project-Preparation material
PDF
20250805_A. Stotz All Weather Strategy - Performance review July 2025.pdf
PPTX
AI-assistance in Knowledge Collection and Curation supporting Safe and Sustai...
PDF
Business model innovation report 2022.pdf
PDF
Nidhal Samdaie CV - International Business Consultant
PDF
Laughter Yoga Basic Learning Workshop Manual
PPTX
HR Introduction Slide (1).pptx on hr intro
PDF
Deliverable file - Regulatory guideline analysis.pdf
PDF
kom-180-proposal-for-a-directive-amending-directive-2014-45-eu-and-directive-...
PPTX
Business Ethics - An introduction and its overview.pptx
PDF
Roadmap Map-digital Banking feature MB,IB,AB
PPTX
New Microsoft PowerPoint Presentation - Copy.pptx
PDF
DOC-20250806-WA0002._20250806_112011_0000.pdf
PDF
Elevate Cleaning Efficiency Using Tallfly Hair Remover Roller Factory Expertise
PPTX
ICG2025_ICG 6th steering committee 30-8-24.pptx
PPTX
Amazon (Business Studies) management studies
PDF
WRN_Investor_Presentation_August 2025.pdf
PDF
COST SHEET- Tender and Quotation unit 2.pdf
PDF
IFRS Notes in your pocket for study all the time
Business Management - unit 1 and 2
Chapter four Project-Preparation material
20250805_A. Stotz All Weather Strategy - Performance review July 2025.pdf
AI-assistance in Knowledge Collection and Curation supporting Safe and Sustai...
Business model innovation report 2022.pdf
Nidhal Samdaie CV - International Business Consultant
Laughter Yoga Basic Learning Workshop Manual
HR Introduction Slide (1).pptx on hr intro
Deliverable file - Regulatory guideline analysis.pdf
kom-180-proposal-for-a-directive-amending-directive-2014-45-eu-and-directive-...
Business Ethics - An introduction and its overview.pptx
Roadmap Map-digital Banking feature MB,IB,AB
New Microsoft PowerPoint Presentation - Copy.pptx
DOC-20250806-WA0002._20250806_112011_0000.pdf
Elevate Cleaning Efficiency Using Tallfly Hair Remover Roller Factory Expertise
ICG2025_ICG 6th steering committee 30-8-24.pptx
Amazon (Business Studies) management studies
WRN_Investor_Presentation_August 2025.pdf
COST SHEET- Tender and Quotation unit 2.pdf
IFRS Notes in your pocket for study all the time

How to Create a l10n Payroll Structure

  • 1. • Software Engineer, RD4HR Team Leader EXPERIENCE 2019
  • 4. ● It has been largely improved ○ Functionally: ■ Easier structures definition ■ New dashboards ■ Less complexity (removed parent/children hierarchy) ■ New employee scheduler (Work Entries) ■ More tools for payroll management ■ New versioning mechanism
  • 5. ● It has been largely improved ○ Technically: The models have been refactored to: ■ Ease the accounting entries generation ■ Batch all the payslip computation and validation ■ Ease the structures definition ■ Allow easy methods inheritance for both rules computation methods and evaluation context
  • 17. ● Attachment of salary ● Meal Vouchers Management ● Expense Reimbursement ● SEPA payment generation ● 281.10, 245.10, DMFA export ● Paid Time Off Allocation ● Company cars management ● Salary configurator ● Credit Time Management ● ... l10n_be only
  • 19. ● Define the global parameters for the structures: ○ The scheduled pay (daily, monthly, …) ○ The default working hours (38h/week, …) ○ The wage type (Hours/month-based). ○ The default structure to use as regular pay
  • 21. ● Define some parameters to compute a payslip: ○ The rules to be computed on the payslip lines ○ The payslip name ○ Take the worked days into account or not ○ The unpaid work entry types ○ Other specific inputs
  • 25. ● Define some parameters to compute the payslip line: ○ The code ○ The name ○ The category ○ Displayed on the payslip or not ○ The applicability/amount rules ○ The journal on which to post the accounting entries ○ The contributor (ONSS, Fédéral State, …)
  • 26. ● Define the way a payslip line is computed: ○ A condition to apply it or not: ■ Always ■ Based on a field and a particular range (min/max) ■ Based on a python code NB: Bold means that the element is evaluated in python (using safe_eval). This will be explained later.
  • 27. ● Define the way a payslip line in computed: ○ The amount, based on: ■ A fixed amount ■ A fixed percentage based on a field ■ On a python code ○ The quantity NB: Bold means that the element is evaluated in python (using safe_eval). This will be explained later.
  • 29. ● Some parameters are evaluated with safe_eval ● The evaluation context ? ○ localdict try: safe_eval( self.amount_python_compute or 0.0, localdict, mode='exec', nocopy=True) return float( localdict['result']), localdict.get('result_qty', 1.0), localdict.get('result_rate', 100.0) except Exception as e: raise UserError(_('Wrong python code defined for salary rule %s (%s).nError: %s') % (self.name, self.code, e))
  • 30. localdict = { **self._get_base_local_dict(), **{ 'categories': BrowsableObject(employee.id, {}, self.env), 'rules': BrowsableObject(employee.id, {}, self.env), 'payslip': Payslips(employee.id, self, self.env), 'worked_days': WorkedDays(employee.id, worked_days_dict, self.env), 'inputs': InputLine(employee.id, inputs_dict, self.env), 'employee': employee, 'contract': contract }, **{ 'result': None, 'result_qty': 1.0, 'result_rate': 100 } } 1 3 2
  • 31. def _get_base_local_dict(self): return { 'float_round': float_round } ● The first block is quite simple… ● It only contains a pointer to a classic Odoo method
  • 32. ● But can be overridden in your own module! def _get_base_local_dict(self): res = super()._get_base_local_dict() res.update({ 'compute_withholding_taxes': compute_withholding_taxes, }) return res def compute_withholding_taxes(payslip, categories, worked_days, inputs): # Do smart stuff return smart_amount
  • 33. localdict = { **self._get_base_local_dict(), **{ 'categories': BrowsableObject(employee.id, {}, self.env), 'rules': BrowsableObject(employee.id, {}, self.env), 'payslip': Payslips(employee.id, self, self.env), 'worked_days': WorkedDays(employee.id, worked_days_dict, self.env), 'inputs': InputLine(employee.id, inputs_dict, self.env), 'employee': employee, 'contract': contract }, **{ 'result': None, 'result_qty': 1.0, 'result_rate': 100 } } 1 3 2
  • 34. ● BrowsableObject ? Vasistasse ? ● So ? It’s mostly the same thing than a browsed record or a dictionary ? class BrowsableObject(object): def __init__(self, employee_id, dict, env): self.employee_id = employee_id self.dict = dict self.env = env def __getattr__(self, attr): return attr in self.dict and self.dict.__getitem__(attr) or 0.0
  • 35. ● So ? It’s mostly the same thing than a browsed record ? ○ NO ! Because we can define more local information on it ! ○ Example: categories is initially empty. 'categories': BrowsableObject(employee.id, {}, self.env)
  • 36. ● So ? It’s mostly the same thing than a browsed record ? ○ But on each computed payslip line, the sum for each rule category is recomputed def _sum_salary_rule_category(localdict, category, amount): if category.parent_id: localdict = _sum_salary_rule_category( localdict, category.parent_id, amount) old_value = localdict['categories'].dict.get(category.code, 0) localdict['categories'].dict[category.code] = old_value + amount return localdict
  • 37. ● So ? It’s mostly the same thing than a browsed record ? ○ Then, in a salary rule, you can use a shortcut like: ○ In that case, you get the sum of all the payslip lines amount that belong to the categories ‘BASIC’ and ‘ALW’. result = categories.BASIC + categories.ALW
  • 38. ● Same for rules, which is also initially empty ● But, is updated each time a payslip line is computed ● You can then access to all the evaluated rules 'rules': BrowsableObject(employee.id, rules_dict, self.env) rules_dict[rule.code] = rule result = 20 if 'CHILD' in rules else 10
  • 39. ● For payslip, it’s a little more complicated ● You could do something like: class Payslips(BrowsableObject): def sum(self, code, from_date, to_date=None): self.env.cr.execute("""SMART SQL QUERY""") res = self.env.cr.fetchone() return res and res[0] or 0.0 def sum_category(self, code, from_date, to_date=None): self.env.cr.execute("""SMART SQL QUERY""") res = self.env.cr.fetchone() return res and res[0] or 0.0 result = payslip.sum('NET', '2018-01-01', '2018-12-31')
  • 40. ● For payslip, it’s a little more complicated class Payslips(BrowsableObject): def rule_parameter(self, code): return self.env['hr.rule.parameter']._get_parameter_from_code( code, self.dict.date_to)
  • 41. ● What is a hr.rule.parameter ? ○ It’s a constant used in a salary rule, that may have different values over the time. (Example: the Child Allowances)
  • 42. result = categories.BRUT <= payslip.rule_parameter('work_bonus_reference_wage_high') ● What is a hr.rule.parameter ? ○ So, we could retrieve the current value for a given parameter, for example in a applicability condition like the following:
  • 43. ● For payslip, it’s a little more complicated ● By default, we retrieve the basic salary (wage - unpaid amount) like this: class Payslips(BrowsableObject): @property def paid_amount(self): return self.dict._get_paid_amount() result = payslip.paid_amount
  • 44. ● worked_days and inputs also are BrowsableObjects, with sum and sum_hours methods, as for payslip ● They are defined as: 'worked_days': WorkedDays(employee.id, worked_days_dict, self.env), 'inputs': InputLine(employee.id, inputs_dict, self.env), worked_days_dict = {line.code: line for line in self.worked_days_line_ids if line.code} inputs_dict = {line.code: line for line in self.input_line_ids if line.code}
  • 45. ● It is thus possible to retrieve the number of worked days on a payslip like: ● Or retrieve the value for an additional payslip line from an input like: worked_days.WORK100.number_of_days result = inputs.YEAREND_BONUS.amount if inputs.YEAREND_BONUS else 0
  • 46. ● employee and contract are simple browsed records, and can be used in a classical way result = 100 if employee.children == 1 else 200 result = contract.car_atn
  • 47. localdict = { **self._get_base_local_dict(), **{ 'categories': BrowsableObject(employee.id, {}, self.env), 'rules': BrowsableObject(employee.id, {}, self.env), 'payslip': Payslips(employee.id, self, self.env), 'worked_days': WorkedDays(employee.id, worked_days_dict, self.env), 'inputs': InputLine(employee.id, inputs_dict, self.env), 'employee': employee, 'contract': contract }, **{ 'result': None, 'result_qty': 1.0, 'result_rate': 100 } } 1 3 2
  • 48. ● You probably noticed, that we often set a variable result on the rules computation formulae ● In fact, for each payslip line, the default value is: ● The python rule can set all those 3 variables for a payslip line: { 'result': None, 'result_qty': 1.0, 'result_rate': 100 } result = categories.TERMINAISON_DOUBLE result_rate = -13.07 result_qty = 6.8/7.67
  • 50. <record id="structure_type_employee_cp200" model="hr.payroll.structure.type"> <field name="name">CP200: Belgian Employee</field> <field name="default_resource_calendar_id" ref="resource.resource_calendar_std_38h"/> <field name="country_id" ref="base.be"/> </record>
  • 51. <record id="hr_payroll_structure_cp200_employee_salary" model="hr.payroll.structure"> <field name="name">CP200: Employees Monthly Pay</field> <field name="country_id" ref="base.be"/> <field name="type_id" ref="l10n_be_hr_payroll.structure_type_employee_cp200"/> <field name="regular_pay" eval="True"/> <field name="unpaid_work_entry_type_ids" eval="[ (4, ref('hr_payroll.work_entry_type_unpaid_leave')), (4, ref('l10n_be_hr_payroll.work_entry_type_unpredictable')), (4, ref('l10n_be_hr_payroll.work_entry_type_long_sick')), (4, ref('l10n_be_hr_payroll.work_entry_type_part_sick')), (4, ref('l10n_be_hr_payroll.work_entry_type_maternity')), (4, ref('l10n_be_hr_payroll.work_entry_type_breast_feeding')), ]"/> </record>
  • 52. <record id="cp200_employees_salary_atn_internet" model="hr.salary.rule"> <field name="category_id" ref="hr_payroll_head_div_impos"/> <field name="name">ATN Internet</field> <field name="code">ATN.INT</field> <field name="sequence">16</field> <field name="condition_select">python</field> <field name="condition_python">result = bool(contract.internet)</field> <field name="amount_select">code</field> <field name="amount_python_compute">result = 5.0</field> <field name="struct_id" ref="hr_payroll_structure_cp200_employee_salary"/> </record>
  • 53. <record id="cp200_employees_salary_mis_ex_onss" model="hr.salary.rule"> <field name="category_id" ref="hr_salary_rule_category_spec_soc_contribution"/> <field name="name">Special social cotisation</field> <field name="code">M.ONSS</field> <field name="amount_select">fix</field> <field name="sequence">165</field> <field name="appears_on_payslip" eval="False"/> <field name="amount_select">code</field> <field name="amount_python_compute">result = compute_special_social_cotisations(payslip, categories, worked_days, inputs)</field> <field name="struct_id" ref="hr_payroll_structure_cp200_employee_salary"/> </record>
  • 54. ● Be careful! By default, a structure has already 3 default rules. ○ BASIC { 'name': 'Basic Salary', 'sequence': 1, 'code': 'BASIC', 'category_id': self.env.ref('hr_payroll.BASIC').id, 'condition_select': 'none', 'amount_select': 'code', 'amount_python_compute': 'result = payslip.paid_amount', }
  • 55. ● Be careful! By default, a structure has already 3 default rules. ○ GROSS { 'name': 'Gross', 'sequence': 100, 'code': 'GROSS', 'category_id': self.env.ref('hr_payroll.GROSS').id, 'condition_select': 'none', 'amount_select': 'code', 'amount_python_compute': 'result = categories.BASIC + categories.ALW', }
  • 56. ● Be careful! By default, a structure has already 3 default rules. ○ NET { 'name': 'Net Salary', 'sequence': 200, 'code': 'NET', 'category_id': self.env.ref('hr_payroll.NET').id, 'condition_select': 'none', 'amount_select': 'code', 'amount_python_compute': 'result = categories.BASIC + categories.ALW + categories.DED', }
  • 57. ● Don’t recreate those lines, override them ;) <function model="hr.salary.rule" name="write"> <value model="hr.salary.rule" search="[ ('struct_id', '=', ref('l10n_be_hr_payroll.hr_payroll_structure_cp200_double_holiday')), ('code', '=', 'NET')]"/> <value eval="{'amount_python_compute': 'result = categories.DP + categories.ALW + categories.DED', 'sequence': '201'}"/> </function>
  • 59. ● Benefits : Your module is under our responsibility concerning the maintenance + less customizations for your customers. ● Contact yti@odoo.com, and open a PR with your modifications. ● It should be included in a l10_CODE_hr_payroll module. ● Try to be generic (Only data for the salary structures, or new computation methods) ● You may propose new features as well (reports, ...), that will be validated internally with the product owner. ⇒ THANK YOU FOR YOUR HELP !