mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-23 05:32:07 +02:00
19.0 vanilla
This commit is contained in:
parent
a1137a1456
commit
e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions
|
|
@ -1,9 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_attendances
|
||||
from . import test_calendar_sync
|
||||
from . import test_hr_employee
|
||||
from . import test_hr_employee_public
|
||||
from . import test_channel
|
||||
from . import test_self_user_access
|
||||
from . import test_mail_activity_plan
|
||||
from . import test_mail_features
|
||||
from . import test_multi_company
|
||||
from . import test_payroll_fields_access
|
||||
from . import test_resource
|
||||
from . import test_ui
|
||||
from . import test_scenario
|
||||
from . import test_hr_department
|
||||
from . import test_hr_version
|
||||
from . import test_hr_contract_versions
|
||||
from . import test_flexible_resource_calendar
|
||||
from . import test_multiple_bank_accounts
|
||||
|
|
|
|||
|
|
@ -9,6 +9,26 @@ class TestHrCommon(common.TransactionCase):
|
|||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestHrCommon, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
|
||||
cls.res_users_hr_officer = mail_new_test_user(cls.env, login='hro', groups='base.group_user,hr.group_hr_user', name='HR Officer', email='hro@example.com')
|
||||
cls.res_users_hr_officer = mail_new_test_user(
|
||||
cls.env,
|
||||
email='hro@example.com',
|
||||
login='hro',
|
||||
groups='base.group_user,hr.group_hr_user,base.group_partner_manager',
|
||||
name='HR Officer',
|
||||
)
|
||||
|
||||
cls.res_users_hr_manager = mail_new_test_user(
|
||||
cls.env,
|
||||
email='manager@example.com',
|
||||
login='manager',
|
||||
groups='base.group_user,hr.group_hr_manager,base.group_partner_manager',
|
||||
name='HR Admin',
|
||||
)
|
||||
|
||||
cls.employee = cls.env['hr.employee'].create({
|
||||
'name': 'Richard',
|
||||
'sex': 'male',
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
})
|
||||
|
|
|
|||
77
odoo-bringout-oca-ocb-hr/hr/tests/test_attendances.py
Normal file
77
odoo-bringout-oca-ocb-hr/hr/tests/test_attendances.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from pytz import timezone
|
||||
|
||||
from datetime import datetime, date
|
||||
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
|
||||
|
||||
class TestAttendances(TestHrCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.env.company.resource_calendar_id.tz = "Europe/Brussels"
|
||||
|
||||
contract_now = cls.employee.create_version({
|
||||
'wage': 1,
|
||||
'contract_date_start': date(2024, 6, 1),
|
||||
'date_version': date(2024, 6, 1),
|
||||
})
|
||||
|
||||
resource_calendar_half_time = cls.env['resource.calendar'].create([{
|
||||
'name': "Test Calendar: Half Time",
|
||||
'company_id': cls.env.company.id,
|
||||
'tz': "Europe/Brussels",
|
||||
'two_weeks_calendar': False,
|
||||
'attendance_ids': [(5, 0, 0)] + [(0, 0, {
|
||||
'name': "Attendance",
|
||||
'dayofweek': dayofweek,
|
||||
'hour_from': hour_from,
|
||||
'hour_to': hour_to,
|
||||
'day_period': day_period,
|
||||
}) for dayofweek, hour_from, hour_to, day_period in [
|
||||
("0", 8.0, 12.0, "morning"),
|
||||
("0", 12.0, 13.0, "lunch"),
|
||||
("0", 13.0, 16.6, "afternoon"),
|
||||
("1", 8.0, 12.0, "morning"),
|
||||
("1", 12.0, 13.0, "lunch"),
|
||||
("1", 13.0, 16.6, "afternoon"),
|
||||
("2", 8.0, 11.8, "morning"),
|
||||
]],
|
||||
}])
|
||||
|
||||
cls.employee.create_version({
|
||||
'resource_calendar_id': resource_calendar_half_time.id,
|
||||
'contract_date_start': date(2024, 6, 1),
|
||||
'contract_date_end': date(2024, 7, 31),
|
||||
'wage': 1,
|
||||
'date_version': date(2024, 7, 1),
|
||||
})
|
||||
|
||||
cls.employee.create_version({
|
||||
'contract_date_start': date(2024, 8, 1),
|
||||
'wage': 1,
|
||||
'date_version': date(2024, 9, 1),
|
||||
})
|
||||
|
||||
cls.employee.resource_calendar_id = contract_now.resource_calendar_id
|
||||
|
||||
def test_incoming_overlapping_contract(self):
|
||||
tz = timezone("Europe/Brussels")
|
||||
check_in_tz = datetime.combine(datetime(2024, 6, 1), datetime.min.time()).astimezone(tz)
|
||||
check_out_tz = datetime.combine(datetime(2024, 6, 30), datetime.max.time()).astimezone(tz)
|
||||
intervals = self.employee._employee_attendance_intervals(check_in_tz, check_out_tz, lunch=False)
|
||||
self.assertEqual(len(intervals), 40)
|
||||
|
||||
check_in_tz = datetime.combine(datetime(2024, 7, 1), datetime.min.time()).astimezone(tz)
|
||||
check_out_tz = datetime.combine(datetime(2024, 7, 31), datetime.max.time()).astimezone(tz)
|
||||
intervals = self.employee._employee_attendance_intervals(check_in_tz, check_out_tz, lunch=False)
|
||||
self.assertEqual(len(intervals), 25)
|
||||
|
||||
check_in_tz = datetime.combine(datetime(2024, 8, 1), datetime.min.time()).astimezone(tz)
|
||||
check_out_tz = datetime.combine(datetime(2024, 8, 31), datetime.max.time()).astimezone(tz)
|
||||
intervals = self.employee._employee_attendance_intervals(check_in_tz, check_out_tz, lunch=False)
|
||||
self.assertEqual(len(intervals), 20)
|
||||
106
odoo-bringout-oca-ocb-hr/hr/tests/test_calendar_sync.py
Normal file
106
odoo-bringout-oca-ocb-hr/hr/tests/test_calendar_sync.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
from odoo.fields import Datetime, Date
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
from odoo.tests import Form
|
||||
|
||||
|
||||
class TestContractCalendars(TestHrCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.calendar_richard = cls.env['resource.calendar'].create({'name': 'Calendar of Richard'})
|
||||
cls.employee.resource_calendar_id = cls.calendar_richard
|
||||
# force old date to test new version to be created.
|
||||
cls.employee.version_id.date_version = Date.to_date('2015-01-01')
|
||||
|
||||
cls.calendar_35h = cls.env['resource.calendar'].create({'name': '35h calendar'})
|
||||
|
||||
cls.contract_cdd_values = {
|
||||
'date_version': Date.to_date('2016-01-01'),
|
||||
'date_start': Date.to_date('2016-01-01'),
|
||||
'name': 'First CDD Contract for Richard',
|
||||
'resource_calendar_id': cls.calendar_35h.id,
|
||||
'wage': 5000.0,
|
||||
}
|
||||
|
||||
cls.contract_fully_flexible_values = {
|
||||
'date_version': Date.to_date('2017-01-01'),
|
||||
'date_start': Date.to_date('2017-01-01'),
|
||||
'name': 'Fully Flexible Contract for Richard',
|
||||
'resource_calendar_id': False,
|
||||
'wage': 5000.0,
|
||||
}
|
||||
|
||||
def test_contract_state_incoming_to_open(self):
|
||||
# Employee's calendar should change
|
||||
self.assertEqual(self.employee.resource_calendar_id, self.calendar_richard)
|
||||
cdd = self.employee.create_version(self.contract_cdd_values)
|
||||
self.assertEqual(self.employee.version_id.id, cdd.id, "The version of the employee should be updated to the last version.")
|
||||
self.assertEqual(self.employee.resource_calendar_id, cdd.resource_calendar_id, "The employee should have the calendar of its contract.")
|
||||
|
||||
def test_set_fully_flexible_contract_should_change_resource_calendar(self):
|
||||
# Setting a running contract with fully flexible calendar should set the employee's calendar to False (fully flexible)
|
||||
self.assertEqual(self.employee.resource_calendar_id, self.calendar_richard)
|
||||
flexijob = self.employee.create_version(self.contract_fully_flexible_values)
|
||||
self.assertEqual(self.employee.version_id.id, flexijob.id, "The version of the employee should be updated to the last version.")
|
||||
self.assertFalse(self.employee.resource_calendar_id, "The employee should have a fully flexible calendar.")
|
||||
|
||||
def test_contract_transfer_leaves(self):
|
||||
|
||||
def create_calendar_leave(start, end, resource=None):
|
||||
return self.env['resource.calendar.leaves'].create({
|
||||
'name': 'leave name',
|
||||
'date_from': start,
|
||||
'date_to': end,
|
||||
'resource_id': resource.id if resource else None,
|
||||
'calendar_id': self.employee.resource_calendar_id.id,
|
||||
'time_type': 'leave',
|
||||
})
|
||||
|
||||
start = Datetime.to_datetime('2015-11-17 07:00:00')
|
||||
end = Datetime.to_datetime('2015-11-20 18:00:00')
|
||||
leave1 = create_calendar_leave(start, end, resource=self.employee.resource_id)
|
||||
|
||||
start = Datetime.to_datetime('2015-11-25 07:00:00')
|
||||
end = Datetime.to_datetime('2015-11-28 18:00:00')
|
||||
leave2 = create_calendar_leave(start, end, resource=self.employee.resource_id)
|
||||
|
||||
# global leave
|
||||
start = Datetime.to_datetime('2015-11-25 07:00:00')
|
||||
end = Datetime.to_datetime('2015-11-28 18:00:00')
|
||||
leave3 = create_calendar_leave(start, end)
|
||||
|
||||
self.calendar_richard.transfer_leaves_to(self.calendar_35h, resources=self.employee.resource_id, from_date=Date.to_date('2015-11-21'))
|
||||
|
||||
self.assertEqual(leave1.calendar_id, self.calendar_richard, "It should stay in Richard's calendar")
|
||||
self.assertEqual(leave3.calendar_id, self.calendar_richard, "Global leave should stay in original calendar")
|
||||
self.assertEqual(leave2.calendar_id, self.calendar_35h, "It should be transferred to the other calendar")
|
||||
|
||||
# Transfer global leaves
|
||||
self.calendar_richard.transfer_leaves_to(self.calendar_35h, resources=None, from_date=Date.to_date('2015-11-21'))
|
||||
|
||||
self.assertEqual(leave3.calendar_id, self.calendar_35h, "Global leave should be transfered")
|
||||
|
||||
def test_calendar_no_desync(self):
|
||||
""" resource_calendar_id cannot be desync between employee and version last version """
|
||||
self.employee.create_version(self.contract_cdd_values)
|
||||
self.assertEqual(self.employee.resource_calendar_id, self.calendar_35h)
|
||||
self.assertEqual(self.employee.version_id.resource_calendar_id, self.calendar_35h)
|
||||
self.assertEqual(self.employee.version_ids[0].resource_calendar_id, self.calendar_richard)
|
||||
calendar_38h = self.env['resource.calendar'].create({'name': '38h calendar'})
|
||||
self.employee.resource_calendar_id = calendar_38h
|
||||
self.assertEqual(self.employee.version_id.resource_calendar_id, calendar_38h)
|
||||
self.assertEqual(self.employee.version_ids[0].resource_calendar_id, self.calendar_richard)
|
||||
|
||||
def test_employee_resource_contract_without_and_with_date_from(self):
|
||||
"""
|
||||
Test setting the resource with an employee contract on resource leave without and with start date.
|
||||
"""
|
||||
leave_form = Form(self.env['resource.calendar.leaves'])
|
||||
leave_form.date_from = False
|
||||
|
||||
leave_form.resource_id = self.employee.resource_id
|
||||
self.assertFalse(leave_form.calendar_id)
|
||||
|
||||
leave_form.date_from = Datetime.to_datetime('2018-01-01 07:00:00')
|
||||
self.assertEqual(leave_form.calendar_id, self.employee.version_id.resource_calendar_id)
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestChannel(TestHrCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestChannel, cls).setUpClass()
|
||||
|
||||
cls.channel = cls.env['mail.channel'].create({'name': 'Test'})
|
||||
cls.channel = cls.env['discuss.channel'].create({'name': 'Test'})
|
||||
|
||||
emp0 = cls.env['hr.employee'].create({
|
||||
'user_id': cls.res_users_hr_officer.id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import pytz
|
||||
from datetime import datetime, date
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
UTC = pytz.timezone('UTC')
|
||||
|
||||
|
||||
class TestFlexibleResourceCalendar(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.calendar_40h_flex = cls.env['resource.calendar'].create({
|
||||
'name': 'Flexible 40h/week',
|
||||
'tz': 'UTC',
|
||||
'hours_per_day': 8.0,
|
||||
'flexible_hours': True,
|
||||
})
|
||||
cls.flex_resource, cls.fully_flex_resource = cls.env['resource.resource'].create([{
|
||||
'name': 'Flex',
|
||||
'tz': 'UTC',
|
||||
'calendar_id': cls.calendar_40h_flex.id,
|
||||
}, {
|
||||
'name': 'fully flex',
|
||||
'tz': 'UTC',
|
||||
'calendar_id': False,
|
||||
}])
|
||||
|
||||
def test_flexible_resource_work_intervals_with_contracts(self):
|
||||
flex_employee, fully_flex_employee = self.env['hr.employee'].create([{
|
||||
'name': "flex employee",
|
||||
'date_version': date(2025, 1, 1),
|
||||
'contract_date_start': date(2025, 1, 1),
|
||||
'contract_date_end': date(2025, 7, 29),
|
||||
'wage': 10,
|
||||
'resource_calendar_id': self.calendar_40h_flex.id,
|
||||
'resource_id': self.flex_resource.id,
|
||||
}, {
|
||||
'name': "fully flex employee",
|
||||
'date_version': date(2025, 1, 1),
|
||||
'contract_date_start': date(2025, 1, 1),
|
||||
'contract_date_end': date(2025, 7, 29),
|
||||
'wage': 10,
|
||||
'resource_calendar_id': False,
|
||||
'resource_id': self.fully_flex_resource.id,
|
||||
}])
|
||||
flex_employee.create_version({
|
||||
'date_version': date(2025, 8, 2),
|
||||
'contract_date_start': date(2025, 8, 2),
|
||||
'wage': 10,
|
||||
'resource_calendar_id': self.calendar_40h_flex.id,
|
||||
})
|
||||
|
||||
fully_flex_employee.create_version({
|
||||
'date_version': date(2025, 8, 2),
|
||||
'contract_date_start': date(2025, 8, 2),
|
||||
'wage': 10,
|
||||
'resource_calendar_id': False,
|
||||
})
|
||||
|
||||
start_dt = datetime(2025, 7, 28).astimezone(UTC)
|
||||
end_dt = datetime(2025, 8, 3, 17).astimezone(UTC)
|
||||
|
||||
resources = self.flex_resource | self.fully_flex_resource
|
||||
work_intervals, hours_per_day, hours_per_week = resources._get_flexible_resource_valid_work_intervals(start_dt, end_dt)
|
||||
self.maxDiff = None
|
||||
for resource in resources:
|
||||
self.assertEqual(work_intervals[resource.id]._items, [
|
||||
(datetime(2025, 7, 28, 0, 0, tzinfo=UTC), datetime(2025, 7, 28, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 7, 29, 0, 0, tzinfo=UTC), datetime(2025, 7, 29, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 8, 2, 0, 0, tzinfo=UTC), datetime(2025, 8, 2, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 8, 3, 0, 0, tzinfo=UTC), datetime(2025, 8, 3, 17, 0, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
], "work intervals should be inside contract 1 and 2 periods, no contracts on 30, 31, 1")
|
||||
|
||||
self.assertDictEqual(hours_per_day[self.flex_resource.id], {
|
||||
date(2025, 7, 28): 8.0,
|
||||
date(2025, 7, 29): 8.0,
|
||||
date(2025, 8, 2): 8.0,
|
||||
date(2025, 8, 3): 8.0,
|
||||
})
|
||||
|
||||
self.assertDictEqual(hours_per_week[self.flex_resource.id], {
|
||||
(2025, 31): 32.0,
|
||||
(2025, 32): 40.0,
|
||||
}, "working day 27, 28, 29 and 02 on week 31, having a valid contract on week 32")
|
||||
|
||||
self.assertTrue(self.fully_flex_resource.id not in hours_per_day, "no date hours limit for fully flexible employees")
|
||||
|
||||
def test_flexible_resource_work_intervals_without_contracts(self):
|
||||
start_dt = datetime(2025, 7, 28).astimezone(UTC)
|
||||
end_dt = datetime(2025, 8, 3, 17).astimezone(UTC)
|
||||
|
||||
resources = self.flex_resource | self.fully_flex_resource
|
||||
work_intervals, hours_per_day, hours_per_week = resources._get_flexible_resource_valid_work_intervals(start_dt, end_dt)
|
||||
self.maxDiff = None
|
||||
for resource in resources:
|
||||
self.assertEqual(work_intervals[resource.id]._items, [
|
||||
(datetime(2025, 7, 28, 0, 0, tzinfo=UTC), datetime(2025, 7, 28, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 7, 29, 0, 0, tzinfo=UTC), datetime(2025, 7, 29, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 7, 30, 0, 0, tzinfo=UTC), datetime(2025, 7, 30, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 7, 31, 0, 0, tzinfo=UTC), datetime(2025, 7, 31, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 8, 1, 0, 0, tzinfo=UTC), datetime(2025, 8, 1, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 8, 2, 0, 0, tzinfo=UTC), datetime(2025, 8, 2, 23, 59, 59, 999999, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
(datetime(2025, 8, 3, 0, 0, tzinfo=UTC), datetime(2025, 8, 3, 17, 0, tzinfo=UTC), self.env['resource.calendar.attendance']),
|
||||
], "when no contracts at all, we get the full period")
|
||||
|
||||
self.assertDictEqual(hours_per_day[self.flex_resource.id], {
|
||||
date(2025, 7, 28): 8.0,
|
||||
date(2025, 7, 29): 8.0,
|
||||
date(2025, 7, 30): 8.0,
|
||||
date(2025, 7, 31): 8.0,
|
||||
date(2025, 8, 1): 8.0,
|
||||
date(2025, 8, 2): 8.0,
|
||||
date(2025, 8, 3): 8.0,
|
||||
}, "when no contracts at all, we get the full period")
|
||||
self.assertTrue(self.fully_flex_resource.id not in hours_per_day, "no date hours limit for fully flexible employees")
|
||||
|
||||
self.assertDictEqual(hours_per_week[self.flex_resource.id], {
|
||||
(2025, 31): 40.0,
|
||||
(2025, 32): 40.0,
|
||||
})
|
||||
594
odoo-bringout-oca-ocb-hr/hr/tests/test_hr_contract_versions.py
Normal file
594
odoo-bringout-oca-ocb-hr/hr/tests/test_hr_contract_versions.py
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import date
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestHrContractVersions(TransactionCase):
|
||||
"""
|
||||
TestHrContractVersions is responsible for testing the behavior and validity of versions
|
||||
with contracts.
|
||||
|
||||
This class tests 2 helper methods: `_get_contract_versions()` and `_get_contracts()`
|
||||
There are multiple scenarios:
|
||||
- No contract, 1 version
|
||||
- 1 contract with 1 version
|
||||
- 1 contract with 5 versions
|
||||
- 2 contracts with 1 version each
|
||||
- 2 contracts with 3 versions each
|
||||
|
||||
For each of them, multiple tests are performed with different start_date, end_date and use_latest_version
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.company = cls.env['res.company'].create({
|
||||
'name': 'Test Company',
|
||||
'country_id': cls.env.ref('base.us').id,
|
||||
})
|
||||
cls.env.user.company_id = cls.company
|
||||
cls.employee = cls.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2025-01-01'
|
||||
})
|
||||
|
||||
def create_version(self, date_version):
|
||||
return self.employee.create_version({
|
||||
'date_version': date_version,
|
||||
})
|
||||
|
||||
def create_versions(self, *dates):
|
||||
res = self.env["hr.version"]
|
||||
for date_version in dates:
|
||||
res |= self.create_version(date_version)
|
||||
return res
|
||||
|
||||
def assert_get_contract_versions(self, date_start, date_end, versions_per_contract_expected):
|
||||
versions_per_contract = self.employee._get_contract_versions(date_start, date_end)[self.employee.id]
|
||||
self.assertEqual(len(versions_per_contract), len(versions_per_contract_expected), "%s contract should be found" % len(versions_per_contract_expected))
|
||||
for vpc, vpc_e in zip(versions_per_contract.values(), versions_per_contract_expected):
|
||||
self.assertEqual(vpc, vpc_e, "invalid number of versions (%s instead of %s) for this contract : contract_date_start : %s" % (len(vpc), len(vpc_e), vpc_e[0].contract_date_start))
|
||||
|
||||
def assert_get_contracts(self, date_start, date_end, use_latest_version, contracts_expected):
|
||||
contracts = self.employee._get_contracts(date_start, date_end, use_latest_version)[self.employee.id]
|
||||
for c, c_e in zip(contracts, contracts_expected):
|
||||
self.assertEqual(c, c_e, "invalid contracts")
|
||||
|
||||
def test_0contract_1version(self):
|
||||
"""
|
||||
We should retrieve no versions, because no contract is defined
|
||||
"""
|
||||
for date_version in ['2025-01-01', '2025-06-01', '2025-12-31']:
|
||||
self.employee.date_version = date_version
|
||||
for date_start in (None, date(2025, 3, 1)):
|
||||
for date_end in (None, date(2025, 6, 15)):
|
||||
for use_latest_version in (True, False):
|
||||
self.assert_get_contract_versions(
|
||||
date_start,
|
||||
date_end,
|
||||
[]
|
||||
)
|
||||
self.assert_get_contracts(
|
||||
date_start,
|
||||
date_end,
|
||||
use_latest_version,
|
||||
[]
|
||||
)
|
||||
|
||||
""" Timeline for this test
|
||||
V : versions
|
||||
C : first version of the contract
|
||||
= : contract
|
||||
|
||||
1/
|
||||
04/01 7/31
|
||||
2025|C---------=====================----------|
|
||||
01/01 06/01 12/31
|
||||
|
||||
2/
|
||||
04/01 7/31
|
||||
2025|----------==========C==========----------|
|
||||
01/01 06/01 12/31
|
||||
|
||||
3/
|
||||
04/01 7/31
|
||||
2025|----------=====================---------C|
|
||||
01/01 06/01 12/31
|
||||
"""
|
||||
def test_1contract_1version(self):
|
||||
"""
|
||||
We should always retrieve the only existing version
|
||||
"""
|
||||
unique_version = self.employee.version_id
|
||||
unique_version.contract_date_start = date(2025, 4, 1)
|
||||
unique_version.contract_date_end = date(2025, 7, 31)
|
||||
for date_version in ['2025-01-01', '2025-06-01', '2025-12-31']:
|
||||
unique_version.date_version = date_version
|
||||
for date_start in (None, date(2025, 3, 1), date(2025, 4, 1)):
|
||||
for date_end in (None, date(2025, 4, 1), date(2025, 6, 15), date(2025, 7, 31)):
|
||||
for use_latest_version in (True, False):
|
||||
self.assert_get_contract_versions(
|
||||
date_start,
|
||||
date_end,
|
||||
[unique_version]
|
||||
)
|
||||
self.assert_get_contracts(
|
||||
date_start,
|
||||
date_end,
|
||||
use_latest_version,
|
||||
unique_version
|
||||
)
|
||||
|
||||
""" Timeline for this setup
|
||||
V : versions
|
||||
C : first version of the contract
|
||||
= : contract
|
||||
|
||||
04/01=========================07/31
|
||||
2025|C---------V---------------V------V--------VV---------|
|
||||
01/01 04/01 06/01 07/01 07/31;08/01
|
||||
"""
|
||||
def setup_1contract_5version(self):
|
||||
contract_versions = self.employee.version_id | self.create_versions(
|
||||
date(2025, 4, 1),
|
||||
date(2025, 6, 1),
|
||||
date(2025, 7, 1),
|
||||
date(2025, 7, 31)
|
||||
)
|
||||
contract_versions.contract_date_start = date(2025, 4, 1)
|
||||
contract_versions.contract_date_end = date(2025, 7, 31)
|
||||
versions_not_in_contract = self.create_version(date(2025, 8, 1))
|
||||
return contract_versions, versions_not_in_contract
|
||||
|
||||
def test_1contract_5version(self):
|
||||
# All versions of the contract should be retrieved
|
||||
expected_contract_versions, _ = self.setup_1contract_5version()
|
||||
self.assert_get_contract_versions(
|
||||
None,
|
||||
None,
|
||||
[expected_contract_versions]
|
||||
)
|
||||
# The last version of the contract should be retrieved
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
None,
|
||||
True,
|
||||
expected_contract_versions[-1]
|
||||
)
|
||||
# The first version of the contract should be retrieved
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
None,
|
||||
False,
|
||||
expected_contract_versions[0]
|
||||
)
|
||||
|
||||
def test_1contract_5version_w_date_start(self):
|
||||
# All versions of the contract should be retrieved
|
||||
expected_contract_versions, _ = self.setup_1contract_5version()
|
||||
self.assert_get_contract_versions(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
[expected_contract_versions]
|
||||
)
|
||||
# The last version of the contract should be retrieved
|
||||
self.assert_get_contracts(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
True,
|
||||
expected_contract_versions[-1]
|
||||
)
|
||||
# We need to retrieve the version closest and before the start date. (first version of the contract)
|
||||
self.assert_get_contracts(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
False,
|
||||
expected_contract_versions[0]
|
||||
)
|
||||
# We need to retrieve the version closest and before the start date. (second version of the contract)
|
||||
self.assert_get_contracts(
|
||||
date(2025, 4, 1),
|
||||
None,
|
||||
False,
|
||||
expected_contract_versions[0]
|
||||
)
|
||||
|
||||
def test_1contract_5version_w_date_start_date_end(self):
|
||||
# all versions of the contract should be retrieved (even if versions are not in the range)
|
||||
expected_contract_versions, _ = self.setup_1contract_5version()
|
||||
self.assert_get_contract_versions(
|
||||
date(2025, 5, 15),
|
||||
date(2025, 6, 15),
|
||||
[expected_contract_versions]
|
||||
)
|
||||
# We need to retrieve the version closest and before the end date.
|
||||
self.assert_get_contracts(
|
||||
date(2025, 4, 15),
|
||||
date(2025, 6, 15),
|
||||
True,
|
||||
expected_contract_versions[2]
|
||||
)
|
||||
# We need to retrieve the version closest and before the start date.
|
||||
self.assert_get_contracts(
|
||||
date(2025, 4, 15),
|
||||
date(2025, 6, 15),
|
||||
False,
|
||||
expected_contract_versions[1]
|
||||
)
|
||||
# We need to retrieve the version closest and before the end date.
|
||||
self.assert_get_contracts(
|
||||
date(2025, 6, 15),
|
||||
date(2025, 7, 15),
|
||||
True,
|
||||
expected_contract_versions[3]
|
||||
)
|
||||
# We need to retrieve the version closest and before the start date.
|
||||
self.assert_get_contracts(
|
||||
date(2025, 6, 15),
|
||||
date(2025, 8, 15),
|
||||
False,
|
||||
expected_contract_versions[2]
|
||||
)
|
||||
|
||||
def test_1contract_5version_w_date_end(self):
|
||||
# All versions of the contract should be retrieved
|
||||
expected_contract_versions, _ = self.setup_1contract_5version()
|
||||
self.assert_get_contract_versions(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
[expected_contract_versions]
|
||||
)
|
||||
# We need to retrieve the version closest and before the end date. (last version of the contract)
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
True,
|
||||
expected_contract_versions[-1]
|
||||
)
|
||||
# We need to retrieve the version closest and before the end date. (last version of the contract)
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 7, 31),
|
||||
True,
|
||||
expected_contract_versions[-1]
|
||||
)
|
||||
# We need to retrieve the version closest and before the end date. (second to last version of the contract)
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 7, 30),
|
||||
True,
|
||||
expected_contract_versions[-2]
|
||||
)
|
||||
# The first version of the contract should be retrieved
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
False,
|
||||
expected_contract_versions[0]
|
||||
)
|
||||
|
||||
""" Timeline for this setup
|
||||
V : versions
|
||||
C : first version of the contract
|
||||
= : contract
|
||||
|
||||
4/01 5/15 6/15 7/31
|
||||
2025|C---------====================-C-====================----------|
|
||||
01/01 06/01
|
||||
"""
|
||||
def setup_2contract_1version_each(self):
|
||||
contract_1_version = self.employee.version_id
|
||||
contract_1_version.contract_date_start = date(2025, 4, 1)
|
||||
contract_1_version.contract_date_end = date(2025, 5, 15)
|
||||
|
||||
contract_2_version = self.create_version(date(2025, 6, 1))
|
||||
contract_2_version.contract_date_start = date(2025, 6, 15)
|
||||
contract_2_version.contract_date_end = date(2025, 7, 31)
|
||||
|
||||
return contract_1_version, contract_2_version
|
||||
|
||||
def test_2contract_1version_each(self):
|
||||
# all versions of all contracts
|
||||
contract_1_version, contract_2_version = self.setup_2contract_1version_each()
|
||||
self.assert_get_contract_versions(
|
||||
None,
|
||||
None,
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# the latest version of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
None,
|
||||
True,
|
||||
contract_1_version | contract_2_version
|
||||
)
|
||||
# the first version of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
None,
|
||||
False,
|
||||
contract_1_version | contract_2_version
|
||||
)
|
||||
|
||||
def test_2contract_1version_each_w_date_start(self):
|
||||
# all versions of all contracts
|
||||
contract_1_version, contract_2_version = self.setup_2contract_1version_each()
|
||||
self.assert_get_contract_versions(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# all versions of the second contract
|
||||
self.assert_get_contract_versions(
|
||||
date(2025, 6, 15),
|
||||
None,
|
||||
[contract_2_version]
|
||||
)
|
||||
# the latest version of each contract
|
||||
self.assert_get_contracts(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
True,
|
||||
contract_1_version | contract_2_version
|
||||
)
|
||||
# the first before the start date, of each contract
|
||||
self.assert_get_contracts(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
False,
|
||||
contract_1_version | contract_2_version
|
||||
)
|
||||
|
||||
def test_2contract_1version_each_w_date_start_date_end(self):
|
||||
# no versions, because no contract active in the range, even if versions are in the range
|
||||
contract_1_version, contract_2_version = self.setup_2contract_1version_each()
|
||||
self.assert_get_contract_versions(
|
||||
date(2025, 5, 16),
|
||||
date(2025, 6, 14),
|
||||
[]
|
||||
)
|
||||
# all versions of each contract
|
||||
self.assert_get_contract_versions(
|
||||
date(2025, 5, 15),
|
||||
date(2025, 6, 15),
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# the first version before the end date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 5, 15),
|
||||
date(2025, 6, 14),
|
||||
True,
|
||||
contract_1_version
|
||||
)
|
||||
# the first version before the start date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 5, 15),
|
||||
date(2025, 6, 14),
|
||||
False,
|
||||
contract_1_version
|
||||
)
|
||||
# the first version before the end date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 5, 16),
|
||||
date(2025, 6, 15),
|
||||
True,
|
||||
contract_2_version
|
||||
)
|
||||
# the first version before the start date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 5, 16),
|
||||
date(2025, 6, 15),
|
||||
False,
|
||||
contract_2_version
|
||||
)
|
||||
# the first version before the end date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 5, 15),
|
||||
date(2025, 6, 15),
|
||||
True,
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# the first version before the start date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 5, 15),
|
||||
date(2025, 6, 15),
|
||||
False,
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
|
||||
def test_2contract_1version_each_w_date_end(self):
|
||||
# all versions of all contracts
|
||||
contract_1_version, contract_2_version = self.setup_2contract_1version_each()
|
||||
self.assert_get_contract_versions(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# all versions of the first contract
|
||||
self.assert_get_contract_versions(
|
||||
None,
|
||||
date(2025, 6, 15),
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# all versions of the first contract
|
||||
self.assert_get_contract_versions(
|
||||
None,
|
||||
date(2025, 6, 14),
|
||||
[contract_1_version]
|
||||
)
|
||||
# the first before the end date, of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
True,
|
||||
contract_1_version | contract_2_version
|
||||
)
|
||||
# the first version of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
False,
|
||||
contract_1_version | contract_2_version
|
||||
)
|
||||
|
||||
""" Timeline for this setup
|
||||
V : versions
|
||||
C : first version of the contract
|
||||
= : contract
|
||||
|
||||
4/01============5/15 6/15============7/31
|
||||
2025|C---------V------------------VV------C-------V------------------VV---------|
|
||||
1/1 4/1 5/15;5/16 6/1 6/15 7/31;8/1
|
||||
"""
|
||||
def setup_2contract_3version_each(self):
|
||||
contract_1_versions = self.employee.version_id | self.create_versions(
|
||||
date(2025, 4, 1),
|
||||
date(2025, 5, 15),
|
||||
)
|
||||
contract_1_versions.contract_date_start = date(2025, 4, 1)
|
||||
contract_1_versions.contract_date_end = date(2025, 5, 15)
|
||||
|
||||
versions_not_in_contract = self.create_version(date(2025, 5, 16))
|
||||
|
||||
contract_2_versions = self.create_versions(
|
||||
date(2025, 6, 1),
|
||||
date(2025, 6, 15),
|
||||
date(2025, 7, 31),
|
||||
)
|
||||
contract_2_versions.contract_date_start = date(2025, 6, 15)
|
||||
contract_2_versions.contract_date_end = date(2025, 7, 31)
|
||||
|
||||
versions_not_in_contract |= self.create_version(date(2025, 8, 1))
|
||||
|
||||
return contract_1_versions, contract_2_versions, versions_not_in_contract
|
||||
|
||||
def test_2contract_3version_each(self):
|
||||
# all versions of all contracts
|
||||
contract_1_version, contract_2_version, _ = self.setup_2contract_3version_each()
|
||||
self.assert_get_contract_versions(
|
||||
None,
|
||||
None,
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# the latest version of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
None,
|
||||
True,
|
||||
contract_1_version[-1] | contract_2_version[-1]
|
||||
)
|
||||
# the first version of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
None,
|
||||
False,
|
||||
contract_1_version[0] | contract_2_version[0]
|
||||
)
|
||||
|
||||
def test_2contract_3version_each_w_date_start(self):
|
||||
# all versions of all contracts
|
||||
contract_1_version, contract_2_version, _ = self.setup_2contract_3version_each()
|
||||
self.assert_get_contract_versions(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# the latest version of each contract
|
||||
self.assert_get_contracts(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
True,
|
||||
contract_1_version[-1] | contract_2_version[-1]
|
||||
)
|
||||
# the first before the start date, of each contract
|
||||
self.assert_get_contracts(
|
||||
date(2025, 3, 1),
|
||||
None,
|
||||
False,
|
||||
contract_1_version[0] | contract_2_version[0]
|
||||
)
|
||||
# the first before the start date, of each contract
|
||||
self.assert_get_contracts(
|
||||
date(2025, 4, 1),
|
||||
None,
|
||||
False,
|
||||
contract_1_version[1] | contract_2_version[0]
|
||||
)
|
||||
|
||||
def test_2contract_3version_each_w_date_start_date_end(self):
|
||||
# all versions of all contracts
|
||||
contract_1_version, contract_2_version, _ = self.setup_2contract_3version_each()
|
||||
self.assert_get_contract_versions(
|
||||
date(2025, 5, 16),
|
||||
date(2025, 6, 14),
|
||||
[]
|
||||
)
|
||||
# the first before the end date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 4, 15),
|
||||
date(2025, 6, 15),
|
||||
True,
|
||||
[contract_1_version[-1], contract_2_version[1]]
|
||||
)
|
||||
# the first before the start date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 4, 15),
|
||||
date(2025, 6, 15),
|
||||
False,
|
||||
[contract_1_version[0], contract_2_version[0]]
|
||||
)
|
||||
# the first before the end date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 6, 15),
|
||||
date(2025, 7, 31),
|
||||
True,
|
||||
contract_2_version[-1]
|
||||
)
|
||||
# the first before the start date, of the contract active in the range
|
||||
self.assert_get_contracts(
|
||||
date(2025, 6, 15),
|
||||
date(2025, 7, 31),
|
||||
False,
|
||||
contract_2_version[1]
|
||||
)
|
||||
|
||||
def test_2contract_3version_each_w_date_end(self):
|
||||
# all versions of all contracts
|
||||
contract_1_version, contract_2_version, _ = self.setup_2contract_3version_each()
|
||||
self.assert_get_contract_versions(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
[contract_1_version, contract_2_version]
|
||||
)
|
||||
# the first before the end date, of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
True,
|
||||
contract_1_version[-1] | contract_2_version[-1]
|
||||
)
|
||||
# the first before the end date, of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 7, 31),
|
||||
True,
|
||||
contract_1_version[-1] | contract_2_version[-1]
|
||||
)
|
||||
# the first before the end date, of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 7, 30),
|
||||
True,
|
||||
contract_1_version[-1] | contract_2_version[-2]
|
||||
)
|
||||
# the latest version of each contract
|
||||
self.assert_get_contracts(
|
||||
None,
|
||||
date(2025, 8, 31),
|
||||
False,
|
||||
contract_1_version[0] | contract_2_version[0]
|
||||
)
|
||||
62
odoo-bringout-oca-ocb-hr/hr/tests/test_hr_department.py
Normal file
62
odoo-bringout-oca-ocb-hr/hr/tests/test_hr_department.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from odoo.addons.hr.tests.test_multi_company import TestMultiCompany
|
||||
|
||||
|
||||
class TestHrDepartment(TestMultiCompany):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.department = cls.env['hr.department'].create({
|
||||
'name': 'test department',
|
||||
})
|
||||
cls.employee_a.department_id = cls.department
|
||||
cls.employee_other_a.department_id = cls.department
|
||||
cls.employee_b.department_id = cls.department
|
||||
|
||||
def test_dapartment_total_employee_count(self):
|
||||
'''
|
||||
Test that employee_count has only the count of employees in the selected companies
|
||||
'''
|
||||
employee_count = self.department.with_company(self.company_a).total_employee # should only count the 2 employees in company_a
|
||||
self.assertEqual(employee_count, 2)
|
||||
|
||||
self.department._compute_total_employee()
|
||||
employee_count = self.department.total_employee # should count all 3 employees
|
||||
self.assertEqual(employee_count, 3)
|
||||
|
||||
def test_department_company_id(self):
|
||||
"""
|
||||
When the parent exists and parent's company changes to non-empty company, the child's company must be equalized to the same company
|
||||
If the parent's company changes to empty, the child's company should not get affected
|
||||
"""
|
||||
|
||||
self.parent_department = self.env['hr.department'].create({
|
||||
'name': 'parent of the test department',
|
||||
'company_id': self.company_a.id,
|
||||
})
|
||||
self.department.company_id = self.company_b.id
|
||||
self.assertTrue(self.department.company_id == self.company_b)
|
||||
self.department.parent_id = self.parent_department.id
|
||||
self.assertTrue(self.department.company_id == self.company_a)
|
||||
self.parent_department.company_id = self.company_b
|
||||
self.assertTrue(self.department.company_id == self.company_b)
|
||||
self.parent_department.company_id = False
|
||||
|
||||
# Child's company should not change
|
||||
|
||||
self.assertTrue(self.department.company_id == self.company_b)
|
||||
|
||||
self.parents_parent_department = self.env['hr.department'].create({
|
||||
'name': 'grandparent of test department',
|
||||
'company_id': False,
|
||||
})
|
||||
self.parent_department.parent_id = self.parents_parent_department.id
|
||||
|
||||
# Since the company of grandparent is False, it will not affect childs.
|
||||
|
||||
self.assertFalse(self.parent_department.company_id)
|
||||
self.assertTrue(self.department.company_id == self.company_b)
|
||||
|
||||
self.parents_parent_department.company_id = self.company_a.id
|
||||
self.assertTrue(self.parent_department.company_id == self.company_a)
|
||||
self.assertTrue(self.department.company_id == self.company_a)
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from psycopg2.errors import UniqueViolation
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.tests import Form
|
||||
from odoo import fields, Command
|
||||
from odoo.fields import Domain
|
||||
from odoo.tests import Form, users, new_test_user, HttpCase, tagged, TransactionCase
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.exceptions import ValidationError
|
||||
from psycopg2.errors import NotNullViolation
|
||||
|
||||
class TestHrEmployee(TestHrCommon):
|
||||
|
||||
|
|
@ -21,6 +28,49 @@ class TestHrEmployee(TestHrCommon):
|
|||
'image_1920': False
|
||||
})
|
||||
|
||||
def test_employee_must_have_active_version(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Batman'
|
||||
})
|
||||
self.assertEqual(len(employee.version_ids), 1)
|
||||
employee_version = employee.version_id
|
||||
with self.assertRaises(ValidationError, msg="An employee should always have a version"):
|
||||
employee.write({'version_ids': False})
|
||||
with self.assertRaises(ValidationError, msg="An employee should always have a version"):
|
||||
employee_version.unlink()
|
||||
with self.assertRaises(ValidationError, msg="An employee should always have a version"):
|
||||
employee_version.write({
|
||||
'employee_id': self.employee_without_image.id
|
||||
})
|
||||
with self.assertRaises(ValidationError, msg="An employee should always have an active version"):
|
||||
employee_version.write({'active': False})
|
||||
|
||||
def test_employee_smart_button_multi_company(self):
|
||||
partner = self.env['res.partner'].create({'name': 'Partner Test'})
|
||||
company_A = self.env['res.company'].create({'name': 'company_A'})
|
||||
company_B = self.env['res.company'].create({'name': 'company_B'})
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'employee_A',
|
||||
'work_contact_id': partner.id,
|
||||
'company_id': company_A.id,
|
||||
})
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'employee_B',
|
||||
'work_contact_id': partner.id,
|
||||
'company_id': company_B.id
|
||||
})
|
||||
|
||||
partner.with_company(company_A)._compute_employees_count()
|
||||
self.assertEqual(partner.employees_count, 1)
|
||||
partner.with_company(company_B)._compute_employees_count()
|
||||
self.assertEqual(partner.employees_count, 1)
|
||||
single_company_action = partner.with_company(company_B).action_open_employees()
|
||||
self.assertEqual(single_company_action.get('view_mode'), 'form')
|
||||
partner.with_company(company_A).with_company(company_B)._compute_employees_count()
|
||||
self.assertEqual(partner.employees_count, 2)
|
||||
multi_company_action = partner.with_company(company_A).with_company(company_B).action_open_employees()
|
||||
self.assertEqual(multi_company_action.get('view_mode'), 'kanban')
|
||||
|
||||
def test_employee_linked_partner(self):
|
||||
user_partner = self.user_without_image.partner_id
|
||||
work_contact = self.employee_without_image.work_contact_id
|
||||
|
|
@ -36,6 +86,38 @@ class TestHrEmployee(TestHrCommon):
|
|||
employee = employee_form.save()
|
||||
self.assertEqual(employee.tz, _tz)
|
||||
|
||||
def test_employee_timezone(self):
|
||||
self.res_users_hr_officer.tz = "Africa/Cairo"
|
||||
Employee = self.env['hr.employee'].with_user(self.res_users_hr_officer)
|
||||
employee_form = Form(Employee)
|
||||
employee_form.user_id = self.res_users_hr_officer
|
||||
employee_form.name = 'Youssef Ahmed'
|
||||
employee_form.work_email = 'yoahm@example.com'
|
||||
employee = employee_form.save()
|
||||
|
||||
# validate timezone sync between employee & user
|
||||
self.assertEqual(employee.tz, self.res_users_hr_officer.tz)
|
||||
|
||||
# validate that we can change timezone on user
|
||||
self.res_users_hr_officer.tz = "Europe/Brussels"
|
||||
self.assertEqual(self.res_users_hr_officer.tz, employee.tz)
|
||||
|
||||
# validate that we can change timezone on employee
|
||||
employee.tz = "Europe/London"
|
||||
self.assertEqual(self.res_users_hr_officer.tz, employee.tz)
|
||||
|
||||
# Check False value on employee
|
||||
with mute_logger('odoo.sql_db'), self.assertRaises(NotNullViolation):
|
||||
employee.tz = False
|
||||
|
||||
# Check False value on user
|
||||
with mute_logger('odoo.sql_db'), self.assertRaises(NotNullViolation):
|
||||
self.res_users_hr_officer.tz = False
|
||||
|
||||
# Check None value on user's calendar
|
||||
with mute_logger('odoo.sql_db'), self.assertRaises(NotNullViolation):
|
||||
self.res_users_hr_officer.company_id.resource_calendar_id.write({'tz': None})
|
||||
|
||||
def test_employee_from_user(self):
|
||||
_tz = 'Pacific/Apia'
|
||||
_tz2 = 'America/Tijuana'
|
||||
|
|
@ -51,18 +133,30 @@ class TestHrEmployee(TestHrCommon):
|
|||
self.assertEqual(employee.work_email, self.res_users_hr_officer.email)
|
||||
self.assertEqual(employee.tz, self.res_users_hr_officer.tz)
|
||||
|
||||
def test_employee_from_user_tz_no_reset(self):
|
||||
def test_employee_computed_from_user(self):
|
||||
self.res_users_hr_officer.name = 'Raoul Grosbedon'
|
||||
self.res_users_hr_officer.email = 'raoul@example.com'
|
||||
Employee = self.env['hr.employee']
|
||||
employee_form = Form(Employee)
|
||||
employee_form.user_id = self.res_users_hr_officer
|
||||
self.assertEqual(employee_form.name, 'Raoul Grosbedon')
|
||||
self.assertEqual(employee_form.work_email, 'raoul@example.com')
|
||||
employee = employee_form.save()
|
||||
self.assertEqual(employee.name, 'Raoul Grosbedon')
|
||||
self.assertEqual(employee.work_email, 'raoul@example.com')
|
||||
|
||||
def test_employee_from_manager_tz_no_reset(self):
|
||||
_tz = 'Pacific/Apia'
|
||||
self.res_users_hr_officer.tz = False
|
||||
Employee = self.env['hr.employee'].with_user(self.res_users_hr_officer)
|
||||
self.res_users_hr_manager.tz = False
|
||||
Employee = self.env['hr.employee'].with_user(self.res_users_hr_manager)
|
||||
employee_form = Form(Employee)
|
||||
employee_form.name = 'Raoul Grosbedon'
|
||||
employee_form.work_email = 'raoul@example.com'
|
||||
employee_form.tz = _tz
|
||||
employee_form.user_id = self.res_users_hr_officer
|
||||
employee_form.user_id = self.res_users_hr_manager
|
||||
employee = employee_form.save()
|
||||
self.assertEqual(employee.name, 'Raoul Grosbedon')
|
||||
self.assertEqual(employee.work_email, self.res_users_hr_officer.email)
|
||||
self.assertEqual(employee.work_email, self.res_users_hr_manager.email)
|
||||
self.assertEqual(employee.tz, _tz)
|
||||
|
||||
def test_employee_has_avatar_even_if_it_has_no_image(self):
|
||||
|
|
@ -126,16 +220,10 @@ class TestHrEmployee(TestHrCommon):
|
|||
self.assertFalse(emp_parent.member_of_department)
|
||||
employees = emp + emp_sub + emp_sub_sub + emp_other + emp_parent
|
||||
self.assertEqual(
|
||||
employees.filtered_domain(employees._search_part_of_department('=', True)),
|
||||
employees.filtered_domain(employees.version_id._search_part_of_department('in', [True])),
|
||||
emp + emp_sub + emp_sub_sub)
|
||||
self.assertEqual(
|
||||
employees.filtered_domain(employees._search_part_of_department('!=', False)),
|
||||
emp + emp_sub + emp_sub_sub)
|
||||
self.assertEqual(
|
||||
employees.filtered_domain(employees._search_part_of_department('=', False)),
|
||||
emp_other + emp_parent)
|
||||
self.assertEqual(
|
||||
employees.filtered_domain(employees._search_part_of_department('!=', True)),
|
||||
employees.filtered_domain(['!'] + employees.version_id._search_part_of_department('in', [True])),
|
||||
emp_other + emp_parent)
|
||||
|
||||
def test_employee_create_from_user(self):
|
||||
|
|
@ -185,7 +273,7 @@ class TestHrEmployee(TestHrCommon):
|
|||
def test_employee_update_work_contact_id(self):
|
||||
"""
|
||||
Check that the `work_contact_id` information is no longer
|
||||
updated when an employee's `user_id` is removed.
|
||||
updated when an employee's `user_id` is added to another employee.
|
||||
"""
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'Test',
|
||||
|
|
@ -209,20 +297,550 @@ class TestHrEmployee(TestHrCommon):
|
|||
employee_B.work_email = 'new_email@example.com'
|
||||
self.assertEqual(employee_A.work_email, 'employee_A@example.com')
|
||||
self.assertEqual(employee_B.work_email, 'new_email@example.com')
|
||||
self.assertFalse(employee_A.work_contact_id)
|
||||
self.assertEqual(employee_B.work_contact_id, user.partner_id)
|
||||
|
||||
def test_unlink_address(self):
|
||||
employee = self.employee_without_image
|
||||
partner = self.env["res.partner"].create({
|
||||
"name": "Mr. Bean",
|
||||
"street": "12 Arbour Road",
|
||||
"city": "London"
|
||||
})
|
||||
employee.address_home_id = partner.id
|
||||
bank = self.env['res.partner.bank'].create({
|
||||
"acc_number": "123",
|
||||
"partner_id": partner.id
|
||||
})
|
||||
employee.bank_account_id = bank.id
|
||||
def test_availability_user_infos_employee(self):
|
||||
""" Ensure that all the user infos needed to display the avatar popover card
|
||||
are available on the model hr.employee.
|
||||
"""
|
||||
user = self.env['res.users'].create([{
|
||||
'name': 'Test user',
|
||||
'login': 'test',
|
||||
'email': 'test@odoo.perso',
|
||||
'phone': '+32488990011',
|
||||
}])
|
||||
employee = self.env['hr.employee'].create([{
|
||||
'name': 'Test employee',
|
||||
'user_id': user.id,
|
||||
}])
|
||||
user_fields = ['email', 'phone', 'im_status']
|
||||
for field in user_fields:
|
||||
self.assertEqual(employee[field], user[field])
|
||||
|
||||
employee.address_home_id = False
|
||||
self.assertFalse(employee.address_home_id)
|
||||
def test_set_user_on_new_employee(self):
|
||||
test_company = self.env['res.company'].create({
|
||||
'name': 'Test User Company',
|
||||
})
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'Hr Officer - employee',
|
||||
'user_id': self.res_users_hr_officer.id,
|
||||
'company_id': test_company.id,
|
||||
})
|
||||
|
||||
self.res_users_hr_officer.write({'company_ids': test_company.ids, 'company_id': test_company.id})
|
||||
|
||||
# Try to set the user with existing employee in the company, on a new employee form
|
||||
employee_form = Form(self.env['hr.employee'].with_user(self.res_users_hr_officer).with_company(company=test_company.id))
|
||||
employee_form.name = "Second employee"
|
||||
employee_form.user_id = self.res_users_hr_officer
|
||||
with mute_logger('odoo.sql_db'), self.assertRaises(UniqueViolation), self.assertRaises(ValidationError):
|
||||
employee_form.save()
|
||||
|
||||
employee_2 = self.env['hr.employee'].create({
|
||||
'name': 'Hr 2 - employee',
|
||||
'company_id': test_company.id,
|
||||
})
|
||||
|
||||
# Try to set the user with existing employee in the company, on another existing employee
|
||||
employee_2_form = Form(employee_2.with_user(self.res_users_hr_officer).with_company(company=test_company.id))
|
||||
employee_2_form.user_id = self.res_users_hr_officer
|
||||
with mute_logger('odoo.sql_db'), self.assertRaises(UniqueViolation), self.assertRaises(ValidationError):
|
||||
employee_2_form.save()
|
||||
|
||||
|
||||
@users('admin')
|
||||
def test_change_user_on_employee(self):
|
||||
test_other_user = self.env['res.users'].create({
|
||||
'name': 'Test Other User',
|
||||
'login': 'test_other_user',
|
||||
})
|
||||
test_other_user.partner_id.company_id = self.env.company
|
||||
test_company = self.env['res.company'].create({
|
||||
'name' : 'Test User Company',
|
||||
})
|
||||
self.env.user.write({'company_ids': test_company.ids, 'company_id': test_company.id})
|
||||
test_user = self.env['res.users'].create({
|
||||
'name': 'Test User',
|
||||
'login': 'test_user',
|
||||
})
|
||||
test_user.partner_id.company_id = test_company
|
||||
bank_account = self.env['res.partner.bank'].create({
|
||||
'acc_number' : '1234567',
|
||||
'partner_id' : test_user.partner_id.id,
|
||||
})
|
||||
test_employee = self.env['hr.employee'].create({
|
||||
'name': 'Test User - employee',
|
||||
'user_id': test_user.id,
|
||||
'company_id': test_company.id,
|
||||
'bank_account_ids': [Command.link(bank_account.id)],
|
||||
})
|
||||
# change user -> bank account change company
|
||||
with Form(test_employee) as employee_form:
|
||||
employee_form.user_id = test_other_user
|
||||
# change user back -> check that there is no company error
|
||||
with Form(test_employee) as employee_form:
|
||||
employee_form.user_id = test_user
|
||||
|
||||
def test_change_user_on_employee_keep_partner(self):
|
||||
"""
|
||||
Check that removing user from employee keeps the link in
|
||||
work_contact_id until the user is assigned to another employee.
|
||||
"""
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'Test User',
|
||||
'login': 'test_user',
|
||||
})
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Test User - employee',
|
||||
'user_id': user.id,
|
||||
})
|
||||
# remove user
|
||||
employee.user_id = None
|
||||
self.assertEqual(employee.work_contact_id, user.partner_id)
|
||||
self.assertFalse(employee.user_id)
|
||||
# create new employee from user
|
||||
user._compute_company_employee()
|
||||
user.action_create_employee()
|
||||
self.assertTrue(len(user.employee_ids) == 1, "Test user should have exactly one employee associated with it")
|
||||
# previous employee shouldn't have a work_contact_id anymore, as the partner is reassigned
|
||||
self.assertFalse(employee.work_contact_id)
|
||||
# the new employee should be associated to both the user and its partner
|
||||
new_employee = user.employee_ids
|
||||
self.assertEqual(new_employee.work_contact_id, user.partner_id)
|
||||
self.assertEqual(new_employee.user_id, user)
|
||||
|
||||
def test_change_user_on_employee_multi_company(self):
|
||||
"""
|
||||
Removing user from employee keeps the link in work_contact_id in the correct company until the user
|
||||
is assigned to another employee, and does not affect employees in other companies. When the unique
|
||||
constraint of one employee per user in one company is triggered, the work_contact_id for the
|
||||
existing employee is nor removed, and employees in other companies are not affected.
|
||||
"""
|
||||
company_A = self.env['res.company'].create({'name': 'company_A'})
|
||||
company_B = self.env['res.company'].create({'name': 'company_B'})
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'Test User',
|
||||
'login': 'test_user',
|
||||
})
|
||||
partner = user.partner_id
|
||||
employee_A = self.env['hr.employee'].create({
|
||||
'name': 'employee_A',
|
||||
'user_id': user.id,
|
||||
'company_id': company_A.id,
|
||||
})
|
||||
employee_B = self.env['hr.employee'].create({
|
||||
'name': 'employee_B',
|
||||
'user_id': user.id,
|
||||
'company_id': company_B.id
|
||||
})
|
||||
# Creating an employee in one company does not remove the link with employee in the other company
|
||||
self.assertEqual(user.with_company(company_A).employee_id, employee_A)
|
||||
self.assertEqual(user.with_company(company_B).employee_id, employee_B)
|
||||
# Partner is linked to both employees
|
||||
partner.with_company(company_A).with_company(company_B)._compute_employees_count()
|
||||
self.assertEqual(partner.employees_count, 2)
|
||||
# Remove user from employee in one company does not affect link user-employee in the other company
|
||||
employee_A.user_id = None
|
||||
self.assertEqual(user.with_company(company_A).employee_id.ids, [])
|
||||
self.assertEqual(user.with_company(company_B).employee_id, employee_B)
|
||||
# Partner still linked to both employees
|
||||
partner.with_company(company_A).with_company(company_B)._compute_employees_count()
|
||||
self.assertEqual(partner.employees_count, 2)
|
||||
# Creating a new employee for a user in company A does not affect link user-employee in the other company
|
||||
new_employee_A = self.env['hr.employee'].create({
|
||||
'name': 'new_employee_A',
|
||||
'user_id': user.id,
|
||||
'company_id': company_A.id,
|
||||
})
|
||||
# User cannot be assigned to more than one employee in the same company. work_contact_id should not be removed.
|
||||
with mute_logger('odoo.sql_db'), self.assertRaises(UniqueViolation), self.assertRaises(ValidationError):
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'new_employee_B',
|
||||
'user_id': user.id,
|
||||
'company_id': company_B.id,
|
||||
})
|
||||
self.assertEqual(user.with_company(company_A).employee_id, new_employee_A)
|
||||
self.assertEqual(user.with_company(company_B).employee_id, employee_B)
|
||||
self.assertEqual(partner.employee_ids, employee_B + new_employee_A)
|
||||
|
||||
def test_avatar(self):
|
||||
# Check simple employee has a generated image (initials)
|
||||
employee_georgette = self.env['hr.employee'].create({'name': 'Georgette Pudubec'})
|
||||
self.assertTrue(employee_georgette.image_1920)
|
||||
self.assertTrue(employee_georgette.avatar_1920)
|
||||
|
||||
self.assertTrue(employee_georgette.work_contact_id)
|
||||
self.assertTrue(employee_georgette.work_contact_id.image_1920)
|
||||
self.assertTrue(employee_georgette.work_contact_id.avatar_1920)
|
||||
|
||||
# Check user has a generate image
|
||||
user_norbert = self.env['res.users'].create({'name': 'Norbert Comidofisse', 'login': 'Norbert6870'})
|
||||
self.assertTrue(user_norbert.image_1920)
|
||||
self.assertTrue(user_norbert.avatar_1920)
|
||||
|
||||
# Check that linked employee got user image
|
||||
employee_norbert = self.env['hr.employee'].create({'name': 'Norbert Employee', 'user_id': user_norbert.id})
|
||||
self.assertEqual(employee_norbert.image_1920, user_norbert.image_1920)
|
||||
self.assertEqual(employee_norbert.avatar_1920, user_norbert.avatar_1920)
|
||||
|
||||
def test_badge_validation(self):
|
||||
# check employee's barcode should be a sequence of digits and alphabets
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Badge Employee'
|
||||
})
|
||||
|
||||
employee_form = Form(employee)
|
||||
employee_form.barcode = 'Test@badge1'
|
||||
with self.assertRaises(ValidationError):
|
||||
employee_form.save()
|
||||
|
||||
employee_form.barcode = 'Testàë@badge'
|
||||
with self.assertRaises(ValidationError):
|
||||
employee_form.save()
|
||||
|
||||
employee_form.barcode = 'Testbadge2'
|
||||
employee_form.save()
|
||||
|
||||
self.assertEqual(employee_form.barcode, 'Testbadge2')
|
||||
|
||||
def test_departure_wizard(self):
|
||||
""" Test the archiving wizard in the case of multiple employees """
|
||||
employee_A, employee_B, employee_C = self.env['hr.employee'].create([
|
||||
{
|
||||
'name': f'Employee {code}',
|
||||
'user_id': False,
|
||||
'work_email': f'employee_{code}@example.com',
|
||||
} for code in ['A', 'B', 'C']
|
||||
])
|
||||
archiving_employees = [employee.id for employee in (employee_A, employee_C)]
|
||||
|
||||
wizard = self.env['hr.departure.wizard'].with_context(
|
||||
employee_termination=True,
|
||||
active_ids=archiving_employees,
|
||||
).create({})
|
||||
wizard.action_register_departure()
|
||||
|
||||
all_employees = employee_A | employee_B | employee_C
|
||||
self.assertEqual(all_employees.filtered(lambda e: e.active), employee_B, "Employees should have been archived")
|
||||
|
||||
def test_search_hr_employee_no_access(self):
|
||||
new_user = new_test_user(self.env, 'employee')
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee',
|
||||
})
|
||||
domain = Domain([
|
||||
('name', '=', 'Test Employee'),
|
||||
('active', '=', True)
|
||||
]).optimize(self.env['hr.employee'])
|
||||
with self.assertNoLogs('odoo.domains'):
|
||||
self.assertEqual(
|
||||
employee.ids,
|
||||
self.env['hr.employee'].with_user(new_user).search(domain).ids,
|
||||
)
|
||||
|
||||
def test_is_flexible(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Employee',
|
||||
})
|
||||
self.assertTrue(employee.resource_calendar_id)
|
||||
self.assertFalse(employee.is_flexible)
|
||||
self.assertFalse(employee.is_fully_flexible)
|
||||
|
||||
employee.resource_calendar_id.flexible_hours = True
|
||||
self.assertTrue(employee.is_flexible)
|
||||
self.assertFalse(employee.is_fully_flexible)
|
||||
|
||||
employee.resource_calendar_id = False
|
||||
self.assertTrue(employee.is_flexible)
|
||||
self.assertTrue(employee.is_fully_flexible)
|
||||
|
||||
def test_resource_calendar_sync_with_employee_one(self):
|
||||
calendar = self.env['resource.calendar'].create({
|
||||
'name': 'test calendar',
|
||||
'flexible_hours': True,
|
||||
})
|
||||
self.assertTrue(self.employee.resource_id)
|
||||
self.assertTrue(self.employee.resource_calendar_id)
|
||||
self.assertEqual(self.employee.resource_calendar_id, self.employee.resource_id.calendar_id)
|
||||
self.assertNotEqual(self.employee.resource_calendar_id, calendar)
|
||||
self.assertTrue(self.employee.resource_calendar_id, self.employee.resource_id.calendar_id)
|
||||
old_calendar = self.employee.resource_calendar_id
|
||||
old_version = self.employee.version_id
|
||||
old_version.date_version = old_version.date_version - relativedelta(days=1)
|
||||
self.employee.resource_calendar_id = calendar
|
||||
self.assertEqual(self.employee.resource_id.calendar_id, calendar)
|
||||
version = self.employee.create_version({'resource_calendar_id': old_calendar.id, 'date_version': fields.Date.today()})
|
||||
self.assertEqual(self.employee.current_version_id, version)
|
||||
self.assertNotEqual(self.employee.current_version_id, old_version)
|
||||
self.assertEqual(self.employee.resource_calendar_id, old_calendar)
|
||||
self.assertEqual(self.employee.resource_id.calendar_id, old_calendar)
|
||||
|
||||
def test_job_title(self):
|
||||
first_job = self.env['hr.job'].create({'name': "first job"})
|
||||
second_job = self.env['hr.job'].create({'name': "second job"})
|
||||
|
||||
with Form(self.employee_without_image) as employee_form:
|
||||
# Assign first job to employee, job title should be job name
|
||||
employee_form.job_id = first_job
|
||||
self.assertEqual(employee_form.job_title, first_job.name)
|
||||
|
||||
# Change job title, job name should not change
|
||||
employee_form.job_title = "custom job title"
|
||||
self.assertEqual(first_job.name, "first job")
|
||||
|
||||
# Change the name of the first job, job title should not be updated
|
||||
first_job.name = "first job modified"
|
||||
self.assertEqual(employee_form.job_title, "custom job title")
|
||||
employee_form.save()
|
||||
|
||||
# Assign second job to employee, job title should be second job name
|
||||
employee_form.job_id = second_job
|
||||
self.assertEqual(employee_form.job_title, second_job.name)
|
||||
|
||||
# Switch back to first job, job title should be first job name
|
||||
employee_form.job_id = first_job
|
||||
self.assertEqual(employee_form.job_title, first_job.name)
|
||||
|
||||
def test_flexible_working_hours(self):
|
||||
"""
|
||||
Test to verifie that get_unusual_days() return false for flexible work schedule
|
||||
"""
|
||||
|
||||
# Creating a flexible working schedule
|
||||
calendar_flex = self.env['resource.calendar'].create([
|
||||
{
|
||||
'tz': "Europe/Brussels",
|
||||
'name': 'flexible hours',
|
||||
'flexible_hours': "True",
|
||||
},
|
||||
])
|
||||
employeeA = self.env['hr.employee'].create({
|
||||
'name': 'Employee',
|
||||
})
|
||||
|
||||
# Testing employeA on regular working schedule
|
||||
days = employeeA._get_unusual_days(str(datetime(2025, 1, 1)), str(datetime(2025, 12, 31)))
|
||||
self.assertTrue(days)
|
||||
self.assertTrue(days['2025-01-04'])
|
||||
|
||||
# Assigning flexible work hours to employeeA
|
||||
employeeA.resource_calendar_id = calendar_flex.id
|
||||
days = employeeA._get_unusual_days(str(datetime(2025, 1, 1)), str(datetime(2025, 12, 31)))
|
||||
self.assertTrue(days)
|
||||
self.assertFalse(days['2025-01-04'])
|
||||
|
||||
def test_user_creation_from_employee_with_invalid_email(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee',
|
||||
'work_email': 'test'
|
||||
})
|
||||
|
||||
action = employee.action_create_users()
|
||||
self.assertEqual(action['params']['message'], f'You need to set a valid work email address for {employee.name}')
|
||||
self.assertFalse(employee.user_id)
|
||||
|
||||
def test_user_creation_from_employee_multi_emails(self):
|
||||
employees = self.env['hr.employee'].create([
|
||||
{
|
||||
'name': 'Existing Email Employee',
|
||||
'work_email': self.user_without_image.email,
|
||||
}, {
|
||||
'name': 'New Employee',
|
||||
'work_email': 'newuser@example.com',
|
||||
}, {
|
||||
'name': 'Invalid Email Employee',
|
||||
'work_email': 'invalid-email',
|
||||
}, {
|
||||
'name': 'Without Email Employee',
|
||||
'work_email': False,
|
||||
}, {
|
||||
'name': 'Formatted Email Employee',
|
||||
'work_email': f'"John Doe" <{self.user_without_image.email_normalized}>',
|
||||
}, {
|
||||
'name': 'Multi Email Employee',
|
||||
'work_email': '"Name1" <name@test.example.com>, "Name 2" <name2@test.example.com>',
|
||||
},
|
||||
])
|
||||
# Add an existing employee who already has a user to the employee list
|
||||
employees += self.employee_without_image
|
||||
context = {'selected_ids': employees.ids}
|
||||
confirmed_employees = self.env['hr.employee'].with_context(context).browse(employees.ids)
|
||||
action = confirmed_employees.action_create_users()
|
||||
|
||||
params = action.get('params')
|
||||
self.assertEqual(params.get('message'), f"User already exists with the same email for Employees {employees[0].name}, {employees[4].name}")
|
||||
params = params.get('next').get('params')
|
||||
self.assertEqual(params.get('message'), f"You need to set a valid work email address for {employees[2].name}, {employees[5].name}")
|
||||
params = params.get('next').get('params')
|
||||
self.assertEqual(params.get('message'), f"You need to set the work email address for {employees[3].name}")
|
||||
params = params.get('next').get('params')
|
||||
self.assertEqual(params.get('message'), f"User already exists for Those Employees {employees[6].name}")
|
||||
params = params.get('next').get('params')
|
||||
self.assertEqual(params.get('message'), f"Users {employees[1].name} creation successful")
|
||||
self.assertTrue(employees[1].user_id)
|
||||
|
||||
def test_user_contact_phone_sync(self):
|
||||
partner = self.env['res.partner'].create({'name': 'Partner Test'})
|
||||
first_company = self.env['res.company'].create({'name': 'First Company'})
|
||||
first_employee = self.env['hr.employee'].create({
|
||||
'name': 'First Employee',
|
||||
'work_contact_id': partner.id,
|
||||
'company_id': first_company.id,
|
||||
})
|
||||
first_employee.write({'work_phone': '12345', 'work_email': 'first_employee@test.com'})
|
||||
self.assertEqual(first_employee.work_phone, partner.phone)
|
||||
self.assertEqual(first_employee.work_email, partner.email)
|
||||
partner.write({'phone': '67890', 'email': 'partner@test.com'})
|
||||
self.assertEqual(partner.phone, first_employee.work_phone)
|
||||
self.assertEqual(partner.email, first_employee.work_email)
|
||||
|
||||
second_company = self.env['res.company'].create({'name': 'Second Company'})
|
||||
second_employee = self.env['hr.employee'].create({
|
||||
'name': 'Second Employee',
|
||||
'work_contact_id': partner.id,
|
||||
'company_id': second_company.id,
|
||||
})
|
||||
second_employee.write({'work_phone': '112233', 'work_email': 'second_employee@test.com'})
|
||||
self.assertNotEqual(second_employee.work_phone, partner.phone)
|
||||
self.assertNotEqual(second_employee.work_phone, first_employee.work_phone)
|
||||
self.assertNotEqual(second_employee.work_email, partner.email)
|
||||
self.assertNotEqual(second_employee.work_email, first_employee.work_email)
|
||||
partner.write({'phone': '445566', 'email': 'partner_updated@test.com'})
|
||||
self.assertNotEqual(partner.phone, second_employee.work_phone)
|
||||
self.assertNotEqual(partner.phone, first_employee.work_phone)
|
||||
self.assertNotEqual(partner.email, second_employee.work_email)
|
||||
self.assertNotEqual(partner.email, first_employee.work_email)
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestHrEmployeeLinks(HttpCase):
|
||||
def test_shared_private_link_permissions(self):
|
||||
"""
|
||||
Employees not part of group_hr_user are not supposed to be able to see
|
||||
private employees pages (e.g.: from a shared link).
|
||||
The tour will check if the correct redirection warning appears when such
|
||||
case happens.
|
||||
"""
|
||||
user_amy = new_test_user(
|
||||
self.env,
|
||||
name="Amy Rose",
|
||||
login='amy',
|
||||
groups='base.group_user' # cannot access private employee profiles
|
||||
)
|
||||
employee_sonic = self.env['hr.employee'].create({
|
||||
'name': 'Sonic the Hedgehog',
|
||||
})
|
||||
with mute_logger('odoo.http'): # ignore raised RedirectWarning
|
||||
self.start_tour(
|
||||
f"/odoo/employees/{employee_sonic.id}",
|
||||
"check_public_employee_link_redirect",
|
||||
login=user_amy.login,
|
||||
)
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestVersionCron(TransactionCase):
|
||||
"""Test the behavior of CRONs affecting hr.version"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# Will be used for default employee version address (contains phone)
|
||||
cls.env.user.company_id = cls.env['res.company'].create(
|
||||
{'name': 'Pokémon Center', 'phone': '+32404040404'}
|
||||
)
|
||||
|
||||
# Employee has a default version that will be overridden
|
||||
with freeze_time("2020-10-07"):
|
||||
cls.employee = cls.env['hr.employee'].create(
|
||||
{
|
||||
'name': 'Charizard',
|
||||
'work_phone': '+32404040404',
|
||||
"distance_home_work": 32,
|
||||
"distance_home_work_unit": 'miles',
|
||||
}
|
||||
)
|
||||
|
||||
def test_version_cron_update_no_fields(self):
|
||||
"""
|
||||
Employees should not see their fields be updated if the CRON does not
|
||||
change their version.
|
||||
"""
|
||||
with freeze_time('2023-10-06'):
|
||||
self.employee.create_version(
|
||||
{'date_version': '2023-10-07', "distance_home_work": 40}
|
||||
)
|
||||
|
||||
# Saving current employee data to compare later on
|
||||
employee_values = {}
|
||||
# some fields cannot be accessed. We need to filter them out
|
||||
employee_fields = [
|
||||
field
|
||||
for field in self.env['hr.employee']._fields
|
||||
if hasattr(self.employee, field)
|
||||
]
|
||||
for field in employee_fields:
|
||||
employee_values[field] = self.employee[field]
|
||||
|
||||
# Should not change to new version
|
||||
with freeze_time('2023-10-06'):
|
||||
self.env['hr.employee']._cron_update_current_version_id()
|
||||
|
||||
for field in employee_fields:
|
||||
self.assertEqual(
|
||||
employee_values[field],
|
||||
self.employee[field],
|
||||
f"""No field should change if _cron_update_current_version_id() does not change the version.
|
||||
However, the field {field} changed""",
|
||||
)
|
||||
|
||||
def test_version_cron_update_fields(self):
|
||||
"""
|
||||
Employees should see some of their field be changed if the CRON changes
|
||||
their version.
|
||||
"""
|
||||
with freeze_time('2023-10-06'):
|
||||
self.employee.create_version(
|
||||
{'date_version': '2023-10-07', "distance_home_work": 40}
|
||||
)
|
||||
current_home_distance = self.employee.distance_home_work
|
||||
current_version = self.employee.current_version_id
|
||||
# Should change to new version
|
||||
with freeze_time('2023-10-07'):
|
||||
self.env['hr.employee']._cron_update_current_version_id()
|
||||
|
||||
self.assertNotEqual(
|
||||
current_version,
|
||||
self.employee.current_version_id,
|
||||
"current_version_id should have changed after calling _cron_update_current_version_id()",
|
||||
)
|
||||
self.assertNotEqual(
|
||||
current_home_distance,
|
||||
self.employee.distance_home_work,
|
||||
"distance_home_work should have changed after calling _cron_update_current_version_id()",
|
||||
)
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestHrEmployeeWebJson(HttpCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# JSON route needs to be enabled for the tests
|
||||
self.env['ir.config_parameter'].sudo().set_param('web.json.enabled', True)
|
||||
|
||||
def test_webjson_employees(self):
|
||||
# Check that json employees can be accessed
|
||||
url = "/json/1/employees"
|
||||
self.env['ir.config_parameter'].set_param('web.json.enabled', True)
|
||||
self.authenticate('admin', 'admin')
|
||||
CSRF_USER_HEADERS = {
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-Site": 'none',
|
||||
"Sec-Fetch-User": "?1",
|
||||
}
|
||||
res = self.url_open(url, headers=CSRF_USER_HEADERS)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
|
|
|||
27
odoo-bringout-oca-ocb-hr/hr/tests/test_hr_employee_public.py
Normal file
27
odoo-bringout-oca-ocb-hr/hr/tests/test_hr_employee_public.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
|
||||
|
||||
class TestHrEmployee(TestHrCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.res_users_without_hr_right = mail_new_test_user(
|
||||
cls.env,
|
||||
email='nhr@example.com',
|
||||
login='nhr',
|
||||
groups='base.group_user,base.group_partner_manager',
|
||||
name='No HR Right',
|
||||
)
|
||||
|
||||
def test_access_related_field_to_hr_employee(self):
|
||||
# Check if a related field related to hr_employee is accessible.
|
||||
self.env['hr.employee.public'].with_user(self.res_users_without_hr_right).search([("email", "!=", False)])
|
||||
|
||||
def test_access_search_on_users_department(self):
|
||||
User = self.env['res.users'].with_user(self.res_users_without_hr_right)
|
||||
User.search([('employee_id.department_id', '=', 1)])
|
||||
842
odoo-bringout-oca-ocb-hr/hr/tests/test_hr_version.py
Normal file
842
odoo-bringout-oca-ocb-hr/hr/tests/test_hr_version.py
Normal file
|
|
@ -0,0 +1,842 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import date
|
||||
from psycopg2.errors import CheckViolation
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import freeze_time
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestHrVersion(TestHrCommon):
|
||||
def test_dates_constraints(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01'
|
||||
})
|
||||
self.assertFalse(employee.contract_date_start)
|
||||
self.assertFalse(employee.contract_date_end)
|
||||
|
||||
employee.write({
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': False
|
||||
})
|
||||
self.assertEqual(employee.contract_date_start, date(2020, 1, 1))
|
||||
self.assertFalse(employee.contract_date_end)
|
||||
|
||||
employee.write({
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31'
|
||||
})
|
||||
self.assertEqual(employee.contract_date_start, date(2020, 1, 1))
|
||||
self.assertEqual(employee.contract_date_end, date(2020, 12, 31))
|
||||
|
||||
employee.write({
|
||||
'contract_date_start': False,
|
||||
'contract_date_end': False
|
||||
})
|
||||
self.assertFalse(employee.contract_date_start)
|
||||
self.assertFalse(employee.contract_date_end)
|
||||
|
||||
with self.assertRaises(CheckViolation), mute_logger('odoo.sql_db'):
|
||||
employee.write({
|
||||
'contract_date_start': False,
|
||||
'contract_date_end': '2020-12-31'
|
||||
})
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
employee.write({
|
||||
'contract_date_start': '2021-01-01',
|
||||
'contract_date_end': '2020-12-31'
|
||||
})
|
||||
|
||||
def test_contracts_no_overlap(self):
|
||||
# Simple overlap cases
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31'
|
||||
})
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
employee.create_version({
|
||||
'date_version': '2019-06-01',
|
||||
'contract_date_start': '2019-06-01',
|
||||
'contract_date_end': '2020-05-31'
|
||||
})
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
employee.create_version({
|
||||
'date_version': '2020-06-01',
|
||||
'contract_date_start': '2020-06-01',
|
||||
'contract_date_end': '2021-05-31'
|
||||
})
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
employee.create_version({
|
||||
'date_version': '2020-02-01',
|
||||
'contract_date_start': '2020-02-01',
|
||||
'contract_date_end': '2020-10-31'
|
||||
})
|
||||
|
||||
# It should not detect overlap with archived versions
|
||||
employee.create_version({
|
||||
'active': False,
|
||||
'date_version': '2019-06-01',
|
||||
'contract_date_start': '2019-06-01',
|
||||
'contract_date_end': '2020-05-31'
|
||||
})
|
||||
|
||||
def test_occupation_dates(self):
|
||||
"""
|
||||
Occupation dates are global for the employee, they are a list of intervals
|
||||
(date_from, date_to) where the employee is in contract (date_from and date_to included).
|
||||
"""
|
||||
# A single version and no contract
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01'
|
||||
})
|
||||
self.assertEqual(employee._get_all_contract_dates(), [])
|
||||
|
||||
# A single version and contract
|
||||
employee.write({
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31'
|
||||
})
|
||||
occupation_dates = [(date(2020, 1, 1), date(2020, 12, 31))]
|
||||
self.assertEqual(employee._get_all_contract_dates(), occupation_dates)
|
||||
|
||||
# 2 versions with 1 contract each
|
||||
employee.create_version({
|
||||
'date_version': '2021-01-01',
|
||||
'contract_date_start': '2021-01-01',
|
||||
'contract_date_end': '2023-12-31'
|
||||
})
|
||||
occupation_dates = [
|
||||
(date(2020, 1, 1), date(2020, 12, 31)),
|
||||
(date(2021, 1, 1), date(2023, 12, 31)),
|
||||
]
|
||||
self.assertEqual(employee._get_all_contract_dates(), occupation_dates)
|
||||
|
||||
# 3 versions with 2 sharing the same contract
|
||||
employee.create_version({
|
||||
'date_version': '2022-01-01',
|
||||
})
|
||||
self.assertEqual(employee._get_all_contract_dates(), occupation_dates)
|
||||
|
||||
# 4 versions with 2 sharing the same contract, the last one is permanent
|
||||
employee.create_version({
|
||||
'date_version': '2025-01-01',
|
||||
'contract_date_start': '2025-01-01',
|
||||
})
|
||||
occupation_dates = [
|
||||
(date(2020, 1, 1), date(2020, 12, 31)),
|
||||
(date(2021, 1, 1), date(2023, 12, 31)),
|
||||
(date(2025, 1, 1), False),
|
||||
]
|
||||
self.assertEqual(employee._get_all_contract_dates(), occupation_dates)
|
||||
|
||||
def test_dates_new_version_out_of_contract(self):
|
||||
"""
|
||||
If the new version falls on a period out of contract, clear the dates
|
||||
"""
|
||||
# Create a new version after the end of the current contract
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31',
|
||||
})
|
||||
|
||||
version = employee.create_version({
|
||||
'date_version': '2021-01-01'
|
||||
})
|
||||
self.assertFalse(version.contract_date_start)
|
||||
self.assertFalse(version.contract_date_end)
|
||||
|
||||
# Forcing the contract_date_start and or contract_date_end in the 'create' should override False
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31',
|
||||
})
|
||||
|
||||
version = employee.create_version({
|
||||
'date_version': '2021-01-01',
|
||||
'contract_date_start': '2021-01-01',
|
||||
'contract_date_end': '2021-12-31'
|
||||
})
|
||||
self.assertEqual(version.contract_date_start, date(2021, 1, 1))
|
||||
self.assertEqual(version.contract_date_end, date(2021, 12, 31))
|
||||
|
||||
# Create a new version before the start of the current contract
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31',
|
||||
})
|
||||
|
||||
version = employee.create_version({'date_version': '2019-01-01'})
|
||||
self.assertFalse(version.contract_date_start)
|
||||
self.assertFalse(version.contract_date_end)
|
||||
|
||||
# Create a new version between two contracts
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31'
|
||||
})
|
||||
|
||||
employee.create_version({
|
||||
'date_version': '2022-01-01',
|
||||
'contract_date_start': '2022-01-01',
|
||||
'contract_date_end': '2022-12-31'
|
||||
})
|
||||
|
||||
version = employee.create_version({
|
||||
'date_version': '2021-01-01',
|
||||
})
|
||||
self.assertFalse(version.contract_date_start)
|
||||
self.assertFalse(version.contract_date_end)
|
||||
|
||||
def test_dates_new_version_in_contract(self):
|
||||
"""
|
||||
If the new version falls on some contract, copy its contract dates
|
||||
"""
|
||||
# Create a new version on a permanent contract
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
})
|
||||
|
||||
version = employee.create_version({
|
||||
'date_version': '2021-01-01'
|
||||
})
|
||||
self.assertEqual(version.contract_date_start, date(2020, 1, 1))
|
||||
self.assertFalse(version.contract_date_end)
|
||||
|
||||
# Create a new version on a fixed term contract
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2021-12-31'
|
||||
})
|
||||
|
||||
version = employee.create_version({'date_version': '2021-01-01'})
|
||||
self.assertEqual(version.contract_date_start, date(2020, 1, 1))
|
||||
self.assertEqual(version.contract_date_end, date(2021, 12, 31))
|
||||
|
||||
# Create a new version on any contract interval regardless of the version valid at that date
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31'
|
||||
})
|
||||
|
||||
employee.create_version({
|
||||
'date_version': '2022-01-01',
|
||||
'contract_date_start': '2021-01-01',
|
||||
'contract_date_end': '2022-12-31'
|
||||
})
|
||||
|
||||
version = employee.create_version({
|
||||
'date_version': '2021-01-01',
|
||||
})
|
||||
self.assertEqual(version.contract_date_start, date(2021, 1, 1))
|
||||
self.assertEqual(version.contract_date_end, date(2022, 12, 31))
|
||||
|
||||
def test_dates_synchronisation(self):
|
||||
"""
|
||||
All versions that share or will share (at the end of a 'write')
|
||||
the same contract_date_start are synchronized.
|
||||
"""
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2021-12-31',
|
||||
})
|
||||
v1 = employee.version_id
|
||||
|
||||
v2 = employee.create_version({
|
||||
'date_version': '2021-01-01',
|
||||
'contract_date_end': '2022-12-31',
|
||||
})
|
||||
self.assertEqual(v2.contract_date_end, date(2022, 12, 31))
|
||||
self.assertEqual(v1.contract_date_end, v2.contract_date_end)
|
||||
|
||||
v1.write({
|
||||
'contract_date_start': '2021-01-01',
|
||||
})
|
||||
self.assertEqual(v1.contract_date_start, date(2021, 1, 1))
|
||||
self.assertEqual(v1.contract_date_start, v2.contract_date_start)
|
||||
|
||||
v2.write({
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31',
|
||||
})
|
||||
self.assertEqual(v2.contract_date_start, date(2020, 1, 1))
|
||||
self.assertEqual(v1.contract_date_start, v2.contract_date_start)
|
||||
self.assertEqual(v2.contract_date_end, date(2020, 12, 31))
|
||||
self.assertEqual(v1.contract_date_end, v2.contract_date_end)
|
||||
|
||||
v3 = employee.create_version({
|
||||
'date_version': '2030-01-01',
|
||||
'contract_date_start': '2030-01-01',
|
||||
})
|
||||
versions = []
|
||||
for i in range(10):
|
||||
versions.append(employee.create_version({
|
||||
'date_version': f'20{31 + i}-01-01',
|
||||
}))
|
||||
v3.write({
|
||||
'contract_date_end': '2040-12-31',
|
||||
})
|
||||
for version in versions:
|
||||
self.assertEqual(version.contract_date_end, date(2040, 12, 31))
|
||||
|
||||
def test_1_version_contract_synchronisation(self):
|
||||
"""
|
||||
When an employee has only one version, the contract_date_start should
|
||||
be synchronized with the date_version.
|
||||
"""
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
})
|
||||
version = employee.version_id
|
||||
|
||||
employee.write({'contract_date_start': '2019-01-01'})
|
||||
self.assertEqual(version.contract_date_start, version.date_version)
|
||||
|
||||
# date_version should not be reset if the contract_date_start is set to False
|
||||
employee.write({'contract_date_start': False})
|
||||
self.assertEqual(version.date_version, date(2019, 1, 1))
|
||||
|
||||
employee.write({'contract_date_start': '2021-01-01'})
|
||||
self.assertEqual(version.contract_date_start, version.date_version)
|
||||
|
||||
def test_2_versions_contract_synchronisation(self):
|
||||
"""
|
||||
When an employee has two versions, the synchronisation should stop
|
||||
"""
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
})
|
||||
v1 = employee.version_id
|
||||
|
||||
employee.write({'contract_date_start': '2019-01-01'})
|
||||
self.assertEqual(v1.contract_date_start, v1.date_version)
|
||||
|
||||
v2 = employee.create_version({'date_version': '2021-01-01'})
|
||||
employee.write({'contract_date_start': '2020-01-01'})
|
||||
self.assertEqual(v1.date_version, date(2019, 1, 1))
|
||||
self.assertEqual(v2.date_version, date(2021, 1, 1))
|
||||
|
||||
# Archived versions do not count.
|
||||
# So if we archive v2, the synchronisation should start again.
|
||||
v2.active = False
|
||||
employee.write({'contract_date_start': '2021-01-01'})
|
||||
self.assertEqual(v1.contract_date_start, v1.date_version)
|
||||
|
||||
def test_in_out_contract(self):
|
||||
"""
|
||||
Check that an employee is in or out of the contract at a specific date.
|
||||
"""
|
||||
# If no contract dates are defined, the employee is not considered in contract
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
})
|
||||
self.assertFalse(employee._is_in_contract(date(2010, 1, 1)))
|
||||
self.assertFalse(employee._is_in_contract(date(2020, 1, 1)))
|
||||
self.assertFalse(employee._is_in_contract(date(2030, 1, 1)))
|
||||
|
||||
# In a permanent contract, the employee is contract since the contract_date_start
|
||||
employee.contract_date_start = '2020-01-01'
|
||||
self.assertFalse(employee._is_in_contract(date(2010, 1, 1)))
|
||||
self.assertTrue(employee._is_in_contract(date(2020, 1, 1)))
|
||||
self.assertTrue(employee._is_in_contract(date(2030, 1, 1)))
|
||||
|
||||
# In a fixed term contract, the employee is contract in between the contract dates
|
||||
employee.contract_date_end = '2029-12-31'
|
||||
self.assertFalse(employee._is_in_contract(date(2010, 1, 1)))
|
||||
self.assertTrue(employee._is_in_contract(date(2020, 1, 1)))
|
||||
self.assertFalse(employee._is_in_contract(date(2030, 1, 1)))
|
||||
|
||||
def test_cron_update_current_version(self):
|
||||
cron = self.env.ref('hr.ir_cron_data_employee_update_current_version')
|
||||
|
||||
with freeze_time(date(2020, 1, 1)), self.enter_registry_test_mode():
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
})
|
||||
v1 = employee.version_id
|
||||
v2 = employee.create_version({
|
||||
'date_version': '2020-01-02'
|
||||
})
|
||||
self.assertEqual(v1, employee.current_version_id)
|
||||
cron.method_direct_trigger()
|
||||
self.assertEqual(v1, employee.current_version_id)
|
||||
|
||||
with freeze_time(date(2020, 1, 2)), self.enter_registry_test_mode():
|
||||
self.assertEqual(v1, employee.current_version_id)
|
||||
cron.method_direct_trigger()
|
||||
self.assertEqual(v2, employee.current_version_id)
|
||||
|
||||
def test_related_fields_on_version(self):
|
||||
""" Some groups have been added to avoid users with basic access to HR app see some critical (like wage field for instance)
|
||||
This test makes sure the groups added in version fields is also in the employee fields related.
|
||||
However, to define the same groups in employee fields, we have to redefine the related fields (readonly=False, related='version_id.{field_name})
|
||||
Otherwise, the field we loose the linked with the version field and could be readonly instead of editable.
|
||||
"""
|
||||
version_fields = {
|
||||
f_name: field
|
||||
for f_name, field in self.env['hr.version']._fields.items()
|
||||
if field.groups and field.groups not in ['hr.group_hr_user', 'base.group_user'] and not (field.related and field.related.startswith('employee_id'))
|
||||
}
|
||||
employee_fields = {
|
||||
f_name: field
|
||||
for f_name, field in self.env['hr.employee']._fields.items()
|
||||
if f_name in version_fields
|
||||
}
|
||||
fields_without_group = []
|
||||
fields_without_related = []
|
||||
fields_readonly = []
|
||||
for f_name, field in employee_fields.items():
|
||||
v_field = version_fields[f_name]
|
||||
if not (field.groups and field.groups != v_field):
|
||||
fields_without_group.append(f_name)
|
||||
elif not (field.related and field.related == f'version_id.{f_name}'):
|
||||
fields_without_related.append(f_name)
|
||||
elif field.readonly != v_field.readonly:
|
||||
fields_readonly.append(f_name)
|
||||
self.assertFalse(fields_without_group, "Inconsistency between some employee fields and version ones (those employees fields should have the same groups than related one in version")
|
||||
self.assertFalse(fields_without_related, "Some employee fields have the same name than the version ones but they are not related")
|
||||
self.assertFalse(fields_readonly, "(Readonly) Inconsistency between some employee fields and version ones, the both fields (in version and employee) have to be readonly or editable")
|
||||
|
||||
def test_multi_edit_contract_sync_same_contract(self):
|
||||
"""
|
||||
Test the multi-edit contract sync feature when the targeted versions share the same contract.
|
||||
"""
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31',
|
||||
})
|
||||
versions = employee.version_id
|
||||
versions |= employee.create_version({'date_version': '2020-04-01'})
|
||||
versions |= employee.create_version({'date_version': '2020-08-01'})
|
||||
|
||||
versions[:2].contract_date_end = "2020-9-30"
|
||||
|
||||
for version in versions:
|
||||
self.assertEqual(version.contract_date_end, date(2020, 9, 30))
|
||||
|
||||
def test_multi_edit_contract_sync_different_contract(self):
|
||||
"""
|
||||
Test the multi-edit contract sync feature when the targeted versions have different contracts.
|
||||
"""
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-5-31',
|
||||
})
|
||||
versions = employee.version_id
|
||||
versions |= employee.create_version({'date_version': '2020-04-01'})
|
||||
versions |= employee.create_version({
|
||||
'date_version': '2020-08-01',
|
||||
'contract_date_start': '2020-08-01',
|
||||
})
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
versions[:2].contract_date_end = "2020-9-30"
|
||||
|
||||
def test_multi_edit_other(self):
|
||||
"""
|
||||
Test the multi-edit when the targeted versions have different contracts
|
||||
Different fields than contract_date_start and contract_date_end are changed.
|
||||
"""
|
||||
jobA = self.env['hr.job'].create({'name': "Job A"})
|
||||
jobB = self.env['hr.job'].create({'name': "Job B"})
|
||||
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-5-31',
|
||||
'job_id': jobA.id,
|
||||
})
|
||||
versions = employee.version_id
|
||||
versions |= employee.create_version({'date_version': '2020-04-01'})
|
||||
versions |= employee.create_version({
|
||||
'date_version': '2020-08-01',
|
||||
'contract_date_start': '2020-08-01',
|
||||
'job_id': jobA.id,
|
||||
})
|
||||
|
||||
versions[1:].job_id = jobB.id
|
||||
|
||||
self.assertEqual(versions[0].job_id.id, jobA.id)
|
||||
for version in versions[1:]:
|
||||
self.assertEqual(version.job_id.id, jobB.id)
|
||||
|
||||
def test_multi_edit_other_and_contract_date_sync(self):
|
||||
"""
|
||||
Test the multi-edit when the targeted versions have the same contract
|
||||
Different contract_dates and other fields are changed.
|
||||
"""
|
||||
jobA = self.env['hr.job'].create({'name': "Job A"})
|
||||
jobB = self.env['hr.job'].create({'name': "Job B"})
|
||||
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-12-31',
|
||||
'job_id': jobA.id,
|
||||
})
|
||||
versions = employee.version_id
|
||||
versions |= employee.create_version({'date_version': '2020-04-01'})
|
||||
versions |= employee.create_version({'date_version': '2020-08-01'})
|
||||
|
||||
versions[1:].write({
|
||||
"contract_date_end": "2020-9-30",
|
||||
"job_id": jobB.id,
|
||||
})
|
||||
|
||||
self.assertEqual(versions[0].job_id.id, jobA.id)
|
||||
self.assertEqual(versions[0].contract_date_end, date(2020, 9, 30))
|
||||
for version in versions[1:]:
|
||||
self.assertEqual(version.job_id.id, jobB.id)
|
||||
self.assertEqual(version.contract_date_end, date(2020, 9, 30))
|
||||
|
||||
def test_delete_version(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
})
|
||||
v1 = employee.version_id
|
||||
v2 = employee.create_version({
|
||||
'date_version': '2021-01-01',
|
||||
})
|
||||
v3 = employee.create_version({
|
||||
'date_version': '2022-01-01',
|
||||
})
|
||||
self.assertEqual(employee.current_version_id, v3)
|
||||
|
||||
v3.unlink()
|
||||
self.assertEqual(employee.current_version_id, v2)
|
||||
v1.unlink()
|
||||
self.assertEqual(employee.current_version_id, v2)
|
||||
with self.assertRaises(ValidationError):
|
||||
v2.unlink()
|
||||
|
||||
def test_multi_edit_multi_employees_no_contract(self):
|
||||
"""
|
||||
Test the multi-edit when there is one version per employee, without contract
|
||||
"""
|
||||
employee_john, employee_rob = self.env['hr.employee'].create([
|
||||
{
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
},
|
||||
{
|
||||
'name': 'Rob Carter',
|
||||
'date_version': '2020-10-18',
|
||||
}
|
||||
])
|
||||
versions = (employee_john | employee_rob).version_id
|
||||
versions.write({
|
||||
'contract_date_start': '2021-10-10'
|
||||
})
|
||||
self.assertEqual(versions[0].contract_date_start, date(2021, 10, 10))
|
||||
self.assertEqual(versions[1].contract_date_start, date(2021, 10, 10))
|
||||
|
||||
def test_multi_edit_multi_employees_mix_contract(self):
|
||||
"""
|
||||
Test the multi-edit when there is one version per employee, some with contract
|
||||
"""
|
||||
employee_john, employee_rob = self.env['hr.employee'].create([
|
||||
{
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
},
|
||||
{
|
||||
'name': 'Rob Carter',
|
||||
'date_version': '2020-10-18',
|
||||
}
|
||||
])
|
||||
versions = (employee_john | employee_rob).version_id
|
||||
versions.write({
|
||||
'contract_date_start': '2021-10-10'
|
||||
})
|
||||
self.assertEqual(versions[0].contract_date_start, date(2021, 10, 10))
|
||||
self.assertEqual(versions[1].contract_date_start, date(2021, 10, 10))
|
||||
|
||||
def test_multi_edit_multi_employees_all_contract(self):
|
||||
"""
|
||||
Test the multi-edit when there is one version per employee, all with different contract
|
||||
"""
|
||||
employee_john, employee_rob = self.env['hr.employee'].create([
|
||||
{
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
},
|
||||
{
|
||||
'name': 'Rob Carter',
|
||||
'date_version': '2020-10-18',
|
||||
'contract_date_start': '2020-10-18',
|
||||
}
|
||||
])
|
||||
versions = (employee_john | employee_rob).version_id
|
||||
versions |= employee_john.create_version({
|
||||
'date_version': '2021-08-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
})
|
||||
versions.write({
|
||||
'contract_date_start': '2021-10-10'
|
||||
})
|
||||
self.assertEqual(versions[0].contract_date_start, date(2021, 10, 10))
|
||||
self.assertEqual(versions[1].contract_date_start, date(2021, 10, 10))
|
||||
self.assertEqual(versions[2].contract_date_start, date(2021, 10, 10))
|
||||
|
||||
def test_multi_edit_multi_employees_incompatible(self):
|
||||
"""
|
||||
Test the multi-edit when there is one version per employee, one with incompatible dates
|
||||
"""
|
||||
employee_john, employee_rob = self.env['hr.employee'].create([
|
||||
{
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
'contract_date_start': '2020-01-01',
|
||||
'contract_date_end': '2020-10-10'
|
||||
},
|
||||
{
|
||||
'name': 'Rob Carter',
|
||||
'date_version': '2020-10-18',
|
||||
'contract_date_start': '2020-10-18',
|
||||
}
|
||||
])
|
||||
versions = (employee_john | employee_rob).version_id
|
||||
versions |= employee_john.create_version({
|
||||
'date_version': '2021-08-01',
|
||||
'contract_date_start': '2021-08-01',
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
versions.write({
|
||||
'contract_date_start': '2021-10-10'
|
||||
})
|
||||
|
||||
def test_hr_version_fields_tracking(self):
|
||||
tracking_blacklist = {
|
||||
"__last_update",
|
||||
"active_employee",
|
||||
"activity_ids",
|
||||
"company_country_id",
|
||||
"contract_wage",
|
||||
"country_code",
|
||||
"create_date",
|
||||
"create_uid",
|
||||
"currency_id",
|
||||
"date_end",
|
||||
"date_start",
|
||||
"departure_description",
|
||||
"display_name",
|
||||
"id",
|
||||
"is_current",
|
||||
"is_flexible",
|
||||
"is_fully_flexible",
|
||||
"is_future",
|
||||
"is_in_contract",
|
||||
"is_past",
|
||||
"job_title",
|
||||
"last_modified_date",
|
||||
"last_modified_on",
|
||||
"last_modified_uid",
|
||||
"member_of_department",
|
||||
"message_follower_ids",
|
||||
"message_ids",
|
||||
"message_partner_ids",
|
||||
"rating_ids",
|
||||
"template_warning",
|
||||
"tz",
|
||||
"website_message_ids",
|
||||
"work_location_name",
|
||||
"work_location_type",
|
||||
"write_date",
|
||||
"write_uid",
|
||||
}
|
||||
|
||||
hr_version_model = self.env['hr.version']
|
||||
fields_without_tracking = []
|
||||
|
||||
for field_name, field in hr_version_model._fields.items():
|
||||
if field_name in tracking_blacklist:
|
||||
continue
|
||||
if field.compute and not field.inverse:
|
||||
continue
|
||||
if field.related:
|
||||
continue
|
||||
if hasattr(field, 'store') and field.store is False:
|
||||
continue
|
||||
if hasattr(field, 'tracking') and not field.tracking:
|
||||
fields_without_tracking.append(field_name)
|
||||
|
||||
self.assertFalse(
|
||||
fields_without_tracking,
|
||||
f"The following hr.version fields should have tracking=True: {fields_without_tracking}",
|
||||
)
|
||||
|
||||
def test_related_fields_on_version_onchange(self):
|
||||
""" This test is to ensure that each _onchange method on version has a corresponding _onchange on employee that calls it. """
|
||||
version_methods = {method for method in dir(self.env['hr.version']) if method.startswith('_onchange')}
|
||||
employee_methods = {method for method in dir(self.env['hr.employee']) if method.startswith('_onchange')}
|
||||
not_implemented_onchanges = version_methods - employee_methods
|
||||
self.assertFalse(
|
||||
not_implemented_onchanges,
|
||||
f"""The following _onchange methods on hr.version should have corresponding methods implemented on hr.employee: {not_implemented_onchanges}\n
|
||||
You might need to implement methods with the same name on hr.employee and call the corresponding self.version_id._onchange inside"""
|
||||
)
|
||||
|
||||
def test_search_on_version_fields(self):
|
||||
Department = self.env['hr.department'].with_context(tracking_disable=True)
|
||||
rd_dep = Department.create({
|
||||
'name': 'Research and devlopment',
|
||||
})
|
||||
employee1, employee2 = employees = self.env['hr.employee'].create([
|
||||
{
|
||||
'contract_date_start': '2020-10-10',
|
||||
'wage': 3000,
|
||||
'name': 'Employee1',
|
||||
'hr_responsible_id': self.res_users_hr_manager.id,
|
||||
'department_id': rd_dep.id,
|
||||
},
|
||||
{
|
||||
'contract_date_start': '2022-10-10',
|
||||
'wage': 2000,
|
||||
'name': 'Employee2',
|
||||
},
|
||||
])
|
||||
internal_user = mail_new_test_user(
|
||||
self.env,
|
||||
email='internal_user@example.com',
|
||||
login='internal_user',
|
||||
name='Internal User',
|
||||
)
|
||||
self.employee.department_id = rd_dep
|
||||
self.employee.user_id = internal_user
|
||||
|
||||
HrEmployeePublic_with_internal_user = self.env['hr.employee.public'].with_user(internal_user)
|
||||
with self.assertRaises(AccessError, msg="Internal user should not be able to access to hr.employee model"):
|
||||
HrEmployeePublic_with_internal_user.search([
|
||||
('employee_id.contract_date_start', '<', '2022-01-01'),
|
||||
('id', 'in', employees.ids),
|
||||
])
|
||||
with self.assertRaises(AccessError, msg="Internal user should not be able to access to hr.employee model"):
|
||||
HrEmployeePublic_with_internal_user.search([('employee_id.wage', '=', 2000), ('id', 'in', employees.ids)])
|
||||
with self.assertRaises(AccessError, msg="Internal user should not be able to access to hr.employee model"):
|
||||
HrEmployeePublic_with_internal_user.search([('employee_id.version_id.wage', '=', 2000), ('id', 'in', employees.ids)])
|
||||
self.assertEqual(
|
||||
HrEmployeePublic_with_internal_user.search([('name', '=', 'Employee2'), ('id', 'in', employees.ids)]),
|
||||
self.env['hr.employee.public'].browse(employee2.id),
|
||||
)
|
||||
self.assertEqual(
|
||||
HrEmployeePublic_with_internal_user.search([('member_of_department', '=', True), ('id', 'in', employees.ids)]),
|
||||
self.env['hr.employee.public'].browse(employee1.id),
|
||||
)
|
||||
|
||||
HrEmployee_with_office_user = self.env['hr.employee'].with_user(self.res_users_hr_officer)
|
||||
self.employee.user_id = self.res_users_hr_officer
|
||||
with self.assertRaises(AccessError, msg="HR Officer should not be able to access to 'payroll fields'"):
|
||||
HrEmployee_with_office_user.search([('contract_date_start', '<', '2022-01-01'), ('id', 'in', employees.ids)])
|
||||
with self.assertRaises(AccessError, msg="HR Officer should not be able to access to 'payroll fields'"):
|
||||
HrEmployee_with_office_user.search([('wage', '=', 2000), ('id', 'in', employees.ids)])
|
||||
with self.assertRaises(AccessError, msg="HR Officer should not be able to access to 'payroll fields'"):
|
||||
HrEmployee_with_office_user.search([('version_id.wage', '=', 2000), ('id', 'in', employees.ids)])
|
||||
self.assertEqual(HrEmployee_with_office_user.search([('name', '=', 'Employee1'), ('id', 'in', employees.ids)]), employee1)
|
||||
self.assertEqual(HrEmployee_with_office_user.search([('hr_responsible_id', '=', self.res_users_hr_manager.id), ('id', 'in', employees.ids)]), employee1)
|
||||
self.assertEqual(HrEmployee_with_office_user.search([('version_id.hr_responsible_id', '=', self.res_users_hr_manager.id), ('id', 'in', employees.ids)]), employee1)
|
||||
self.assertEqual(HrEmployee_with_office_user.search([('member_of_department', '=', True), ('id', 'in', employees.ids)]), employee1)
|
||||
|
||||
if payroll_group := self.env.ref('hr_payroll.group_hr_payroll_user', raise_if_not_found=False):
|
||||
self.res_users_hr_manager.group_ids += payroll_group
|
||||
HrEmployee_with_manager_user = self.env['hr.employee'].with_user(self.res_users_hr_manager)
|
||||
self.employee.user_id = self.res_users_hr_manager
|
||||
self.assertEqual(HrEmployee_with_manager_user.search([('contract_date_start', '<', '2022-01-01'), ('id', 'in', employees.ids)]), employee1)
|
||||
self.assertEqual(HrEmployee_with_manager_user.search([('wage', '=', 2000), ('id', 'in', employees.ids)]), employee2)
|
||||
self.assertEqual(HrEmployee_with_manager_user.search([('version_id.wage', '=', 2000), ('id', 'in', employees.ids)]), employee2)
|
||||
self.assertEqual(HrEmployee_with_manager_user.search([('hr_responsible_id', '=', self.res_users_hr_manager.id), ('id', 'in', employees.ids)]), employee1)
|
||||
self.assertEqual(HrEmployee_with_manager_user.search([('version_id.hr_responsible_id', '=', self.res_users_hr_manager.id), ('id', 'in', employees.ids)]), employee1)
|
||||
self.assertEqual(HrEmployee_with_manager_user.search([('member_of_department', '=', True), ('id', 'in', employees.ids)]), employee1)
|
||||
|
||||
def test_archive_or_unassign_all_versions(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
})
|
||||
another_employee = self.env['hr.employee'].create({
|
||||
'name': 'Jane Doe'
|
||||
})
|
||||
employee.create_version({
|
||||
'date_version': '2021-01-01',
|
||||
})
|
||||
# make sure there are at least 2 versions
|
||||
self.assertEqual(len(employee.version_ids), 2)
|
||||
# attempt to archive all versions
|
||||
with self.assertRaises(ValidationError):
|
||||
employee.version_ids.action_archive()
|
||||
# attempt to reassign all versions
|
||||
with self.assertRaises(ValidationError):
|
||||
employee.version_ids.write({"employee_id": another_employee.id})
|
||||
|
||||
def test_unlink_version_except_one(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
'date_version': '2020-01-01',
|
||||
})
|
||||
version = employee.create_version({
|
||||
'date_version': '2021-01-01',
|
||||
})
|
||||
self.assertEqual(len(employee.version_ids), 2)
|
||||
version.unlink()
|
||||
self.assertEqual(len(employee.version_ids), 1)
|
||||
|
||||
def test_date_version_sync_contract_date_start_for_single_version(self):
|
||||
"""
|
||||
This test is to ensure that in case when an employee has only one version, writing contract_date_start on the employee
|
||||
will synchronize the version.date_version with that contract_date_start
|
||||
"""
|
||||
with freeze_time(date(2025, 12, 20)), self.enter_registry_test_mode():
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'John Doe',
|
||||
})
|
||||
version = employee.version_id
|
||||
self.assertEqual(version.date_version, date(2025, 12, 20))
|
||||
|
||||
employee.write({'contract_date_start': '2025-12-10'})
|
||||
self.assertEqual(version.contract_date_start, date(2025, 12, 10))
|
||||
self.assertEqual(version.date_version, date(2025, 12, 10))
|
||||
|
||||
# date_version should not be reset if contract_date_start is cleared
|
||||
employee.write({'contract_date_start': False})
|
||||
self.assertEqual(version.date_version, date(2025, 12, 10))
|
||||
|
||||
# and setting it again should re-sync
|
||||
employee.write({'contract_date_start': '2025-12-15'})
|
||||
self.assertEqual(version.contract_date_start, date(2025, 12, 15))
|
||||
self.assertEqual(version.date_version, date(2025, 12, 15))
|
||||
288
odoo-bringout-oca-ocb-hr/hr/tests/test_mail_activity_plan.py
Normal file
288
odoo-bringout-oca-ocb-hr/hr/tests/test_mail_activity_plan.py
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import fields, Command
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.mail.tests.common_activity import ActivityScheduleCase
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
class ActivityScheduleHRCase(ActivityScheduleCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.plan_onboarding = cls.env['mail.activity.plan'].create({
|
||||
'name': 'Test Onboarding',
|
||||
'res_model': 'hr.employee',
|
||||
'template_ids': [
|
||||
Command.create({
|
||||
'activity_type_id': cls.activity_type_todo.id,
|
||||
'responsible_id': cls.user_admin.id,
|
||||
'responsible_type': 'other',
|
||||
'sequence': 10,
|
||||
'summary': 'Plan training',
|
||||
}), Command.create({
|
||||
'activity_type_id': cls.activity_type_todo.id,
|
||||
'responsible_id': False,
|
||||
'responsible_type': 'coach',
|
||||
'sequence': 20,
|
||||
'summary': 'Training',
|
||||
}),
|
||||
]
|
||||
})
|
||||
cls.plan_party = cls.env['mail.activity.plan'].create({
|
||||
'name': 'Test Party Plan',
|
||||
'res_model': 'res.partner',
|
||||
'template_ids': [
|
||||
Command.create({
|
||||
'activity_type_id': cls.activity_type_todo.id,
|
||||
'responsible_id': cls.user_admin.id,
|
||||
'responsible_type': 'on_demand',
|
||||
'sequence': 10,
|
||||
'summary': 'Party',
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
cls.user_manager = mail_new_test_user(
|
||||
cls.env,
|
||||
email='test.manager@test.mycompany.com',
|
||||
groups='base.group_user,hr.group_hr_manager',
|
||||
login='test_manager',
|
||||
name='Test Manager',
|
||||
)
|
||||
cls.user_coach = mail_new_test_user(
|
||||
cls.env,
|
||||
email='test.coach@test.mycompany.com',
|
||||
groups='base.group_user,hr.group_hr_manager',
|
||||
login='test_coach',
|
||||
name='Test Coach',
|
||||
)
|
||||
cls.user_employee_1 = mail_new_test_user(
|
||||
cls.env,
|
||||
email='test.employee1@test.mycompany.com',
|
||||
groups='base.group_user,hr.group_hr_manager',
|
||||
login='test_employee1',
|
||||
name='Test Employee 1',
|
||||
)
|
||||
cls.user_employee_2 = mail_new_test_user(
|
||||
cls.env,
|
||||
email='test.employee2@test.mycompany.com',
|
||||
groups='base.group_user,hr.group_hr_manager',
|
||||
login='test_employee2',
|
||||
name='Test Employee 2',
|
||||
)
|
||||
cls.user_employee_dep_b = mail_new_test_user(
|
||||
cls.env,
|
||||
email='test.employeedepb@test.mycompany.com',
|
||||
groups='base.group_user,hr.group_hr_manager',
|
||||
login='test_employee_dep_b',
|
||||
name='Test Employee DepB',
|
||||
)
|
||||
cls.users = cls.user_manager + cls.user_coach + cls.user_employee_1 + cls.user_employee_2 + cls.user_employee_dep_b
|
||||
|
||||
cls.user_internal_basic = mail_new_test_user(
|
||||
cls.env,
|
||||
email='non.employee@test.mycompany.com',
|
||||
groups='base.group_user',
|
||||
login='non_employee',
|
||||
name='Non Employee',
|
||||
)
|
||||
|
||||
cls.employees = cls.env['hr.employee'].create([
|
||||
{
|
||||
'name': user.name,
|
||||
'user_id': user.id,
|
||||
'work_email': user.email,
|
||||
} for user in cls.users
|
||||
])
|
||||
cls.employee_manager, cls.employee_coach, cls.employee_1, cls.employee_2, cls.employee_dep_b = cls.employees
|
||||
cls.department_a = cls.env['hr.department'].create({
|
||||
'name': 'Test Department A',
|
||||
'member_ids': [Command.link(employee.id) for employee in cls.employees - cls.employee_dep_b],
|
||||
})
|
||||
cls.department_b = cls.env['hr.department'].create({
|
||||
'name': 'Test Department B',
|
||||
'member_ids': [Command.link(cls.employee_dep_b.id)],
|
||||
})
|
||||
cls.employee_1.coach_id = cls.employee_coach
|
||||
cls.employee_1.parent_id = cls.employee_manager
|
||||
cls.employee_2.coach_id = cls.employee_coach
|
||||
cls.employee_2.parent_id = cls.employee_manager
|
||||
cls.employee_coach.parent_id = cls.employee_manager
|
||||
cls.employee_dep_b.coach_id = cls.employee_coach
|
||||
|
||||
cls.employee_3 = cls.employee_coach
|
||||
cls.employee_4 = cls.employee_manager
|
||||
cls.employee_4.coach_id = cls.employee_coach
|
||||
for employee, date_start in ((cls.employee_1, '2023-08-01'),
|
||||
(cls.employee_2, '2023-09-01'),
|
||||
(cls.employee_3, '2023-12-01'),
|
||||
(cls.employee_4, '2024-01-01')):
|
||||
employee.version_id.write({
|
||||
'contract_date_end': fields.Date.from_string('2025-12-31'),
|
||||
'contract_date_start': fields.Date.from_string(date_start),
|
||||
'date_version': fields.Date.from_string(date_start),
|
||||
'name': 'Contract',
|
||||
'wage': 1,
|
||||
})
|
||||
|
||||
|
||||
@tagged('mail_activity', 'mail_activity_plan')
|
||||
class TestActivitySchedule(ActivityScheduleHRCase):
|
||||
|
||||
@users('admin')
|
||||
def test_department(self):
|
||||
""" Check that the allowed plan are filtered according to the department. """
|
||||
no_plan = self.env['mail.activity.plan']
|
||||
plan_department_a, plan_department_b = self.env['mail.activity.plan'].create([
|
||||
{
|
||||
'department_id': department.id,
|
||||
'name': f'plan {department.name}',
|
||||
'res_model': 'hr.employee',
|
||||
'template_ids': [(0, 0, {'activity_type_id': self.activity_type_todo.id})],
|
||||
} for department in self.department_a + self.department_b
|
||||
])
|
||||
for employees, expected_department, authorized_plans, non_authorized_plans in (
|
||||
(self.employee_1 + self.employee_dep_b, False, self.plan_onboarding, no_plan),
|
||||
(self.employee_1 + self.employee_2, self.department_a, self.plan_onboarding + plan_department_a, plan_department_b),
|
||||
(self.employee_1, self.department_a, self.plan_onboarding + plan_department_a, plan_department_b),
|
||||
(self.employee_dep_b, self.department_b, self.plan_onboarding + plan_department_b, plan_department_a),
|
||||
):
|
||||
with self._instantiate_activity_schedule_wizard(employees) as form:
|
||||
if expected_department:
|
||||
self.assertEqual(form.department_id, expected_department)
|
||||
else:
|
||||
self.assertFalse(form.department_id)
|
||||
for plan in non_authorized_plans:
|
||||
self.assertNotIn(plan, form.plan_available_ids)
|
||||
for plan in authorized_plans:
|
||||
with self._instantiate_activity_schedule_wizard(employees) as form:
|
||||
form.plan_id = plan
|
||||
|
||||
def test_res_model_compatibility(self):
|
||||
""" Check that we cannot change the plan model to a model different
|
||||
of employee if hr plan specific features are used. """
|
||||
with self.assertRaises(
|
||||
UserError,
|
||||
msg="Coach, manager or employee can only be chosen as template responsible with employee plan."):
|
||||
self.plan_onboarding.res_model = 'res.partner'
|
||||
self.plan_onboarding.template_ids[1].responsible_type = 'on_demand'
|
||||
self.plan_onboarding.res_model = 'res.partner'
|
||||
self.plan_onboarding.res_model = 'hr.employee'
|
||||
self.plan_onboarding.template_ids[1].responsible_type = 'manager'
|
||||
with self.assertRaises(
|
||||
UserError,
|
||||
msg="Coach, manager or employee can only be chosen as template responsible with employee plan."):
|
||||
self.plan_onboarding.res_model = 'res.partner'
|
||||
self.plan_onboarding.template_ids[1].responsible_type = 'employee'
|
||||
with self.assertRaises(
|
||||
UserError,
|
||||
msg="Coach, manager or employee can only be chosen as template responsible with employee plan."):
|
||||
self.plan_onboarding.res_model = 'res.partner'
|
||||
self.plan_onboarding.template_ids[1].responsible_type = 'on_demand'
|
||||
self.plan_onboarding.res_model = 'res.partner'
|
||||
self.plan_onboarding.res_model = 'hr.employee'
|
||||
self.plan_onboarding.department_id = self.department_a
|
||||
self.plan_onboarding.res_model = 'res.partner'
|
||||
self.assertFalse(self.plan_onboarding.department_id)
|
||||
|
||||
def test_responsible(self):
|
||||
""" Check that the responsible is correctly configured. """
|
||||
self.plan_onboarding.template_ids[0].write({
|
||||
'responsible_type': 'manager',
|
||||
'responsible_id': False,
|
||||
})
|
||||
self.plan_onboarding.write({
|
||||
'template_ids': [(0, 0, {
|
||||
'activity_type_id': self.activity_type_todo.id,
|
||||
'summary': 'Send feedback to the manager',
|
||||
'responsible_type': 'employee',
|
||||
'sequence': 30,
|
||||
})],
|
||||
})
|
||||
for employees in (self.employee_1, self.employee_1 + self.employee_2):
|
||||
# Happy case
|
||||
form = self._instantiate_activity_schedule_wizard(employees)
|
||||
form.plan_id = self.plan_onboarding
|
||||
expected_summary_lines = [
|
||||
('Plan training', self.user_manager.id if len(employees) == 1 else False),
|
||||
('Training', self.user_coach.id if len(employees) == 1 else False),
|
||||
('Send feedback to the manager', employees.user_id.id if len(employees) == 1 else False),
|
||||
]
|
||||
for summary_line, (expected_description, expected_responsible_id) in zip(
|
||||
form.plan_schedule_line_ids._records, expected_summary_lines, strict=True
|
||||
):
|
||||
self.assertEqual(summary_line['line_description'], expected_description)
|
||||
self.assertEqual(summary_line['responsible_user_id'], expected_responsible_id)
|
||||
self.assertFalse(form.has_error)
|
||||
wizard = form.save()
|
||||
wizard.action_schedule_plan()
|
||||
for employee in employees:
|
||||
activities = self.get_last_activities(employee, 3)
|
||||
self.assertEqual(len(activities), 3)
|
||||
self.assertEqual(activities[0].user_id, self.user_manager)
|
||||
self.assertEqual(activities[1].user_id, self.user_coach)
|
||||
self.assertEqual(activities[2].user_id, employee.user_id)
|
||||
|
||||
# Cases with errors
|
||||
self.employee_1.parent_id = False
|
||||
self.employee_1.coach_id = False
|
||||
form = self._instantiate_activity_schedule_wizard(employees)
|
||||
form.plan_id = self.plan_onboarding
|
||||
self.assertTrue(form.has_error)
|
||||
n_error = form.error.count('<li>')
|
||||
self.assertEqual(n_error, 2)
|
||||
self.assertIn(f'Manager of employee {self.employee_1.name} is not set.', form.error)
|
||||
self.assertIn(f'Coach of employee {self.employee_1.name} is not set.', form.error)
|
||||
with self.assertRaises(ValidationError):
|
||||
form.save()
|
||||
self.employee_1.parent_id = self.employee_manager
|
||||
self.employee_1.coach_id = self.employee_coach
|
||||
self.employee_coach.user_id = False
|
||||
self.employee_manager.user_id = False
|
||||
form = self._instantiate_activity_schedule_wizard(employees)
|
||||
form.plan_id = self.plan_onboarding
|
||||
self.assertTrue(form.has_warning)
|
||||
n_warning = form.warning.count('<li>')
|
||||
self.assertEqual(n_warning, 2 * len(employees))
|
||||
self.assertIn(f"The user of {self.employee_1.name}'s coach is not set.", form.warning)
|
||||
self.assertIn(f'The manager of {self.employee_1.name} should be linked to a user.', form.warning)
|
||||
if len(employees) > 1:
|
||||
self.assertIn(f"The user of {self.employee_2.name}'s coach is not set.", form.warning)
|
||||
self.assertIn(f'The manager of {self.employee_2.name} should be linked to a user.', form.warning)
|
||||
# should save without error, with coach
|
||||
form.save()
|
||||
self.employee_coach.user_id = self.user_coach
|
||||
self.employee_manager.user_id = self.user_manager
|
||||
|
||||
@freeze_time('2023-08-31')
|
||||
@users('admin')
|
||||
def test_default_due_date(self):
|
||||
for employees, plan_date in (
|
||||
(self.employee_1, '2023-09-30'),
|
||||
(self.employee_2, '2023-09-30'),
|
||||
(self.employee_3, '2023-12-01'),
|
||||
(self.employee_4, '2024-01-01'),
|
||||
(self.employee_1 + self.employee_2 + self.employee_3, '2023-09-30'),
|
||||
(self.employee_2 + self.employee_3, '2023-09-30'),
|
||||
(self.employee_1 + self.employee_3, '2023-09-30'),
|
||||
(self.employee_3 + self.employee_4, '2023-12-01'),
|
||||
(self.employee_4 + self.employee_3, '2023-12-01'),
|
||||
):
|
||||
with self._instantiate_activity_schedule_wizard(employees) as form:
|
||||
form.plan_id = self.plan_onboarding
|
||||
self.assertEqual(form.plan_date, fields.Date.from_string(plan_date))
|
||||
|
||||
# not applicable on other models
|
||||
customers = self.env['res.partner'].create([
|
||||
{'name': 'Customer1'},
|
||||
{'name': 'Customer2'},
|
||||
])
|
||||
with self._instantiate_activity_schedule_wizard(customers) as form:
|
||||
form.plan_id = self.plan_party
|
||||
self.assertEqual(form.plan_date, fields.Date.from_string('2023-08-31'))
|
||||
69
odoo-bringout-oca-ocb-hr/hr/tests/test_mail_features.py
Normal file
69
odoo-bringout-oca-ocb-hr/hr/tests/test_mail_features.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install', 'mail_flow')
|
||||
class TestHrEmployeeMail(TestHrCommon, MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.test_template_employee = cls.env['mail.template'].with_user(cls.user_admin).create({
|
||||
'auto_delete': True,
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'email_from': '{{ object.user_id.email_formatted or user.email_formatted or "" }}',
|
||||
'model_id': cls.env['ir.model']._get_id('hr.employee'),
|
||||
'name': 'Test Hr Template',
|
||||
'subject': 'Test {{ object.name }}',
|
||||
'use_default_to': True,
|
||||
})
|
||||
# note: email and phone are user related fields
|
||||
cls.test_employee = cls.env['hr.employee'].create([
|
||||
{
|
||||
'company_id': cls.company_admin.id,
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
'name': 'QuickEmployee',
|
||||
'work_email': 'quick.employee@test.example.com',
|
||||
'work_phone': '+32455001122',
|
||||
},
|
||||
])
|
||||
|
||||
def test_assert_initial_values(self):
|
||||
self.assertTrue(self.test_employee.work_contact_id)
|
||||
self.assertFalse(self.test_employee.message_partner_ids)
|
||||
self.assertFalse(self.test_employee.email)
|
||||
self.assertFalse(self.test_employee.phone)
|
||||
self.assertFalse(self.test_employee.user_id)
|
||||
|
||||
def test_employee_get_default_recipients(self):
|
||||
employee = self.test_employee.with_user(self.res_users_hr_officer)
|
||||
defaults = employee._message_get_default_recipients()
|
||||
self.assertDictEqual(
|
||||
defaults[employee.id],
|
||||
{'email_cc': '', 'email_to': '', 'partner_ids': self.test_employee.work_contact_id.ids},
|
||||
)
|
||||
|
||||
def test_employee_get_suggested_recipients(self):
|
||||
employee = self.test_employee.with_user(self.res_users_hr_officer)
|
||||
suggested = employee._message_get_suggested_recipients()
|
||||
self.assertListEqual(suggested, [
|
||||
{
|
||||
'create_values': {},
|
||||
'email': self.test_employee.work_contact_id.email_normalized,
|
||||
'name': self.test_employee.work_contact_id.name,
|
||||
'partner_id': self.test_employee.work_contact_id.id,
|
||||
},
|
||||
])
|
||||
|
||||
def test_employee_template(self):
|
||||
employee, template = self.test_employee.with_user(self.res_users_hr_officer), self.test_template_employee.with_user(self.res_users_hr_officer)
|
||||
message = employee.message_post_with_source(
|
||||
template,
|
||||
message_type='comment',
|
||||
subtype_id=self.env.ref('mail.mt_comment').id,
|
||||
)
|
||||
self.assertEqual(
|
||||
message.notified_partner_ids, self.test_employee.work_contact_id,
|
||||
'Matches suggested recipients',
|
||||
)
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import Form
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
from odoo.addons.base.models.ir_qweb import QWebException
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
class TestMultiCompany(TestHrCommon):
|
||||
class TestMultiCompanyReport(TestHrCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
|
@ -35,7 +37,70 @@ class TestMultiCompany(TestHrCommon):
|
|||
self.assertIn(b'Machin', content)
|
||||
|
||||
def test_single_company_report(self):
|
||||
with self.assertRaises(QWebException): # CacheMiss followed by AccessError
|
||||
with self.assertRaises(AccessError): # CacheMiss followed by AccessError
|
||||
self.env['ir.actions.report'].with_user(self.res_users_hr_officer).with_company(
|
||||
self.company_1
|
||||
)._render_qweb_pdf('hr.hr_employee_print_badge', res_ids=self.employees.ids)
|
||||
|
||||
|
||||
class TestMultiCompany(TestHrCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.company_a = cls.env['res.company'].create({'name': 'Company A'})
|
||||
cls.company_b = cls.env['res.company'].create({'name': 'Company B'})
|
||||
|
||||
cls.user_a = mail_new_test_user(cls.env, login='user_a', company_id=cls.company_a.id, company_ids=(cls.company_a | cls.company_b).ids)
|
||||
cls.user_b = mail_new_test_user(cls.env, login='user_b', company_id=cls.company_b.id)
|
||||
|
||||
cls.employee_a = cls.env['hr.employee'].create({
|
||||
'name': 'Employee A',
|
||||
'company_id': cls.company_a.id,
|
||||
'user_id': cls.user_a.id,
|
||||
})
|
||||
|
||||
cls.employee_other_a = cls.env['hr.employee'].create({
|
||||
'name': 'Employee Other A',
|
||||
'company_id': cls.company_a.id,
|
||||
})
|
||||
|
||||
cls.employee_b = cls.env['hr.employee'].create({
|
||||
'name': 'Employee B',
|
||||
'company_id': cls.company_b.id,
|
||||
'user_id': cls.user_b.id,
|
||||
'parent_id': cls.employee_a.id,
|
||||
})
|
||||
|
||||
cls.employee_other_b = cls.env['hr.employee'].create({
|
||||
'name': 'Employee Other B',
|
||||
'company_id': cls.company_b.id,
|
||||
})
|
||||
|
||||
cls.env.flush_all()
|
||||
cls.env.invalidate_all()
|
||||
|
||||
def test_read_manager_employee(self):
|
||||
# UserB should be able to read its manager's record - without being connected
|
||||
# on company A
|
||||
self.employee_a.with_user(self.user_b).with_company(self.company_b).name
|
||||
|
||||
self.employee_b.with_user(self.user_a).with_company(self.company_a).name
|
||||
|
||||
# UserB should not be able to read other employees in that company
|
||||
with self.assertRaises(AccessError):
|
||||
self.employee_other_a.with_user(self.user_b).with_company(self.company_b).name
|
||||
|
||||
def test_read_no_manager_company(self):
|
||||
self.employee_b.parent_id = False
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.employee_a.with_user(self.user_b).name
|
||||
|
||||
def test_compute_presence_state(self):
|
||||
self.user_a.company_ids = self.company_a
|
||||
# user A should still read the employee since he is the manager of that employee
|
||||
self.employee_b.with_user(self.user_a).with_company(self.company_a).name
|
||||
|
||||
# user A should still read hr_presence_state even if he does not have access to the company of the employee
|
||||
self.employee_b.with_user(self.user_a).with_company(self.company_a).hr_presence_state
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestEmployeeMultipleBanksUi(HttpCase):
|
||||
def test_employee_profile_tour(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Johnny H.',
|
||||
})
|
||||
self.start_tour("/odoo", 'hr_employee_multiple_bank_accounts_tour', login="admin", timeout=200)
|
||||
total = 0
|
||||
for ba in employee.bank_account_ids:
|
||||
ba_percentage = employee.salary_distribution[str(ba.id)]['amount']
|
||||
ba_is_percentage = employee.salary_distribution[str(ba.id)]['amount_is_percentage']
|
||||
self.assertEqual(ba_is_percentage, True)
|
||||
self.assertAlmostEqual(ba_percentage, 33.33, delta=0.011)
|
||||
total += ba_percentage
|
||||
self.assertAlmostEqual(total, 100.0, "Total must amount to 100.")
|
||||
102
odoo-bringout-oca-ocb-hr/hr/tests/test_payroll_fields_access.py
Normal file
102
odoo-bringout-oca-ocb-hr/hr/tests/test_payroll_fields_access.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPayrollFieldsAccess(TransactionCase):
|
||||
def test_related_fields_on_version(self):
|
||||
""" Some groups have been added to avoid users with basic access to HR app see some critical (like wage field for instance)
|
||||
This test makes sure the groups added in version fields is also in the employee fields related.
|
||||
However, to define the same groups in employee fields, we have to redefine the related fields (readonly=False, related='version_id.{field_name})
|
||||
Otherwise, the field we loose the linked with the version field and could be readonly instead of editable.
|
||||
"""
|
||||
version_fields = {
|
||||
f_name: field
|
||||
for f_name, field in self.env['hr.version']._fields.items()
|
||||
if field.groups and field.groups not in ['hr.group_hr_user', 'base.group_user'] and not (field.related and field.related.startswith('employee_id'))
|
||||
}
|
||||
employee_fields = {
|
||||
f_name: field
|
||||
for f_name, field in self.env['hr.employee']._fields.items()
|
||||
if f_name in version_fields
|
||||
}
|
||||
fields_without_group = []
|
||||
fields_without_related = []
|
||||
fields_readonly = []
|
||||
for f_name, field in employee_fields.items():
|
||||
v_field = version_fields[f_name]
|
||||
if not (field.groups and field.groups != v_field):
|
||||
fields_without_group.append(f_name)
|
||||
elif not (field.related and field.related == f'version_id.{f_name}'):
|
||||
fields_without_related.append(f_name)
|
||||
elif field.readonly != v_field.readonly:
|
||||
fields_readonly.append(f_name)
|
||||
self.assertFalse(fields_without_group, "Inconsistency between some employee fields and version ones (those employees fields should have the same groups than related one in version")
|
||||
self.assertFalse(fields_without_related, "Some employee fields have the same name than the version ones but they are not related")
|
||||
self.assertFalse(fields_readonly, "(Readonly) Inconsistency between some employee fields and version ones, the both fields (in version and employee) have to be readonly or editable")
|
||||
|
||||
def _test_payroll_fields_are_hidden_to_non_payroll_users(self, model_name, view_id, payroll_page_name):
|
||||
form_view = self.env.ref(view_id)
|
||||
form_view_get_result = self.env['hr.employee'].get_view(form_view.id, 'form')
|
||||
form_view_arch = form_view_get_result['arch']
|
||||
node = etree.fromstring(form_view_arch)
|
||||
self.assertTrue(node.xpath(f"//page[@name='{payroll_page_name}']"), f"[{model_name}] Payroll page should be found in the form view.")
|
||||
payroll_field_node_list = node.xpath(f"//page[@name='{payroll_page_name}']//field[not(ancestor::field)]")
|
||||
self.assertTrue(payroll_field_node_list, f"[{model_name}] At least one field should be found inside Payroll information page.")
|
||||
payroll_field_names = [
|
||||
payroll_field_node.attrib['name']
|
||||
for payroll_field_node in payroll_field_node_list
|
||||
]
|
||||
current_payroll_field_names = {
|
||||
f_name
|
||||
for f_name, field in self.env[model_name]._fields.items()
|
||||
if field.groups and ('hr.group_hr_manager' in field.groups or 'hr_payroll.group_hr_payroll_user' in field.groups)
|
||||
}
|
||||
whitelist_field_names = [
|
||||
'resource_calendar_id',
|
||||
'employee_type',
|
||||
'tz',
|
||||
'currency_id',
|
||||
'lang',
|
||||
'registration_number',
|
||||
'standard_calendar_id',
|
||||
'employee_age',
|
||||
'distance_home_work',
|
||||
'distance_home_work_unit',
|
||||
'show_billable_time_target',
|
||||
'billable_time_target',
|
||||
'holidays',
|
||||
'car_id',
|
||||
'new_car',
|
||||
'new_car_model_id',
|
||||
'ordered_car_id',
|
||||
'fuel_type',
|
||||
'transport_mode_bike',
|
||||
'bike_id',
|
||||
'new_bike',
|
||||
'new_bike_model_id',
|
||||
'originated_offer_id',
|
||||
'is_non_resident',
|
||||
'structure_id'
|
||||
]
|
||||
missing_group_field_names = [
|
||||
f_name
|
||||
for f_name in payroll_field_names
|
||||
if f_name not in current_payroll_field_names and f_name not in whitelist_field_names
|
||||
]
|
||||
self.assertFalse(
|
||||
missing_group_field_names,
|
||||
"[{}] Missing payroll group on following fields: \n - {}".format(
|
||||
model_name,
|
||||
'\n - '.join(missing_group_field_names),
|
||||
),
|
||||
)
|
||||
|
||||
def test_payroll_fields_are_hidden_to_non_payroll_users_in_employee_form_view(self):
|
||||
self._test_payroll_fields_are_hidden_to_non_payroll_users('hr.employee', 'hr.view_employee_form', 'payroll_information')
|
||||
|
||||
def test_payroll_fields_are_hidden_to_non_payroll_users_in_version_form_view(self):
|
||||
self._test_payroll_fields_are_hidden_to_non_payroll_users('hr.version', 'hr.hr_contract_template_form_view', 'information')
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import datetime
|
||||
from pytz import timezone, utc
|
||||
|
||||
from odoo.addons.resource.models.resource import Intervals, sum_intervals
|
||||
from datetime import datetime, date
|
||||
from pytz import utc, timezone
|
||||
|
||||
from odoo.tools.intervals import Intervals
|
||||
from odoo.fields import Date
|
||||
from odoo.tools.date_utils import sum_intervals
|
||||
|
||||
from .common import TestHrCommon
|
||||
|
||||
|
|
@ -23,6 +26,50 @@ class TestResource(TestHrCommon):
|
|||
cls.env.cr.execute("UPDATE hr_employee SET create_date=%s WHERE id=%s",
|
||||
(cls.employee_niv_create_date, cls.employee_niv.id))
|
||||
|
||||
cls.calendar_richard = cls.env['resource.calendar'].create({'name': 'Calendar of Richard'})
|
||||
cls.employee.resource_calendar_id = cls.calendar_richard
|
||||
|
||||
cls.calendar_35h = cls.env['resource.calendar'].create({
|
||||
'name': '35h calendar',
|
||||
'attendance_ids': [
|
||||
(0, 0, {'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Monday Lunch', 'dayofweek': '0', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
|
||||
(0, 0, {'name': 'Monday Evening', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
|
||||
(0, 0, {'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Tuesday Lunch', 'dayofweek': '1', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
|
||||
(0, 0, {'name': 'Tuesday Evening', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
|
||||
(0, 0, {'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Wednesday Lunch', 'dayofweek': '2', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
|
||||
(0, 0, {'name': 'Wednesday Evening', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
|
||||
(0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Thursday Lunch', 'dayofweek': '3', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
|
||||
(0, 0, {'name': 'Thursday Evening', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
|
||||
(0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}),
|
||||
(0, 0, {'name': 'Friday Lunch', 'dayofweek': '4', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'}),
|
||||
(0, 0, {'name': 'Friday Evening', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'})
|
||||
],
|
||||
})
|
||||
|
||||
cls.contract_cdd = cls.employee.version_id
|
||||
cls.contract_cdd.write({
|
||||
'date_version': Date.to_date('2021-09-01'),
|
||||
'contract_date_start': Date.to_date('2021-09-01'),
|
||||
'contract_date_end': Date.to_date('2021-10-31'),
|
||||
'name': 'First CDD Contract for Richard',
|
||||
'resource_calendar_id': cls.calendar_35h.id,
|
||||
'wage': 5000.0,
|
||||
'employee_id': cls.employee.id,
|
||||
})
|
||||
cls.contract_cdi_values = {
|
||||
'date_version': Date.to_date('2021-11-01'),
|
||||
'contract_date_start': Date.to_date('2021-11-01'),
|
||||
'contract_date_end': False,
|
||||
'name': 'CDI Contract for Richard',
|
||||
'resource_calendar_id': cls.calendar_richard.id,
|
||||
'wage': 5000.0,
|
||||
'employee_id': cls.employee.id,
|
||||
}
|
||||
|
||||
def test_calendars_validity_within_period_default(self):
|
||||
calendars = self.employee_niv.resource_id._get_calendars_validity_within_period(
|
||||
utc.localize(datetime(2021, 7, 1, 8, 0, 0)),
|
||||
|
|
@ -55,3 +102,114 @@ class TestResource(TestHrCommon):
|
|||
niv_entry = calendars[self.employee_niv.resource_id.id]
|
||||
self.assertFalse(niv_entry[self.calendar_40h] - interval, "Interval should cover all calendar's validity")
|
||||
self.assertFalse(interval - niv_entry[self.calendar_40h], "Calendar validity should cover all interval")
|
||||
|
||||
def test_availability_hr_infos_resource(self):
|
||||
""" Ensure that all the hr infos needed to display the avatar popover card
|
||||
are available on the model resource.resource, even if the employee is archived
|
||||
"""
|
||||
user = self.env['res.users'].create([{
|
||||
'name': 'Test user',
|
||||
'login': 'test',
|
||||
'email': 'test@odoo.perso',
|
||||
'phone': '+32488990011',
|
||||
}])
|
||||
department = self.env['hr.department'].create([{
|
||||
'name': 'QA',
|
||||
}])
|
||||
resource = self.env['resource.resource'].create([{
|
||||
'name': 'Test resource',
|
||||
'user_id': user.id,
|
||||
}])
|
||||
employee = self.env['hr.employee'].create([{
|
||||
'name': 'Test employee',
|
||||
'active': False,
|
||||
'user_id': user.id,
|
||||
'job_title': 'Tester',
|
||||
'department_id': department.id,
|
||||
'work_email': 'test@odoo.pro',
|
||||
'work_phone': '+32800100100',
|
||||
'resource_id': resource.id,
|
||||
}])
|
||||
for field in 'email', 'phone', 'im_status':
|
||||
self.assertEqual(resource[field], user[field])
|
||||
for field in 'job_title', 'department_id', 'work_email', 'work_phone', 'show_hr_icon_display', 'hr_icon_display':
|
||||
self.assertEqual(resource[field], employee[field])
|
||||
|
||||
def test_calendars_validity_within_period(self):
|
||||
self.employee.create_version(self.contract_cdi_values)
|
||||
tz = timezone(self.employee.tz)
|
||||
calendars = self.employee.resource_id._get_calendars_validity_within_period(
|
||||
tz.localize(datetime(2021, 10, 1, 0, 0, 0)),
|
||||
tz.localize(datetime(2021, 12, 1, 0, 0, 0)),
|
||||
)
|
||||
interval_35h = Intervals([(
|
||||
tz.localize(datetime(2021, 10, 1, 0, 0, 0)),
|
||||
tz.localize(datetime.combine(date(2021, 10, 31), datetime.max.time())),
|
||||
self.env['resource.calendar.attendance']
|
||||
)])
|
||||
interval_40h = Intervals([(
|
||||
tz.localize(datetime(2021, 11, 1, 0, 0, 0)),
|
||||
tz.localize(datetime(2021, 12, 1, 0, 0, 0)),
|
||||
self.env['resource.calendar.attendance']
|
||||
)])
|
||||
|
||||
self.assertEqual(1, len(calendars), "The dict returned by calendars validity should only have 1 entry")
|
||||
self.assertEqual(2, len(calendars[self.employee.resource_id.id]), "Jean should only have one calendar")
|
||||
richard_entries = calendars[self.employee.resource_id.id]
|
||||
for calendar in richard_entries:
|
||||
self.assertTrue(calendar in (self.calendar_35h | self.calendar_richard), "Each calendar should be listed")
|
||||
if calendar == self.calendar_35h:
|
||||
self.assertFalse(richard_entries[calendar] - interval_35h, "Interval 35h should cover all calendar 35h validity")
|
||||
self.assertFalse(interval_35h - richard_entries[calendar], "Calendar 35h validity should cover all interval 35h")
|
||||
elif calendar == self.calendar_richard:
|
||||
self.assertFalse(richard_entries[calendar] - interval_40h, "Interval 40h should cover all calendar 40h validity")
|
||||
self.assertFalse(interval_40h - richard_entries[calendar], "Calendar 40h validity should cover all interval 40h")
|
||||
|
||||
def test_queries(self):
|
||||
employees_test = self.env['hr.employee'].create([{
|
||||
'name': 'Employee ' + str(i),
|
||||
} for i in range(0, 50)])
|
||||
for emp in employees_test:
|
||||
self.contract_cdd.copy({'employee_id': emp.id})
|
||||
self.contract_cdi_values['employee_id'] = emp.id
|
||||
self.employee.create_version(self.contract_cdi_values)
|
||||
|
||||
start = utc.localize(datetime(2021, 9, 1, 0, 0, 0))
|
||||
end = utc.localize(datetime(2021, 11, 30, 23, 59, 59))
|
||||
with self.assertQueryCount(165):
|
||||
work_intervals, _ = (employees_test | self.employee).resource_id._get_valid_work_intervals(start, end)
|
||||
|
||||
self.assertEqual(len(work_intervals), 51)
|
||||
|
||||
def test_get_valid_work_intervals(self):
|
||||
self.employee.create_version(self.contract_cdi_values)
|
||||
start = timezone(self.employee.tz).localize(datetime(2021, 10, 24, 2, 0, 0))
|
||||
end = timezone(self.employee.tz).localize(datetime(2021, 11, 6, 23, 59, 59))
|
||||
work_intervals, _ = self.employee.resource_id._get_valid_work_intervals(start, end)
|
||||
sum_work_intervals = sum_intervals(work_intervals[self.employee.resource_id.id])
|
||||
self.assertEqual(75, sum_work_intervals, "Sum of the work intervals for the employee should be 35h+40h = 75h")
|
||||
|
||||
def test_multi_contract_attendance(self):
|
||||
""" Verify whether retrieving an employee's calendar attendances can
|
||||
handle multiple contracts with different calendars.
|
||||
"""
|
||||
|
||||
date_from = utc.localize(datetime(2021, 10, 1, 0, 0, 0))
|
||||
date_to = utc.localize(datetime(2021, 11, 30, 0, 0, 0))
|
||||
|
||||
attendances = self.employee._get_calendar_attendances(date_from, date_to)
|
||||
self.assertEqual(21 * 7, attendances['hours'],
|
||||
"Attendances should only include running or finished contracts.")
|
||||
|
||||
self.employee.create_version(self.contract_cdi_values)
|
||||
|
||||
attendances = self.employee._get_calendar_attendances(date_from, date_to)
|
||||
self.assertEqual(21 * 7 + 21 * 8, attendances['hours'],
|
||||
"Attendances should add up multiple contracts with varying work weeks.")
|
||||
|
||||
def test_alter_resource_calendar_of_resouce(self):
|
||||
self.assertEqual(self.employee.resource_calendar_id, self.employee.resource_id.calendar_id)
|
||||
self.assertEqual(self.employee.version_id.resource_calendar_id, self.employee.resource_id.calendar_id)
|
||||
self.employee.resource_id.write({'calendar_id': self.calendar_40h})
|
||||
self.assertEqual(self.employee.resource_calendar_id, self.employee.resource_id.calendar_id)
|
||||
self.assertEqual(self.employee.version_id.resource_calendar_id, self.employee.resource_id.calendar_id)
|
||||
|
|
|
|||
9
odoo-bringout-oca-ocb-hr/hr/tests/test_scenario.py
Normal file
9
odoo-bringout-oca-ocb-hr/hr/tests/test_scenario.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestHrScenario(TransactionCase):
|
||||
|
||||
def test_load_scenario(self):
|
||||
self.env['hr.employee']._load_scenario()
|
||||
|
|
@ -5,55 +5,32 @@ from collections import OrderedDict
|
|||
from itertools import chain
|
||||
from lxml import etree
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
from odoo.tests import new_test_user, tagged, Form
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSelfAccessProfile(TestHrCommon):
|
||||
class TestSelfAccessPreferences(TestHrCommon):
|
||||
|
||||
def test_access_my_profile(self):
|
||||
""" A simple user should be able to read all fields in his profile """
|
||||
def test_access_preferences_view(self):
|
||||
""" A simple user should be able to read all fields in the preferences form """
|
||||
james = new_test_user(self.env, login='hel', groups='base.group_user', name='Simple employee', email='ric@example.com')
|
||||
james = james.with_user(james)
|
||||
james_bank_account = self.env['res.partner.bank'].create({'acc_number': 'BE1234567890', 'partner_id': james.partner_id.id})
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'James',
|
||||
'user_id': james.id,
|
||||
'bank_account_id': self.env['res.partner.bank'].create({'acc_number': 'BE1234567890', 'partner_id': james.partner_id.id}).id
|
||||
'bank_account_ids': [Command.link(james_bank_account.id)]
|
||||
})
|
||||
view = self.env.ref('hr.res_users_view_form_profile')
|
||||
view = self.env.ref('hr.res_users_view_form_preferences')
|
||||
view_infos = james.get_view(view.id)
|
||||
fields = [el.get('name') for el in etree.fromstring(view_infos['arch']).xpath('//field[not(ancestor::field)]')]
|
||||
james.read(fields)
|
||||
|
||||
def test_readonly_fields(self):
|
||||
""" Employee related fields should be readonly if self editing is not allowed """
|
||||
self.env['ir.config_parameter'].sudo().set_param('hr.hr_employee_self_edit', False)
|
||||
james = new_test_user(self.env, login='hel', groups='base.group_user', name='Simple employee', email='ric@example.com')
|
||||
james = james.with_user(james)
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'James',
|
||||
'user_id': james.id,
|
||||
})
|
||||
|
||||
view = self.env.ref('hr.res_users_view_form_profile')
|
||||
fields = james._fields
|
||||
view_infos = james.get_view(view.id)
|
||||
employee_related_fields = {
|
||||
el.get('name')
|
||||
for el in etree.fromstring(view_infos['arch']).xpath('//field[not(ancestor::field)]')
|
||||
if fields[el.get('name')].related and fields[el.get('name')].related.split('.')[0] == 'employee_id'
|
||||
}
|
||||
|
||||
form = Form(james, view=view)
|
||||
for field in employee_related_fields:
|
||||
with self.assertRaises(AssertionError, msg="Field '%s' should be readonly in the employee profile when self editing is not allowed." % field):
|
||||
form.__setattr__(field, 'some value')
|
||||
|
||||
|
||||
def test_profile_view_fields(self):
|
||||
""" A simple user should see all fields in profile view, even if they are protected by groups """
|
||||
view = self.env.ref('hr.res_users_view_form_profile')
|
||||
def test_preferences_view_fields(self):
|
||||
""" A simple user should see all fields in preferences view, even if they are protected by groups """
|
||||
view = self.env.ref('hr.res_users_view_form_preferences')
|
||||
|
||||
# For reference, check the view with user with every groups protecting user fields
|
||||
all_groups_xml_ids = chain(*[
|
||||
|
|
@ -66,7 +43,7 @@ class TestSelfAccessProfile(TestHrCommon):
|
|||
for xml_id in all_groups_xml_ids:
|
||||
all_groups |= self.env.ref(xml_id.strip())
|
||||
user_all_groups = new_test_user(self.env, groups='base.group_user', login='hel', name='God')
|
||||
user_all_groups.write({'groups_id': [(4, group.id, False) for group in all_groups]})
|
||||
user_all_groups.write({'group_ids': [(4, group.id, False) for group in all_groups]})
|
||||
view_infos = self.env['res.users'].with_user(user_all_groups).get_view(view.id)
|
||||
full_fields = [el.get('name') for el in etree.fromstring(view_infos['arch']).xpath('//field[not(ancestor::field)]')]
|
||||
|
||||
|
|
@ -78,7 +55,7 @@ class TestSelfAccessProfile(TestHrCommon):
|
|||
# Compare both
|
||||
self.assertEqual(full_fields, fields, "View fields should not depend on user's groups")
|
||||
|
||||
def test_access_my_profile_toolbar(self):
|
||||
def test_access_preferences_view_toolbar(self):
|
||||
""" A simple user shouldn't have the possibilities to see the 'Change Password' action"""
|
||||
james = new_test_user(self.env, login='jam', groups='base.group_user', name='Simple employee', email='jam@example.com')
|
||||
james = james.with_user(james)
|
||||
|
|
@ -86,9 +63,8 @@ class TestSelfAccessProfile(TestHrCommon):
|
|||
'name': 'James',
|
||||
'user_id': james.id,
|
||||
})
|
||||
view = self.env.ref('hr.res_users_view_form_profile')
|
||||
toolbar = james.get_views([(view.id, 'form')], {'toolbar': True})['views']['form']['toolbar']
|
||||
available_actions = toolbar.get('action', [])
|
||||
view = self.env.ref('hr.res_users_view_form_preferences')
|
||||
available_actions = james.get_views([(view.id, 'form')], {'toolbar': True})['views']['form']['toolbar'].get('action', {})
|
||||
change_password_action = self.env.ref("base.change_password_wizard_action")
|
||||
|
||||
self.assertFalse(any(x['id'] == change_password_action.id for x in available_actions))
|
||||
|
|
@ -100,10 +76,30 @@ class TestSelfAccessProfile(TestHrCommon):
|
|||
'name': 'John',
|
||||
'user_id': john.id,
|
||||
})
|
||||
view = self.env.ref('hr.res_users_view_form_profile')
|
||||
view = self.env.ref('hr.res_users_view_form_preferences')
|
||||
available_actions = john.get_views([(view.id, 'form')], {'toolbar': True})['views']['form']['toolbar']['action']
|
||||
self.assertTrue(any(x['id'] == change_password_action.id for x in available_actions))
|
||||
|
||||
def test_employee_fields_groups(self):
|
||||
# Note: If this tests is crashing, this is probably because the linked field on the error
|
||||
# message is defined on hr.employee only (and not on hr.employee.public) and has no group
|
||||
# defined on it (at least hr.group_hr_user).
|
||||
internal_user = new_test_user(self.env, login='mireille', groups='base.group_user', name='Mireille', email='mireille@example.com')
|
||||
self.env['hr.employee'].with_user(internal_user).search([]).read([])
|
||||
|
||||
def test_open_preferences_with_group_without_external_id(self):
|
||||
"""Test opening preferences when the user belongs to a group without an external ID."""
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'John',
|
||||
'user_id': self.env.user.id,
|
||||
})
|
||||
group = self.env['res.groups'].create({
|
||||
'name': "Test Group",
|
||||
})
|
||||
self.env.user.group_ids = [Command.link(group.id)]
|
||||
action = self.env.user.action_get()
|
||||
self.assertEqual(action['type'], 'ir.actions.act_window')
|
||||
self.assertEqual(action['display_name'], 'Change my Preferences')
|
||||
|
||||
class TestSelfAccessRights(TestHrCommon):
|
||||
|
||||
|
|
@ -114,13 +110,12 @@ class TestSelfAccessRights(TestHrCommon):
|
|||
cls.richard_emp = cls.env['hr.employee'].create({
|
||||
'name': 'Richard',
|
||||
'user_id': cls.richard.id,
|
||||
'address_home_id': cls.env['res.partner'].create({'name': 'Richard', 'phone': '21454', 'type': 'private'}).id,
|
||||
'private_phone': '21454',
|
||||
})
|
||||
cls.hubert = new_test_user(cls.env, login='hub', groups='base.group_user', name='Simple employee', email='hub@example.com')
|
||||
cls.hubert_emp = cls.env['hr.employee'].create({
|
||||
'name': 'Hubert',
|
||||
'user_id': cls.hubert.id,
|
||||
'address_home_id': cls.env['res.partner'].create({'name': 'Hubert', 'type': 'private'}).id,
|
||||
})
|
||||
|
||||
cls.protected_fields_emp = OrderedDict([(k, v) for k, v in cls.env['hr.employee']._fields.items() if v.groups == 'hr.group_hr_user'])
|
||||
|
|
@ -140,6 +135,14 @@ class TestSelfAccessRights(TestHrCommon):
|
|||
def testReadOtherEmployee(self):
|
||||
with self.assertRaises(AccessError):
|
||||
self.hubert_emp.with_user(self.richard).read(self.protected_fields_emp.keys())
|
||||
# Check simple user can read all public fields of private employee
|
||||
public_fields = [
|
||||
field_name
|
||||
for field_name in self.env['hr.employee.public']._fields
|
||||
if field_name in self.env['hr.employee']._fields
|
||||
]
|
||||
res = self.hubert_emp.with_user(self.richard).read(public_fields)
|
||||
self.assertEqual(len(public_fields), len(res[0]))
|
||||
|
||||
# Write hr.employee #
|
||||
def testWriteSelfEmployee(self):
|
||||
|
|
@ -161,56 +164,14 @@ class TestSelfAccessRights(TestHrCommon):
|
|||
with self.assertRaises(AccessError):
|
||||
self.hubert.with_user(self.richard).read(self.self_protected_fields_user)
|
||||
|
||||
# Write res.users #
|
||||
def testWriteSelfUserEmployeeSettingFalse(self):
|
||||
for f, v in self.self_protected_fields_user.items():
|
||||
with self.assertRaises(AccessError):
|
||||
self.richard.with_user(self.richard).write({f: 'dummy'})
|
||||
|
||||
def testWriteSelfUserEmployee(self):
|
||||
self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', True)
|
||||
for f, v in self.self_protected_fields_user.items():
|
||||
val = None
|
||||
if v.type == 'char' or v.type == 'text':
|
||||
val = '0000' if f == 'pin' else 'dummy'
|
||||
val = '0000' if f in ['pin', 'barcode'] else 'dummy'
|
||||
if val is not None:
|
||||
self.richard.with_user(self.richard).write({f: val})
|
||||
|
||||
def testWriteSelfUserPreferencesEmployee(self):
|
||||
# self should always be able to update non hr.employee fields if
|
||||
# they are in SELF_READABLE_FIELDS
|
||||
self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', False)
|
||||
# should not raise
|
||||
vals = [
|
||||
{'tz': "Australia/Sydney"},
|
||||
{'email': "new@example.com"},
|
||||
{'signature': "<p>I'm Richard!</p>"},
|
||||
{'notification_type': "email"},
|
||||
]
|
||||
for v in vals:
|
||||
# should not raise
|
||||
self.richard.with_user(self.richard).write(v)
|
||||
|
||||
def testWriteOtherUserPreferencesEmployee(self):
|
||||
# self should always be able to update non hr.employee fields if
|
||||
# they are in SELF_READABLE_FIELDS
|
||||
self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', False)
|
||||
vals = [
|
||||
{'tz': "Australia/Sydney"},
|
||||
{'email': "new@example.com"},
|
||||
{'signature': "<p>I'm Richard!</p>"},
|
||||
{'notification_type': "email"},
|
||||
]
|
||||
for v in vals:
|
||||
with self.assertRaises(AccessError):
|
||||
self.hubert.with_user(self.richard).write(v)
|
||||
|
||||
def testWriteSelfPhoneEmployee(self):
|
||||
# phone is a related from res.partner (from base) but added in SELF_READABLE_FIELDS
|
||||
self.env['ir.config_parameter'].set_param('hr.hr_employee_self_edit', False)
|
||||
with self.assertRaises(AccessError):
|
||||
self.richard.with_user(self.richard).write({'phone': '2154545'})
|
||||
|
||||
def testWriteOtherUserEmployee(self):
|
||||
for f in self.self_protected_fields_user:
|
||||
with self.assertRaises(AccessError):
|
||||
|
|
@ -220,6 +181,28 @@ class TestSelfAccessRights(TestHrCommon):
|
|||
# Searching user based on employee_id field should not raise bad query error
|
||||
self.env['res.users'].with_user(self.richard).search([('employee_id', 'ilike', 'Hubert')])
|
||||
|
||||
# Write hr.department
|
||||
def testWriteDepartmentEmployee(self):
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['hr.department'].with_user(self.richard).create({'name': 'New Dept'})
|
||||
dept = self.env['hr.department'].create({'name': 'New Dept'})
|
||||
with self.assertRaises(AccessError):
|
||||
dept.with_user(self.richard).write({'name': 'Renamed Dept'})
|
||||
|
||||
def test_onchange_readable_fields_with_no_access(self):
|
||||
"""
|
||||
The purpose is to test that the onchange logic takes into account `SELF_READABLE_FIELDS`.
|
||||
|
||||
The view contains fields that are in `SELF_READABLE_FIELDS` (example: `private_street`).
|
||||
Even if the user does not have read access to the employee,
|
||||
it should not cause an access error if these fields are in `SELF_READABLE_FIELDS`.
|
||||
"""
|
||||
self.env['res.lang']._activate_lang("fr_FR")
|
||||
with Form(self.richard.with_user(self.richard), view='hr.res_users_view_form_preferences') as form:
|
||||
# triggering an onchange should not trigger some access error
|
||||
form.lang = "fr_FR"
|
||||
form.tz = "Europe/Brussels"
|
||||
|
||||
def test_access_employee_account(self):
|
||||
hubert = new_test_user(self.env, login='hubert', groups='base.group_user', name='Hubert Bonisseur de La Bath', email='hubert@oss.fr')
|
||||
hubert = hubert.with_user(hubert)
|
||||
|
|
@ -227,16 +210,14 @@ class TestSelfAccessRights(TestHrCommon):
|
|||
hubert_emp = self.env['hr.employee'].create({
|
||||
'name': 'Hubert',
|
||||
'user_id': hubert.id,
|
||||
'bank_account_id': hubert_acc.id
|
||||
'bank_account_ids': [Command.link(hubert_acc.id)]
|
||||
})
|
||||
hubert.partner_id.sudo().employee_ids = hubert_emp
|
||||
|
||||
self.assertFalse(hubert.user_has_groups('hr.group_hr_user'))
|
||||
self.assertFalse(hubert.env.user.has_group('hr.group_hr_user'))
|
||||
self.assertFalse(hubert.env.su)
|
||||
|
||||
self.assertEqual(hubert.read(['employee_bank_account_id'])[0]['employee_bank_account_id'][1], 'FR******7890')
|
||||
self.assertEqual(hubert.sudo().employee_bank_account_id.display_name, 'FR******7890')
|
||||
self.assertEqual(hubert_emp.with_user(hubert).sudo().bank_account_id.display_name, 'FR******7890')
|
||||
self.assertEqual(hubert.sudo().employee_bank_account_ids.display_name, 'FR******7890')
|
||||
self.assertEqual(hubert_emp.with_user(hubert).sudo().bank_account_ids.display_name, 'FR******7890')
|
||||
|
||||
hubert_acc.invalidate_recordset(["display_name"])
|
||||
self.assertEqual(hubert_emp.with_user(hubert).sudo().bank_account_id.sudo(False).display_name, 'FR******7890')
|
||||
self.assertEqual(hubert_emp.with_user(hubert).sudo().bank_account_ids.sudo(False).display_name, 'FR******7890')
|
||||
|
|
|
|||
|
|
@ -1,17 +1,47 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import HttpCase, tagged, new_test_user
|
||||
from odoo.tests import HttpCase, freeze_time, tagged, new_test_user
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
|
||||
@tagged('-at_install', 'post_install', 'is_tour')
|
||||
class TestEmployeeUi(HttpCase):
|
||||
def test_employee_profile_tour(self):
|
||||
user = new_test_user(self.env, login='davidelora', groups='base.group_user')
|
||||
johnny_user = new_test_user(self.env, login="johnny", name="Johnny H.")
|
||||
|
||||
self.env['hr.employee'].create([{
|
||||
'name': 'Johnny H.',
|
||||
"user_id": johnny_user.id,
|
||||
}, {
|
||||
'name': 'David Elora',
|
||||
'user_id': user.id,
|
||||
}])
|
||||
|
||||
self.start_tour("/web", 'hr_employee_tour', login="davidelora")
|
||||
self.start_tour("/odoo", 'hr_employee_tour', login="davidelora")
|
||||
|
||||
@freeze_time('2024-01-01')
|
||||
def test_version_timeline_auto_save_tour(self):
|
||||
# as payroll tap access will be overridden by hr_payroll
|
||||
is_payroll_installed = self.env['ir.module.module'].search_count([
|
||||
('name', '=', 'hr_payroll'), ('state', '=', 'installed')])
|
||||
group = 'hr_payroll.group_hr_payroll_manager' if is_payroll_installed else 'hr.group_hr_manager'
|
||||
user = new_test_user(self.env, login='alice', groups=group)
|
||||
bob_user = new_test_user(self.env, login="Bob", name="Bob M.")
|
||||
|
||||
self.env['hr.employee'].create([{
|
||||
'name': 'Alice',
|
||||
'user_id': user.id,
|
||||
}])
|
||||
|
||||
bob_employee = self.env['hr.employee'].create([{
|
||||
'name': 'Bob M.',
|
||||
"user_id": bob_user.id,
|
||||
}])
|
||||
|
||||
bob_employee.write({
|
||||
'contract_date_start': '2024-01-01',
|
||||
'contract_date_end': False,
|
||||
})
|
||||
|
||||
self.start_tour("/odoo", 'version_timeline_auto_save_tour', login="alice")
|
||||
self.assertFalse(bob_employee.version_ids[-1].contract_date_start)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue