702 lines
31 KiB
Python
702 lines
31 KiB
Python
# -*- coding:utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import date, datetime, timedelta
|
|
|
|
from odoo.exceptions import UserError
|
|
from odoo.fields import Datetime
|
|
from odoo.tests.common import tagged
|
|
from odoo.addons.hr_payroll_holidays.tests.common import TestPayrollHolidaysBase
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
from freezegun import freeze_time
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestTimeoffDefer(TestPayrollHolidaysBase):
|
|
|
|
def test_no_defer(self):
|
|
#create payslip -> waiting or draft
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Donald Payslip',
|
|
'employee_id': self.emp.id,
|
|
})
|
|
|
|
# Puts the payslip to draft/waiting
|
|
payslip.compute_sheet()
|
|
|
|
#create a time off for our employee, validating it now should not put it as to_defer
|
|
leave = self.env['hr.leave'].create({
|
|
'name': 'Golf time',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': (date.today() + relativedelta(day=13)),
|
|
'request_date_to': (date.today() + relativedelta(day=16)),
|
|
})
|
|
leave.action_approve()
|
|
|
|
self.assertNotEqual(leave.payslip_state, 'blocked', 'Leave should not be to defer')
|
|
|
|
def test_to_defer(self):
|
|
#create payslip
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Donald Payslip',
|
|
'employee_id': self.emp.id,
|
|
})
|
|
|
|
# Puts the payslip to draft/waiting
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
|
|
#create a time off for our employee, validating it now should put it as to_defer
|
|
leave = self.env['hr.leave'].create({
|
|
'name': 'Golf time',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': (date.today() + relativedelta(day=13)),
|
|
'request_date_to': (date.today() + relativedelta(day=16)),
|
|
})
|
|
leave.action_approve()
|
|
self.assertEqual(leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
def test_multi_payslip_defer(self):
|
|
#A leave should only be set to defer if ALL colliding with the time period of the time off are in a done state
|
|
# it should not happen if a payslip for that time period is still in a waiting state
|
|
|
|
# create payslip -> waiting
|
|
waiting_payslip = self.env['hr.payslip'].create({
|
|
'name': 'Donald Payslip draft',
|
|
'employee_id': self.emp.id,
|
|
})
|
|
# payslip -> validated
|
|
done_payslip = self.env['hr.payslip'].create({
|
|
'name': 'Donald Payslip done',
|
|
'employee_id': self.emp.id,
|
|
})
|
|
|
|
# Puts the waiting payslip to draft/waiting
|
|
waiting_payslip.compute_sheet()
|
|
# Puts the done payslip to the done state
|
|
done_payslip.compute_sheet()
|
|
done_payslip.action_payslip_done()
|
|
|
|
#create a time off for our employee, validating it now should not put it as to_defer
|
|
leave = self.env['hr.leave'].create({
|
|
'name': 'Golf time',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': (date.today() + relativedelta(day=13)),
|
|
'request_date_to': (date.today() + relativedelta(day=16)),
|
|
})
|
|
leave.action_approve()
|
|
|
|
self.assertNotEqual(leave.payslip_state, 'blocked', 'Leave should not be to defer')
|
|
|
|
def test_payslip_paid_past(self):
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
})
|
|
|
|
payslip.compute_sheet()
|
|
self.assertEqual(payslip.state, 'draft')
|
|
self.assertTrue(payslip.line_ids)
|
|
|
|
leave_1 = self.env['hr.leave'].with_user(self.vlad).create({
|
|
'name': 'Tennis',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': '2022-01-12',
|
|
'request_date_to': '2022-01-12',
|
|
})
|
|
with freeze_time('2022-02-02'):
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
|
|
leave_1.sudo().action_approve()
|
|
self.assertEqual(leave_1.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
# A Simple User can request a leave if a payslip is paid
|
|
leave_2 = self.env['hr.leave'].with_user(self.vlad).create({
|
|
'name': 'Tennis',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': '2022-01-19',
|
|
'request_date_to': '2022-01-19',
|
|
})
|
|
leave_2.sudo().action_approve()
|
|
self.assertEqual(leave_2.payslip_state, 'blocked', 'Leave should be to defer')
|
|
try:
|
|
leave_2.with_user(self.env.ref('base.user_admin')).unlink()
|
|
except UserError:
|
|
self.fail('A leave created after payslip validation should be deletable.')
|
|
|
|
# Check overlapping periods with no payslip
|
|
leave_3 = self.env['hr.leave'].with_user(self.vlad).create({
|
|
'name': 'Tennis',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': '2022-01-31',
|
|
'request_date_to': '2022-02-01',
|
|
})
|
|
leave_3.sudo().action_approve()
|
|
self.assertEqual(leave_3.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
leave_4 = self.env['hr.leave'].with_user(self.vlad).create({
|
|
'name': 'Tennis',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': '2021-01-31',
|
|
'request_date_to': '2022-01-03',
|
|
})
|
|
leave_4.sudo().action_approve()
|
|
self.assertEqual(leave_4.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
def test_unlink_leave_after_payslip_generation(self):
|
|
"""
|
|
Ensures that a request for time off can be deleted after validating
|
|
the payslip for the related period.
|
|
"""
|
|
leave = self.env['hr.leave'].with_user(self.vlad).create({
|
|
'name': 'Leave',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': '2022-01-10',
|
|
'request_date_to': '2022-01-10',
|
|
})
|
|
self.assertEqual(leave.state, 'confirm')
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
})
|
|
payslip.compute_sheet()
|
|
with freeze_time(datetime.now() + timedelta(hours=1)): # Prevents non-determinism
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
try:
|
|
leave.with_user(self.env.ref('base.user_admin')).unlink()
|
|
except UserError:
|
|
self.fail('A leave that is not validated should be deletable, even after payslip validation.')
|
|
|
|
def test_report_to_next_month(self):
|
|
self.emp.version_ids.generate_work_entries(date(2022, 1, 1), date(2022, 2, 28))
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
|
|
leave = self.env['hr.leave'].new({
|
|
'name': 'Tennis',
|
|
'employee_id': self.emp.id,
|
|
'holiday_status_id': self.leave_type.id,
|
|
'request_date_from': date(2022, 1, 31),
|
|
'request_date_to': date(2022, 1, 31),
|
|
'request_hour_from': 7,
|
|
'request_hour_to': 18,
|
|
})
|
|
leave._compute_date_from_to()
|
|
leave = self.env['hr.leave'].create(leave._convert_to_write(leave._cache))
|
|
leave.action_approve()
|
|
self.assertEqual(leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
leave.action_report_to_next_month()
|
|
reported_work_entries = self.env['hr.work.entry'].search([
|
|
('employee_id', '=', self.emp.id),
|
|
('company_id', '=', self.env.company.id),
|
|
('state', '=', 'draft'),
|
|
('work_entry_type_id', '=', self.leave_type.work_entry_type_id.id),
|
|
('date', '>=', Datetime.to_datetime('2022-02-01')),
|
|
('date', '<=', datetime.combine(Datetime.to_datetime('2022-02-28'), datetime.max.time()))
|
|
])
|
|
self.assertEqual(reported_work_entries[0].date, date(2022, 2, 1))
|
|
self.assertEqual(reported_work_entries[0].duration, 8)
|
|
|
|
def test_report_to_next_month_overlap(self):
|
|
"""
|
|
If the time off overlap over 2 months, only report the exceeding part from january
|
|
In case leaves go over two months, only the leaves that are in the first month should be defered
|
|
"""
|
|
self.emp.version_ids.generate_work_entries(date(2022, 1, 1), date(2022, 2, 28))
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
|
|
leave = self.env['hr.leave'].new({
|
|
'name': 'Tennis',
|
|
'employee_id': self.emp.id,
|
|
'holiday_status_id': self.leave_type.id,
|
|
'request_date_from': date(2022, 1, 31),
|
|
'request_date_to': date(2022, 2, 2),
|
|
'request_hour_from': 7,
|
|
'request_hour_to': 18,
|
|
})
|
|
leave._compute_date_from_to()
|
|
leave = self.env['hr.leave'].create(leave._convert_to_write(leave._cache))
|
|
leave.action_approve()
|
|
self.assertEqual(leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
leave.action_report_to_next_month()
|
|
reported_work_entries = self.env['hr.work.entry'].search([
|
|
('employee_id', '=', self.emp.id),
|
|
('company_id', '=', self.env.company.id),
|
|
('state', '=', 'draft'),
|
|
('work_entry_type_id', '=', self.leave_type.work_entry_type_id.id),
|
|
('date', '>=', Datetime.to_datetime('2022-02-01')),
|
|
('date', '<=', datetime.combine(Datetime.to_datetime('2022-02-28'), datetime.max.time()))
|
|
])
|
|
self.assertEqual(len(reported_work_entries), 3)
|
|
self.assertEqual(list({we.date.day for we in reported_work_entries}), [1, 2, 3])
|
|
self.assertEqual(reported_work_entries[0].date, date(2022, 2, 1))
|
|
self.assertEqual(reported_work_entries[0].duration, 8)
|
|
|
|
def test_report_to_next_month_not_enough_days(self):
|
|
# If the time off contains too many days to be reported to next months, raise
|
|
self.emp.version_ids.generate_work_entries(date(2022, 1, 1), date(2022, 2, 28))
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
|
|
leave = self.env['hr.leave'].new({
|
|
'name': 'Tennis',
|
|
'employee_id': self.emp.id,
|
|
'holiday_status_id': self.leave_type.id,
|
|
'request_date_from': date(2022, 1, 1),
|
|
'request_date_to': date(2022, 1, 31),
|
|
'request_hour_from': 7,
|
|
'request_hour_to': 18,
|
|
})
|
|
leave._compute_date_from_to()
|
|
leave = self.env['hr.leave'].create(leave._convert_to_write(leave._cache))
|
|
leave.action_approve()
|
|
self.assertEqual(leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
with self.assertRaises(UserError):
|
|
leave.action_report_to_next_month()
|
|
|
|
def test_report_to_next_month_long_time_off(self):
|
|
# If the time off overlap over more than 2 months, raise
|
|
self.emp.version_ids.generate_work_entries(date(2022, 1, 1), date(2022, 2, 28))
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
|
|
leave = self.env['hr.leave'].new({
|
|
'name': 'Tennis',
|
|
'employee_id': self.emp.id,
|
|
'holiday_status_id': self.leave_type.id,
|
|
'request_date_from': date(2022, 1, 1),
|
|
'request_date_to': date(2022, 3, 10),
|
|
'request_hour_from': 7,
|
|
'request_hour_to': 18,
|
|
})
|
|
leave._compute_date_from_to()
|
|
leave = self.env['hr.leave'].create(leave._convert_to_write(leave._cache))
|
|
leave.action_approve()
|
|
self.assertEqual(leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
with self.assertRaises(UserError):
|
|
leave.action_report_to_next_month()
|
|
|
|
def test_report_to_next_month_2_months_time_off(self):
|
|
"""
|
|
In this test, we want to defer a timeoff with a not rounded duration (7.6h)
|
|
"""
|
|
self.emp.version_ids.generate_work_entries(date(2026, 2, 1), date(2026, 2, 28))
|
|
resource_calendar = self.env['resource.calendar'].create({
|
|
'name': 'Test Two Week Calendar No Breaks',
|
|
'hours_per_day': 7.6,
|
|
'hours_per_week': 38,
|
|
'full_time_required_hours': 38,
|
|
'attendance_ids': [
|
|
(0, 0, {'name': 'Monday', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 15.6, 'day_period': 'full_day'}),
|
|
(0, 0, {'name': 'Tuesday', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 15.6, 'day_period': 'full_day'}),
|
|
(0, 0, {'name': 'Wednesday', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 15.6, 'day_period': 'full_day'}),
|
|
(0, 0, {'name': 'Thursday', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 15.6, 'day_period': 'full_day'}),
|
|
(0, 0, {'name': 'Friday', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 15.6, 'day_period': 'full_day'}),
|
|
],
|
|
})
|
|
self.emp.resource_calendar_id = resource_calendar
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2026-02-01',
|
|
'date_to': '2026-02-28',
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
|
|
leave = self.env['hr.leave'].create([{
|
|
'name': 'Tennis',
|
|
'employee_id': self.emp.id,
|
|
'holiday_status_id': self.leave_type.id,
|
|
'request_date_from': date(2026, 2, 27),
|
|
'request_date_to': date(2026, 3, 5),
|
|
}])
|
|
leave.action_approve()
|
|
self.emp.version_ids.generate_work_entries(date(2026, 3, 1), date(2026, 3, 31))
|
|
self.assertEqual(leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
leave.action_report_to_next_month()
|
|
defered_work_entry = self.env['hr.work.entry'].search([
|
|
('date', '=', date(2026, 3, 6)),
|
|
('version_id', 'in', self.emp.version_ids.ids)
|
|
])
|
|
self.assertEqual(defered_work_entry.work_entry_type_id.code, 'LEAVETEST300')
|
|
|
|
def test_report_to_next_month_half_days(self):
|
|
self.leave_type.request_unit = 'half_day'
|
|
self.emp.version_ids.generate_work_entries(date(2022, 1, 1), date(2022, 2, 28))
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
|
|
leave = self.env['hr.leave'].new({
|
|
'name': 'Tennis',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': date(2022, 1, 31),
|
|
'request_date_to': date(2022, 1, 31),
|
|
'request_date_from_period': 'am',
|
|
'request_date_to_period': 'am',
|
|
})
|
|
leave._compute_date_from_to()
|
|
leave = self.env['hr.leave'].create(leave._convert_to_write(leave._cache))
|
|
|
|
leave.action_approve()
|
|
self.assertEqual(leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
leave.action_report_to_next_month()
|
|
reported_work_entries = self.env['hr.work.entry'].search([
|
|
('employee_id', '=', self.emp.id),
|
|
('company_id', '=', self.env.company.id),
|
|
('state', '=', 'draft'),
|
|
('work_entry_type_id', '=', self.leave_type.work_entry_type_id.id),
|
|
('date', '>=', Datetime.to_datetime('2022-02-01')),
|
|
('date', '<=', datetime.combine(Datetime.to_datetime('2022-02-28'), datetime.max.time()))
|
|
])
|
|
self.assertEqual(len(reported_work_entries), 1)
|
|
self.assertEqual(reported_work_entries[0].date, date(2022, 2, 1))
|
|
self.assertEqual(reported_work_entries[0].duration, 4)
|
|
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-02-01',
|
|
'date_to': '2022-02-28',
|
|
})
|
|
payslip.compute_sheet()
|
|
self.assertEqual(2, len(payslip.worked_days_line_ids))
|
|
self.assertTrue(any(wd.work_entry_type_id == self.leave_type.work_entry_type_id for wd in payslip.worked_days_line_ids))
|
|
leave_worked_day_line = payslip.worked_days_line_ids.filtered(lambda wd: wd.work_entry_type_id == self.leave_type.work_entry_type_id)
|
|
self.assertEqual(0.5, leave_worked_day_line.number_of_days)
|
|
self.assertEqual(4, leave_worked_day_line.number_of_hours)
|
|
|
|
def test_report_to_next_month_hourly(self):
|
|
self.leave_type.request_unit = 'hour'
|
|
self.emp.version_ids.generate_work_entries(date(2022, 1, 1), date(2022, 2, 28))
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
leave = self.env['hr.leave'].new({
|
|
'name': 'Tennis',
|
|
'holiday_status_id': self.leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'request_date_from': date(2022, 1, 31),
|
|
'request_date_to': date(2022, 1, 31),
|
|
'request_hour_from': 10.0,
|
|
'request_hour_to': 12.0,
|
|
})
|
|
leave._compute_date_from_to()
|
|
leave = self.env['hr.leave'].create(leave._convert_to_write(leave._cache))
|
|
|
|
leave.action_approve()
|
|
self.assertEqual(leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
leave.action_report_to_next_month()
|
|
reported_work_entries = self.env['hr.work.entry'].search([
|
|
('employee_id', '=', self.emp.id),
|
|
('company_id', '=', self.env.company.id),
|
|
('state', '=', 'draft'),
|
|
('work_entry_type_id', '=', self.leave_type.work_entry_type_id.id),
|
|
('date', '>=', Datetime.to_datetime('2022-02-01')),
|
|
('date', '<=', datetime.combine(Datetime.to_datetime('2022-02-28'), datetime.max.time()))
|
|
])
|
|
self.assertEqual(len(reported_work_entries), 1)
|
|
self.assertEqual(reported_work_entries[0].date, date(2022, 2, 1))
|
|
self.assertEqual(reported_work_entries[0].duration, 2)
|
|
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2022-02-01',
|
|
'date_to': '2022-02-28',
|
|
})
|
|
payslip.compute_sheet()
|
|
self.assertEqual(2, len(payslip.worked_days_line_ids))
|
|
self.assertTrue(any(wd.work_entry_type_id == self.leave_type.work_entry_type_id for wd in payslip.worked_days_line_ids))
|
|
leave_worked_day_line = payslip.worked_days_line_ids.filtered(lambda wd: wd.work_entry_type_id == self.leave_type.work_entry_type_id)
|
|
self.assertEqual(2, leave_worked_day_line.number_of_hours)
|
|
|
|
def test_defer_next_month_double_time_off(self):
|
|
"""
|
|
If you have a time off 5 days on Jun and 3 days on july, when you "defer it to next month"
|
|
it's only the 5 days of Jun that should be postponed to july.
|
|
"""
|
|
self.emp.version_ids._generate_work_entries(datetime(2023, 6, 1), datetime(2023, 7, 31))
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'toto payslip',
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2023-06-01',
|
|
'date_to': '2023-06-30',
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip.action_payslip_done()
|
|
self.assertEqual(payslip.state, 'validated')
|
|
|
|
leave_data = [{
|
|
'name': 'Paid Time Off',
|
|
'employee_id': self.emp.id,
|
|
'holiday_status_id': self.leave_type.id,
|
|
'request_date_from': '2023-06-26 00:00:00',
|
|
'request_date_to': '2023-06-30 23:59:59',
|
|
}, {
|
|
'name': 'Paid Time Off',
|
|
'employee_id': self.emp.id,
|
|
'holiday_status_id': self.leave_type.id,
|
|
'request_date_from': '2023-07-03 00:00:00',
|
|
'request_date_to': '2023-07-05 23:59:59',
|
|
}]
|
|
leaves = self.env['hr.leave'].create(leave_data)
|
|
leaves.action_approve()
|
|
leaves[0].action_report_to_next_month()
|
|
# reported work entries between the 1st of july 2023 to the 31st of july 2023
|
|
july_work_entries = self.env['hr.work.entry'].search([
|
|
('employee_id', '=', self.emp.id),
|
|
('company_id', '=', self.env.company.id),
|
|
('state', '=', 'draft'),
|
|
('work_entry_type_id', '=', self.leave_type.work_entry_type_id.id),
|
|
('date', '>=', Datetime.to_datetime('2023-07-01')),
|
|
('date', '<=', datetime.combine(Datetime.to_datetime('2023-07-31'), datetime.max.time()))
|
|
])
|
|
# The length of reported work entries is 8 because we are generating records for 8 days of leave.
|
|
# These leaves cover the period from July 3rd to July 12th, excluding July 1st and 2nd as they are designated holidays.
|
|
self.assertEqual(len(july_work_entries), 8)
|
|
self.assertEqual(list({we.date.day for we in july_work_entries}), [3, 4, 5, 6, 7, 10, 11, 12])
|
|
self.assertEqual(july_work_entries[0].date, date(2023, 7, 3))
|
|
self.assertEqual(july_work_entries[0].duration, 8)
|
|
|
|
def test_leave_overlaping_over_2_month(self):
|
|
"""
|
|
The part of the leave in the first month closed by a payslip should be deferred on the next month.
|
|
|
|
- A leave for the 2 whole weeks (2025-05-26 -> 2025-06-06) overlapping over 2 months
|
|
- May payslip computed and validated
|
|
- Compute the payslip of June
|
|
|
|
5 leave day from May should be deferred and added to the leave of june on work entries and payslip of june.
|
|
This should results in 10 leave day on June.
|
|
"""
|
|
# Generate the work entries
|
|
may_work_entries = self.emp.version_ids._generate_work_entries(
|
|
datetime.fromisoformat('2025-05-01'),
|
|
datetime.fromisoformat('2025-05-31')
|
|
)
|
|
june_work_entries = self.emp.version_ids._generate_work_entries(
|
|
datetime.fromisoformat('2025-06-01'),
|
|
datetime.fromisoformat('2025-06-30')
|
|
)
|
|
self.assertRecordValues((may_work_entries + june_work_entries).work_entry_type_id, [{'code': 'WORK100'}])
|
|
self.assertEqual(set(may_work_entries.mapped('state')), {'draft'})
|
|
|
|
# Create and validate payslips like an HR user would do:
|
|
# create a payslip batch, gets the payslips created through the wizard call and validate the batch
|
|
may_payslip_batch = self.env['hr.payslip.run'].create({
|
|
'name': 'May 2025 payslip batch',
|
|
'date_start': '2025-05-01',
|
|
'date_end': '2025-05-31',
|
|
'company_id': self.emp.company_id.id,
|
|
})
|
|
may_payslip_batch.generate_payslips(employee_ids=self.emp.ids)
|
|
may_payslip_batch.action_validate()
|
|
self.assertEqual(set(may_work_entries.mapped('state')), {'validated'})
|
|
|
|
# Create a time off overlaping over 2 months
|
|
overlapping_work_entry_type = self.env['hr.work.entry.type'].create({
|
|
'name': 'Annoying overlaping work entry type',
|
|
'is_leave': True,
|
|
'code': 'OVERLAPING101',
|
|
})
|
|
overlapping_leave_type = self.env['hr.leave.type'].create({
|
|
'name': 'Annoying overlaping leave type',
|
|
'work_entry_type_id': overlapping_work_entry_type.id,
|
|
'time_type': 'leave',
|
|
'requires_allocation': False,
|
|
})
|
|
overlapping_leave = self.env['hr.leave'].create({
|
|
'name': 'Annoying overlaping leave',
|
|
'holiday_status_id': overlapping_leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2025-05-26 00:00:01',
|
|
'date_to': '2025-06-06 23:59:59',
|
|
'request_date_from': '2025-05-26 00:00:01',
|
|
'request_date_to': '2025-06-06 23:59:59',
|
|
})
|
|
overlapping_leave.action_approve()
|
|
self.assertEqual(overlapping_leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
# Defer the blocked time off
|
|
overlapping_leave.action_report_to_next_month()
|
|
|
|
# Ensure the timeoff work entries have been deferred on June and that May stays untouched
|
|
self.assertEqual(len(june_work_entries.filtered(lambda we: we.work_entry_type_id.code == 'OVERLAPING101')),
|
|
10, "There should only have 10 OVERLAPING101 work entry deferred (1 Work entry per leave day)")
|
|
|
|
# Ensure the worked days lines gets computed correctly on June payslip
|
|
june_payslip_batch = self.env['hr.payslip.run'].create({
|
|
'name': 'June 2025 payslip batch',
|
|
'date_start': '2025-06-01',
|
|
'date_end': '2025-06-30',
|
|
})
|
|
june_payslip_batch.generate_payslips(employee_ids=self.emp.ids)
|
|
june_payslip_batch.action_validate()
|
|
payslip = june_payslip_batch.slip_ids
|
|
self.assertEqual(
|
|
set(payslip.worked_days_line_ids.mapped('number_of_days')),
|
|
{10, 11},
|
|
"There should be 10 timeoff (5 from may and 5 from june) and 11 Attendances (21 - 10) on the payslip"
|
|
)
|
|
|
|
def test_timeoff_overlaping_with_a_public_holiday_shouldnt_be_deferred(self):
|
|
"""
|
|
A leave overlaping on a public holiday shouldn't be deferred as the public holiday was already a leave.
|
|
|
|
- Public holiday on Thursday 2025-05-29 with code LEAVECANTMISS3000
|
|
- May payslip already computed and validated
|
|
- A leave for the 2 whole weeks (2025-05-26 -> 2025-06-06) overlapping with the Public holiday
|
|
- Compute the payslip of June
|
|
|
|
Only 4 leave day from May should be deferred on work entries and payslip of june.
|
|
"""
|
|
# Register a public holiday with a specific work entry type
|
|
self.env['resource.calendar.leaves'].create({
|
|
'name': "Supreme leader party you can't miss",
|
|
'date_from': Datetime.from_string('2025-05-29 00:00:00'),
|
|
'date_to': Datetime.from_string('2025-05-29 23:59:59'),
|
|
'work_entry_type_id': self.env['hr.work.entry.type'].create({
|
|
'name': "Public holiday leaves that can't be missed",
|
|
'code': 'LEAVECANTMISS3000',
|
|
'is_leave': True,
|
|
}).id,
|
|
})
|
|
|
|
# Generate the work entries
|
|
may_work_entries = self.emp.version_ids._generate_work_entries(
|
|
datetime.fromisoformat('2025-05-01'),
|
|
datetime.fromisoformat('2025-05-31')
|
|
)
|
|
june_work_entries = self.emp.version_ids._generate_work_entries(
|
|
datetime.fromisoformat('2025-06-01'),
|
|
datetime.fromisoformat('2025-06-30')
|
|
)
|
|
|
|
# Ensure the public holiday was taken into account
|
|
self.assertRecordValues(may_work_entries.work_entry_type_id, [{'code': 'WORK100'}, {'code': 'LEAVECANTMISS3000'}])
|
|
self.assertRecordValues(june_work_entries.work_entry_type_id, [{'code': 'WORK100'}])
|
|
|
|
# Create and validate payslips like an HR user would do:
|
|
# create a payslip batch, gets the payslips created through the wizard call and validate the batch
|
|
may_payslip_batch = self.env['hr.payslip.run'].create({
|
|
'name': 'May 2025 payslip batch',
|
|
'date_start': '2025-05-01',
|
|
'date_end': '2025-05-31',
|
|
'company_id': self.emp.company_id.id,
|
|
})
|
|
may_payslip_batch.generate_payslips(employee_ids=self.emp.ids)
|
|
may_payslip_batch.action_validate()
|
|
|
|
# Create a time off overlaping with the public holiday
|
|
overlapping_work_entry_type = self.env['hr.work.entry.type'].create({
|
|
'name': 'Annoying overlaping work entry type',
|
|
'is_leave': True,
|
|
'code': 'OVERLAPING101',
|
|
})
|
|
overlapping_leave_type = self.env['hr.leave.type'].create({
|
|
'name': 'Annoying overlaping leave type',
|
|
'work_entry_type_id': overlapping_work_entry_type.id,
|
|
'time_type': 'leave',
|
|
'requires_allocation': False,
|
|
})
|
|
overlapping_leave = self.env['hr.leave'].create({
|
|
'name': 'Annoying overlaping leave',
|
|
'holiday_status_id': overlapping_leave_type.id,
|
|
'employee_id': self.emp.id,
|
|
'date_from': '2025-05-26 00:00:01',
|
|
'date_to': '2025-06-06 23:59:59',
|
|
'request_date_from': '2025-05-26 00:00:01',
|
|
'request_date_to': '2025-06-06 23:59:59',
|
|
})
|
|
overlapping_leave.action_approve()
|
|
self.assertEqual(overlapping_leave.payslip_state, 'blocked', 'Leave should be to defer')
|
|
|
|
# Defer the blocked time off
|
|
overlapping_leave.action_report_to_next_month()
|
|
|
|
# Ensure the timeoff work entries have been deferred on June and that May stays untouched
|
|
self.assertEqual(set(may_work_entries.work_entry_type_id.mapped('code')),
|
|
{'WORK100', 'LEAVECANTMISS3000'}, "Nothing should have changed regarding May work entries")
|
|
self.assertEqual(set(june_work_entries.work_entry_type_id.mapped('code')),
|
|
{'WORK100', 'OVERLAPING101'}, "June work entries should contain the deferred work entries from May")
|
|
self.assertEqual(len(june_work_entries.filtered(lambda we: we.work_entry_type_id.code == 'OVERLAPING101')),
|
|
9, "There should only have 9 OVERLAPING101 work entry deferred (1 Work entry per leave day)")
|
|
|
|
# Ensure the worked days lines gets computed correctly on June payslip
|
|
june_payslip_batch = self.env['hr.payslip.run'].create({
|
|
'name': 'June 2025 payslip batch',
|
|
'date_start': '2025-06-01',
|
|
'date_end': '2025-06-30',
|
|
})
|
|
june_payslip_batch.generate_payslips(employee_ids=self.emp.ids)
|
|
june_payslip_batch.action_validate()
|
|
payslip = june_payslip_batch.slip_ids
|
|
self.assertEqual(set(payslip.worked_days_line_ids.mapped('number_of_days')),
|
|
{9, 12}, "There should be 9 timeoff and 12 Attendances on the payslip")
|