From e25dda3790e448efd934b52f672a8b83f688e907 Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Wed, 26 Apr 2017 11:33:57 +0200 Subject: [PATCH] [IMP] l10n_be_hr_payroll: Update all advantages + Refactor the views PURPOSE ======= A very small amount of all the existing belgian advantages for employees and workers. The goal of this commit is to implement/update all the advantages and refactor the contract form view to be sexy and usable. SPECIFICATION ============= Contract ~~~~~~~~ - Add advantages: - Transport mode: - Company car - Public transport - Others - Thirteen Month, Double Holidays - Commissions on target + Integration with warrants - Fuel card - Internet - Mobile - Meal Vouchers - Holidays and holidays compensations - Eco checks - Compute employers costs. It's influenced obviously by: - The employee wage - The social security service fees (UCM, ...) - The various advantages - The social security contributions - Implement all the mechanisms to compute those different values and advantages - Refactor the contract form view to be sexy Employee ~~~~~~~~ - Add missing fields to be able to compute correcly the Net Salary from the Gross Salary: - Disabled spouse, spouse fiscal situation and revenues - Dependent people (childen, juniors, seniors, disabled) --- addons/l10n_be_hr_payroll/__manifest__.py | 3 +- .../data/l10n_be_hr_payroll_demo.xml | 34 --- .../models/l10n_be_hr_payroll.py | 242 +++++++++++++++++- .../views/l10n_be_hr_payroll_view.xml | 156 +++++++++-- 4 files changed, 362 insertions(+), 73 deletions(-) delete mode 100644 addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_demo.xml diff --git a/addons/l10n_be_hr_payroll/__manifest__.py b/addons/l10n_be_hr_payroll/__manifest__.py index dbdc45db691..12f4c72db74 100644 --- a/addons/l10n_be_hr_payroll/__manifest__.py +++ b/addons/l10n_be_hr_payroll/__manifest__.py @@ -20,9 +20,8 @@ Belgian Payroll Rules. * Salary Maj, ONSS, Withholding Tax, Child Allowance, ... """, - 'demo': ['data/l10n_be_hr_payroll_demo.xml'], 'website': 'https://www.odoo.com/page/accounting', - 'data':[ + 'data': [ 'views/l10n_be_hr_payroll_view.xml', 'data/l10n_be_hr_payroll_data.xml', 'data/hr.salary.rule.csv', diff --git a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_demo.xml b/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_demo.xml deleted file mode 100644 index f45f732dc99..00000000000 --- a/addons/l10n_be_hr_payroll/data/l10n_be_hr_payroll_demo.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - Developer's Contract (Belgium payroll C.P. 218) - - - - - - - - - - - single - - - - 54 Rue Jean Baptiste Colette BP - Attiches - 59551 - - onss@yourcompany.example.com - - - Shanghai Branch - Anshun Road 83, Room 1603 - - Shanghai - 200052 - - fiscal@yourcompany.example.com - - 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 c8ca237988b..17436cd10c1 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 @@ -1,30 +1,246 @@ # -*- coding:utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models -from odoo.addons import decimal_precision as dp +from odoo import api, fields, models, _ + +from odoo.exceptions import ValidationError class HrContract(models.Model): _inherit = 'hr.contract' - travel_reimbursement_amount = fields.Float(string='Reimbursement of travel expenses', digits=dp.get_precision('Payroll')) - car_company_amount = fields.Float(string='Company car employer', digits=dp.get_precision('Payroll')) - car_employee_deduction = fields.Float(string='Company Car Deduction for Worker', digits=dp.get_precision('Payroll')) - misc_onss_deduction = fields.Float(string='Miscellaneous exempt ONSS', digits=dp.get_precision('Payroll')) - meal_voucher_amount = fields.Float(string='Check Value Meal', digits=dp.get_precision('Payroll')) - meal_voucher_employee_deduction = fields.Float(string='Check Value Meal - by worker', digits=dp.get_precision('Payroll')) - insurance_employee_deduction = fields.Float(string='Insurance Group - by worker', digits=dp.get_precision('Payroll')) - misc_advantage_amount = fields.Float(string='Benefits of various nature', digits=dp.get_precision('Payroll')) - additional_net_amount = fields.Float(string='Net supplements', digits=dp.get_precision('Payroll')) - retained_net_amount = fields.Float('Net retained ', digits=dp.get_precision('Payroll')) + transport_mode = fields.Selection([ + ('company_car', 'Company car'), + ('public_transport', 'Public Transport'), + ('others', 'Other'), + ], string="Transport", default='company_car') + car_atn = fields.Monetary(string='ATN Company Car') + public_transport_employee_amount = fields.Monetary('Paid by the employee (Monthly)') + thirteen_month = fields.Monetary(compute='_compute_holidays_advantages', string='13th Month') + double_holidays = fields.Monetary(compute='_compute_holidays_advantages', string='Double holidays') + warrant_value_employee = fields.Monetary(compute='_compute_warrants_cost', string="Warrant value for the employee") + + # Employer costs fields + final_yearly_costs = fields.Monetary(compute='_compute_final_yearly_costs', readonly=False, + string='Total Employee Cost', groups="hr.group_hr_manager") + monthly_yearly_costs = fields.Monetary(compute='_compute_monthly_yearly_costs', string='Monthly Equivalent Cost', readonly=True) + ucm_insurance = fields.Monetary(compute='_compute_ucm_insurance', string="Social Secretary Costs") + social_security_contributions = fields.Monetary(compute='_compute_social_security_contributions', string="Social Security Contributions") + yearly_cost_before_charges = fields.Monetary(compute='_compute_yearly_cost_before_charges', string="Yearly Costs Before Charges") + meal_voucher_paid_by_employer = fields.Monetary(compute='_compute_meal_voucher_paid_by_employer', string="Meal Voucher Paid by Employer") + company_car_total_depreciated_cost = fields.Monetary() + public_transport_reimbursed_amount = fields.Monetary(string='Reimbursed amount', + compute='_compute_public_transport_reimbursed_amount', readonly=False, store=True) + others_reimbursed_amount = fields.Monetary(string='Reimbursed amount') + transport_employer_cost = fields.Monetary(compute='_compute_transport_employer_cost', string="Employer cost from employee transports") + warrants_cost = fields.Monetary(compute='_compute_warrants_cost') + + # Advantages + commission_on_target = fields.Monetary(string="Commission on Target", default=1000) + fuel_card = fields.Monetary(string="Fuel Card", default=150) + internet = fields.Monetary(string="Internet", default=38, help="Your internet subscription will be paid up to this amount.") + representation_fees = fields.Monetary(string="Representation Fees", default=150.0) + mobile = fields.Monetary(string="Mobile", default=30, help="Your mobile subscription will be paid up to this amount.") + mobile_plus = fields.Monetary(string="International Communication", default=50, help="Your international mobile subscription will be paid up to this amount.") + meal_voucher_amount = fields.Monetary(string="Meal Vouchers", default=7.45) + holidays = fields.Float(string="Holidays", default=20) + holidays_compensation = fields.Monetary(compute='_compute_holidays_compensation', string="Holidays Compensation") + wage_with_holidays = fields.Monetary(compute='_compute_wage_with_holidays', sting="Wage update with holidays retenues") + additional_net_amount = fields.Monetary(string="Net Supplements") + retained_net_amount = fields.Monetary(sting="Net Retained") + eco_checks = fields.Monetary("Eco Vouchers", default=250) + + @api.depends('holidays', 'wage') + def _compute_wage_with_holidays(self): + for contract in self: + if contract.holidays > 20.0: + yearly_cost = contract.final_yearly_costs * (1.0 - (contract.holidays - 20.0) / 220.0) + contract.wage_with_holidays = contract._get_gross_from_employer_costs(yearly_cost) + else: + contract.wage_with_holidays = contract.wage + + @api.depends('transport_mode', 'company_car_total_depreciated_cost', + 'public_transport_reimbursed_amount', 'others_reimbursed_amount') + def _compute_transport_employer_cost(self): + for contract in self: + if contract.transport_mode == 'company_car': + contract.transport_employer_cost = contract.company_car_total_depreciated_cost + elif contract.transport_mode == 'public_transport': + contract.transport_employer_cost = contract.public_transport_reimbursed_amount + elif contract.transport_mode == 'others': + contract.transport_employer_cost == contract.others_reimbursed_amount + + @api.depends('commission_on_target') + def _compute_warrants_cost(self): + for contract in self: + contract.warrants_cost = contract.commission_on_target * 1.326 / 1.05 * 12.0 + contract.warrant_value_employee = contract.warrants_cost * 0.54 + + @api.depends('wage', 'fuel_card', 'representation_fees', 'transport_employer_cost', + 'internet', 'mobile', 'mobile_plus') + def _compute_yearly_cost_before_charges(self): + for contract in self: + contract.yearly_cost_before_charges = 12.0 * ( + contract.wage * (1.0 + 1.0 / 12.0) + + contract.fuel_card + + contract.representation_fees + + contract.internet + + contract.mobile + + contract.mobile_plus + + contract.transport_employer_cost + ) + + @api.depends('yearly_cost_before_charges', 'ucm_insurance', 'social_security_contributions', + 'social_security_contributions', 'double_holidays', 'warrants_cost', 'meal_voucher_paid_by_employer') + def _compute_final_yearly_costs(self): + for contract in self: + contract.final_yearly_costs = ( + contract.yearly_cost_before_charges + + contract.ucm_insurance + + contract.social_security_contributions + + contract.double_holidays + + contract.warrants_cost + + (220.0 * contract.meal_voucher_paid_by_employer) + ) + + @api.depends('holidays', 'final_yearly_costs') + def _compute_holidays_compensation(self): + for contract in self: + if contract.holidays < 20: + decrease_amount = contract.final_yearly_costs * (20.0 - contract.holidays) / 220.0 + contract.holidays_compensation = decrease_amount + else: + contract.holidays_compensation = 0.0 + + @api.onchange('final_yearly_costs') + def _onchange_final_yearly_costs(self): + self.wage = self._get_gross_from_employer_costs(self.final_yearly_costs) + + @api.depends('meal_voucher_amount') + def _compute_meal_voucher_paid_by_employer(self): + for contract in self: + contract.meal_voucher_paid_by_employer = contract.meal_voucher_amount * (1 - 0.1463) + + @api.depends('wage') + def _compute_social_security_contributions(self): + for contract in self: + total_wage = contract.wage * 13.0 + contract.social_security_contributions = (total_wage) * 0.3507 + + @api.depends('wage') + def _compute_ucm_insurance(self): + for contract in self: + contract.ucm_insurance = (contract.wage * 12.0) * 0.05 + + @api.depends('public_transport_employee_amount') + def _compute_public_transport_reimbursed_amount(self): + for contract in self: + contract.public_transport_reimbursed_amount = contract._get_public_transport_reimbursed_amount(contract.public_transport_employee_amount) + + def _get_public_transport_reimbursed_amount(self, amount): + return amount * 0.68 + + @api.depends('final_yearly_costs') + def _compute_monthly_yearly_costs(self): + for contract in self: + contract.monthly_yearly_costs = contract.final_yearly_costs / 12.0 + + @api.depends('wage') + def _compute_holidays_advantages(self): + for contract in self: + contract.double_holidays = contract.wage * 0.92 + contract.thirteen_month = contract.wage + + @api.onchange('mobile', 'mobile_plus') + def _onchange_mobile(self): + if self.mobile_plus and not self.mobile: + raise ValidationError(_('You should have a mobile subscription to select an international communication amount!')) + + def _get_internet_amount(self, has_internet): + if has_internet: + return self.get_attribute('internet', 'default_value') + else: + return 0.0 + + def _get_mobile_amount(self, has_mobile, international_communication): + if has_mobile and international_communication: + return self.get_attribute('mobile', 'default_value') + self.get_attribute('mobile_plus', 'default_value') + elif has_mobile: + return self.get_attribute('mobile', 'default_value') + else: + return 0.0 + + def _get_gross_from_employer_costs(self, yearly_cost): + contract = self + remaining_for_gross = yearly_cost \ + - 12.0 * contract.representation_fees \ + - 12.0 * contract.fuel_card \ + - 12.0 * contract.internet \ + - 12.0 * (contract.mobile + contract.mobile_plus) \ + - 12.0 * contract.transport_employer_cost \ + - (1.326 / 1.05 * 12.0) * contract.commission_on_target \ + - 220.0 * contract.meal_voucher_paid_by_employer + gross = remaining_for_gross / (12.0 * 0.05 + 13.0 + 13.0 * 0.3507 + 0.92) + return gross class HrEmployee(models.Model): _inherit = 'hr.employee' - spouse_fiscal_status = fields.Selection([('without income','Without Income'),('with income','With Income')], string='Tax status for spouse') + spouse_fiscal_status = fields.Selection([ + ('without income', 'Without Income'), + ('with income', 'With Income') + ], string='Tax status for spouse') + disabled = fields.Boolean(string="Disabled", help="If the employee is declared disabled by law") disabled_spouse_bool = fields.Boolean(string='Disabled Spouse', help='if recipient spouse is declared disabled by law') disabled_children_bool = fields.Boolean(string='Disabled Children', help='if recipient children is/are declared disabled by law') resident_bool = fields.Boolean(string='Nonresident', help='if recipient lives in a foreign country') disabled_children_number = fields.Integer('Number of disabled children') + dependent_children = fields.Integer(compute='_compute_dependent_children', string='Considered number of dependent children') + other_dependent_people = fields.Boolean(string="Other Dependent People", help="If other people are dependent on the employee") + other_senior_dependent = fields.Integer('# seniors (>=65)', help="Number of seniors dependent on the employee, including the disabled ones") + other_disabled_senior_dependent = fields.Integer('# disabled seniors (>=65)') + other_juniors_dependent = fields.Integer('# people (<65)', help="Number of juniors dependent on the employee, including the disabled ones") + other_disabled_juniors_dependent = fields.Integer('# disabled people (<65)') + dependent_seniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent seniors") + dependent_juniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent juniors") + spouse_net_revenue = fields.Float(string="Spouse Net Revenue", help="Own professional income, other than pensions, annuities or similar income") + spouse_other_net_revenue = fields.Float(string="Spouse Other Net Revenue", + help='Own professional income which is exclusively composed of pensions, annuities or similar income') + + @api.constrains('spouse_fiscal_status', 'spouse_net_revenue', 'spouse_other_net_revenue') + def _check_spouse_revenue(self): + for employee in self: + if employee.spouse_fiscal_status == 'with income' and not employee.spouse_net_revenue and not employee.spouse_other_net_revenue: + raise ValidationError(_("The revenue for the spouse can't be equal to zero is the fiscal status is 'With Income'.")) + + @api.onchange('spouse_fiscal_status') + def _onchange_spouse_fiscal_status(self): + self.spouse_net_revenue = 0.0 + self.spouse_other_net_revenue = 0.0 + + @api.onchange('disabled_children_bool') + def _onchange_disabled_children_bool(self): + self.disabled_children_number = 0 + + @api.onchange('other_dependent_people') + def _onchange_other_dependent_people(self): + self.other_senior_dependent = 0.0 + self.other_disabled_senior_dependent = 0.0 + self.other_juniors_dependent = 0.0 + self.other_disabled_juniors_dependent = 0.0 + + @api.depends('disabled_children_bool', 'disabled_children_number', 'children') + def _compute_dependent_children(self): + for employee in self: + if employee.disabled_children_bool: + employee.dependent_children = employee.children + employee.disabled_children_number + else: + employee.dependent_children = employee.children + + @api.depends('other_dependent_people', 'other_senior_dependent', + 'other_disabled_senior_dependent', 'other_juniors_dependent', 'other_disabled_juniors_dependent') + def _compute_dependent_people(self): + for employee in self: + employee.dependent_seniors = employee.other_senior_dependent + employee.other_disabled_senior_dependent + employee.dependent_juniors = employee.other_juniors_dependent + employee.other_disabled_juniors_dependent 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 7cf651f8019..b07a075956e 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 @@ -1,27 +1,124 @@ - - hr.contract.form.inherit - hr.contract - 20 - - - - - - - - - - - - - - - - - - + + hr.contract.form + hr.contract + + + + + + + + + + + + + + + + + + + + + + + hr.employee.inherit.form @@ -29,7 +126,6 @@ 30 - @@ -37,11 +133,23 @@ + + + + + + + + + + + + + -