19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:39 +01:00
parent 38c6088dcc
commit d9452d2060
243 changed files with 30797 additions and 10815 deletions

View file

@ -1,5 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_resource
from . import test_performance
from . import (
test_calendar,
test_mixin,
test_performance,
test_resource,
test_resource_errors,
test_timezones,
)

View file

@ -1,104 +1,174 @@
# -*- 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 import fields
from odoo.tests.common import TransactionCase
class TestResourceCommon(TransactionCase):
@classmethod
def datetime_tz(cls, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
""" Return a `datetime` object with a given timezone (if given). """
dt = datetime(year, month, day, hour, minute, second, microsecond)
return timezone(tzinfo).localize(dt) if tzinfo else dt
@classmethod
def datetime_str(cls, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
""" Return a fields.Datetime value with the given timezone. """
dt = datetime(year, month, day, hour, minute, second, microsecond)
if tzinfo:
dt = timezone(tzinfo).localize(dt).astimezone(utc)
return fields.Datetime.to_string(dt)
@classmethod
def _define_calendar(cls, name, attendances, tz):
return cls.env['resource.calendar'].create({
'name': name,
'tz': tz,
'attendance_ids': [
(0, 0, {
'name': '%s_%d' % (name, index),
'hour_from': att[0],
'hour_to': att[1],
'dayofweek': str(att[2]),
})
for index, att in enumerate(attendances)
],
})
return cls.env["resource.calendar"].create(
{
"name": name,
"tz": tz,
"attendance_ids": [
(
0,
0,
{
"name": "%s_%d" % (name, index),
"hour_from": att[0],
"hour_to": att[1],
"dayofweek": str(att[2]),
"duration_days": att[3],
},
)
for index, att in enumerate(attendances)
],
},
)
@classmethod
def _define_calendar_2_weeks(cls, name, attendances, tz):
return cls.env['resource.calendar'].create({
'name': name,
'tz': tz,
'two_weeks_calendar': True,
'attendance_ids': [
(0, 0, {
'name': '%s_%d' % (name, index),
'hour_from': att[0],
'hour_to': att[1],
'dayofweek': str(att[2]),
'week_type': att[3],
'display_type': att[4],
'sequence': att[5],
})
for index, att in enumerate(attendances)
],
})
return cls.env["resource.calendar"].create(
{
"name": name,
"tz": tz,
"two_weeks_calendar": True,
"attendance_ids": [
(
0,
0,
{
"name": "%s_%d" % (name, index),
"hour_from": att[0],
"hour_to": att[1],
"dayofweek": str(att[2]),
"week_type": att[3],
},
)
for index, att in enumerate(attendances)
],
},
)
@classmethod
def setUpClass(cls):
super(TestResourceCommon, cls).setUpClass()
super().setUpClass()
cls.env.company.resource_calendar_id.tz = "Europe/Brussels"
# UTC+1 winter, UTC+2 summer
cls.calendar_jean = cls._define_calendar('40 Hours', [(8, 16, i) for i in range(5)], 'Europe/Brussels')
cls.calendar_jean = cls._define_calendar(
"40 Hours", [(8, 16, i, 1) for i in range(5)], "Europe/Brussels",
)
# UTC+6
cls.calendar_patel = cls._define_calendar('38 Hours', sum([((9, 12, i), (13, 17, i)) for i in range(5)], ()), 'Etc/GMT-6')
cls.calendar_patel = cls._define_calendar(
"38 Hours",
sum((((9, 12, i, 3 / 7), (13, 17, i, 4 / 7)) for i in range(5)), ()),
"Etc/GMT-6",
)
# UTC-8 winter, UTC-7 summer
cls.calendar_john = cls._define_calendar('8+12 Hours', [(8, 16, 1), (8, 13, 4), (16, 23, 4)], 'America/Los_Angeles')
cls.calendar_john = cls._define_calendar(
"8+12 Hours",
[(8, 16, 1, 1), (8, 13, 4, 5 / 12), (16, 23, 4, 7 / 12)],
"America/Los_Angeles",
)
# UTC+1 winter, UTC+2 summer
cls.calendar_jules = cls._define_calendar_2_weeks('Week 1: 30 Hours - Week 2: 16 Hours', [
(0, 0, 0, '0', 'line_section', 0), (8, 16, 0, '0', False, 1), (9, 17, 1, '0', False, 2),
(0, 0, 0, '1', 'line_section', 10), (8, 16, 0, '1', False, 11), (7, 15, 2, '1', False, 12),
(8, 16, 3, '1', False, 13), (10, 16, 4, '1', False, 14)], 'Europe/Brussels')
cls.calendar_jules = cls._define_calendar_2_weeks(
"Week 1: 30 Hours - Week 2: 16 Hours",
[
(8, 16, 0, "0"),
(9, 17, 1, "0"),
(8, 16, 0, "1"),
(7, 15, 2, "1"),
(8, 16, 3, "1"),
(10, 16, 4, "1"),
],
"Europe/Brussels",
)
cls.calendar_paul = cls._define_calendar('Morning and evening shifts', sum([((2, 7, i), (10, 16, i)) for i in range(5)], ()), 'America/Noronha')
cls.calendar_paul = cls._define_calendar(
"Morning and evening shifts",
sum((((2, 7, i, 0.5), (10, 16, i, 0.5)) for i in range(5)), ()),
"America/Noronha",
)
cls.calendar_bob = cls._define_calendar(
"Calendar with adjacent attendances",
sum((((8, 12, i, 0.5), (12, 16, i, 0.5)) for i in range(5)), ()),
"Europe/Brussels",
)
# Employee is linked to a resource.resource via resource.mixin
cls.jean = cls.env['resource.test'].create({
'name': 'Jean',
'resource_calendar_id': cls.calendar_jean.id,
})
cls.patel = cls.env['resource.test'].create({
'name': 'Patel',
'resource_calendar_id': cls.calendar_patel.id,
})
cls.john = cls.env['resource.test'].create({
'name': 'John',
'resource_calendar_id': cls.calendar_john.id,
})
cls.jules = cls.env['resource.test'].create({
'name': 'Jules',
'resource_calendar_id': cls.calendar_jules.id,
})
cls.jean = cls.env["resource.test"].create(
{
"name": "Jean",
"resource_calendar_id": cls.calendar_jean.id,
},
)
cls.patel = cls.env["resource.test"].create(
{
"name": "Patel",
"resource_calendar_id": cls.calendar_patel.id,
},
)
cls.john = cls.env["resource.test"].create(
{
"name": "John",
"resource_calendar_id": cls.calendar_john.id,
},
)
cls.jules = cls.env["resource.test"].create(
{
"name": "Jules",
"resource_calendar_id": cls.calendar_jules.id,
},
)
cls.paul = cls.env['resource.test'].create({
'name': 'Paul',
'resource_calendar_id': cls.calendar_paul.id,
})
cls.paul = cls.env["resource.test"].create(
{
"name": "Paul",
"resource_calendar_id": cls.calendar_paul.id,
},
)
cls.bob = cls.env["resource.test"].create(
{
"name": "Bob",
"resource_calendar_id": cls.calendar_bob.id,
},
)
cls.two_weeks_resource = cls._define_calendar_2_weeks(
'Two weeks resource',
"Two weeks resource",
[
(0, 0, 0, '0', 'line_section', 0),
(8, 16, 0, '0', False, 1),
(8, 16, 1, '0', False, 2),
(8, 16, 2, '0', False, 3),
(8, 16, 3, '0', False, 4),
(8, 16, 4, '0', False, 5),
(0, 0, 0, '1', 'line_section', 10),
(8, 16, 0, '1', False, 11),
(8, 16, 1, '1', False, 12),
(8, 16, 2, '1', False, 13),
(8, 16, 3, '1', False, 14),
(8, 16, 4, '1', False, 15)
(8, 16, 0, "0"),
(8, 16, 1, "0"),
(8, 16, 2, "0"),
(8, 16, 3, "0"),
(8, 16, 4, "0"),
(8, 16, 0, "1"),
(8, 16, 1, "1"),
(8, 16, 2, "1"),
(8, 16, 3, "1"),
(8, 16, 4, "1"),
],
'Europe/Brussels'
"Europe/Brussels",
)

View file

@ -0,0 +1,485 @@
from datetime import date, datetime
from pytz import timezone
from odoo import fields
from odoo.addons.test_resource.tests.common import TestResourceCommon
class TestCalendar(TestResourceCommon):
def setUp(self):
super().setUp()
def test_get_work_hours_count(self):
self.env['resource.calendar.leaves'].create({
'name': 'Global Time Off',
'resource_id': False,
'calendar_id': self.calendar_jean.id,
'date_from': self.datetime_str(2018, 4, 3, 0, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 3, 23, 59, 59, tzinfo=self.jean.tz),
})
self.env['resource.calendar.leaves'].create({
'name': 'leave for Jean',
'calendar_id': self.calendar_jean.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 5, 0, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 5, 23, 59, 59, tzinfo=self.jean.tz),
})
hours = self.calendar_jean.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.jean.tz),
)
self.assertEqual(hours, 32)
hours = self.calendar_jean.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.jean.tz),
compute_leaves=False,
)
self.assertEqual(hours, 40)
# leave of size 0
self.env['resource.calendar.leaves'].create({
'name': 'zero_length',
'calendar_id': self.calendar_patel.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 3, 0, 0, 0, tzinfo=self.patel.tz),
'date_to': self.datetime_str(2018, 4, 3, 0, 0, 0, tzinfo=self.patel.tz),
})
hours = self.calendar_patel.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.patel.tz),
)
self.assertEqual(hours, 35)
# leave of medium size
leave = self.env['resource.calendar.leaves'].create({
'name': 'zero_length',
'calendar_id': self.calendar_patel.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 3, 9, 0, 0, tzinfo=self.patel.tz),
'date_to': self.datetime_str(2018, 4, 3, 12, 0, 0, tzinfo=self.patel.tz),
})
hours = self.calendar_patel.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.patel.tz),
)
self.assertEqual(hours, 32)
leave.unlink()
# leave of very small size
leave = self.env['resource.calendar.leaves'].create({
'name': 'zero_length',
'calendar_id': self.calendar_patel.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 3, 0, 0, 0, tzinfo=self.patel.tz),
'date_to': self.datetime_str(2018, 4, 3, 0, 0, 10, tzinfo=self.patel.tz),
})
hours = self.calendar_patel.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.patel.tz),
)
self.assertEqual(hours, 35)
leave.unlink()
# no timezone given should be converted to UTC
# Should equal to a leave between 2018/04/03 10:00:00 and 2018/04/04 10:00:00
leave = self.env['resource.calendar.leaves'].create({
'name': 'no timezone',
'calendar_id': self.calendar_patel.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 3, 4, 0, 0),
'date_to': self.datetime_str(2018, 4, 4, 4, 0, 0),
})
hours = self.calendar_patel.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.patel.tz),
)
self.assertEqual(hours, 28)
hours = self.calendar_patel.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 23, 59, 59, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.patel.tz),
)
self.assertEqual(hours, 0)
leave.unlink()
# 2 weeks calendar week 1
hours = self.calendar_jules.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jules.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.jules.tz),
)
self.assertEqual(hours, 30)
# 2 weeks calendar week 1
hours = self.calendar_jules.get_work_hours_count(
self.datetime_tz(2018, 4, 16, 0, 0, 0, tzinfo=self.jules.tz),
self.datetime_tz(2018, 4, 20, 23, 59, 59, tzinfo=self.jules.tz),
)
self.assertEqual(hours, 30)
# 2 weeks calendar week 2
hours = self.calendar_jules.get_work_hours_count(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.jules.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.jules.tz),
)
self.assertEqual(hours, 16)
# 2 weeks calendar week 2, leave during a day where he doesn't work this week
leave = self.env['resource.calendar.leaves'].create({
'name': 'Time Off Jules week 2',
'calendar_id': self.calendar_jules.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 11, 4, 0, 0, tzinfo=self.jules.tz),
'date_to': self.datetime_str(2018, 4, 13, 4, 0, 0, tzinfo=self.jules.tz),
})
hours = self.calendar_jules.get_work_hours_count(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.jules.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.jules.tz),
)
self.assertEqual(hours, 16)
leave.unlink()
# 2 weeks calendar week 2, leave during a day where he works this week
leave = self.env['resource.calendar.leaves'].create({
'name': 'Time Off Jules week 2',
'calendar_id': self.calendar_jules.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 9, 0, 0, 0, tzinfo=self.jules.tz),
'date_to': self.datetime_str(2018, 4, 9, 23, 59, 0, tzinfo=self.jules.tz),
})
hours = self.calendar_jules.get_work_hours_count(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.jules.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.jules.tz),
)
self.assertEqual(hours, 8)
leave.unlink()
# leave without calendar, should count for anyone in the company
leave = self.env['resource.calendar.leaves'].create({
'name': 'small leave',
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 3, 9, 0, 0, tzinfo=self.patel.tz),
'date_to': self.datetime_str(2018, 4, 3, 12, 0, 0, tzinfo=self.patel.tz),
})
hours = self.calendar_patel.get_work_hours_count(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.patel.tz),
)
self.assertEqual(hours, 32)
def test_calendar_working_hours_count(self):
calendar = self.env['resource.calendar'].create({
'name': 'Standard 35 hours/week',
'company_id': self.env.company.id,
'tz': 'UTC',
'attendance_ids': [(5, 0, 0),
(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': 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 Afternoon', '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 Afternoon', '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 Afternoon', '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 Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 16, 'day_period': 'afternoon'}),
],
})
res = calendar.get_work_hours_count(
fields.Datetime.from_string('2017-05-03 14:03:00'), # Wednesday (8:00-12:00, 13:00-16:00)
fields.Datetime.from_string('2017-05-04 11:03:00'), # Thursday (8:00-12:00, 13:00-16:00)
compute_leaves=False)
self.assertEqual(res, 5.0)
def test_calendar_working_hours_24(self):
self.att_4 = self.env['resource.calendar.attendance'].create({
'name': 'Att4',
'calendar_id': self.calendar_jean.id,
'dayofweek': '2',
'hour_from': 0,
'hour_to': 24,
})
res = self.calendar_jean.get_work_hours_count(
self.datetime_tz(2018, 6, 19, 23, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 6, 21, 1, 0, 0, tzinfo=self.jean.tz),
compute_leaves=True)
self.assertAlmostEqual(res, 24.0)
def test_plan_hours(self):
self.env['resource.calendar.leaves'].create({
'name': 'global',
'calendar_id': self.calendar_jean.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 11, 0, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 11, 23, 59, 59, tzinfo=self.jean.tz),
})
time = self.calendar_jean.plan_hours(2, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=False)
self.assertEqual(time, self.datetime_tz(2018, 4, 10, 10, 0, 0, tzinfo=self.jean.tz))
time = self.calendar_jean.plan_hours(20, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=False)
self.assertEqual(time, self.datetime_tz(2018, 4, 12, 12, 0, 0, tzinfo=self.jean.tz))
time = self.calendar_jean.plan_hours(5, self.datetime_tz(2018, 4, 10, 15, 0, 0, tzinfo=self.jean.tz), compute_leaves=True)
self.assertEqual(time, self.datetime_tz(2018, 4, 12, 12, 0, 0, tzinfo=self.jean.tz))
# negative planning
time = self.calendar_jean.plan_hours(-10, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=True)
self.assertEqual(time, self.datetime_tz(2018, 4, 6, 14, 0, 0, tzinfo=self.jean.tz))
# zero planning with holidays
time = self.calendar_jean.plan_hours(0, self.datetime_tz(2018, 4, 11, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=True)
self.assertEqual(time, self.datetime_tz(2018, 4, 12, 8, 0, 0, tzinfo=self.jean.tz))
time = self.calendar_jean.plan_hours(0, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=False)
self.assertEqual(time, self.datetime_tz(2018, 4, 10, 8, 0, 0, tzinfo=self.jean.tz))
# very small planning
time = self.calendar_jean.plan_hours(0.0002, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=True)
self.assertEqual(time, self.datetime_tz(2018, 4, 10, 8, 0, 0, 720000, tzinfo=self.jean.tz))
# huge planning
time = self.calendar_jean.plan_hours(3000, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=False)
self.assertEqual(time, self.datetime_tz(2019, 9, 16, 16, 0, 0, tzinfo=self.jean.tz))
def test_plan_days(self):
self.env['resource.calendar.leaves'].create({
'name': 'global',
'calendar_id': self.calendar_jean.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 11, 0, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 11, 23, 59, 59, tzinfo=self.jean.tz),
})
time = self.calendar_jean.plan_days(1, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=False)
self.assertEqual(time, self.datetime_tz(2018, 4, 10, 16, 0, 0, tzinfo=self.jean.tz))
time = self.calendar_jean.plan_days(3, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=False)
self.assertEqual(time, self.datetime_tz(2018, 4, 12, 16, 0, 0, tzinfo=self.jean.tz))
time = self.calendar_jean.plan_days(4, self.datetime_tz(2018, 4, 10, 16, 0, 0, tzinfo=self.jean.tz), compute_leaves=True)
self.assertEqual(time, self.datetime_tz(2018, 4, 17, 16, 0, 0, tzinfo=self.jean.tz))
# negative planning
time = self.calendar_jean.plan_days(-10, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=True)
self.assertEqual(time, self.datetime_tz(2018, 3, 27, 8, 0, 0, tzinfo=self.jean.tz))
# zero planning
time = self.calendar_jean.plan_days(0, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=True)
self.assertEqual(time, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz))
# very small planning returns False in this case
# TODO: decide if this behaviour is alright
time = self.calendar_jean.plan_days(0.0002, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=True)
self.assertEqual(time, False)
# huge planning
# TODO: Same as above
# NOTE: Maybe allow to set a max limit to the method
time = self.calendar_jean.plan_days(3000, self.datetime_tz(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz), compute_leaves=False)
self.assertEqual(time, False)
def test_closest_time(self):
# Calendar:
# Tuesdays 8-16
# Fridays 8-13 and 16-23
dt = self.datetime_tz(2020, 4, 2, 7, 0, 0, tzinfo=self.john.tz)
calendar_dt = self.calendar_john._get_closest_work_time(dt)
self.assertFalse(calendar_dt, "It should not return any value for unattended days")
dt = self.datetime_tz(2020, 4, 3, 7, 0, 0, tzinfo=self.john.tz)
range_start = self.datetime_tz(2020, 4, 3, 8, 0, 0, tzinfo=self.john.tz)
range_end = self.datetime_tz(2020, 4, 3, 19, 0, 0, tzinfo=self.john.tz)
calendar_dt = self.calendar_john._get_closest_work_time(dt, search_range=(range_start, range_end))
self.assertFalse(calendar_dt, "It should not return any value if dt outside of range")
dt = self.datetime_tz(2020, 4, 3, 7, 0, 0, tzinfo=self.john.tz) # before
start = self.datetime_tz(2020, 4, 3, 8, 0, 0, tzinfo=self.john.tz)
calendar_dt = self.calendar_john._get_closest_work_time(dt)
self.assertEqual(calendar_dt, start, "It should return the start of the day")
dt = self.datetime_tz(2020, 4, 3, 10, 0, 0, tzinfo=self.john.tz) # after
start = self.datetime_tz(2020, 4, 3, 8, 0, 0, tzinfo=self.john.tz)
calendar_dt = self.calendar_john._get_closest_work_time(dt)
self.assertEqual(calendar_dt, start, "It should return the start of the closest attendance")
dt = self.datetime_tz(2020, 4, 3, 7, 0, 0, tzinfo=self.john.tz) # before
end = self.datetime_tz(2020, 4, 3, 13, 0, 0, tzinfo=self.john.tz)
calendar_dt = self.calendar_john._get_closest_work_time(dt, match_end=True)
self.assertEqual(calendar_dt, end, "It should return the end of the closest attendance")
dt = self.datetime_tz(2020, 4, 3, 14, 0, 0, tzinfo=self.john.tz) # after
end = self.datetime_tz(2020, 4, 3, 13, 0, 0, tzinfo=self.john.tz)
calendar_dt = self.calendar_john._get_closest_work_time(dt, match_end=True)
self.assertEqual(calendar_dt, end, "It should return the end of the closest attendance")
dt = self.datetime_tz(2020, 4, 3, 0, 0, 0, tzinfo=self.john.tz)
start = self.datetime_tz(2020, 4, 3, 8, 0, 0, tzinfo=self.john.tz)
calendar_dt = self.calendar_john._get_closest_work_time(dt)
self.assertEqual(calendar_dt, start, "It should return the start of the closest attendance")
dt = self.datetime_tz(2020, 4, 3, 23, 59, 59, tzinfo=self.john.tz)
end = self.datetime_tz(2020, 4, 3, 23, 0, 0, tzinfo=self.john.tz)
calendar_dt = self.calendar_john._get_closest_work_time(dt, match_end=True)
self.assertEqual(calendar_dt, end, "It should return the end of the closest attendance")
def test_attendance_interval_edge_tz(self):
# When genereting the attendance intervals in an edge timezone, the last interval shouldn't
# be truncated if the timezone is correctly set
self.env.user.tz = "America/Los_Angeles"
self.calendar_jean.tz = "America/Los_Angeles"
attendances = self.calendar_jean._attendance_intervals_batch(
datetime.combine(date(2023, 1, 1), datetime.min.time(), tzinfo=timezone("UTC")),
datetime.combine(date(2023, 1, 31), datetime.max.time(), tzinfo=timezone("UTC")))
last_attendance = list(attendances[False])[-1]
self.assertEqual(last_attendance[0].replace(tzinfo=None), datetime(2023, 1, 31, 8))
self.assertEqual(last_attendance[1].replace(tzinfo=None), datetime(2023, 1, 31, 15, 59, 59, 999999))
attendances = self.calendar_jean._attendance_intervals_batch(
datetime.combine(date(2023, 1, 1), datetime.min.time(), tzinfo=timezone("America/Los_Angeles")),
datetime.combine(date(2023, 1, 31), datetime.max.time(), tzinfo=timezone("America/Los_Angeles")))
last_attendance = list(attendances[False])[-1]
self.assertEqual(last_attendance[0].replace(tzinfo=None), datetime(2023, 1, 31, 8))
self.assertEqual(last_attendance[1].replace(tzinfo=None), datetime(2023, 1, 31, 16))
def test_resource_calendar_update(self):
""" Ensure leave calendar gets set correctly when updating resource calendar. """
holiday = self.env['resource.calendar.leaves'].create({
'name': "May Day",
'calendar_id': self.calendar_jean.id,
'date_from': self.datetime_str(2024, 5, 1, 0, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2024, 5, 1, 23, 59, 59, tzinfo=self.jean.tz),
})
# Jean takes a leave
leave = self.env['resource.calendar.leaves'].create({
'name': "Jean is AFK",
'calendar_id': self.calendar_jean.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2024, 5, 10, 8, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2024, 5, 10, 16, 0, 0, tzinfo=self.jean.tz),
})
# Jean changes working schedule to Jules'
self.jean.resource_calendar_id = self.calendar_jules
self.assertEqual(leave.calendar_id, self.calendar_jules, "Leave calendar should update")
self.assertEqual(holiday.calendar_id, self.calendar_jean, "Global leave shouldn't change")
def test_compute_work_time_rate_with_one_week_calendar(self):
"""Test Case: check if the computation of the work time rate in the resource.calendar is correct."""
# Define a mid time
resource_calendar = self.env['resource.calendar'].create({
'name': 'Calendar Mid-Time',
'tz': "Europe/Brussels",
'two_weeks_calendar': False,
'full_time_required_hours': 40,
'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'}),
],
})
self.assertAlmostEqual(resource_calendar.work_time_rate, 50, 2)
# Define a 4/5
resource_calendar.write({
'name': 'Calendar (4 / 5)',
'attendance_ids': [
(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'}),
],
})
self.assertAlmostEqual(resource_calendar.work_time_rate, 80, 2)
# Define a 9/10
resource_calendar.write({
'name': 'Calendar (9 / 10)',
'attendance_ids': [(0, 0, {'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'})],
})
self.assertAlmostEqual(resource_calendar.work_time_rate, 90, 2)
# Define a Full-Time
resource_calendar.write({
'name': 'Calendar Full-Time',
'attendance_ids': [(0, 0, {'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'})],
})
self.assertAlmostEqual(resource_calendar.work_time_rate, 100, 2)
def test_compute_work_time_rate_with_two_weeks_calendar(self):
"""Test Case: check if the computation of the work time rate in the resource.calendar is correct."""
def create_attendance_ids(attendance_list):
return [(0, 0, {'week_type': str(i), **attendance}) for i in range(0, 2) for attendance in attendance_list]
attendance_list = [
{'name': 'Monday Morning', 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'},
{'name': 'Monday Lunch', 'dayofweek': '0', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'},
{'name': 'Monday Afternoon', 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'},
{'name': 'Tuesday Morning', 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'},
{'name': 'Tuesday Lunch', 'dayofweek': '1', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'},
{'name': 'Tuesday Afternoon', 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'},
{'name': 'Wednesday Morning', 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'},
]
# Define a mid time
resource_calendar = self.env['resource.calendar'].create({
'name': 'Calendar Mid-Time',
'tz': "Europe/Brussels",
'two_weeks_calendar': True,
'full_time_required_hours': 40,
'attendance_ids': create_attendance_ids(attendance_list),
})
self.assertAlmostEqual(resource_calendar.work_time_rate, 50, 2)
attendance_list = [
{'name': 'Wednesday Afternoon', 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'},
{'name': 'Thursday Morning', 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'},
{'name': 'Thursday Lunch', 'dayofweek': '3', 'hour_from': 12, 'hour_to': 13, 'day_period': 'lunch'},
{'name': 'Thursday Afternoon', 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'},
]
# Define a 4/5
resource_calendar.write({
'name': 'Calendar (4 / 5)',
'attendance_ids': create_attendance_ids(attendance_list),
})
self.assertAlmostEqual(resource_calendar.work_time_rate, 80, 2)
# Define a 9/10
resource_calendar.write({
'name': 'Calendar (9 / 10)',
'attendance_ids': create_attendance_ids([{'name': 'Friday Morning', 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}]),
})
self.assertAlmostEqual(resource_calendar.work_time_rate, 90, 2)
# Define a Full-Time
resource_calendar.write({
'name': 'Calendar Full-Time',
'attendance_ids': create_attendance_ids([{'name': 'Friday Afternoon', 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}]),
})
self.assertAlmostEqual(resource_calendar.work_time_rate, 100, 2)

View file

@ -0,0 +1,517 @@
from datetime import date, datetime
from odoo.addons.test_resource.tests.common import TestResourceCommon
class TestResMixin(TestResourceCommon):
def test_adjust_calendar(self):
# Calendar:
# Tuesdays 8-16
# Fridays 8-13 and 16-23
result = self.john._adjust_to_calendar(
self.datetime_tz(2020, 4, 3, 9, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 4, 3, 14, 0, 0, tzinfo=self.john.tz),
)
self.assertEqual(result[self.john], (
self.datetime_tz(2020, 4, 3, 8, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 4, 3, 13, 0, 0, tzinfo=self.john.tz),
))
result = self.john._adjust_to_calendar(
self.datetime_tz(2020, 4, 3, 13, 1, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 4, 3, 14, 0, 0, tzinfo=self.john.tz),
)
self.assertEqual(result[self.john], (
self.datetime_tz(2020, 4, 3, 16, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 4, 3, 23, 0, 0, tzinfo=self.john.tz),
))
result = self.john._adjust_to_calendar(
self.datetime_tz(2020, 4, 4, 9, 0, 0, tzinfo=self.john.tz), # both a day without attendance
self.datetime_tz(2020, 4, 4, 14, 0, 0, tzinfo=self.john.tz),
)
self.assertEqual(result[self.john], (None, None))
result = self.john._adjust_to_calendar(
self.datetime_tz(2020, 4, 3, 8, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 4, 4, 14, 0, 0, tzinfo=self.john.tz), # day without attendance
)
self.assertEqual(result[self.john], (
self.datetime_tz(2020, 4, 3, 8, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 4, 3, 23, 0, 0, tzinfo=self.john.tz),
))
result = self.john._adjust_to_calendar(
self.datetime_tz(2020, 4, 2, 8, 0, 0, tzinfo=self.john.tz), # day without attendance
self.datetime_tz(2020, 4, 3, 14, 0, 0, tzinfo=self.john.tz),
)
self.assertEqual(result[self.john], (
self.datetime_tz(2020, 4, 3, 8, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 4, 3, 13, 0, 0, tzinfo=self.john.tz),
))
result = self.john._adjust_to_calendar(
self.datetime_tz(2020, 3, 31, 0, 0, 0, tzinfo=self.john.tz), # search between Tuesday and Thursday
self.datetime_tz(2020, 4, 2, 23, 59, 59, tzinfo=self.john.tz),
)
self.assertEqual(result[self.john], (
self.datetime_tz(2020, 3, 31, 8, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 3, 31, 16, 0, tzinfo=self.john.tz),
))
result = self.john._adjust_to_calendar(
self.datetime_tz(2020, 3, 31, 0, 0, 0, tzinfo=self.john.tz), # search between Tuesday and Friday
self.datetime_tz(2020, 4, 3, 23, 59, 59, tzinfo=self.john.tz),
)
result = self.john._adjust_to_calendar(
self.datetime_tz(2020, 3, 31, 8, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2020, 4, 3, 23, 0, 0, tzinfo=self.john.tz),
)
# It should find the start and end within the search range
result = self.paul._adjust_to_calendar(
self.datetime_tz(2020, 4, 2, 2, 0, 0, tzinfo='UTC'),
self.datetime_tz(2020, 4, 3, 1, 59, 59, tzinfo='UTC'),
)
self.assertEqual(result[self.paul], (
self.datetime_tz(2020, 4, 2, 4, 0, tzinfo='UTC'),
self.datetime_tz(2020, 4, 2, 18, 0, tzinfo='UTC'),
), "It should have found the start and end of the shift on the same day on April 2nd, 2020")
def test_adjust_calendar_timezone_before(self):
# Calendar:
# Every day 8-16
self.jean.tz = 'Asia/Tokyo'
self.calendar_jean.tz = 'Europe/Brussels'
result = self.jean._adjust_to_calendar(
self.datetime_tz(2020, 4, 1, 0, 0, 0, tzinfo='Asia/Tokyo'),
self.datetime_tz(2020, 4, 1, 23, 59, 59, tzinfo='Asia/Tokyo'),
)
self.assertEqual(result[self.jean], (
self.datetime_tz(2020, 4, 1, 8, 0, 0, tzinfo='Asia/Tokyo'),
self.datetime_tz(2020, 4, 1, 16, 0, 0, tzinfo='Asia/Tokyo'),
), "It should have found a starting time the 1st")
def test_adjust_calendar_timezone_after(self):
# Calendar:
# Tuesdays 8-16
# Fridays 8-13 and 16-23
tz = 'Europe/Brussels'
self.john.tz = tz
result = self.john._adjust_to_calendar(
datetime(2020, 4, 2, 23, 0, 0), # The previous day in UTC, but the 3rd in Europe/Brussels
datetime(2020, 4, 3, 20, 0, 0),
)
self.assertEqual(result[self.john], (
datetime(2020, 4, 3, 6, 0, 0),
datetime(2020, 4, 3, 21, 0, 0),
), "It should have found a starting time the 3rd")
def test_work_days_data(self):
# Looking at Jean's calendar
# Viewing it as Jean
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 16, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(data, {'days': 5, 'hours': 40})
# Viewing it as Bob
data = self.bob._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.bob.tz),
self.datetime_tz(2018, 4, 6, 16, 0, 0, tzinfo=self.bob.tz),
)[self.bob.id]
self.assertEqual(data, {'days': 5, 'hours': 40})
# Viewing it as Patel
# Views from 2018/04/01 20:00:00 to 2018/04/06 12:00:00
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 6, 16, 0, 0, tzinfo=self.patel.tz),
)[self.jean.id]
self.assertEqual(data, {'days': 4.5, 'hours': 36}) # We see only 36 hours
# Viewing it as John
# Views from 2018/04/02 09:00:00 to 2018/04/07 02:00:00
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2018, 4, 6, 16, 0, 0, tzinfo=self.john.tz),
)[self.jean.id]
# still showing as 5 days because of rounding, but we see only 39 hours
self.assertEqual(data, {'days': 4.875, 'hours': 39})
# Looking at John's calendar
# Viewing it as Jean
# Views from 2018/04/01 15:00:00 to 2018/04/06 14:00:00
data = self.john._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.john.id]
self.assertEqual(data, {'days': 1.417, 'hours': 13})
# Viewing it as Patel
# Views from 2018/04/01 11:00:00 to 2018/04/06 10:00:00
data = self.john._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.patel.tz),
)[self.john.id]
self.assertEqual(data, {'days': 1.167, 'hours': 10})
# Viewing it as John
data = self.john._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.john.tz),
)[self.john.id]
self.assertEqual(data, {'days': 2, 'hours': 20})
# using Jean as a timezone reference
data = self.john._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.john.tz),
calendar=self.calendar_jean,
)[self.john.id]
self.assertEqual(data, {'days': 5, 'hours': 40})
# half days
leave = self.env['resource.calendar.leaves'].create({
'name': 'half',
'calendar_id': self.calendar_jean.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 14, 0, 0, tzinfo=self.jean.tz),
})
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(data, {'days': 4.5, 'hours': 36})
# using John as a timezone reference, leaves are outside attendances
data = self.john._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.john.tz),
calendar=self.calendar_jean,
)[self.john.id]
self.assertEqual(data, {'days': 5, 'hours': 40})
leave.unlink()
# leave size 0
leave = self.env['resource.calendar.leaves'].create({
'name': 'zero',
'calendar_id': self.calendar_jean.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
})
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(data, {'days': 5, 'hours': 40})
leave.unlink()
# leave very small size
leave = self.env['resource.calendar.leaves'].create({
'name': 'small',
'calendar_id': self.calendar_jean.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 10, 0, 1, tzinfo=self.jean.tz),
})
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(data['days'], 5)
self.assertAlmostEqual(data['hours'], 40, 2)
def test_leaves_days_data(self):
# Jean takes a leave
self.env['resource.calendar.leaves'].create({
'name': 'Jean is visiting India',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 10, 8, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 10, 16, 0, 0, tzinfo=self.jean.tz),
})
# John takes a leave for Jean
self.env['resource.calendar.leaves'].create({
'name': 'Jean is comming in USA',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 12, 8, 0, 0, tzinfo=self.john.tz),
'date_to': self.datetime_str(2018, 4, 12, 16, 0, 0, tzinfo=self.john.tz),
})
# Jean asks to see how much leave he has taken
data = self.jean._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.jean.tz),
)[self.jean.id]
# Sees only 1 day and 8 hours because, as john is in UTC-7 the second leave is not in
# the attendances of Jean
self.assertEqual(data, {'days': 1, 'hours': 8})
# Patel Asks to see when Jean has taken some leaves
# Patel should see the same
data = self.jean._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.patel.tz),
)[self.jean.id]
self.assertEqual(data, {'days': 1, 'hours': 8})
# use Patel as a resource, jean's leaves are not visible
datas = self.patel._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.patel.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.patel.tz),
calendar=self.calendar_jean,
)[self.patel.id]
self.assertEqual(datas['days'], 0)
self.assertEqual(datas['hours'], 0)
# Jean takes a leave for John
# Gives 3 hours (3/8 of a day)
self.env['resource.calendar.leaves'].create({
'name': 'John is sick',
'calendar_id': self.john.resource_calendar_id.id,
'resource_id': self.john.resource_id.id,
'date_from': self.datetime_str(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 10, 20, 0, 0, tzinfo=self.jean.tz),
})
# John takes a leave
# Gives all day (12 hours)
self.env['resource.calendar.leaves'].create({
'name': 'John goes to holywood',
'calendar_id': self.john.resource_calendar_id.id,
'resource_id': self.john.resource_id.id,
'date_from': self.datetime_str(2018, 4, 13, 7, 0, 0, tzinfo=self.john.tz),
'date_to': self.datetime_str(2018, 4, 13, 18, 0, 0, tzinfo=self.john.tz),
})
# John asks how much leaves he has
# He sees that he has only 15 hours of leave in his attendances
data = self.john._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.john.tz),
)[self.john.id]
self.assertEqual(data, {'days': 0.958, 'hours': 10})
# half days
leave = self.env['resource.calendar.leaves'].create({
'name': 'half',
'calendar_id': self.calendar_jean.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 14, 0, 0, tzinfo=self.jean.tz),
})
data = self.jean._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(data, {'days': 0.5, 'hours': 4})
leave.unlink()
# leave size 0
leave = self.env['resource.calendar.leaves'].create({
'name': 'zero',
'calendar_id': self.calendar_jean.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
})
data = self.jean._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(data, {'days': 0, 'hours': 0})
leave.unlink()
# leave very small size
leave = self.env['resource.calendar.leaves'].create({
'name': 'small',
'calendar_id': self.calendar_jean.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 10, 0, 1, tzinfo=self.jean.tz),
})
data = self.jean._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(data['days'], 0)
self.assertAlmostEqual(data['hours'], 0, 2)
leave.unlink()
def test_list_leaves(self):
jean_leave = self.env['resource.calendar.leaves'].create({
'name': "Jean's son is sick",
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': False,
'date_from': self.datetime_str(2018, 4, 10, 0, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 10, 23, 59, 59, tzinfo=self.jean.tz),
})
leaves = self.jean.list_leaves(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.jean.tz),
)
self.assertEqual(leaves, [(date(2018, 4, 10), 8, jean_leave)])
# half days
leave = self.env['resource.calendar.leaves'].create({
'name': 'half',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 14, 0, 0, tzinfo=self.jean.tz),
})
leaves = self.jean.list_leaves(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)
self.assertEqual(leaves, [(date(2018, 4, 2), 4, leave)])
leave.unlink()
# very small size
leave = self.env['resource.calendar.leaves'].create({
'name': 'small',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 10, 0, 1, tzinfo=self.jean.tz),
})
leaves = self.jean.list_leaves(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)
self.assertEqual(len(leaves), 1)
self.assertEqual(leaves[0][0], date(2018, 4, 2))
self.assertAlmostEqual(leaves[0][1], 0, 2)
self.assertEqual(leaves[0][2].id, leave.id)
leave.unlink()
# size 0
leave = self.env['resource.calendar.leaves'].create({
'name': 'zero',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
})
leaves = self.jean.list_leaves(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)
self.assertEqual(leaves, [])
leave.unlink()
def test_list_work_time_per_day(self):
working_time = self.john._list_work_time_per_day(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.john.tz),
)[self.john.id]
self.assertEqual(working_time, [
(date(2018, 4, 10), 8),
(date(2018, 4, 13), 12),
])
# change john's resource's timezone
self.john.resource_id.tz = 'Europe/Brussels'
self.assertEqual(self.john.tz, 'Europe/Brussels')
self.assertEqual(self.calendar_john.tz, 'America/Los_Angeles')
working_time = self.john._list_work_time_per_day(
self.datetime_tz(2018, 4, 9, 0, 0, 0, tzinfo=self.john.tz),
self.datetime_tz(2018, 4, 13, 23, 59, 59, tzinfo=self.john.tz),
)[self.john.id]
self.assertEqual(working_time, [
(date(2018, 4, 10), 8),
(date(2018, 4, 13), 12),
])
# half days
leave = self.env['resource.calendar.leaves'].create({
'name': 'small',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 14, 0, 0, tzinfo=self.jean.tz),
})
working_time = self.jean._list_work_time_per_day(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(working_time, [
(date(2018, 4, 2), 4),
(date(2018, 4, 3), 8),
(date(2018, 4, 4), 8),
(date(2018, 4, 5), 8),
(date(2018, 4, 6), 8),
])
leave.unlink()
# very small size
leave = self.env['resource.calendar.leaves'].create({
'name': 'small',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 10, 0, 1, tzinfo=self.jean.tz),
})
working_time = self.jean._list_work_time_per_day(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(len(working_time), 5)
self.assertEqual(working_time[0][0], date(2018, 4, 2))
self.assertAlmostEqual(working_time[0][1], 8, 2)
leave.unlink()
# size 0
leave = self.env['resource.calendar.leaves'].create({
'name': 'zero',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 2, 10, 0, 0, tzinfo=self.jean.tz),
})
working_time = self.jean._list_work_time_per_day(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jean.tz),
self.datetime_tz(2018, 4, 6, 23, 0, 0, tzinfo=self.jean.tz),
)[self.jean.id]
self.assertEqual(working_time, [
(date(2018, 4, 2), 8),
(date(2018, 4, 3), 8),
(date(2018, 4, 4), 8),
(date(2018, 4, 5), 8),
(date(2018, 4, 6), 8),
])
leave.unlink()

