From b63128099991358640efb91bf985e25e9d0d72b0 Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Thu, 7 Mar 2019 16:43:04 +0100 Subject: [PATCH] [REF] hr_payroll: Remove parent_id hierarchy + Add structures categorization 1/ Remove the parent-children relation on salary structures Currently it is difficult to see what rules are applied on salary structures. Most of the structures are inheriting 3 rules (Basic, Gross, Net) from the 'Base for new structures', and those rules are not shown. Same issue if a structure has another one as parent (as in the French localization). This bring more annoying issues: - Since the saas-12.1, we can already set different accounts for different companies on the salary rules for which we want to post journal entries. But for the parent rules, like the 'NET' amount, it's impossible to post the journal entries on different account for different salary structures. (eg: for CP200 employees and CP200 workers). As the rules are following a M2M relation on the structure, we have to remove the parent_id and to recreate all the rules in the final structure. - If we are not at ease with the M2M relation (which is the case for most of the end users), we will simply modify the rule on the parent structure. This was already done on the French localization on which we modified the code, sequence and the name. This could have some desastrous consequences as this will break the order on which the rules are computed for other structures for the sequence, or break other integration with the payroll as we also modify the code, for example the salary configurator. As this parent-children relation on structure could cause several issues difficult to spot and as this is quite difficult to correctly visualize the structure composition, it is better to remove it and define all the rules inside the structure itself. This improves also the code readability as we are not trying to recursively retrieve the rules and find a proper sequence inside them. 2/ Make the rules on the salary structure a o2m instead of a m2m The issues described just above indicates that it would be better to remove the M2M relation between salary structures and salary rules, and make it a O2M relation instead. That way, no more confusion and misconfiguration of the rules fields (code, sequence, accounts, ...). 3/ Remove the parent-children relation on rules A salary rule can have a parent salary rule. This mechanism, which is not obvious at all for an end user allows to specify a parent rules. If the condition is met for the parent rule (eg: the employee has at least 1 child), all the children rules could be applied (eg: Deduction of 90 euros if the number of children is between 1 and 1). Visually it doesn't make a lot of sense, as it displays one line with an amount of 0 for the parent, and one line with the real amount for each applied child. On the other hand, no child rule is linked to the structure and an end user has to click on the parent rule, to see the children and understand that something mystic and hidden is existing. This is equivalent to define as many classic rules as we have children, or even better, define 1 rule that will compute the real amount directly, as the number of children can be accessed from the localdict when computing the salary line. This commit also removed the relation. 4/ Clean some brols in the code (technical) The 3 first points of the spec allow us to clean a little the code readability and complexity. 5/ Adapt all the localizations for these changes All the localizations are impacted by those changes. What is done in this commit is mainly: - l10n_be_hr_payroll: Make 3 separated files for the 3 existing structures (the fourth one, belgian worker, is not correct and is removed). For each structure, define explicitely the rules (in the correct order by sequence). This multiplies the total number of rules as some of them were shared (like the withholding tax rule for example), but all the code that is supposed to be modified each year/quarter/whenever the government decides to modify the law has been moved into a python file, and is available the computation context. This will also allow us to update the computation rules without having to update the module. - l10n_fr_hr_payroll: There were 3 rules following this scheme: Base for new structure | Basic structure | ---------------------- | | Cadre Non-Cadre All the rules parent rules have been duplicated into the 2 remaining structures, cadre and non-cadre. There were a lot of parent-children relations between the rules that have been adapted to the new model. - l10n_in_hr_payroll: There was a lot of rules in data, and a structure in demo data, which was using 4 or 5 of these rules. As the rules doesn't seem to make a lot of sense altogether, everything was moved in demo data. 6/ Introduce a new Salary Structure Type model Currently we have a model hr.contract.type with a M2O on the contracts. This has never been used in 9 years and is removed in this commit. On the other hand, we would like to define a new model hr.payroll.structure.type (eg: CP200 Employee), which will line all the salary structures the Belgian localization could bring (Classic salary, double holidays, 13th month, ...). This field is defined on the structure, and is displayed on the contract (as the same place than the contract type we just removed). On this model we could also define some fields like: - The default pay period: Selection field, default = monthly. This is applied on the contract when selecting the structure. - The Working Schedule, m2o. This is applied on the contract when selecting the structure For example, on the 'commission paritaire' for teachers, we could set the default pay period to 'Every 15 days' and the working schedule to '19 hours / week'. Every contract under this structure would take those values. Migration ========= For a migration point of view: - All the rules that had a parent_rule_id should be set to appears_on_payslip=False. The column parent_rule_id can be dropped afterward. - All the rules that are coming from a parent structure or higher should be duplicated and the field 'struct_id' should be set on the current structure. The column parent_id can be dropped afterward. - All the rules that were defined on the structure should have the field 'struct_id' defined on them. - The rules that are not linked to a structure should be unlinked. - The column type_id on the contract could be dropped, no need to keep the values too. - A default structure type is defined in data ('Employee'). This could be set to all the existing structure for the newly created 'type_id' field. TaskID: 1942832 --- addons/hr_contract/data/hr_contract_data.xml | 16 - addons/hr_contract/data/hr_contract_demo.xml | 13 - addons/hr_contract/models/hr_contract.py | 12 - .../hr_contract/security/ir.model.access.csv | 1 - .../hr_contract/views/hr_contract_views.xml | 55 - addons/hr_payroll/data/hr_payroll_data.xml | 44 +- addons/hr_payroll/data/hr_payroll_demo.xml | 73 +- addons/hr_payroll/models/hr_contract.py | 19 +- addons/hr_payroll/models/hr_employee.py | 10 +- addons/hr_payroll/models/hr_payslip.py | 120 +- addons/hr_payroll/models/hr_salary_rule.py | 131 +- .../hr_payroll/security/ir.model.access.csv | 1 + addons/hr_payroll/tests/common.py | 3 - .../hr_payroll/tests/test_multi_contract.py | 1 - addons/hr_payroll/views/hr_contract_views.xml | 7 +- addons/hr_payroll/views/hr_payroll_menu.xml | 7 + .../hr_payroll/views/hr_salary_rule_views.xml | 109 +- .../tests/test_hr_payroll_account.py | 16 +- .../views/hr_payroll_account_views.xml | 2 +- addons/l10n_be_hr_payroll/__manifest__.py | 3 + .../cp200/employee_double_holidays_data.xml | 233 ++++ .../data/cp200/employee_salary_data.xml | 251 ++++ .../cp200/employee_thirteen_month_data.xml | 78 ++ .../data/l10n_be_hr_payroll_data.xml | 622 +-------- addons/l10n_be_hr_payroll/models/__init__.py | 1 + .../l10n_be_hr_payroll/models/hr_payslip.py | 226 ++++ .../models/l10n_be_hr_payroll.py | 7 +- .../views/l10n_be_hr_payroll_view.xml | 10 - .../data/l10n_be_hr_payroll_account_data.xml | 19 +- .../data/l10n_be_hr_payroll_account_demo.xml | 2 +- addons/l10n_fr_hr_payroll/__manifest__.py | 2 + .../data/l10n_fr_hr_payroll_data.xml | 1152 +---------------- .../l10n_fr_hr_payroll_employe_cadre_data.xml | 933 +++++++++++++ ...n_fr_hr_payroll_employe_non_cadre_data.xml | 819 ++++++++++++ .../l10n_fr_hr_payroll/report/fiche_paye.py | 18 +- .../report/report_l10n_fr_fiche_paye.xml | 29 +- addons/l10n_in_hr_payroll/__manifest__.py | 27 +- .../data/hr.salary.rule.csv | 5 - .../data/l10n_in_hr_payroll_data.xml | 465 ------- .../data/l10n_in_hr_payroll_demo.xml | 516 ++++++++ .../demo/l10n_in_hr_payroll_demo.xml | 12 - 41 files changed, 3343 insertions(+), 2727 deletions(-) create mode 100644 addons/l10n_be_hr_payroll/data/cp200/employee_double_holidays_data.xml create mode 100644 addons/l10n_be_hr_payroll/data/cp200/employee_salary_data.xml create mode 100644 addons/l10n_be_hr_payroll/data/cp200/employee_thirteen_month_data.xml create mode 100644 addons/l10n_be_hr_payroll/models/hr_payslip.py create mode 100644 addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_employe_cadre_data.xml create mode 100644 addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_employe_non_cadre_data.xml delete mode 100644 addons/l10n_in_hr_payroll/data/hr.salary.rule.csv create mode 100644 addons/l10n_in_hr_payroll/data/l10n_in_hr_payroll_demo.xml delete mode 100644 addons/l10n_in_hr_payroll/demo/l10n_in_hr_payroll_demo.xml diff --git a/addons/hr_contract/data/hr_contract_data.xml b/addons/hr_contract/data/hr_contract_data.xml index 136ba02ed27..546b36def1b 100644 --- a/addons/hr_contract/data/hr_contract_data.xml +++ b/addons/hr_contract/data/hr_contract_data.xml @@ -2,22 +2,6 @@ - - - Employee - 5 - - - - Worker - 10 - - - - Subcontractor - 15 - - To Renew diff --git a/addons/hr_contract/data/hr_contract_demo.xml b/addons/hr_contract/data/hr_contract_demo.xml index eef59fd9813..c7092205f57 100644 --- a/addons/hr_contract/data/hr_contract_demo.xml +++ b/addons/hr_contract/data/hr_contract_demo.xml @@ -3,7 +3,6 @@ Mitchell Admin Contract - Ronnie Hart Contract - Marketing Executive Contract - Randall Lewis Contract - @@ -56,7 +52,6 @@ Demo Contract - Walter Horton Contract - Sharlene Rhodes Contract - @@ -96,7 +89,6 @@ Toni Jimenez - @@ -110,7 +102,6 @@ Jennie Fletcher Contract - Paul Williams Contract - Keith Byrd Contract - @@ -150,7 +139,6 @@ Audrey Peterson Contract - @@ -164,7 +152,6 @@ Tina Williamson Contract - diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py index 81e4a14e752..bf1c5fbd58f 100644 --- a/addons/hr_contract/models/hr_contract.py +++ b/addons/hr_contract/models/hr_contract.py @@ -48,18 +48,7 @@ class Employee(models.Model): return self.search([])._get_contracts(date_from, date_to, states=states) -class ContractType(models.Model): - - _name = 'hr.contract.type' - _description = 'Contract Type' - _order = 'sequence, id' - - name = fields.Char(string='Contract Type', required=True) - sequence = fields.Integer(help="Gives the sequence when displaying a list of Contract.", default=10) - - class Contract(models.Model): - _name = 'hr.contract' _description = 'Contract' _inherit = ['mail.thread', 'mail.activity.mixin'] @@ -68,7 +57,6 @@ class Contract(models.Model): active = fields.Boolean(default=True) employee_id = fields.Many2one('hr.employee', string='Employee') department_id = fields.Many2one('hr.department', string="Department") - type_id = fields.Many2one('hr.contract.type', string="Employee Category", required=True, default=lambda self: self.env['hr.contract.type'].search([], limit=1)) job_id = fields.Many2one('hr.job', string='Job Position') date_start = fields.Date('Start Date', required=True, default=fields.Date.today, help="Start date of the contract.") diff --git a/addons/hr_contract/security/ir.model.access.csv b/addons/hr_contract/security/ir.model.access.csv index 69719db666f..5bdda089639 100644 --- a/addons/hr_contract/security/ir.model.access.csv +++ b/addons/hr_contract/security/ir.model.access.csv @@ -2,5 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_hr_resource_manager,hr.employee.resource.manager,resource.model_resource_resource,hr.group_hr_manager,1,1,1,1 access_hr_resource_calendar_user,hr.employee.resource.calendar.user,resource.model_resource_calendar,hr.group_hr_user,1,1,1,1 access_hr_resource_calendar_attendance_user,hr.employee.resource.calendar.attendance.user,resource.model_resource_calendar_attendance,hr.group_hr_user,1,1,1,1 -access_hr_contract_type_manager,hr.contract.type.manager,model_hr_contract_type,hr_contract.group_hr_contract_manager,1,1,1,1 access_hr_contract_manager,hr.contract.manager,model_hr_contract,hr_contract.group_hr_contract_manager,1,1,1,1 diff --git a/addons/hr_contract/views/hr_contract_views.xml b/addons/hr_contract/views/hr_contract_views.xml index 41f69531cf0..5f42ca22720 100644 --- a/addons/hr_contract/views/hr_contract_views.xml +++ b/addons/hr_contract/views/hr_contract_views.xml @@ -83,7 +83,6 @@ - @@ -117,7 +116,6 @@ - @@ -174,7 +172,6 @@ - @@ -239,64 +236,12 @@ - - hr.contract.type.form - hr.contract.type - -
- - - - - -
-
-
- - - hr.contract.type.tree - hr.contract.type - - - - - - - - - - hr.contract.type.search - hr.contract.type - - - - - - - - - - Contract Types - hr.contract.type - form - tree,form - - - - - + + Employee + + 40 @@ -49,46 +53,6 @@ COMP
- - Basic Salary - - BASIC - - none - code - result = contract.wage - - - - Gross - - GROSS - - none - code - result = categories.BASIC + categories.ALW - - - - Net Salary - - NET - - none - code - result = categories.BASIC + categories.ALW + categories.DED - - - - - - - - BASE - Base for new structures - - - diff --git a/addons/hr_payroll/data/hr_payroll_demo.xml b/addons/hr_payroll/data/hr_payroll_demo.xml index ab77c6d03e8..a8cf6585243 100644 --- a/addons/hr_payroll/data/hr_payroll_demo.xml +++ b/addons/hr_payroll/data/hr_payroll_demo.xml @@ -19,7 +19,15 @@ Meal Voucher Register - + + + + MEGG + Marketing Executive for Gilles Gravie + + + + percentage @@ -30,6 +38,7 @@ House Rent Allowance + @@ -39,36 +48,17 @@ Conveyance Allowance - - - - fix - - - PT - - - Professional Tax - - - - percentage - - - contract.wage - PF - - - Provident Fund + fix - + CAGG Conveyance Allowance For Gravie + @@ -80,22 +70,30 @@ Meal Voucher + - - - - ME - Marketing Executive - - + + percentage + + + contract.wage + PF + + + Provident Fund + - - MEGG - Marketing Executive for Gilles Gravie - - + + fix + + + PT + + + Professional Tax + @@ -113,10 +111,9 @@ Marketing Executive Contract - - + Default contract for marketing executives @@ -125,7 +122,6 @@ Contract For Gilles Gravie - @@ -136,7 +132,6 @@ Contract For Mitchell Admin - diff --git a/addons/hr_payroll/models/hr_contract.py b/addons/hr_payroll/models/hr_contract.py index 53217a4b4b9..b167478c755 100644 --- a/addons/hr_payroll/models/hr_contract.py +++ b/addons/hr_payroll/models/hr_contract.py @@ -4,11 +4,13 @@ from odoo import api, fields, models from datetime import date + class HrContract(models.Model): _inherit = 'hr.contract' _description = 'Employee Contract' struct_id = fields.Many2one('hr.payroll.structure', string='Salary Structure') + structure_type_id = fields.Many2one(related='struct_id.type_id') schedule_pay = fields.Selection([ ('monthly', 'Monthly'), ('quarterly', 'Quarterly'), @@ -20,7 +22,6 @@ class HrContract(models.Model): ], string='Scheduled Pay', index=True, default='monthly', help="Defines the frequency of the wage payment.") resource_calendar_id = fields.Many2one(required=True, help="Employee's working schedule.") - hours_per_week = fields.Float(related='resource_calendar_id.hours_per_week') full_time_required_hours = fields.Float(related='resource_calendar_id.full_time_required_hours') is_fulltime = fields.Boolean(related='resource_calendar_id.is_fulltime') @@ -29,6 +30,13 @@ class HrContract(models.Model): def _check_contracts(self): self._get_leaves()._check_contracts() + @api.onchange('struct_id') + def _onchange_struct_id(self): + if self.struct_id.type_id.default_schedule_pay: + self.schedule_pay = self.struct_id.type_id.default_schedule_pay + if self.struct_id.type_id.default_resource_calendar_id: + self.resource_calendar_id = self.struct_id.type_id.default_resource_calendar_id + @api.multi def _get_leaves(self): return self.env['hr.leave'].search([ @@ -36,12 +44,3 @@ class HrContract(models.Model): ('date_from', '<=', max([end or date.max for end in self.mapped('date_end')])), ('date_to', '>=', min(self.mapped('date_start'))), ]) - - @api.multi - def get_all_structures(self): - """ - @return: the structures linked to the given contracts, ordered by hierachy (parent=False first, - then first level children and so on) and without duplicata - """ - structures = self.mapped('struct_id') - return structures._get_parent_structure() if structures else self.env['hr.payroll.structure'] diff --git a/addons/hr_payroll/models/hr_employee.py b/addons/hr_payroll/models/hr_employee.py index 527f70c41ad..83a12bcadb9 100644 --- a/addons/hr_payroll/models/hr_employee.py +++ b/addons/hr_payroll/models/hr_employee.py @@ -1,11 +1,11 @@ # -*- coding:utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models, _ -from odoo.addons.resource.models.resource import Intervals -from datetime import datetime, timedelta +from datetime import datetime import pytz +from odoo import api, fields, models + class HrEmployee(models.Model): _inherit = 'hr.employee' @@ -13,14 +13,12 @@ class HrEmployee(models.Model): slip_ids = fields.One2many('hr.payslip', 'employee_id', string='Payslips', readonly=True) payslip_count = fields.Integer(compute='_compute_payslip_count', string='Payslip Count', groups="hr_payroll.group_hr_payroll_user") - registration_number = fields.Char('Registration Number of the Employee', copy=False) _sql_constraints = [ ('unique_registration_number', 'UNIQUE(registration_number, company_id)', 'No duplication of registration numbers is allowed') ] - @api.multi def _compute_payslip_count(self): for employee in self: employee.payslip_count = len(employee.slip_ids) @@ -33,7 +31,6 @@ class HrEmployee(models.Model): ('state', 'in', ['draft', 'confirmed']) ])) - @api.multi def write(self, vals): res = super(HrEmployee, self).write(vals) if vals.get('contract_id'): @@ -51,7 +48,6 @@ class HrEmployee(models.Model): attendance_type = self.env.ref('hr_payroll.benefit_type_attendance') vals_list = [] - leaves = self.env['hr.leave'] date_start = _format_datetime(date_start) date_stop = _format_datetime(date_stop) diff --git a/addons/hr_payroll/models/hr_payslip.py b/addons/hr_payroll/models/hr_payslip.py index 3277d473f47..c6d5df5b605 100644 --- a/addons/hr_payroll/models/hr_payslip.py +++ b/addons/hr_payroll/models/hr_payslip.py @@ -143,7 +143,7 @@ class HrPayslip(models.Model): number = payslip.number or self.env['ir.sequence'].next_by_code('salary.slip') # delete old payslip lines payslip.line_ids.unlink() - lines = [(0, 0, line) for line in payslip._get_payslip_lines(payslip.contract_id)] + lines = [(0, 0, line) for line in payslip._get_payslip_lines()] payslip.write({'line_ids': lines, 'number': number, 'state': 'verify', 'compute_date': fields.Date.today()}) return True @@ -181,89 +181,67 @@ class HrPayslip(models.Model): 'float_round': float_round } - # YTI TODO: pass recordset as argument @api.model - def _get_payslip_lines(self, contracts): - # YTI TODO: Move into browsable_object.py ? + def _get_payslip_lines(self): def _sum_salary_rule_category(localdict, category, amount): if category.parent_id: localdict = _sum_salary_rule_category(localdict, category.parent_id, amount) - localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount + localdict['categories'].dict[category.code] = localdict['categories'].dict.get(category.code, 0) + amount return localdict self.ensure_one() - #we keep a dict with the result because a value can be overwritten by another rule with the same code - result_dict = {} + result = {} rules_dict = {} - worked_days_dict = {} - inputs_dict = {} - blacklist = [] + 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} - for code in self.worked_days_line_ids.mapped('code'): - worked_days_dict[code] = self.worked_days_line_ids.filtered(lambda l: l.code == code) - for code in self.input_line_ids.mapped('code'): - inputs_dict[code] = self.input_line_ids.filtered(lambda l: l.code == code) + employee = self.employee_id + contract = self.contract_id - categories = BrowsableObject(self.employee_id.id, {}, self.env) - inputs = InputLine(self.employee_id.id, inputs_dict, self.env) - worked_days = WorkedDays(self.employee_id.id, worked_days_dict, self.env) - payslips = Payslips(self.employee_id.id, self, self.env) - rules = BrowsableObject(self.employee_id.id, rules_dict, self.env) - - baselocaldict = {**self._get_base_local_dict(), **{'categories': categories, 'rules': rules, 'payslip': payslips, 'worked_days': worked_days, 'inputs': inputs}} - #get the ids of the structures on the contracts and their parent id as well - if len(contracts) == 1 and self.struct_id: - structures = self.struct_id._get_parent_structure() - else: - structures = contracts.get_all_structures() - #get the rules of the structure and thier children - rule_ids = structures.get_all_rules() - #run the rules by sequence - sorted_rule_ids = [id for id, sequence in sorted(rule_ids, key=lambda x:x[1])] - sorted_rules = self.env['hr.salary.rule'].browse(sorted_rule_ids) - - for contract in contracts: - employee = contract.employee_id - localdict = dict(baselocaldict, employee=employee, contract=contract) - for rule in sorted_rules: - key = rule.code + '-' + str(contract.id) - localdict['result'] = None - localdict['result_qty'] = 1.0 - localdict['result_rate'] = 100 - #check if the rule can be applied - if rule._satisfy_condition(localdict) and rule.id not in blacklist: - #compute the amount of the rule - amount, qty, rate = rule._compute_rule(localdict) - #check if there is already a rule computed with that code - previous_amount = rule.code in localdict and localdict[rule.code] or 0.0 - #set/overwrite the amount computed for this rule in the localdict - tot_rule = amount * qty * rate / 100.0 - localdict[rule.code] = tot_rule - rules_dict[rule.code] = rule - #sum the amount for its salary category - localdict = _sum_salary_rule_category(localdict, rule.category_id, tot_rule - previous_amount) - #create/overwrite the rule in the temporary results - result_dict[key] = { - 'sequence': rule.sequence, - 'code': rule.code, - 'name': rule.name, - 'note': rule.note, - 'salary_rule_id': rule.id, - 'contract_id': contract.id, - 'employee_id': contract.employee_id.id, - 'amount': amount, - 'quantity': qty, - 'rate': rate, - } - else: - #blacklist this rule and its children - blacklist += [id for id, seq in rule._recursive_search_of_rules()] - - return list(result_dict.values()) + localdict = { + **self._get_base_local_dict(), + **{ + 'categories': BrowsableObject(employee.id, {}, self.env), + 'rules': BrowsableObject(employee.id, rules_dict, 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 + } + } + for rule in sorted(self.struct_id.rule_ids, key=lambda x: x.sequence): + localdict.update({ + 'result': None, + 'result_qty': 1.0, + 'result_rate': 100}) + if rule._satisfy_condition(localdict): + amount, qty, rate = rule._compute_rule(localdict) + #check if there is already a rule computed with that code + previous_amount = rule.code in localdict and localdict[rule.code] or 0.0 + #set/overwrite the amount computed for this rule in the localdict + tot_rule = amount * qty * rate / 100.0 + localdict[rule.code] = tot_rule + rules_dict[rule.code] = rule + # sum the amount for its salary category + localdict = _sum_salary_rule_category(localdict, rule.category_id, tot_rule - previous_amount) + # create/overwrite the rule in the temporary results + result[rule.code] = { + 'sequence': rule.sequence, + 'code': rule.code, + 'name': rule.name, + 'note': rule.note, + 'salary_rule_id': rule.id, + 'contract_id': contract.id, + 'employee_id': employee.id, + 'amount': amount, + 'quantity': qty, + 'rate': rate, + } + return result.values() @api.onchange('employee_id', 'contract_id', 'date_from', 'date_to') def onchange_employee(self): - if (not self.employee_id) or (not self.date_from) or (not self.date_to): return diff --git a/addons/hr_payroll/models/hr_salary_rule.py b/addons/hr_payroll/models/hr_salary_rule.py index 81062738d5c..044b94299b3 100644 --- a/addons/hr_payroll/models/hr_salary_rule.py +++ b/addons/hr_payroll/models/hr_salary_rule.py @@ -8,40 +8,59 @@ from odoo.tools.safe_eval import safe_eval from odoo.addons import decimal_precision as dp class HrPayrollStructure(models.Model): - """ - Salary structure used to defined - - Basic - - Allowances - - Deductions - """ _name = 'hr.payroll.structure' _description = 'Salary Structure' - @api.model - def _get_parent(self): - return self.env.ref('hr_payroll.structure_base', False) - @api.model def _get_default_report_id(self): return self.env.ref('hr_payroll.action_report_payslip', False) + @api.model + def _get_default_rule_ids(self): + return [ + (0, 0, { + '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 = contract.wage', + }), + (0, 0, { + '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', + }), + (0, 0, { + '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', + 'register_id': self.env.ref('hr_payroll.contrib_register_employees').id + }) + ] + name = fields.Char(required=True) code = fields.Char(string='Reference', required=True) + type_id = fields.Many2one('hr.payroll.structure.type', default=lambda self: self.env.ref('hr_payroll.structure_type_employee', raise_if_not_found=False)) country_id = fields.Many2one('res.country', string='Country') note = fields.Text(string='Description') - parent_id = fields.Many2one('hr.payroll.structure', string='Parent', default=_get_parent) - children_ids = fields.One2many('hr.payroll.structure', 'parent_id', string='Children', copy=True) - rule_ids = fields.Many2many('hr.salary.rule', 'hr_structure_salary_rule_rel', 'struct_id', 'rule_id', string='Salary Rules') + rule_ids = fields.One2many( + 'hr.salary.rule', 'struct_id', + string='Salary Rules', default=_get_default_rule_ids) report_id = fields.Many2one('ir.actions.report', string="Report", required=True, domain="[('model','=','hr.payslip'),('report_type','=','qweb-pdf')]", default=_get_default_report_id) payslip_name = fields.Char(string="Payslip Name", translate=True, help="Name to be set on a payslip. Example: 'End of the year bonus'. If not set, the default value is 'Salary Slip'") - @api.constrains('parent_id') - def _check_parent_id(self): - if not self._check_recursion(): - raise ValidationError(_('You cannot create a recursive salary structure.')) - @api.multi @api.returns('self', lambda value: value.id) def copy(self, default=None): @@ -49,23 +68,24 @@ class HrPayrollStructure(models.Model): default = dict(default or {}, code=_("%s (copy)") % (self.code)) return super(HrPayrollStructure, self).copy(default) - @api.multi - def get_all_rules(self): - """ - @return: returns a list of tuple (id, sequence) of rules that are maybe to apply - """ - all_rules = [] - for struct in self: - all_rules += struct.rule_ids._recursive_search_of_rules() - return list(set(all_rules)) - @api.multi - def _get_parent_structure(self): - parent = self.mapped('parent_id') - if parent: - parent = parent._get_parent_structure() - return parent | self +class HrPayrollStructureType(models.Model): + _name = 'hr.payroll.structure.type' + _description = 'Salary Structure Type' + name = fields.Char('Structure Type') + default_schedule_pay = fields.Selection([ + ('monthly', 'Monthly'), + ('quarterly', 'Quarterly'), + ('semi-annually', 'Semi-annually'), + ('annually', 'Annually'), + ('weekly', 'Weekly'), + ('bi-weekly', 'Bi-weekly'), + ('bi-monthly', 'Bi-monthly'), + ], string='Default Scheduled Pay', default='monthly', + help="Defines the frequency of the wage payment.") + default_resource_calendar_id = fields.Many2one( + 'resource.calendar', 'Default Working Hours') class HrContributionRegister(models.Model): _name = 'hr.contribution.register' @@ -104,6 +124,7 @@ class HrSalaryRule(models.Model): code = fields.Char(required=True, help="The code of salary rules can be used as reference in computation of other rules. " "In that case, it is case sensitive.") + struct_id = fields.Many2one('hr.payroll.structure', string="Salary Structure") sequence = fields.Integer(required=True, index=True, default=5, help='Use to arrange calculation sequence') quantity = fields.Char(default='1.0', @@ -116,7 +137,6 @@ class HrSalaryRule(models.Model): help="If the active field is set to false, it will allow you to hide the salary rule without removing it.") appears_on_payslip = fields.Boolean(string='Appears on Payslip', default=True, help="Used to display the salary rule on payslip.") - parent_rule_id = fields.Many2one('hr.salary.rule', string='Parent Salary Rule', index=True) condition_select = fields.Selection([ ('none', 'Always True'), ('range', 'Range'), @@ -168,74 +188,51 @@ class HrSalaryRule(models.Model): result = contract.wage * 0.10''') amount_percentage_base = fields.Char(string='Percentage based on', help='result will be affected to a variable') - child_ids = fields.One2many('hr.salary.rule', 'parent_rule_id', string='Child Salary Rule', copy=True) register_id = fields.Many2one('hr.contribution.register', string='Contribution Register', help="Eventual third party involved in the salary payment of the employees.") note = fields.Text(string='Description') - @api.constrains('parent_rule_id') - def _check_parent_rule_id(self): - if not self._check_recursion(parent='parent_rule_id'): - raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rules.')) - - @api.multi - def _recursive_search_of_rules(self): - """ - @return: returns a list of tuple (id, sequence) which are all the children of the passed rule_ids - """ - children_rules = [] - for rule in self.filtered(lambda rule: rule.child_ids): - children_rules += rule.child_ids._recursive_search_of_rules() - return [(rule.id, rule.sequence) for rule in self] + children_rules - - #TODO should add some checks on the type of result (should be float) @api.multi def _compute_rule(self, localdict): """ - :param localdict: dictionary containing the environement in which to compute the rule - :return: returns a tuple build as the base/amount computed, the quantity and the rate + :param localdict: dictionary containing the current computation environment + :return: returns a tuple (amount, qty, rate) :rtype: (float, float, float) """ self.ensure_one() if self.amount_select == 'fix': try: - return self.amount_fix, float(safe_eval(self.quantity, localdict)), 100.0 + return self.amount_fix or 0.0, float(safe_eval(self.quantity, localdict)), 100.0 except Exception as e: raise UserError(_('Wrong quantity defined for salary rule %s (%s).\nError: %s') % (self.name, self.code, e)) - elif self.amount_select == 'percentage': + if self.amount_select == 'percentage': try: return (float(safe_eval(self.amount_percentage_base, localdict)), float(safe_eval(self.quantity, localdict)), - self.amount_percentage) + self.amount_percentage or 0.0) except Exception as e: raise UserError(_('Wrong percentage base or quantity defined for salary rule %s (%s).\nError: %s') % (self.name, self.code, e)) - else: + else: # python code try: - safe_eval(self.amount_python_compute, localdict, mode='exec', nocopy=True) + 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)) @api.multi def _satisfy_condition(self, localdict): - """ - @param contract_id: id of hr.contract to be tested - @return: returns True if the given rule match the condition for the given contract. Return False otherwise. - """ self.ensure_one() - if self.condition_select == 'none': return True - elif self.condition_select == 'range': + if self.condition_select == 'range': try: result = safe_eval(self.condition_range, localdict) - return self.condition_range_min <= result and result <= self.condition_range_max or False + return self.condition_range_min <= result <= self.condition_range_max except: raise UserError(_('Wrong range condition defined for salary rule %s (%s).') % (self.name, self.code)) else: # python code try: safe_eval(self.condition_python, localdict, mode='exec', nocopy=True) - return localdict.get('result', False) + return localdict.get('result', False) except: raise UserError(_('Wrong python condition defined for salary rule %s (%s).') % (self.name, self.code)) - diff --git a/addons/hr_payroll/security/ir.model.access.csv b/addons/hr_payroll/security/ir.model.access.csv index 68d8dd8f6d7..d4961980605 100644 --- a/addons/hr_payroll/security/ir.model.access.csv +++ b/addons/hr_payroll/security/ir.model.access.csv @@ -13,3 +13,4 @@ access_hr_benefit,access_hr_benefit,model_hr_benefit,group_hr_payroll_user,1,1,1 access_hr_benefit_type_officer,access_hr_benefit_type_officer,model_hr_benefit_type,group_hr_payroll_user,1,0,0,0 access_hr_benefit_employee,hr.benefit.employee,model_hr_user_benefit_employee,group_hr_payroll_user,1,1,1,1 access_hr_benefit_type_manager,access_hr_benefit_type_manager,model_hr_benefit_type,group_hr_payroll_manager,1,1,1,1 +access_hr_payroll_structure_type,hr.payroll.structure.type,model_hr_payroll_structure_type,hr_payroll.group_hr_payroll_user,1,1,1,1 diff --git a/addons/hr_payroll/tests/common.py b/addons/hr_payroll/tests/common.py index c64419282e5..83129e301a0 100644 --- a/addons/hr_payroll/tests/common.py +++ b/addons/hr_payroll/tests/common.py @@ -43,7 +43,6 @@ class TestPayslipBase(TransactionCase): 'date_start': Date.today(), 'name': 'Contract for Richard', 'wage': 5000.0, - 'type_id': self.ref('hr_contract.hr_contract_type_emp'), 'employee_id': self.richard_emp.id, 'struct_id': self.developer_pay_structure.id, }) @@ -91,7 +90,6 @@ class TestPayslipContractBase(TestPayslipBase): 'name': 'First CDD Contract for Richard', 'resource_calendar_id': self.calendar_40h.id, 'wage': 5000.0, - 'type_id': self.ref('hr_contract.hr_contract_type_emp'), 'employee_id': self.richard_emp.id, 'struct_id': self.developer_pay_structure.id, 'state': 'close', @@ -103,7 +101,6 @@ class TestPayslipContractBase(TestPayslipBase): 'name': 'Contract for Richard', 'resource_calendar_id': self.calendar_35h.id, 'wage': 5000.0, - 'type_id': self.ref('hr_contract.hr_contract_type_emp'), 'employee_id': self.richard_emp.id, 'struct_id': self.developer_pay_structure.id, 'state': 'open', diff --git a/addons/hr_payroll/tests/test_multi_contract.py b/addons/hr_payroll/tests/test_multi_contract.py index 75f07871e45..a15c63afdce 100644 --- a/addons/hr_payroll/tests/test_multi_contract.py +++ b/addons/hr_payroll/tests/test_multi_contract.py @@ -97,7 +97,6 @@ class TestPayslipMultiContract(TestPayslipContractBase): 'name': 'Contract for Richard', 'resource_calendar_id': self.calendar_40h.id, 'wage': 5000.0, - 'type_id': self.ref('hr_contract.hr_contract_type_emp'), 'employee_id': self.richard_emp.id, 'struct_id': self.developer_pay_structure.id, 'state': 'open', diff --git a/addons/hr_payroll/views/hr_contract_views.xml b/addons/hr_payroll/views/hr_contract_views.xml index 6677e26f580..540bbb22b34 100644 --- a/addons/hr_payroll/views/hr_contract_views.xml +++ b/addons/hr_payroll/views/hr_contract_views.xml @@ -6,12 +6,11 @@ hr.contract - - - - + + + diff --git a/addons/hr_payroll/views/hr_payroll_menu.xml b/addons/hr_payroll/views/hr_payroll_menu.xml index 4222ee02b1a..adec170a78a 100644 --- a/addons/hr_payroll/views/hr_payroll_menu.xml +++ b/addons/hr_payroll/views/hr_payroll_menu.xml @@ -113,6 +113,13 @@ sequence="30" /> + + - hr.payroll.structure.tree hr.payroll.structure + @@ -41,25 +41,15 @@ - - hr.payroll.structure.tree - hr.payroll.structure - children_ids - - - - - - - - - hr.payroll.structure.select hr.payroll.structure + + + @@ -73,14 +63,13 @@ - - + - + @@ -101,6 +90,57 @@ hr.payroll.structure form tree,kanban,form + {'search_default_group_by_type_id': 1} + + + + hr.payroll.structure.type.form + hr.payroll.structure.type + +
+ + + + + + + + + + + +
+
+
+ + + hr.payroll.structure.type.tree + hr.payroll.structure.type + + + + + + + + + + + hr.payroll.structure.type.search + hr.payroll.structure.type + + + + + + + + + Structure Types + hr.payroll.structure.type + form + tree,form + @@ -229,7 +269,6 @@
- hr.salary.rule.list hr.salary.rule @@ -238,6 +277,7 @@ + @@ -272,19 +312,6 @@ - - hr.salary.rule.tree - hr.salary.rule - child_ids - - - - - - - - - hr.salary.rule.form hr.salary.rule @@ -298,7 +325,8 @@ - + + @@ -322,11 +350,6 @@ - - - - - @@ -343,9 +366,11 @@ + + @@ -356,15 +381,7 @@ hr.salary.rule form tree,kanban,form - [('parent_rule_id','=',False)] + {'search_default_group_by_struct_id': 1} - - -
diff --git a/addons/hr_payroll_account/tests/test_hr_payroll_account.py b/addons/hr_payroll_account/tests/test_hr_payroll_account.py index 0813ffd635d..638cd64a468 100644 --- a/addons/hr_payroll_account/tests/test_hr_payroll_account.py +++ b/addons/hr_payroll_account/tests/test_hr_payroll_account.py @@ -43,28 +43,14 @@ class TestHrPayrollAccount(common.TransactionCase): 'bank_account_id': self.res_partner_bank.bank_id.id, }) - self.hr_structure_softwaredeveloper = self.env['hr.payroll.structure'].create({ - 'name': 'Salary Structure for Software Developer', - 'code': 'SD', - 'parent_id': self.ref('hr_payroll.structure_base'), - 'rule_ids': [(6, 0, [ - self.ref('hr_payroll.hr_salary_rule_houserentallowance1'), - self.ref('hr_payroll.hr_salary_rule_convanceallowance1'), - self.ref('hr_payroll.hr_salary_rule_professionaltax1'), - self.ref('hr_payroll.hr_salary_rule_providentfund1'), - self.ref('hr_payroll.hr_salary_rule_meal_voucher'), - ])], - }) - # Create account journal. self.hr_contract_john = self.env['hr.contract'].create({ 'date_end': fields.Date.to_string(datetime.now() + timedelta(days=365)), 'date_start': fields.Date.today(), 'name': 'Contract for John', 'wage': 5000.0, - 'type_id': self.ref('hr_contract.hr_contract_type_emp'), 'employee_id': self.hr_employee_john.id, - 'struct_id': self.hr_structure_softwaredeveloper.id, + 'struct_id': self.env.ref('hr_payroll.structure_002').id, 'journal_id': self.ref('hr_payroll_account.expenses_journal'), }) diff --git a/addons/hr_payroll_account/views/hr_payroll_account_views.xml b/addons/hr_payroll_account/views/hr_payroll_account_views.xml index fa4d9cd6258..c0e8f9b5488 100644 --- a/addons/hr_payroll_account/views/hr_payroll_account_views.xml +++ b/addons/hr_payroll_account/views/hr_payroll_account_views.xml @@ -20,7 +20,7 @@ hr.salary.rule - + diff --git a/addons/l10n_be_hr_payroll/__manifest__.py b/addons/l10n_be_hr_payroll/__manifest__.py index 9f5afe39b84..739d2dc1145 100644 --- a/addons/l10n_be_hr_payroll/__manifest__.py +++ b/addons/l10n_be_hr_payroll/__manifest__.py @@ -25,6 +25,9 @@ Belgian Payroll Rules. 'views/reports.xml', 'views/l10n_be_hr_payroll_view.xml', 'data/l10n_be_hr_payroll_data.xml', + 'data/cp200/employee_double_holidays_data.xml', + 'data/cp200/employee_salary_data.xml', + 'data/cp200/employee_thirteen_month_data.xml', 'data/report_paperformat.xml', 'views/res_config_settings_views.xml', 'wizard/l10n_be_individual_account_wizard_views.xml', diff --git a/addons/l10n_be_hr_payroll/data/cp200/employee_double_holidays_data.xml b/addons/l10n_be_hr_payroll/data/cp200/employee_double_holidays_data.xml new file mode 100644 index 00000000000..9dbc2d60c4a --- /dev/null +++ b/addons/l10n_be_hr_payroll/data/cp200/employee_double_holidays_data.xml @@ -0,0 +1,233 @@ + + + + + + + + CP 200 DH + Double Holidays Belgian Employee + + + Double Holidays Slip + + + + + + + + + + + + + Double Holiday Pay + D.P + 2 + none + code + result = categories.BASIC * 0.92 + + + + + + ATN Internet + ATN.INT + 16 + python + result = bool(contract.internet) + code + result = 5.0 + + + + + + ATN Mobile + ATN.MOB + 17 + python + result = bool(contract.mobile) + code + result = 12.0 + + + + + + Gross Salary + SALARY + 20 + none + code + +result = categories.BASIC +if contract.internet: + result += 5.0 +if contract.mobile: + result += 12.0 + + + + + + + + + Gross Salary + SALARY + 21 + none + code + result = SALARY * 0.85 + + + + + + + Social contribution + ONSS + 41 + none + percentage + SALARY + -13.07 + + + + + + + Employment Bonus + EmpBonus.1 + 42 + python + result = categories.BRUT <= 2560.57 + code + result = compute_employment_bonus_employees(payslip, categories, worked_days, inputs) + + + + + + Reimbursement of travel expenses + Tr.E + code + 65 + python + result = bool(contract.others_reimbursed_amount) + result = contract.others_reimbursed_amount + + + + + Total Gross + 90 + GROSSIP + + python + result = bool(contract.ip) + code + result = categories.BASIC + categories.ALW + + + + + + Intellectual Property + IP.PART + 91 + python + result = bool(contract.ip) + code + result = - categories.GROSSIP * contract.ip_wage_rate / 100.0 + + + + + + Gross + GROSS + 101 + none + code + result = categories.DP + categories.ALW + + + + + + Withholding Tax + P.P + 102 + none + code + result = compute_double_holiday_withholding_taxes(payslip, categories, worked_days, inputs) + + + + + + ATN Internet + ATN.INT.2 + 161 + python + result = contract.internet + code + result = -5.0 + + + + + + ATN Mobile + ATN.MOB.2 + 162 + python + result = contract.mobile + code + result = -12.0 + + + + + + Intellectual Property + IP + code + 172 + python + result = bool(contract.ip) + result = categories.GROSSIP * contract.ip_wage_rate / 100.0 + + + + + + Intellectual Property Income Deduction + IP.DED + code + 173 + python + result = bool(contract.ip) + result = compute_ip_deduction(payslip, categories, worked_days, inputs) + + + + + + Net + NET + 201 + none + code + result = categories.DP + categories.ALW + categories.DED + + + + diff --git a/addons/l10n_be_hr_payroll/data/cp200/employee_salary_data.xml b/addons/l10n_be_hr_payroll/data/cp200/employee_salary_data.xml new file mode 100644 index 00000000000..439f23b3649 --- /dev/null +++ b/addons/l10n_be_hr_payroll/data/cp200/employee_salary_data.xml @@ -0,0 +1,251 @@ + + + + + + + + CP 200 + Belgian Employee + + + + + + + + + + + + + ATN Internet + ATN.INT + 16 + python + result = bool(contract.internet) + code + result = 5.0 + + + + + + ATN Mobile + ATN.MOB + 17 + python + result = bool(contract.mobile) + code + result = 12.0 + + + + + + Gross Salary + SALARY + 20 + none + code + +result = categories.BASIC +if contract.internet: + result += 5.0 +if contract.mobile: + result += 12.0 + + + + + + + + Social contribution + ONSS + 41 + none + percentage + SALARY + -13.07 + + + + + + + Employment Bonus + EmpBonus.1 + 42 + python + result = categories.BRUT <= 2560.57 + code + result = compute_employment_bonus_employees(payslip, categories, worked_days, inputs) + + + + + + Reimbursement of travel expenses + Tr.E + code + 65 + python + result = bool(contract.others_reimbursed_amount) + result = contract.others_reimbursed_amount + + + + + + ATN Company Car + ATN.CAR + 70 + python + result = bool(contract.transport_mode_car and contract.car_atn) + code + result = contract.car_atn + + + + + Total Gross + 90 + GROSSIP + + python + result = bool(contract.ip) + code + result = categories.BASIC + categories.ALW + + + + + + Intellectual Property + IP.PART + 91 + python + result = bool(contract.ip) + code + result = - categories.GROSSIP * contract.ip_wage_rate / 100.0 + + + + + + Withholding Tax + P.P + 120 + none + + code + result = compute_withholding_taxes(payslip, categories, worked_days, inputs) + + + + + + + ATN Company Car + ATN.CAR.2 + 160 + python + result = bool(contract.transport_mode_car and contract.car_atn) + code + result = -contract.car_atn + + + + + + ATN Internet + ATN.INT.2 + 161 + python + result = contract.internet + code + result = -5.0 + + + + + + Special social cotisation + M.ONSS + fix + 165 + + code + result = compute_special_social_cotisations(payslip, categories, worked_days, inputs) + + + + + + Retain on Meal Voucher + MEAL_V_EMP + 165 + python + result = bool(contract.meal_voucher_amount) + percentage + -100.0 + contract.meal_voucher_amount - contract.meal_voucher_paid_by_employer + worked_days.WORK100 and worked_days.WORK100.number_of_days + + + + + + + + ATN Mobile + ATN.MOB.2 + 162 + python + result = contract.mobile + code + result = -12.0 + + + + + + Representation Fees + REP.FEES + code + 171 + python + result = bool(contract.representation_fees) + result = contract.representation_fees + + + + + + Intellectual Property + IP + code + 172 + python + result = bool(contract.ip) + result = categories.GROSSIP * contract.ip_wage_rate / 100.0 + + + + + + Intellectual Property Income Deduction + IP.DED + code + 173 + python + result = bool(contract.ip) + result = compute_ip_deduction(payslip, categories, worked_days, inputs) + + + + + diff --git a/addons/l10n_be_hr_payroll/data/cp200/employee_thirteen_month_data.xml b/addons/l10n_be_hr_payroll/data/cp200/employee_thirteen_month_data.xml new file mode 100644 index 00000000000..db1ec592744 --- /dev/null +++ b/addons/l10n_be_hr_payroll/data/cp200/employee_thirteen_month_data.xml @@ -0,0 +1,78 @@ + + + + + + + CP 200 EOYB + 13th Month + + + 13th Month Slip + + + + + + + + + + + + Gross Salary + SALARY + 20 + none + code + +result = categories.BASIC +if contract.internet: + result += 5.0 +if contract.mobile: + result += 12.0 + + + + + + + + Social contribution + ONSS + 41 + none + percentage + SALARY + -13.07 + + + + + + + Withholding Tax + P.P + 102 + none + code + result = compute_thirteen_month_withholding_taxes(payslip, categories, worked_days, inputs) + + + + + + Special social cotisation + M.ONSS + fix + 165 + + code + result = compute_special_social_cotisations(payslip, categories, worked_days, inputs) + + + + + diff --git a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml index 9a6e0fbd180..ebee5ccacee 100644 --- a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml +++ b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_data.xml @@ -1,12 +1,10 @@ - - -result = contract.wage_with_holidays - + + CP200: Belgian Employee - + Office National de Sécurité Sociale (ONSS) 1 @@ -36,7 +34,7 @@ result = contract.wage_with_holidays - + Gross BRUT @@ -80,632 +78,20 @@ result = contract.wage_with_holidays SSC - IP. Part. IP. PART - Total Gross GROSSIP - Double Holiday Pay DP - - - - Worker Salary. Premium pay of 108% - SALARY - none - code - result = categories.BASIC * 1.08 - - - - Gross Salary - SALARY - none - code - -result = categories.BASIC -if contract.internet: - result += 5.0 -if contract.mobile: - result += 12.0 - - 20 - - - - - Social contribution - ONSS - 41 - none - percentage - SALARY - -13.07 - - - - - Employment Bonus - EmpBonus.1 - 42 - python - result = categories.BRUT <= 2560.57 - code - -salary = categories.BRUT -if salary <= 1641.62: - result = 201.62 -elif salary <= 2560.57: - result = 201.62 - (0.2194 * (salary - 1641.62)) - - - - - Employment Bonus - EmpBonus.2 - 42 - python - result = categories.BRUT + <= 2560.57 - code - -salary = categories.BRUT -if salary <= 1641.62: - result = 217.75 -elif salary <= 2560.57: - result = 217.75 - (0.2370 * (salary - 1641.62)) - - - - - Total Gross - 90 - GROSSIP - - python - result = bool(contract.ip) - code - result = categories.BASIC + categories.ALW - - - - - Intellectual Property - IP.PART - 91 - python - -result = bool(contract.ip) - - code - -result = - categories.GROSSIP * contract.ip_wage_rate / 100.0 - - - - - - Special social cotisation - M.ONSS - fix - 165 - - code - -wage = categories.BASIC -if employee.resident_bool: - result = 0.0 -elif employee.marital in ['divorced', 'single', 'widower'] or (employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='without income'): - if wage >= 0.01 and wage <= 1095.09: - result = 0.0 - elif wage >= 1095.10 and wage <= 1945.38: - result = 0.0 - elif wage >= 1945.39 and wage <= 2190.18: - result = -min((wage - 1945.38) * 0.076, 18.60) - elif wage >= 2190.19 and wage <= 6038.82: - result = -min(18.60 + (wage - 2190.18) * 0.011, 60.94) - else: - result = -60.94 -elif employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='with income': - if wage >= 0.01 and wage <= 1095.09: - result = 0.0 - elif wage >= 1095.10 and wage <= 1945.38: - result = -9.30 - elif wage >= 1945.39 and wage <= 2190.18: - result = -min(max((wage - 1945.38) * 0.076, 9.30), 18.60) - elif wage >= 2190.19 and wage <= 6038.82: - result = -min(18.60 + (wage - 2190.18) * 0.011, 51.64) - else: - result = -51.64 - - - - - Withholding Tax - P.P - 120 - none - - code - -def compute_basic_bareme(value): - if value <= 12860.0: - basic_bareme = value * 0.2675 - elif value <= 19630.0: - basic_bareme = 3440.05 + 0.428 * (value - 12860.00) - elif value <= 40470.00: - basic_bareme = 6337.61 + 0.4815 * (value - 19630.00) - else: - basic_bareme = 16372.07 + 0.535 * (value - 40470.0) - return float_round(basic_bareme, precision_rounding=0.01) - -def convert_to_month(value): - return float_round(value / 12.0, precision_rounding=0.01, rounding_method='DOWN') - -# PART 1: Withholding tax amount computation -withholding_tax_amount = 0.0 -lower_bound = categories.GROSS - categories.GROSS % 15 - -# yearly_gross_revenue = Revenu Annuel Brut -yearly_gross_revenue = lower_bound * 12.0 - -# yearly_net_taxable_amount = Revenu Annuel Net Imposable -if yearly_gross_revenue <= 16033.33: - yearly_net_taxable_revenue = yearly_gross_revenue * (1.0 - 0.3) -else: - yearly_net_taxable_revenue = yearly_gross_revenue - 4810.0 - -# BAREME III: Non resident -if employee.resident_bool: - basic_bareme = compute_basic_bareme(yearly_net_taxable_revenue) - withholding_tax_amount = convert_to_month(basic_bareme) -else: - # BAREME I: Isolated or spouse with income - if employee.marital in ['divorced', 'single', 'widower'] or (employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='with income'): - basic_bareme = max(compute_basic_bareme(yearly_net_taxable_revenue) - 2065.1, 0.0) - withholding_tax_amount = convert_to_month(basic_bareme) - - # BAREME II: spouse without income - if employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='without income': - yearly_net_taxable_revenue_for_spouse = min(yearly_net_taxable_revenue * 0.3, 10930.0) - basic_bareme_1 = compute_basic_bareme(yearly_net_taxable_revenue_for_spouse) - basic_bareme_2 = compute_basic_bareme(yearly_net_taxable_revenue - yearly_net_taxable_revenue_for_spouse) - withholding_tax_amount = convert_to_month(max(basic_bareme_1 + basic_bareme_2 - 4130.20, 0)) - -# Reduction for isolated people and for other family charges -if employee.marital in ['divorced', 'single', 'widower'] or (employee.spouse_net_revenue > 0.0 or employee.spouse_other_net_revenue > 0.0): - if employee.marital in ['divorced', 'single', 'widower']: - withholding_tax_amount -= 26.0 - if employee.marital == 'widower' or (employee.marital in ['divorced', 'single', 'widower'] and employee.dependent_children): - withholding_tax_amount -= 36.0 - if employee.disabled: - withholding_tax_amount -= 36.0 - if employee.other_dependent_people and employee.dependent_seniors: - withholding_tax_amount -= 80 * employee.dependent_seniors - if employee.other_dependent_people and employee.dependent_juniors: - withholding_tax_amount -= 36.0 * employee.dependent_juniors - if employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='with income' and employee.spouse_net_revenue <= 230.0: - withholding_tax_amount -= 115.0 - if employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='with income' and not employee.spouse_net_revenue and employee.spouse_other_net_revenue <= 459.0: - withholding_tax_amount -= 229.5 -if employee.marital in ['married', 'cohabitant'] and employee.spouse_net_revenue == 0.0 and employee.spouse_other_net_revenue == 0.0: - if employee.disabled: - withholding_tax_amount -= 36.0 - if employee.disabled_spouse_bool: - withholding_tax_amount -= 36.0 - if employee.other_dependent_people and employee.dependent_seniors: - withholding_tax_amount -= 80.0 * employee.dependent_seniors - if employee.other_dependent_people and employee.dependent_juniors: - withholding_tax_amount -= 36.0 * employee.dependent_juniors - -# Child Allowances -if employee.dependent_children: - if employee.dependent_children == 1: - withholding_tax_amount -= 36.0 - if employee.dependent_children == 2: - withholding_tax_amount -= 104.0 - if employee.dependent_children == 3: - withholding_tax_amount -= 275.0 - if employee.dependent_children == 4: - withholding_tax_amount -= 483.0 - if employee.dependent_children == 5: - withholding_tax_amount -= 713.0 - if employee.dependent_children == 6: - withholding_tax_amount -= 944.0 - if employee.dependent_children == 7: - withholding_tax_amount -= 1174.0 - if employee.dependent_children >= 8: - withholding_tax_amount -= 1428.0 + (employee.dependent_children - 8) * 256.0 - -result = - max(withholding_tax_amount, 0.0) - - - - - - - Withholding Tax - P.P - 102 - none - code - -taux = [(8460.0, 0), (10830.0, 0.1917), - (13775.0, 0.2120), (16520.0, 0.2625), - (18690.0, 0.3130), (20870.0, 0.3433), - (25230.0, 0.3634), (27450.0, 0.3937), - (36360.0, 0.4239), (47480.0, 0.4744)] - -def find_taux(x): - for a, b in taux: - if x <= a: - return b - return 0.535 - -# Up to 12 children -children_exoneration = [0.0, 13329.0, 16680.0, 21820.0, 27560.0, 33300.0, 39040.0, 44780.0, 50520.0, 56260.0, 62000.0, 67740.0, 73480.0] -# Only if no more than 5 children -children_reduction = [(0, 0), (22940.0, 0.075), (22940.0, 0.2), (25235.0, 0.35), (29825.0, 0.55), (32120.0, 0.75)] - -n = employee.dependent_children -yearly_revenue = categories.GROSS * 12.0 - -if 0 < n < 13 and yearly_revenue <= children_exoneration[n]: - yearly_revenue = yearly_revenue - (children_exoneration[n] - yearly_revenue) - -if n <= 5 and yearly_revenue <= children_reduction[n][0]: - withholding_tax_amount = yearly_revenue * find_taux(yearly_revenue) * (1 - children_reduction[n][1]) -else: - withholding_tax_amount = yearly_revenue * find_taux(yearly_revenue) -result = -withholding_tax_amount / 12.0 - - - - - - Withholding Tax - P.P - 102 - none - code - -taux = [(8460.0, 0), (10830.0, 0.2322), - (13775.0, 0.2523), (16520.0, 0.3028), - (18690.0, 0.2533), (20870.0, 0.3836), - (25230.0, 0.4038), (27450.0, 0.4341), - (36360.0, 0.4644), (47480.0, 0.5148)] - -def find_taux(x): - for a, b in taux: - if x <= a: - return b - return 0.535 - -# Up to 12 children -children_exoneration = [0.0, 13329.0, 16680.0, 21820.0, 27560.0, 33300.0, 39040.0, 44780.0, 50520.0, 56260.0, 62000.0, 67740.0, 73480.0] -# Only if no more than 5 children -children_reduction = [(0, 0), (22940.0, 0.075), (22940.0, 0.2), (25235.0, 0.35), (29825.0, 0.55), (32120.0, 0.75)] - -n = employee.dependent_children -yearly_revenue = categories.GROSS * 12.0 - -if 0 < n < 13 and yearly_revenue <= children_exoneration[n]: - yearly_revenue = yearly_revenue - (children_exoneration[n] - yearly_revenue) - -if n <= 5 and yearly_revenue <= children_reduction[n][0]: - withholding_tax_amount = yearly_revenue * find_taux(yearly_revenue) * (1 - children_reduction[n][1]) -else: - withholding_tax_amount = yearly_revenue * find_taux(yearly_revenue) -result = -withholding_tax_amount / 12.0 - - - - - - ATN Company Car - ATN.CAR - 70 - python - -result = bool(contract.transport_mode_car and contract.car_atn) - - code - -result = contract.car_atn - - - - - ATN Internet - ATN.INT - 16 - python - result = bool(contract.internet) - code - result = 5.0 - - - - ATN Mobile - ATN.MOB - 17 - python - result = bool(contract.mobile) - code - result = 12.0 - - - - ATN Company Car - ATN.CAR.2 - 160 - python - -result = bool(contract.transport_mode_car and contract.car_atn) - - code - -result = -contract.car_atn - - - - - ATN Internet - ATN.INT.2 - 161 - python - result = contract.internet - code - result = -5.0 - - - - ATN Mobile - ATN.MOB.2 - 162 - python - result = contract.mobile - code - result = -12.0 - - - - Commission on Target - COM - 170 - python - result = bool(contract.commission_on_target) - code - result = contract.warrant_value_employee / 12.0 - - - - - Retain on Meal Voucher - MEAL_V_EMP - 165 - python - result = bool(contract.meal_voucher_amount) - percentage - -100.0 - contract.meal_voucher_amount - contract.meal_voucher_paid_by_employer - worked_days.WORK100 and worked_days.WORK100.number_of_days - - - - - - Reimbursement of travel expenses - Tr.E - code - 65 - python - result = bool(contract.others_reimbursed_amount) - result = contract.others_reimbursed_amount - - - - Representation Fees - REP.FEES - code - 171 - python - result = bool(contract.representation_fees) - result = contract.representation_fees - - - - Intellectual Property - IP - code - 172 - python - result = bool(contract.ip) - result = categories.GROSSIP * contract.ip_wage_rate / 100.0 - - - - Intellectual Property Income Deduction - IP.DED - code - 173 - python - result = bool(contract.ip) - -tax_rate = 0.15 -ip_amount = categories.GROSSIP * contract.ip_wage_rate / 100.0 -if ip_amount >= 0.0 and ip_amount <= 15660: - tax_rate = tax_rate / 2.0 -elif ip_amount > 15660 and ip_amount <=31320: - tax_rate = tax_rate * 3.0 / 4.0 -result = -min(ip_amount * tax_rate, 11745) - - - - - Net Supplement - Net.S - code - 185 - python - result = bool(contract.additional_net_amount) - result = contract.additional_net_amount - - - - Net retained - Net.R - code - 190 - python - result = bool(contract.retained_net_amount) - result = -contract.retained_net_amount - - - - - - Double Holiday Pay - D.P - 2 - none - code - result = categories.BASIC * 0.92 - - - - - - Gross Salary - SALARY - 21 - none - code - result = SALARY * 0.85 - - - - - - Gross - GROSS - 101 - none - code - result = categories.DP + categories.ALW - - - - - Net - NET - 201 - none - code - result = categories.DP + categories.ALW + categories.DED - - - - CP 200 - Belgian Employee - - - - - - C.P.219 - Belgian Worker - - - - - - - CP 200 DH - Double Holidays Belgian Employee - - - - - Double Holidays Slip - - - - CP 200 EOYB - 13th Month - - - - - 13th Month Slip - - diff --git a/addons/l10n_be_hr_payroll/models/__init__.py b/addons/l10n_be_hr_payroll/models/__init__.py index 66a6f5ddc74..c7278c194c6 100644 --- a/addons/l10n_be_hr_payroll/models/__init__.py +++ b/addons/l10n_be_hr_payroll/models/__init__.py @@ -4,3 +4,4 @@ from . import l10n_be_hr_payroll from . import res_config_settings from . import res_users +from . import hr_payslip diff --git a/addons/l10n_be_hr_payroll/models/hr_payslip.py b/addons/l10n_be_hr_payroll/models/hr_payslip.py new file mode 100644 index 00000000000..35a7b8f83ff --- /dev/null +++ b/addons/l10n_be_hr_payroll/models/hr_payslip.py @@ -0,0 +1,226 @@ +#-*- coding:utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models +from odoo.tools import float_round + + +class Payslip(models.Model): + _inherit = 'hr.payslip' + + def _get_base_local_dict(self): + res = super()._get_base_local_dict() + res.update({ + 'compute_ip_deduction': compute_ip_deduction, + 'compute_withholding_taxes': compute_withholding_taxes, + 'compute_employment_bonus_employees': compute_employment_bonus_employees, + 'compute_special_social_cotisations': compute_special_social_cotisations, + 'compute_double_holiday_withholding_taxes': compute_double_holiday_withholding_taxes, + 'compute_thirteen_month_withholding_taxes': compute_thirteen_month_withholding_taxes, + }) + return res + + +def compute_withholding_taxes(payslip, categories, worked_days, inputs): + + def compute_basic_bareme(value): + if value <= 12860.0: + basic_bareme = value * 0.2675 + elif value <= 19630.0: + basic_bareme = 3440.05 + 0.428 * (value - 12860.00) + elif value <= 40470.00: + basic_bareme = 6337.61 + 0.4815 * (value - 19630.00) + else: + basic_bareme = 16372.07 + 0.535 * (value - 40470.0) + return float_round(basic_bareme, precision_rounding=0.01) + + def convert_to_month(value): + return float_round(value / 12.0, precision_rounding=0.01, rounding_method='DOWN') + + employee = payslip.contract_id.employee_id + # PART 1: Withholding tax amount computation + withholding_tax_amount = 0.0 + lower_bound = categories.GROSS - categories.GROSS % 15 + + # yearly_gross_revenue = Revenu Annuel Brut + yearly_gross_revenue = lower_bound * 12.0 + + # yearly_net_taxable_amount = Revenu Annuel Net Imposable + if yearly_gross_revenue <= 16033.33: + yearly_net_taxable_revenue = yearly_gross_revenue * (1.0 - 0.3) + else: + yearly_net_taxable_revenue = yearly_gross_revenue - 4810.0 + + # BAREME III: Non resident + if employee.resident_bool: + basic_bareme = compute_basic_bareme(yearly_net_taxable_revenue) + withholding_tax_amount = convert_to_month(basic_bareme) + else: + # BAREME I: Isolated or spouse with income + if employee.marital in ['divorced', 'single', 'widower'] or (employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='with income'): + basic_bareme = max(compute_basic_bareme(yearly_net_taxable_revenue) - 2065.1, 0.0) + withholding_tax_amount = convert_to_month(basic_bareme) + + # BAREME II: spouse without income + if employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='without income': + yearly_net_taxable_revenue_for_spouse = min(yearly_net_taxable_revenue * 0.3, 10930.0) + basic_bareme_1 = compute_basic_bareme(yearly_net_taxable_revenue_for_spouse) + basic_bareme_2 = compute_basic_bareme(yearly_net_taxable_revenue - yearly_net_taxable_revenue_for_spouse) + withholding_tax_amount = convert_to_month(max(basic_bareme_1 + basic_bareme_2 - 4130.20, 0)) + + # Reduction for isolated people and for other family charges + if employee.marital in ['divorced', 'single', 'widower'] or (employee.spouse_net_revenue > 0.0 or employee.spouse_other_net_revenue > 0.0): + if employee.marital in ['divorced', 'single', 'widower']: + withholding_tax_amount -= 26.0 + if employee.marital == 'widower' or (employee.marital in ['divorced', 'single', 'widower'] and employee.dependent_children): + withholding_tax_amount -= 36.0 + if employee.disabled: + withholding_tax_amount -= 36.0 + if employee.other_dependent_people and employee.dependent_seniors: + withholding_tax_amount -= 80 * employee.dependent_seniors + if employee.other_dependent_people and employee.dependent_juniors: + withholding_tax_amount -= 36.0 * employee.dependent_juniors + if employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='with income' and employee.spouse_net_revenue <= 230.0: + withholding_tax_amount -= 115.0 + if employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='with income' and not employee.spouse_net_revenue and employee.spouse_other_net_revenue <= 459.0: + withholding_tax_amount -= 229.5 + if employee.marital in ['married', 'cohabitant'] and employee.spouse_net_revenue == 0.0 and employee.spouse_other_net_revenue == 0.0: + if employee.disabled: + withholding_tax_amount -= 36.0 + if employee.disabled_spouse_bool: + withholding_tax_amount -= 36.0 + if employee.other_dependent_people and employee.dependent_seniors: + withholding_tax_amount -= 80.0 * employee.dependent_seniors + if employee.other_dependent_people and employee.dependent_juniors: + withholding_tax_amount -= 36.0 * employee.dependent_juniors + + # Child Allowances + if employee.dependent_children: + if employee.dependent_children == 1: + withholding_tax_amount -= 36.0 + if employee.dependent_children == 2: + withholding_tax_amount -= 104.0 + if employee.dependent_children == 3: + withholding_tax_amount -= 275.0 + if employee.dependent_children == 4: + withholding_tax_amount -= 483.0 + if employee.dependent_children == 5: + withholding_tax_amount -= 713.0 + if employee.dependent_children == 6: + withholding_tax_amount -= 944.0 + if employee.dependent_children == 7: + withholding_tax_amount -= 1174.0 + if employee.dependent_children >= 8: + withholding_tax_amount -= 1428.0 + (employee.dependent_children - 8) * 256.0 + + return - max(withholding_tax_amount, 0.0) + +def compute_special_social_cotisations(payslip, categories, worked_days, inputs): + employee = payslip.contract_id.employee_id + wage = categories.BASIC + if employee.resident_bool: + result = 0.0 + elif employee.marital in ['divorced', 'single', 'widower'] or (employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='without income'): + if 0.01 <= wage <= 1095.09: + result = 0.0 + elif 1095.10 <= wage <= 1945.38: + result = 0.0 + elif 1945.39 <= wage <= 2190.18: + result = -min((wage - 1945.38) * 0.076, 18.60) + elif 2190.19 <= wage <= 6038.82: + result = -min(18.60 + (wage - 2190.18) * 0.011, 60.94) + else: + result = -60.94 + elif employee.marital in ['married', 'cohabitant'] and employee.spouse_fiscal_status=='with income': + if 0.01 <= wage <= 1095.09: + result = 0.0 + elif 1095.10 <= wage <= 1945.38: + result = -9.30 + elif 1945.39 <= wage <= 2190.18: + result = -min(max((wage - 1945.38) * 0.076, 9.30), 18.60) + elif 2190.19 <= wage <= 6038.82: + result = -min(18.60 + (wage - 2190.18) * 0.011, 51.64) + else: + result = -51.64 + return result + +def compute_ip_deduction(payslip, categories, worked_days, inputs): + tax_rate = 0.15 + ip_amount = categories.GROSSIP * payslip.contract_id.ip_wage_rate / 100.0 + if 0.0 <= ip_amount <= 15660: + tax_rate = tax_rate / 2.0 + elif 15660.0 < ip_amount <= 31320: + tax_rate = tax_rate * 3.0 / 4.0 + return - min(ip_amount * tax_rate, 11745) + +def compute_employment_bonus_employees(payslip, categories, worked_days, inputs): + salary = categories.BRUT + if salary <= 1641.62: + result = 201.62 + elif salary <= 2560.57: + result = 201.62 - (0.2194 * (salary - 1641.62)) + return result + +def compute_double_holiday_withholding_taxes(payslip, categories, worked_days, inputs): + rates = [ + (8460.0, 0), (10830.0, 0.1917), + (13775.0, 0.2120), (16520.0, 0.2625), + (18690.0, 0.3130), (20870.0, 0.3433), + (25230.0, 0.3634), (27450.0, 0.3937), + (36360.0, 0.4239), (47480.0, 0.4744)] + + employee = payslip.contract_id.employee_id + def find_rates(x): + for a, b in rates: + if x <= a: + return b + return 0.535 + + # Up to 12 children + children_exoneration = [0.0, 13329.0, 16680.0, 21820.0, 27560.0, 33300.0, 39040.0, 44780.0, 50520.0, 56260.0, 62000.0, 67740.0, 73480.0] + # Only if no more than 5 children + children_reduction = [(0, 0), (22940.0, 0.075), (22940.0, 0.2), (25235.0, 0.35), (29825.0, 0.55), (32120.0, 0.75)] + + n = employee.dependent_children + yearly_revenue = categories.GROSS * 12.0 + + if 0 < n < 13 and yearly_revenue <= children_exoneration[n]: + yearly_revenue = yearly_revenue - (children_exoneration[n] - yearly_revenue) + + if n <= 5 and yearly_revenue <= children_reduction[n][0]: + withholding_tax_amount = yearly_revenue * find_rates(yearly_revenue) * (1 - children_reduction[n][1]) + else: + withholding_tax_amount = yearly_revenue * find_rates(yearly_revenue) + return -withholding_tax_amount / 12.0 + +def compute_thirteen_month_withholding_taxes(payslip, categories, worked_days, inputs): + employee = payslip.contract_id.employee_id + rates = [ + (8460.0, 0), (10830.0, 0.2322), + (13775.0, 0.2523), (16520.0, 0.3028), + (18690.0, 0.2533), (20870.0, 0.3836), + (25230.0, 0.4038), (27450.0, 0.4341), + (36360.0, 0.4644), (47480.0, 0.5148)] + + def find_rates(x): + for a, b in rates: + if x <= a: + return b + return 0.535 + + # Up to 12 children + children_exoneration = [0.0, 13329.0, 16680.0, 21820.0, 27560.0, 33300.0, 39040.0, 44780.0, 50520.0, 56260.0, 62000.0, 67740.0, 73480.0] + # Only if no more than 5 children + children_reduction = [(0, 0), (22940.0, 0.075), (22940.0, 0.2), (25235.0, 0.35), (29825.0, 0.55), (32120.0, 0.75)] + + n = employee.dependent_children + yearly_revenue = categories.GROSS * 12.0 + + if 0 < n < 13 and yearly_revenue <= children_exoneration[n]: + yearly_revenue = yearly_revenue - (children_exoneration[n] - yearly_revenue) + + if n <= 5 and yearly_revenue <= children_reduction[n][0]: + withholding_tax_amount = yearly_revenue * find_rates(yearly_revenue) * (1 - children_reduction[n][1]) + else: + withholding_tax_amount = yearly_revenue * find_rates(yearly_revenue) + return -withholding_tax_amount / 12.0 diff --git a/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py b/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py index e4063cc7139..8ad8a75f92a 100644 --- a/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py +++ b/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py @@ -6,6 +6,7 @@ from odoo import api, fields, models, _ from odoo.exceptions import ValidationError +# YTI TODO: Split me into 2 files class HrContract(models.Model): _inherit = 'hr.contract' @@ -61,12 +62,6 @@ class HrContract(models.Model): help="Number of days of paid leaves the employee gets per year.") wage_with_holidays = fields.Monetary(compute='_compute_wage_with_holidays', inverse='_inverse_wage_with_holidays', tracking=True, string="Wage update with holidays retenues") - additional_net_amount = fields.Monetary(string="Net Supplements", - tracking=True, - help="Monthly net amount the employee receives.") - retained_net_amount = fields.Monetary(sting="Net Retained", - tracking=True, - help="Monthly net amount that is retained on the employee's salary.") eco_checks = fields.Monetary("Eco Vouchers", help="Yearly amount the employee receives in the form of eco vouchers.") ip = fields.Boolean(default=False, tracking=True) diff --git a/addons/l10n_be_hr_payroll/views/l10n_be_hr_payroll_view.xml b/addons/l10n_be_hr_payroll/views/l10n_be_hr_payroll_view.xml index 9b2eb609e08..89ed8d461a7 100644 --- a/addons/l10n_be_hr_payroll/views/l10n_be_hr_payroll_view.xml +++ b/addons/l10n_be_hr_payroll/views/l10n_be_hr_payroll_view.xml @@ -26,16 +26,6 @@ / month -