252 lines
13 KiB
Python
252 lines
13 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details
|
|
from datetime import datetime
|
|
from pytz import UTC, timezone
|
|
|
|
from .common import TestPlanningContractCommon
|
|
from odoo.tests import new_test_user
|
|
from odoo.tools.intervals import Intervals
|
|
|
|
|
|
class TestPlanningContract(TestPlanningContractCommon):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
# This contract ends at the 15th of the month
|
|
cls.employee_bert.version_id.write({ # Fixed term contract
|
|
'contract_date_end': datetime.strptime('2015-11-15', '%Y-%m-%d'),
|
|
'contract_date_start': datetime.strptime('2015-01-01', '%Y-%m-%d'),
|
|
'date_version': datetime.strptime('2015-01-01', '%Y-%m-%d'),
|
|
'name': 'First CDD Contract for Jules',
|
|
'resource_calendar_id': cls.calendar_40h.id,
|
|
'wage': 5000.0,
|
|
})
|
|
cls.contract_cdd = cls.employee_bert.version_id
|
|
|
|
# This contract starts the next day
|
|
cls.contract_cdi = cls.employee_bert.create_version({
|
|
'contract_date_start': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'contract_date_end': False,
|
|
'date_version': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'name': 'Contract for Jules',
|
|
'resource_calendar_id': cls.calendar_35h.id,
|
|
'wage': 5000.0,
|
|
})
|
|
|
|
def test_employee_contract_validity_per_period(self):
|
|
start = datetime(2015, 11, 8, 00, 00, 00, tzinfo=UTC)
|
|
end = datetime(2015, 11, 21, 23, 59, 59, tzinfo=UTC)
|
|
calendars_validity_within_period = self.resource_bert._get_calendars_validity_within_period(start, end, default_company=self.employee_bert.company_id)
|
|
tz = timezone(self.employee_bert.tz)
|
|
|
|
self.assertEqual(len(calendars_validity_within_period[self.resource_bert.id]), 2, "There should exist 2 calendars within the period")
|
|
interval_calendar_40h = Intervals([(
|
|
start,
|
|
tz.localize(datetime.combine(self.contract_cdd.date_end, datetime.max.time())),
|
|
self.env['resource.calendar.attendance']
|
|
)])
|
|
interval_calendar_35h = Intervals([(
|
|
tz.localize(datetime.combine(self.contract_cdi.date_start, datetime.min.time())),
|
|
end,
|
|
self.env['resource.calendar.attendance']
|
|
)])
|
|
computed_interval_40h = calendars_validity_within_period[self.resource_bert.id][self.calendar_40h]
|
|
computed_interval_35h = calendars_validity_within_period[self.resource_bert.id][self.calendar_35h]
|
|
self.assertFalse(computed_interval_40h - interval_calendar_40h, "The interval of validity for the 40h calendar must be from 2015-11-16 to 2015-11-21, not more")
|
|
self.assertFalse(interval_calendar_40h - computed_interval_40h, "The interval of validity for the 40h calendar must be from 2015-11-16 to 2015-11-21, not less")
|
|
self.assertFalse(computed_interval_35h - interval_calendar_35h, "The interval of validity for the 35h calendar must be from 2015-11-08 to 2015-11-15, not more")
|
|
self.assertFalse(interval_calendar_35h - computed_interval_35h, "The interval of validity for the 35h calendar must be from 2015-11-08 to 2015-11-15, not less")
|
|
|
|
def test_employee_work_intervals(self):
|
|
start = datetime(2015, 11, 8, 00, 00, 00, tzinfo=UTC)
|
|
end = datetime(2015, 11, 21, 23, 59, 59, tzinfo=UTC)
|
|
work_intervals, _ = self.resource_bert._get_valid_work_intervals(start, end)
|
|
sum_work_intervals = sum(
|
|
(stop - start).total_seconds() / 3600
|
|
for start, stop, _resource in work_intervals[self.resource_bert.id]
|
|
)
|
|
self.assertEqual(75, sum_work_intervals, "Sum of the work intervals for the employee Jules should be 40h+35h = 75h")
|
|
|
|
def test_employee_work_planning_hours_info(self):
|
|
planning_hours_info = self.env['planning.slot']._gantt_progress_bar(
|
|
'resource_id', self.resource_bert.ids, datetime(2015, 11, 8), datetime(2015, 11, 21, 23, 59, 59)
|
|
)
|
|
self.assertEqual(75, planning_hours_info[self.resource_bert.id]['max_value'], "Work hours for the employee Jules should be 40h+35h = 75h")
|
|
|
|
def test_gantt_progress_bar_work_intervals_timezone(self):
|
|
self.calendar_40h.tz = 'Europe/Brussels'
|
|
self.resource_bert.calendar_id = self.calendar_40h
|
|
|
|
self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2015, 12, 16, 8, 0, 0),
|
|
'end_datetime': datetime(2015, 12, 16, 17, 0, 0),
|
|
'resource_id': self.resource_bert.id,
|
|
})
|
|
domain = [['start_datetime', '<', datetime(2015, 12, 18, 0, 0, 0)], ['end_datetime', '>', datetime(2015, 12, 15, 0, 0, 0)]]
|
|
planning_hours_info = self.env['planning.slot']._get_gantt_planning_data(domain, datetime(2015, 12, 16), datetime(2015, 12, 17))
|
|
|
|
res_intervals = [
|
|
(UTC.localize(datetime(2015, 12, 16, 7, 0, 0)), UTC.localize(datetime(2015, 12, 16, 11, 0, 0))),
|
|
(UTC.localize(datetime(2015, 12, 16, 12, 0, 0)), UTC.localize(datetime(2015, 12, 16, 16, 0, 0)))
|
|
]
|
|
self.assertEqual(planning_hours_info['work_intervals'][self.resource_bert.id], res_intervals, "work_intervals should be in UTC")
|
|
|
|
def test_creation_recurrencing_planning_without_employee_contract_access(self):
|
|
"""
|
|
The creation of recurring schedules requires information on contracts.
|
|
The goal is to test whether a user who is "Administrator" for Planning
|
|
but has no Employee and Contract rights can use this flow.
|
|
"""
|
|
planning_value = {
|
|
'name': 'Test planning',
|
|
'start_datetime': datetime(2015, 11, 8, 00, 00, 00),
|
|
'end_datetime': datetime(2015, 11, 21, 23, 59, 59),
|
|
'resource_id': self.resource_bert.id,
|
|
'repeat': True,
|
|
'repeat_type': 'x_times',
|
|
'repeat_number': 2,
|
|
'repeat_interval': 1,
|
|
'repeat_unit': 'month',
|
|
}
|
|
user_admin = new_test_user(
|
|
self.env, "User Admin",
|
|
groups='planning.group_planning_manager,hr.group_hr_manager,hr.group_hr_manager'
|
|
)
|
|
user_without_contract = new_test_user(
|
|
self.env, "User Without Contract",
|
|
groups='planning.group_planning_manager,hr.group_hr_manager'
|
|
)
|
|
user_without_employee = new_test_user(
|
|
self.env, "User Without Employee",
|
|
groups='planning.group_planning_manager'
|
|
)
|
|
self.env['planning.slot'].with_user(user_admin).create(planning_value)
|
|
self.env.invalidate_all()
|
|
self.env['planning.slot'].with_user(user_without_contract).create(planning_value)
|
|
self.env.invalidate_all()
|
|
self.env['planning.slot'].with_user(user_without_employee).create(planning_value)
|
|
|
|
def test_allocated_hours_of_slot_with_flexible_contract(self):
|
|
"""
|
|
When a flexible or fully flexible employee plan a slot outside the period of
|
|
the contract, the pre-computed allocated hours should be 0 hours.
|
|
Additionally, apply the test for a flexible contract with no end date.
|
|
"""
|
|
|
|
# 1) Set a fully flexible contract for an employee
|
|
self.employee_joseph.version_id.write({
|
|
'contract_date_end': datetime.strptime('2015-12-16', '%Y-%m-%d'),
|
|
'contract_date_start': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'date_version': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'name': 'Fully flex contract for Joseph',
|
|
'resource_calendar_id': False,
|
|
'wage': 5000.0,
|
|
})
|
|
|
|
# Create a slot outside the contract period
|
|
slot_outside = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2015, 12, 17, 8, 0, 0),
|
|
'end_datetime': datetime(2015, 12, 17, 12, 0, 0),
|
|
'resource_id': self.resource_joseph.id,
|
|
})
|
|
self.assertEqual(slot_outside.allocated_hours, 0, "The allocated hours should be 0 for a slot outside the contract period of a fully flexible employee")
|
|
|
|
# Create a slot inside the contract period
|
|
slot_inside = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2015, 12, 16, 1, 0, 0),
|
|
'end_datetime': datetime(2015, 12, 16, 12, 0, 0),
|
|
'resource_id': self.resource_joseph.id,
|
|
})
|
|
self.assertEqual(slot_inside.allocated_hours, 11, "The allocated hours should equal the whole length of the slot inside the contract period of a fully flexible employee")
|
|
|
|
# 2) Set a flexible contract for another employee
|
|
self.employee_janice.version_id.write({
|
|
'date_version': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'contract_date_start': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'contract_date_end': datetime.strptime('2015-12-16', '%Y-%m-%d'),
|
|
'name': 'Flex contract for Janice',
|
|
'resource_calendar_id': self.calendar_30h_flex.id,
|
|
'wage': 5000.0,
|
|
})
|
|
|
|
# Create a slot outside the contract period
|
|
slot_outside = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2015, 12, 17, 8, 0, 0),
|
|
'end_datetime': datetime(2015, 12, 17, 12, 0, 0),
|
|
'resource_id': self.resource_janice.id,
|
|
})
|
|
self.assertEqual(slot_outside.allocated_hours, 0, "The allocated hours should be 0 for a slot outside the contract period of a flexible employee")
|
|
|
|
# Create a slot inside the contract period
|
|
slot_inside = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2015, 12, 16, 8, 0, 0),
|
|
'end_datetime': datetime(2015, 12, 16, 14, 0, 0),
|
|
'resource_id': self.resource_janice.id,
|
|
})
|
|
self.assertEqual(slot_inside.allocated_hours, 6, "The allocated hours should be 6 for a slot inside the contract period of a flexible employee")
|
|
|
|
# 3) Set a flexible contract with no end date for another employee
|
|
self.employee_janice.create_version({
|
|
'date_version': datetime.strptime('2016-01-16', '%Y-%m-%d'),
|
|
'contract_date_start': datetime.strptime('2016-01-16', '%Y-%m-%d'),
|
|
'contract_date_end': False,
|
|
'name': 'Flex contract for Janice with no end date',
|
|
'resource_calendar_id': self.calendar_30h_flex.id,
|
|
'wage': 5000.0,
|
|
})
|
|
|
|
slot_inside = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2016, 1, 16, 8, 0, 0),
|
|
'end_datetime': datetime(2016, 1, 16, 14, 0, 0),
|
|
'resource_id': self.resource_janice.id,
|
|
})
|
|
self.assertEqual(slot_inside.allocated_hours, 6, "The allocated hours should be 6 for a slot inside the contract period of a flexible employee with no end date")
|
|
|
|
def test_allocated_hours_of_slot_overlapping_with_contract_period(self):
|
|
"""
|
|
When a flexible or fully flexible employee plan a slot partially outside the period of
|
|
the contract, the pre-computed allocated hours should be the hours within the contract period.
|
|
"""
|
|
|
|
# 1) Set a fully flexible contract for an employee
|
|
self.employee_joseph.version_id.write({
|
|
'date_version': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'contract_date_start': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'contract_date_end': datetime.strptime('2015-12-16', '%Y-%m-%d'), # contract will end at 2015-12-16 23:59:59
|
|
'name': 'Fully flex contract for Joseph',
|
|
'resource_calendar_id': False,
|
|
'wage': 5000.0,
|
|
})
|
|
|
|
# Create a slot partially outside the contract period
|
|
slot_outside = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2015, 12, 16, 8, 0, 0),
|
|
'end_datetime': datetime(2015, 12, 17, 12, 0, 0),
|
|
'resource_id': self.resource_joseph.id,
|
|
})
|
|
self.assertEqual(slot_outside.allocated_hours, 16.0, "The allocated hours should be 16h (8AM to 12PM) of the 16th of December for the fully flexible employee")
|
|
|
|
# 2) Set a flexible contract for another employee
|
|
self.employee_janice.version_id.write({
|
|
'date_version': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'contract_date_start': datetime.strptime('2015-11-16', '%Y-%m-%d'),
|
|
'contract_date_end': datetime.strptime('2015-12-16', '%Y-%m-%d'),
|
|
'name': 'Flex contract for Janice',
|
|
'resource_calendar_id': self.calendar_30h_flex.id,
|
|
'wage': 5000.0,
|
|
})
|
|
|
|
# Create a slot partially outside the contract period
|
|
slot_outside = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2015, 12, 16, 8, 0, 0),
|
|
'end_datetime': datetime(2015, 12, 17, 8, 0, 0),
|
|
'resource_id': self.resource_janice.id,
|
|
})
|
|
self.assertEqual(slot_outside.allocated_hours, 6.0, "The allocated hours should be 6h (the max per day) for the flexible employee")
|
|
|
|
def test_allocated_hours_on_open_shift(self):
|
|
slot = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2015, 12, 16, 8, 0, 0),
|
|
'end_datetime': datetime(2015, 12, 17, 8, 0, 0),
|
|
})
|
|
self.assertEqual(slot.allocated_hours, 8, "The allocated hours should be computed without any issue")
|