View file

@ -32,7 +32,7 @@ class TestResourcePerformance(TransactionCase):
start = pytz.utc.localize(datetime.now() + relativedelta(month=1, day=1))
stop = pytz.utc.localize(datetime.now() + relativedelta(month=12, day=31))
start_time = time.time()
calendar._attendance_intervals_batch(start, stop, resources=resources)
calendar._attendance_intervals_batch(start, stop, resources=resources.resource_id)
_logger.info('Attendance Intervals Batch (100): --- %s seconds ---', time.time() - start_time)
# Before
#INFO master test_performance: Attendance Intervals Batch (100): --- 2.0667169094085693 seconds ---

View file

@ -0,0 +1,28 @@
from odoo.exceptions import ValidationError
from odoo.addons.test_resource.tests.common import TestResourceCommon
class TestErrors(TestResourceCommon):
def setUp(self):
super().setUp()
def test_create_negative_leave(self):
# from > to
with self.assertRaises(ValidationError):
self.env['resource.calendar.leaves'].create({
'name': 'error cannot return in the past',
'resource_id': False,
'calendar_id': self.calendar_jean.id,
'date_from': self.datetime_str(2018, 4, 3, 20, 0, 0, tzinfo=self.jean.tz),
'date_to': self.datetime_str(2018, 4, 3, 0, 0, 0, tzinfo=self.jean.tz),
})
with self.assertRaises(ValidationError):
self.env['resource.calendar.leaves'].create({
'name': 'error caused by timezones',
'resource_id': False,
'calendar_id': self.calendar_jean.id,
'date_from': self.datetime_str(2018, 4, 3, 10, 0, 0, tzinfo='UTC'),
'date_to': self.datetime_str(2018, 4, 3, 12, 0, 0, tzinfo='Etc/GMT-6'),
})

