Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] *hr_payroll: Benefits data and 13th month #30562

Closed
wants to merge 8 commits into from
@@ -16,6 +16,7 @@
<record id="holiday_status_hw" model="hr.leave.type">
<field name="name">Home Workings</field>
<field name="color_name">lightgreen</field>
<field name="allocation_type">no</field>
<field name="leave_notif_subtype_id" eval="ref('mt_leave_home_working')"/>
</record>

@@ -42,7 +43,7 @@
<!--Unpaid Leave -->
<record id="holiday_status_unpaid" model="hr.leave.type">
<field name="name">Unpaid</field>
<field name="allocation_type">fixed_allocation</field>
<field name="allocation_type">no</field>
<field name="validation_type">both</field>
<field name="color_name">brown</field>
<field name="unpaid" eval="True"/>
@@ -56,7 +56,7 @@
<field name="category_id" ref="hr_payroll.BASIC"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = contract.wage</field>
<field name="amount_python_compute">result = payslip.get_basic()</field>
</record>

<record id="hr_rule_taxable" model="hr.salary.rule">
@@ -109,11 +109,45 @@
<field name="code">WORK100</field>
</record>

<record id="benefit_type_leave" model="hr.benefit.type">
<field name="name">Generic Time Off</field>
<record id="benefit_type_home_working" model="hr.benefit.type">
<field name="name">Home Working</field>
<field name="code">WORK110</field>
<field name="color">2</field>
<field name="is_leave">True</field>
</record>
<record id="hr_holidays.holiday_status_hw" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_home_working"></field>
</record>

<record id="benefit_type_unpaid_leave" model="hr.benefit.type">
<field name="name">Unpaid</field>
<field name="code">LEAVE100</field>
<field name="color">3</field>
<field name="is_leave">True</field>
<field name="unpaid">True</field>
<field name="color">5</field>
</record>
<record id="hr_holidays.holiday_status_unpaid" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_unpaid_leave"></field>
</record>

<record id="benefit_type_sick_leave" model="hr.benefit.type">
<field name="name">Sick Time Off</field>
<field name="code">LEAVE110</field>
<field name="is_leave">True</field>
<field name="color">5</field>
</record>
<record id="hr_holidays.holiday_status_sl" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_sick_leave"></field>
</record>

<record id="benefit_type_legal_leave" model="hr.benefit.type">
<field name="name" eval="'Legal Leaves '+time.strftime('%Y')"/>
<field name="code">LEAVE120</field>
<field name="is_leave">True</field>
<field name="color">5</field>
</record>
<record id="hr_holidays.holiday_status_cl" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_legal_leave"></field>
</record>

</data>
@@ -146,60 +146,10 @@
<field name="state">open</field>
</record>

<!-- Benefits -->
<record id="benefit_type_home_working" model="hr.benefit.type">
<field name="name">Home Working</field>
<field name="code">WORK200</field>
<field name="color">2</field>
</record>

<record id="benefit_type_extra_hours" model="hr.benefit.type">
<field name="name">Extra hours</field>
<field name="code">WORK300</field>
<field name="color">2</field>
</record>

<record id="benefit_type_long_leave" model="hr.benefit.type">
<field name="name">Long Term Time Off</field>
<field name="code">LEAVE200</field>
<field name="is_leave">True</field>
<field name="color">4</field>
</record>

<record id="benefit_type_unpaid_leave" model="hr.benefit.type">
<field name="name">Unpaid Time Off</field>
<field name="code">LEAVE300</field>
<field name="is_leave">True</field>
<field name="color">5</field>
</record>

<record id="benefit_type_sick_leave" model="hr.benefit.type">
<field name="name">Sick Time Off</field>
<field name="code">LEAVE400</field>
<field name="is_leave">True</field>
<field name="color">5</field>
</record>

<!-- add benefit type to leave type -->
<record id="hr_holidays.holiday_status_sl" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_sick_leave"></field>
<field name="name">Extra Work</field>
<field name="color">0</field>
<field name="code">WORK112</field>
</record>

<record id="hr_holidays.holiday_status_unpaid" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_unpaid_leave"></field>
</record>

<record id="hr_holidays.holiday_status_hw" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_home_working"></field>
</record>

<record id="hr_holidays.holiday_status_cl" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_leave"></field>
</record>

<record id="hr_holidays.holiday_status_comp" model="hr.leave.type">
<field name="benefit_type_id" ref="benefit_type_leave"></field>
</record>

</odoo>

@@ -60,3 +60,6 @@ def sum(self, code, from_date, to_date=None):
(self.employee_id, from_date, to_date, code))
res = self.env.cr.fetchone()
return res and res[0] or 0.0

