forked from Mapan/odoo17e
633 lines
33 KiB
Python
633 lines
33 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details
|
|
from datetime import datetime, time, timedelta
|
|
from dateutil.relativedelta import relativedelta
|
|
from freezegun import freeze_time
|
|
from odoo.exceptions import UserError
|
|
|
|
from odoo import fields
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.tests.common import Form
|
|
from odoo.tests import new_test_user
|
|
|
|
from odoo.addons.mail.tests.common import MockEmail
|
|
from .common import TestCommonPlanning
|
|
|
|
class TestPlanning(TestCommonPlanning, MockEmail):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.classPatch(cls.env.cr, 'now', fields.datetime.now)
|
|
with freeze_time('2019-5-1'):
|
|
cls.setUpEmployees()
|
|
calendar_joseph = cls.env['resource.calendar'].create({
|
|
'name': 'Calendar 1',
|
|
'tz': 'UTC',
|
|
'hours_per_day': 8.0,
|
|
'attendance_ids': [
|
|
(0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 9, 'hour_to': 13, 'day_period': 'morning'}),
|
|
(0, 0, {'name': 'Thursday Lunch', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 14, 'day_period': 'lunch'}),
|
|
(0, 0, {'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 14, 'hour_to': 18, 'day_period': 'afternoon'}),
|
|
]
|
|
})
|
|
calendar_bert = cls.env['resource.calendar'].create({
|
|
'name': 'Calendar 2',
|
|
'tz': 'UTC',
|
|
'hours_per_day': 4,
|
|
'attendance_ids': [
|
|
(0, 0, {'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'morning'}),
|
|
],
|
|
})
|
|
calendar = cls.env['resource.calendar'].create({
|
|
'name': 'Classic 40h/week',
|
|
'tz': 'UTC',
|
|
'hours_per_day': 8.0,
|
|
'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 Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, '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 Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, '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 Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, '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 Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, '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 Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'})
|
|
]
|
|
})
|
|
cls.env.user.company_id.resource_calendar_id = calendar
|
|
cls.employee_joseph.resource_calendar_id = calendar_joseph
|
|
cls.employee_bert.resource_calendar_id = calendar_bert
|
|
cls.slot, cls.slot2 = cls.env['planning.slot'].create([
|
|
{
|
|
'start_datetime': datetime(2019, 6, 27, 8, 0, 0),
|
|
'end_datetime': datetime(2019, 6, 27, 18, 0, 0),
|
|
},
|
|
{
|
|
'start_datetime': datetime(2019, 6, 27, 8, 0, 0),
|
|
'end_datetime': datetime(2019, 6, 28, 18, 0, 0),
|
|
}
|
|
])
|
|
cls.template = cls.env['planning.slot.template'].create({
|
|
'start_time': 11,
|
|
'duration': 4,
|
|
})
|
|
|
|
def test_allocated_hours_defaults(self):
|
|
self.assertEqual(self.slot.allocated_hours, 8, "It should follow the calendar of the resource to compute the allocated hours.")
|
|
self.assertEqual(self.slot.allocated_percentage, 100, "It should have the default value")
|
|
|
|
def test_change_percentage(self):
|
|
self.slot.allocated_percentage = 60
|
|
self.assertEqual(self.slot.allocated_hours, 8 * 0.60, "It should 60%% of working hours")
|
|
self.slot2.allocated_percentage = 60
|
|
self.assertEqual(self.slot2.allocated_hours, 16 * 0.60)
|
|
|
|
def test_change_hours_more(self):
|
|
self.slot.allocated_hours = 12
|
|
self.assertEqual(self.slot.allocated_percentage, 150)
|
|
self.slot2.allocated_hours = 24
|
|
self.assertEqual(self.slot2.allocated_percentage, 150)
|
|
|
|
def test_change_hours_less(self):
|
|
self.slot.allocated_hours = 4
|
|
self.assertEqual(self.slot.allocated_percentage, 50)
|
|
self.slot2.allocated_hours = 8
|
|
self.assertEqual(self.slot2.allocated_percentage, 50)
|
|
|
|
def test_change_start(self):
|
|
self.slot.start_datetime += relativedelta(hours=2)
|
|
self.assertEqual(self.slot.allocated_percentage, 100, "It should still be 100%")
|
|
self.assertEqual(self.slot.allocated_hours, 8, "It should decreased by 2 hours")
|
|
|
|
def test_change_start_partial(self):
|
|
self.slot.allocated_percentage = 80
|
|
self.slot.start_datetime += relativedelta(hours=2)
|
|
self.slot.flush_recordset()
|
|
self.slot.invalidate_recordset()
|
|
self.assertEqual(self.slot.allocated_hours, 8 * 0.8, "It should be decreased by 2 hours and percentage applied")
|
|
self.assertEqual(self.slot.allocated_percentage, 80, "It should still be 80%")
|
|
|
|
def test_change_end(self):
|
|
self.slot.end_datetime -= relativedelta(hours=2)
|
|
self.assertEqual(self.slot.allocated_percentage, 100, "It should still be 100%")
|
|
self.assertEqual(self.slot.allocated_hours, 8, "It should decreased by 2 hours")
|
|
|
|
def test_set_template(self):
|
|
self.env.user.tz = 'Europe/Brussels'
|
|
self.slot.template_id = self.template
|
|
self.assertEqual(self.slot.start_datetime, datetime(2019, 6, 27, 9, 0), 'It should set time from template, in user timezone (11am CET -> 9am UTC)')
|
|
|
|
def test_change_employee_with_template(self):
|
|
self.slot.template_id = self.template
|
|
self.env.flush_all()
|
|
|
|
# simulate public user (no tz)
|
|
self.env.user.tz = False
|
|
self.slot.resource_id = self.employee_janice.resource_id
|
|
self.assertEqual(self.slot.template_id, self.template, 'It should keep the template')
|
|
self.assertEqual(self.slot.start_datetime, datetime(2019, 6, 27, 15, 0), 'It should adjust for employee timezone: 11am EDT -> 3pm UTC')
|
|
|
|
def test_change_employee(self):
|
|
""" Ensures that changing the employee does not have an impact to the shift. """
|
|
self.env.user.tz = 'UTC'
|
|
self.slot.resource_id = self.employee_joseph.resource_id
|
|
self.assertEqual(self.slot.start_datetime, datetime(2019, 6, 27, 8, 0), 'It should not adjust to employee calendar')
|
|
self.assertEqual(self.slot.end_datetime, datetime(2019, 6, 27, 18, 0), 'It should not adjust to employee calendar')
|
|
self.slot.resource_id = self.employee_bert.resource_id
|
|
self.assertEqual(self.slot.start_datetime, datetime(2019, 6, 27, 8, 0), 'It should not adjust to employee calendar')
|
|
self.assertEqual(self.slot.end_datetime, datetime(2019, 6, 27, 18, 0), 'It should not adjust to employee calendar')
|
|
|
|
def test_create_with_employee(self):
|
|
""" This test's objective is to mimic shift creation from the gant view and ensure that the correct behavior is met.
|
|
This test objective is to test the default values when creating a new shift for an employee when provided defaults are within employee's calendar workdays
|
|
"""
|
|
self.env.user.tz = 'UTC'
|
|
PlanningSlot = self.env['planning.slot'].with_context(
|
|
tz='UTC',
|
|
default_start_datetime='2019-06-27 00:00:00',
|
|
default_end_datetime='2019-06-27 23:59:59',
|
|
default_resource_id=self.resource_joseph.id)
|
|
defaults = PlanningSlot.default_get(['resource_id', 'start_datetime', 'end_datetime'])
|
|
self.assertEqual(defaults.get('start_datetime'), datetime(2019, 6, 27, 9, 0), 'It should be adjusted to employee calendar: 0am -> 9pm')
|
|
self.assertEqual(defaults.get('end_datetime'), datetime(2019, 6, 27, 18, 0), 'It should be adjusted to employee calendar: 0am -> 18pm')
|
|
|
|
def test_specific_time_creation(self):
|
|
self.env.user.tz = 'UTC'
|
|
PlanningSlot = self.env['planning.slot'].with_context(
|
|
tz='UTC',
|
|
default_start_datetime='2020-10-05 06:00:00',
|
|
default_end_datetime='2020-10-05 12:30:00',
|
|
planning_keep_default_datetime=True)
|
|
defaults = PlanningSlot.default_get(['start_datetime', 'end_datetime'])
|
|
self.assertEqual(defaults.get('start_datetime'), datetime(2020, 10, 5, 6, 0), 'start_datetime should not change')
|
|
self.assertEqual(defaults.get('end_datetime'), datetime(2020, 10, 5, 12, 30), 'end_datetime should not change')
|
|
|
|
def test_create_with_employee_outside_schedule(self):
|
|
""" This test objective is to test the default values when creating a new shift for an employee when provided defaults are not within employee's calendar workdays """
|
|
self.env.user.tz = 'UTC'
|
|
PlanningSlot = self.env['planning.slot'].with_context(
|
|
tz='UTC',
|
|
default_start_datetime='2019-06-26 00:00:00',
|
|
default_end_datetime='2019-06-26 23:59:59',
|
|
default_resource_id=self.resource_joseph.id)
|
|
defaults = PlanningSlot.default_get(['resource_id', 'start_datetime', 'end_datetime'])
|
|
self.assertEqual(defaults.get('start_datetime'), datetime(2019, 6, 26, 00, 0), 'It should still be the default start_datetime 0am')
|
|
self.assertEqual(defaults.get('end_datetime'), datetime(2019, 6, 26, 23, 59, 59), 'It should adjust to employee calendar: 0am -> 9pm')
|
|
|
|
def test_create_without_employee(self):
|
|
""" This test objective is to test the default values when creating a new shift when no employee is set """
|
|
self.env.user.tz = 'UTC'
|
|
PlanningSlot = self.env['planning.slot'].with_context(
|
|
tz='UTC',
|
|
default_start_datetime='2019-06-27 00:00:00',
|
|
default_end_datetime='2019-06-27 23:59:59',
|
|
default_resource_id=False)
|
|
defaults = PlanningSlot.default_get(['resource_id', 'start_datetime', 'end_datetime'])
|
|
self.assertEqual(defaults.get('start_datetime'), datetime(2019, 6, 27, 8, 0), 'It should adjust to employee calendar: 0am -> 9pm')
|
|
self.assertEqual(defaults.get('end_datetime'), datetime(2019, 6, 27, 17, 0), 'It should adjust to employee calendar: 0am -> 9pm')
|
|
|
|
def test_unassign_employee_with_template(self):
|
|
# we are going to put everybody in EDT, because if the employee has a different timezone from the company this workflow does not work.
|
|
self.env.user.tz = 'America/New_York'
|
|
self.env.user.company_id.resource_calendar_id.tz = 'America/New_York'
|
|
self.slot.template_id = self.template
|
|
self.env.flush_all()
|
|
self.assertEqual(self.slot.start_datetime, datetime(2019, 6, 27, 15, 0), 'It should set time from template, in user timezone (11am EDT -> 3pm UTC)')
|
|
|
|
# simulate public user (no tz)
|
|
self.env.user.tz = False
|
|
self.slot.resource_id = self.resource_janice.id
|
|
self.env.flush_all()
|
|
self.assertEqual(self.slot.start_datetime, datetime(2019, 6, 27, 15, 0), 'It should adjust to employee timezone')
|
|
|
|
self.slot.resource_id = None
|
|
self.assertEqual(self.slot.template_id, self.template, 'It should keep the template')
|
|
self.assertEqual(self.slot.start_datetime, datetime(2019, 6, 27, 15, 0), 'It should reset to company calendar timezone: 11am EDT -> 3pm UTC')
|
|
|
|
def test_compute_overlap_count(self):
|
|
self.slot_6_2 = self.env['planning.slot'].create({
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2019, 6, 2, 8, 0),
|
|
'end_datetime': datetime(2019, 6, 2, 17, 0),
|
|
})
|
|
self.slot_6_3 = self.env['planning.slot'].create({
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2019, 6, 3, 8, 0),
|
|
'end_datetime': datetime(2019, 6, 3, 17, 0),
|
|
})
|
|
self.env['planning.slot'].create({
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2019, 6, 2, 10, 0),
|
|
'end_datetime': datetime(2019, 6, 2, 12, 0),
|
|
})
|
|
self.env['planning.slot'].create({
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2019, 6, 2, 16, 0),
|
|
'end_datetime': datetime(2019, 6, 2, 18, 0),
|
|
})
|
|
self.env['planning.slot'].create({
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2019, 6, 2, 18, 0),
|
|
'end_datetime': datetime(2019, 6, 2, 20, 0),
|
|
})
|
|
self.assertEqual(2, self.slot_6_2.overlap_slot_count, '2 slots overlap')
|
|
self.assertEqual(0, self.slot_6_3.overlap_slot_count, 'no slot overlap')
|
|
|
|
def test_compute_datetime_with_template_slot(self):
|
|
""" Test if the start and end datetimes of a planning.slot are correctly computed with the template slot
|
|
|
|
Test Case:
|
|
=========
|
|
1) Create a planning.slot.template with start_hours = 11 pm and duration = 3 hours.
|
|
2) Create a planning.slot for one day and add the template.
|
|
3) Check if the start and end dates are on two days and not one.
|
|
4) Check if the allocating hours is equal to the duration in the template.
|
|
"""
|
|
self.resource_bert.calendar_id = False
|
|
template_slot = self.env['planning.slot.template'].create({
|
|
'start_time': 23,
|
|
'duration': 3,
|
|
})
|
|
|
|
slot = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2021, 1, 1, 0, 0),
|
|
'end_datetime': datetime(2021, 1, 1, 23, 59),
|
|
'resource_id': self.resource_bert.id,
|
|
})
|
|
|
|
slot.write({
|
|
'template_id': template_slot.id,
|
|
})
|
|
|
|
self.assertEqual(slot.start_datetime, datetime(2021, 1, 1, 23, 0), 'The start datetime should have the same hour and minutes defined in the template in the resource timezone.')
|
|
self.assertEqual(slot.end_datetime, datetime(2021, 1, 2, 2, 0), 'The end datetime of this slot should be 3 hours after the start datetime as mentionned in the template in the resource timezone.')
|
|
self.assertEqual(slot.allocated_hours, 3, 'The allocated hours of this slot should be the duration defined in the template in the resource timezone.')
|
|
|
|
def test_planning_state(self):
|
|
""" The purpose of this test case is to check the planning state """
|
|
self.slot.resource_id = self.employee_bert.resource_id
|
|
self.assertEqual(self.slot.state, 'draft', 'Planning is draft mode.')
|
|
self.slot.action_send()
|
|
self.assertEqual(self.slot.state, 'published', 'Planning is published.')
|
|
|
|
def test_create_working_calendar_period(self):
|
|
""" A default dates should be calculated based on the working calendar of the company whatever the period """
|
|
test = Form(self.env['planning.slot'].with_context(
|
|
default_start_datetime=datetime(2019, 5, 27, 0, 0),
|
|
default_end_datetime=datetime(2019, 5, 27, 23, 59, 59)
|
|
))
|
|
slot = test.save()
|
|
self.assertEqual(slot.start_datetime, datetime(2019, 5, 27, 8, 0), 'It should adjust to employee calendar: 0am -> 9pm')
|
|
self.assertEqual(slot.end_datetime, datetime(2019, 5, 27, 17, 0), 'It should adjust to employee calendar: 0am -> 9pm')
|
|
|
|
# For weeks period
|
|
test_week = Form(self.env['planning.slot'].with_context(
|
|
default_start_datetime=datetime(2019, 6, 23, 0, 0),
|
|
default_end_datetime=datetime(2019, 6, 29, 23, 59, 59)
|
|
))
|
|
|
|
test_week = test_week.save()
|
|
self.assertEqual(test_week.start_datetime, datetime(2019, 6, 24, 8, 0), 'It should adjust to employee calendar: 0am -> 9pm')
|
|
self.assertEqual(test_week.end_datetime, datetime(2019, 6, 28, 17, 0), 'It should adjust to employee calendar: 0am -> 9pm')
|
|
|
|
def test_create_planing_slot_without_start_date(self):
|
|
"Test to create planning slot with template id and without start date"
|
|
planning_role = self.env['planning.role'].create({'name': 'role x'})
|
|
template = self.env['planning.slot.template'].create({
|
|
'start_time': 10,
|
|
'duration': 5.0,
|
|
'role_id': planning_role.id,
|
|
})
|
|
with Form(self.env['planning.slot']) as slot_form:
|
|
slot_form.template_id = template
|
|
slot_form.start_datetime = False
|
|
slot_form.template_id = self.template
|
|
self.assertEqual(slot_form.template_id, self.template)
|
|
|
|
def test_shift_switching(self):
|
|
""" The purpose of this test is to check the main back-end mechanism of switching shifts between employees """
|
|
bert_user = new_test_user(self.env,
|
|
login='bert_user',
|
|
groups='planning.group_planning_user',
|
|
name='Bert User',
|
|
email='user@example.com')
|
|
self.employee_bert.user_id = bert_user.id
|
|
joseph_user = new_test_user(self.env,
|
|
login='joseph_user',
|
|
groups='planning.group_planning_user',
|
|
name='Joseph User',
|
|
email='juser@example.com')
|
|
self.employee_joseph.user_id = joseph_user.id
|
|
|
|
# Lets first try to switch a shift that is in the past - should throw an error
|
|
self.slot.resource_id = self.employee_bert.resource_id
|
|
self.assertEqual(self.slot.is_past, True, 'The shift for this test should be in the past')
|
|
with self.assertRaises(UserError):
|
|
self.slot.with_user(bert_user).action_switch_shift()
|
|
|
|
# Lets now try to switch a shift that is not ours - it should again throw an error
|
|
self.assertEqual(self.slot.resource_id, self.employee_bert.resource_id, 'The shift should be assigned to Bert')
|
|
with self.assertRaises(UserError):
|
|
self.slot.with_user(joseph_user).action_switch_shift()
|
|
|
|
# Lets now to try to switch a shift that is both in the future and is ours - this should not throw an error
|
|
test_slot = self.env['planning.slot'].create({
|
|
'start_datetime': datetime.now() + relativedelta(days=2),
|
|
'end_datetime': datetime.now() + relativedelta(days=4),
|
|
'state': 'published',
|
|
'employee_id': bert_user.employee_id.id,
|
|
'resource_id': self.employee_bert.resource_id.id,
|
|
})
|
|
|
|
with self.mock_mail_gateway():
|
|
self.assertEqual(test_slot.request_to_switch, False, 'Before requesting to switch, the request to switch should be False')
|
|
test_slot.with_user(bert_user).action_switch_shift()
|
|
self.assertEqual(test_slot.request_to_switch, True, 'After the switch action, the request to switch should be True')
|
|
|
|
# Lets now assign another user to the shift - this should remove the request to switch and assign the shift
|
|
test_slot.with_user(joseph_user).action_self_assign()
|
|
self.assertEqual(test_slot.request_to_switch, False, 'After the assign action, the request to switch should be False')
|
|
self.assertEqual(test_slot.resource_id, self.employee_joseph.resource_id, 'The shift should now be assigned to Joseph')
|
|
|
|
# Lets now create a new request and then change the start datetime of the switch - this should remove the request to switch
|
|
test_slot.with_user(joseph_user).action_switch_shift()
|
|
self.assertEqual(test_slot.request_to_switch, True, 'After the switch action, the request to switch should be True')
|
|
test_slot.write({'start_datetime': (datetime.now() + relativedelta(days=3)).strftime("%Y-%m-%d %H:%M:%S")})
|
|
self.assertEqual(test_slot.request_to_switch, False, 'After the change, the request to switch should be False')
|
|
|
|
self.assertEqual(len(self._new_mails), 1)
|
|
self.assertMailMailWEmails(
|
|
[bert_user.partner_id.email],
|
|
None,
|
|
author=joseph_user.partner_id,
|
|
)
|
|
|
|
def test_name_long_duration(self):
|
|
""" Set an absurdly high duration to ensure we validate it and get an error """
|
|
template_slot = self.env['planning.slot.template'].create({
|
|
'start_time': 9,
|
|
'duration': 100000,
|
|
})
|
|
with self.assertRaises(ValidationError):
|
|
# only try to get the name, this triggers its compute
|
|
template_slot.name
|
|
|
|
@freeze_time("2023-11-20")
|
|
def test_shift_creation_from_role(self):
|
|
self.env.user.tz = 'Asia/Kolkata'
|
|
self.env.user.company_id.resource_calendar_id.tz = 'Asia/Kolkata'
|
|
PlanningRole = self.env['planning.role']
|
|
PlanningTemplate = self.env['planning.slot.template']
|
|
|
|
role_a = PlanningRole.create({'name': 'role a'})
|
|
role_b = PlanningRole.create({'name': 'role b'})
|
|
|
|
template_a = PlanningTemplate.create({
|
|
'start_time': 8,
|
|
'duration': 2.0,
|
|
'role_id': role_a.id
|
|
})
|
|
self.assertEqual(template_a.duration_days, 1, "Duration in days should be a 1 day according to resource calendar.")
|
|
self.assertEqual(template_a.end_time, 10.0, "End time should be 2 hours from start hours.")
|
|
|
|
template_b = PlanningTemplate.create({
|
|
'start_time': 8,
|
|
'duration': 4.0,
|
|
'role_id': role_b.id
|
|
})
|
|
|
|
slot = self.env['planning.slot'].create({'template_id': template_a.id})
|
|
self.assertEqual(slot.role_id.id, slot.template_autocomplete_ids.mapped('role_id').id, "Role of the slot and shift template should be same.")
|
|
|
|
slot.template_id = template_b.id
|
|
self.assertEqual(slot.role_id.id, slot.template_autocomplete_ids.mapped('role_id').id, "Role of the slot and shift template should be same.")
|
|
|
|
def test_manage_archived_resources(self):
|
|
with freeze_time("2020-04-22"):
|
|
self.env.user.tz = 'UTC'
|
|
slot_1, slot_2, slot_3 = self.env['planning.slot'].create([
|
|
{
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2020, 4, 20, 8, 0),
|
|
'end_datetime': datetime(2020, 4, 24, 17, 0),
|
|
},
|
|
{
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2020, 4, 20, 8, 0),
|
|
'end_datetime': datetime(2020, 4, 21, 17, 0),
|
|
},
|
|
{
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2020, 4, 23, 8, 0),
|
|
'end_datetime': datetime(2020, 4, 24, 17, 0),
|
|
},
|
|
])
|
|
|
|
slot1_initial_end_date = slot_1.end_datetime
|
|
slot2_initial_end_date = slot_2.end_datetime
|
|
|
|
self.resource_bert.employee_id.action_archive()
|
|
|
|
self.assertEqual(slot_1.end_datetime, datetime.combine(fields.Date.today()+ timedelta(days=1), time.min), 'End date of the splited shift should be today')
|
|
self.assertNotEqual(slot_1.end_datetime, slot1_initial_end_date, 'End date should be updated')
|
|
self.assertEqual(slot_2.end_datetime, slot2_initial_end_date, 'End date should be the same')
|
|
self.assertFalse(slot_3.resource_id, 'Resource should be the False for archeived resource shifts')
|
|
|
|
def test_avoid_rounding_error_when_creating_template(self):
|
|
"""
|
|
Regression test: in some odd circumstances,
|
|
a floating point error during the divmod conversion from float -> hours/min can lead to incorrect minutes
|
|
5.1 after a divmod(1) gives back minutes = 0.0999999999964 instead of 1, hence the source of error
|
|
"""
|
|
template = self.env['planning.slot.template'].create({
|
|
'start_time': 8,
|
|
'duration': 5.1, # corresponds to 5:06
|
|
})
|
|
self.assertEqual(template.start_time + template.duration, 13.1, 'Template end time should be the start + duration')
|
|
slot = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2021, 1, 1, 0, 0),
|
|
'end_datetime': datetime(2021, 1, 1, 23, 59),
|
|
})
|
|
slot.write({
|
|
'template_id': template.id,
|
|
})
|
|
self.assertEqual(slot.end_datetime.minute, 6, 'The min should be 6, just like in the template, not 5 due to rounding error')
|
|
|
|
def test_copy_planning_shift(self):
|
|
""" Test state of the planning shift is only copied once we are in the planning split tool
|
|
|
|
Test Case:
|
|
=========
|
|
1) Create a planning shift with state published.
|
|
2) Copy the planning shift as we are in the planning split tool (planning_split_tool=True in the context).
|
|
3) Check the state of the new planning shift is published.
|
|
4) Copy the planning shift as we are not in the planning split tool (planning_split_tool=False in the context).
|
|
5) Check the state of the new planning shift is draft.
|
|
6) Copy the planning shift without the context (= diplicate a shift).
|
|
7) Check the state of the new planning shift is draft.
|
|
"""
|
|
self.env.user.tz = 'UTC'
|
|
slot = self.env['planning.slot'].create({
|
|
'resource_id': self.resource_bert.id,
|
|
'start_datetime': datetime(2020, 4, 20, 8, 0),
|
|
'end_datetime': datetime(2020, 4, 24, 17, 0),
|
|
'state': 'published',
|
|
})
|
|
self.assertEqual(slot.state, 'published', 'The state of the shift should be published')
|
|
|
|
slot1 = slot.with_context(planning_split_tool=True).copy()
|
|
self.assertEqual(slot1.state, 'published', 'The state of the shift should be copied')
|
|
|
|
slot2 = slot.with_context(planning_split_tool=False).copy()
|
|
self.assertEqual(slot2.state, 'draft', 'The state of the shift should not be copied')
|
|
|
|
slot3 = slot.copy()
|
|
self.assertEqual(slot3.state, 'draft', 'The state of the shift should not be copied')
|
|
|
|
def test_calculate_slot_duration_flexible_hours(self):
|
|
""" Ensures that _calculate_slot_duration function rounds up days only when there is an extra non-full day left """
|
|
|
|
employee = self.env['hr.employee'].create({
|
|
'name': 'Test Employee',
|
|
'tz': 'UTC',
|
|
})
|
|
employee.resource_id.calendar_id = False
|
|
|
|
# the diff between start and end is exactly 6 days
|
|
planning_slot_1 = self.env['planning.slot'].create({
|
|
'resource_id': employee.resource_id.id,
|
|
'start_datetime': datetime(2024, 2, 23, 6, 0, 0),
|
|
'end_datetime': datetime(2024, 2, 29, 6, 0, 0),
|
|
})
|
|
self.assertEqual(planning_slot_1.allocated_hours, 48.0)
|
|
|
|
# the diff between start and end is 6 days and 8 hours, hence the diff should be approximated to 7 days
|
|
planning_slot_2 = self.env['planning.slot'].create({
|
|
'resource_id': employee.resource_id.id,
|
|
'start_datetime': datetime(2024, 2, 23, 8, 0, 0),
|
|
'end_datetime': datetime(2024, 2, 29, 16, 0, 0),
|
|
})
|
|
self.assertEqual(planning_slot_2.allocated_hours, 56.0)
|
|
|
|
def test_auto_plan_employee_with_break_company_no_breaks(self):
|
|
""" Test auto-planning an employee with break, while company calendar without breaks
|
|
|
|
Test Case:
|
|
=========
|
|
1) Create company calendar with 24 hours per day.
|
|
2) Create employee with night shifts calendar, with 30 minutes break at midnight.
|
|
3) Create shift from 21:30 to 6:00 with 8 allocated hours.
|
|
4) Auto-plan the shift.
|
|
5) Check the shift is assigned to the employee.
|
|
6) Check the allocated hours remain the same.
|
|
"""
|
|
# Create a 24-hour company calendar
|
|
calendar_24hr = self.env['resource.calendar'].create({
|
|
'name': '24/24 Company Calendar',
|
|
'tz': 'UTC',
|
|
'hours_per_day': 24.0,
|
|
'attendance_ids': [
|
|
(0, 0, {'name': 'Morning ' + str(day), 'dayofweek': str(day), 'hour_from': 0, 'hour_to': 12, 'day_period': 'morning'})
|
|
for day in range(7)
|
|
] + [
|
|
(0, 0, {'name': 'Afternoon ' + str(day), 'dayofweek': str(day), 'hour_from': 12, 'hour_to': 24, 'day_period': 'afternoon'})
|
|
for day in range(7)
|
|
],
|
|
})
|
|
self.env.user.company_id.resource_calendar_id = calendar_24hr
|
|
|
|
night_shifts_calendar = self.env['resource.calendar'].create({
|
|
'name': 'Night Shifts Calendar',
|
|
'tz': 'UTC',
|
|
'hours_per_day': 8.0,
|
|
'attendance_ids': [
|
|
(0, 0, {'name': 'Afternoon ' + str(day), 'dayofweek': str(day), 'hour_from': 21.5, 'hour_to': 24, 'day_period': 'afternoon'})
|
|
for day in range(7)
|
|
] + [
|
|
(0, 0, {'name': 'Break ' + str(day), 'dayofweek': str(day), 'hour_from': 0, 'hour_to': 0.5, 'day_period': 'lunch'})
|
|
for day in range(7)
|
|
] + [
|
|
(0, 0, {'name': 'morning ' + str(day), 'dayofweek': str(day), 'hour_from': 0.5, 'hour_to': 6, 'day_period': 'morning'})
|
|
for day in range(7)
|
|
],
|
|
})
|
|
# Create an employee linked to this calendar
|
|
night_employee = self.env['hr.employee'].create({
|
|
'name': 'Night employee',
|
|
'resource_calendar_id': night_shifts_calendar.id,
|
|
})
|
|
|
|
# Create a shift from 21:30 to 6:00 with an allocated 8 hours
|
|
night_shift = self.env['planning.slot'].create({
|
|
'name': 'Night Shift',
|
|
'start_datetime': datetime(2024, 5, 10, 21, 30),
|
|
'end_datetime': datetime(2024, 5, 11, 6, 0),
|
|
})
|
|
night_shift.allocated_hours = 8
|
|
# Execute auto-plan to assign the employee
|
|
night_shift.auto_plan_id()
|
|
|
|
self.assertEqual(night_shift.resource_id, night_employee.resource_id, 'The night shift should be assigned to the night employee')
|
|
self.assertEqual(night_shift.allocated_hours, 8, 'The allocated hours should remain the same')
|
|
self.assertEqual(night_shift.allocated_percentage, 100, 'The allocated percentage should be 100% as the resource will work the allocated hours')
|
|
|
|
def test_write_multiple_slots(self):
|
|
""" Test that we can write a resource_id on multiple slots at once. """
|
|
slots = self.env['planning.slot'].create([
|
|
{'start_datetime': datetime(2024, 5, 10, 8, 0), 'end_datetime': datetime(2024, 5, 10, 17, 0)},
|
|
{'start_datetime': datetime(2024, 6, 10, 8, 0), 'end_datetime': datetime(2024, 6, 10, 17, 0)},
|
|
])
|
|
slots.write({'resource_id': self.resource_bert.id})
|
|
self.assertEqual(slots.resource_id, self.resource_bert)
|
|
|
|
def test_write_without_resource(self):
|
|
slot = self.env['planning.slot'].create(
|
|
{'start_datetime': datetime(2024, 5, 10, 8, 0), 'end_datetime': datetime(2024, 5, 10, 17, 0)}
|
|
)
|
|
slot.write({
|
|
'repeat' : True,
|
|
'recurrence_update': 'all',
|
|
'start_datetime': datetime(2024, 5, 10, 9, 0),
|
|
'end_datetime': datetime(2024, 5, 10, 18, 0),
|
|
})
|
|
self.assertRecordValues(slot, [{
|
|
'repeat': True,
|
|
'start_datetime': datetime(2024, 5, 10, 9, 0),
|
|
'end_datetime': datetime(2024, 5, 10, 18, 0),
|
|
}])
|
|
|
|
@freeze_time('2021-01-01')
|
|
def test_allocated_hours_when_template_is_during_a_break(self):
|
|
self.resource_janice.tz = 'UTC'
|
|
template_slot = self.env['planning.slot.template'].create({
|
|
'start_time': 11,
|
|
'duration': 4,
|
|
})
|
|
|
|
slot = self.env['planning.slot'].create({
|
|
'start_datetime': datetime(2021, 1, 1, 0, 0),
|
|
'end_datetime': datetime(2021, 1, 1, 23, 59),
|
|
'resource_id': self.resource_janice.id,
|
|
})
|
|
|
|
slot.write({
|
|
'template_id': template_slot.id,
|
|
})
|
|
|
|
self.assertEqual(slot.start_datetime, datetime(2021, 1, 1, 11, 0))
|
|
self.assertEqual(slot.end_datetime, datetime(2021, 1, 1, 16, 0))
|
|
self.assertEqual(slot.allocated_hours, 4)
|
|
|
|
def test_allocated_hours_shift_duplication(self):
|
|
self.slot.resource_id = self.resource_joseph
|
|
self.assertEqual(self.slot.allocated_hours, 8)
|
|
slot2 = self.slot.copy({'resource_id': self.resource_bert.id})
|
|
self.assertEqual(slot2.allocated_hours, 4, "The allocated hours should have been recomputed with the new resource after copying the shift.")
|