191 lines
8.5 KiB
Python
191 lines
8.5 KiB
Python
from datetime import date, datetime, timedelta
|
|
|
|
from odoo.addons.mail.tests.common import MailCommon, mail_new_test_user
|
|
from odoo.tests import TransactionCase, tagged, users
|
|
|
|
|
|
@tagged('post_install_l10n', 'post_install', '-at_install')
|
|
class TestHrEmployee(TransactionCase):
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.company_us, self.company_be = self.env['res.company'].create([
|
|
{
|
|
'name': 'US Company',
|
|
'country_id': self.env.ref('base.us').id
|
|
},
|
|
{
|
|
'name': 'BE Company',
|
|
'country_id': self.env.ref('base.be').id
|
|
}
|
|
])
|
|
self.env.user.company_ids |= self.company_us
|
|
self.env.user.company_id = self.company_us # hr.version retrieves this partner in _get_default_address_id()
|
|
|
|
def test_company_context(self):
|
|
# This test is testing a hr_employee/hr_version feature, but must be in l10n_us as we need the ssnid constraint.
|
|
# We ensure that the company is passed in the context to the version by creating a belgian employee from the
|
|
# US company with an invalid US SNN. It must not raise a ValidationError.
|
|
be1, be2, us1, be3, us2 = self.env['hr.employee'].with_company(self.company_us).create([
|
|
{'name': 'Belgian Employee 1', 'ssnid': '1', 'company_id': self.company_be.id},
|
|
{'name': 'Belgian Employee 2', 'ssnid': '2', 'company_id': self.company_be.id},
|
|
{'name': 'US Employee 1', 'ssnid': '111111111', 'company_id': self.company_us.id},
|
|
{'name': 'Belgian Employee 3', 'ssnid': '3', 'company_id': self.company_be.id},
|
|
{'name': 'US Employee 2', 'ssnid': '222222222', 'company_id': self.company_us.id},
|
|
])
|
|
# We also test that the record are correctly re-ordered
|
|
self.assertEqual(be1.ssnid, '1')
|
|
self.assertEqual(be2.ssnid, '2')
|
|
self.assertEqual(be3.ssnid, '3')
|
|
self.assertEqual(us1.ssnid, '111111111')
|
|
self.assertEqual(us2.ssnid, '222222222')
|
|
|
|
|
|
@tagged('post_install_l10n', 'post_install', '-at_install', 'mail_track')
|
|
class TestLeaveAllocation(MailCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.company_us = cls.env['res.company'].create({
|
|
'name': 'US Company',
|
|
'country_id': cls.env.ref('base.us').id
|
|
})
|
|
cls.env.user.company_ids |= cls.company_us
|
|
|
|
cls.user_us_payroll = mail_new_test_user(
|
|
cls.env,
|
|
company_id=cls.company_us.id,
|
|
country_id=cls.env.ref('base.us').id,
|
|
groups='base.group_user,base.group_partner_manager,hr.group_hr_manager,hr_payroll.group_hr_payroll_user,hr_holidays.group_hr_holidays_manager',
|
|
login='user_us_payroll',
|
|
name='Urusla Udidgood',
|
|
)
|
|
|
|
cls.test_employee = cls.env['hr.employee'].with_company(cls.company_us).create({
|
|
'name': 'Us Employee', 'company_id': cls.company_us.id,
|
|
})
|
|
|
|
cls.accrual_entry_type, cls.no_accrual_entry_type = cls.env['hr.leave.type'].with_user(cls.user_us_payroll).create([
|
|
{
|
|
'l10n_us_show_on_payslip': True,
|
|
'name': 'Test Accrual',
|
|
}, {
|
|
'l10n_us_show_on_payslip': False,
|
|
'name': 'Test No Accrual',
|
|
},
|
|
])
|
|
|
|
@users('user_us_payroll')
|
|
def test_accrual(self):
|
|
""" Test accrual computation, which is globally computing number of days
|
|
for a given date, taking into account manual update done for that
|
|
timeframe. """
|
|
now = datetime(2025, 12, 1, 10, 0, 0)
|
|
now_1d = now + timedelta(days=1)
|
|
now_5d = now + timedelta(days=5)
|
|
now_6d = now + timedelta(days=6)
|
|
now_10d = now + timedelta(days=10)
|
|
next_month = datetime(2026, 1, 1, 10, 0, 0)
|
|
|
|
with self.mock_datetime_and_now(now):
|
|
alloc = self.env['hr.leave.allocation'].create({
|
|
'date_from': now.date(),
|
|
'date_to': False,
|
|
'employee_id': self.test_employee.id,
|
|
'number_of_days': 2,
|
|
'holiday_status_id': self.accrual_entry_type.id,
|
|
})
|
|
alloc_2 = self.env['hr.leave.allocation'].create({
|
|
'date_from': now.date(),
|
|
'date_to': False,
|
|
'employee_id': self.test_employee.id,
|
|
'number_of_days': 1.5,
|
|
'holiday_status_id': self.accrual_entry_type.id,
|
|
})
|
|
alloc_no_accrual = self.env['hr.leave.allocation'].create({
|
|
'date_from': now.date(),
|
|
'date_to': False,
|
|
'employee_id': self.test_employee.id,
|
|
'number_of_days': 2,
|
|
'holiday_status_id': self.no_accrual_entry_type.id,
|
|
})
|
|
all_alloc = alloc + alloc_2 + alloc_no_accrual
|
|
all_alloc.action_approve()
|
|
self.flush_tracking()
|
|
|
|
# Five days later: we check accrual: sum of first allocation on accrual types
|
|
with self.mock_datetime_and_now(now_5d):
|
|
hours_count = alloc._l10n_us_get_total_allocated(now_5d.date())
|
|
self.assertEqual(hours_count, 16, 'Two work days')
|
|
with self.mock_datetime_and_now(now_5d):
|
|
hours_count = alloc_no_accrual._l10n_us_get_total_allocated(now_5d.date())
|
|
self.assertEqual(hours_count, 16, 'Accrual counted even if no use (aka no filtering in computation)')
|
|
with self.mock_datetime_and_now(now_5d):
|
|
hours_count = alloc_2._l10n_us_get_total_allocated(now_5d.date())
|
|
self.assertEqual(hours_count, 12, 'One and an half work days')
|
|
with self.mock_datetime_and_now(now_5d):
|
|
hours_count = all_alloc._l10n_us_get_total_allocated(now_5d.date())
|
|
self.assertEqual(hours_count, 44, 'Should sum everything')
|
|
|
|
# Changed allocations in 1 and 6 days
|
|
with self.mock_datetime_and_now(now_1d):
|
|
alloc.write({
|
|
'number_of_days': 5,
|
|
})
|
|
self.flush_tracking()
|
|
with self.mock_datetime_and_now(now_6d):
|
|
alloc.write({
|
|
'number_of_days': 1,
|
|
})
|
|
self.flush_tracking()
|
|
|
|
hours_count = alloc._l10n_us_get_total_allocated(now_5d.date())
|
|
self.assertEqual(hours_count, 40, 'Five work days')
|
|
hours_count = alloc._l10n_us_get_total_allocated(now_6d.date())
|
|
self.assertEqual(hours_count, 8, 'One work days')
|
|
|
|
# Add some allocations, and check payslips afterwards to check batch computation
|
|
with self.mock_datetime_and_now(now_10d):
|
|
alloc_2.write({
|
|
'number_of_days': 5.5,
|
|
})
|
|
self.flush_tracking()
|
|
with self.mock_datetime_and_now(next_month):
|
|
new_alloc = self.env['hr.leave.allocation'].create({
|
|
'date_from': next_month.date(),
|
|
'date_to': False,
|
|
'employee_id': self.test_employee.id,
|
|
'number_of_days': 4,
|
|
'holiday_status_id': self.accrual_entry_type.id,
|
|
})
|
|
new_alloc.action_approve()
|
|
self.flush_tracking()
|
|
with self.mock_datetime_and_now(now_5d):
|
|
hours_count = all_alloc._l10n_us_get_total_allocated(next_month.date() - timedelta(days=1))
|
|
self.assertEqual(hours_count, 68,
|
|
'Sum: alloc is 1 day, alloc_2 is 5.5 days, alloc_no_accrual is 2 days, total is 8.5 days -> 68 hours (*8)')
|
|
|
|
# Test with payslips (hehe, slip)
|
|
month_payslip = self.env["hr.payslip"].create({
|
|
'date_from': date(2025, 12, 1),
|
|
'date_to': date(2025, 12, 31),
|
|
'employee_id': self.test_employee.id,
|
|
'name': "This Month Payslip",
|
|
})
|
|
month_leave_lines = month_payslip._l10n_us_get_leave_lines()
|
|
self.assertEqual(month_leave_lines[0]['accrual'], 52,
|
|
'Sum: alloc is 1 day, alloc_2 is 5.5 days, alloc_no_accrual is not counted, total is 6.5 days -> 52 hours (*8)')
|
|
self.assertEqual(month_leave_lines[0]['balance'], 52)
|
|
|
|
next_month_payslip = self.env["hr.payslip"].create({
|
|
'date_from': date(2026, 1, 1),
|
|
'date_to': date(2026, 1, 31),
|
|
'employee_id': self.test_employee.id,
|
|
'name': "Next Month Payslip",
|
|
})
|
|
month_leave_lines = next_month_payslip._l10n_us_get_leave_lines()
|
|
self.assertEqual(month_leave_lines[0]['accrual'], 32,
|
|
'This month has only new_alloc, 4 days')
|
|
self.assertEqual(month_leave_lines[0]['balance'], 84)
|