def get_basic(self):
return self.dict._get_basic()
@@ -282,12 +282,18 @@ class HrBenefitType(models.Model):
_description = 'hr.benefit.type'

name = fields.Char(required=True)
code = fields.Char()
code = fields.Char(required=True, help="The code that can be used in the salary rules")
color = fields.Integer(default=1) # Will be used with the new calendar/kanban view
sequence = fields.Integer(default=25)
sequence = fields.Integer(default=10, required=True)
active = fields.Boolean('Active', default=True,
help="If the active field is set to false, it will allow you to hide the benefit type without removing it.")
is_leave = fields.Boolean(default=False, string="Leave")
unpaid = fields.Boolean(default=False, string="Unpaid")

_sql_constraints = [
('_unique_code', 'unique (code)', "Benefit type codes must be unique."),
]


class Contacts(models.Model):
""" Personnal calendar filter """
@@ -139,7 +139,10 @@ def action_validate(self):
def action_refuse(self):
super(HrLeave, self).action_refuse()
benefits = self.env['hr.benefit'].search([('leave_id', 'in', self.ids)])
benefits.write({'display_warning': False, 'active': False})
benefits.write({'display_warning': False})

# Archive associated leave benefits
benefits.filtered(lambda b: b.benefit_type_id.is_leave).write({'active': False})
return True

def _get_number_of_days(self, date_from, date_to, employee_id):
@@ -66,11 +66,22 @@ class HrPayslip(models.Model):
payslip_run_id = fields.Many2one('hr.payslip.run', string='Batche Name', readonly=True,
copy=False, states={'draft': [('readonly', False)], 'verify': [('readonly', False)]}, ondelete='cascade')
compute_date = fields.Date('Computed On')
unpaid_amount = fields.Float(compute='_compute_unpaid_deduction', digits=dp.get_precision('Payroll'))

basic_wage = fields.Monetary(compute='_compute_basic_net')
net_wage = fields.Monetary(compute='_compute_basic_net')
currency_id = fields.Many2one(related='contract_id.currency_id')

@api.multi
def _compute_unpaid_deduction(self):
for payslip in self:
payslip.unpaid_amount = payslip._get_unpaid_deduction()

@api.multi
def _get_basic(self):
self.ensure_one()
return self.contract_id.wage - self._get_unpaid_deduction()

@api.multi
def _compute_basic_net(self):
for payslip in self:
@@ -167,8 +178,7 @@ def get_worked_day_lines(self, contract, date_from, date_to):
if hours:
line = {
'name': benefit_type.name,
'sequence': benefit_type.sequence,
'code': benefit_type.code,
'benefit_type_id': benefit_type.id,
'number_of_days': hours / calendar.hours_per_day, # n_days returned by benefit_days_data doesn't make sense for extra work
'number_of_hours': hours,
}
@@ -288,6 +298,8 @@ def onchange_employee(self):
for r in worked_days_line_ids:
worked_days_lines += worked_days_lines.new(r)
self.worked_days_line_ids = worked_days_lines
self._compute_worked_days_lines_amount()
return

@api.onchange('struct_id')
def _onchange_struct_id(self):
@@ -296,6 +308,24 @@ def _onchange_struct_id(self):
payslip_name = self.struct_id.payslip_name or _('Salary Slip')
self.name = '%s - %s - %s' % (payslip_name, self.employee_id.name, tools.ustr(babel.dates.format_date(date=ttyme, format='MMMM-y', locale=locale)))

def _get_unpaid_deduction(self):
self.ensure_one()
days_per_week = len(set(self.contract_id.resource_calendar_id.normal_attendance_ids.mapped('dayofweek')))
if days_per_week:
avg_per_day = self.contract_id.wage * 3 / 13 / days_per_week # there are always 13 weeks in 3 months
unpaid_worked_days_lines = self.worked_days_line_ids.filtered(lambda line: line.benefit_type_id.unpaid)
return sum(unpaid_worked_days_lines.mapped('number_of_days')) * avg_per_day
return 0.0

def _compute_worked_days_lines_amount(self):
for payslip in self:
deduction = payslip._get_unpaid_deduction()
basic = payslip.contract_id.wage - deduction
paid_worked_days_lines = self.worked_days_line_ids.filtered(lambda line: not line.benefit_type_id.unpaid)
total_paid_days = sum(paid_worked_days_lines.mapped('number_of_days'))
for line in paid_worked_days_lines:
line.amount = line.number_of_days / total_paid_days * basic

def get_salary_line_total(self, code):
self.ensure_one()
line = self.line_ids.filtered(lambda line: line.code == code)
@@ -365,12 +395,15 @@ class HrPayslipWorkedDays(models.Model):

