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 -