View file

@ -0,0 +1,256 @@
from datetime import date, datetime
from freezegun import freeze_time
from pytz import utc
from odoo.addons.test_resource.tests.common import TestResourceCommon
class TestTimezones(TestResourceCommon):
def setUp(self):
super().setUp()
self.tz1 = 'Etc/GMT+6'
self.tz2 = 'Europe/Brussels'
self.tz3 = 'Etc/GMT-10'
self.tz4 = 'Etc/GMT+10'
def test_work_hours_count(self):
# When no timezone => UTC
count = self.calendar_jean.get_work_hours_count(
self.datetime_tz(2018, 4, 10, 8, 0, 0),
self.datetime_tz(2018, 4, 10, 12, 0, 0),
)
self.assertEqual(count, 4)
# This timezone is not the same as the calendar's one
count = self.calendar_jean.get_work_hours_count(
self.datetime_tz(2018, 4, 10, 8, 0, 0, tzinfo=self.tz1),
self.datetime_tz(2018, 4, 10, 12, 0, 0, tzinfo=self.tz1),
)
self.assertEqual(count, 0)
# Using two different timezones
# 10-04-2018 06:00:00 - 10-04-2018 02:00:00
count = self.calendar_jean.get_work_hours_count(
self.datetime_tz(2018, 4, 10, 8, 0, 0, tzinfo=self.tz2),
self.datetime_tz(2018, 4, 10, 12, 0, 0, tzinfo=self.tz3),
)
self.assertEqual(count, 0)
# Using two different timezones
# 2018-04-10 06:00:00 - 2018-04-10 22:00:00
count = self.calendar_jean.get_work_hours_count(
self.datetime_tz(2018, 4, 10, 8, 0, 0, tzinfo=self.tz2),
self.datetime_tz(2018, 4, 10, 12, 0, 0, tzinfo=self.tz4),
)
self.assertEqual(count, 8)
def test_plan_hours(self):
dt = self.calendar_jean.plan_hours(10, self.datetime_tz(2018, 4, 10, 8, 0, 0))
self.assertEqual(dt, self.datetime_tz(2018, 4, 11, 10, 0, 0))
dt = self.calendar_jean.plan_hours(10, self.datetime_tz(2018, 4, 10, 8, 0, 0, tzinfo=self.tz4))
self.assertEqual(dt, self.datetime_tz(2018, 4, 11, 22, 0, 0, tzinfo=self.tz4))
def test_plan_days(self):
dt = self.calendar_jean.plan_days(2, self.datetime_tz(2018, 4, 10, 8, 0, 0))
self.assertEqual(dt, self.datetime_tz(2018, 4, 11, 14, 0, 0))
# We lose one day because of timezone
dt = self.calendar_jean.plan_days(2, self.datetime_tz(2018, 4, 10, 8, 0, 0, tzinfo=self.tz4))
self.assertEqual(dt, self.datetime_tz(2018, 4, 12, 4, 0, 0, tzinfo=self.tz4))
def test_work_data(self):
# 09-04-2018 10:00:00 - 13-04-2018 18:00:00
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 9, 8, 0, 0),
self.datetime_tz(2018, 4, 13, 16, 0, 0),
)[self.jean.id]
self.assertEqual(data, {'days': 4.75, 'hours': 38})
# 09-04-2018 00:00:00 - 13-04-2018 08:00:00
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 9, 8, 0, 0, tzinfo=self.tz3),
self.datetime_tz(2018, 4, 13, 16, 0, 0, tzinfo=self.tz3),
)[self.jean.id]
self.assertEqual(data, {'days': 4, 'hours': 32})
# 09-04-2018 08:00:00 - 14-04-2018 12:00:00
data = self.jean._get_work_days_data_batch(
self.datetime_tz(2018, 4, 9, 8, 0, 0, tzinfo=self.tz2),
self.datetime_tz(2018, 4, 13, 16, 0, 0, tzinfo=self.tz4),
)[self.jean.id]
self.assertEqual(data, {'days': 5, 'hours': 40})
# Jules with 2 weeks calendar
# 02-04-2018 00:00:00 - 6-04-2018 23:59:59
data = self.jules._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jules.tz),
self.datetime_tz(2018, 4, 6, 23, 59, 59, tzinfo=self.jules.tz),
)[self.jules.id]
self.assertEqual(data, {'days': 4, 'hours': 30})
# Jules with 2 weeks calendar
# 02-04-2018 00:00:00 - 14-04-2018 23:59:59
data = self.jules._get_work_days_data_batch(
self.datetime_tz(2018, 4, 2, 0, 0, 0, tzinfo=self.jules.tz),
self.datetime_tz(2018, 4, 14, 23, 59, 59, tzinfo=self.jules.tz),
)[self.jules.id]
self.assertEqual(data, {'days': 6, 'hours': 46})
# Jules with 2 weeks calendar
# 12-29-2014 00:00:00 - 27-12-2019 23:59:59 => 261 weeks
# 130 weeks type 1: 131*4 = 524 days and 131*30 = 3930 hours
# 131 weeks type 2: 130*2 = 260 days and 130*16 = 2080 hours
data = self.jules._get_work_days_data_batch(
self.datetime_tz(2014, 12, 29, 0, 0, 0, tzinfo=self.jules.tz),
self.datetime_tz(2019, 12, 27, 23, 59, 59, tzinfo=self.jules.tz),
)[self.jules.id]
self.assertEqual(data, {'days': 784, 'hours': 6010})
def test_leave_data(self):
self.env['resource.calendar.leaves'].create({
'name': '',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 9, 8, 0, 0, tzinfo=self.tz2),
'date_to': self.datetime_str(2018, 4, 9, 14, 0, 0, tzinfo=self.tz2),
})
# 09-04-2018 10:00:00 - 13-04-2018 18:00:00
data = self.jean._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 9, 8, 0, 0),
self.datetime_tz(2018, 4, 13, 16, 0, 0),
)[self.jean.id]
self.assertEqual(data, {'days': 0.5, 'hours': 4})
# 09-04-2018 00:00:00 - 13-04-2018 08:00:00
data = self.jean._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 9, 8, 0, 0, tzinfo=self.tz3),
self.datetime_tz(2018, 4, 13, 16, 0, 0, tzinfo=self.tz3),
)[self.jean.id]
self.assertEqual(data, {'days': 0.75, 'hours': 6})
# 09-04-2018 08:00:00 - 14-04-2018 12:00:00
data = self.jean._get_leave_days_data_batch(
self.datetime_tz(2018, 4, 9, 8, 0, 0, tzinfo=self.tz2),
self.datetime_tz(2018, 4, 13, 16, 0, 0, tzinfo=self.tz4),
)[self.jean.id]
self.assertEqual(data, {'days': 0.75, 'hours': 6})
def test_leaves(self):
leave = self.env['resource.calendar.leaves'].create({
'name': '',
'calendar_id': self.jean.resource_calendar_id.id,
'resource_id': self.jean.resource_id.id,
'date_from': self.datetime_str(2018, 4, 9, 8, 0, 0, tzinfo=self.tz2),
'date_to': self.datetime_str(2018, 4, 9, 14, 0, 0, tzinfo=self.tz2),
})
# 09-04-2018 10:00:00 - 13-04-2018 18:00:00
leaves = self.jean.list_leaves(
self.datetime_tz(2018, 4, 9, 8, 0, 0),
self.datetime_tz(2018, 4, 13, 16, 0, 0),
)
self.assertEqual(leaves, [(date(2018, 4, 9), 4, leave)])
# 09-04-2018 00:00:00 - 13-04-2018 08:00:00
leaves = self.jean.list_leaves(
self.datetime_tz(2018, 4, 9, 8, 0, 0, tzinfo=self.tz3),
self.datetime_tz(2018, 4, 13, 16, 0, 0, tzinfo=self.tz3),
)
self.assertEqual(leaves, [(date(2018, 4, 9), 6, leave)])
# 09-04-2018 08:00:00 - 14-04-2018 12:00:00
leaves = self.jean.list_leaves(
self.datetime_tz(2018, 4, 9, 8, 0, 0, tzinfo=self.tz2),
self.datetime_tz(2018, 4, 13, 16, 0, 0, tzinfo=self.tz4),
)
self.assertEqual(leaves, [(date(2018, 4, 9), 6, leave)])
def test_works(self):
work = self.jean._list_work_time_per_day(
self.datetime_tz(2018, 4, 9, 8, 0, 0),
self.datetime_tz(2018, 4, 13, 16, 0, 0),
)[self.jean.id]
self.assertEqual(work, [
(date(2018, 4, 9), 6),
(date(2018, 4, 10), 8),
(date(2018, 4, 11), 8),
(date(2018, 4, 12), 8),
(date(2018, 4, 13), 8),
])
work = self.jean._list_work_time_per_day(
self.datetime_tz(2018, 4, 9, 8, 0, 0, tzinfo=self.tz3),
self.datetime_tz(2018, 4, 13, 16, 0, 0, tzinfo=self.tz3),
)[self.jean.id]
self.assertEqual(len(work), 4)
self.assertEqual(work, [
(date(2018, 4, 9), 8),
(date(2018, 4, 10), 8),
(date(2018, 4, 11), 8),
(date(2018, 4, 12), 8),
])
work = self.jean._list_work_time_per_day(
self.datetime_tz(2018, 4, 9, 8, 0, 0, tzinfo=self.tz2),
self.datetime_tz(2018, 4, 13, 16, 0, 0, tzinfo=self.tz4),
)[self.jean.id]
self.assertEqual(work, [
(date(2018, 4, 9), 8),
(date(2018, 4, 10), 8),
(date(2018, 4, 11), 8),
(date(2018, 4, 12), 8),
(date(2018, 4, 13), 8),
])
@freeze_time("2022-09-21 15:30:00", tz_offset=-10)
def test_unavailable_intervals(self):
resource = self.env['resource.resource'].create({
'name': 'resource',
'tz': self.tz3,
})
intervals = resource._get_unavailable_intervals(datetime(2022, 9, 21), datetime(2022, 9, 22))
self.assertEqual(next(iter(intervals.values())), [
(datetime(2022, 9, 21, 0, 0, tzinfo=utc), datetime(2022, 9, 21, 6, 0, tzinfo=utc)),
(datetime(2022, 9, 21, 10, 0, tzinfo=utc), datetime(2022, 9, 21, 11, 0, tzinfo=utc)),
(datetime(2022, 9, 21, 15, 0, tzinfo=utc), datetime(2022, 9, 22, 0, 0, tzinfo=utc)),
])
def test_flexible_resource_leave_interval(self):
"""
Test whole day off for a flexible resource.
The standard 8 - 17 leave should be converted to a whole day leave interval for the flexible resource.
"""
flexible_calendar = self.env['resource.calendar'].create({
'name': 'Flex Calendar',
'tz': 'UTC',
'flexible_hours': True,
'full_time_required_hours': 40,
'hours_per_day': 8,
'hours_per_week': 40,
})
flex_resource = self.env['resource.resource'].create({
'name': 'Test FlexResource',
'calendar_id': flexible_calendar.id,
})
self.env['resource.calendar.leaves'].create({
'name': 'Standard Time Off',
'calendar_id': flexible_calendar.id,
'resource_id': flex_resource.id,
'date_from': '2025-03-07 08:00:00',
'date_to': '2025-03-07 17:00:00',
})
start_dt = datetime(2025, 3, 7, 8, 0, 0, tzinfo=utc)
end_dt = datetime(2025, 3, 7, 16, 00, 00, 00, tzinfo=utc)
intervals = flexible_calendar._leave_intervals_batch(start_dt, end_dt, [flex_resource])
intervals_list = list(intervals[flex_resource.id])
self.assertEqual(len(intervals_list), 1, "There should be one leave interval")
interval = intervals_list[0]
self.assertEqual(interval[0], start_dt, "The start of the interval should be 08:00:00")
self.assertEqual(interval[1], end_dt, "The end of the interval should be 16:00:00")