name = fields.Char(string='Description', required=True)
payslip_id = fields.Many2one('hr.payslip', string='Pay Slip', required=True, ondelete='cascade', index=True)
sequence = fields.Integer(required=True, index=True, default=10)
code = fields.Char(required=True, help="The code that can be used in the salary rules")
benefit_type_id = fields.Many2one('hr.benefit.type', required=True)
sequence = fields.Integer(related='benefit_type_id.sequence')
code = fields.Char(related='benefit_type_id.code')
unpaid = fields.Boolean(related='benefit_type_id.unpaid')
number_of_days = fields.Float(string='Number of Days')
number_of_hours = fields.Float(string='Number of Hours')
contract_id = fields.Many2one(related='payslip_id.contract_id', string='Contract', required=True,
help="The contract for which applied this worked days")
amount = fields.Float(digits=dp.get_precision('Payroll'))


class HrPayslipInput(models.Model):
@@ -78,7 +78,7 @@ class ResourceCalendarLeave(models.Model):
class ResourceMixin(models.AbstractModel):
_inherit = "resource.mixin"

def get_benefit_days_data(self, benefit_type, from_datetime, to_datetime, calendar=None):
def get_benefit_days_data(self, benefit_types, from_datetime, to_datetime, calendar=None):
"""
By default the resource calendar is used, but it can be
changed using the `calendar` argument.
@@ -88,20 +88,23 @@ def get_benefit_days_data(self, benefit_type, from_datetime, to_datetime, calend
"""
resource = self.resource_id
calendar = calendar or self.resource_calendar_id
benefit_type_ids = benefit_type.ids
if benefit_type == self.env.ref('hr_payroll.benefit_type_attendance'): # special case for global attendances
benefit_type_ids += [False]# no benefit type = normal/global attendance
domain = [('benefit_type_id', 'in', benefit_type_ids)]

type_leave = benefit_types.filtered(lambda t: t.is_leave)
type_attendance = benefit_types - type_leave

leave_domain = [('benefit_type_id', 'in', type_leave.ids)]
attendance_type_ids = type_attendance.ids
if self.env.ref('hr_payroll.benefit_type_attendance') in type_attendance: # special case for global attendances
attendance_type_ids += [False] # no benefit type = normal/global attendance
attendance_domain = [('benefit_type_id', 'in', attendance_type_ids)]

# naive datetimes are made explicit in UTC
from_datetime = self._timezone_datetime(from_datetime)
to_datetime = self._timezone_datetime(to_datetime)

day_total = self._get_day_total(from_datetime, to_datetime, calendar, resource)
# actual hours per day
if benefit_type.is_leave:
intervals = calendar._attendance_intervals(from_datetime, to_datetime, resource) & calendar._leave_intervals(from_datetime, to_datetime, resource, domain) # use domain to only retrieve leaves of this type
else:
intervals = calendar._attendance_intervals(from_datetime, to_datetime, resource, domain) - calendar._leave_intervals(from_datetime, to_datetime, resource)

return self._get_days_data(intervals, day_total)
leave_intervals = calendar._attendance_intervals(from_datetime, to_datetime, resource) & calendar._leave_intervals(from_datetime, to_datetime, resource, leave_domain) # use domain to only retrieve leaves of this type
attendance_intervals = calendar._attendance_intervals(from_datetime, to_datetime, resource, attendance_domain) - calendar._leave_intervals(from_datetime, to_datetime, resource)

return self._get_days_data(leave_intervals | attendance_intervals, day_total)
@@ -5,3 +5,4 @@
from . import test_benefit
from . import test_multi_contract
from . import test_calendar_sync
from . import test_payslip_computation
@@ -51,14 +51,32 @@ def setUp(self):
self.benefit_type_leave = self.env['hr.benefit.type'].create({
'name': 'Leave',
'is_leave': True,
'code': 'LEAVE100'
'code': 'LEAVE101'
})
self.benefit_type_unpaid = self.env['hr.benefit.type'].create({
'name': 'Unpaid Leave',
'is_leave': True,
'unpaid': True,
'code': 'LEAVE222'
})
self.leave_type_unpaid = self.env['hr.leave.type'].create({
'name': 'Unpaid Leaves',
'time_type': 'leave',
'allocation_type': 'no',
'benefit_type_id': self.benefit_type_unpaid.id
})
self.leave_type = self.env['hr.leave.type'].create({
'name': 'Legal Leaves',
'name': 'Paid Time Off',
'time_type': 'leave',
'allocation_type': 'no',
'benefit_type_id': self.benefit_type_leave.id
})
self.benefit_type = self.env['hr.benefit.type'].create({
'name': 'Extra attendance',
'is_leave': False,
'code': 'WORK222'
})


class TestPayslipContractBase(TestPayslipBase):

Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.