Files

172 lines
7.1 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.hr_work_entry_attendance.tests.common import HrWorkEntryAttendanceCommon
from odoo import Command
from datetime import datetime, date
from odoo.tests import tagged
@tagged('-at_install', 'post_install', 'payslip_overtime')
class TestPayslipOvertime(HrWorkEntryAttendanceCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.struct_type = cls.env['hr.payroll.structure.type'].create({
'name': 'Test Structure Type',
'wage_type': 'hourly',
})
cls.struct = cls.env['hr.payroll.structure'].create({
'name': 'Test Structure - Worker',
'type_id': cls.struct_type.id,
})
cls.payslip = cls.env['hr.payslip'].create({
'name': 'Test Payslip',
'employee_id': cls.employee.id,
'struct_id': cls.struct.id,
'date_from': '2022-01-01',
'date_to': '2022-01-31',
})
cls.ruleset = cls.env['hr.attendance.overtime.ruleset'].create({
'name': 'Ruleset schedule quantity',
'rule_ids': [Command.create({
'name': 'Rule schedule quantity',
'base_off': 'quantity',
'expected_hours_from_contract': True,
'quantity_period': 'day',
'paid': True,
})],
})
cls.contract.structure_type_id = cls.struct_type
cls.contract.hourly_wage = 100
cls.contract.overtime_from_attendance = True
cls.contract.ruleset_id = cls.ruleset
cls.company = cls.payslip.company_id
def test_overtime_outside_period(self):
# Right before the payslip period
self.env['hr.attendance.overtime.line'].create({
'employee_id': self.employee.id,
'date': date(2021, 12, 31),
'duration': 5,
})
# Right after the payslip period
self.env['hr.attendance.overtime.line'].create({
'employee_id': self.employee.id,
'date': date(2022, 2, 1),
'duration': 5,
})
# Since contract resource_calendar_id's default to the company's,
# we can just change the company's resource_calendar_id's timezone.
self.env.company.resource_calendar_id.tz = "Asia/Manila"
self.payslip._compute_worked_days_line_ids()
self.assertFalse(self.payslip.worked_days_line_ids.filtered(lambda w: w.code == 'OVERTIME'))
self.env.company.resource_calendar_id.tz = "Indian/Maldives"
self.payslip._compute_worked_days_line_ids()
self.assertFalse(self.payslip.worked_days_line_ids.filtered(lambda w: w.code == 'OVERTIME'))
self.env.company.resource_calendar_id.tz = "Europe/Brussels"
self.payslip._compute_worked_days_line_ids()
self.assertFalse(self.payslip.worked_days_line_ids.filtered(lambda w: w.code == 'OVERTIME'))
def test_with_overtime(self):
self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2022, 1, 3, 0, 0, 0),
'check_out': datetime(2022, 1, 3, 20, 0, 0),
})
self.payslip._compute_worked_days_line_ids()
self.assertEqual(self.payslip.worked_days_line_ids.filtered(lambda w: w.code == 'OVERTIME').number_of_hours, 11)
def test_with_negative_overtime(self):
self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2022, 1, 3, 9, 0, 0),
'check_out': datetime(2022, 1, 3, 12, 0, 0),
})
self.payslip._compute_worked_days_line_ids()
self.assertFalse(self.payslip.worked_days_line_ids.filtered(lambda w: w.code == 'OVERTIME'))
def test_with_overtime_calendar_contract(self):
self.contract.work_entry_source = 'calendar'
self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2022, 1, 3, 0, 0, 0),
'check_out': datetime(2022, 1, 3, 20, 0, 0),
})
self.payslip._compute_worked_days_line_ids()
self.assertEqual(self.payslip.worked_days_line_ids.filtered(lambda w: w.code == 'OVERTIME').number_of_hours, 11)
def test_overtime_duration_precision_no_leak_to_attendance(self):
calendar_8h = self.env['resource.calendar'].create({
'name': 'Classic 40h/week',
'attendance_ids': [
Command.create({
'name': f'Day {i}',
'dayofweek': str(i),
'hour_from': 9.0,
'hour_to': 17.0,
'day_period': 'morning',
})
for i in range(5)
],
})
self.contract.resource_calendar_id = calendar_8h
self.contract.hourly_wage = 3600
overtime_seconds = 5
scheduled_hours = 8.0
check_in = datetime(2022, 1, 3, 9, 0, 0)
check_out = datetime(2022, 1, 3, 17, 0, overtime_seconds)
self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': check_in,
'check_out': check_out,
})
self.payslip.version_id.generate_work_entries(
self.payslip.date_from, self.payslip.date_to, force=True
)
self.payslip._compute_worked_days_line_ids()
self.payslip.compute_sheet()
attendance_amount = sum(
self.payslip.worked_days_line_ids
.filtered(lambda w: w.code == 'WORK100')
.mapped('amount'),
0.0,
)
overtime_amount = sum(
self.payslip.worked_days_line_ids
.filtered(lambda w: w.code == 'OVERTIME')
.mapped('amount'),
0.0,
)
# hourly_wage=3600 with amount_rate=1.0: 1 monetary unit = 1 second of work
expected_attendance_amount = scheduled_hours * self.contract.hourly_wage
expected_overtime_amount = overtime_seconds / 3600 * self.contract.hourly_wage
self.assertAlmostEqual(attendance_amount, expected_attendance_amount, places=2)
self.assertAlmostEqual(overtime_amount, expected_overtime_amount, places=2)
def test_overtime_with_approval(self):
"""Test that the overtime is taken into account only when it's approved."""
self.company.write({
"attendance_overtime_validation": "by_manager"
})
attendance = self.env['hr.attendance'].create({
'employee_id': self.employee.id,
'check_in': datetime(2022, 1, 3, 0, 0, 0),
'check_out': datetime(2022, 1, 3, 20, 0, 0),
})
self.payslip._compute_worked_days_line_ids()
self.assertFalse(self.payslip.worked_days_line_ids.filtered(lambda w: w.code == 'OVERTIME'))
# Approve the overtime
attendance.action_approve_overtime()
self.payslip.version_id.generate_work_entries(self.payslip.date_from, self.payslip.date_to, force=True)
self.payslip._compute_worked_days_line_ids()
self.assertTrue(self.payslip.worked_days_line_ids.filtered(lambda w: w.code == 'OVERTIME'))