[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)
This commit is contained in:
Yannick Tivisse
2017-04-26 15:08:09 +02:00
parent 10466fb58e
commit e25dda3790
4 changed files with 362 additions and 73 deletions
+1 -2
View File
@@ -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',
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Employee Contract -->
<record id="hr_payroll.hr_contract_firstcontract1" model="hr.contract">
<field name="name">Developer's Contract (Belgium payroll C.P. 218)</field>
<field name="struct_id" ref="hr_payroll_salary_structure_employee"/>
<field eval="2500.0" name="wage"/>
<field eval="6.0" name="meal_voucher_amount"/>
<field eval="1.09" name="meal_voucher_employee_deduction"/>
<field eval="250.0" name="car_company_amount"/>
<field eval="110" name="car_employee_deduction"/>
<field eval="50.0" name="additional_net_amount"/>
</record>
<record id="hr_payroll.hr_employee_payroll" model="hr.employee">
<field name="marital">single</field>
</record>
<record id="res_partner_onss" model="res.partner">
<field name="street">54 Rue Jean Baptiste Colette BP </field>
<field name="city">Attiches</field>
<field name="zip">59551</field>
<field name="country_id" ref="base.fr"/>
<field name="email">onss@yourcompany.example.com</field>
</record>
<record id="res_partner_pp" model="res.partner">
<field name="street">Shanghai Branch</field>
<field name="street2">Anshun Road 83, Room 1603 - </field>
<field name="city">Shanghai</field>
<field name="zip">200052</field>
<field name="country_id" ref="base.cn"/>
<field name="email">fiscal@yourcompany.example.com</field>
</record>
</odoo>
@@ -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
@@ -1,27 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_contract_form_l10n_be_inherit" model="ir.ui.view">
<field name="name">hr.contract.form.inherit</field>
<field name="model">hr.contract</field>
<field name="priority">20</field>
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='struct_id']" position="after">
<field name="travel_reimbursement_amount"/>
<field name="car_company_amount"/>
<field name="car_employee_deduction"/>
<field name="misc_onss_deduction"/>
<field name="meal_voucher_amount"/>
<field name="meal_voucher_employee_deduction"/>
<field name="misc_advantage_amount"/>
<field name="additional_net_amount"/>
<field name="retained_net_amount"/>
<field name="insurance_employee_deduction"/>
</xpath>
</data>
</field>
</record>
<record id="hr_contract_view_form" model="ir.ui.view">
<field name="name">hr.contract.form</field>
<field name="model">hr.contract</field>
<field name="inherit_id" ref="hr_payroll.hr_contract_form_inherit"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='wage']" position="after">
<label for="wage_with_holidays" attrs="{'invisible': [('holidays', '&lt;=', 20.0)]}"/>
<div class="o_row" attrs="{'invisible': [('holidays', '&lt;=', 20.0)]}">
<field name="wage_with_holidays" nolabel="1"/>
<span>/ month</span>
</div>
<label for="fuel_card"/>
<div class="o_row">
<field name="fuel_card" nolabel="1"/>
<span>/ month</span>
</div>
<label for="meal_voucher_amount"/>
<div class="o_row">
<field name="meal_voucher_amount" nolabel="1"/>
<span>/ worked day</span>
</div>
<label for="representation_fees"/>
<div class="o_row">
<field name="representation_fees" nolabel="1"/>
<span>/ month</span>
</div>
<label for="additional_net_amount"/>
<div class="o_row">
<field name="additional_net_amount" nolabel="1"/>
<span>/ month</span>
</div>
<label for="retained_net_amount"/>
<div class="o_row">
<field name="retained_net_amount" nolabel="1"/>
<span>/ month</span>
</div>
<label for="commission_on_target"/>
<div class="o_row">
<field name="commission_on_target" nolabel="1"/>
<span>/ month</span>
</div>
</xpath>
<xpath expr="//group[@name='salary_and_advantages']" position="after">
<group string="Employee Costs" groups="hr.group_hr_manager">
<field name="yearly_cost_before_charges" invisible="1"/>
<field name="social_security_contributions" invisible="1"/>
<field name="warrants_cost" invisible="1"/>
<field name="warrant_value_employee" invisible="1"/>
<field name="ucm_insurance" invisible="1"/>
<field name="meal_voucher_paid_by_employer" invisible="1"/>
<label for="final_yearly_costs"/>
<div class="o_row">
<field name="final_yearly_costs" nolabel="1"/>
<span>/ year</span>
</div>
<field name="monthly_yearly_costs"/>
</group>
<group string="Monthly Advantages in Nature">
<field name="transport_mode" widget="radio"/>
<field name="car_atn" attrs="{'invisible': [('transport_mode', '!=', 'company_car')]}" invisible="1"/>
<field name="transport_employer_cost" invisible="1"/>
<label string="Public Transport" attrs="{'invisible': [('transport_mode', '!=', 'public_transport')]}"/>
<div attrs="{'invisible': [('transport_mode', '!=', 'public_transport')]}">
<div>
<label for="public_transport_employee_amount" class="oe_inline"/>
<field name="public_transport_employee_amount" class="oe_inline"/>
</div>
<div>
<label for="public_transport_reimbursed_amount" class="oe_inline"/>
<field name="public_transport_reimbursed_amount" class="oe_inline"/>
</div>
</div>
<label string="Other" attrs="{'invisible': [('transport_mode', '!=', 'others')]}"/>
<div attrs="{'invisible': [('transport_mode', '!=', 'others')]}" class="o_row">
<field name="others_reimbursed_amount" class="oe_inline" nolabel="1"/>
<span>/ month</span>
</div>
<label for="internet"/>
<div class="o_row">
<field name="internet" nolabel="1"/>
<span>/ month</span>
</div>
<label for="mobile"/>
<div class="o_row">
<field name="mobile" nolabel="1"/>
<span>/ month</span>
</div>
<label for="mobile_plus"/>
<div class="o_row">
<field name="mobile_plus" nolabel="1"/>
<span>/ month</span>
</div>
</group>
<group string="Yearly Advantages">
<label for="eco_checks"/>
<div class="o_row">
<field name="eco_checks" nolabel="1"/>
<span>/ year</span>
</div>
<label for="thirteen_month"/>
<div class="o_row">
<field name="thirteen_month" nolabel="1"/>
<span>/ year</span>
</div>
<label for="double_holidays"/>
<div class="o_row">
<field name="double_holidays" nolabel="1"/>
<span>/ year</span>
</div>
<label for="holidays"/>
<div class="o_row">
<field name="holidays" nolabel="1"/>
<span>/ year</span>
</div>
<field name="holidays_compensation" invisible="1"/>
</group>
</xpath>
</field>
</record>
<record id="hr_employee_form__l10n_be_view_for" model="ir.ui.view">
<field name="name">hr.employee.inherit.form</field>
@@ -29,7 +126,6 @@
<field name="inherit_id" ref="hr_contract.hr_hr_employee_view_form2"/>
<field name="priority">30</field>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='vehicle_distance']" position="after">
<field name="resident_bool" eval="False"/>
</xpath>
@@ -37,11 +133,23 @@
<field name="spouse_fiscal_status" attrs="{'invisible':[('marital','!=','married')],'required':[('marital','=','married')]}" colspan="1" help="if spouse has professionnel income or not"/>
<field name="disabled_spouse_bool" attrs="{'invisible':[('marital','!=','married')]}" colspan="1"/>
</xpath>
<xpath expr="//field[@name='gender']" position="after">
<field name="disabled"/>
</xpath>
<xpath expr="//field[@name='children']" position="after">
<field name="disabled_children_bool"/>
<field name="disabled_children_number" attrs="{'invisible':[('disabled_children_bool','!=',True)]}" colspan="1" help="number of dependent children declared as disabled"/>
<field name="dependent_children" invisible="1"/>
<field name="other_dependent_people"/>
<field name="other_senior_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
<field name="other_disabled_senior_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
<field name="other_juniors_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
<field name="other_disabled_juniors_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
</xpath>
<xpath expr="//field[@name='spouse_fiscal_status']" position="after">
<field name="spouse_net_revenue" attrs="{'invisible': ['|', ('marital', '!=', 'married'), ('spouse_fiscal_status', '!=', 'with income')]}"/>
<field name="spouse_other_net_revenue" attrs="{'invisible': ['|', ('marital', '!=', 'married'), ('spouse_fiscal_status', '!=', 'with income')]}"/>
</xpath>
</data>
</field>
</record>
</odoo>